From ae4b03163792f0b6e933933e5d37df87dc3fd566 Mon Sep 17 00:00:00 2001 From: mattab Date: Thu, 28 Mar 2013 12:42:39 +1300 Subject: Mass conversion of all files to the newly agreed coding standard: PSR 1/2 Converting Piwik core source files, PHP, JS, TPL, CSS More info: http://piwik.org/participate/coding-standards/ --- plugins/API/API.php | 3125 +++--- plugins/API/Controller.php | 168 +- plugins/API/css/styles.css | 47 +- plugins/API/templates/listAllAPI.tpl | 26 +- plugins/Actions/API.php | 991 +- plugins/Actions/Actions.php | 1099 +- plugins/Actions/Archiving.php | 950 +- plugins/Actions/ArchivingHelper.php | 939 +- plugins/Actions/Controller.php | 1007 +- plugins/Actions/templates/indexSiteSearch.tpl | 20 +- plugins/Annotations/API.php | 691 +- plugins/Annotations/AnnotationList.php | 824 +- plugins/Annotations/Annotations.php | 96 +- plugins/Annotations/Controller.php | 406 +- plugins/Annotations/templates/annotation.tpl | 83 +- .../Annotations/templates/annotationManager.tpl | 36 +- plugins/Annotations/templates/annotations.js | 1194 +-- plugins/Annotations/templates/annotations.tpl | 47 +- .../Annotations/templates/evolutionAnnotations.tpl | 18 +- plugins/Annotations/templates/styles.css | 216 +- plugins/AnonymizeIP/AnonymizeIP.php | 106 +- plugins/CoreAdminHome/API.php | 425 +- plugins/CoreAdminHome/Controller.php | 442 +- plugins/CoreAdminHome/CoreAdminHome.php | 214 +- plugins/CoreAdminHome/templates/generalSettings.js | 231 +- .../CoreAdminHome/templates/generalSettings.tpl | 456 +- plugins/CoreAdminHome/templates/header.tpl | 125 +- .../templates/jsTrackingGenerator.css | 67 +- .../CoreAdminHome/templates/jsTrackingGenerator.js | 661 +- .../templates/jsTrackingGenerator.tpl | 429 +- plugins/CoreAdminHome/templates/menu.css | 89 +- plugins/CoreAdminHome/templates/menu.tpl | 37 +- plugins/CoreAdminHome/templates/optOut.tpl | 49 +- plugins/CoreAdminHome/templates/styles.css | 165 +- plugins/CoreHome/Controller.php | 419 +- plugins/CoreHome/CoreHome.php | 162 +- .../DataTableRowAction/MultiRowEvolution.php | 134 +- .../CoreHome/DataTableRowAction/RowEvolution.php | 548 +- plugins/CoreHome/templates/autocomplete.js | 463 +- plugins/CoreHome/templates/broadcast.js | 449 +- plugins/CoreHome/templates/calendar.js | 1094 +- plugins/CoreHome/templates/cloud.css | 51 +- plugins/CoreHome/templates/cloud.tpl | 45 +- plugins/CoreHome/templates/datatable.css | 706 +- plugins/CoreHome/templates/datatable.js | 3739 ++++--- plugins/CoreHome/templates/datatable.tpl | 111 +- plugins/CoreHome/templates/datatable_actions.tpl | 101 +- .../templates/datatable_actions_recursive.tpl | 71 +- .../templates/datatable_actions_subdatable.tpl | 30 +- plugins/CoreHome/templates/datatable_cell.tpl | 23 +- plugins/CoreHome/templates/datatable_footer.tpl | 198 +- plugins/CoreHome/templates/datatable_js.tpl | 7 +- plugins/CoreHome/templates/datatable_manager.js | 356 +- plugins/CoreHome/templates/datatable_rowactions.js | 540 +- plugins/CoreHome/templates/date.js | 77 +- plugins/CoreHome/templates/donate.css | 156 +- plugins/CoreHome/templates/donate.js | 282 +- plugins/CoreHome/templates/donate.tpl | 117 +- plugins/CoreHome/templates/footer.tpl | 2 +- plugins/CoreHome/templates/graph.tpl | 74 +- plugins/CoreHome/templates/header.tpl | 40 +- plugins/CoreHome/templates/header_message.tpl | 54 +- plugins/CoreHome/templates/html_report_body.tpl | 142 +- plugins/CoreHome/templates/html_report_footer.tpl | 2 +- plugins/CoreHome/templates/html_report_header.tpl | 55 +- plugins/CoreHome/templates/iframe_buster_body.tpl | 6 +- .../CoreHome/templates/iframe_buster_header.tpl | 8 +- plugins/CoreHome/templates/index_before_menu.tpl | 6 +- plugins/CoreHome/templates/index_content.tpl | 27 +- plugins/CoreHome/templates/jqplot.css | 199 +- plugins/CoreHome/templates/jqplot.js | 2600 +++-- .../CoreHome/templates/jquery.ui.autocomplete.css | 61 +- plugins/CoreHome/templates/js_css_includes.tpl | 2 +- plugins/CoreHome/templates/js_disabled_notice.tpl | 4 +- plugins/CoreHome/templates/js_global_variables.tpl | 68 +- plugins/CoreHome/templates/logo.tpl | 15 +- plugins/CoreHome/templates/menu.js | 64 +- plugins/CoreHome/templates/menu.tpl | 26 +- plugins/CoreHome/templates/menu_init.js | 6 +- plugins/CoreHome/templates/misc.js | 319 +- plugins/CoreHome/templates/period_select.tpl | 56 +- plugins/CoreHome/templates/piwik_tag.tpl | 59 +- plugins/CoreHome/templates/popover.js | 399 +- .../templates/popover_multirowevolution.tpl | 66 +- .../CoreHome/templates/popover_rowevolution.tpl | 62 +- plugins/CoreHome/templates/promo_video.tpl | 248 +- .../CoreHome/templates/reports_by_dimension.tpl | 45 +- plugins/CoreHome/templates/sites_selection.tpl | 47 +- plugins/CoreHome/templates/sparkline.js | 104 +- plugins/CoreHome/templates/sparkline_footer.tpl | 10 +- plugins/CoreHome/templates/styles.css | 213 +- plugins/CoreHome/templates/tooltip.js | 216 +- plugins/CoreHome/templates/top_bar.tpl | 12 +- plugins/CoreHome/templates/top_bar_hello_menu.tpl | 10 +- plugins/CoreHome/templates/top_bar_top_menu.tpl | 21 +- plugins/CoreHome/templates/top_screen.tpl | 4 +- .../CoreHome/templates/warning_invalid_host.tpl | 9 +- plugins/CorePluginsAdmin/Controller.php | 129 +- plugins/CorePluginsAdmin/CorePluginsAdmin.php | 50 +- plugins/CorePluginsAdmin/templates/manage.tpl | 105 +- plugins/CoreUpdater/Controller.php | 694 +- plugins/CoreUpdater/CoreUpdater.php | 132 +- .../templates/cli_update_database_done.tpl | 80 +- .../CoreUpdater/templates/cli_update_welcome.tpl | 44 +- plugins/CoreUpdater/templates/header.tpl | 68 +- .../CoreUpdater/templates/update_database_done.tpl | 123 +- .../templates/update_new_version_available.tpl | 29 +- .../templates/update_one_click_results.tpl | 19 +- plugins/CoreUpdater/templates/update_welcome.tpl | 208 +- plugins/CustomVariables/API.php | 144 +- plugins/CustomVariables/Controller.php | 78 +- plugins/CustomVariables/CustomVariables.php | 631 +- plugins/DBStats/API.php | 584 +- plugins/DBStats/Controller.php | 717 +- plugins/DBStats/DBStats.php | 128 +- plugins/DBStats/MySQLMetadataProvider.php | 706 +- plugins/DBStats/templates/index.tpl | 231 +- plugins/Dashboard/Controller.php | 137 +- plugins/Dashboard/Dashboard.php | 285 +- plugins/Dashboard/templates/dashboard.css | 266 +- plugins/Dashboard/templates/dashboard.js | 62 +- plugins/Dashboard/templates/dashboardObject.js | 182 +- plugins/Dashboard/templates/dashboardWidget.js | 82 +- plugins/Dashboard/templates/header.tpl | 159 +- plugins/Dashboard/templates/index.tpl | 105 +- plugins/Dashboard/templates/standalone.tpl | 13 +- plugins/Dashboard/templates/widgetMenu.js | 320 +- plugins/DoNotTrack/DoNotTrack.php | 97 +- plugins/ExampleAPI/API.php | 323 +- plugins/ExampleAPI/ExampleAPI.php | 38 +- plugins/ExamplePlugin/Controller.php | 220 +- plugins/ExamplePlugin/ExamplePlugin.php | 120 +- .../ExamplePlugin/config/local.config.sample.php | 6 +- plugins/ExamplePlugin/lang/en.php | 20 +- plugins/ExamplePlugin/templates/piwikDownloads.tpl | 26 +- plugins/ExampleRssWidget/Controller.php | 7 +- plugins/ExampleRssWidget/ExampleRssWidget.php | 62 +- plugins/ExampleRssWidget/Rss.php | 23 +- plugins/ExampleRssWidget/templates/styles.css | 43 +- plugins/ExampleUI/API.php | 174 +- plugins/ExampleUI/Controller.php | 234 +- plugins/ExampleUI/ExampleUI.php | 87 +- plugins/Feedback/Controller.php | 110 +- plugins/Feedback/Feedback.php | 94 +- plugins/Feedback/templates/feedback.js | 58 +- plugins/Feedback/templates/index.tpl | 103 +- plugins/Feedback/templates/sent.tpl | 31 +- plugins/Feedback/templates/styles.css | 83 +- plugins/Goals/API.php | 1061 +- plugins/Goals/Controller.php | 1083 +- plugins/Goals/Goals.php | 1738 ++- plugins/Goals/templates/GoalForm.js | 230 +- plugins/Goals/templates/add_edit_goal.tpl | 109 +- plugins/Goals/templates/add_new_goal.tpl | 15 +- plugins/Goals/templates/form_add_goal.tpl | 180 +- plugins/Goals/templates/goals.css | 33 +- plugins/Goals/templates/list_goal_edit.tpl | 88 +- plugins/Goals/templates/list_top_dimension.tpl | 11 +- plugins/Goals/templates/overview.tpl | 73 +- plugins/Goals/templates/single_goal.tpl | 73 +- .../Goals/templates/title_and_evolution_graph.tpl | 109 +- plugins/ImageGraph/API.php | 1053 +- plugins/ImageGraph/Controller.php | 106 +- plugins/ImageGraph/ImageGraph.php | 273 +- plugins/ImageGraph/StaticGraph.php | 598 +- plugins/ImageGraph/StaticGraph/3DPie.php | 18 +- plugins/ImageGraph/StaticGraph/Evolution.php | 22 +- plugins/ImageGraph/StaticGraph/Exception.php | 86 +- plugins/ImageGraph/StaticGraph/GridGraph.php | 851 +- plugins/ImageGraph/StaticGraph/HorizontalBar.php | 346 +- plugins/ImageGraph/StaticGraph/Pie.php | 18 +- plugins/ImageGraph/StaticGraph/PieGraph.php | 217 +- plugins/ImageGraph/StaticGraph/VerticalBar.php | 32 +- .../templates/debug_graphs_all_sizes.tpl | 91 +- plugins/ImageGraph/templates/index.tpl | 4 +- plugins/Installation/Controller.php | 1913 ++-- plugins/Installation/FormDatabaseSetup.php | 528 +- plugins/Installation/FormFirstWebsiteSetup.php | 105 +- plugins/Installation/FormGeneralSetup.php | 113 +- plugins/Installation/Installation.php | 142 +- plugins/Installation/View.php | 79 +- plugins/Installation/templates/allSteps.tpl | 18 +- plugins/Installation/templates/databaseCheck.tpl | 44 +- plugins/Installation/templates/databaseSetup.tpl | 14 +- .../templates/displayJavascriptCode.tpl | 28 +- plugins/Installation/templates/finished.tpl | 2 +- .../Installation/templates/firstWebsiteSetup.tpl | 20 +- plugins/Installation/templates/generalSetup.tpl | 2 +- plugins/Installation/templates/install.css | 240 +- .../Installation/templates/integrityDetails.tpl | 75 +- plugins/Installation/templates/structure.tpl | 126 +- plugins/Installation/templates/systemCheck.tpl | 14 +- plugins/Installation/templates/systemCheckPage.css | 46 +- plugins/Installation/templates/systemCheckPage.tpl | 24 +- .../Installation/templates/systemCheckSection.tpl | 558 +- .../Installation/templates/systemCheck_legend.tpl | 20 +- plugins/Installation/templates/tablesCreation.tpl | 96 +- plugins/Installation/templates/welcome.tpl | 62 +- plugins/LanguagesManager/API.php | 322 +- plugins/LanguagesManager/Controller.php | 47 +- plugins/LanguagesManager/LanguagesManager.php | 440 +- .../LanguagesManager/templates/languageSelector.js | 82 +- plugins/LanguagesManager/templates/languages.tpl | 26 +- plugins/Live/API.php | 888 +- plugins/Live/Controller.php | 255 +- plugins/Live/Live.php | 92 +- plugins/Live/Visitor.php | 1010 +- plugins/Live/templates/index.tpl | 56 +- plugins/Live/templates/lastVisits.tpl | 145 +- plugins/Live/templates/live.css | 116 +- plugins/Live/templates/scripts/live.js | 128 +- plugins/Live/templates/simpleLastVisitCount.tpl | 232 +- plugins/Live/templates/totalVisits.tpl | 51 +- plugins/Live/templates/visitorLog.tpl | 581 +- plugins/Login/Auth.php | 177 +- plugins/Login/Controller.php | 972 +- plugins/Login/FormLogin.php | 36 +- plugins/Login/FormResetPassword.php | 32 +- plugins/Login/Login.php | 355 +- plugins/Login/templates/header.tpl | 114 +- plugins/Login/templates/login.css | 251 +- plugins/Login/templates/login.js | 191 +- plugins/Login/templates/login.tpl | 141 +- plugins/Login/templates/message.tpl | 14 +- plugins/MobileMessaging/API.php | 893 +- plugins/MobileMessaging/Controller.php | 91 +- plugins/MobileMessaging/CountryCallingCodes.php | 486 +- plugins/MobileMessaging/GSMCharset.php | 276 +- plugins/MobileMessaging/MobileMessaging.php | 621 +- .../MobileMessaging/ReportRenderer/Exception.php | 90 +- plugins/MobileMessaging/ReportRenderer/Sms.php | 198 +- plugins/MobileMessaging/SMSProvider.php | 283 +- plugins/MobileMessaging/SMSProvider/Clockwork.php | 163 +- .../SMSProvider/StubbedProvider.php | 24 +- .../scripts/MobileMessagingSettings.js | 337 +- .../MobileMessaging/templates/ReportParameters.tpl | 105 +- plugins/MobileMessaging/templates/SMSReport.tpl | 138 +- plugins/MobileMessaging/templates/Settings.tpl | 347 +- plugins/MultiSites/API.php | 930 +- plugins/MultiSites/Controller.php | 431 +- plugins/MultiSites/MultiSites.php | 157 +- plugins/MultiSites/templates/common.js | 411 +- plugins/MultiSites/templates/index.tpl | 191 +- plugins/MultiSites/templates/row.tpl | 37 +- plugins/MultiSites/templates/styles.css | 76 +- plugins/Overlay/API.php | 219 +- plugins/Overlay/Controller.php | 340 +- plugins/Overlay/Overlay.php | 46 +- plugins/Overlay/client/client.css | 161 +- plugins/Overlay/client/client.js | 518 +- plugins/Overlay/client/followingpages.js | 1018 +- plugins/Overlay/client/translations.js | 56 +- plugins/Overlay/client/urlnormalizer.js | 385 +- plugins/Overlay/templates/error_wrong_domain.tpl | 54 +- plugins/Overlay/templates/helper.js | 44 +- plugins/Overlay/templates/index.css | 131 +- plugins/Overlay/templates/index.js | 408 +- plugins/Overlay/templates/index.tpl | 60 +- plugins/Overlay/templates/index_noframe.tpl | 32 +- plugins/Overlay/templates/notify_parent_iframe.tpl | 16 +- plugins/Overlay/templates/rowaction.js | 68 +- plugins/Overlay/templates/sidebar.tpl | 34 +- plugins/PDFReports/API.php | 1430 ++- plugins/PDFReports/Controller.php | 102 +- plugins/PDFReports/PDFReports.php | 1208 +-- plugins/PDFReports/config/tcpdf_config.php | 432 +- plugins/PDFReports/templates/add.tpl | 260 +- plugins/PDFReports/templates/index.tpl | 62 +- plugins/PDFReports/templates/list.tpl | 182 +- plugins/PDFReports/templates/pdf.js | 242 +- plugins/PDFReports/templates/report_parameters.tpl | 187 +- plugins/PrivacyManager/Controller.php | 557 +- plugins/PrivacyManager/LogDataPurger.php | 615 +- plugins/PrivacyManager/PrivacyManager.php | 560 +- plugins/PrivacyManager/ReportsPurger.php | 733 +- plugins/PrivacyManager/templates/databaseSize.tpl | 4 +- .../PrivacyManager/templates/privacySettings.js | 300 +- .../PrivacyManager/templates/privacySettings.tpl | 469 +- plugins/Provider/API.php | 51 +- plugins/Provider/Controller.php | 53 +- plugins/Provider/Provider.php | 481 +- plugins/Provider/functions.php | 63 +- plugins/Proxy/Controller.php | 313 +- plugins/Proxy/Proxy.php | 38 +- plugins/Proxy/templates/exportImage.tpl | 19 +- plugins/Referers/API.php | 969 +- plugins/Referers/Controller.php | 1349 ++- plugins/Referers/Referers.php | 1153 +- plugins/Referers/functions.php | 262 +- .../Referers/templates/Websites_SocialNetworks.tpl | 8 +- plugins/Referers/templates/index.tpl | 128 +- .../Referers/templates/searchEngines_Keywords.tpl | 8 +- plugins/SEO/API.php | 153 +- plugins/SEO/Controller.php | 55 +- plugins/SEO/MajesticClient.php | 154 +- plugins/SEO/RankChecker.php | 379 +- plugins/SEO/SEO.php | 44 +- plugins/SEO/templates/index.tpl | 78 +- plugins/SEO/templates/rank.js | 2 +- plugins/SecurityInfo/Controller.php | 42 +- plugins/SecurityInfo/PhpSecInfo/PhpSecInfo.php | 976 +- .../PhpSecInfo/Test/Application/php.php | 77 +- .../PhpSecInfo/Test/Application/piwik.php | 55 +- .../PhpSecInfo/Test/CGI/force_redirect.php | 157 +- .../PhpSecInfo/Test/Core/allow_url_fopen.php | 100 +- .../PhpSecInfo/Test/Core/allow_url_include.php | 107 +- .../PhpSecInfo/Test/Core/display_errors.php | 85 +- .../PhpSecInfo/Test/Core/expose_php.php | 85 +- .../PhpSecInfo/Test/Core/file_uploads.php | 86 +- plugins/SecurityInfo/PhpSecInfo/Test/Core/gid.php | 112 +- .../PhpSecInfo/Test/Core/magic_quotes_gpc.php | 83 +- .../PhpSecInfo/Test/Core/memory_limit.php | 87 +- .../PhpSecInfo/Test/Core/open_basedir.php | 81 +- .../PhpSecInfo/Test/Core/post_max_size.php | 68 +- .../PhpSecInfo/Test/Core/register_globals.php | 83 +- plugins/SecurityInfo/PhpSecInfo/Test/Core/uid.php | 114 +- .../PhpSecInfo/Test/Core/upload_max_filesize.php | 84 +- .../PhpSecInfo/Test/Core/upload_tmp_dir.php | 133 +- .../PhpSecInfo/Test/Curl/file_support.php | 76 +- .../PhpSecInfo/Test/Session/save_path.php | 165 +- .../PhpSecInfo/Test/Session/use_trans_sid.php | 63 +- .../PhpSecInfo/Test/Suhosin/extension.php | 39 +- .../SecurityInfo/PhpSecInfo/Test/Suhosin/patch.php | 67 +- plugins/SecurityInfo/PhpSecInfo/Test/Test.php | 1087 +- .../PhpSecInfo/Test/Test_Application.php | 71 +- plugins/SecurityInfo/PhpSecInfo/Test/Test_Cgi.php | 78 +- plugins/SecurityInfo/PhpSecInfo/Test/Test_Core.php | 50 +- plugins/SecurityInfo/PhpSecInfo/Test/Test_Curl.php | 80 +- .../SecurityInfo/PhpSecInfo/Test/Test_Session.php | 52 +- .../SecurityInfo/PhpSecInfo/Test/Test_Suhosin.php | 65 +- plugins/SecurityInfo/SecurityInfo.php | 56 +- plugins/SecurityInfo/templates/index.tpl | 41 +- plugins/SitesManager/API.php | 2655 +++-- plugins/SitesManager/Controller.php | 359 +- plugins/SitesManager/SitesManager.php | 408 +- .../templates/DisplayAlternativeTags.tpl | 203 +- .../templates/DisplayJavascriptCode.tpl | 109 +- plugins/SitesManager/templates/SitesManager.js | 551 +- plugins/SitesManager/templates/SitesManager.tpl | 648 +- plugins/Transitions/API.php | 582 +- plugins/Transitions/Controller.php | 150 +- plugins/Transitions/Transitions.php | 691 +- plugins/Transitions/templates/transitions.css | 197 +- plugins/Transitions/templates/transitions.js | 2380 ++--- plugins/Transitions/templates/transitions.tpl | 91 +- plugins/UserCountry/API.php | 381 +- plugins/UserCountry/Controller.php | 957 +- plugins/UserCountry/GeoIPAutoUpdater.php | 1124 +- plugins/UserCountry/LocationProvider.php | 883 +- plugins/UserCountry/LocationProvider/Default.php | 182 +- plugins/UserCountry/LocationProvider/GeoIp.php | 495 +- .../UserCountry/LocationProvider/GeoIp/Pecl.php | 641 +- plugins/UserCountry/LocationProvider/GeoIp/Php.php | 651 +- .../LocationProvider/GeoIp/ServerBased.php | 547 +- plugins/UserCountry/UserCountry.php | 1012 +- plugins/UserCountry/functions.php | 180 +- plugins/UserCountry/templates/admin.js | 298 +- plugins/UserCountry/templates/adminIndex.tpl | 199 +- plugins/UserCountry/templates/index.tpl | 29 +- plugins/UserCountry/templates/styles.css | 59 +- plugins/UserCountry/templates/updaterSetup.tpl | 5 +- plugins/UserCountryMap/Controller.php | 392 +- plugins/UserCountryMap/UserCountryMap.php | 42 +- plugins/UserCountryMap/css/map.css | 7 +- plugins/UserCountryMap/css/qtip.css | 218 +- plugins/UserCountryMap/css/realtime-map.css | 45 +- plugins/UserCountryMap/css/visitor-map.css | 31 +- plugins/UserCountryMap/js/realtime-map.js | 132 +- plugins/UserCountryMap/js/vendor/chroma.min.js | 680 +- .../UserCountryMap/js/vendor/jquery.qtip.min.js | 364 +- plugins/UserCountryMap/js/vendor/kartograph.js | 10638 ++++++++++--------- plugins/UserCountryMap/js/vendor/kartograph.min.js | 1674 ++- plugins/UserCountryMap/js/vendor/kmeans.js | 256 +- plugins/UserCountryMap/js/vendor/raphael-min.js | 2215 +++- plugins/UserCountryMap/js/visitor-map.js | 759 +- plugins/UserCountryMap/templates/realtime-map.tpl | 60 +- plugins/UserCountryMap/templates/visitor-map.tpl | 77 +- plugins/UserSettings/API.php | 372 +- plugins/UserSettings/Controller.php | 360 +- plugins/UserSettings/UserSettings.php | 859 +- plugins/UserSettings/functions.php | 314 +- plugins/UserSettings/templates/index.tpl | 42 +- plugins/UsersManager/API.php | 1307 ++- plugins/UsersManager/Controller.php | 675 +- plugins/UsersManager/UsersManager.php | 245 +- plugins/UsersManager/templates/UsersManager.js | 315 +- plugins/UsersManager/templates/UsersManager.tpl | 251 +- plugins/UsersManager/templates/userSettings.js | 99 +- plugins/UsersManager/templates/userSettings.tpl | 225 +- plugins/VisitFrequency/API.php | 258 +- plugins/VisitFrequency/Controller.php | 170 +- plugins/VisitFrequency/VisitFrequency.php | 234 +- plugins/VisitFrequency/templates/index.tpl | 4 +- plugins/VisitFrequency/templates/sparklines.tpl | 13 +- plugins/VisitTime/API.php | 285 +- plugins/VisitTime/Controller.php | 181 +- plugins/VisitTime/VisitTime.php | 416 +- plugins/VisitTime/templates/index.tpl | 8 +- plugins/VisitorGenerator/Controller.php | 278 +- plugins/VisitorGenerator/VisitorGenerator.php | 50 +- plugins/VisitorGenerator/templates/generate.tpl | 19 +- plugins/VisitorGenerator/templates/index.tpl | 58 +- plugins/VisitorInterest/API.php | 208 +- plugins/VisitorInterest/Controller.php | 196 +- plugins/VisitorInterest/VisitorInterest.php | 633 +- plugins/VisitorInterest/templates/index.tpl | 1 - plugins/VisitsSummary/API.php | 274 +- plugins/VisitsSummary/Controller.php | 301 +- plugins/VisitsSummary/VisitsSummary.php | 112 +- plugins/VisitsSummary/templates/sparklines.tpl | 102 +- plugins/Widgetize/Controller.php | 120 +- plugins/Widgetize/Widgetize.php | 112 +- plugins/Widgetize/templates/iframe.tpl | 24 +- plugins/Widgetize/templates/index.tpl | 165 +- plugins/Widgetize/templates/js.tpl | 36 +- plugins/Widgetize/templates/test_jsinclude.tpl | 7 +- plugins/Widgetize/templates/widgetize.js | 148 +- 417 files changed, 66577 insertions(+), 63470 deletions(-) (limited to 'plugins') diff --git a/plugins/API/API.php b/plugins/API/API.php index 0181bd6996..211122da30 100644 --- a/plugins/API/API.php +++ b/plugins/API/API.php @@ -13,56 +13,58 @@ /** * @package Piwik_API */ -class Piwik_API extends Piwik_Plugin { - - public function getInformation() - { - return array( - 'description' => Piwik_Translate('API_PluginDescription'), - 'author' => 'Piwik', - 'author_homepage' => 'http://piwik.org/', - 'version' => Piwik_Version::VERSION, - ); - } - - public function getListHooksRegistered() - { - return array( - 'AssetManager.getCssFiles' => 'getCssFiles', - 'TopMenu.add' => 'addTopMenu', - ); - } - - public function addTopMenu() - { - $apiUrlParams = array('module' => 'API', 'action' => 'listAllAPI'); - $tooltip = Piwik_Translate('API_TopLinkTooltip'); - - Piwik_AddTopMenu('General_API', $apiUrlParams, true, 7, $isHTML = false, $tooltip); - - $this->addTopMenuMobileApp(); - } - - protected function addTopMenuMobileApp() - { - if (empty($_SERVER['HTTP_USER_AGENT'])) { - return; - } - require_once PIWIK_INCLUDE_PATH . '/libs/UserAgentParser/UserAgentParser.php'; - $os = UserAgentParser::getOperatingSystem($_SERVER['HTTP_USER_AGENT']); - if ($os && in_array($os['id'], array('AND', 'IPD', 'IPA', 'IPH'))) { - Piwik_AddTopMenu('Piwik Mobile App', array('module' => 'Proxy', 'action' => 'redirect', 'url' => 'http://piwik.org/mobile/'), true, 4); - } - } - - /** - * @param Piwik_Event_Notification $notification notification object - */ - public function getCssFiles($notification) { - $cssFiles = &$notification->getNotificationObject(); - - $cssFiles[] = "plugins/API/css/styles.css"; - } +class Piwik_API extends Piwik_Plugin +{ + + public function getInformation() + { + return array( + 'description' => Piwik_Translate('API_PluginDescription'), + 'author' => 'Piwik', + 'author_homepage' => 'http://piwik.org/', + 'version' => Piwik_Version::VERSION, + ); + } + + public function getListHooksRegistered() + { + return array( + 'AssetManager.getCssFiles' => 'getCssFiles', + 'TopMenu.add' => 'addTopMenu', + ); + } + + public function addTopMenu() + { + $apiUrlParams = array('module' => 'API', 'action' => 'listAllAPI'); + $tooltip = Piwik_Translate('API_TopLinkTooltip'); + + Piwik_AddTopMenu('General_API', $apiUrlParams, true, 7, $isHTML = false, $tooltip); + + $this->addTopMenuMobileApp(); + } + + protected function addTopMenuMobileApp() + { + if (empty($_SERVER['HTTP_USER_AGENT'])) { + return; + } + require_once PIWIK_INCLUDE_PATH . '/libs/UserAgentParser/UserAgentParser.php'; + $os = UserAgentParser::getOperatingSystem($_SERVER['HTTP_USER_AGENT']); + if ($os && in_array($os['id'], array('AND', 'IPD', 'IPA', 'IPH'))) { + Piwik_AddTopMenu('Piwik Mobile App', array('module' => 'Proxy', 'action' => 'redirect', 'url' => 'http://piwik.org/mobile/'), true, 4); + } + } + + /** + * @param Piwik_Event_Notification $notification notification object + */ + public function getCssFiles($notification) + { + $cssFiles = & $notification->getNotificationObject(); + + $cssFiles[] = "plugins/API/css/styles.css"; + } } @@ -85,1619 +87,1488 @@ class Piwik_API extends Piwik_Plugin { */ class Piwik_API_API { - static private $instance = null; - - /** - * @return Piwik_API_API - */ - static public function getInstance() - { - if (self::$instance == null) - { - self::$instance = new self; - } - return self::$instance; - } - - /** - * Get Piwik version - * @return string - */ - public function getPiwikVersion() - { - Piwik::checkUserHasSomeViewAccess(); - return Piwik_Version::VERSION; - } - - /** - * Returns the section [APISettings] if defined in config.ini.php - * @return array - */ - public function getSettings() - { - return Piwik_Config::getInstance()->APISettings; - } - - /** - * Derive the unit name from a column name - * @param $column - * @param $idSite - * @return string - * @ignore - */ - static public function getUnit($column, $idSite) - { - $nameToUnit = array( - '_rate' => '%', - 'revenue' => Piwik::getCurrency($idSite), - '_time_' => 's' - ); - - foreach ($nameToUnit as $pattern => $type) - { - if (strpos($column, $pattern) !== false) - { - return $type; - } - } - - return ''; - } - - /** - * Is a lower value for a given column better? - * @param $column - * @return bool - * - * @ignore - */ - static public function isLowerValueBetter($column) - { - $lowerIsBetterPatterns = array( - 'bounce', 'exit' - ); - - foreach ($lowerIsBetterPatterns as $pattern) - { - if (strpos($column, $pattern) !== false) - { - return true; - } - } - - return false; - } - - /** - * Default translations for many core metrics. - * This is used for exports with translated labels. The exports contain columns that - * are not visible in the UI and not present in the API meta data. These columns are - * translated here. - * @return array - */ - static public function getDefaultMetricTranslations() - { - $trans = array( - 'label' => 'General_ColumnLabel', - 'date' => 'General_Date', - 'avg_time_on_page' => 'General_ColumnAverageTimeOnPage', - 'sum_time_spent' => 'General_ColumnSumVisitLength', - 'sum_visit_length' => 'General_ColumnSumVisitLength', - 'bounce_count' => 'General_ColumnBounces', - 'bounce_count_returning' => 'VisitFrequency_ColumnBounceCountForReturningVisits', - 'max_actions' => 'General_ColumnMaxActions', - 'max_actions_returning' => 'VisitFrequency_ColumnMaxActionsInReturningVisit', - 'nb_visits_converted_returning' => 'VisitFrequency_ColumnNbReturningVisitsConverted', - 'sum_visit_length_returning' => 'VisitFrequency_ColumnSumVisitLengthReturning', - 'nb_visits_converted' => 'General_ColumnVisitsWithConversions', - 'nb_conversions' => 'Goals_ColumnConversions', - 'revenue' => 'Goals_ColumnRevenue', - 'nb_hits' => 'General_ColumnPageviews', - 'entry_nb_visits' => 'General_ColumnEntrances', - 'entry_nb_uniq_visitors' => 'General_ColumnUniqueEntrances', - 'exit_nb_visits' => 'General_ColumnExits', - 'exit_nb_uniq_visitors' => 'General_ColumnUniqueExits', - 'entry_bounce_count' => 'General_ColumnBounces', - 'exit_bounce_count' => 'General_ColumnBounces', - 'exit_rate' => 'General_ColumnExitRate' - ); - - $trans = array_map('Piwik_Translate', $trans); - - $dailySum = ' ('.Piwik_Translate('General_DailySum').')'; - $afterEntry = ' '.Piwik_Translate('General_AfterEntry'); - - $trans['sum_daily_nb_uniq_visitors'] = Piwik_Translate('General_ColumnNbUniqVisitors').$dailySum; - $trans['sum_daily_entry_nb_uniq_visitors'] = Piwik_Translate('General_ColumnUniqueEntrances').$dailySum; - $trans['sum_daily_exit_nb_uniq_visitors'] = Piwik_Translate('General_ColumnUniqueExits').$dailySum; - $trans['entry_nb_actions'] = Piwik_Translate('General_ColumnNbActions').$afterEntry; - $trans['entry_sum_visit_length'] = Piwik_Translate('General_ColumnSumVisitLength').$afterEntry; - - $api = self::getInstance(); - $trans = array_merge($api->getDefaultMetrics(), $api->getDefaultProcessedMetrics(), $trans); - - return $trans; - } - - public function getDefaultMetrics() - { - $translations = array( - // Standard metrics - 'nb_visits' => 'General_ColumnNbVisits', - 'nb_uniq_visitors' => 'General_ColumnNbUniqVisitors', - 'nb_actions' => 'General_ColumnNbActions', + static private $instance = null; + + /** + * @return Piwik_API_API + */ + static public function getInstance() + { + if (self::$instance == null) { + self::$instance = new self; + } + return self::$instance; + } + + /** + * Get Piwik version + * @return string + */ + public function getPiwikVersion() + { + Piwik::checkUserHasSomeViewAccess(); + return Piwik_Version::VERSION; + } + + /** + * Returns the section [APISettings] if defined in config.ini.php + * @return array + */ + public function getSettings() + { + return Piwik_Config::getInstance()->APISettings; + } + + /** + * Derive the unit name from a column name + * @param $column + * @param $idSite + * @return string + * @ignore + */ + static public function getUnit($column, $idSite) + { + $nameToUnit = array( + '_rate' => '%', + 'revenue' => Piwik::getCurrency($idSite), + '_time_' => 's' + ); + + foreach ($nameToUnit as $pattern => $type) { + if (strpos($column, $pattern) !== false) { + return $type; + } + } + + return ''; + } + + /** + * Is a lower value for a given column better? + * @param $column + * @return bool + * + * @ignore + */ + static public function isLowerValueBetter($column) + { + $lowerIsBetterPatterns = array( + 'bounce', 'exit' + ); + + foreach ($lowerIsBetterPatterns as $pattern) { + if (strpos($column, $pattern) !== false) { + return true; + } + } + + return false; + } + + /** + * Default translations for many core metrics. + * This is used for exports with translated labels. The exports contain columns that + * are not visible in the UI and not present in the API meta data. These columns are + * translated here. + * @return array + */ + static public function getDefaultMetricTranslations() + { + $trans = array( + 'label' => 'General_ColumnLabel', + 'date' => 'General_Date', + 'avg_time_on_page' => 'General_ColumnAverageTimeOnPage', + 'sum_time_spent' => 'General_ColumnSumVisitLength', + 'sum_visit_length' => 'General_ColumnSumVisitLength', + 'bounce_count' => 'General_ColumnBounces', + 'bounce_count_returning' => 'VisitFrequency_ColumnBounceCountForReturningVisits', + 'max_actions' => 'General_ColumnMaxActions', + 'max_actions_returning' => 'VisitFrequency_ColumnMaxActionsInReturningVisit', + 'nb_visits_converted_returning' => 'VisitFrequency_ColumnNbReturningVisitsConverted', + 'sum_visit_length_returning' => 'VisitFrequency_ColumnSumVisitLengthReturning', + 'nb_visits_converted' => 'General_ColumnVisitsWithConversions', + 'nb_conversions' => 'Goals_ColumnConversions', + 'revenue' => 'Goals_ColumnRevenue', + 'nb_hits' => 'General_ColumnPageviews', + 'entry_nb_visits' => 'General_ColumnEntrances', + 'entry_nb_uniq_visitors' => 'General_ColumnUniqueEntrances', + 'exit_nb_visits' => 'General_ColumnExits', + 'exit_nb_uniq_visitors' => 'General_ColumnUniqueExits', + 'entry_bounce_count' => 'General_ColumnBounces', + 'exit_bounce_count' => 'General_ColumnBounces', + 'exit_rate' => 'General_ColumnExitRate' + ); + + $trans = array_map('Piwik_Translate', $trans); + + $dailySum = ' (' . Piwik_Translate('General_DailySum') . ')'; + $afterEntry = ' ' . Piwik_Translate('General_AfterEntry'); + + $trans['sum_daily_nb_uniq_visitors'] = Piwik_Translate('General_ColumnNbUniqVisitors') . $dailySum; + $trans['sum_daily_entry_nb_uniq_visitors'] = Piwik_Translate('General_ColumnUniqueEntrances') . $dailySum; + $trans['sum_daily_exit_nb_uniq_visitors'] = Piwik_Translate('General_ColumnUniqueExits') . $dailySum; + $trans['entry_nb_actions'] = Piwik_Translate('General_ColumnNbActions') . $afterEntry; + $trans['entry_sum_visit_length'] = Piwik_Translate('General_ColumnSumVisitLength') . $afterEntry; + + $api = self::getInstance(); + $trans = array_merge($api->getDefaultMetrics(), $api->getDefaultProcessedMetrics(), $trans); + + return $trans; + } + + public function getDefaultMetrics() + { + $translations = array( + // Standard metrics + 'nb_visits' => 'General_ColumnNbVisits', + 'nb_uniq_visitors' => 'General_ColumnNbUniqVisitors', + 'nb_actions' => 'General_ColumnNbActions', // Do not display these in reports, as they are not so relevant // They are used to process metrics below // 'nb_visits_converted' => 'General_ColumnVisitsWithConversions', // 'max_actions' => 'General_ColumnMaxActions', // 'sum_visit_length' => 'General_ColumnSumVisitLength', // 'bounce_count' - ); - $translations = array_map('Piwik_Translate', $translations); - return $translations; - } - - public function getDefaultProcessedMetrics() - { - $translations = array( - // Processed in AddColumnsProcessedMetrics - 'nb_actions_per_visit' => 'General_ColumnActionsPerVisit', - 'avg_time_on_site' => 'General_ColumnAvgTimeOnSite', - 'bounce_rate' => 'General_ColumnBounceRate', - 'conversion_rate' => 'General_ColumnConversionRate', - ); - return array_map('Piwik_Translate', $translations); - } - - public function getDefaultMetricsDocumentation() - { - $documentation = array( - 'nb_visits' => 'General_ColumnNbVisitsDocumentation', - 'nb_uniq_visitors' => 'General_ColumnNbUniqVisitorsDocumentation', - 'nb_actions' => 'General_ColumnNbActionsDocumentation', - 'nb_actions_per_visit' => 'General_ColumnActionsPerVisitDocumentation', - 'avg_time_on_site' => 'General_ColumnAvgTimeOnSiteDocumentation', - 'bounce_rate' => 'General_ColumnBounceRateDocumentation', - 'conversion_rate' => 'General_ColumnConversionRateDocumentation', - 'avg_time_on_page' => 'General_ColumnAverageTimeOnPageDocumentation', - 'nb_hits' => 'General_ColumnPageviewsDocumentation', - 'exit_rate' => 'General_ColumnExitRateDocumentation' - ); - return array_map('Piwik_Translate', $documentation); - } - - public function getSegmentsMetadata($idSites = array(), $_hideImplementationData = true) - { - $segments = array(); - Piwik_PostEvent('API.getSegmentsMetadata', $segments, $idSites); - - $segments[] = array( - 'type' => 'dimension', - 'category' => '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), - ); - $segments[] = array( - 'type' => 'dimension', - 'category' => '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'), - ); - $segments[] = array( - 'type' => 'metric', - 'category' => 'Visit', - 'name' => 'General_NbActions', - 'segment' => 'actions', - 'sqlSegment' => 'log_visit.visit_total_actions', - ); - $segments[] = array( - 'type' => 'metric', - 'category' => 'Visit', - 'name' => 'General_NbSearches', - 'segment' => 'searches', - 'sqlSegment' => 'log_visit.visit_total_searches', - 'acceptedValues' => 'To select all visits who used internal Site Search, use: &segment=searches>0', - ); - $segments[] = array( - 'type' => 'metric', - 'category' => 'Visit', - 'name' => 'General_ColumnVisitDuration', - 'segment' => 'visitDuration', - 'sqlSegment' => 'log_visit.visit_total_time', - ); - $segments[] = array( - 'type' => 'dimension', - 'category' => 'Visit', - 'name' => Piwik_Translate('General_VisitType') . ". ".Piwik_Translate('General_VisitTypeExample', '"&segment=visitorType==returning,visitorType==returningCustomer"'), - 'segment' => 'visitorType', - 'acceptedValues' => 'new, returning, returningCustomer', - 'sqlSegment' => 'log_visit.visitor_returning', - 'sqlFilter' => create_function('$type', 'return $type == "new" ? 0 : ($type == "returning" ? 1 : 2);'), - ); - $segments[] = array( - 'type' => 'metric', - 'category' => 'Visit', - 'name' => 'General_DaysSinceLastVisit', - 'segment' => 'daysSinceLastVisit', - 'sqlSegment' => 'log_visit.visitor_days_since_last', - ); - $segments[] = array( - 'type' => 'metric', - 'category' => 'Visit', - 'name' => 'General_DaysSinceFirstVisit', - 'segment' => 'daysSinceFirstVisit', - 'sqlSegment' => 'log_visit.visitor_days_since_first', - ); - $segments[] = array( - 'type' => 'metric', - 'category' => 'Visit', - 'name' => 'General_NumberOfVisits', - 'segment' => 'visitCount', - 'sqlSegment' => 'log_visit.visitor_count_visits', - ); - - $segments[] = array( - 'type' => 'dimension', - 'category' => 'Visit', - 'name' => 'General_VisitConvertedGoal', - 'segment' => 'visitConverted', - 'acceptedValues' => '0, 1', - 'sqlSegment' => 'log_visit.visit_goal_converted', - ); - - $segments[] = array( - 'type' => 'dimension', - 'category' => 'Visit', - 'name' => Piwik_Translate('General_EcommerceVisitStatus', '"&segment=visitEcommerceStatus==ordered,visitEcommerceStatus==orderedThenAbandonedCart"'), - 'segment' => 'visitEcommerceStatus', - 'acceptedValues' => implode(", ", self::$visitEcommerceStatus), - 'sqlSegment' => 'log_visit.visit_goal_buyer', - 'sqlFilter' => array('Piwik_API_API', 'getVisitEcommerceStatus'), - ); - - $segments[] = array( - 'type' => 'metric', - 'category' => 'Visit', - 'name' => 'General_DaysSinceLastEcommerceOrder', - 'segment' => 'daysSinceLastEcommerceOrder', - 'sqlSegment' => 'log_visit.visitor_days_since_order', - ); - - foreach ($segments as &$segment) - { - $segment['name'] = Piwik_Translate($segment['name']); - $segment['category'] = Piwik_Translate($segment['category']); - - if($_hideImplementationData) - { - unset($segment['sqlFilter']); - unset($segment['sqlSegment']); - } - } - - usort($segments, array($this, 'sortSegments')); - return $segments; - } - - static protected $visitEcommerceStatus = array( - Piwik_Tracker_GoalManager::TYPE_BUYER_NONE => 'none', - Piwik_Tracker_GoalManager::TYPE_BUYER_ORDERED => 'ordered', - Piwik_Tracker_GoalManager::TYPE_BUYER_OPEN_CART => 'abandonedCart', - Piwik_Tracker_GoalManager::TYPE_BUYER_ORDERED_AND_OPEN_CART => 'orderedThenAbandonedCart', - ); - - /** - * @ignore - */ - static public function getVisitEcommerceStatusFromId($id) - { - if(!isset(self::$visitEcommerceStatus[$id])) - { - throw new Exception("Unexpected ECommerce status value "); - } - return self::$visitEcommerceStatus[$id]; - } - - /** - * @ignore - */ - static public function getVisitEcommerceStatus($status) - { - $id = array_search($status, self::$visitEcommerceStatus); - if($id === false) - { - throw new Exception("Invalid 'visitEcommerceStatus' segment value"); - } - return $id; - } - - private function sortSegments($row1, $row2) - { - $columns = array('type', 'category', 'name', 'segment'); - foreach($columns as $column) - { - // Keep segments ordered alphabetically inside categories.. - $type = -1; - if($column == 'name') $type = 1; - $compare = $type * strcmp($row1[$column], $row2[$column]); - - // hack so that custom variables "page" are grouped together in the doc - if($row1['category'] == Piwik_Translate('CustomVariables_CustomVariables') - && $row1['category'] == $row2['category']) { - $compare = strcmp($row1['segment'], $row2['segment']); - return $compare; - } - if($compare != 0){ - return $compare; - } - } - return $compare; - } - - /** - * Returns the url to application logo (~280x110px) - * - * @param bool $pathOnly If true, returns path relative to doc root. Otherwise, returns a URL. - * @return string - */ - public function getLogoUrl($pathOnly=false) - { - $logo = 'themes/default/images/logo.png'; - if(Piwik_Config::getInstance()->branding['use_custom_logo'] == 1 - && file_exists(Piwik_Common::getPathToPiwikRoot() .'/themes/logo.png')) - { - $logo = 'themes/logo.png'; - } - if(!$pathOnly) { - return Piwik::getPiwikUrl() . $logo; - } - return Piwik_Common::getPathToPiwikRoot() .'/'. $logo; - } - - /** - * Returns the url to header logo (~127x50px) - * - * @param bool $pathOnly If true, returns path relative to doc root. Otherwise, returns a URL. - * @return string - */ - public function getHeaderLogoUrl($pathOnly=false) - { - $logo = 'themes/default/images/logo-header.png'; - if(Piwik_Config::getInstance()->branding['use_custom_logo'] == 1 - && file_exists(Piwik_Common::getPathToPiwikRoot() .'/themes/logo-header.png')) - { - $logo = 'themes/logo-header.png'; - } - if(!$pathOnly) { - return Piwik::getPiwikUrl() . $logo; - } - return Piwik_Common::getPathToPiwikRoot() .'/'. $logo; - } - - /** - * Returns the URL to application SVG Logo - * - * @param bool $pathOnly If true, returns path relative to doc root. Otherwise, returns a URL. - * @return string - */ - public function getSVGLogoUrl($pathOnly=false) - { - $logo = 'themes/default/images/logo.svg'; - if(Piwik_Config::getInstance()->branding['use_custom_logo'] == 1 - && file_exists(Piwik_Common::getPathToPiwikRoot() .'/themes/logo.svg')) - { - $logo = 'themes/logo.svg'; - } - if(!$pathOnly) { - return Piwik::getPiwikUrl() . $logo; - } - return Piwik_Common::getPathToPiwikRoot() .'/'. $logo; - } - - /** - * Returns whether there is an SVG Logo available. - * - * @return bool - */ - public function hasSVGLogo() { - if (Piwik_Config::getInstance()->branding['use_custom_logo'] == 0) { - /* We always have our application logo */ - return true; - } else if(Piwik_Config::getInstance()->branding['use_custom_logo'] == 1 - && file_exists(Piwik_Common::getPathToPiwikRoot() .'/themes/logo.svg')) - { - return true; - } - - return false; - } - - /** - * Loads reports metadata, then return the requested one, - * matching optional API parameters. - */ - public function getMetadata($idSite, $apiModule, $apiAction, $apiParameters = array(), $language = false, - $period = false, $date = false, $hideMetricsDoc = false, $showSubtableReports = false) - { - Piwik_Translate::getInstance()->reloadLanguage($language); - $reportsMetadata = $this->getReportMetadata($idSite, $period, $date, $hideMetricsDoc, $showSubtableReports); - - foreach($reportsMetadata as $report) - { - // See ArchiveProcessing/Period.php - unique visitors are not processed for period != day - if(($period && $period != 'day') && !($apiModule == 'VisitsSummary' && $apiAction == 'get')) - { - unset($report['metrics']['nb_uniq_visitors']); - } - if($report['module'] == $apiModule - && $report['action'] == $apiAction) - { - // No custom parameters - if(empty($apiParameters) - && empty($report['parameters'])) - { - return array($report); - } - if(empty($report['parameters'])) - { - continue; - } - $diff = array_diff($report['parameters'], $apiParameters); - if(empty($diff)) - { - return array($report); - } - } - } - return false; - } - - /** - * Triggers a hook to ask plugins for available Reports. - * Returns metadata information about each report (category, name, dimension, metrics, etc.) - * - * @param string $idSites Comma separated list of website Ids - * @return array - */ - public function getReportMetadata($idSites = '', $period = false, $date = false, $hideMetricsDoc = false, - $showSubtableReports = false) - { - $idSites = Piwik_Site::getIdSitesFromIdSitesString($idSites); - if(!empty($idSites)) - { - Piwik::checkUserHasViewAccess($idSites); - } - - $parameters = array( 'idSites' => $idSites, 'period' => $period, 'date' => $date); - - $availableReports = array(); - Piwik_PostEvent('API.getReportMetadata', $availableReports, $parameters); - foreach ($availableReports as &$availableReport) { - if (!isset($availableReport['metrics'])) { - $availableReport['metrics'] = $this->getDefaultMetrics(); - } - if (!isset($availableReport['processedMetrics'])) { - $availableReport['processedMetrics'] = $this->getDefaultProcessedMetrics(); - } - - if ($hideMetricsDoc) // remove metric documentation if it's not wanted - { - unset($availableReport['metricsDocumentation']); - } - else if (!isset($availableReport['metricsDocumentation'])) - { - // set metric documentation to default if it's not set - $availableReport['metricsDocumentation'] = $this->getDefaultMetricsDocumentation(); - } - - // if hide/show columns specified, hide/show metrics & docs - $availableReport['metrics'] = $this->hideShowMetrics($availableReport['metrics']); - $availableReport['processedMetrics'] = $this->hideShowMetrics($availableReport['processedMetrics']); - if (isset($availableReport['metricsDocumentation'])) - { - $availableReport['metricsDocumentation'] = - $this->hideShowMetrics($availableReport['metricsDocumentation']); - } - } - - // Some plugins need to add custom metrics after all plugins hooked in - Piwik_PostEvent('API.getReportMetadata.end', $availableReports, $parameters); - // Oh this is not pretty! Until we have event listeners order parameter... - Piwik_PostEvent('API.getReportMetadata.end.end', $availableReports, $parameters); - - // Sort results to ensure consistent order - usort($availableReports, array($this, 'sort')); - - // Add the magic API.get report metadata aggregating all plugins API.get API calls automatically - $this->addApiGetMetdata($availableReports); - - $knownMetrics = array_merge( $this->getDefaultMetrics(), $this->getDefaultProcessedMetrics() ); - foreach($availableReports as &$availableReport) - { - // Ensure all metrics have a translation - $metrics = $availableReport['metrics']; - $cleanedMetrics = array(); - foreach($metrics as $metricId => $metricTranslation) - { - // When simply the column name was given, ie 'metric' => array( 'nb_visits' ) - // $metricTranslation is in this case nb_visits. We look for a known translation. - if(is_numeric($metricId) - && isset($knownMetrics[$metricTranslation])) - { - $metricId = $metricTranslation; - $metricTranslation = $knownMetrics[$metricTranslation]; - } - $cleanedMetrics[$metricId] = $metricTranslation; - } - $availableReport['metrics'] = $cleanedMetrics; - - // Remove array elements that are false (to clean up API output) - foreach($availableReport as $attributeName => $attributeValue) - { - if(empty($attributeValue)) - { - unset($availableReport[$attributeName]); - } - } - // when there are per goal metrics, don't display conversion_rate since it can differ from per goal sum - if(isset($availableReport['metricsGoal'])) - { - unset($availableReport['processedMetrics']['conversion_rate']); - unset($availableReport['metricsGoal']['conversion_rate']); - } - - // Processing a uniqueId for each report, - // can be used by UIs as a key to match a given report - $uniqueId = $availableReport['module'] . '_' . $availableReport['action']; - if(!empty($availableReport['parameters'])) - { - foreach($availableReport['parameters'] as $key => $value) - { - $uniqueId .= '_' . $key . '--' . $value; - } - } - $availableReport['uniqueId'] = $uniqueId; - - // Order is used to order reports internally, but not meant to be used outside - unset($availableReport['order']); - } - - // remove subtable reports - if (!$showSubtableReports) - { - foreach ($availableReports as $idx => $report) - { - if (isset($report['isSubtableReport']) && $report['isSubtableReport']) - { - unset($availableReports[$idx]); - } - } - } - - return array_values($availableReports); // make sure array has contiguous key values - } - - - /** - * Add the metadata for the API.get report - * In other plugins, this would hook on 'API.getReportMetadata' - */ - private function addApiGetMetdata(&$availableReports) - { - $metadata = array( - 'category' => Piwik_Translate('General_API'), - 'name' => Piwik_Translate('General_MainMetrics'), - 'module' => 'API', - 'action' => 'get', - 'metrics' => array(), - 'processedMetrics' => array(), - 'metricsDocumentation' => array(), - 'order' => 1 - ); - - $indexesToMerge = array('metrics', 'processedMetrics', 'metricsDocumentation'); - - foreach ($availableReports as $report) - { - if ($report['action'] == 'get') - { - foreach ($indexesToMerge as $index) - { - if (isset($report[$index]) - && is_array($report[$index])) - { - $metadata[$index] = array_merge($metadata[$index], $report[$index]); - } - } - } - } - - $availableReports[] = $metadata; - } - - public function getProcessedReport( $idSite, $period, $date, $apiModule, $apiAction, $segment = false, - $apiParameters = false, $idGoal = false, $language = false, - $showTimer = true, $hideMetricsDoc = false, $idSubtable = false, $showRawMetrics = false) + ); + $translations = array_map('Piwik_Translate', $translations); + return $translations; + } + + public function getDefaultProcessedMetrics() + { + $translations = array( + // Processed in AddColumnsProcessedMetrics + 'nb_actions_per_visit' => 'General_ColumnActionsPerVisit', + 'avg_time_on_site' => 'General_ColumnAvgTimeOnSite', + 'bounce_rate' => 'General_ColumnBounceRate', + 'conversion_rate' => 'General_ColumnConversionRate', + ); + return array_map('Piwik_Translate', $translations); + } + + public function getDefaultMetricsDocumentation() + { + $documentation = array( + 'nb_visits' => 'General_ColumnNbVisitsDocumentation', + 'nb_uniq_visitors' => 'General_ColumnNbUniqVisitorsDocumentation', + 'nb_actions' => 'General_ColumnNbActionsDocumentation', + 'nb_actions_per_visit' => 'General_ColumnActionsPerVisitDocumentation', + 'avg_time_on_site' => 'General_ColumnAvgTimeOnSiteDocumentation', + 'bounce_rate' => 'General_ColumnBounceRateDocumentation', + 'conversion_rate' => 'General_ColumnConversionRateDocumentation', + 'avg_time_on_page' => 'General_ColumnAverageTimeOnPageDocumentation', + 'nb_hits' => 'General_ColumnPageviewsDocumentation', + 'exit_rate' => 'General_ColumnExitRateDocumentation' + ); + return array_map('Piwik_Translate', $documentation); + } + + public function getSegmentsMetadata($idSites = array(), $_hideImplementationData = true) { - $timer = new Piwik_Timer(); - if($apiParameters === false) - { - $apiParameters = array(); - } - if(!empty($idGoal) - && empty($apiParameters['idGoal'])) - { - $apiParameters['idGoal'] = $idGoal; - } + $segments = array(); + Piwik_PostEvent('API.getSegmentsMetadata', $segments, $idSites); + + $segments[] = array( + 'type' => 'dimension', + 'category' => '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), + ); + $segments[] = array( + 'type' => 'dimension', + 'category' => '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'), + ); + $segments[] = array( + 'type' => 'metric', + 'category' => 'Visit', + 'name' => 'General_NbActions', + 'segment' => 'actions', + 'sqlSegment' => 'log_visit.visit_total_actions', + ); + $segments[] = array( + 'type' => 'metric', + 'category' => 'Visit', + 'name' => 'General_NbSearches', + 'segment' => 'searches', + 'sqlSegment' => 'log_visit.visit_total_searches', + 'acceptedValues' => 'To select all visits who used internal Site Search, use: &segment=searches>0', + ); + $segments[] = array( + 'type' => 'metric', + 'category' => 'Visit', + 'name' => 'General_ColumnVisitDuration', + 'segment' => 'visitDuration', + 'sqlSegment' => 'log_visit.visit_total_time', + ); + $segments[] = array( + 'type' => 'dimension', + 'category' => 'Visit', + 'name' => Piwik_Translate('General_VisitType') . ". " . Piwik_Translate('General_VisitTypeExample', '"&segment=visitorType==returning,visitorType==returningCustomer"'), + 'segment' => 'visitorType', + 'acceptedValues' => 'new, returning, returningCustomer', + 'sqlSegment' => 'log_visit.visitor_returning', + 'sqlFilter' => create_function('$type', 'return $type == "new" ? 0 : ($type == "returning" ? 1 : 2);'), + ); + $segments[] = array( + 'type' => 'metric', + 'category' => 'Visit', + 'name' => 'General_DaysSinceLastVisit', + 'segment' => 'daysSinceLastVisit', + 'sqlSegment' => 'log_visit.visitor_days_since_last', + ); + $segments[] = array( + 'type' => 'metric', + 'category' => 'Visit', + 'name' => 'General_DaysSinceFirstVisit', + 'segment' => 'daysSinceFirstVisit', + 'sqlSegment' => 'log_visit.visitor_days_since_first', + ); + $segments[] = array( + 'type' => 'metric', + 'category' => 'Visit', + 'name' => 'General_NumberOfVisits', + 'segment' => 'visitCount', + 'sqlSegment' => 'log_visit.visitor_count_visits', + ); + + $segments[] = array( + 'type' => 'dimension', + 'category' => 'Visit', + 'name' => 'General_VisitConvertedGoal', + 'segment' => 'visitConverted', + 'acceptedValues' => '0, 1', + 'sqlSegment' => 'log_visit.visit_goal_converted', + ); + + $segments[] = array( + 'type' => 'dimension', + 'category' => 'Visit', + 'name' => Piwik_Translate('General_EcommerceVisitStatus', '"&segment=visitEcommerceStatus==ordered,visitEcommerceStatus==orderedThenAbandonedCart"'), + 'segment' => 'visitEcommerceStatus', + 'acceptedValues' => implode(", ", self::$visitEcommerceStatus), + 'sqlSegment' => 'log_visit.visit_goal_buyer', + 'sqlFilter' => array('Piwik_API_API', 'getVisitEcommerceStatus'), + ); + + $segments[] = array( + 'type' => 'metric', + 'category' => 'Visit', + 'name' => 'General_DaysSinceLastEcommerceOrder', + 'segment' => 'daysSinceLastEcommerceOrder', + 'sqlSegment' => 'log_visit.visitor_days_since_order', + ); + + foreach ($segments as &$segment) { + $segment['name'] = Piwik_Translate($segment['name']); + $segment['category'] = Piwik_Translate($segment['category']); + + if ($_hideImplementationData) { + unset($segment['sqlFilter']); + unset($segment['sqlSegment']); + } + } + + usort($segments, array($this, 'sortSegments')); + return $segments; + } + + static protected $visitEcommerceStatus = array( + Piwik_Tracker_GoalManager::TYPE_BUYER_NONE => 'none', + Piwik_Tracker_GoalManager::TYPE_BUYER_ORDERED => 'ordered', + Piwik_Tracker_GoalManager::TYPE_BUYER_OPEN_CART => 'abandonedCart', + Piwik_Tracker_GoalManager::TYPE_BUYER_ORDERED_AND_OPEN_CART => 'orderedThenAbandonedCart', + ); + + /** + * @ignore + */ + static public function getVisitEcommerceStatusFromId($id) + { + if (!isset(self::$visitEcommerceStatus[$id])) { + throw new Exception("Unexpected ECommerce status value "); + } + return self::$visitEcommerceStatus[$id]; + } + + /** + * @ignore + */ + static public function getVisitEcommerceStatus($status) + { + $id = array_search($status, self::$visitEcommerceStatus); + if ($id === false) { + throw new Exception("Invalid 'visitEcommerceStatus' segment value"); + } + return $id; + } + + private function sortSegments($row1, $row2) + { + $columns = array('type', 'category', 'name', 'segment'); + foreach ($columns as $column) { + // Keep segments ordered alphabetically inside categories.. + $type = -1; + if ($column == 'name') $type = 1; + $compare = $type * strcmp($row1[$column], $row2[$column]); + + // hack so that custom variables "page" are grouped together in the doc + if ($row1['category'] == Piwik_Translate('CustomVariables_CustomVariables') + && $row1['category'] == $row2['category'] + ) { + $compare = strcmp($row1['segment'], $row2['segment']); + return $compare; + } + if ($compare != 0) { + return $compare; + } + } + return $compare; + } + + /** + * Returns the url to application logo (~280x110px) + * + * @param bool $pathOnly If true, returns path relative to doc root. Otherwise, returns a URL. + * @return string + */ + public function getLogoUrl($pathOnly = false) + { + $logo = 'themes/default/images/logo.png'; + if (Piwik_Config::getInstance()->branding['use_custom_logo'] == 1 + && file_exists(Piwik_Common::getPathToPiwikRoot() . '/themes/logo.png') + ) { + $logo = 'themes/logo.png'; + } + if (!$pathOnly) { + return Piwik::getPiwikUrl() . $logo; + } + return Piwik_Common::getPathToPiwikRoot() . '/' . $logo; + } + + /** + * Returns the url to header logo (~127x50px) + * + * @param bool $pathOnly If true, returns path relative to doc root. Otherwise, returns a URL. + * @return string + */ + public function getHeaderLogoUrl($pathOnly = false) + { + $logo = 'themes/default/images/logo-header.png'; + if (Piwik_Config::getInstance()->branding['use_custom_logo'] == 1 + && file_exists(Piwik_Common::getPathToPiwikRoot() . '/themes/logo-header.png') + ) { + $logo = 'themes/logo-header.png'; + } + if (!$pathOnly) { + return Piwik::getPiwikUrl() . $logo; + } + return Piwik_Common::getPathToPiwikRoot() . '/' . $logo; + } + + /** + * Returns the URL to application SVG Logo + * + * @param bool $pathOnly If true, returns path relative to doc root. Otherwise, returns a URL. + * @return string + */ + public function getSVGLogoUrl($pathOnly = false) + { + $logo = 'themes/default/images/logo.svg'; + if (Piwik_Config::getInstance()->branding['use_custom_logo'] == 1 + && file_exists(Piwik_Common::getPathToPiwikRoot() . '/themes/logo.svg') + ) { + $logo = 'themes/logo.svg'; + } + if (!$pathOnly) { + return Piwik::getPiwikUrl() . $logo; + } + return Piwik_Common::getPathToPiwikRoot() . '/' . $logo; + } + + /** + * Returns whether there is an SVG Logo available. + * + * @return bool + */ + public function hasSVGLogo() + { + if (Piwik_Config::getInstance()->branding['use_custom_logo'] == 0) { + /* We always have our application logo */ + return true; + } else if (Piwik_Config::getInstance()->branding['use_custom_logo'] == 1 + && file_exists(Piwik_Common::getPathToPiwikRoot() . '/themes/logo.svg') + ) { + return true; + } + + return false; + } + + /** + * Loads reports metadata, then return the requested one, + * matching optional API parameters. + */ + public function getMetadata($idSite, $apiModule, $apiAction, $apiParameters = array(), $language = false, + $period = false, $date = false, $hideMetricsDoc = false, $showSubtableReports = false) + { + Piwik_Translate::getInstance()->reloadLanguage($language); + $reportsMetadata = $this->getReportMetadata($idSite, $period, $date, $hideMetricsDoc, $showSubtableReports); + + foreach ($reportsMetadata as $report) { + // See ArchiveProcessing/Period.php - unique visitors are not processed for period != day + if (($period && $period != 'day') && !($apiModule == 'VisitsSummary' && $apiAction == 'get')) { + unset($report['metrics']['nb_uniq_visitors']); + } + if ($report['module'] == $apiModule + && $report['action'] == $apiAction + ) { + // No custom parameters + if (empty($apiParameters) + && empty($report['parameters']) + ) { + return array($report); + } + if (empty($report['parameters'])) { + continue; + } + $diff = array_diff($report['parameters'], $apiParameters); + if (empty($diff)) { + return array($report); + } + } + } + return false; + } + + /** + * Triggers a hook to ask plugins for available Reports. + * Returns metadata information about each report (category, name, dimension, metrics, etc.) + * + * @param string $idSites Comma separated list of website Ids + * @return array + */ + public function getReportMetadata($idSites = '', $period = false, $date = false, $hideMetricsDoc = false, + $showSubtableReports = false) + { + $idSites = Piwik_Site::getIdSitesFromIdSitesString($idSites); + if (!empty($idSites)) { + Piwik::checkUserHasViewAccess($idSites); + } + + $parameters = array('idSites' => $idSites, 'period' => $period, 'date' => $date); + + $availableReports = array(); + Piwik_PostEvent('API.getReportMetadata', $availableReports, $parameters); + foreach ($availableReports as &$availableReport) { + if (!isset($availableReport['metrics'])) { + $availableReport['metrics'] = $this->getDefaultMetrics(); + } + if (!isset($availableReport['processedMetrics'])) { + $availableReport['processedMetrics'] = $this->getDefaultProcessedMetrics(); + } + + if ($hideMetricsDoc) // remove metric documentation if it's not wanted + { + unset($availableReport['metricsDocumentation']); + } else if (!isset($availableReport['metricsDocumentation'])) { + // set metric documentation to default if it's not set + $availableReport['metricsDocumentation'] = $this->getDefaultMetricsDocumentation(); + } + + // if hide/show columns specified, hide/show metrics & docs + $availableReport['metrics'] = $this->hideShowMetrics($availableReport['metrics']); + $availableReport['processedMetrics'] = $this->hideShowMetrics($availableReport['processedMetrics']); + if (isset($availableReport['metricsDocumentation'])) { + $availableReport['metricsDocumentation'] = + $this->hideShowMetrics($availableReport['metricsDocumentation']); + } + } + + // Some plugins need to add custom metrics after all plugins hooked in + Piwik_PostEvent('API.getReportMetadata.end', $availableReports, $parameters); + // Oh this is not pretty! Until we have event listeners order parameter... + Piwik_PostEvent('API.getReportMetadata.end.end', $availableReports, $parameters); + + // Sort results to ensure consistent order + usort($availableReports, array($this, 'sort')); + + // Add the magic API.get report metadata aggregating all plugins API.get API calls automatically + $this->addApiGetMetdata($availableReports); + + $knownMetrics = array_merge($this->getDefaultMetrics(), $this->getDefaultProcessedMetrics()); + foreach ($availableReports as &$availableReport) { + // Ensure all metrics have a translation + $metrics = $availableReport['metrics']; + $cleanedMetrics = array(); + foreach ($metrics as $metricId => $metricTranslation) { + // When simply the column name was given, ie 'metric' => array( 'nb_visits' ) + // $metricTranslation is in this case nb_visits. We look for a known translation. + if (is_numeric($metricId) + && isset($knownMetrics[$metricTranslation]) + ) { + $metricId = $metricTranslation; + $metricTranslation = $knownMetrics[$metricTranslation]; + } + $cleanedMetrics[$metricId] = $metricTranslation; + } + $availableReport['metrics'] = $cleanedMetrics; + + // Remove array elements that are false (to clean up API output) + foreach ($availableReport as $attributeName => $attributeValue) { + if (empty($attributeValue)) { + unset($availableReport[$attributeName]); + } + } + // when there are per goal metrics, don't display conversion_rate since it can differ from per goal sum + if (isset($availableReport['metricsGoal'])) { + unset($availableReport['processedMetrics']['conversion_rate']); + unset($availableReport['metricsGoal']['conversion_rate']); + } + + // Processing a uniqueId for each report, + // can be used by UIs as a key to match a given report + $uniqueId = $availableReport['module'] . '_' . $availableReport['action']; + if (!empty($availableReport['parameters'])) { + foreach ($availableReport['parameters'] as $key => $value) { + $uniqueId .= '_' . $key . '--' . $value; + } + } + $availableReport['uniqueId'] = $uniqueId; + + // Order is used to order reports internally, but not meant to be used outside + unset($availableReport['order']); + } + + // remove subtable reports + if (!$showSubtableReports) { + foreach ($availableReports as $idx => $report) { + if (isset($report['isSubtableReport']) && $report['isSubtableReport']) { + unset($availableReports[$idx]); + } + } + } + + return array_values($availableReports); // make sure array has contiguous key values + } + + + /** + * Add the metadata for the API.get report + * In other plugins, this would hook on 'API.getReportMetadata' + */ + private function addApiGetMetdata(&$availableReports) + { + $metadata = array( + 'category' => Piwik_Translate('General_API'), + 'name' => Piwik_Translate('General_MainMetrics'), + 'module' => 'API', + 'action' => 'get', + 'metrics' => array(), + 'processedMetrics' => array(), + 'metricsDocumentation' => array(), + 'order' => 1 + ); + + $indexesToMerge = array('metrics', 'processedMetrics', 'metricsDocumentation'); + + foreach ($availableReports as $report) { + if ($report['action'] == 'get') { + foreach ($indexesToMerge as $index) { + if (isset($report[$index]) + && is_array($report[$index]) + ) { + $metadata[$index] = array_merge($metadata[$index], $report[$index]); + } + } + } + } + + $availableReports[] = $metadata; + } + + public function getProcessedReport($idSite, $period, $date, $apiModule, $apiAction, $segment = false, + $apiParameters = false, $idGoal = false, $language = false, + $showTimer = true, $hideMetricsDoc = false, $idSubtable = false, $showRawMetrics = false) + { + $timer = new Piwik_Timer(); + if ($apiParameters === false) { + $apiParameters = array(); + } + if (!empty($idGoal) + && empty($apiParameters['idGoal']) + ) { + $apiParameters['idGoal'] = $idGoal; + } // Is this report found in the Metadata available reports? $reportMetadata = $this->getMetadata($idSite, $apiModule, $apiAction, $apiParameters, $language, - $period, $date, $hideMetricsDoc, $showSubtableReports = true); - if(empty($reportMetadata)) - { - throw new Exception("Requested report $apiModule.$apiAction for Website id=$idSite not found in the list of available reports. \n"); + $period, $date, $hideMetricsDoc, $showSubtableReports = true); + if (empty($reportMetadata)) { + throw new Exception("Requested report $apiModule.$apiAction for Website id=$idSite not found in the list of available reports. \n"); } $reportMetadata = reset($reportMetadata); - // Generate Api call URL passing custom parameters - $parameters = array_merge( $apiParameters, array( - 'method' => $apiModule.'.'.$apiAction, - 'idSite' => $idSite, - 'period' => $period, - 'date' => $date, - 'format' => 'original', - 'serialize' => '0', - 'language' => $language, - 'idSubtable' => $idSubtable, - )); - if(!empty($segment)) $parameters['segment'] = $segment; - - $url = Piwik_Url::getQueryStringFromParameters($parameters); + // Generate Api call URL passing custom parameters + $parameters = array_merge($apiParameters, array( + 'method' => $apiModule . '.' . $apiAction, + 'idSite' => $idSite, + 'period' => $period, + 'date' => $date, + 'format' => 'original', + 'serialize' => '0', + 'language' => $language, + 'idSubtable' => $idSubtable, + )); + if (!empty($segment)) $parameters['segment'] = $segment; + + $url = Piwik_Url::getQueryStringFromParameters($parameters); $request = new Piwik_API_Request($url); try { - /** @var Piwik_DataTable */ - $dataTable = $request->process(); - } catch(Exception $e) { - throw new Exception("API returned an error: ".$e->getMessage()."\n"); - } - - list($newReport, $columns, $rowsMetadata) = $this->handleTableReport($idSite, $dataTable, $reportMetadata, isset($reportMetadata['dimension']), $showRawMetrics); - foreach($columns as $columnId => &$name) - { - $name = ucfirst($name); - } - $website = new Piwik_Site($idSite); + /** @var Piwik_DataTable */ + $dataTable = $request->process(); + } catch (Exception $e) { + throw new Exception("API returned an error: " . $e->getMessage() . "\n"); + } + + list($newReport, $columns, $rowsMetadata) = $this->handleTableReport($idSite, $dataTable, $reportMetadata, isset($reportMetadata['dimension']), $showRawMetrics); + foreach ($columns as $columnId => &$name) { + $name = ucfirst($name); + } + $website = new Piwik_Site($idSite); // $segment = new Piwik_Segment($segment, $idSite); - $period = Piwik_Period::advancedFactory($period, $date); - $period = $period->getLocalizedLongString(); + $period = Piwik_Period::advancedFactory($period, $date); + $period = $period->getLocalizedLongString(); - $return = array( - 'website' => $website->getName(), - 'prettyDate' => $period, + $return = array( + 'website' => $website->getName(), + 'prettyDate' => $period, // 'prettySegment' => $segment->getPrettyString(), - 'metadata' => $reportMetadata, - 'columns' => $columns, - 'reportData' => $newReport, - 'reportMetadata' => $rowsMetadata, - ); - if($showTimer) - { - $return['timerMillis'] = $timer->getTimeMs(0); - } - return $return; + 'metadata' => $reportMetadata, + 'columns' => $columns, + 'reportData' => $newReport, + 'reportMetadata' => $rowsMetadata, + ); + if ($showTimer) { + $return['timerMillis'] = $timer->getTimeMs(0); + } + return $return; } - /** - * Enhance a $dataTable using metadata : - * - * - remove metrics based on $reportMetadata['metrics'] - * - add 0 valued metrics if $dataTable doesn't provide all $reportMetadata['metrics'] - * - format metric values to a 'human readable' format - * - extract row metadata to a separate Piwik_DataTable_Simple|Piwik_DataTable_Array : $rowsMetadata - * - translate metric names to a separate array : $columns - * - * @param int $idSite enables monetary value formatting based on site currency - * @param Piwik_DataTable|Piwik_DataTable_Array $dataTable - * @param array $reportMetadata - * @param boolean $hasDimension - * @return array Piwik_DataTable_Simple|Piwik_DataTable_Array $newReport with human readable format & array $columns list of translated column names & Piwik_DataTable_Simple|Piwik_DataTable_Array $rowsMetadata - **/ + /** + * Enhance a $dataTable using metadata : + * + * - remove metrics based on $reportMetadata['metrics'] + * - add 0 valued metrics if $dataTable doesn't provide all $reportMetadata['metrics'] + * - format metric values to a 'human readable' format + * - extract row metadata to a separate Piwik_DataTable_Simple|Piwik_DataTable_Array : $rowsMetadata + * - translate metric names to a separate array : $columns + * + * @param int $idSite enables monetary value formatting based on site currency + * @param Piwik_DataTable|Piwik_DataTable_Array $dataTable + * @param array $reportMetadata + * @param boolean $hasDimension + * @return array Piwik_DataTable_Simple|Piwik_DataTable_Array $newReport with human readable format & array $columns list of translated column names & Piwik_DataTable_Simple|Piwik_DataTable_Array $rowsMetadata + **/ private function handleTableReport($idSite, $dataTable, &$reportMetadata, $hasDimension, $showRawMetrics = false) { - $columns = $reportMetadata['metrics']; - - if($hasDimension) - { - $columns = array_merge( - array('label' => $reportMetadata['dimension'] ), - $columns - ); - - if(isset($reportMetadata['processedMetrics'])) - { - $processedMetricsAdded = $this->getDefaultProcessedMetrics(); - foreach($processedMetricsAdded as $processedMetricId => $processedMetricTranslation) - { - // this processed metric can be displayed for this report - if(isset($reportMetadata['processedMetrics'][$processedMetricId])) - { - $columns[$processedMetricId] = $processedMetricTranslation; - } - } - } - - // Display the global Goal metrics - if(isset($reportMetadata['metricsGoal'])) - { - $metricsGoalDisplay = array('revenue'); - // Add processed metrics to be displayed for this report - foreach($metricsGoalDisplay as $goalMetricId) - { - if(isset($reportMetadata['metricsGoal'][$goalMetricId])) - { - $columns[$goalMetricId] = $reportMetadata['metricsGoal'][$goalMetricId]; - } - } - } - - if(isset($reportMetadata['processedMetrics'])) - { - // Add processed metrics - $dataTable->filter('AddColumnsProcessedMetrics', array($deleteRowsWithNoVisit = false)); - } - } - - $columns = $this->hideShowMetrics($columns); - - // $dataTable is an instance of Piwik_DataTable_Array when multiple periods requested - if ($dataTable instanceof Piwik_DataTable_Array) - { - // Need a new Piwik_DataTable_Array to store the 'human readable' values - $newReport = new Piwik_DataTable_Array(); - $newReport->setKeyName("prettyDate"); - - // Need a new Piwik_DataTable_Array to store report metadata - $rowsMetadata = new Piwik_DataTable_Array(); - $rowsMetadata->setKeyName("prettyDate"); - - // Process each Piwik_DataTable_Simple entry - foreach($dataTable->getArray() as $label => $simpleDataTable) - { - $this->removeEmptyColumns($columns, $reportMetadata, $simpleDataTable); - - list($enhancedSimpleDataTable, $rowMetadata) = $this->handleSimpleDataTable($idSite, $simpleDataTable, $columns, $hasDimension, $showRawMetrics); - $enhancedSimpleDataTable->metadata = $simpleDataTable->metadata; - - $period = $simpleDataTable->metadata['period']->getLocalizedLongString(); - $newReport->addTable($enhancedSimpleDataTable, $period); - $rowsMetadata->addTable($rowMetadata, $period); - } - } - else - { - $this->removeEmptyColumns($columns, $reportMetadata, $dataTable); - list($newReport, $rowsMetadata) = $this->handleSimpleDataTable($idSite, $dataTable, $columns, $hasDimension, $showRawMetrics); - } - - return array( - $newReport, - $columns, - $rowsMetadata - ); + $columns = $reportMetadata['metrics']; + + if ($hasDimension) { + $columns = array_merge( + array('label' => $reportMetadata['dimension']), + $columns + ); + + if (isset($reportMetadata['processedMetrics'])) { + $processedMetricsAdded = $this->getDefaultProcessedMetrics(); + foreach ($processedMetricsAdded as $processedMetricId => $processedMetricTranslation) { + // this processed metric can be displayed for this report + if (isset($reportMetadata['processedMetrics'][$processedMetricId])) { + $columns[$processedMetricId] = $processedMetricTranslation; + } + } + } + + // Display the global Goal metrics + if (isset($reportMetadata['metricsGoal'])) { + $metricsGoalDisplay = array('revenue'); + // Add processed metrics to be displayed for this report + foreach ($metricsGoalDisplay as $goalMetricId) { + if (isset($reportMetadata['metricsGoal'][$goalMetricId])) { + $columns[$goalMetricId] = $reportMetadata['metricsGoal'][$goalMetricId]; + } + } + } + + if (isset($reportMetadata['processedMetrics'])) { + // Add processed metrics + $dataTable->filter('AddColumnsProcessedMetrics', array($deleteRowsWithNoVisit = false)); + } + } + + $columns = $this->hideShowMetrics($columns); + + // $dataTable is an instance of Piwik_DataTable_Array when multiple periods requested + if ($dataTable instanceof Piwik_DataTable_Array) { + // Need a new Piwik_DataTable_Array to store the 'human readable' values + $newReport = new Piwik_DataTable_Array(); + $newReport->setKeyName("prettyDate"); + + // Need a new Piwik_DataTable_Array to store report metadata + $rowsMetadata = new Piwik_DataTable_Array(); + $rowsMetadata->setKeyName("prettyDate"); + + // Process each Piwik_DataTable_Simple entry + foreach ($dataTable->getArray() as $label => $simpleDataTable) { + $this->removeEmptyColumns($columns, $reportMetadata, $simpleDataTable); + + list($enhancedSimpleDataTable, $rowMetadata) = $this->handleSimpleDataTable($idSite, $simpleDataTable, $columns, $hasDimension, $showRawMetrics); + $enhancedSimpleDataTable->metadata = $simpleDataTable->metadata; + + $period = $simpleDataTable->metadata['period']->getLocalizedLongString(); + $newReport->addTable($enhancedSimpleDataTable, $period); + $rowsMetadata->addTable($rowMetadata, $period); + } + } else { + $this->removeEmptyColumns($columns, $reportMetadata, $dataTable); + list($newReport, $rowsMetadata) = $this->handleSimpleDataTable($idSite, $dataTable, $columns, $hasDimension, $showRawMetrics); + } + + return array( + $newReport, + $columns, + $rowsMetadata + ); + } + + /** + * Removes metrics from the list of columns and the report meta data if they are marked empty + * in the data table meta data. + */ + private function removeEmptyColumns(&$columns, &$reportMetadata, $dataTable) + { + $emptyColumns = $dataTable->getMetadata(Piwik_DataTable::EMPTY_COLUMNS_METADATA_NAME); + + if (!is_array($emptyColumns)) { + return; + } + + $columns = $this->hideShowMetrics($columns, $emptyColumns); + + if (isset($reportMetadata['metrics'])) { + $reportMetadata['metrics'] = $this->hideShowMetrics($reportMetadata['metrics'], $emptyColumns); + } + + if (isset($reportMetadata['metricsDocumentation'])) { + $reportMetadata['metricsDocumentation'] = $this->hideShowMetrics($reportMetadata['metricsDocumentation'], $emptyColumns); + } } - /** - * Removes metrics from the list of columns and the report meta data if they are marked empty - * in the data table meta data. - */ - private function removeEmptyColumns( &$columns, &$reportMetadata, $dataTable ) - { - $emptyColumns = $dataTable->getMetadata(Piwik_DataTable::EMPTY_COLUMNS_METADATA_NAME); - - if (!is_array($emptyColumns)) - { - return; - } - - $columns = $this->hideShowMetrics($columns, $emptyColumns); - - if (isset($reportMetadata['metrics'])) - { - $reportMetadata['metrics'] = $this->hideShowMetrics($reportMetadata['metrics'], $emptyColumns); - } - - if (isset($reportMetadata['metricsDocumentation'])) - { - $reportMetadata['metricsDocumentation'] = $this->hideShowMetrics($reportMetadata['metricsDocumentation'], $emptyColumns); - } - } - - /** + /** * Removes column names from an array based on the values in the hideColumns, * showColumns query parameters. This is a hack that provides the ColumnDelete * filter functionality in processed reports. * * @param array $columns List of metrics shown in a processed report. - * @param array $emptyColumns Empty columns from the data table meta data. + * @param array $emptyColumns Empty columns from the data table meta data. * @return array Filtered list of metrics. */ - private function hideShowMetrics( $columns, $emptyColumns = array() ) + private function hideShowMetrics($columns, $emptyColumns = array()) + { + if (!is_array($columns)) { + return $columns; + } + + // remove columns if hideColumns query parameters exist + $columnsToRemove = Piwik_Common::getRequestVar('hideColumns', ''); + if ($columnsToRemove != '') { + $columnsToRemove = explode(',', $columnsToRemove); + foreach ($columnsToRemove as $name) { + // if a column to remove is in the column list, remove it + if (isset($columns[$name])) { + unset($columns[$name]); + } + } + } + + // remove columns if showColumns query parameters exist + $columnsToKeep = Piwik_Common::getRequestVar('showColumns', ''); + if ($columnsToKeep != '') { + $columnsToKeep = explode(',', $columnsToKeep); + $columnsToKeep[] = 'label'; + + foreach ($columns as $name => $ignore) { + // if the current column should not be kept, remove it + $idx = array_search($name, $columnsToKeep); + if ($idx === FALSE) // if $name is not in $columnsToKeep + { + unset($columns[$name]); + } + } + } + + // remove empty columns + if (is_array($emptyColumns)) { + foreach ($emptyColumns as $column) { + if (isset($columns[$column])) { + unset($columns[$column]); + } + } + } + + return $columns; + } + + /** + * Enhance $simpleDataTable using metadata : + * + * - remove metrics based on $reportMetadata['metrics'] + * - add 0 valued metrics if $simpleDataTable doesn't provide all $reportMetadata['metrics'] + * - format metric values to a 'human readable' format + * - extract row metadata to a separate Piwik_DataTable_Simple $rowsMetadata + * + * @param int $idSite enables monetary value formatting based on site currency + * @param Piwik_DataTable_Simple $simpleDataTable + * @param array $metadataColumns + * @param boolean $hasDimension + * @param bool $returnRawMetrics If set to true, the original metrics will be returned + * + * @return array Piwik_DataTable $enhancedDataTable filtered metrics with human readable format & Piwik_DataTable_Simple $rowsMetadata + */ + private function handleSimpleDataTable($idSite, $simpleDataTable, $metadataColumns, $hasDimension, $returnRawMetrics = false) + { + // new DataTable to store metadata + $rowsMetadata = new Piwik_DataTable(); + + // new DataTable to store 'human readable' values + if ($hasDimension) { + $enhancedDataTable = new Piwik_DataTable(); + } else { + $enhancedDataTable = new Piwik_DataTable_Simple(); + } + + // add missing metrics + foreach ($simpleDataTable->getRows() as $row) { + $rowMetrics = $row->getColumns(); + foreach ($metadataColumns as $id => $name) { + if (!isset($rowMetrics[$id])) { + $row->addColumn($id, 0); + } + } + } + + foreach ($simpleDataTable->getRows() as $row) { + $enhancedRow = new Piwik_DataTable_Row(); + $enhancedDataTable->addRow($enhancedRow); + $rowMetrics = $row->getColumns(); + foreach ($rowMetrics as $columnName => $columnValue) { + // filter metrics according to metadata definition + if (isset($metadataColumns[$columnName])) { + // generate 'human readable' metric values + $prettyValue = Piwik::getPrettyValue($idSite, $columnName, $columnValue, $htmlAllowed = false, $timeAsSentence = false); + $enhancedRow->addColumn($columnName, $prettyValue); + } // For example the Maps Widget requires the raw metrics to do advanced datavis + elseif ($returnRawMetrics) { + $enhancedRow->addColumn($columnName, $columnValue); + } + } + + // If report has a dimension, extract metadata into a distinct DataTable + if ($hasDimension) { + $rowMetadata = $row->getMetadata(); + $idSubDataTable = $row->getIdSubDataTable(); + + // Create a row metadata only if there are metadata to insert + if (count($rowMetadata) > 0 || !is_null($idSubDataTable)) { + $metadataRow = new Piwik_DataTable_Row(); + $rowsMetadata->addRow($metadataRow); + + foreach ($rowMetadata as $metadataKey => $metadataValue) { + $metadataRow->addColumn($metadataKey, $metadataValue); + } + + if (!is_null($idSubDataTable)) { + $metadataRow->addColumn('idsubdatatable', $idSubDataTable); + } + } + } + } + + return array( + $enhancedDataTable, + $rowsMetadata + ); + } + + /** + * API metadata are sorted by category/name, + * with a little tweak to replicate the standard Piwik category ordering + * + * @param string $a + * @param string $b + * @return int + */ + private function sort($a, $b) { - if (!is_array($columns)) - { - return $columns; - } - - // remove columns if hideColumns query parameters exist - $columnsToRemove = Piwik_Common::getRequestVar('hideColumns', ''); - if ($columnsToRemove != '') - { - $columnsToRemove = explode(',', $columnsToRemove); - foreach ($columnsToRemove as $name) - { - // if a column to remove is in the column list, remove it - if (isset($columns[$name])) - { - unset($columns[$name]); - } - } - } - - // remove columns if showColumns query parameters exist - $columnsToKeep = Piwik_Common::getRequestVar('showColumns', ''); - if ($columnsToKeep != '') - { - $columnsToKeep = explode(',', $columnsToKeep); - $columnsToKeep[] = 'label'; - - foreach ($columns as $name => $ignore) - { - // if the current column should not be kept, remove it - $idx = array_search($name, $columnsToKeep); - if ($idx === FALSE) // if $name is not in $columnsToKeep - { - unset($columns[$name]); - } - } - } - - // remove empty columns - if (is_array($emptyColumns)) - { - foreach ($emptyColumns as $column) - { - if (isset($columns[$column])) - { - unset($columns[$column]); - } - } - } - - return $columns; + static $order = null; + if (is_null($order)) { + $order = array( + Piwik_Translate('General_MultiSitesSummary'), + Piwik_Translate('VisitsSummary_VisitsSummary'), + Piwik_Translate('Goals_Ecommerce'), + Piwik_Translate('Actions_Actions'), + Piwik_Translate('Actions_SubmenuSitesearch'), + Piwik_Translate('Referers_Referers'), + Piwik_Translate('Goals_Goals'), + Piwik_Translate('General_Visitors'), + Piwik_Translate('UserSettings_VisitorSettings'), + ); + } + return ($category = strcmp(array_search($a['category'], $order), array_search($b['category'], $order))) == 0 + ? (@$a['order'] < @$b['order'] ? -1 : 1) + : $category; + } + + /** + * Get a combined report of the *.get API methods. + */ + public function get($idSite, $period, $date, $segment = false, $columns = false) + { + $columns = Piwik::getArrayFromApiParameter($columns); + + // build columns map for faster checks later on + $columnsMap = array(); + foreach ($columns as $column) { + $columnsMap[$column] = true; + } + + // find out which columns belong to which plugin + $columnsByPlugin = array(); + $meta = Piwik_API_API::getInstance()->getReportMetadata($idSite, $period, $date); + foreach ($meta as $reportMeta) { + // scan all *.get reports + if ($reportMeta['action'] == 'get' && !isset($reportMeta['parameters']) + && $reportMeta['module'] != 'API' + ) { + $plugin = $reportMeta['module']; + foreach ($reportMeta['metrics'] as $column => $columnTranslation) { + // a metric from this report has been requested + if (isset($columnsMap[$column]) + // or by default, return all metrics + || empty($columnsMap) + ) { + $columnsByPlugin[$plugin][] = $column; + } + } + } + } + krsort($columnsByPlugin); + + $mergedDataTable = false; + $params = compact('idSite', 'period', 'date', 'segment', 'idGoal'); + foreach ($columnsByPlugin as $plugin => $columns) { + // load the data + $className = 'Piwik_' . $plugin . '_API'; + $params['columns'] = implode(',', $columns); + $dataTable = Piwik_API_Proxy::getInstance()->call($className, 'get', $params); + // make sure the table has all columns + $array = ($dataTable instanceof Piwik_DataTable_Array ? $dataTable->getArray() : array($dataTable)); + foreach ($array as $table) { + // we don't support idSites=all&date=DATE1,DATE2 + if ($table instanceof Piwik_DataTable) { + $firstRow = $table->getFirstRow(); + if (!$firstRow) { + $firstRow = new Piwik_DataTable_Row; + $table->addRow($firstRow); + } + foreach ($columns as $column) { + if ($firstRow->getColumn($column) === false) { + $firstRow->setColumn($column, 0); + } + } + } + } + + // merge reports + if ($mergedDataTable === false) { + $mergedDataTable = $dataTable; + } else { + $this->mergeDataTables($mergedDataTable, $dataTable); + } + } + return $mergedDataTable; + } + + + /** + * Merge the columns of two data tables. + * Manipulates the first table. + */ + private function mergeDataTables($table1, $table2) + { + // handle table arrays + if ($table1 instanceof Piwik_DataTable_Array && $table2 instanceof Piwik_DataTable_Array) { + $subTables2 = $table2->getArray(); + foreach ($table1->getArray() as $index => $subTable1) { + $subTable2 = $subTables2[$index]; + $this->mergeDataTables($subTable1, $subTable2); + } + return; + } + + $firstRow1 = $table1->getFirstRow(); + $firstRow2 = $table2->getFirstRow(); + if ($firstRow2 instanceof Piwik_DataTable_Row) { + foreach ($firstRow2->getColumns() as $metric => $value) { + $firstRow1->setColumn($metric, $value); + } + } + } + + + /** + * Given an API report to query (eg. "Referers.getKeywords", and a Label (eg. "free%20software"), + * this function will query the API for the previous days/weeks/etc. and will return + * a ready to use data structure containing the metrics for the requested Label, along with enriched information (min/max values, etc.) + * + * @return array + */ + 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') { + // load days in the range + $period = 'day'; + } + + if (!Piwik_Archive::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); + + if ($label) { + $labels = explode(',', $label); + $labels = array_unique($labels); + } else { + $labels = array(); + } + + $dataTable = $this->loadRowEvolutionDataFromAPI($idSite, $period, $date, $apiModule, $apiAction, $labels, $segment, $idGoal); + + if (empty($labels)) { + // if no labels specified, use all possible labels as list + foreach ($dataTable->getArray() as $table) { + $labels = array_merge($labels, $table->getColumn('label')); + } + $labels = array_unique($labels); + } + + if (count($labels) > 1) { + $data = $this->getMultiRowEvolution( + $dataTable, + $idSite, + $period, + $date, + $apiModule, + $apiAction, + $labels, + $segment, + $column, + $language, + $idGoal, + $legendAppendMetric, + $labelUseAbsoluteUrl + ); + } else { + $data = $this->getSingleRowEvolution( + $dataTable, + $idSite, + $period, + $date, + $apiModule, + $apiAction, + $labels[0], + $segment, + $language, + $idGoal, + $labelUseAbsoluteUrl + ); + } + return $data; } - /** - * Enhance $simpleDataTable using metadata : - * - * - remove metrics based on $reportMetadata['metrics'] - * - add 0 valued metrics if $simpleDataTable doesn't provide all $reportMetadata['metrics'] - * - format metric values to a 'human readable' format - * - extract row metadata to a separate Piwik_DataTable_Simple $rowsMetadata - * - * @param int $idSite enables monetary value formatting based on site currency - * @param Piwik_DataTable_Simple $simpleDataTable - * @param array $metadataColumns - * @param boolean $hasDimension - * @param bool $returnRawMetrics If set to true, the original metrics will be returned - * - * @return array Piwik_DataTable $enhancedDataTable filtered metrics with human readable format & Piwik_DataTable_Simple $rowsMetadata - */ - private function handleSimpleDataTable($idSite, $simpleDataTable, $metadataColumns, $hasDimension, $returnRawMetrics = false) - { - // new DataTable to store metadata - $rowsMetadata = new Piwik_DataTable(); - - // new DataTable to store 'human readable' values - if($hasDimension) - { - $enhancedDataTable = new Piwik_DataTable(); - } - else - { - $enhancedDataTable = new Piwik_DataTable_Simple(); - } - - // add missing metrics - foreach($simpleDataTable->getRows() as $row) - { - $rowMetrics = $row->getColumns(); - foreach($metadataColumns as $id => $name) - { - if(!isset($rowMetrics[$id])) - { - $row->addColumn($id, 0); - } - } - } - - foreach($simpleDataTable->getRows() as $row) - { - $enhancedRow = new Piwik_DataTable_Row(); - $enhancedDataTable->addRow($enhancedRow); - $rowMetrics = $row->getColumns(); - foreach($rowMetrics as $columnName => $columnValue) - { - // filter metrics according to metadata definition - if(isset($metadataColumns[$columnName])) - { - // generate 'human readable' metric values - $prettyValue = Piwik::getPrettyValue($idSite, $columnName, $columnValue, $htmlAllowed = false, $timeAsSentence = false); - $enhancedRow->addColumn($columnName, $prettyValue); - } - // For example the Maps Widget requires the raw metrics to do advanced datavis - elseif($returnRawMetrics) - { - $enhancedRow->addColumn($columnName, $columnValue); - } - } - - // If report has a dimension, extract metadata into a distinct DataTable - if($hasDimension) - { - $rowMetadata = $row->getMetadata(); - $idSubDataTable = $row->getIdSubDataTable(); - - // Create a row metadata only if there are metadata to insert - if(count($rowMetadata) > 0 || !is_null($idSubDataTable)) - { - $metadataRow = new Piwik_DataTable_Row(); - $rowsMetadata->addRow($metadataRow); - - foreach($rowMetadata as $metadataKey => $metadataValue) - { - $metadataRow->addColumn($metadataKey, $metadataValue); - } - - if(!is_null($idSubDataTable)) - { - $metadataRow->addColumn('idsubdatatable', $idSubDataTable); - } - } - } - } - - return array( - $enhancedDataTable, - $rowsMetadata - ); - } - - /** - * API metadata are sorted by category/name, - * with a little tweak to replicate the standard Piwik category ordering - * - * @param string $a - * @param string $b - * @return int - */ - private function sort($a, $b) - { - static $order = null; - if(is_null($order)) - { - $order = array( - Piwik_Translate('General_MultiSitesSummary'), - Piwik_Translate('VisitsSummary_VisitsSummary'), - Piwik_Translate('Goals_Ecommerce'), - Piwik_Translate('Actions_Actions'), - Piwik_Translate('Actions_SubmenuSitesearch'), - Piwik_Translate('Referers_Referers'), - Piwik_Translate('Goals_Goals'), - Piwik_Translate('General_Visitors'), - Piwik_Translate('UserSettings_VisitorSettings'), - ); - } - return ($category = strcmp(array_search($a['category'], $order), array_search($b['category'], $order))) == 0 - ? (@$a['order'] < @$b['order'] ? -1 : 1) - : $category; - } - - /** - * Get a combined report of the *.get API methods. - */ - public function get( $idSite, $period, $date, $segment = false, $columns = false) - { - $columns = Piwik::getArrayFromApiParameter($columns); - - // build columns map for faster checks later on - $columnsMap = array(); - foreach ($columns as $column) { - $columnsMap[$column] = true; - } - - // find out which columns belong to which plugin - $columnsByPlugin = array(); - $meta = Piwik_API_API::getInstance()->getReportMetadata($idSite, $period, $date); - foreach ($meta as $reportMeta) - { - // scan all *.get reports - if ($reportMeta['action'] == 'get' && !isset($reportMeta['parameters']) - && $reportMeta['module'] != 'API') - { - $plugin = $reportMeta['module']; - foreach ($reportMeta['metrics'] as $column => $columnTranslation) - { - // a metric from this report has been requested - if (isset($columnsMap[$column]) - // or by default, return all metrics - || empty($columnsMap)) - { - $columnsByPlugin[$plugin][] = $column; - } - } - } - } - krsort($columnsByPlugin); - - $mergedDataTable = false; - $params = compact('idSite', 'period', 'date', 'segment', 'idGoal'); - foreach ($columnsByPlugin as $plugin => $columns) - { - // load the data - $className = 'Piwik_'.$plugin.'_API'; - $params['columns'] = implode(',', $columns); - $dataTable = Piwik_API_Proxy::getInstance()->call($className, 'get', $params); - // make sure the table has all columns - $array = ($dataTable instanceof Piwik_DataTable_Array ? $dataTable->getArray() : array($dataTable)); - foreach ($array as $table) - { - // we don't support idSites=all&date=DATE1,DATE2 - if($table instanceof Piwik_DataTable) - { - $firstRow = $table->getFirstRow(); - if(!$firstRow) - { - $firstRow = new Piwik_DataTable_Row; - $table->addRow($firstRow); - } - foreach ($columns as $column) - { - if ($firstRow->getColumn($column) === false) - { - $firstRow->setColumn($column, 0); - } - } - } - } - - // merge reports - if($mergedDataTable === false) - { - $mergedDataTable = $dataTable; - } - else - { - $this->mergeDataTables($mergedDataTable, $dataTable); - } - } - return $mergedDataTable; - } - - - /** - * Merge the columns of two data tables. - * Manipulates the first table. - */ - private function mergeDataTables($table1, $table2) - { - // handle table arrays - if ($table1 instanceof Piwik_DataTable_Array && $table2 instanceof Piwik_DataTable_Array) - { - $subTables2 = $table2->getArray(); - foreach ($table1->getArray() as $index => $subTable1) - { - $subTable2 = $subTables2[$index]; - $this->mergeDataTables($subTable1, $subTable2); - } - return; - } - - $firstRow1 = $table1->getFirstRow(); - $firstRow2 = $table2->getFirstRow(); - if($firstRow2 instanceof Piwik_DataTable_Row) - { - foreach ($firstRow2->getColumns() as $metric => $value) - { - $firstRow1->setColumn($metric, $value); - } - } - } - - - /** - * Given an API report to query (eg. "Referers.getKeywords", and a Label (eg. "free%20software"), - * this function will query the API for the previous days/weeks/etc. and will return - * a ready to use data structure containing the metrics for the requested Label, along with enriched information (min/max values, etc.) - * - * @return array - */ - public function getRowEvolution($idSite, $period, $date, $apiModule, $apiAction, $label = false, $segment = false, $column = false, $language = false, $idGoal = false, $legendAppendMetric = true, $labelUseAbsoluteUrl = true) + /** + * Get row evolution for a single label + * @return array containing report data, metadata, label, logo + */ + private function getSingleRowEvolution($dataTable, $idSite, $period, $date, $apiModule, $apiAction, $label, $segment, $language = false, $idGoal = false, $labelUseAbsoluteUrl = true) { - // validation of requested $period & $date - if ($period == 'range') - { - // load days in the range - $period = 'day'; - } - - if(!Piwik_Archive::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); - - if($label) - { - $labels = explode(',', $label); - $labels = array_unique($labels); - } - else - { - $labels = array(); - } - - $dataTable = $this->loadRowEvolutionDataFromAPI($idSite, $period, $date, $apiModule, $apiAction, $labels, $segment, $idGoal); - - if (empty($labels)) - { - // if no labels specified, use all possible labels as list - foreach ($dataTable->getArray() as $table) - { - $labels = array_merge($labels, $table->getColumn('label')); - } - $labels = array_unique($labels); - } - - if (count($labels) > 1) - { - $data = $this->getMultiRowEvolution( - $dataTable, - $idSite, - $period, - $date, - $apiModule, - $apiAction, - $labels, - $segment, - $column, - $language, - $idGoal, - $legendAppendMetric, - $labelUseAbsoluteUrl - ); - } - else - { - $data = $this->getSingleRowEvolution( - $dataTable, - $idSite, - $period, - $date, - $apiModule, - $apiAction, - $labels[0], - $segment, - $language, - $idGoal, - $labelUseAbsoluteUrl - ); - } - return $data; - } - - /** - * Get row evolution for a single label - * @return array containing report data, metadata, label, logo - */ - private function getSingleRowEvolution($dataTable, $idSite, $period, $date, $apiModule, $apiAction, $label, $segment, $language=false, $idGoal = false, $labelUseAbsoluteUrl = true) - { - $metadata = $this->getRowEvolutionMetaData($idSite, $period, $date, $apiModule, $apiAction, $language, $idGoal); - $metricNames = array_keys($metadata['metrics']); - - $logo = $actualLabel = false; - $urlFound = false; - foreach ($dataTable->getArray() as $date => $subTable) - { - /** @var $subTable Piwik_DataTable */ - $subTable->applyQueuedFilters(); - if ($subTable->getRowsCount() > 0) - { - /** @var $row Piwik_DataTable_Row */ - $row = $subTable->getFirstRow(); - - if (!$actualLabel) - { - $logo = $row->getMetadata('logo'); - - $actualLabel = $this->getRowUrlForEvolutionLabel($row, $apiModule, $apiAction, $labelUseAbsoluteUrl); - $urlFound = $actualLabel !== false; - if(empty($actualLabel)) - { - $actualLabel = $row->getColumn('label'); - } - - } - - // remove all columns that are not in the available metrics. - // this removes the label as well (which is desired for two reasons: (1) it was passed - // in the request, (2) it would cause the evolution graph to show the label in the legend). - foreach ($row->getColumns() as $column => $value) - { - if (!in_array($column, $metricNames)) - { - $row->deleteColumn($column); - } - } - - $row->deleteMetadata(); - } - } - - $this->enhanceRowEvolutionMetaData($metadata, $dataTable); - - // if we have a recursive label and no url, use the path - if (!$urlFound) - { - $actualLabel = str_replace(Piwik_API_DataTableManipulator_LabelFilter::SEPARATOR_RECURSIVE_LABEL, ' - ', $label); - } - - $return = array( - 'label' => Piwik_DataTable_Filter_SafeDecodeLabel::safeDecodeLabel($actualLabel), - 'reportData' => $dataTable, - 'metadata' => $metadata - ); - if(!empty($logo)){ - $return['logo'] = $logo; - } - return $return; - } - - private function getRowUrlForEvolutionLabel($row, $apiModule, $apiAction, $labelUseAbsoluteUrl) - { - $url = $row->getMetadata('url'); - if ($url - && ($apiModule == 'Actions' - || ($apiModule == 'Referers' - && $apiAction == 'getWebsites')) - && $labelUseAbsoluteUrl - ) { - $actualLabel = preg_replace(';^http(s)?://(www.)?;i', '', $url); - return $actualLabel; - } - return false; - } - - /** - * @param $idSite - * @param $period - * @param $date - * @param $apiModule - * @param $apiAction - * @param $label - * @param $segment - * @param $idGoal - * @throws Exception - * @return Piwik_DataTable_Array|Piwik_DataTable - */ - private function loadRowEvolutionDataFromAPI($idSite, $period, $date, $apiModule, $apiAction, $label = false, $segment = false, $idGoal = false) - { - if (!is_array($label)) - { - $label = array($label); - } - $label = array_map('rawurlencode', $label); - - $parameters = array( - 'method' => $apiModule.'.'.$apiAction, - 'label' => $label, - 'idSite' => $idSite, - 'period' => $period, - 'date' => $date, - 'format' => 'original', - 'serialize' => '0', - 'segment' => $segment, - 'idGoal' => $idGoal, - - // if more than one label is used, we add empty rows for labels we can't - // find to ensure we know the order of the rows in the returned data table - 'labelFilterAddEmptyRows' => count($label) > 1 ? 1 : 0, - ); - - // 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 - // this is because the AddColumnProcessedMetrics filter removes all datable rows lacking this metric - if - ( - $apiModule != 'Actions' - && - ($apiModule != 'Goals' || ($apiAction != 'getVisitsUntilConversion' && $apiAction != 'getDaysToConversion')) - && $label // do not request processed metrics when retrieving top n labels - ) - { - $parameters['filter_add_columns_when_show_all_columns'] = '1'; - } - - $url = Piwik_Url::getQueryStringFromParameters($parameters); - + $metadata = $this->getRowEvolutionMetaData($idSite, $period, $date, $apiModule, $apiAction, $language, $idGoal); + $metricNames = array_keys($metadata['metrics']); + + $logo = $actualLabel = false; + $urlFound = false; + foreach ($dataTable->getArray() as $date => $subTable) { + /** @var $subTable Piwik_DataTable */ + $subTable->applyQueuedFilters(); + if ($subTable->getRowsCount() > 0) { + /** @var $row Piwik_DataTable_Row */ + $row = $subTable->getFirstRow(); + + if (!$actualLabel) { + $logo = $row->getMetadata('logo'); + + $actualLabel = $this->getRowUrlForEvolutionLabel($row, $apiModule, $apiAction, $labelUseAbsoluteUrl); + $urlFound = $actualLabel !== false; + if (empty($actualLabel)) { + $actualLabel = $row->getColumn('label'); + } + + } + + // remove all columns that are not in the available metrics. + // this removes the label as well (which is desired for two reasons: (1) it was passed + // in the request, (2) it would cause the evolution graph to show the label in the legend). + foreach ($row->getColumns() as $column => $value) { + if (!in_array($column, $metricNames)) { + $row->deleteColumn($column); + } + } + + $row->deleteMetadata(); + } + } + + $this->enhanceRowEvolutionMetaData($metadata, $dataTable); + + // if we have a recursive label and no url, use the path + if (!$urlFound) { + $actualLabel = str_replace(Piwik_API_DataTableManipulator_LabelFilter::SEPARATOR_RECURSIVE_LABEL, ' - ', $label); + } + + $return = array( + 'label' => Piwik_DataTable_Filter_SafeDecodeLabel::safeDecodeLabel($actualLabel), + 'reportData' => $dataTable, + 'metadata' => $metadata + ); + if (!empty($logo)) { + $return['logo'] = $logo; + } + return $return; + } + + private function getRowUrlForEvolutionLabel($row, $apiModule, $apiAction, $labelUseAbsoluteUrl) + { + $url = $row->getMetadata('url'); + if ($url + && ($apiModule == 'Actions' + || ($apiModule == 'Referers' + && $apiAction == 'getWebsites')) + && $labelUseAbsoluteUrl + ) { + $actualLabel = preg_replace(';^http(s)?://(www.)?;i', '', $url); + return $actualLabel; + } + return false; + } + + /** + * @param $idSite + * @param $period + * @param $date + * @param $apiModule + * @param $apiAction + * @param $label + * @param $segment + * @param $idGoal + * @throws Exception + * @return Piwik_DataTable_Array|Piwik_DataTable + */ + private function loadRowEvolutionDataFromAPI($idSite, $period, $date, $apiModule, $apiAction, $label = false, $segment = false, $idGoal = false) + { + if (!is_array($label)) { + $label = array($label); + } + $label = array_map('rawurlencode', $label); + + $parameters = array( + 'method' => $apiModule . '.' . $apiAction, + 'label' => $label, + 'idSite' => $idSite, + 'period' => $period, + 'date' => $date, + 'format' => 'original', + 'serialize' => '0', + 'segment' => $segment, + 'idGoal' => $idGoal, + + // if more than one label is used, we add empty rows for labels we can't + // find to ensure we know the order of the rows in the returned data table + 'labelFilterAddEmptyRows' => count($label) > 1 ? 1 : 0, + ); + + // 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 + // this is because the AddColumnProcessedMetrics filter removes all datable rows lacking this metric + if + ( + $apiModule != 'Actions' + && + ($apiModule != 'Goals' || ($apiAction != 'getVisitsUntilConversion' && $apiAction != 'getDaysToConversion')) + && $label // do not request processed metrics when retrieving top n labels + ) { + $parameters['filter_add_columns_when_show_all_columns'] = '1'; + } + + $url = Piwik_Url::getQueryStringFromParameters($parameters); + $request = new Piwik_API_Request($url); - try { - $dataTable = $request->process(); - } catch (Exception $e) { - throw new Exception("API returned an error: ".$e->getMessage()."\n"); - } - - return $dataTable; - } - - /** - * For a given API report, returns a simpler version - * of the metadata (will return only the metrics and the dimension name) - * @param $idSite - * @param $period - * @param $date - * @param $apiModule - * @param $apiAction - * @param $language - * @param $idGoal - * @throws Exception - * @return array - */ - private function getRowEvolutionMetaData($idSite, $period, $date, $apiModule, $apiAction, $language, $idGoal = false) - { - $apiParameters = array(); - if(!empty($idGoal) && $idGoal > 0 ) { - $apiParameters = array( 'idGoal' => $idGoal); - } - $reportMetadata = $this->getMetadata($idSite, $apiModule, $apiAction, $apiParameters, $language, $period, $date, $hideMetricsDoc = false, $showSubtableReports = true); - - if (empty($reportMetadata)) - { - throw new Exception("Requested report $apiModule.$apiAction for Website id=$idSite " - . "not found in the list of available reports. \n"); - } - - $reportMetadata = reset($reportMetadata); - - $metrics = $reportMetadata['metrics']; - if (isset($reportMetadata['processedMetrics']) && is_array($reportMetadata['processedMetrics'])) - { - $metrics = $metrics + $reportMetadata['processedMetrics']; - } - - $dimension = $reportMetadata['dimension']; - - return compact('metrics', 'dimension'); - } - - /** - * Given the Row evolution dataTable, and the associated metadata, - * enriches the metadata with min/max values, and % change between the first period and the last one - * @param array $metadata - * @param Piwik_DataTable_Array $dataTable - */ - private function enhanceRowEvolutionMetaData(&$metadata, $dataTable) - { - // prepare result array for metrics - $metricsResult = array(); - foreach ($metadata['metrics'] as $metric => $name) - { - $metricsResult[$metric] = array('name' => $name); - - if(!empty($metadata['logos'][$metric])) { - $metricsResult[$metric]['logo'] = $metadata['logos'][$metric]; - } - } - unset($metadata['logos']); - - $subDataTables = $dataTable->getArray(); - $firstDataTable = reset($subDataTables); - $firstDataTableRow = $firstDataTable->getFirstRow(); - $lastDataTable = end($subDataTables); - $lastDataTableRow = $lastDataTable->getFirstRow(); - - // Process min/max values - $firstNonZeroFound = array(); - foreach ($subDataTables as $subDataTable) - { - // $subDataTable is the report for one period, it has only one row - $firstRow = $subDataTable->getFirstRow(); - foreach ($metadata['metrics'] as $metric => $label) - { - $value = $firstRow ? floatval($firstRow->getColumn($metric)) : 0; - if ($value > 0) - { - $firstNonZeroFound[$metric] = true; - } - else if (!isset($firstNonZeroFound[$metric])) - { - continue; - } - if (!isset($metricsResult[$metric]['min']) - || $metricsResult[$metric]['min'] > $value) - { - $metricsResult[$metric]['min'] = $value; - } - if (!isset($metricsResult[$metric]['max']) - || $metricsResult[$metric]['max'] < $value) - { - $metricsResult[$metric]['max'] = $value; - } - } - } - - // Process % change between first/last values - foreach ($metadata['metrics'] as $metric => $label) - { - $first = $firstDataTableRow ? floatval($firstDataTableRow->getColumn($metric)) : 0; - $last = $lastDataTableRow ? floatval($lastDataTableRow->getColumn($metric)) : 0; - - // do not calculate evolution if the first value is 0 (to avoid divide-by-zero) - if ($first == 0) - { - continue; - } - - $change = Piwik_DataTable_Filter_CalculateEvolutionFilter::calculate($last, $first, $quotientPrecision = 0); + try { + $dataTable = $request->process(); + } catch (Exception $e) { + throw new Exception("API returned an error: " . $e->getMessage() . "\n"); + } + + return $dataTable; + } + + /** + * For a given API report, returns a simpler version + * of the metadata (will return only the metrics and the dimension name) + * @param $idSite + * @param $period + * @param $date + * @param $apiModule + * @param $apiAction + * @param $language + * @param $idGoal + * @throws Exception + * @return array + */ + private function getRowEvolutionMetaData($idSite, $period, $date, $apiModule, $apiAction, $language, $idGoal = false) + { + $apiParameters = array(); + if (!empty($idGoal) && $idGoal > 0) { + $apiParameters = array('idGoal' => $idGoal); + } + $reportMetadata = $this->getMetadata($idSite, $apiModule, $apiAction, $apiParameters, $language, $period, $date, $hideMetricsDoc = false, $showSubtableReports = true); + + if (empty($reportMetadata)) { + throw new Exception("Requested report $apiModule.$apiAction for Website id=$idSite " + . "not found in the list of available reports. \n"); + } + + $reportMetadata = reset($reportMetadata); + + $metrics = $reportMetadata['metrics']; + if (isset($reportMetadata['processedMetrics']) && is_array($reportMetadata['processedMetrics'])) { + $metrics = $metrics + $reportMetadata['processedMetrics']; + } + + $dimension = $reportMetadata['dimension']; + + return compact('metrics', 'dimension'); + } + + /** + * Given the Row evolution dataTable, and the associated metadata, + * enriches the metadata with min/max values, and % change between the first period and the last one + * @param array $metadata + * @param Piwik_DataTable_Array $dataTable + */ + private function enhanceRowEvolutionMetaData(&$metadata, $dataTable) + { + // prepare result array for metrics + $metricsResult = array(); + foreach ($metadata['metrics'] as $metric => $name) { + $metricsResult[$metric] = array('name' => $name); + + if (!empty($metadata['logos'][$metric])) { + $metricsResult[$metric]['logo'] = $metadata['logos'][$metric]; + } + } + unset($metadata['logos']); + + $subDataTables = $dataTable->getArray(); + $firstDataTable = reset($subDataTables); + $firstDataTableRow = $firstDataTable->getFirstRow(); + $lastDataTable = end($subDataTables); + $lastDataTableRow = $lastDataTable->getFirstRow(); + + // Process min/max values + $firstNonZeroFound = array(); + foreach ($subDataTables as $subDataTable) { + // $subDataTable is the report for one period, it has only one row + $firstRow = $subDataTable->getFirstRow(); + foreach ($metadata['metrics'] as $metric => $label) { + $value = $firstRow ? floatval($firstRow->getColumn($metric)) : 0; + if ($value > 0) { + $firstNonZeroFound[$metric] = true; + } else if (!isset($firstNonZeroFound[$metric])) { + continue; + } + if (!isset($metricsResult[$metric]['min']) + || $metricsResult[$metric]['min'] > $value + ) { + $metricsResult[$metric]['min'] = $value; + } + if (!isset($metricsResult[$metric]['max']) + || $metricsResult[$metric]['max'] < $value + ) { + $metricsResult[$metric]['max'] = $value; + } + } + } + + // Process % change between first/last values + foreach ($metadata['metrics'] as $metric => $label) { + $first = $firstDataTableRow ? floatval($firstDataTableRow->getColumn($metric)) : 0; + $last = $lastDataTableRow ? floatval($lastDataTableRow->getColumn($metric)) : 0; + + // do not calculate evolution if the first value is 0 (to avoid divide-by-zero) + if ($first == 0) { + continue; + } + + $change = Piwik_DataTable_Filter_CalculateEvolutionFilter::calculate($last, $first, $quotientPrecision = 0); $change = Piwik_DataTable_Filter_CalculateEvolutionFilter::prependPlusSignToNumber($change); - $metricsResult[$metric]['change'] = $change; - } - - $metadata['metrics'] = $metricsResult; - } - - /** Get row evolution for a multiple labels */ - private function getMultiRowEvolution($dataTable, $idSite, $period, $date, $apiModule, $apiAction, $labels, $segment, $column, $language=false, $idGoal=false, $legendAppendMetric=true, $labelUseAbsoluteUrl=true) - { - $actualLabels = $logos = array(); - - $metadata = $this->getRowEvolutionMetaData($idSite, $period, $date, $apiModule, $apiAction, $language, $idGoal); - - if (!isset($metadata['metrics'][$column])) - { - // invalid column => use the first one that's available - $metrics = array_keys($metadata['metrics']); - $column = reset($metrics); - } - - // get the processed label and logo (if any) for every requested label - $actualLabels = $logos = array(); - foreach ($labels as $labelIdx => $label) - { - foreach ($dataTable->getArray() as $table) - { - // find row for this label. LabelFilter will add empty rows and - // keep them ordered in the same way the labels array is, so we - // assume the $labelIdx is also the row Id - $labelRow = $table->getRowFromId($labelIdx); - - if ($labelRow) - { - $actualLabels[$labelIdx] = $this->getRowUrlForEvolutionLabel( - $labelRow, $apiModule, $apiAction, $labelUseAbsoluteUrl); - - $logos[$labelIdx] = $labelRow->getMetadata('logo'); - - if (!empty($actualLabels[$labelIdx])) - { - break; - } - } - } - - if (empty($actualLabels[$labelIdx])) - { - $actualLabels[$labelIdx] = $this->cleanOriginalLabel($label); - } - } - - // convert rows to be array($column.'_'.$labelIdx => $value) as opposed to - // array('label' => $label, 'column' => $value). - $dataTableMulti = $dataTable->getEmptyClone(); - foreach ($dataTable->getArray() as $tableLabel => $table) - { - $newRow = new Piwik_DataTable_Row(); - - foreach ($table->getRows() as $rowId => $row) - { - $value = $row->getColumn($column); - $value = floatVal(str_replace(',', '.', $value)); - if ($value == '') - { - $value = 0; - } - - $newLabel = $column.'_'.$rowId; // $rowId corresponds to the label index - - $newRow->addColumn($newLabel, $value); - } - - $newTable = $table->getEmptyClone(); - $newTable->addRow($newRow); - $dataTableMulti->addTable($newTable, $tableLabel); - } - - // the available metrics for the report are returned as metadata / columns - $metadata['columns'] = $metadata['metrics']; - - // metadata / metrics should document the rows that are compared - // this way, UI code can be reused - $metadata['metrics'] = array(); - foreach ($actualLabels as $labelIndex => $label) - { - if($legendAppendMetric) - { - $label .= ' ('.$metadata['columns'][$column].')'; - } - $metricName = $column.'_'.$labelIndex; - $metadata['metrics'][$metricName] = Piwik_DataTable_Filter_SafeDecodeLabel::safeDecodeLabel($label); - - if(!empty($logos[$labelIndex])) - { - $metadata['logos'][$metricName] = $logos[$labelIndex]; - } - } - - $this->enhanceRowEvolutionMetaData($metadata, $dataTableMulti); - - return array( - 'column' => $column, - 'reportData' => $dataTableMulti, - 'metadata' => $metadata - ); - } - - /** - * Returns a prettier, more comprehensible version of a row evolution label - * for display. - */ - private function cleanOriginalLabel( $label ) - { - return str_replace(Piwik_API_DataTableManipulator_LabelFilter::SEPARATOR_RECURSIVE_LABEL, ' - ', $label); - } - - /** - * Performs multiple API requests at once and returns every result. - * - * @param array $urls The array of API requests. - */ - public function getBulkRequest( $urls ) - { - if (empty($urls)) - { - return array(); - } - - $urls = Piwik_Common::unsanitizeInputValues($urls); - - $result = array(); - foreach ($urls as $url) - { - $req = new Piwik_API_Request($url); - $result[] = $req->process(); - } - return $result; - } + $metricsResult[$metric]['change'] = $change; + } + + $metadata['metrics'] = $metricsResult; + } + + /** Get row evolution for a multiple labels */ + private function getMultiRowEvolution($dataTable, $idSite, $period, $date, $apiModule, $apiAction, $labels, $segment, $column, $language = false, $idGoal = false, $legendAppendMetric = true, $labelUseAbsoluteUrl = true) + { + $actualLabels = $logos = array(); + + $metadata = $this->getRowEvolutionMetaData($idSite, $period, $date, $apiModule, $apiAction, $language, $idGoal); + + if (!isset($metadata['metrics'][$column])) { + // invalid column => use the first one that's available + $metrics = array_keys($metadata['metrics']); + $column = reset($metrics); + } + + // get the processed label and logo (if any) for every requested label + $actualLabels = $logos = array(); + foreach ($labels as $labelIdx => $label) { + foreach ($dataTable->getArray() as $table) { + // find row for this label. LabelFilter will add empty rows and + // keep them ordered in the same way the labels array is, so we + // assume the $labelIdx is also the row Id + $labelRow = $table->getRowFromId($labelIdx); + + if ($labelRow) { + $actualLabels[$labelIdx] = $this->getRowUrlForEvolutionLabel( + $labelRow, $apiModule, $apiAction, $labelUseAbsoluteUrl); + + $logos[$labelIdx] = $labelRow->getMetadata('logo'); + + if (!empty($actualLabels[$labelIdx])) { + break; + } + } + } + + if (empty($actualLabels[$labelIdx])) { + $actualLabels[$labelIdx] = $this->cleanOriginalLabel($label); + } + } + + // convert rows to be array($column.'_'.$labelIdx => $value) as opposed to + // array('label' => $label, 'column' => $value). + $dataTableMulti = $dataTable->getEmptyClone(); + foreach ($dataTable->getArray() as $tableLabel => $table) { + $newRow = new Piwik_DataTable_Row(); + + foreach ($table->getRows() as $rowId => $row) { + $value = $row->getColumn($column); + $value = floatVal(str_replace(',', '.', $value)); + if ($value == '') { + $value = 0; + } + + $newLabel = $column . '_' . $rowId; // $rowId corresponds to the label index + + $newRow->addColumn($newLabel, $value); + } + + $newTable = $table->getEmptyClone(); + $newTable->addRow($newRow); + $dataTableMulti->addTable($newTable, $tableLabel); + } + + // the available metrics for the report are returned as metadata / columns + $metadata['columns'] = $metadata['metrics']; + + // metadata / metrics should document the rows that are compared + // this way, UI code can be reused + $metadata['metrics'] = array(); + foreach ($actualLabels as $labelIndex => $label) { + if ($legendAppendMetric) { + $label .= ' (' . $metadata['columns'][$column] . ')'; + } + $metricName = $column . '_' . $labelIndex; + $metadata['metrics'][$metricName] = Piwik_DataTable_Filter_SafeDecodeLabel::safeDecodeLabel($label); + + if (!empty($logos[$labelIndex])) { + $metadata['logos'][$metricName] = $logos[$labelIndex]; + } + } + + $this->enhanceRowEvolutionMetaData($metadata, $dataTableMulti); + + return array( + 'column' => $column, + 'reportData' => $dataTableMulti, + 'metadata' => $metadata + ); + } + + /** + * Returns a prettier, more comprehensible version of a row evolution label + * for display. + */ + private function cleanOriginalLabel($label) + { + return str_replace(Piwik_API_DataTableManipulator_LabelFilter::SEPARATOR_RECURSIVE_LABEL, ' - ', $label); + } + + /** + * Performs multiple API requests at once and returns every result. + * + * @param array $urls The array of API requests. + */ + public function getBulkRequest($urls) + { + if (empty($urls)) { + return array(); + } + + $urls = Piwik_Common::unsanitizeInputValues($urls); + + $result = array(); + foreach ($urls as $url) { + $req = new Piwik_API_Request($url); + $result[] = $req->process(); + } + return $result; + } } diff --git a/plugins/API/Controller.php b/plugins/API/Controller.php index 134b055715..6c3a5424fa 100644 --- a/plugins/API/Controller.php +++ b/plugins/API/Controller.php @@ -1,109 +1,105 @@ General['API_datatable_default_limit']; - } - $request = new Piwik_API_Request('token_auth='.Piwik_Common::getRequestVar('token_auth', 'anonymous', 'string')); - echo $request->process(); - } + function index() + { + // when calling the API through http, we limit the number of returned results + if (!isset($_GET['filter_limit'])) { + $_GET['filter_limit'] = Piwik_Config::getInstance()->General['API_datatable_default_limit']; + } + $request = new Piwik_API_Request('token_auth=' . Piwik_Common::getRequestVar('token_auth', 'anonymous', 'string')); + echo $request->process(); + } - public function listAllMethods() - { - $ApiDocumentation = new Piwik_API_DocumentationGenerator(); - echo $ApiDocumentation->getAllInterfaceString( $outputExampleUrls = true, $prefixUrls = Piwik_Common::getRequestVar('prefixUrl', '') ); - } - - public function listAllAPI() - { - $view = Piwik_View::factory("listAllAPI"); - $this->setGeneralVariablesView($view); - - $ApiDocumentation = new Piwik_API_DocumentationGenerator(); - $view->countLoadedAPI = Piwik_API_Proxy::getInstance()->getCountRegisteredClasses(); - $view->list_api_methods_with_links = $ApiDocumentation->getAllInterfaceString(); - echo $view->render(); - } - - public function listSegments() - { - $segments = Piwik_API_API::getInstance()->getSegmentsMetadata($this->idSite); - - $tableDimensions = $tableMetrics = ''; - $customVariables=0; - $lastCategory=array(); - foreach($segments as $segment) - { - $onlyDisplay = array('customVariableName1', 'customVariableName2', 'customVariableValue1', 'customVariableValue2', 'customVariablePageName1', 'customVariablePageValue1'); - $customVariableWillBeDisplayed = in_array($segment['segment'], $onlyDisplay); - // Don't display more than 4 custom variables name/value rows - if($segment['category'] == 'Custom Variables' - && !$customVariableWillBeDisplayed) - { - continue; - } - - $thisCategory = $segment['category']; - $output = ''; - if(empty($lastCategory[$segment['type']]) - || $lastCategory[$segment['type']] != $thisCategory) - { - $output .= ''.$thisCategory.''; - } - - $lastCategory[$segment['type']] = $thisCategory; - - $exampleValues = isset($segment['acceptedValues']) - ? 'Example values: '.$segment['acceptedValues'].'' - : ''; - $restrictedToAdmin = isset($segment['permission']) ? '
Note: This segment can only be used by an Admin user' : ''; - $output .= ' - '.$segment['segment'].' - '.$segment['name'] .$restrictedToAdmin.'
'.$exampleValues.' + public function listAllMethods() + { + $ApiDocumentation = new Piwik_API_DocumentationGenerator(); + echo $ApiDocumentation->getAllInterfaceString($outputExampleUrls = true, $prefixUrls = Piwik_Common::getRequestVar('prefixUrl', '')); + } + + public function listAllAPI() + { + $view = Piwik_View::factory("listAllAPI"); + $this->setGeneralVariablesView($view); + + $ApiDocumentation = new Piwik_API_DocumentationGenerator(); + $view->countLoadedAPI = Piwik_API_Proxy::getInstance()->getCountRegisteredClasses(); + $view->list_api_methods_with_links = $ApiDocumentation->getAllInterfaceString(); + echo $view->render(); + } + + public function listSegments() + { + $segments = Piwik_API_API::getInstance()->getSegmentsMetadata($this->idSite); + + $tableDimensions = $tableMetrics = ''; + $customVariables = 0; + $lastCategory = array(); + foreach ($segments as $segment) { + $onlyDisplay = array('customVariableName1', 'customVariableName2', 'customVariableValue1', 'customVariableValue2', 'customVariablePageName1', 'customVariablePageValue1'); + $customVariableWillBeDisplayed = in_array($segment['segment'], $onlyDisplay); + // Don't display more than 4 custom variables name/value rows + if ($segment['category'] == 'Custom Variables' + && !$customVariableWillBeDisplayed + ) { + continue; + } + + $thisCategory = $segment['category']; + $output = ''; + if (empty($lastCategory[$segment['type']]) + || $lastCategory[$segment['type']] != $thisCategory + ) { + $output .= '' . $thisCategory . ''; + } + + $lastCategory[$segment['type']] = $thisCategory; + + $exampleValues = isset($segment['acceptedValues']) + ? 'Example values: ' . $segment['acceptedValues'] . '' + : ''; + $restrictedToAdmin = isset($segment['permission']) ? '
Note: This segment can only be used by an Admin user' : ''; + $output .= ' + ' . $segment['segment'] . ' + ' . $segment['name'] . $restrictedToAdmin . '
' . $exampleValues . ' '; - - // Show only 2 custom variables and display message for rest - if($customVariableWillBeDisplayed) - { - $customVariables++; - if($customVariables == count($onlyDisplay)) - { - $output .= ' There are 5 custom variables available, so you can segment across any segment name and value range. + + // Show only 2 custom variables and display message for rest + if ($customVariableWillBeDisplayed) { + $customVariables++; + if ($customVariables == count($onlyDisplay)) { + $output .= ' There are 5 custom variables available, so you can segment across any segment name and value range.
For example, customVariableName1==Type;customVariableValue1==Customer
Returns all visitors that have the Custom Variable "Type" set to "Customer".
Custom Variables of scope "page" can be queried separately. For example, to query the Custom Variable of scope "page",
stored in index 1, you would use the segment customVariablePageName1==ArticleLanguage;customVariablePageValue1==FR '; - } - } - - - if($segment['type'] == 'dimension') { - $tableDimensions .= $output; - } else { - $tableMetrics .= $output; - } - } - - echo " + } + } + + + if ($segment['type'] == 'dimension') { + $tableDimensions .= $output; + } else { + $tableMetrics .= $output; + } + } + + echo " Dimensions $tableDimensions @@ -114,5 +110,5 @@ class Piwik_API_Controller extends Piwik_Controller $tableMetrics
"; - } + } } diff --git a/plugins/API/css/styles.css b/plugins/API/css/styles.css index 2b2f547da6..06a3c9f435 100644 --- a/plugins/API/css/styles.css +++ b/plugins/API/css/styles.css @@ -1,45 +1,48 @@ - #token_auth { - background-color:#E8FFE9; - border:1px solid #00CC3A; - margin: 0 0 16px 8px; - padding: 12px; - line-height:4em; + background-color: #E8FFE9; + border: 1px solid #00CC3A; + margin: 0 0 16px 8px; + padding: 12px; + line-height: 4em; } + .example, .example A { - color:#9E9E9E; + color: #9E9E9E; } -.page_api{ - padding:0 15px 0 15px; - font-size:13px; +.page_api { + padding: 0 15px 0 15px; + font-size: 13px; } .page_api h2 { - border-bottom:1px solid #DADADA; - margin:10px -15px 15px 0; - padding:0 0 5px 0; - font-size:24px; + border-bottom: 1px solid #DADADA; + margin: 10px -15px 15px 0; + padding: 0 0 5px 0; + font-size: 24px; } .page_api p { - line-height:140%; - padding-bottom:20px; + line-height: 140%; + padding-bottom: 20px; } .apiFirstLine { - font-weight:bold; - padding-bottom:10px; + font-weight: bold; + padding-bottom: 10px; } + .page_api ul { list-style: disc outside none; margin-left: 25px; } + .apiDescription { - line-height:1.5em; - padding-bottom:1em; + line-height: 1.5em; + padding-bottom: 1em; } + .apiMethod { - margin-bottom:5px; - margin-left:20px; + margin-bottom: 5px; + margin-left: 20px; } \ No newline at end of file diff --git a/plugins/API/templates/listAllAPI.tpl b/plugins/API/templates/listAllAPI.tpl index d59d651500..70f02ef4a9 100644 --- a/plugins/API/templates/listAllAPI.tpl +++ b/plugins/API/templates/listAllAPI.tpl @@ -7,24 +7,28 @@
{include file="CoreHome/templates/period_select.tpl"}
- +

{'API_QuickDocumentationTitle'|translate}

+

{'API_PluginDescription'|translate}

- + {if $isSuperUser}

{'API_GenerateVisits'|translate:'VisitorGenerator':'VisitorGenerator'}

{/if} - -

{'API_MoreInformation'|translate:"":"":"":""}

- + +

+ {'API_MoreInformation'|translate:"":"":"":""} +

+

{'API_UserAuthentication'|translate}

+

- {'API_UsingTokenAuth'|translate:'':'':""}
- &token_auth={$token_auth}
- {'API_KeepTokenSecret'|translate:'':''} - - {$list_api_methods_with_links} -
+ {'API_UsingTokenAuth'|translate:'':'':""}
+ &token_auth={$token_auth}
+ {'API_KeepTokenSecret'|translate:'':''} + + {$list_api_methods_with_links} +
{include file="CoreHome/templates/footer.tpl"} diff --git a/plugins/Actions/API.php b/plugins/Actions/API.php index bb0367c742..060912f19f 100644 --- a/plugins/Actions/API.php +++ b/plugins/Actions/API.php @@ -1,10 +1,10 @@ Actions metrics for each row. - * - * It is also possible to request data for a specific Page Title with "getPageTitle" - * and setting the parameter pageName to the page title you wish to request. - * Similarly, you can request metrics for a given Page URL via "getPageUrl", a Download file via "getDownload" + * + * It is also possible to request data for a specific Page Title with "getPageTitle" + * and setting the parameter pageName to the page title you wish to request. + * Similarly, you can request metrics for a given Page URL via "getPageUrl", a Download file via "getDownload" * and an outlink via "getOutlink". - * + * * Note: pageName, pageUrl, outlinkUrl, downloadUrl parameters must be URL encoded before you call the API. * @package Piwik_Actions */ class Piwik_Actions_API { - static private $instance = null; - - /** - * @return Piwik_Actions_API - */ - static public function getInstance() - { - if (self::$instance == null) - { - self::$instance = new self; - } - return self::$instance; - } - - - /** - * Backward compatibility. Fallsback to getPageTitles() instead. - * @deprecated Deprecated since Piwik 0.5 - * @ignore - * - * @param int $idSite - * @param string $period - * @param $date - * @param bool $segment - * @param bool $expanded - * @param bool|int $idSubtable - * @return Piwik_DataTable|Piwik_DataTable_Array - */ - public function getActions( $idSite, $period, $date, $segment = false, $expanded = false, $idSubtable = false ) - { - return $this->getPageTitles( $idSite, $period, $date, $segment, $expanded, $idSubtable ); - } - - /** - * Returns the list of metrics (pages, downloads, outlinks) - * - * @param int $idSite - * @param string $period - * @param string $date - * @param bool|string $segment - * @param bool|array $columns - * @return Piwik_DataTable - */ - public function get( $idSite, $period, $date, $segment = false, $columns = false) - { - 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', - ); - - // get requested columns - $columns = Piwik::getArrayFromApiParameter($columns); - if(!empty($columns)) - { - // get the columns that are available and requested - $columns = array_intersect($columns, array_values($metrics)); - $columns = array_values($columns); // make sure indexes are right - $nameReplace = array(); - foreach ($columns as $i => $column) - { - $fullColumn = array_search($column, $metrics); - $columns[$i] = $fullColumn; - $nameReplace[$fullColumn] = $column; - } - } - else - { - // get all columns - $columns = array_keys($metrics); - $nameReplace = &$metrics; - } - - $table = $archive->getDataTableFromNumeric($columns); - - // replace labels (remove Actions_) - $table->filter('ReplaceColumnNames', array($nameReplace)); - - return $table; - } + static private $instance = null; /** - * @param int $idSite - * @param string $period - * @param Piwik_Date $date - * @param bool $segment - * @param bool $expanded - * @param bool $idSubtable + * @return Piwik_Actions_API + */ + static public function getInstance() + { + if (self::$instance == null) { + self::$instance = new self; + } + return self::$instance; + } + + + /** + * Backward compatibility. Fallsback to getPageTitles() instead. + * @deprecated Deprecated since Piwik 0.5 + * @ignore * + * @param int $idSite + * @param string $period + * @param $date + * @param bool $segment + * @param bool $expanded + * @param bool|int $idSubtable * @return Piwik_DataTable|Piwik_DataTable_Array */ - public function getPageUrls( $idSite, $period, $date, $segment = false, $expanded = false, $idSubtable = false ) - { - $dataTable = Piwik_Archive::getDataTableFromArchive('Actions_actions_url', $idSite, $period, $date, $segment, $expanded, $idSubtable ); - $this->filterPageDatatable($dataTable); - $this->filterActionsDataTable($dataTable, $expanded); - return $dataTable; - } + public function getActions($idSite, $period, $date, $segment = false, $expanded = false, $idSubtable = false) + { + return $this->getPageTitles($idSite, $period, $date, $segment, $expanded, $idSubtable); + } /** - * @param int $idSite - * @param string $period - * @param Piwik_Date $date - * @param bool $segment - * @param bool $expanded - * @param bool $idSubtable + * Returns the list of metrics (pages, downloads, outlinks) + * + * @param int $idSite + * @param string $period + * @param string $date + * @param bool|string $segment + * @param bool|array $columns + * @return Piwik_DataTable + */ + public function get($idSite, $period, $date, $segment = false, $columns = false) + { + 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', + ); + + // get requested columns + $columns = Piwik::getArrayFromApiParameter($columns); + if (!empty($columns)) { + // get the columns that are available and requested + $columns = array_intersect($columns, array_values($metrics)); + $columns = array_values($columns); // make sure indexes are right + $nameReplace = array(); + foreach ($columns as $i => $column) { + $fullColumn = array_search($column, $metrics); + $columns[$i] = $fullColumn; + $nameReplace[$fullColumn] = $column; + } + } else { + // get all columns + $columns = array_keys($metrics); + $nameReplace = & $metrics; + } + + $table = $archive->getDataTableFromNumeric($columns); + + // replace labels (remove Actions_) + $table->filter('ReplaceColumnNames', array($nameReplace)); + + return $table; + } + + /** + * @param int $idSite + * @param string $period + * @param Piwik_Date $date + * @param bool $segment + * @param bool $expanded + * @param bool $idSubtable + * + * @return Piwik_DataTable|Piwik_DataTable_Array + */ + public function getPageUrls($idSite, $period, $date, $segment = false, $expanded = false, $idSubtable = false) + { + $dataTable = Piwik_Archive::getDataTableFromArchive('Actions_actions_url', $idSite, $period, $date, $segment, $expanded, $idSubtable); + $this->filterPageDatatable($dataTable); + $this->filterActionsDataTable($dataTable, $expanded); + return $dataTable; + } + + /** + * @param int $idSite + * @param string $period + * @param Piwik_Date $date + * @param bool $segment + * @param bool $expanded + * @param bool $idSubtable * * @return Piwik_DataTable|Piwik_DataTable_Array */ - public function getPageUrlsFollowingSiteSearch( $idSite, $period, $date, $segment = false, $expanded = false, $idSubtable = false ) - { - $dataTable = $this->getPageUrls($idSite, $period, $date, $segment, $expanded, $idSubtable); - $this->keepPagesFollowingSearch($dataTable); - return $dataTable; - } + public function getPageUrlsFollowingSiteSearch($idSite, $period, $date, $segment = false, $expanded = false, $idSubtable = false) + { + $dataTable = $this->getPageUrls($idSite, $period, $date, $segment, $expanded, $idSubtable); + $this->keepPagesFollowingSearch($dataTable); + return $dataTable; + } /** - * @param int $idSite - * @param string $period - * @param Piwik_Date $date - * @param bool $segment - * @param bool $expanded - * @param bool $idSubtable + * @param int $idSite + * @param string $period + * @param Piwik_Date $date + * @param bool $segment + * @param bool $expanded + * @param bool $idSubtable * * @return Piwik_DataTable|Piwik_DataTable_Array */ - public function getPageTitlesFollowingSiteSearch( $idSite, $period, $date, $segment = false, $expanded = false, $idSubtable = false ) - { - $dataTable = $this->getPageTitles($idSite, $period, $date, $segment, $expanded, $idSubtable); - $this->keepPagesFollowingSearch($dataTable); - return $dataTable; - } + public function getPageTitlesFollowingSiteSearch($idSite, $period, $date, $segment = false, $expanded = false, $idSubtable = false) + { + $dataTable = $this->getPageTitles($idSite, $period, $date, $segment, $expanded, $idSubtable); + $this->keepPagesFollowingSearch($dataTable); + return $dataTable; + } /** * @param Piwik_DataTable $dataTable */ protected function keepPagesFollowingSearch($dataTable) - { - // Keep only pages which are following site search - $dataTable->filter('ColumnCallbackDeleteRow', array( - 'nb_hits_following_search', - create_function('$value', 'return $value > 0;') - )); - } - - /** - * Returns a DataTable with analytics information for every unique entry page URL, for - * the specified site, period & segment. - */ - public function getEntryPageUrls( $idSite, $period, $date, $segment = false, $expanded = false, $idSubtable = false ) - { - $dataTable = $this->getPageUrls($idSite, $period, $date, $segment, $expanded, $idSubtable); - $this->filterNonEntryActions($dataTable); - return $dataTable; - } - - /** - * Returns a DataTable with analytics information for every unique exit page URL, for - * the specified site, period & segment. - */ - public function getExitPageUrls( $idSite, $period, $date, $segment = false, $expanded = false, $idSubtable = false ) - { - $dataTable = $this->getPageUrls($idSite, $period, $date, $segment, $expanded, $idSubtable); - $this->filterNonExitActions($dataTable); - return $dataTable; - } - - public function getPageUrl( $pageUrl, $idSite, $period, $date, $segment = false) - { - $callBackParameters = array('Actions_actions_url', $idSite, $period, $date, $segment, $expanded = false, $idSubtable = false ); - $dataTable = $this->getFilterPageDatatableSearch($callBackParameters, $pageUrl, Piwik_Tracker_Action::TYPE_ACTION_URL); - $this->filterPageDatatable($dataTable); - $this->filterActionsDataTable($dataTable); - return $dataTable; - } - - public function getPageTitles( $idSite, $period, $date, $segment = false, $expanded = false, $idSubtable = false) - { - $dataTable = Piwik_Archive::getDataTableFromArchive('Actions_actions', $idSite, $period, $date, $segment, $expanded, $idSubtable); - $this->filterPageDatatable($dataTable); - $this->filterActionsDataTable($dataTable, $expanded); - return $dataTable; - } - - /** - * Returns a Piwik_DataTable with analytics information for every unique entry page title - * for the given site, time period & segment. - */ - public function getEntryPageTitles( $idSite, $period, $date, $segment = false, $expanded = false, - $idSubtable = false ) - { - $dataTable = $this->getPageTitles($idSite, $period, $date, $segment, $expanded, $idSubtable); - $this->filterNonEntryActions($dataTable); - return $dataTable; - } - - /** - * Returns a Piwik_DataTable with analytics information for every unique exit page title - * for the given site, time period & segment. - */ - public function getExitPageTitles( $idSite, $period, $date, $segment = false, $expanded = false, - $idSubtable = false ) - { - $dataTable = $this->getPageTitles($idSite, $period, $date, $segment, $expanded, $idSubtable); - $this->filterNonExitActions($dataTable); - return $dataTable; - } - - public function getPageTitle( $pageName, $idSite, $period, $date, $segment = false) - { - $callBackParameters = array('Actions_actions', $idSite, $period, $date, $segment, $expanded = false, $idSubtable = false ); - $dataTable = $this->getFilterPageDatatableSearch($callBackParameters, $pageName, Piwik_Tracker_Action::TYPE_ACTION_NAME); - $this->filterPageDatatable($dataTable); - $this->filterActionsDataTable($dataTable); - return $dataTable; - } - - public function getDownloads( $idSite, $period, $date, $segment = false, $expanded = false, $idSubtable = false ) - { - $dataTable = Piwik_Archive::getDataTableFromArchive('Actions_downloads', $idSite, $period, $date, $segment, $expanded, $idSubtable ); - $this->filterActionsDataTable($dataTable, $expanded); - return $dataTable; - } - - public function getDownload( $downloadUrl, $idSite, $period, $date, $segment = false) - { - $callBackParameters = array('Actions_downloads', $idSite, $period, $date, $segment, $expanded = false, $idSubtable = false ); - $dataTable = $this->getFilterPageDatatableSearch($callBackParameters, $downloadUrl, Piwik_Tracker_Action::TYPE_DOWNLOAD); - $this->filterActionsDataTable($dataTable); - return $dataTable; - } - - public function getOutlinks( $idSite, $period, $date, $segment = false, $expanded = false, $idSubtable = false ) - { - $dataTable = Piwik_Archive::getDataTableFromArchive('Actions_outlink', $idSite, $period, $date, $segment, $expanded, $idSubtable ); - $this->filterActionsDataTable($dataTable, $expanded); - return $dataTable; - } - - public function getOutlink( $outlinkUrl, $idSite, $period, $date, $segment = false) - { - $callBackParameters = array('Actions_outlink', $idSite, $period, $date, $segment, $expanded = false, $idSubtable = false ); - $dataTable = $this->getFilterPageDatatableSearch($callBackParameters, $outlinkUrl, Piwik_Tracker_Action::TYPE_OUTLINK); - $this->filterActionsDataTable($dataTable); - return $dataTable; - } - - 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); - $this->filterPageDatatable($dataTable); - $this->filterActionsDataTable($dataTable); - $this->addPagesPerSearchColumn($dataTable); - return $dataTable; - } - - //Visitors can search, and then click "next" to view more results. This is the average number of search results pages viewed for this keyword. - public function addPagesPerSearchColumn($dataTable, $columnToRead = 'nb_hits') - { - $dataTable->filter('ColumnCallbackAddColumnQuotient', array('nb_pages_per_search', $columnToRead, 'nb_visits', $precision = 1)); - } - - protected function getSiteSearchKeywordsRaw($idSite, $period, $date, $segment) - { - $dataTable = Piwik_Archive::getDataTableFromArchive('Actions_sitesearch', $idSite, $period, $date, $segment, $expanded = false); - return $dataTable; - } - - public function getSiteSearchNoResultKeywords( $idSite, $period, $date, $segment = false ) - { - $dataTable = $this->getSiteSearchKeywordsRaw($idSite, $period, $date, $segment); - // Delete all rows that have some results - $dataTable->filter('ColumnCallbackDeleteRow', - array( - Piwik_Archive::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); - $this->filterPageDatatable($dataTable); - $this->filterActionsDataTable($dataTable); - $this->addPagesPerSearchColumn($dataTable); - return $dataTable; - } + { + // Keep only pages which are following site search + $dataTable->filter('ColumnCallbackDeleteRow', array( + 'nb_hits_following_search', + create_function('$value', 'return $value > 0;') + )); + } + + /** + * Returns a DataTable with analytics information for every unique entry page URL, for + * the specified site, period & segment. + */ + public function getEntryPageUrls($idSite, $period, $date, $segment = false, $expanded = false, $idSubtable = false) + { + $dataTable = $this->getPageUrls($idSite, $period, $date, $segment, $expanded, $idSubtable); + $this->filterNonEntryActions($dataTable); + return $dataTable; + } /** - * @param int $idSite - * @param string $period - * @param Piwik_Date $date - * @param bool $segment + * Returns a DataTable with analytics information for every unique exit page URL, for + * the specified site, period & segment. + */ + public function getExitPageUrls($idSite, $period, $date, $segment = false, $expanded = false, $idSubtable = false) + { + $dataTable = $this->getPageUrls($idSite, $period, $date, $segment, $expanded, $idSubtable); + $this->filterNonExitActions($dataTable); + return $dataTable; + } + + public function getPageUrl($pageUrl, $idSite, $period, $date, $segment = false) + { + $callBackParameters = array('Actions_actions_url', $idSite, $period, $date, $segment, $expanded = false, $idSubtable = false); + $dataTable = $this->getFilterPageDatatableSearch($callBackParameters, $pageUrl, Piwik_Tracker_Action::TYPE_ACTION_URL); + $this->filterPageDatatable($dataTable); + $this->filterActionsDataTable($dataTable); + return $dataTable; + } + + public function getPageTitles($idSite, $period, $date, $segment = false, $expanded = false, $idSubtable = false) + { + $dataTable = Piwik_Archive::getDataTableFromArchive('Actions_actions', $idSite, $period, $date, $segment, $expanded, $idSubtable); + $this->filterPageDatatable($dataTable); + $this->filterActionsDataTable($dataTable, $expanded); + return $dataTable; + } + + /** + * Returns a Piwik_DataTable with analytics information for every unique entry page title + * for the given site, time period & segment. + */ + public function getEntryPageTitles($idSite, $period, $date, $segment = false, $expanded = false, + $idSubtable = false) + { + $dataTable = $this->getPageTitles($idSite, $period, $date, $segment, $expanded, $idSubtable); + $this->filterNonEntryActions($dataTable); + return $dataTable; + } + + /** + * Returns a Piwik_DataTable with analytics information for every unique exit page title + * for the given site, time period & segment. + */ + public function getExitPageTitles($idSite, $period, $date, $segment = false, $expanded = false, + $idSubtable = false) + { + $dataTable = $this->getPageTitles($idSite, $period, $date, $segment, $expanded, $idSubtable); + $this->filterNonExitActions($dataTable); + return $dataTable; + } + + public function getPageTitle($pageName, $idSite, $period, $date, $segment = false) + { + $callBackParameters = array('Actions_actions', $idSite, $period, $date, $segment, $expanded = false, $idSubtable = false); + $dataTable = $this->getFilterPageDatatableSearch($callBackParameters, $pageName, Piwik_Tracker_Action::TYPE_ACTION_NAME); + $this->filterPageDatatable($dataTable); + $this->filterActionsDataTable($dataTable); + return $dataTable; + } + + public function getDownloads($idSite, $period, $date, $segment = false, $expanded = false, $idSubtable = false) + { + $dataTable = Piwik_Archive::getDataTableFromArchive('Actions_downloads', $idSite, $period, $date, $segment, $expanded, $idSubtable); + $this->filterActionsDataTable($dataTable, $expanded); + return $dataTable; + } + + public function getDownload($downloadUrl, $idSite, $period, $date, $segment = false) + { + $callBackParameters = array('Actions_downloads', $idSite, $period, $date, $segment, $expanded = false, $idSubtable = false); + $dataTable = $this->getFilterPageDatatableSearch($callBackParameters, $downloadUrl, Piwik_Tracker_Action::TYPE_DOWNLOAD); + $this->filterActionsDataTable($dataTable); + return $dataTable; + } + + public function getOutlinks($idSite, $period, $date, $segment = false, $expanded = false, $idSubtable = false) + { + $dataTable = Piwik_Archive::getDataTableFromArchive('Actions_outlink', $idSite, $period, $date, $segment, $expanded, $idSubtable); + $this->filterActionsDataTable($dataTable, $expanded); + return $dataTable; + } + + public function getOutlink($outlinkUrl, $idSite, $period, $date, $segment = false) + { + $callBackParameters = array('Actions_outlink', $idSite, $period, $date, $segment, $expanded = false, $idSubtable = false); + $dataTable = $this->getFilterPageDatatableSearch($callBackParameters, $outlinkUrl, Piwik_Tracker_Action::TYPE_OUTLINK); + $this->filterActionsDataTable($dataTable); + return $dataTable; + } + + 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); + $this->filterPageDatatable($dataTable); + $this->filterActionsDataTable($dataTable); + $this->addPagesPerSearchColumn($dataTable); + return $dataTable; + } + + //Visitors can search, and then click "next" to view more results. This is the average number of search results pages viewed for this keyword. + public function addPagesPerSearchColumn($dataTable, $columnToRead = 'nb_hits') + { + $dataTable->filter('ColumnCallbackAddColumnQuotient', array('nb_pages_per_search', $columnToRead, 'nb_visits', $precision = 1)); + } + + protected function getSiteSearchKeywordsRaw($idSite, $period, $date, $segment) + { + $dataTable = Piwik_Archive::getDataTableFromArchive('Actions_sitesearch', $idSite, $period, $date, $segment, $expanded = false); + return $dataTable; + } + + public function getSiteSearchNoResultKeywords($idSite, $period, $date, $segment = false) + { + $dataTable = $this->getSiteSearchKeywordsRaw($idSite, $period, $date, $segment); + // Delete all rows that have some results + $dataTable->filter('ColumnCallbackDeleteRow', + array( + Piwik_Archive::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); + $this->filterPageDatatable($dataTable); + $this->filterActionsDataTable($dataTable); + $this->addPagesPerSearchColumn($dataTable); + return $dataTable; + } + + /** + * @param int $idSite + * @param string $period + * @param Piwik_Date $date + * @param bool $segment * * @return Piwik_DataTable|Piwik_DataTable_Array */ - public function getSiteSearchCategories( $idSite, $period, $date, $segment = false ) - { - Piwik_Actions::checkCustomVariablesPluginEnabled(); - $customVariables = Piwik_CustomVariables_API::getInstance()->getCustomVariables($idSite, $period, $date, $segment, $expanded = false, $_leavePiwikCoreVariables = true); - - $customVarNameToLookFor = Piwik_Tracker_Action::CVAR_KEY_SEARCH_CATEGORY; - - $dataTable = new Piwik_DataTable(); - // Handle case where date=last30&period=day - // TODO: this logic should really be refactored somewhere, this is ugly! - if($customVariables instanceof Piwik_DataTable_Array) - { - $dataTable = $customVariables->getEmptyClone(); - - $customVariableDatatables = $customVariables->getArray(); - $dataTables = $dataTable->getArray(); - foreach($customVariableDatatables as $key => $customVariableTableForDate) - { - // we do not enter the IF, in the case idSite=1,3 AND period=day&date=datefrom,dateto, - if(isset($customVariableTableForDate->metadata['period'])) - { - $row = $customVariableTableForDate->getRowFromLabel($customVarNameToLookFor); - if($row) - { - $dateRewrite = $customVariableTableForDate->metadata['period']->getDateStart()->toString(); - $idSubtable = $row->getIdSubDataTable(); - $categories = Piwik_CustomVariables_API::getInstance()->getCustomVariablesValuesFromNameId($idSite, $period, $dateRewrite, $idSubtable, $segment); - $dataTable->addTable($categories, $key); - } - } - } - } - elseif($customVariables instanceof Piwik_DataTable) - { - $row = $customVariables->getRowFromLabel($customVarNameToLookFor); - if($row) - { - $idSubtable = $row->getIdSubDataTable(); - $dataTable = Piwik_CustomVariables_API::getInstance()->getCustomVariablesValuesFromNameId($idSite, $period, $date, $idSubtable, $segment); - } - } - $this->filterActionsDataTable($dataTable); - $this->addPagesPerSearchColumn($dataTable, $columnToRead = 'nb_actions'); - return $dataTable; - } - - /** - * Will search in the DataTable for a Label matching the searched string - * and return only the matching row, or an empty datatable - */ - protected function getFilterPageDatatableSearch($callBackParameters, $search, $actionType, $table = false, - $searchTree = false) - { - if ($searchTree === false) - { - // build the query parts that are searched inside the tree - if($actionType == Piwik_Tracker_Action::TYPE_ACTION_NAME) - { - $searchedString = Piwik_Common::unsanitizeInputValue($search); - } - else - { - $idSite = $callBackParameters[1]; - try { - $searchedString = Piwik_Tracker_Action::excludeQueryParametersFromUrl($search, $idSite); - } catch(Exception $e) { - $searchedString = $search; - } - } - Piwik_Actions_ArchivingHelper::reloadConfig(); - $searchTree = Piwik_Actions_ArchivingHelper::getActionExplodedNames($searchedString, $actionType); - } - - if ($table === false) - { - // fetch the data table - $table = call_user_func_array(array('Piwik_Archive', 'getDataTableFromArchive'), $callBackParameters); - - if ($table instanceof Piwik_DataTable_Array) - { - // search an array of tables, e.g. when using date=last30 - // note that if the root is an array, we filter all children - // if an array occurs inside the nested table, we only look for the first match (see below) - $newTableArray = $table->getEmptyClone(); - - foreach ($table->getArray() as $label => $subTable) - { - $newSubTable = $this->doFilterPageDatatableSearch($callBackParameters, $subTable, $searchTree); - - $newTableArray->addTable($newSubTable, $label); - } - - return $newTableArray; - } - - } - - return $this->doFilterPageDatatableSearch($callBackParameters, $table, $searchTree); - } - - /** - * This looks very similar to LabelFilter.php should it be refactored somehow? FIXME - */ - protected function doFilterPageDatatableSearch($callBackParameters, $table, $searchTree) - { - // filter a data table array - if ($table instanceof Piwik_DataTable_Array) - { - foreach ($table->getArray() as $subTable) - { - $filteredSubTable = $this->doFilterPageDatatableSearch($callBackParameters, $subTable, $searchTree); - - if ($filteredSubTable->getRowsCount() > 0) - { - // match found in a sub table, return and stop searching the others - return $filteredSubTable; - } - } - - // nothing found in all sub tables - return new Piwik_DataTable; - } - - // filter regular data table - if ($table instanceof Piwik_DataTable) - { - // search for the first part of the tree search - $search = array_shift($searchTree); - $row = $table->getRowFromLabel($search); - if ($row === false) - { - // not found - $result = new Piwik_DataTable; - $result->metadata = $table->metadata; - return $result; - } - - // end of tree search reached - if (count($searchTree) == 0) - { - $result = new Piwik_DataTable(); - $result->addRow($row); - $result->metadata = $table->metadata; - return $result; - } - - // match found on this level and more levels remaining: go deeper - $idSubTable = $row->getIdSubDataTable(); - $callBackParameters[6] = $idSubTable; - $table = call_user_func_array(array('Piwik_Archive', 'getDataTableFromArchive'), $callBackParameters); - return $this->doFilterPageDatatableSearch($callBackParameters, $table, $searchTree); - } - - throw new Exception("For this API function, DataTable ".get_class($table)." is not supported"); - } - - /** - * Common filters for Page URLs and Page Titles - */ - protected function filterPageDatatable($dataTable) - { - // Average time on page = total time on page / number visits on that page - $dataTable->queueFilter('ColumnCallbackAddColumnQuotient', array('avg_time_on_page', 'sum_time_spent', 'nb_visits', 0)); - - // Bounce rate = single page visits on this page / visits started on this page - $dataTable->queueFilter('ColumnCallbackAddColumnPercentage', array('bounce_rate', 'entry_bounce_count', 'entry_nb_visits', 0)); - - // % Exit = Number of visits that finished on this page / visits on this page - $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); - if ($hasTimeGeneration) { - // Average generation time = total generation time / number of pageviews - $dataTable->queueFilter('ColumnCallbackAddColumnQuotient', array('avg_time_generation', 'sum_time_generation', 'nb_hits_with_time_generation', 3)); - } else { - // 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))); - if ($dataTable instanceof Piwik_DataTable) { - $emptyColumns = $dataTable->getMetadata(Piwik_DataTable::EMPTY_COLUMNS_METADATA_NAME); - if (!is_array($emptyColumns)) { - $emptyColumns = array(); - } - $emptyColumns[] = 'sum_time_generation'; - $emptyColumns[] = 'avg_time_generation'; - $dataTable->setMetadata(Piwik_DataTable::EMPTY_COLUMNS_METADATA_NAME, $emptyColumns); - } - } - } - - /** - * Common filters for all Actions API getters - */ - protected function filterActionsDataTable($dataTable, $expanded = false) - { - // Must be applied before Sort in this case, since the DataTable can contain both int and strings indexes - // (in the transition period between pre 1.2 and post 1.2 datatable structure) - $dataTable->filter('ReplaceColumnNames'); - $dataTable->filter('Sort', array('nb_visits', 'desc', $naturalSort = false, $expanded)); - - $dataTable->queueFilter('ReplaceSummaryRowLabel'); - } - - /** - * Removes DataTable rows referencing actions that were never the first action of a visit. - * - * @param Piwik_DataTable $dataTable - */ - private function filterNonEntryActions( $dataTable ) - { - $dataTable->filter('ColumnCallbackDeleteRow', array('entry_nb_visits', 'strlen')); - } - - /** - * Removes DataTable rows referencing actions that were never the last action of a visit. - * - * @param Piwik_DataTable $dataTable - */ - private function filterNonExitActions( $dataTable ) - { - $dataTable->filter('ColumnCallbackDeleteRow', array('exit_nb_visits', 'strlen')); - } + public function getSiteSearchCategories($idSite, $period, $date, $segment = false) + { + Piwik_Actions::checkCustomVariablesPluginEnabled(); + $customVariables = Piwik_CustomVariables_API::getInstance()->getCustomVariables($idSite, $period, $date, $segment, $expanded = false, $_leavePiwikCoreVariables = true); + + $customVarNameToLookFor = Piwik_Tracker_Action::CVAR_KEY_SEARCH_CATEGORY; + + $dataTable = new Piwik_DataTable(); + // Handle case where date=last30&period=day + // TODO: this logic should really be refactored somewhere, this is ugly! + if ($customVariables instanceof Piwik_DataTable_Array) { + $dataTable = $customVariables->getEmptyClone(); + + $customVariableDatatables = $customVariables->getArray(); + $dataTables = $dataTable->getArray(); + foreach ($customVariableDatatables as $key => $customVariableTableForDate) { + // we do not enter the IF, in the case idSite=1,3 AND period=day&date=datefrom,dateto, + if (isset($customVariableTableForDate->metadata['period'])) { + $row = $customVariableTableForDate->getRowFromLabel($customVarNameToLookFor); + if ($row) { + $dateRewrite = $customVariableTableForDate->metadata['period']->getDateStart()->toString(); + $idSubtable = $row->getIdSubDataTable(); + $categories = Piwik_CustomVariables_API::getInstance()->getCustomVariablesValuesFromNameId($idSite, $period, $dateRewrite, $idSubtable, $segment); + $dataTable->addTable($categories, $key); + } + } + } + } elseif ($customVariables instanceof Piwik_DataTable) { + $row = $customVariables->getRowFromLabel($customVarNameToLookFor); + if ($row) { + $idSubtable = $row->getIdSubDataTable(); + $dataTable = Piwik_CustomVariables_API::getInstance()->getCustomVariablesValuesFromNameId($idSite, $period, $date, $idSubtable, $segment); + } + } + $this->filterActionsDataTable($dataTable); + $this->addPagesPerSearchColumn($dataTable, $columnToRead = 'nb_actions'); + return $dataTable; + } + + /** + * Will search in the DataTable for a Label matching the searched string + * and return only the matching row, or an empty datatable + */ + protected function getFilterPageDatatableSearch($callBackParameters, $search, $actionType, $table = false, + $searchTree = false) + { + if ($searchTree === false) { + // build the query parts that are searched inside the tree + if ($actionType == Piwik_Tracker_Action::TYPE_ACTION_NAME) { + $searchedString = Piwik_Common::unsanitizeInputValue($search); + } else { + $idSite = $callBackParameters[1]; + try { + $searchedString = Piwik_Tracker_Action::excludeQueryParametersFromUrl($search, $idSite); + } catch (Exception $e) { + $searchedString = $search; + } + } + Piwik_Actions_ArchivingHelper::reloadConfig(); + $searchTree = Piwik_Actions_ArchivingHelper::getActionExplodedNames($searchedString, $actionType); + } + + if ($table === false) { + // fetch the data table + $table = call_user_func_array(array('Piwik_Archive', 'getDataTableFromArchive'), $callBackParameters); + + if ($table instanceof Piwik_DataTable_Array) { + // search an array of tables, e.g. when using date=last30 + // note that if the root is an array, we filter all children + // if an array occurs inside the nested table, we only look for the first match (see below) + $newTableArray = $table->getEmptyClone(); + + foreach ($table->getArray() as $label => $subTable) { + $newSubTable = $this->doFilterPageDatatableSearch($callBackParameters, $subTable, $searchTree); + + $newTableArray->addTable($newSubTable, $label); + } + + return $newTableArray; + } + + } + + return $this->doFilterPageDatatableSearch($callBackParameters, $table, $searchTree); + } + + /** + * This looks very similar to LabelFilter.php should it be refactored somehow? FIXME + */ + protected function doFilterPageDatatableSearch($callBackParameters, $table, $searchTree) + { + // filter a data table array + if ($table instanceof Piwik_DataTable_Array) { + foreach ($table->getArray() as $subTable) { + $filteredSubTable = $this->doFilterPageDatatableSearch($callBackParameters, $subTable, $searchTree); + + if ($filteredSubTable->getRowsCount() > 0) { + // match found in a sub table, return and stop searching the others + return $filteredSubTable; + } + } + + // nothing found in all sub tables + return new Piwik_DataTable; + } + + // filter regular data table + if ($table instanceof Piwik_DataTable) { + // search for the first part of the tree search + $search = array_shift($searchTree); + $row = $table->getRowFromLabel($search); + if ($row === false) { + // not found + $result = new Piwik_DataTable; + $result->metadata = $table->metadata; + return $result; + } + + // end of tree search reached + if (count($searchTree) == 0) { + $result = new Piwik_DataTable(); + $result->addRow($row); + $result->metadata = $table->metadata; + return $result; + } + + // match found on this level and more levels remaining: go deeper + $idSubTable = $row->getIdSubDataTable(); + $callBackParameters[6] = $idSubTable; + $table = call_user_func_array(array('Piwik_Archive', 'getDataTableFromArchive'), $callBackParameters); + return $this->doFilterPageDatatableSearch($callBackParameters, $table, $searchTree); + } + + throw new Exception("For this API function, DataTable " . get_class($table) . " is not supported"); + } + + /** + * Common filters for Page URLs and Page Titles + */ + protected function filterPageDatatable($dataTable) + { + // Average time on page = total time on page / number visits on that page + $dataTable->queueFilter('ColumnCallbackAddColumnQuotient', array('avg_time_on_page', 'sum_time_spent', 'nb_visits', 0)); + + // Bounce rate = single page visits on this page / visits started on this page + $dataTable->queueFilter('ColumnCallbackAddColumnPercentage', array('bounce_rate', 'entry_bounce_count', 'entry_nb_visits', 0)); + + // % Exit = Number of visits that finished on this page / visits on this page + $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); + if ($hasTimeGeneration) { + // Average generation time = total generation time / number of pageviews + $dataTable->queueFilter('ColumnCallbackAddColumnQuotient', array('avg_time_generation', 'sum_time_generation', 'nb_hits_with_time_generation', 3)); + } else { + // 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))); + if ($dataTable instanceof Piwik_DataTable) { + $emptyColumns = $dataTable->getMetadata(Piwik_DataTable::EMPTY_COLUMNS_METADATA_NAME); + if (!is_array($emptyColumns)) { + $emptyColumns = array(); + } + $emptyColumns[] = 'sum_time_generation'; + $emptyColumns[] = 'avg_time_generation'; + $dataTable->setMetadata(Piwik_DataTable::EMPTY_COLUMNS_METADATA_NAME, $emptyColumns); + } + } + } + + /** + * Common filters for all Actions API getters + */ + protected function filterActionsDataTable($dataTable, $expanded = false) + { + // Must be applied before Sort in this case, since the DataTable can contain both int and strings indexes + // (in the transition period between pre 1.2 and post 1.2 datatable structure) + $dataTable->filter('ReplaceColumnNames'); + $dataTable->filter('Sort', array('nb_visits', 'desc', $naturalSort = false, $expanded)); + + $dataTable->queueFilter('ReplaceSummaryRowLabel'); + } + + /** + * Removes DataTable rows referencing actions that were never the first action of a visit. + * + * @param Piwik_DataTable $dataTable + */ + private function filterNonEntryActions($dataTable) + { + $dataTable->filter('ColumnCallbackDeleteRow', array('entry_nb_visits', 'strlen')); + } + + /** + * Removes DataTable rows referencing actions that were never the last action of a visit. + * + * @param Piwik_DataTable $dataTable + */ + private function filterNonExitActions($dataTable) + { + $dataTable->filter('ColumnCallbackDeleteRow', array('exit_nb_visits', 'strlen')); + } } diff --git a/plugins/Actions/Actions.php b/plugins/Actions/Actions.php index 230968e66f..635b0d95c4 100644 --- a/plugins/Actions/Actions.php +++ b/plugins/Actions/Actions.php @@ -8,7 +8,7 @@ * @category Piwik_Plugins * @package Piwik_Actions */ - + /** * Actions plugin * @@ -18,592 +18,585 @@ */ class Piwik_Actions extends Piwik_Plugin { - public function getInformation() - { - $info = array( - 'description' => Piwik_Translate('Actions_PluginDescription'), - 'author' => 'Piwik', - 'author_homepage' => 'http://piwik.org/', - 'version' => Piwik_Version::VERSION, - ); - return $info; - } - - - public function getListHooksRegistered() - { - $hooks = array( - 'ArchiveProcessing_Day.compute' => 'archiveDay', - 'ArchiveProcessing_Period.compute' => 'archivePeriod', - 'WidgetsList.add' => 'addWidgets', - 'Menu.add' => 'addMenus', - 'API.getReportMetadata' => 'getReportMetadata', - 'API.getSegmentsMetadata' => 'getSegmentsMetadata', - ); - return $hooks; - } - - /** - * @param Piwik_Event_Notification $notification notification object - */ - public function getSegmentsMetadata($notification) - { - $segments =& $notification->getNotificationObject(); - $sqlFilter = array($this, 'getIdActionFromSegment'); - - // entry and exit pages of visit - $segments[] = array( - 'type' => 'dimension', - 'category' => 'Actions_Actions', - 'name' => 'Actions_ColumnEntryPageURL', - 'segment' => 'entryPageUrl', - 'sqlSegment' => 'log_visit.visit_entry_idaction_url', - 'sqlFilter' => $sqlFilter, + public function getInformation() + { + $info = array( + 'description' => Piwik_Translate('Actions_PluginDescription'), + 'author' => 'Piwik', + 'author_homepage' => 'http://piwik.org/', + 'version' => Piwik_Version::VERSION, + ); + return $info; + } + + + public function getListHooksRegistered() + { + $hooks = array( + 'ArchiveProcessing_Day.compute' => 'archiveDay', + 'ArchiveProcessing_Period.compute' => 'archivePeriod', + 'WidgetsList.add' => 'addWidgets', + 'Menu.add' => 'addMenus', + 'API.getReportMetadata' => 'getReportMetadata', + 'API.getSegmentsMetadata' => 'getSegmentsMetadata', + ); + return $hooks; + } + + /** + * @param Piwik_Event_Notification $notification notification object + */ + public function getSegmentsMetadata($notification) + { + $segments =& $notification->getNotificationObject(); + $sqlFilter = array($this, 'getIdActionFromSegment'); + + // entry and exit pages of visit + $segments[] = array( + 'type' => 'dimension', + 'category' => 'Actions_Actions', + 'name' => 'Actions_ColumnEntryPageURL', + 'segment' => 'entryPageUrl', + 'sqlSegment' => 'log_visit.visit_entry_idaction_url', + 'sqlFilter' => $sqlFilter, ); $segments[] = array( - 'type' => 'dimension', - 'category' => 'Actions_Actions', - 'name' => 'Actions_ColumnEntryPageTitle', - 'segment' => 'entryPageTitle', - 'sqlSegment' => 'log_visit.visit_entry_idaction_name', - 'sqlFilter' => $sqlFilter, + 'type' => 'dimension', + 'category' => 'Actions_Actions', + 'name' => 'Actions_ColumnEntryPageTitle', + 'segment' => 'entryPageTitle', + 'sqlSegment' => 'log_visit.visit_entry_idaction_name', + 'sqlFilter' => $sqlFilter, ); $segments[] = array( - 'type' => 'dimension', - 'category' => 'Actions_Actions', - 'name' => 'Actions_ColumnExitPageURL', - 'segment' => 'exitPageUrl', - 'sqlSegment' => 'log_visit.visit_exit_idaction_url', - 'sqlFilter' => $sqlFilter, + 'type' => 'dimension', + 'category' => 'Actions_Actions', + 'name' => 'Actions_ColumnExitPageURL', + 'segment' => 'exitPageUrl', + 'sqlSegment' => 'log_visit.visit_exit_idaction_url', + 'sqlFilter' => $sqlFilter, ); $segments[] = array( - 'type' => 'dimension', - 'category' => 'Actions_Actions', - 'name' => 'Actions_ColumnExitPageTitle', - 'segment' => 'exitPageTitle', - 'sqlSegment' => 'log_visit.visit_exit_idaction_name', - 'sqlFilter' => $sqlFilter, + 'type' => 'dimension', + 'category' => 'Actions_Actions', + 'name' => 'Actions_ColumnExitPageTitle', + 'segment' => 'exitPageTitle', + 'sqlSegment' => 'log_visit.visit_exit_idaction_name', + 'sqlFilter' => $sqlFilter, ); - + // single pages $segments[] = array( - 'type' => 'dimension', - 'category' => 'Actions_Actions', - 'name' => 'Actions_ColumnPageURL', - 'segment' => 'pageUrl', - 'sqlSegment' => 'log_link_visit_action.idaction_url', - 'sqlFilter' => $sqlFilter, - 'acceptedValues' => "All these segments must be URL encoded, for example: ".urlencode('http://example.com/path/page?query'), + 'type' => 'dimension', + 'category' => 'Actions_Actions', + 'name' => 'Actions_ColumnPageURL', + 'segment' => 'pageUrl', + 'sqlSegment' => 'log_link_visit_action.idaction_url', + 'sqlFilter' => $sqlFilter, + 'acceptedValues' => "All these segments must be URL encoded, for example: " . urlencode('http://example.com/path/page?query'), ); - $segments[] = array( - 'type' => 'dimension', - 'category' => 'Actions_Actions', - 'name' => 'Actions_ColumnPageName', - 'segment' => 'pageTitle', - 'sqlSegment' => 'log_link_visit_action.idaction_name', - 'sqlFilter' => $sqlFilter, - ); - // TODO here could add keyword segment and hack $sqlFilter to make it select the right idaction - } - - /** - * Convert segment expression to an action ID or an SQL expression. - * - * This method is used as a sqlFilter-callback for the segments of this plugin. - * Usually, these callbacks only return a value that should be compared to the - * column in the database. In this case, that doesn't work since multiple IDs - * can match an expression (e.g. "pageUrl=@foo"). - * @param string $string - * @param string $sqlField - * @param string $matchType - * @throws Exception - * @return array|int|string - */ - public function getIdActionFromSegment($string, $sqlField, $matchType='==') - { - // Field is visit_*_idaction_url or visit_*_idaction_name - $actionType = strpos($sqlField, '_name') === false - ? Piwik_Tracker_Action::TYPE_ACTION_URL - : Piwik_Tracker_Action::TYPE_ACTION_NAME; - - if ($actionType == Piwik_Tracker_Action::TYPE_ACTION_URL) - { - // for urls trim protocol and www because it is not recorded in the db - $string = preg_replace('@^http[s]?://(www\.)?@i', '', $string); - } - + $segments[] = array( + 'type' => 'dimension', + 'category' => 'Actions_Actions', + 'name' => 'Actions_ColumnPageName', + 'segment' => 'pageTitle', + 'sqlSegment' => 'log_link_visit_action.idaction_name', + 'sqlFilter' => $sqlFilter, + ); + // TODO here could add keyword segment and hack $sqlFilter to make it select the right idaction + } + + /** + * Convert segment expression to an action ID or an SQL expression. + * + * This method is used as a sqlFilter-callback for the segments of this plugin. + * Usually, these callbacks only return a value that should be compared to the + * column in the database. In this case, that doesn't work since multiple IDs + * can match an expression (e.g. "pageUrl=@foo"). + * @param string $string + * @param string $sqlField + * @param string $matchType + * @throws Exception + * @return array|int|string + */ + public function getIdActionFromSegment($string, $sqlField, $matchType = '==') + { + // Field is visit_*_idaction_url or visit_*_idaction_name + $actionType = strpos($sqlField, '_name') === false + ? Piwik_Tracker_Action::TYPE_ACTION_URL + : Piwik_Tracker_Action::TYPE_ACTION_NAME; + + if ($actionType == Piwik_Tracker_Action::TYPE_ACTION_URL) { + // for urls trim protocol and www because it is not recorded in the db + $string = preg_replace('@^http[s]?://(www\.)?@i', '', $string); + } + // exact matches work by returning the id directly - if ($matchType == Piwik_SegmentExpression::MATCH_EQUAL - || $matchType == Piwik_SegmentExpression::MATCH_NOT_EQUAL) - { + if ($matchType == Piwik_SegmentExpression::MATCH_EQUAL + || $matchType == Piwik_SegmentExpression::MATCH_NOT_EQUAL + ) { $sql = Piwik_Tracker_Action::getSqlSelectActionId(); $bind = array($string, $string, $actionType); $idAction = Piwik_FetchOne($sql, $bind); // if the action is not found, we hack -100 to ensure it tries to match against an integer // otherwise binding idaction_name to "false" returns some rows for some reasons (in case &segment=pageTitle==Větrnásssssss) - if(empty($idAction)) - { + if (empty($idAction)) { $idAction = -100; } return $idAction; } - + // now, we handle the cases =@ (contains) and !@ (does not contain) - + // build the expression based on the match type - $sql = 'SELECT idaction FROM '.Piwik_Common::prefixTable('log_action').' WHERE '; - switch ($matchType) - { + $sql = 'SELECT idaction FROM ' . Piwik_Common::prefixTable('log_action') . ' WHERE '; + switch ($matchType) { case '=@': // use concat to make sure, no %s occurs because some plugins use %s in their sql - $sql .= '( name LIKE CONCAT("%", ?, "%") AND type = '.$actionType.' )'; + $sql .= '( name LIKE CONCAT("%", ?, "%") AND type = ' . $actionType . ' )'; break; case '!@': - $sql .= '( name NOT LIKE CONCAT("%", ?, "%") AND type = '.$actionType.' )'; + $sql .= '( name NOT LIKE CONCAT("%", ?, "%") AND type = ' . $actionType . ' )'; break; default: throw new Exception("This match type is not available for action-segments."); break; } - + return array( // mark that the returned value is an sql-expression instead of a literal value - 'SQL' => $sql, - 'bind' => $string + 'SQL' => $sql, + 'bind' => $string ); - } - - /** - * Returns metadata for available reports - * - * @param Piwik_Event_Notification $notification notification object - */ - public function getReportMetadata($notification) - { - $reports = &$notification->getNotificationObject(); - - $reports[] = array( - 'category' => Piwik_Translate('Actions_Actions'), - 'name' => Piwik_Translate('Actions_Actions') . ' - ' . Piwik_Translate('General_MainMetrics'), - 'module' => 'Actions', - 'action' => 'get', - 'metrics' => array( - 'nb_pageviews' => Piwik_Translate('General_ColumnPageviews'), - 'nb_uniq_pageviews' => Piwik_Translate('General_ColumnUniquePageviews'), - 'nb_downloads' => Piwik_Translate('Actions_ColumnDownloads'), - 'nb_uniq_downloads' => Piwik_Translate('Actions_ColumnUniqueDownloads'), - 'nb_outlinks' => Piwik_Translate('Actions_ColumnOutlinks'), - 'nb_uniq_outlinks' => Piwik_Translate('Actions_ColumnUniqueOutlinks'), - 'nb_searches' => Piwik_Translate('Actions_ColumnSearches'), - 'nb_keywords' => Piwik_Translate('Actions_ColumnSiteSearchKeywords'), - ), - 'metricsDocumentation' => array( - 'nb_pageviews' => Piwik_Translate('General_ColumnPageviewsDocumentation'), - 'nb_uniq_pageviews' => Piwik_Translate('General_ColumnUniquePageviewsDocumentation'), - 'nb_downloads' => Piwik_Translate('Actions_ColumnClicksDocumentation'), - 'nb_uniq_downloads' => Piwik_Translate('Actions_ColumnUniqueClicksDocumentation'), - 'nb_outlinks' => Piwik_Translate('Actions_ColumnClicksDocumentation'), - 'nb_uniq_outlinks' => Piwik_Translate('Actions_ColumnUniqueClicksDocumentation'), - 'nb_searches' => Piwik_Translate('Actions_ColumnSearchesDocumentation'), + } + + /** + * Returns metadata for available reports + * + * @param Piwik_Event_Notification $notification notification object + */ + public function getReportMetadata($notification) + { + $reports = & $notification->getNotificationObject(); + + $reports[] = array( + 'category' => Piwik_Translate('Actions_Actions'), + 'name' => Piwik_Translate('Actions_Actions') . ' - ' . Piwik_Translate('General_MainMetrics'), + 'module' => 'Actions', + 'action' => 'get', + 'metrics' => array( + 'nb_pageviews' => Piwik_Translate('General_ColumnPageviews'), + 'nb_uniq_pageviews' => Piwik_Translate('General_ColumnUniquePageviews'), + 'nb_downloads' => Piwik_Translate('Actions_ColumnDownloads'), + 'nb_uniq_downloads' => Piwik_Translate('Actions_ColumnUniqueDownloads'), + 'nb_outlinks' => Piwik_Translate('Actions_ColumnOutlinks'), + 'nb_uniq_outlinks' => Piwik_Translate('Actions_ColumnUniqueOutlinks'), + 'nb_searches' => Piwik_Translate('Actions_ColumnSearches'), + 'nb_keywords' => Piwik_Translate('Actions_ColumnSiteSearchKeywords'), + ), + 'metricsDocumentation' => array( + 'nb_pageviews' => Piwik_Translate('General_ColumnPageviewsDocumentation'), + 'nb_uniq_pageviews' => Piwik_Translate('General_ColumnUniquePageviewsDocumentation'), + 'nb_downloads' => Piwik_Translate('Actions_ColumnClicksDocumentation'), + 'nb_uniq_downloads' => Piwik_Translate('Actions_ColumnUniqueClicksDocumentation'), + 'nb_outlinks' => Piwik_Translate('Actions_ColumnClicksDocumentation'), + 'nb_uniq_outlinks' => Piwik_Translate('Actions_ColumnUniqueClicksDocumentation'), + 'nb_searches' => Piwik_Translate('Actions_ColumnSearchesDocumentation'), // 'nb_keywords' => Piwik_Translate('Actions_ColumnSiteSearchKeywords'), - ), - 'processedMetrics' => false, - 'order' => 1 - ); - - $metrics = array( - 'nb_hits' => Piwik_Translate('General_ColumnPageviews'), - 'nb_visits' => Piwik_Translate('General_ColumnUniquePageviews'), - 'bounce_rate' => Piwik_Translate('General_ColumnBounceRate'), - 'avg_time_on_page' => Piwik_Translate('General_ColumnAverageTimeOnPage'), - 'exit_rate' => Piwik_Translate('General_ColumnExitRate'), - 'avg_time_generation' => Piwik_Translate('General_ColumnAverageGenerationTime') - ); - - $documentation = array( - 'nb_hits' => Piwik_Translate('General_ColumnPageviewsDocumentation'), - 'nb_visits' => Piwik_Translate('General_ColumnUniquePageviewsDocumentation'), - 'bounce_rate' => Piwik_Translate('General_ColumnPageBounceRateDocumentation'), - 'avg_time_on_page' => Piwik_Translate('General_ColumnAverageTimeOnPageDocumentation'), - 'exit_rate' => Piwik_Translate('General_ColumnExitRateDocumentation'), - 'avg_time_generation' => Piwik_Translate('General_ColumnAverageGenerationTimeDocumentation'), - ); - - // pages report - $reports[] = array( - 'category' => Piwik_Translate('Actions_Actions'), - 'name' => Piwik_Translate('Actions_PageUrls'), - 'module' => 'Actions', - 'action' => 'getPageUrls', - 'dimension' => Piwik_Translate('Actions_ColumnPageURL'), - 'metrics' => $metrics, - 'metricsDocumentation' => $documentation, - 'documentation' => Piwik_Translate('Actions_PagesReportDocumentation', '
') - .'
'.Piwik_Translate('General_UsePlusMinusIconsDocumentation'), - 'processedMetrics' => false, - 'actionToLoadSubTables' => 'getPageUrls', - 'order' => 2 - ); - - // entry pages report - $reports[] = array( - 'category' => Piwik_Translate('Actions_Actions'), - 'name' => Piwik_Translate('Actions_SubmenuPagesEntry'), - 'module' => 'Actions', - 'action' => 'getEntryPageUrls', - 'dimension' => Piwik_Translate('Actions_ColumnPageURL'), - 'metrics' => array( - 'entry_nb_visits' => Piwik_Translate('General_ColumnEntrances'), - 'entry_bounce_count' => Piwik_Translate('General_ColumnBounces'), - 'bounce_rate' => Piwik_Translate('General_ColumnBounceRate'), - ), - 'metricsDocumentation' => array( - 'entry_nb_visits' => Piwik_Translate('General_ColumnEntrancesDocumentation'), - 'entry_bounce_count' => Piwik_Translate('General_ColumnBouncesDocumentation'), - 'bounce_rate' => Piwik_Translate('General_ColumnBounceRateForPageDocumentation') - ), - 'documentation' => Piwik_Translate('Actions_EntryPagesReportDocumentation', '
') - .' '.Piwik_Translate('General_UsePlusMinusIconsDocumentation'), - 'processedMetrics' => false, - 'actionToLoadSubTables' => 'getEntryPageUrls', - 'order' => 3 - ); - - // exit pages report - $reports[] = array( - 'category' => Piwik_Translate('Actions_Actions'), - 'name' => Piwik_Translate('Actions_SubmenuPagesExit'), - 'module' => 'Actions', - 'action' => 'getExitPageUrls', - 'dimension' => Piwik_Translate('Actions_ColumnPageURL'), - 'metrics' => array( - 'exit_nb_visits' => Piwik_Translate('General_ColumnExits'), - 'nb_visits' => Piwik_Translate('General_ColumnUniquePageviews'), - 'exit_rate' => Piwik_Translate('General_ColumnExitRate') - ), - 'metricsDocumentation' => array( - 'exit_nb_visits' => Piwik_Translate('General_ColumnExitsDocumentation'), - 'nb_visits' => Piwik_Translate('General_ColumnUniquePageviewsDocumentation'), - 'exit_rate' => Piwik_Translate('General_ColumnExitRateDocumentation') - ), - 'documentation' => Piwik_Translate('Actions_ExitPagesReportDocumentation', '
') - .' '.Piwik_Translate('General_UsePlusMinusIconsDocumentation'), - 'processedMetrics' => false, - 'actionToLoadSubTables' => 'getExitPageUrls', - 'order' => 4 - ); - - // page titles report - $reports[] = array( - 'category' => Piwik_Translate('Actions_Actions'), - 'name' => Piwik_Translate('Actions_SubmenuPageTitles'), - 'module' => 'Actions', - 'action' => 'getPageTitles', - 'dimension' => Piwik_Translate('Actions_ColumnPageName'), - 'metrics' => $metrics, - 'metricsDocumentation' => $documentation, - 'documentation' => Piwik_Translate('Actions_PageTitlesReportDocumentation', array('
', htmlentities(''))), - 'processedMetrics' => false, - 'actionToLoadSubTables' => 'getPageTitles', - 'order' => 5, - - ); - - // entry page titles report - $reports[] = array( - 'category' => Piwik_Translate('Actions_Actions'), - 'name' => Piwik_Translate('Actions_EntryPageTitles'), - 'module' => 'Actions', - 'action' => 'getEntryPageTitles', - 'dimension' => Piwik_Translate('Actions_ColumnPageName'), - 'metrics' => array( - 'entry_nb_visits' => Piwik_Translate('General_ColumnEntrances'), - 'entry_bounce_count' => Piwik_Translate('General_ColumnBounces'), - 'bounce_rate' => Piwik_Translate('General_ColumnBounceRate'), - ), - 'metricsDocumentation' => array( - 'entry_nb_visits' => Piwik_Translate('General_ColumnEntrancesDocumentation'), - 'entry_bounce_count' => Piwik_Translate('General_ColumnBouncesDocumentation'), - 'bounce_rate' => Piwik_Translate('General_ColumnBounceRateForPageDocumentation') - ), - 'documentation' => Piwik_Translate('Actions_ExitPageTitlesReportDocumentation', '<br />') - .' '.Piwik_Translate('General_UsePlusMinusIconsDocumentation'), - 'processedMetrics' => false, - 'actionToLoadSubTables' => 'getEntryPageTitles', - 'order' => 6 - ); - - // exit page titles report - $reports[] = array( - 'category' => Piwik_Translate('Actions_Actions'), - 'name' => Piwik_Translate('Actions_ExitPageTitles'), - 'module' => 'Actions', - 'action' => 'getExitPageTitles', - 'dimension' => Piwik_Translate('Actions_ColumnPageName'), - 'metrics' => array( - 'exit_nb_visits' => Piwik_Translate('General_ColumnExits'), - 'nb_visits' => Piwik_Translate('General_ColumnUniquePageviews'), - 'exit_rate' => Piwik_Translate('General_ColumnExitRate') - ), - 'metricsDocumentation' => array( - 'exit_nb_visits' => Piwik_Translate('General_ColumnExitsDocumentation'), - 'nb_visits' => Piwik_Translate('General_ColumnUniquePageviewsDocumentation'), - 'exit_rate' => Piwik_Translate('General_ColumnExitRateDocumentation') - ), - 'documentation' => Piwik_Translate('Actions_EntryPageTitlesReportDocumentation', '<br />') - .' '.Piwik_Translate('General_UsePlusMinusIconsDocumentation'), - 'processedMetrics' => false, - 'actionToLoadSubTables' => 'getExitPageTitles', - 'order' => 7 - ); - - $documentation = array( - 'nb_visits' => Piwik_Translate('Actions_ColumnUniqueClicksDocumentation'), - 'nb_hits' => Piwik_Translate('Actions_ColumnClicksDocumentation') - ); - - // outlinks report - $reports[] = array( - 'category' => Piwik_Translate('Actions_Actions'), - 'name' => Piwik_Translate('Actions_SubmenuOutlinks'), - 'module' => 'Actions', - 'action' => 'getOutlinks', - 'dimension' => Piwik_Translate('Actions_ColumnClickedURL'), - 'metrics' => array( - 'nb_visits' => Piwik_Translate('Actions_ColumnUniqueClicks'), - 'nb_hits' => Piwik_Translate('Actions_ColumnClicks') - ), - 'metricsDocumentation' => $documentation, - 'documentation' => Piwik_Translate('Actions_OutlinksReportDocumentation').' ' - .Piwik_Translate('Actions_OutlinkDocumentation').'<br />' - .Piwik_Translate('General_UsePlusMinusIconsDocumentation'), - 'processedMetrics' => false, - 'actionToLoadSubTables' => 'getOutlinks', - 'order' => 8, - ); - - // downloads report - $reports[] = array( - 'category' => Piwik_Translate('Actions_Actions'), - 'name' => Piwik_Translate('Actions_SubmenuDownloads'), - 'module' => 'Actions', - 'action' => 'getDownloads', - 'dimension' => Piwik_Translate('Actions_ColumnDownloadURL'), - 'metrics' => array( - 'nb_visits' => Piwik_Translate('Actions_ColumnUniqueDownloads'), - 'nb_hits' => Piwik_Translate('Actions_ColumnDownloads') - ), - 'metricsDocumentation' => $documentation, - 'documentation' => Piwik_Translate('Actions_DownloadsReportDocumentation', '<br />'), - 'processedMetrics' => false, - 'actionToLoadSubTables' => 'getDownloads', - 'order' => 9, - ); - - if($this->isSiteSearchEnabled()) - { - // Search Keywords - $reports[] = array( - 'category' => Piwik_Translate('Actions_SubmenuSitesearch'), - 'name' => Piwik_Translate('Actions_WidgetSearchKeywords'), - 'module' => 'Actions', - 'action' => 'getSiteSearchKeywords', - 'dimension' => Piwik_Translate('Actions_ColumnSearchKeyword'), - 'metrics' => array( - 'nb_visits' => Piwik_Translate('Actions_ColumnSearches'), - 'nb_pages_per_search' => Piwik_Translate('Actions_ColumnPagesPerSearch'), - 'exit_rate' => Piwik_Translate('Actions_ColumnSearchExits'), - ), - 'metricsDocumentation' => array( - 'nb_visits' => Piwik_Translate('Actions_ColumnSearchesDocumentation'), - 'nb_pages_per_search' => Piwik_Translate('Actions_ColumnPagesPerSearchDocumentation'), - 'exit_rate' => Piwik_Translate('Actions_ColumnSearchExitsDocumentation'), - ), - 'documentation' => Piwik_Translate('Actions_SiteSearchKeywordsDocumentation') . '<br/><br/>' . Piwik_Translate('Actions_SiteSearchIntro') . '<br/><br/>' - . '<a href="http://piwik.org/docs/site-search/" target="_blank">'. Piwik_Translate('Actions_LearnMoreAboutSiteSearchLink') . '</a>', - 'processedMetrics' => false, - 'order' => 15 - ); - // No Result Search Keywords - $reports[] = array( - 'category' => Piwik_Translate('Actions_SubmenuSitesearch'), - 'name' => Piwik_Translate('Actions_WidgetSearchNoResultKeywords'), - 'module' => 'Actions', - 'action' => 'getSiteSearchNoResultKeywords', - 'dimension' => Piwik_Translate('Actions_ColumnNoResultKeyword'), - 'metrics' => array( - 'nb_visits' => Piwik_Translate('Actions_ColumnSearches'), - 'exit_rate' => Piwik_Translate('Actions_ColumnSearchExits'), - ), - 'metricsDocumentation' => array( - 'nb_visits' => Piwik_Translate('Actions_ColumnSearchesDocumentation'), - 'exit_rate' => Piwik_Translate('Actions_ColumnSearchExitsDocumentation'), - ), - 'documentation' => Piwik_Translate('Actions_SiteSearchIntro'). '<br /><br />' . Piwik_Translate('Actions_SiteSearchKeywordsNoResultDocumentation'), - 'processedMetrics' => false, - 'order' => 16 - ); - - if(self::isCustomVariablesPluginsEnabled()) { - // Search Categories - $reports[] = array( - 'category' => Piwik_Translate('Actions_SubmenuSitesearch'), - 'name' => Piwik_Translate('Actions_WidgetSearchCategories'), - 'module' => 'Actions', - 'action' => 'getSiteSearchCategories', - 'dimension' => Piwik_Translate('Actions_ColumnSearchCategory'), - 'metrics' => array( - 'nb_visits' => Piwik_Translate('Actions_ColumnSearches'), - 'nb_pages_per_search' => Piwik_Translate('Actions_ColumnPagesPerSearch'), - 'exit_rate' => Piwik_Translate('Actions_ColumnSearchExits'), - ), - 'metricsDocumentation' => array( - 'nb_visits' => Piwik_Translate('Actions_ColumnSearchesDocumentation'), - 'nb_pages_per_search' => Piwik_Translate('Actions_ColumnPagesPerSearchDocumentation'), - 'exit_rate' => Piwik_Translate('Actions_ColumnSearchExitsDocumentation'), - ), - 'documentation' => Piwik_Translate('Actions_SiteSearchCategories1') . '<br/>' . Piwik_Translate('Actions_SiteSearchCategories2'), - 'processedMetrics' => false, - 'order' => 17 - ); - } - - $documentation = Piwik_Translate('Actions_SiteSearchFollowingPagesDoc') .'<br/>'.Piwik_Translate('General_UsePlusMinusIconsDocumentation'); - // Pages URLs following Search - $reports[] = array( - 'category' => Piwik_Translate('Actions_SubmenuSitesearch'), - 'name' => Piwik_Translate('Actions_WidgetPageUrlsFollowingSearch'), - 'module' => 'Actions', - 'action' => 'getPageUrlsFollowingSiteSearch', - 'dimension' => Piwik_Translate('General_ColumnDestinationPage'), - 'metrics' => array( - 'nb_hits_following_search' => Piwik_Translate('General_ColumnViewedAfterSearch'), - 'nb_hits' => Piwik_Translate('General_ColumnTotalPageviews'), - ), - 'metricsDocumentation' => array( - 'nb_hits_following_search' => Piwik_Translate('General_ColumnViewedAfterSearchDocumentation'), - 'nb_hits' => Piwik_Translate('General_ColumnPageviewsDocumentation'), - ), - 'documentation' => $documentation, - 'processedMetrics' => false, - 'order' => 18 - ); - // Pages Titles following Search - $reports[] = array( - 'category' => Piwik_Translate('Actions_SubmenuSitesearch'), - 'name' => Piwik_Translate('Actions_WidgetPageTitlesFollowingSearch'), - 'module' => 'Actions', - 'action' => 'getPageTitlesFollowingSiteSearch', - 'dimension' => Piwik_Translate('General_ColumnDestinationPage'), - 'metrics' => array( - 'nb_hits_following_search' => Piwik_Translate('General_ColumnViewedAfterSearch'), - 'nb_hits' => Piwik_Translate('General_ColumnTotalPageviews'), - ), - 'metricsDocumentation' => array( - 'nb_hits_following_search' => Piwik_Translate('General_ColumnViewedAfterSearchDocumentation'), - 'nb_hits' => Piwik_Translate('General_ColumnPageviewsDocumentation'), - ), - 'documentation' => $documentation, - 'processedMetrics' => false, - 'order' => 19 - ); - } - } - - function addWidgets() - { - Piwik_AddWidget( 'Actions_Actions', 'Actions_SubmenuPages', 'Actions', 'getPageUrls'); - Piwik_AddWidget( 'Actions_Actions', 'Actions_WidgetPageTitles', 'Actions', 'getPageTitles'); - Piwik_AddWidget( 'Actions_Actions', 'Actions_SubmenuOutlinks', 'Actions', 'getOutlinks'); - Piwik_AddWidget( 'Actions_Actions', 'Actions_SubmenuDownloads', 'Actions', 'getDownloads'); - Piwik_AddWidget( 'Actions_Actions', 'Actions_WidgetPagesEntry', 'Actions', 'getEntryPageUrls'); - Piwik_AddWidget( 'Actions_Actions', 'Actions_WidgetPagesExit', 'Actions', 'getExitPageUrls'); - Piwik_AddWidget( 'Actions_Actions', 'Actions_WidgetEntryPageTitles', 'Actions', 'getEntryPageTitles' ); - Piwik_AddWidget( 'Actions_Actions', 'Actions_WidgetExitPageTitles', 'Actions', 'getExitPageTitles' ); - - if($this->isSiteSearchEnabled()) - { - Piwik_AddWidget( 'Actions_SubmenuSitesearch', 'Actions_WidgetSearchKeywords', 'Actions', 'getSiteSearchKeywords'); - - if(self::isCustomVariablesPluginsEnabled()) { - Piwik_AddWidget( 'Actions_SubmenuSitesearch', 'Actions_WidgetSearchCategories', 'Actions', 'getSiteSearchCategories'); - } - Piwik_AddWidget( 'Actions_SubmenuSitesearch', 'Actions_WidgetSearchNoResultKeywords', 'Actions', 'getSiteSearchNoResultKeywords'); - Piwik_AddWidget( 'Actions_SubmenuSitesearch', 'Actions_WidgetPageUrlsFollowingSearch', 'Actions', 'getPageUrlsFollowingSiteSearch'); - Piwik_AddWidget( 'Actions_SubmenuSitesearch', 'Actions_WidgetPageTitlesFollowingSearch', 'Actions', 'getPageTitlesFollowingSiteSearch'); - } - } - - function addMenus() - { - Piwik_AddMenu('Actions_Actions', '', array('module' => 'Actions', 'action' => 'indexPageUrls'), true, 15); - Piwik_AddMenu('Actions_Actions', 'Actions_SubmenuPages', array('module' => 'Actions', 'action' => 'indexPageUrls'), true, 1); - Piwik_AddMenu('Actions_Actions', 'Actions_SubmenuPagesEntry', array('module' => 'Actions', 'action' => 'indexEntryPageUrls'), true, 2); - Piwik_AddMenu('Actions_Actions', 'Actions_SubmenuPagesExit', array('module' => 'Actions', 'action' => 'indexExitPageUrls'), true, 3); - Piwik_AddMenu('Actions_Actions', 'Actions_SubmenuPageTitles', array('module' => 'Actions', 'action' => 'indexPageTitles'), true, 4); - Piwik_AddMenu('Actions_Actions', 'Actions_SubmenuOutlinks', array('module' => 'Actions', 'action' => 'indexOutlinks'), true, 6); - Piwik_AddMenu('Actions_Actions', 'Actions_SubmenuDownloads', array('module' => 'Actions', 'action' => 'indexDownloads'), true, 7); - - if($this->isSiteSearchEnabled()) - { - Piwik_AddMenu('Actions_Actions', 'Actions_SubmenuSitesearch', array('module' => 'Actions', 'action' => 'indexSiteSearch'), true, 5); - } - } - - protected function isSiteSearchEnabled() - { - $idSite = Piwik_Common::getRequestVar('idSite', 0, 'int'); - if($idSite == 0 ) { - return false; - } - return Piwik_Site::isSiteSearchEnabledFor($idSite); - } - - - /** - * @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" : - * visits, unique visitors, bounce count, sum visit length. - * - * @param Piwik_Event_Notification $notification notification object - */ - public function archiveDay( $notification ) - { - /* @var $archiveProcessing Piwik_ArchiveProcessing_Day */ - $archiveProcessing = $notification->getNotificationObject(); - - if(!$archiveProcessing->shouldProcessReportsForPlugin($this->getPluginName())) return; - - $actionsArchiving = new Piwik_Actions_Archiving($archiveProcessing->idsite); - return $actionsArchiving->archiveDay($archiveProcessing); - } - - static public function checkCustomVariablesPluginEnabled() - { - if(!self::isCustomVariablesPluginsEnabled()) - { - throw new Exception("To Track Site Search Categories, please ask the Piwik Administrator to enable the 'Custom Variables' plugin in Settings > Plugins."); - } - } - - static protected function isCustomVariablesPluginsEnabled() - { - return Piwik_PluginsManager::getInstance()->isPluginActivated('CustomVariables'); - } + ), + 'processedMetrics' => false, + 'order' => 1 + ); + + $metrics = array( + 'nb_hits' => Piwik_Translate('General_ColumnPageviews'), + 'nb_visits' => Piwik_Translate('General_ColumnUniquePageviews'), + 'bounce_rate' => Piwik_Translate('General_ColumnBounceRate'), + 'avg_time_on_page' => Piwik_Translate('General_ColumnAverageTimeOnPage'), + 'exit_rate' => Piwik_Translate('General_ColumnExitRate'), + 'avg_time_generation' => Piwik_Translate('General_ColumnAverageGenerationTime') + ); + + $documentation = array( + 'nb_hits' => Piwik_Translate('General_ColumnPageviewsDocumentation'), + 'nb_visits' => Piwik_Translate('General_ColumnUniquePageviewsDocumentation'), + 'bounce_rate' => Piwik_Translate('General_ColumnPageBounceRateDocumentation'), + 'avg_time_on_page' => Piwik_Translate('General_ColumnAverageTimeOnPageDocumentation'), + 'exit_rate' => Piwik_Translate('General_ColumnExitRateDocumentation'), + 'avg_time_generation' => Piwik_Translate('General_ColumnAverageGenerationTimeDocumentation'), + ); + + // pages report + $reports[] = array( + 'category' => Piwik_Translate('Actions_Actions'), + 'name' => Piwik_Translate('Actions_PageUrls'), + 'module' => 'Actions', + 'action' => 'getPageUrls', + 'dimension' => Piwik_Translate('Actions_ColumnPageURL'), + 'metrics' => $metrics, + 'metricsDocumentation' => $documentation, + 'documentation' => Piwik_Translate('Actions_PagesReportDocumentation', '<br />') + . '<br />' . Piwik_Translate('General_UsePlusMinusIconsDocumentation'), + 'processedMetrics' => false, + 'actionToLoadSubTables' => 'getPageUrls', + 'order' => 2 + ); + + // entry pages report + $reports[] = array( + 'category' => Piwik_Translate('Actions_Actions'), + 'name' => Piwik_Translate('Actions_SubmenuPagesEntry'), + 'module' => 'Actions', + 'action' => 'getEntryPageUrls', + 'dimension' => Piwik_Translate('Actions_ColumnPageURL'), + 'metrics' => array( + 'entry_nb_visits' => Piwik_Translate('General_ColumnEntrances'), + 'entry_bounce_count' => Piwik_Translate('General_ColumnBounces'), + 'bounce_rate' => Piwik_Translate('General_ColumnBounceRate'), + ), + 'metricsDocumentation' => array( + 'entry_nb_visits' => Piwik_Translate('General_ColumnEntrancesDocumentation'), + 'entry_bounce_count' => Piwik_Translate('General_ColumnBouncesDocumentation'), + 'bounce_rate' => Piwik_Translate('General_ColumnBounceRateForPageDocumentation') + ), + 'documentation' => Piwik_Translate('Actions_EntryPagesReportDocumentation', '<br />') + . ' ' . Piwik_Translate('General_UsePlusMinusIconsDocumentation'), + 'processedMetrics' => false, + 'actionToLoadSubTables' => 'getEntryPageUrls', + 'order' => 3 + ); + + // exit pages report + $reports[] = array( + 'category' => Piwik_Translate('Actions_Actions'), + 'name' => Piwik_Translate('Actions_SubmenuPagesExit'), + 'module' => 'Actions', + 'action' => 'getExitPageUrls', + 'dimension' => Piwik_Translate('Actions_ColumnPageURL'), + 'metrics' => array( + 'exit_nb_visits' => Piwik_Translate('General_ColumnExits'), + 'nb_visits' => Piwik_Translate('General_ColumnUniquePageviews'), + 'exit_rate' => Piwik_Translate('General_ColumnExitRate') + ), + 'metricsDocumentation' => array( + 'exit_nb_visits' => Piwik_Translate('General_ColumnExitsDocumentation'), + 'nb_visits' => Piwik_Translate('General_ColumnUniquePageviewsDocumentation'), + 'exit_rate' => Piwik_Translate('General_ColumnExitRateDocumentation') + ), + 'documentation' => Piwik_Translate('Actions_ExitPagesReportDocumentation', '<br />') + . ' ' . Piwik_Translate('General_UsePlusMinusIconsDocumentation'), + 'processedMetrics' => false, + 'actionToLoadSubTables' => 'getExitPageUrls', + 'order' => 4 + ); + + // page titles report + $reports[] = array( + 'category' => Piwik_Translate('Actions_Actions'), + 'name' => Piwik_Translate('Actions_SubmenuPageTitles'), + 'module' => 'Actions', + 'action' => 'getPageTitles', + 'dimension' => Piwik_Translate('Actions_ColumnPageName'), + 'metrics' => $metrics, + 'metricsDocumentation' => $documentation, + 'documentation' => Piwik_Translate('Actions_PageTitlesReportDocumentation', array('<br />', htmlentities('<title>'))), + 'processedMetrics' => false, + 'actionToLoadSubTables' => 'getPageTitles', + 'order' => 5, + + ); + + // entry page titles report + $reports[] = array( + 'category' => Piwik_Translate('Actions_Actions'), + 'name' => Piwik_Translate('Actions_EntryPageTitles'), + 'module' => 'Actions', + 'action' => 'getEntryPageTitles', + 'dimension' => Piwik_Translate('Actions_ColumnPageName'), + 'metrics' => array( + 'entry_nb_visits' => Piwik_Translate('General_ColumnEntrances'), + 'entry_bounce_count' => Piwik_Translate('General_ColumnBounces'), + 'bounce_rate' => Piwik_Translate('General_ColumnBounceRate'), + ), + 'metricsDocumentation' => array( + 'entry_nb_visits' => Piwik_Translate('General_ColumnEntrancesDocumentation'), + 'entry_bounce_count' => Piwik_Translate('General_ColumnBouncesDocumentation'), + 'bounce_rate' => Piwik_Translate('General_ColumnBounceRateForPageDocumentation') + ), + 'documentation' => Piwik_Translate('Actions_ExitPageTitlesReportDocumentation', '<br />') + . ' ' . Piwik_Translate('General_UsePlusMinusIconsDocumentation'), + 'processedMetrics' => false, + 'actionToLoadSubTables' => 'getEntryPageTitles', + 'order' => 6 + ); + + // exit page titles report + $reports[] = array( + 'category' => Piwik_Translate('Actions_Actions'), + 'name' => Piwik_Translate('Actions_ExitPageTitles'), + 'module' => 'Actions', + 'action' => 'getExitPageTitles', + 'dimension' => Piwik_Translate('Actions_ColumnPageName'), + 'metrics' => array( + 'exit_nb_visits' => Piwik_Translate('General_ColumnExits'), + 'nb_visits' => Piwik_Translate('General_ColumnUniquePageviews'), + 'exit_rate' => Piwik_Translate('General_ColumnExitRate') + ), + 'metricsDocumentation' => array( + 'exit_nb_visits' => Piwik_Translate('General_ColumnExitsDocumentation'), + 'nb_visits' => Piwik_Translate('General_ColumnUniquePageviewsDocumentation'), + 'exit_rate' => Piwik_Translate('General_ColumnExitRateDocumentation') + ), + 'documentation' => Piwik_Translate('Actions_EntryPageTitlesReportDocumentation', '<br />') + . ' ' . Piwik_Translate('General_UsePlusMinusIconsDocumentation'), + 'processedMetrics' => false, + 'actionToLoadSubTables' => 'getExitPageTitles', + 'order' => 7 + ); + + $documentation = array( + 'nb_visits' => Piwik_Translate('Actions_ColumnUniqueClicksDocumentation'), + 'nb_hits' => Piwik_Translate('Actions_ColumnClicksDocumentation') + ); + + // outlinks report + $reports[] = array( + 'category' => Piwik_Translate('Actions_Actions'), + 'name' => Piwik_Translate('Actions_SubmenuOutlinks'), + 'module' => 'Actions', + 'action' => 'getOutlinks', + 'dimension' => Piwik_Translate('Actions_ColumnClickedURL'), + 'metrics' => array( + 'nb_visits' => Piwik_Translate('Actions_ColumnUniqueClicks'), + 'nb_hits' => Piwik_Translate('Actions_ColumnClicks') + ), + 'metricsDocumentation' => $documentation, + 'documentation' => Piwik_Translate('Actions_OutlinksReportDocumentation') . ' ' + . Piwik_Translate('Actions_OutlinkDocumentation') . '<br />' + . Piwik_Translate('General_UsePlusMinusIconsDocumentation'), + 'processedMetrics' => false, + 'actionToLoadSubTables' => 'getOutlinks', + 'order' => 8, + ); + + // downloads report + $reports[] = array( + 'category' => Piwik_Translate('Actions_Actions'), + 'name' => Piwik_Translate('Actions_SubmenuDownloads'), + 'module' => 'Actions', + 'action' => 'getDownloads', + 'dimension' => Piwik_Translate('Actions_ColumnDownloadURL'), + 'metrics' => array( + 'nb_visits' => Piwik_Translate('Actions_ColumnUniqueDownloads'), + 'nb_hits' => Piwik_Translate('Actions_ColumnDownloads') + ), + 'metricsDocumentation' => $documentation, + 'documentation' => Piwik_Translate('Actions_DownloadsReportDocumentation', '<br />'), + 'processedMetrics' => false, + 'actionToLoadSubTables' => 'getDownloads', + 'order' => 9, + ); + + if ($this->isSiteSearchEnabled()) { + // Search Keywords + $reports[] = array( + 'category' => Piwik_Translate('Actions_SubmenuSitesearch'), + 'name' => Piwik_Translate('Actions_WidgetSearchKeywords'), + 'module' => 'Actions', + 'action' => 'getSiteSearchKeywords', + 'dimension' => Piwik_Translate('Actions_ColumnSearchKeyword'), + 'metrics' => array( + 'nb_visits' => Piwik_Translate('Actions_ColumnSearches'), + 'nb_pages_per_search' => Piwik_Translate('Actions_ColumnPagesPerSearch'), + 'exit_rate' => Piwik_Translate('Actions_ColumnSearchExits'), + ), + 'metricsDocumentation' => array( + 'nb_visits' => Piwik_Translate('Actions_ColumnSearchesDocumentation'), + 'nb_pages_per_search' => Piwik_Translate('Actions_ColumnPagesPerSearchDocumentation'), + 'exit_rate' => Piwik_Translate('Actions_ColumnSearchExitsDocumentation'), + ), + 'documentation' => Piwik_Translate('Actions_SiteSearchKeywordsDocumentation') . '<br/><br/>' . Piwik_Translate('Actions_SiteSearchIntro') . '<br/><br/>' + . '<a href="http://piwik.org/docs/site-search/" target="_blank">' . Piwik_Translate('Actions_LearnMoreAboutSiteSearchLink') . '</a>', + 'processedMetrics' => false, + 'order' => 15 + ); + // No Result Search Keywords + $reports[] = array( + 'category' => Piwik_Translate('Actions_SubmenuSitesearch'), + 'name' => Piwik_Translate('Actions_WidgetSearchNoResultKeywords'), + 'module' => 'Actions', + 'action' => 'getSiteSearchNoResultKeywords', + 'dimension' => Piwik_Translate('Actions_ColumnNoResultKeyword'), + 'metrics' => array( + 'nb_visits' => Piwik_Translate('Actions_ColumnSearches'), + 'exit_rate' => Piwik_Translate('Actions_ColumnSearchExits'), + ), + 'metricsDocumentation' => array( + 'nb_visits' => Piwik_Translate('Actions_ColumnSearchesDocumentation'), + 'exit_rate' => Piwik_Translate('Actions_ColumnSearchExitsDocumentation'), + ), + 'documentation' => Piwik_Translate('Actions_SiteSearchIntro') . '<br /><br />' . Piwik_Translate('Actions_SiteSearchKeywordsNoResultDocumentation'), + 'processedMetrics' => false, + 'order' => 16 + ); + + if (self::isCustomVariablesPluginsEnabled()) { + // Search Categories + $reports[] = array( + 'category' => Piwik_Translate('Actions_SubmenuSitesearch'), + 'name' => Piwik_Translate('Actions_WidgetSearchCategories'), + 'module' => 'Actions', + 'action' => 'getSiteSearchCategories', + 'dimension' => Piwik_Translate('Actions_ColumnSearchCategory'), + 'metrics' => array( + 'nb_visits' => Piwik_Translate('Actions_ColumnSearches'), + 'nb_pages_per_search' => Piwik_Translate('Actions_ColumnPagesPerSearch'), + 'exit_rate' => Piwik_Translate('Actions_ColumnSearchExits'), + ), + 'metricsDocumentation' => array( + 'nb_visits' => Piwik_Translate('Actions_ColumnSearchesDocumentation'), + 'nb_pages_per_search' => Piwik_Translate('Actions_ColumnPagesPerSearchDocumentation'), + 'exit_rate' => Piwik_Translate('Actions_ColumnSearchExitsDocumentation'), + ), + 'documentation' => Piwik_Translate('Actions_SiteSearchCategories1') . '<br/>' . Piwik_Translate('Actions_SiteSearchCategories2'), + 'processedMetrics' => false, + 'order' => 17 + ); + } + + $documentation = Piwik_Translate('Actions_SiteSearchFollowingPagesDoc') . '<br/>' . Piwik_Translate('General_UsePlusMinusIconsDocumentation'); + // Pages URLs following Search + $reports[] = array( + 'category' => Piwik_Translate('Actions_SubmenuSitesearch'), + 'name' => Piwik_Translate('Actions_WidgetPageUrlsFollowingSearch'), + 'module' => 'Actions', + 'action' => 'getPageUrlsFollowingSiteSearch', + 'dimension' => Piwik_Translate('General_ColumnDestinationPage'), + 'metrics' => array( + 'nb_hits_following_search' => Piwik_Translate('General_ColumnViewedAfterSearch'), + 'nb_hits' => Piwik_Translate('General_ColumnTotalPageviews'), + ), + 'metricsDocumentation' => array( + 'nb_hits_following_search' => Piwik_Translate('General_ColumnViewedAfterSearchDocumentation'), + 'nb_hits' => Piwik_Translate('General_ColumnPageviewsDocumentation'), + ), + 'documentation' => $documentation, + 'processedMetrics' => false, + 'order' => 18 + ); + // Pages Titles following Search + $reports[] = array( + 'category' => Piwik_Translate('Actions_SubmenuSitesearch'), + 'name' => Piwik_Translate('Actions_WidgetPageTitlesFollowingSearch'), + 'module' => 'Actions', + 'action' => 'getPageTitlesFollowingSiteSearch', + 'dimension' => Piwik_Translate('General_ColumnDestinationPage'), + 'metrics' => array( + 'nb_hits_following_search' => Piwik_Translate('General_ColumnViewedAfterSearch'), + 'nb_hits' => Piwik_Translate('General_ColumnTotalPageviews'), + ), + 'metricsDocumentation' => array( + 'nb_hits_following_search' => Piwik_Translate('General_ColumnViewedAfterSearchDocumentation'), + 'nb_hits' => Piwik_Translate('General_ColumnPageviewsDocumentation'), + ), + 'documentation' => $documentation, + 'processedMetrics' => false, + 'order' => 19 + ); + } + } + + function addWidgets() + { + Piwik_AddWidget('Actions_Actions', 'Actions_SubmenuPages', 'Actions', 'getPageUrls'); + Piwik_AddWidget('Actions_Actions', 'Actions_WidgetPageTitles', 'Actions', 'getPageTitles'); + Piwik_AddWidget('Actions_Actions', 'Actions_SubmenuOutlinks', 'Actions', 'getOutlinks'); + Piwik_AddWidget('Actions_Actions', 'Actions_SubmenuDownloads', 'Actions', 'getDownloads'); + Piwik_AddWidget('Actions_Actions', 'Actions_WidgetPagesEntry', 'Actions', 'getEntryPageUrls'); + Piwik_AddWidget('Actions_Actions', 'Actions_WidgetPagesExit', 'Actions', 'getExitPageUrls'); + Piwik_AddWidget('Actions_Actions', 'Actions_WidgetEntryPageTitles', 'Actions', 'getEntryPageTitles'); + Piwik_AddWidget('Actions_Actions', 'Actions_WidgetExitPageTitles', 'Actions', 'getExitPageTitles'); + + if ($this->isSiteSearchEnabled()) { + Piwik_AddWidget('Actions_SubmenuSitesearch', 'Actions_WidgetSearchKeywords', 'Actions', 'getSiteSearchKeywords'); + + if (self::isCustomVariablesPluginsEnabled()) { + Piwik_AddWidget('Actions_SubmenuSitesearch', 'Actions_WidgetSearchCategories', 'Actions', 'getSiteSearchCategories'); + } + Piwik_AddWidget('Actions_SubmenuSitesearch', 'Actions_WidgetSearchNoResultKeywords', 'Actions', 'getSiteSearchNoResultKeywords'); + Piwik_AddWidget('Actions_SubmenuSitesearch', 'Actions_WidgetPageUrlsFollowingSearch', 'Actions', 'getPageUrlsFollowingSiteSearch'); + Piwik_AddWidget('Actions_SubmenuSitesearch', 'Actions_WidgetPageTitlesFollowingSearch', 'Actions', 'getPageTitlesFollowingSiteSearch'); + } + } + + function addMenus() + { + Piwik_AddMenu('Actions_Actions', '', array('module' => 'Actions', 'action' => 'indexPageUrls'), true, 15); + Piwik_AddMenu('Actions_Actions', 'Actions_SubmenuPages', array('module' => 'Actions', 'action' => 'indexPageUrls'), true, 1); + Piwik_AddMenu('Actions_Actions', 'Actions_SubmenuPagesEntry', array('module' => 'Actions', 'action' => 'indexEntryPageUrls'), true, 2); + Piwik_AddMenu('Actions_Actions', 'Actions_SubmenuPagesExit', array('module' => 'Actions', 'action' => 'indexExitPageUrls'), true, 3); + Piwik_AddMenu('Actions_Actions', 'Actions_SubmenuPageTitles', array('module' => 'Actions', 'action' => 'indexPageTitles'), true, 4); + Piwik_AddMenu('Actions_Actions', 'Actions_SubmenuOutlinks', array('module' => 'Actions', 'action' => 'indexOutlinks'), true, 6); + Piwik_AddMenu('Actions_Actions', 'Actions_SubmenuDownloads', array('module' => 'Actions', 'action' => 'indexDownloads'), true, 7); + + if ($this->isSiteSearchEnabled()) { + Piwik_AddMenu('Actions_Actions', 'Actions_SubmenuSitesearch', array('module' => 'Actions', 'action' => 'indexSiteSearch'), true, 5); + } + } + + protected function isSiteSearchEnabled() + { + $idSite = Piwik_Common::getRequestVar('idSite', 0, 'int'); + if ($idSite == 0) { + return false; + } + return Piwik_Site::isSiteSearchEnabledFor($idSite); + } + + + /** + * @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" : + * visits, unique visitors, bounce count, sum visit length. + * + * @param Piwik_Event_Notification $notification notification object + */ + public function archiveDay($notification) + { + /* @var $archiveProcessing Piwik_ArchiveProcessing_Day */ + $archiveProcessing = $notification->getNotificationObject(); + + if (!$archiveProcessing->shouldProcessReportsForPlugin($this->getPluginName())) return; + + $actionsArchiving = new Piwik_Actions_Archiving($archiveProcessing->idsite); + return $actionsArchiving->archiveDay($archiveProcessing); + } + + static public function checkCustomVariablesPluginEnabled() + { + if (!self::isCustomVariablesPluginsEnabled()) { + throw new Exception("To Track Site Search Categories, please ask the Piwik Administrator to enable the 'Custom Variables' plugin in Settings > Plugins."); + } + } + + static protected function isCustomVariablesPluginsEnabled() + { + return Piwik_PluginsManager::getInstance()->isPluginActivated('CustomVariables'); + } } diff --git a/plugins/Actions/Archiving.php b/plugins/Actions/Archiving.php index c0d36d1083..db71faf788 100644 --- a/plugins/Actions/Archiving.php +++ b/plugins/Actions/Archiving.php @@ -16,100 +16,100 @@ */ class Piwik_Actions_Archiving { - protected $actionsTablesByType = null; - - public static $actionTypes = array( - Piwik_Tracker_Action::TYPE_ACTION_URL, - Piwik_Tracker_Action::TYPE_OUTLINK, - Piwik_Tracker_Action::TYPE_DOWNLOAD, - Piwik_Tracker_Action::TYPE_ACTION_NAME, - Piwik_Tracker_Action::TYPE_SITE_SEARCH, - ); - - 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, - ); - - 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, - ); - - protected $isSiteSearchEnabled = false; - - function __construct($idSite) - { - $this->isSiteSearchEnabled = Piwik_Site::isSiteSearchEnabledFor($idSite); - } - - /** - * Archives Actions reports for a Period - * @param Piwik_ArchiveProcessing $archiveProcessing - * @return bool - */ - public function archivePeriod(Piwik_ArchiveProcessing $archiveProcessing) - { - Piwik_Actions_ArchivingHelper::reloadConfig(); - $dataTableToSum = array( - 'Actions_actions', - 'Actions_downloads', - 'Actions_outlink', - 'Actions_actions_url', - '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', - )); - - // 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; - } - - /** - * Archives Actions reports for a Day - * - * @param Piwik_ArchiveProcessing $archiveProcessing - * @return bool - */ - public function archiveDay(Piwik_ArchiveProcessing $archiveProcessing) - { - $rankingQueryLimit = self::getRankingQueryLimit(); - Piwik_Actions_ArchivingHelper::reloadConfig(); - - $this->initActionsTables(); - $this->archiveDayActions($archiveProcessing, $rankingQueryLimit); - $this->archiveDayEntryActions($archiveProcessing, $rankingQueryLimit); - $this->archiveDayExitActions($archiveProcessing, $rankingQueryLimit); - $this->archiveDayActionsTime($archiveProcessing, $rankingQueryLimit); - - // Record the final datasets - $this->archiveDayRecordInDatabase($archiveProcessing); - - return true; - } - - /* - * Page URLs and Page names, general stats - */ - protected function archiveDayActions($archiveProcessing, $rankingQueryLimit) - { - $select = "log_action.name, + protected $actionsTablesByType = null; + + public static $actionTypes = array( + Piwik_Tracker_Action::TYPE_ACTION_URL, + Piwik_Tracker_Action::TYPE_OUTLINK, + Piwik_Tracker_Action::TYPE_DOWNLOAD, + Piwik_Tracker_Action::TYPE_ACTION_NAME, + Piwik_Tracker_Action::TYPE_SITE_SEARCH, + ); + + 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, + ); + + 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, + ); + + protected $isSiteSearchEnabled = false; + + function __construct($idSite) + { + $this->isSiteSearchEnabled = Piwik_Site::isSiteSearchEnabledFor($idSite); + } + + /** + * Archives Actions reports for a Period + * @param Piwik_ArchiveProcessing $archiveProcessing + * @return bool + */ + public function archivePeriod(Piwik_ArchiveProcessing $archiveProcessing) + { + Piwik_Actions_ArchivingHelper::reloadConfig(); + $dataTableToSum = array( + 'Actions_actions', + 'Actions_downloads', + 'Actions_outlink', + 'Actions_actions_url', + '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', + )); + + // 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; + } + + /** + * Archives Actions reports for a Day + * + * @param Piwik_ArchiveProcessing $archiveProcessing + * @return bool + */ + public function archiveDay(Piwik_ArchiveProcessing $archiveProcessing) + { + $rankingQueryLimit = self::getRankingQueryLimit(); + Piwik_Actions_ArchivingHelper::reloadConfig(); + + $this->initActionsTables(); + $this->archiveDayActions($archiveProcessing, $rankingQueryLimit); + $this->archiveDayEntryActions($archiveProcessing, $rankingQueryLimit); + $this->archiveDayExitActions($archiveProcessing, $rankingQueryLimit); + $this->archiveDayActionsTime($archiveProcessing, $rankingQueryLimit); + + // Record the final datasets + $this->archiveDayRecordInDatabase($archiveProcessing); + + return true; + } + + /* + * Page URLs and Page names, general stats + */ + protected function archiveDayActions($archiveProcessing, $rankingQueryLimit) + { + $select = "log_action.name, log_action.type, log_action.idaction, log_action.url_prefix, @@ -117,432 +117,422 @@ class Piwik_Actions_Archiving count(distinct log_link_visit_action.idvisitor) as `" . Piwik_Archive::INDEX_NB_UNIQ_VISITORS . "`, count(*) as `" . Piwik_Archive::INDEX_PAGE_NB_HITS . "`, sum( - case when " . Piwik_Tracker_Action::DB_COLUMN_TIME_GENERATION ." is null + case when " . Piwik_Tracker_Action::DB_COLUMN_TIME_GENERATION . " is null then 0 - else " . Piwik_Tracker_Action::DB_COLUMN_TIME_GENERATION ." + else " . Piwik_Tracker_Action::DB_COLUMN_TIME_GENERATION . " end ) / 1000 as `" . Piwik_Archive::INDEX_PAGE_SUM_TIME_GENERATION . "`, sum( - case when " . Piwik_Tracker_Action::DB_COLUMN_TIME_GENERATION ." is null + 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 . "`"; - $from = array( - "log_link_visit_action", - array( - "table" => "log_action", - "joinOn" => "log_link_visit_action.%s = log_action.idaction" - ) - ); + $from = array( + "log_link_visit_action", + array( + "table" => "log_action", + "joinOn" => "log_link_visit_action.%s = log_action.idaction" + ) + ); - $where = "log_link_visit_action.server_time >= ? + $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.%s IS NOT NULL"; - $groupBy = "log_action.idaction"; - $orderBy = "`" . Piwik_Archive::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'); - 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_Archive::INDEX_PAGE_SUM_TIME_GENERATION, 'sum'); - $rankingQuery->addColumn(Piwik_Archive::INDEX_PAGE_NB_HITS_WITH_TIME_GENERATION, 'sum'); - $rankingQuery->partitionResultIntoMultipleGroups('type', array_keys($this->actionsTablesByType)); - } - - // Special Magic to get - // 1) No result Keywords - // 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 . "`"; - - //we need an extra JOIN to know whether the referrer "idaction_name_ref" was a Site Search request - $from[] = array( - "table" => "log_action", - "tableAlias" => "log_action_name_ref", - "joinOn" => "log_link_visit_action.idaction_name_ref = log_action_name_ref.idaction" - ); - - $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."`"; - - $select .= $selectFlagNoResultKeywords - . $selectSiteSearchFollowingPages; - // Not working yet + $groupBy = "log_action.idaction"; + $orderBy = "`" . Piwik_Archive::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'); + 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_Archive::INDEX_PAGE_SUM_TIME_GENERATION, 'sum'); + $rankingQuery->addColumn(Piwik_Archive::INDEX_PAGE_NB_HITS_WITH_TIME_GENERATION, 'sum'); + $rankingQuery->partitionResultIntoMultipleGroups('type', array_keys($this->actionsTablesByType)); + } + + // Special Magic to get + // 1) No result Keywords + // 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 . "`"; + + //we need an extra JOIN to know whether the referrer "idaction_name_ref" was a Site Search request + $from[] = array( + "table" => "log_action", + "tableAlias" => "log_action_name_ref", + "joinOn" => "log_link_visit_action.idaction_name_ref = log_action_name_ref.idaction" + ); + + $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 . "`"; + + $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_url", $archiveProcessing, $rankingQuery); - } - - /** - * Entry actions for Page URLs and Page names - */ - protected function archiveDayEntryActions($archiveProcessing, $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->partitionResultIntoMultipleGroups('type', array_keys($this->actionsTablesByType)); - - $extraSelects = 'log_action.type, log_action.name,'; - $from = array( - "log_visit", - array( - "table" => "log_action", - "joinOn" => "log_visit.%s = log_action.idaction" - ) - ); - $orderBy = "`" . Piwik_Archive::INDEX_PAGE_ENTRY_NB_ACTIONS . "` DESC, log_action.name ASC"; - } else { - $extraSelects = false; - $from = "log_visit"; - $orderBy = false; - } - - $select = "log_visit.%s as idaction, $extraSelects + } + + $this->archiveDayQueryProcess($select, $from, $where, $orderBy, $groupBy, + "idaction_name", $archiveProcessing, $rankingQuery); + + $this->archiveDayQueryProcess($select, $from, $where, $orderBy, $groupBy, + "idaction_url", $archiveProcessing, $rankingQuery); + } + + /** + * Entry actions for Page URLs and Page names + */ + protected function archiveDayEntryActions($archiveProcessing, $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->partitionResultIntoMultipleGroups('type', array_keys($this->actionsTablesByType)); + + $extraSelects = 'log_action.type, log_action.name,'; + $from = array( + "log_visit", + array( + "table" => "log_action", + "joinOn" => "log_visit.%s = log_action.idaction" + ) + ); + $orderBy = "`" . Piwik_Archive::INDEX_PAGE_ENTRY_NB_ACTIONS . "` DESC, log_action.name ASC"; + } else { + $extraSelects = false; + $from = "log_visit"; + $orderBy = false; + } + + $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 . "`"; - $where = "log_visit.visit_last_action_time >= ? + $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_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_name", $archiveProcessing, $rankingQuery); - } - - - /** - * Time per action - */ - protected function archiveDayActionsTime($archiveProcessing, $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->partitionResultIntoMultipleGroups('type', array_keys($this->actionsTablesByType)); - - $extraSelects = "log_action.type, log_action.name, count(*) as `" . Piwik_Archive::INDEX_PAGE_NB_HITS . "`,"; - $from = array( - "log_link_visit_action", - array( - "table" => "log_action", - "joinOn" => "log_link_visit_action.%s = log_action.idaction" - ) - ); - $orderBy = "`" . Piwik_Archive::INDEX_PAGE_NB_HITS . "` DESC, log_action.name ASC"; - } else { - $extraSelects = false; - $from = "log_link_visit_action"; - $orderBy = false; - } - - $select = "log_link_visit_action.%s as idaction, $extraSelects + $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_name", $archiveProcessing, $rankingQuery); + } + + + /** + * Time per action + */ + protected function archiveDayActionsTime($archiveProcessing, $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->partitionResultIntoMultipleGroups('type', array_keys($this->actionsTablesByType)); + + $extraSelects = "log_action.type, log_action.name, count(*) as `" . Piwik_Archive::INDEX_PAGE_NB_HITS . "`,"; + $from = array( + "log_link_visit_action", + array( + "table" => "log_action", + "joinOn" => "log_link_visit_action.%s = log_action.idaction" + ) + ); + $orderBy = "`" . Piwik_Archive::INDEX_PAGE_NB_HITS . "` DESC, log_action.name ASC"; + } else { + $extraSelects = false; + $from = "log_link_visit_action"; + $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 . "`"; - $where = "log_link_visit_action.server_time >= ? + $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_link_visit_action.%s, idaction"; - - $this->archiveDayQueryProcess($select, $from, $where, $orderBy, $groupBy, - "idaction_url_ref", $archiveProcessing, $rankingQuery); - - $this->archiveDayQueryProcess($select, $from, $where, $orderBy, $groupBy, - "idaction_name_ref", $archiveProcessing, $rankingQuery); - } - - /** - * Exit actions - */ - public function archiveDayExitActions($archiveProcessing, $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->partitionResultIntoMultipleGroups('type', array_keys($this->actionsTablesByType)); - - $extraSelects = 'log_action.type, log_action.name,'; - $from = array( - "log_visit", - array( - "table" => "log_action", - "joinOn" => "log_visit.%s = log_action.idaction" - ) - ); - $orderBy = "`" . Piwik_Archive::INDEX_PAGE_EXIT_NB_VISITS . "` DESC, log_action.name ASC"; - } else { - $extraSelects = false; - $from = "log_visit"; - $orderBy = false; - } - - $select = "log_visit.%s as idaction, $extraSelects + $groupBy = "log_link_visit_action.%s, idaction"; + + $this->archiveDayQueryProcess($select, $from, $where, $orderBy, $groupBy, + "idaction_url_ref", $archiveProcessing, $rankingQuery); + + $this->archiveDayQueryProcess($select, $from, $where, $orderBy, $groupBy, + "idaction_name_ref", $archiveProcessing, $rankingQuery); + } + + /** + * Exit actions + */ + public function archiveDayExitActions($archiveProcessing, $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->partitionResultIntoMultipleGroups('type', array_keys($this->actionsTablesByType)); + + $extraSelects = 'log_action.type, log_action.name,'; + $from = array( + "log_visit", + array( + "table" => "log_action", + "joinOn" => "log_visit.%s = log_action.idaction" + ) + ); + $orderBy = "`" . Piwik_Archive::INDEX_PAGE_EXIT_NB_VISITS . "` DESC, log_action.name ASC"; + } else { + $extraSelects = false; + $from = "log_visit"; + $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 . "`"; - $where = "log_visit.visit_last_action_time >= ? + $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_visit.%s, idaction"; - - $this->archiveDayQueryProcess($select, $from, $where, $orderBy, $groupBy, - "visit_exit_idaction_url", $archiveProcessing, $rankingQuery); - - $this->archiveDayQueryProcess($select, $from, $where, $orderBy, $groupBy, - "visit_exit_idaction_name", $archiveProcessing, $rankingQuery); - return array($rankingQuery, $extraSelects, $from, $orderBy, $select, $where, $groupBy); - } - - - /** - * Records in the DB the archived reports for Page views, Downloads, Outlinks, and Page titles - * - * @param $archiveProcessing - */ - protected function archiveDayRecordInDatabase($archiveProcessing) - { - 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))); - 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); - } - - protected function deleteUnusedColumnsFromKeywordsDataTable($dataTable) - { - $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->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 - )); - } - - - /** - * 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'] - ); - } - - - /** - * @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) - { - // 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; - } - - - /** - * 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. - * - * @param $dataTable Piwik_DataTable - */ - static public function deleteInvalidSummedColumnsFromDataTable($dataTable) - { - foreach($dataTable->getRows() as $id => $row) - { - if(($idSubtable = $row->getIdSubDataTable()) !== null - || $id === Piwik_DataTable::ID_SUMMARY_ROW) - { - if ($idSubtable !== null) - { - $subtable = Piwik_DataTable_Manager::getInstance()->getTable($idSubtable); - self::deleteInvalidSummedColumnsFromDataTable($subtable); - } - - if ($row instanceof Piwik_DataTable_Row_DataTableSummary) - { - $row->recalculate(); - } - - foreach(self::$invalidSummedColumnNameToDeleteFromDayArchive as $name) - { - $row->deleteColumn($name); - } - } - } - - // And this as well - self::removeEmptyColumns($dataTable); - } - - /** - * 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); - - $this->actionsTablesByType[$type] = $dataTable; - } - } - - protected function isSiteSearchEnabled() - { - return $this->isSiteSearchEnabled; - } + $groupBy = "log_visit.%s, idaction"; + + $this->archiveDayQueryProcess($select, $from, $where, $orderBy, $groupBy, + "visit_exit_idaction_url", $archiveProcessing, $rankingQuery); + + $this->archiveDayQueryProcess($select, $from, $where, $orderBy, $groupBy, + "visit_exit_idaction_name", $archiveProcessing, $rankingQuery); + return array($rankingQuery, $extraSelects, $from, $orderBy, $select, $where, $groupBy); + } + + + /** + * Records in the DB the archived reports for Page views, Downloads, Outlinks, and Page titles + * + * @param $archiveProcessing + */ + protected function archiveDayRecordInDatabase($archiveProcessing) + { + 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))); + 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); + } + + protected function deleteUnusedColumnsFromKeywordsDataTable($dataTable) + { + $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->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 + )); + } + + + /** + * 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'] + ); + } + + + /** + * @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) + { + // 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; + } + + + /** + * 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. + * + * @param $dataTable Piwik_DataTable + */ + static public function deleteInvalidSummedColumnsFromDataTable($dataTable) + { + foreach ($dataTable->getRows() as $id => $row) { + if (($idSubtable = $row->getIdSubDataTable()) !== null + || $id === Piwik_DataTable::ID_SUMMARY_ROW + ) { + if ($idSubtable !== null) { + $subtable = Piwik_DataTable_Manager::getInstance()->getTable($idSubtable); + self::deleteInvalidSummedColumnsFromDataTable($subtable); + } + + if ($row instanceof Piwik_DataTable_Row_DataTableSummary) { + $row->recalculate(); + } + + foreach (self::$invalidSummedColumnNameToDeleteFromDayArchive as $name) { + $row->deleteColumn($name); + } + } + } + + // And this as well + self::removeEmptyColumns($dataTable); + } + + /** + * 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); + + $this->actionsTablesByType[$type] = $dataTable; + } + } + + protected function isSiteSearchEnabled() + { + return $this->isSiteSearchEnabled; + } } diff --git a/plugins/Actions/ArchivingHelper.php b/plugins/Actions/ArchivingHelper.php index 30fb58b555..739e8a2fef 100644 --- a/plugins/Actions/ArchivingHelper.php +++ b/plugins/Actions/ArchivingHelper.php @@ -19,495 +19,452 @@ class Piwik_Actions_ArchivingHelper { - const OTHERS_ROW_KEY = ''; - - /** - * @param Zend_Db_Statement|PDOStatement $query - * @param string|bool $fieldQueried - * @param array $actionsTablesByType - * @return int - */ - static public function updateActionsTableWithRowQuery($query, $fieldQueried, & $actionsTablesByType) - { - $rowsProcessed = 0; - while( $row = $query->fetch() ) - { - if(empty($row['idaction'])) - { - $row['type'] = ($fieldQueried == 'idaction_url' ? Piwik_Tracker_Action::TYPE_ACTION_URL : Piwik_Tracker_Action::TYPE_ACTION_NAME); - // This will be replaced with 'X not defined' later - $row['name'] = ''; - // Yes, this is kind of a hack, so we don't mix 'page url not defined' with 'page title not defined' etc. - $row['idaction'] = -$row['type']; - } - - if($row['type'] != Piwik_Tracker_Action::TYPE_SITE_SEARCH) - { - unset($row[Piwik_Archive::INDEX_SITE_SEARCH_HAS_NO_RESULT]); - } - - // This will appear as <url /> in the API, which is actually very important to keep - // eg. When there's at least one row in a report that does not have a URL, not having this <url/> would break HTML/PDF reports. - $url = ''; - if($row['type'] == Piwik_Tracker_Action::TYPE_SITE_SEARCH - || $row['type'] == Piwik_Tracker_Action::TYPE_ACTION_NAME) - { - $url = null; - } - elseif(!empty($row['name']) - && $row['name'] != Piwik_DataTable::LABEL_SUMMARY_ROW) - { - $url = Piwik_Tracker_Action::reconstructNormalizedUrl((string)$row['name'], $row['url_prefix']); - } - - if(isset($row['name']) - && isset($row['type'])) - { - $actionName = $row['name']; - $actionType = $row['type']; - $urlPrefix = $row['url_prefix']; - $idaction = $row['idaction']; - - // in some unknown case, the type field is NULL, as reported in #1082 - we ignore this page view - if(empty($actionType)) - { - if ($idaction != Piwik_DataTable::LABEL_SUMMARY_ROW) - { - self::setCachedActionRow($idaction, $actionType, false); - } - continue; - } - - $actionRow = self::getActionRow($actionName, $actionType, $urlPrefix, $actionsTablesByType); - - self::setCachedActionRow($idaction, $actionType, $actionRow); - } - else - { - $actionRow = self::getCachedActionRow($row['idaction'], $row['type']); - - // Action processed as "to skip" for some reasons - if($actionRow === false) - { - continue; - } - } - - - if (is_null($actionRow)) - { - continue; - } - - // Here we do ensure that, the Metadata URL set for a given row, is the one from the Pageview with the most hits. - // This is to ensure that when, different URLs are loaded with the same page name. - // For example http://piwik.org and http://id.piwik.org are reported in Piwik > Actions > Pages with /index - // But, we must make sure http://piwik.org is used to link & for transitions - // Note: this code is partly duplicated from Piwik_DataTable_Row->sumRowMetadata() - if( !is_null($url) - && !$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) - { - $actionRow->setMetadata('url', $url); - $actionRow->maxVisitsSummed = $row[Piwik_Archive::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; - } - } - - if ($row['type'] != Piwik_Tracker_Action::TYPE_ACTION_URL - && $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_Archive::INDEX_PAGE_NB_HITS_WITH_TIME_GENERATION, $row)) { - unset($row[Piwik_Archive::INDEX_PAGE_NB_HITS_WITH_TIME_GENERATION]); - } - } - - unset($row['name']); - unset($row['type']); - unset($row['idaction']); - unset($row['url_prefix']); - - foreach($row as $name => $value) - { - // in some edge cases, we have twice the same action name with 2 different idaction - // - this happens when 2 visitors visit the same new page at the same time, and 2 actions get recorded for the same name - // - this could also happen when 2 URLs end up having the same label (eg. 2 subdomains get aggregated to the "/index" page name) - if(($alreadyValue = $actionRow->getColumn($name)) !== false) - { - $actionRow->setColumn($name, $alreadyValue+$value); - } - else - { - $actionRow->addColumn($name, $value); - } - } - - // 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) - { - // 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) - { - $actionRow->addColumn($name, $value); - } - } - $rowsProcessed++; - } - - // just to make sure php copies the last $actionRow in the $parentTable array - $actionRow =& $actionsTablesByType; - return $rowsProcessed; - } - - static public $maximumRowsInDataTableLevelZero; - static public $maximumRowsInSubDataTable; - static public $columnToSortByBeforeTruncation; - - static protected $actionUrlCategoryDelimiter = null; - static protected $actionTitleCategoryDelimiter = null; - static protected $defaultActionName = null; - static protected $defaultActionNameWhenNotDefined = null; - static protected $defaultActionUrlWhenNotDefined = null; - - static public function reloadConfig() - { - // for BC, we read the old style delimiter first (see #1067)Row - $actionDelimiter = @Piwik_Config::getInstance()->General['action_category_delimiter']; - if(empty($actionDelimiter)) - { - self::$actionUrlCategoryDelimiter = Piwik_Config::getInstance()->General['action_url_category_delimiter']; - self::$actionTitleCategoryDelimiter = Piwik_Config::getInstance()->General['action_title_category_delimiter']; - } - else - { - self::$actionUrlCategoryDelimiter = self::$actionTitleCategoryDelimiter = $actionDelimiter; - } - - self::$defaultActionName = Piwik_Config::getInstance()->General['action_default_name']; - self::$columnToSortByBeforeTruncation = Piwik_Archive::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']; - - Piwik_DataTable::setMaximumDepthLevelAllowedAtLeast(self::getSubCategoryLevelLimit() + 1); - } - - - /** - * The default row is used when archiving, if data is inconsistent in the DB, - * there could be pages that have exit/entry hits, but don't yet - * have a record in the table (or the record was truncated). - * - * @return Piwik_DataTable_Row - */ - static private function getDefaultRow() - { - static $row = false; - if($row === false) { - // This row is used in the case where an action is know as an exit_action - // but this action was not properly recorded when it was hit in the first place - // 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, - ))); - } - return $row; - } - - /** - * Given a page name and type, builds a recursive datatable where - * each level of the tree is a category, based on the page name split by a delimiter (slash / by default) - * - * @param string $actionName - * @param int $actionType - * @param int $urlPrefix - * @param array $actionsTablesByType - * @return Piwik_DataTable - */ - protected static function getActionRow( $actionName, $actionType, $urlPrefix=null, &$actionsTablesByType ) - { - // we work on the root table of the given TYPE (either ACTION_URL or DOWNLOAD or OUTLINK etc.) - /* @var Piwik_DataTable $currentTable */ - $currentTable =& $actionsTablesByType[$actionType]; - - // check for ranking query cut-off - if ($actionName == Piwik_DataTable::LABEL_SUMMARY_ROW) - { - $summaryRow = $currentTable->getRowFromId(Piwik_DataTable::ID_SUMMARY_ROW); - if ($summaryRow === false) - { - $summaryRow = $currentTable->addSummaryRow(self::createSummaryRow()); - } - return $summaryRow; - } - - // go to the level of the subcategory - $actionExplodedNames = self::getActionExplodedNames($actionName, $actionType, $urlPrefix); - list($row, $level) = $currentTable->walkPath( - $actionExplodedNames, self::getDefaultRowColumns(), self::$maximumRowsInSubDataTable); - - return $row; - } - - /** - * Explodes action name into an array of elements. - * - * NOTE: before calling this function make sure Piwik_Actions_ArchivingHelper::reloadConfig(); is called - * - * for downloads: - * we explode link http://piwik.org/some/path/piwik.zip into an array( 'piwik.org', '/some/path/piwik.zip' ); - * - * for outlinks: - * we explode link http://dev.piwik.org/some/path into an array( 'dev.piwik.org', '/some/path' ); - * - * for action urls: - * we explode link http://piwik.org/some/path into an array( 'some', 'path' ); - * - * for action names: - * we explode name 'Piwik / Category 1 / Category 2' into an array('Piwik', 'Category 1', 'Category 2'); - * - * @param string action name - * @param int action type - * @param int url prefix (only used for TYPE_ACTION_URL) - * @return array of exploded elements from $name - */ - static public function getActionExplodedNames($name, $type, $urlPrefix=null) - { - // Site Search does not split Search keywords - if($type == Piwik_Tracker_Action::TYPE_SITE_SEARCH) - { - return array($name); - } - - $matches = array(); - $isUrl = false; - $name = str_replace("\n", "", $name); - - $urlRegexAfterDomain = '([^/]+)[/]?([^#]*)[#]?(.*)'; - if ($urlPrefix === null) - { - // match url with protocol (used for outlinks / downloads) - $urlRegex = '@^http[s]?://'.$urlRegexAfterDomain.'$@i'; - } - else - { - // the name is a url that does not contain protocol and www anymore - // we know that normalization has been done on db level because $urlPrefix is set - $urlRegex = '@^'.$urlRegexAfterDomain.'$@i'; - } - - preg_match($urlRegex, $name, $matches); - if( count($matches) ) - { - $isUrl = true; - $urlHost = $matches[1]; - $urlPath = $matches[2]; - $urlFragment = $matches[3]; - } - - if($type == Piwik_Tracker_Action::TYPE_DOWNLOAD - || $type == Piwik_Tracker_Action::TYPE_OUTLINK) - { - if( $isUrl ) - { - return array(trim($urlHost), '/' . trim($urlPath)); - } - } - - if( $isUrl ) - { - $name = $urlPath; - - if( $name === '' || substr($name, -1) == '/' ) - { - $name .= self::$defaultActionName; - } - } - - if($type == Piwik_Tracker_Action::TYPE_ACTION_NAME) - { - $categoryDelimiter = self::$actionTitleCategoryDelimiter; - } - else - { - $categoryDelimiter = self::$actionUrlCategoryDelimiter; - } - - - if( $isUrl ) - { - $urlFragment = Piwik_Tracker_Action::processUrlFragment($urlFragment); - if(!empty($urlFragment)) { - $name .= '#' . $urlFragment; - } - } - - if(empty($categoryDelimiter)) - { - return array( trim($name) ); - } - - $split = explode($categoryDelimiter, $name, self::getSubCategoryLevelLimit()); - - // trim every category and remove empty categories - $split = array_map('trim', $split); - $split = array_filter($split, 'strlen'); - - // forces array key to start at 0 - $split = array_values($split); - - if( empty($split) ) - { - $defaultName = self::getUnknownActionName($type); - return array( trim($defaultName) ); - } - - $lastPageName = end($split); - // we are careful to prefix the page URL / name with some value - // so that if a page has the same name as a category - // we don't merge both entries - if($type != Piwik_Tracker_Action::TYPE_ACTION_NAME ) - { - $lastPageName = '/' . $lastPageName; - } - else - { - $lastPageName = ' ' . $lastPageName; - } - $split[count($split)-1] = $lastPageName; - return array_values( $split ); - } - - /** - * Gets the key for the cache of action rows from an action ID and type. - * - * @param int $idAction - * @param int $actionType - * @return string|int - */ - private static function getCachedActionRowKey( $idAction, $actionType ) - { - return $idAction == Piwik_DataTable::LABEL_SUMMARY_ROW - ? $actionType.'_others' - : $idAction; - } - - /** - * Returns the configured sub-category level limit. - * - * @return int - */ - public static function getSubCategoryLevelLimit() - { - return Piwik_Config::getInstance()->General['action_category_level_limit']; - } - - /** - * Returns default label for the action type - * - * @param $type - * @return string - */ - static public function getUnknownActionName($type) - { - if(empty(self::$defaultActionNameWhenNotDefined)) - { - self::$defaultActionNameWhenNotDefined = Piwik_Translate('General_NotDefined', Piwik_Translate('Actions_ColumnPageName')); - self::$defaultActionUrlWhenNotDefined = Piwik_Translate('General_NotDefined', Piwik_Translate('Actions_ColumnPageURL')); - } - if($type == Piwik_Tracker_Action::TYPE_ACTION_NAME) - { - return self::$defaultActionNameWhenNotDefined; - } - return self::$defaultActionUrlWhenNotDefined; - } - - /** - * Static cache to store Rows during processing - */ - static protected $cacheParsedAction = array(); - - public static function clearActionsCache() - { - self::$cacheParsedAction = array(); - } - - /** - * Get cached action row by id & type. If $idAction is set to -1, the 'Others' row - * for the specific action type will be returned. - * - * @param int $idAction - * @param int $actionType - * @return Piwik_DataTable_Row|false - */ - private static function getCachedActionRow( $idAction, $actionType ) - { - $cacheLabel = self::getCachedActionRowKey($idAction, $actionType); - - if (!isset(self::$cacheParsedAction[$cacheLabel])) - { - // This can happen when - // - We select an entry page ID that was only seen yesterday, so wasn't selected in the first query - // - We count time spent on a page, when this page was only seen yesterday - return false; - } - - return self::$cacheParsedAction[$cacheLabel]; - } - - /** - * Set cached action row for an id & type. - * - * @param int $idAction - * @param int $actionType - * @param Piwik_DataTable_Row - */ - private static function setCachedActionRow( $idAction, $actionType, $actionRow ) - { - $cacheLabel = self::getCachedActionRowKey($idAction, $actionType); - self::$cacheParsedAction[$cacheLabel] = $actionRow; - } - - /** - * Returns the default columns for a row in an Actions DataTable. - * - * @return array - */ - 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); - } - - /** - * Creates a summary row for an Actions DataTable. - * - * @return Piwik_DataTable_Row - */ - private static function createSummaryRow() - { - return new Piwik_DataTable_Row(array( - Piwik_DataTable_Row::COLUMNS => - array('label' => Piwik_DataTable::LABEL_SUMMARY_ROW) + self::getDefaultRowColumns() - )); - } + const OTHERS_ROW_KEY = ''; + + /** + * @param Zend_Db_Statement|PDOStatement $query + * @param string|bool $fieldQueried + * @param array $actionsTablesByType + * @return int + */ + static public function updateActionsTableWithRowQuery($query, $fieldQueried, & $actionsTablesByType) + { + $rowsProcessed = 0; + while ($row = $query->fetch()) { + if (empty($row['idaction'])) { + $row['type'] = ($fieldQueried == 'idaction_url' ? Piwik_Tracker_Action::TYPE_ACTION_URL : Piwik_Tracker_Action::TYPE_ACTION_NAME); + // This will be replaced with 'X not defined' later + $row['name'] = ''; + // Yes, this is kind of a hack, so we don't mix 'page url not defined' with 'page title not defined' etc. + $row['idaction'] = -$row['type']; + } + + if ($row['type'] != Piwik_Tracker_Action::TYPE_SITE_SEARCH) { + unset($row[Piwik_Archive::INDEX_SITE_SEARCH_HAS_NO_RESULT]); + } + + // This will appear as <url /> in the API, which is actually very important to keep + // eg. When there's at least one row in a report that does not have a URL, not having this <url/> would break HTML/PDF reports. + $url = ''; + if ($row['type'] == Piwik_Tracker_Action::TYPE_SITE_SEARCH + || $row['type'] == Piwik_Tracker_Action::TYPE_ACTION_NAME + ) { + $url = null; + } elseif (!empty($row['name']) + && $row['name'] != Piwik_DataTable::LABEL_SUMMARY_ROW + ) { + $url = Piwik_Tracker_Action::reconstructNormalizedUrl((string)$row['name'], $row['url_prefix']); + } + + if (isset($row['name']) + && isset($row['type']) + ) { + $actionName = $row['name']; + $actionType = $row['type']; + $urlPrefix = $row['url_prefix']; + $idaction = $row['idaction']; + + // in some unknown case, the type field is NULL, as reported in #1082 - we ignore this page view + if (empty($actionType)) { + if ($idaction != Piwik_DataTable::LABEL_SUMMARY_ROW) { + self::setCachedActionRow($idaction, $actionType, false); + } + continue; + } + + $actionRow = self::getActionRow($actionName, $actionType, $urlPrefix, $actionsTablesByType); + + self::setCachedActionRow($idaction, $actionType, $actionRow); + } else { + $actionRow = self::getCachedActionRow($row['idaction'], $row['type']); + + // Action processed as "to skip" for some reasons + if ($actionRow === false) { + continue; + } + } + + + if (is_null($actionRow)) { + continue; + } + + // Here we do ensure that, the Metadata URL set for a given row, is the one from the Pageview with the most hits. + // This is to ensure that when, different URLs are loaded with the same page name. + // For example http://piwik.org and http://id.piwik.org are reported in Piwik > Actions > Pages with /index + // But, we must make sure http://piwik.org is used to link & for transitions + // Note: this code is partly duplicated from Piwik_DataTable_Row->sumRowMetadata() + if (!is_null($url) + && !$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 + ) { + $actionRow->setMetadata('url', $url); + $actionRow->maxVisitsSummed = $row[Piwik_Archive::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; + } + } + + if ($row['type'] != Piwik_Tracker_Action::TYPE_ACTION_URL + && $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_Archive::INDEX_PAGE_NB_HITS_WITH_TIME_GENERATION, $row)) { + unset($row[Piwik_Archive::INDEX_PAGE_NB_HITS_WITH_TIME_GENERATION]); + } + } + + unset($row['name']); + unset($row['type']); + unset($row['idaction']); + unset($row['url_prefix']); + + foreach ($row as $name => $value) { + // in some edge cases, we have twice the same action name with 2 different idaction + // - this happens when 2 visitors visit the same new page at the same time, and 2 actions get recorded for the same name + // - this could also happen when 2 URLs end up having the same label (eg. 2 subdomains get aggregated to the "/index" page name) + if (($alreadyValue = $actionRow->getColumn($name)) !== false) { + $actionRow->setColumn($name, $alreadyValue + $value); + } else { + $actionRow->addColumn($name, $value); + } + } + + // 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) { + // 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) { + $actionRow->addColumn($name, $value); + } + } + $rowsProcessed++; + } + + // just to make sure php copies the last $actionRow in the $parentTable array + $actionRow =& $actionsTablesByType; + return $rowsProcessed; + } + + static public $maximumRowsInDataTableLevelZero; + static public $maximumRowsInSubDataTable; + static public $columnToSortByBeforeTruncation; + + static protected $actionUrlCategoryDelimiter = null; + static protected $actionTitleCategoryDelimiter = null; + static protected $defaultActionName = null; + static protected $defaultActionNameWhenNotDefined = null; + static protected $defaultActionUrlWhenNotDefined = null; + + static public function reloadConfig() + { + // for BC, we read the old style delimiter first (see #1067)Row + $actionDelimiter = @Piwik_Config::getInstance()->General['action_category_delimiter']; + if (empty($actionDelimiter)) { + self::$actionUrlCategoryDelimiter = Piwik_Config::getInstance()->General['action_url_category_delimiter']; + self::$actionTitleCategoryDelimiter = Piwik_Config::getInstance()->General['action_title_category_delimiter']; + } else { + self::$actionUrlCategoryDelimiter = self::$actionTitleCategoryDelimiter = $actionDelimiter; + } + + self::$defaultActionName = Piwik_Config::getInstance()->General['action_default_name']; + self::$columnToSortByBeforeTruncation = Piwik_Archive::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']; + + Piwik_DataTable::setMaximumDepthLevelAllowedAtLeast(self::getSubCategoryLevelLimit() + 1); + } + + + /** + * The default row is used when archiving, if data is inconsistent in the DB, + * there could be pages that have exit/entry hits, but don't yet + * have a record in the table (or the record was truncated). + * + * @return Piwik_DataTable_Row + */ + static private function getDefaultRow() + { + static $row = false; + if ($row === false) { + // This row is used in the case where an action is know as an exit_action + // but this action was not properly recorded when it was hit in the first place + // 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, + ))); + } + return $row; + } + + /** + * Given a page name and type, builds a recursive datatable where + * each level of the tree is a category, based on the page name split by a delimiter (slash / by default) + * + * @param string $actionName + * @param int $actionType + * @param int $urlPrefix + * @param array $actionsTablesByType + * @return Piwik_DataTable + */ + protected static function getActionRow($actionName, $actionType, $urlPrefix = null, &$actionsTablesByType) + { + // we work on the root table of the given TYPE (either ACTION_URL or DOWNLOAD or OUTLINK etc.) + /* @var Piwik_DataTable $currentTable */ + $currentTable =& $actionsTablesByType[$actionType]; + + // check for ranking query cut-off + if ($actionName == Piwik_DataTable::LABEL_SUMMARY_ROW) { + $summaryRow = $currentTable->getRowFromId(Piwik_DataTable::ID_SUMMARY_ROW); + if ($summaryRow === false) { + $summaryRow = $currentTable->addSummaryRow(self::createSummaryRow()); + } + return $summaryRow; + } + + // go to the level of the subcategory + $actionExplodedNames = self::getActionExplodedNames($actionName, $actionType, $urlPrefix); + list($row, $level) = $currentTable->walkPath( + $actionExplodedNames, self::getDefaultRowColumns(), self::$maximumRowsInSubDataTable); + + return $row; + } + + /** + * Explodes action name into an array of elements. + * + * NOTE: before calling this function make sure Piwik_Actions_ArchivingHelper::reloadConfig(); is called + * + * for downloads: + * we explode link http://piwik.org/some/path/piwik.zip into an array( 'piwik.org', '/some/path/piwik.zip' ); + * + * for outlinks: + * we explode link http://dev.piwik.org/some/path into an array( 'dev.piwik.org', '/some/path' ); + * + * for action urls: + * we explode link http://piwik.org/some/path into an array( 'some', 'path' ); + * + * for action names: + * we explode name 'Piwik / Category 1 / Category 2' into an array('Piwik', 'Category 1', 'Category 2'); + * + * @param string action name + * @param int action type + * @param int url prefix (only used for TYPE_ACTION_URL) + * @return array of exploded elements from $name + */ + static public function getActionExplodedNames($name, $type, $urlPrefix = null) + { + // Site Search does not split Search keywords + if ($type == Piwik_Tracker_Action::TYPE_SITE_SEARCH) { + return array($name); + } + + $matches = array(); + $isUrl = false; + $name = str_replace("\n", "", $name); + + $urlRegexAfterDomain = '([^/]+)[/]?([^#]*)[#]?(.*)'; + if ($urlPrefix === null) { + // match url with protocol (used for outlinks / downloads) + $urlRegex = '@^http[s]?://' . $urlRegexAfterDomain . '$@i'; + } else { + // the name is a url that does not contain protocol and www anymore + // we know that normalization has been done on db level because $urlPrefix is set + $urlRegex = '@^' . $urlRegexAfterDomain . '$@i'; + } + + preg_match($urlRegex, $name, $matches); + if (count($matches)) { + $isUrl = true; + $urlHost = $matches[1]; + $urlPath = $matches[2]; + $urlFragment = $matches[3]; + } + + if ($type == Piwik_Tracker_Action::TYPE_DOWNLOAD + || $type == Piwik_Tracker_Action::TYPE_OUTLINK + ) { + if ($isUrl) { + return array(trim($urlHost), '/' . trim($urlPath)); + } + } + + if ($isUrl) { + $name = $urlPath; + + if ($name === '' || substr($name, -1) == '/') { + $name .= self::$defaultActionName; + } + } + + if ($type == Piwik_Tracker_Action::TYPE_ACTION_NAME) { + $categoryDelimiter = self::$actionTitleCategoryDelimiter; + } else { + $categoryDelimiter = self::$actionUrlCategoryDelimiter; + } + + + if ($isUrl) { + $urlFragment = Piwik_Tracker_Action::processUrlFragment($urlFragment); + if (!empty($urlFragment)) { + $name .= '#' . $urlFragment; + } + } + + if (empty($categoryDelimiter)) { + return array(trim($name)); + } + + $split = explode($categoryDelimiter, $name, self::getSubCategoryLevelLimit()); + + // trim every category and remove empty categories + $split = array_map('trim', $split); + $split = array_filter($split, 'strlen'); + + // forces array key to start at 0 + $split = array_values($split); + + if (empty($split)) { + $defaultName = self::getUnknownActionName($type); + return array(trim($defaultName)); + } + + $lastPageName = end($split); + // we are careful to prefix the page URL / name with some value + // so that if a page has the same name as a category + // we don't merge both entries + if ($type != Piwik_Tracker_Action::TYPE_ACTION_NAME) { + $lastPageName = '/' . $lastPageName; + } else { + $lastPageName = ' ' . $lastPageName; + } + $split[count($split) - 1] = $lastPageName; + return array_values($split); + } + + /** + * Gets the key for the cache of action rows from an action ID and type. + * + * @param int $idAction + * @param int $actionType + * @return string|int + */ + private static function getCachedActionRowKey($idAction, $actionType) + { + return $idAction == Piwik_DataTable::LABEL_SUMMARY_ROW + ? $actionType . '_others' + : $idAction; + } + + /** + * Returns the configured sub-category level limit. + * + * @return int + */ + public static function getSubCategoryLevelLimit() + { + return Piwik_Config::getInstance()->General['action_category_level_limit']; + } + + /** + * Returns default label for the action type + * + * @param $type + * @return string + */ + static public function getUnknownActionName($type) + { + if (empty(self::$defaultActionNameWhenNotDefined)) { + self::$defaultActionNameWhenNotDefined = Piwik_Translate('General_NotDefined', Piwik_Translate('Actions_ColumnPageName')); + self::$defaultActionUrlWhenNotDefined = Piwik_Translate('General_NotDefined', Piwik_Translate('Actions_ColumnPageURL')); + } + if ($type == Piwik_Tracker_Action::TYPE_ACTION_NAME) { + return self::$defaultActionNameWhenNotDefined; + } + return self::$defaultActionUrlWhenNotDefined; + } + + /** + * Static cache to store Rows during processing + */ + static protected $cacheParsedAction = array(); + + public static function clearActionsCache() + { + self::$cacheParsedAction = array(); + } + + /** + * Get cached action row by id & type. If $idAction is set to -1, the 'Others' row + * for the specific action type will be returned. + * + * @param int $idAction + * @param int $actionType + * @return Piwik_DataTable_Row|false + */ + private static function getCachedActionRow($idAction, $actionType) + { + $cacheLabel = self::getCachedActionRowKey($idAction, $actionType); + + if (!isset(self::$cacheParsedAction[$cacheLabel])) { + // This can happen when + // - We select an entry page ID that was only seen yesterday, so wasn't selected in the first query + // - We count time spent on a page, when this page was only seen yesterday + return false; + } + + return self::$cacheParsedAction[$cacheLabel]; + } + + /** + * Set cached action row for an id & type. + * + * @param int $idAction + * @param int $actionType + * @param Piwik_DataTable_Row + */ + private static function setCachedActionRow($idAction, $actionType, $actionRow) + { + $cacheLabel = self::getCachedActionRowKey($idAction, $actionType); + self::$cacheParsedAction[$cacheLabel] = $actionRow; + } + + /** + * Returns the default columns for a row in an Actions DataTable. + * + * @return array + */ + 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); + } + + /** + * Creates a summary row for an Actions DataTable. + * + * @return Piwik_DataTable_Row + */ + private static function createSummaryRow() + { + return new Piwik_DataTable_Row(array( + Piwik_DataTable_Row::COLUMNS => + array('label' => Piwik_DataTable::LABEL_SUMMARY_ROW) + self::getDefaultRowColumns() + )); + } } diff --git a/plugins/Actions/Controller.php b/plugins/Actions/Controller.php index 00eb49a804..aed8efd683 100644 --- a/plugins/Actions/Controller.php +++ b/plugins/Actions/Controller.php @@ -16,513 +16,502 @@ */ class Piwik_Actions_Controller extends Piwik_Controller { - const ACTIONS_REPORT_ROWS_DISPLAY = 100; - - protected function getPageUrlsView($currentAction, $controllerActionSubtable, $apiAction) - { - $view = Piwik_ViewDataTable::factory(); - $view->init($this->pluginName, $currentAction, $apiAction, $controllerActionSubtable); - $view->setColumnTranslation('label', Piwik_Translate('Actions_ColumnPageURL')); - return $view; - } - - /** - * PAGES - * @param bool $fetch - * @return string - */ - - public function indexPageUrls($fetch = false) - { - return Piwik_View::singleReport( - Piwik_Translate('Actions_SubmenuPages'), - $this->getPageUrls(true), $fetch); - } - - public function getPageUrls($fetch = false) - { - $view = $this->getPageUrlsView(__FUNCTION__, 'getPageUrls', 'Actions.getPageUrls'); - $this->configureViewPages($view); - $this->configureViewActions($view); - return $this->renderView($view, $fetch); - } - - protected function configureViewPages($view) - { - $view->setColumnsToDisplay( array('label','nb_hits','nb_visits', 'bounce_rate', 'avg_time_on_page', 'exit_rate', 'avg_time_generation') ); - } - - /** - * ENTRY PAGES - * @param bool $fetch - * @return string|void - */ - public function indexEntryPageUrls($fetch = false) - { - return Piwik_View::singleReport( - Piwik_Translate('Actions_SubmenuPagesEntry'), - $this->getEntryPageUrls(true), $fetch); - } - - public function getEntryPageUrls($fetch = false) - { - $view = $this->getPageUrlsView(__FUNCTION__, 'getEntryPageUrls', 'Actions.getEntryPageUrls'); - $this->configureViewEntryPageUrls($view); - $this->configureViewActions($view); - return $this->renderView($view, $fetch); - } - - protected function configureViewEntryPageUrls($view) - { - $view->setSortedColumn('entry_nb_visits'); - $view->setColumnsToDisplay( array('label','entry_nb_visits', 'entry_bounce_count', 'bounce_rate') ); - $view->setColumnTranslation('label', Piwik_Translate('Actions_ColumnEntryPageURL')); - $view->setColumnTranslation('entry_bounce_count', Piwik_Translate('General_ColumnBounces')); - $view->setColumnTranslation('entry_nb_visits', Piwik_Translate('General_ColumnEntrances')); - $view->addRelatedReports(Piwik_Translate('Actions_SubmenuPagesEntry'), array( - 'Actions.getEntryPageTitles' => Piwik_Translate('Actions_EntryPageTitles') - )); - $view->setReportUrl('Actions', $this->getEntryPageUrlActionForLink()); - } - - /* - * EXIT PAGES - */ - public function indexExitPageUrls($fetch = false) - { - return Piwik_View::singleReport( - Piwik_Translate('Actions_SubmenuPagesExit'), - $this->getExitPageUrls(true), $fetch); - } - - public function getExitPageUrls($fetch = false) - { - $view = $this->getPageUrlsView(__FUNCTION__, 'getExitPageUrls', 'Actions.getExitPageUrls'); - $this->configureViewExitPageUrls($view); - $this->configureViewActions($view); - return $this->renderView($view, $fetch); - } - - protected function configureViewExitPageUrls($view) - { - $view->setSortedColumn('exit_nb_visits'); - $view->setColumnsToDisplay( array('label', 'exit_nb_visits', 'nb_visits', 'exit_rate') ); - $view->setColumnTranslation('label', Piwik_Translate('Actions_ColumnExitPageURL')); - $view->setColumnTranslation('exit_nb_visits', Piwik_Translate('General_ColumnExits')); - $view->addRelatedReports(Piwik_Translate('Actions_SubmenuPagesExit'), array( - 'Actions.getExitPageTitles' => Piwik_Translate('Actions_ExitPageTitles') - )); - $view->setReportUrl('Actions', $this->getExitPageUrlActionForLink()); - } - - /* - * SITE SEARCH - */ - public function indexSiteSearch() - { - $view = Piwik_View::factory('indexSiteSearch'); - - $view->keywords = $this->getSiteSearchKeywords( true ); - $view->noResultKeywords = $this->getSiteSearchNoResultKeywords( true ); - $view->pagesUrlsFollowingSiteSearch = $this->getPageUrlsFollowingSiteSearch( true ); - - $categoryTrackingEnabled = Piwik_PluginsManager::getInstance()->isPluginActivated('CustomVariables'); - if($categoryTrackingEnabled) - { - $view->categories = $this->getSiteSearchCategories( true ); - } - - echo $view->render(); - } - - public function getSiteSearchKeywords($fetch = false) - { - $view = Piwik_ViewDataTable::factory(); - $view->init( $this->pluginName, __FUNCTION__, 'Actions.getSiteSearchKeywords' ); - $this->configureViewSiteSearchKeywords($view); - return $this->renderView($view, $fetch); - } - - public function getSiteSearchNoResultKeywords($fetch = false) - { - $view = Piwik_ViewDataTable::factory(); - $view->init( $this->pluginName, __FUNCTION__, 'Actions.getSiteSearchNoResultKeywords' ); - $this->configureViewSiteSearchKeywords($view); - $view->setColumnsToDisplay(array('label', 'nb_visits', 'exit_rate')); - $view->setColumnTranslation('label', Piwik_Translate('Actions_ColumnNoResultKeyword')); - return $this->renderView($view, $fetch); - } - - public function configureViewSiteSearchKeywords(Piwik_ViewDataTable $view) - { - $view->setColumnTranslation('label', Piwik_Translate('Actions_ColumnSearchKeyword')); - $view->setColumnsToDisplay(array('label', 'nb_visits', 'nb_pages_per_search', 'exit_rate')); - $view->setColumnTranslation('nb_visits', Piwik_Translate('Actions_ColumnSearches')); - $view->setColumnTranslation('exit_rate', str_replace("% ", "% ", Piwik_Translate('Actions_ColumnSearchExits'))); - $view->setColumnTranslation('nb_pages_per_search', Piwik_Translate('Actions_ColumnPagesPerSearch')); - $view->disableShowBarChart(); - $view->disableShowAllColumns(); - } - - public function getSiteSearchCategories($fetch = false) - { - $view = Piwik_ViewDataTable::factory(); - $view->init( $this->pluginName, __FUNCTION__, 'Actions.getSiteSearchCategories' ); - $view->setColumnTranslation('label', Piwik_Translate('Actions_ColumnSearchCategory')); - $view->setColumnTranslation('nb_visits', Piwik_Translate('Actions_ColumnSearches')); - $view->setColumnsToDisplay( array('label','nb_visits', 'nb_pages_per_search') ); - $view->setColumnTranslation('nb_pages_per_search', Piwik_Translate('Actions_ColumnPagesPerSearch')); - $view->disableShowAllColumns(); - $view->disableShowBarChart(); - $view->disableRowEvolution(); - return $this->renderView($view, $fetch); - } - - - public function getPageUrlsFollowingSiteSearch($fetch = false) - { - $view = Piwik_ViewDataTable::factory(); - $view->init( $this->pluginName, __FUNCTION__, 'Actions.getPageUrlsFollowingSiteSearch', 'getPageUrlsFollowingSiteSearch' ); - $view->addRelatedReports(Piwik_Translate('Actions_WidgetPageUrlsFollowingSearch'), array( - 'Actions.getPageTitlesFollowingSiteSearch' => Piwik_Translate('Actions_WidgetPageTitlesFollowingSearch'), - )); - $view = $this->configureViewPagesFollowingSiteSearch($view); - return $this->renderView($view, $fetch); - } - - public function getPageTitlesFollowingSiteSearch($fetch = false) - { - $view = Piwik_ViewDataTable::factory(); - $view->init( $this->pluginName, __FUNCTION__, 'Actions.getPageTitlesFollowingSiteSearch', 'getPageTitlesFollowingSiteSearch' ); - $view->addRelatedReports(Piwik_Translate('Actions_WidgetPageTitlesFollowingSearch'), array( - 'Actions.getPageUrlsFollowingSiteSearch' => Piwik_Translate('Actions_WidgetPageUrlsFollowingSearch'), - )); - $view = $this->configureViewPagesFollowingSiteSearch($view); - return $this->renderView($view, $fetch); - } - - public function configureViewPagesFollowingSiteSearch($view) - { - $view->setColumnsToDisplay(array('label', 'nb_hits_following_search', 'nb_hits')); - $view->setColumnTranslation('nb_hits_following_search', Piwik_Translate('General_ColumnViewedAfterSearch')); - $view->setColumnTranslation('label', Piwik_Translate('General_ColumnDestinationPage')); - $view->setSortedColumn('nb_hits_following_search'); - $view->setColumnTranslation('nb_hits', Piwik_Translate('General_ColumnTotalPageviews')); - $view->disableExcludeLowPopulation(); - $view = $this->configureViewActions($view, $doSetTranslations = false); - return $view; - } - - /* - * PAGE TITLES - */ - public function indexPageTitles($fetch = false) - { - return Piwik_View::singleReport( - Piwik_Translate('Actions_SubmenuPageTitles'), - $this->getPageTitles(true), $fetch); - } - - public function getPageTitles($fetch = false) - { - $view = Piwik_ViewDataTable::factory(); - $view->init( $this->pluginName, - __FUNCTION__, - 'Actions.getPageTitles', - 'getPageTitlesSubDataTable' ); - $view->setColumnTranslation('label', Piwik_Translate('Actions_ColumnPageName')); - $view->addRelatedReports(Piwik_Translate('Actions_SubmenuPageTitles'), array( - 'Actions.getEntryPageTitles' => Piwik_Translate('Actions_EntryPageTitles'), - 'Actions.getExitPageTitles' => Piwik_Translate('Actions_ExitPageTitles'), - )); - $view->setReportUrl('Actions', $this->getPageTitlesActionForLink()); - $this->configureViewPages($view); - $this->configureViewActions($view); - return $this->renderView($view, $fetch); - } - - public function getPageTitlesSubDataTable($fetch = false) - { - $view = Piwik_ViewDataTable::factory(); - $view->init( $this->pluginName, - __FUNCTION__, - 'Actions.getPageTitles', - 'getPageTitlesSubDataTable' ); - $this->configureViewPages($view); - $this->configureViewActions($view); - return $this->renderView($view, $fetch); - } - - /** - * Echos or returns a report displaying analytics data for every unique entry - * page title. - * - * @param bool $fetch True to return the view as a string, false to echo it. - * @return string - */ - public function getEntryPageTitles( $fetch = false ) - { - $view = Piwik_ViewDataTable::factory(); - $view->init($this->pluginName, __FUNCTION__, 'Actions.getEntryPageTitles', __FUNCTION__); - $view->setColumnTranslation('label', Piwik_Translate('Actions_ColumnEntryPageTitle')); - $view->setColumnTranslation('entry_bounce_count', Piwik_Translate('General_ColumnBounces')); - $view->setColumnTranslation('entry_nb_visits', Piwik_Translate('General_ColumnEntrances')); - $view->setColumnsToDisplay( array('label','entry_nb_visits', 'entry_bounce_count', 'bounce_rate') ); - - $entryPageUrlAction = $this->getEntryPageUrlActionForLink(); - $view->addRelatedReports(Piwik_Translate('Actions_EntryPageTitles'), array( - 'Actions.getPageTitles' => Piwik_Translate('Actions_SubmenuPageTitles'), - "Actions.$entryPageUrlAction" => Piwik_Translate('Actions_SubmenuPagesEntry'), - )); - - $this->configureViewActions($view); - - return $this->renderView($view, $fetch); - } - - /** - * Echos or returns a report displaying analytics data for every unique exit - * page title. - * - * @param bool $fetch True to return the view as a string, false to echo it. - * @return string - */ - public function getExitPageTitles( $fetch = false ) - { - $view = Piwik_ViewDataTable::factory(); - $view->init($this->pluginName, __FUNCTION__, 'Actions.getExitPageTitles', __FUNCTION__); - $view->setColumnTranslation('label', Piwik_Translate('Actions_ColumnExitPageTitle')); - $view->setColumnTranslation('exit_nb_visits', Piwik_Translate('General_ColumnExits')); - $view->setColumnsToDisplay( array('label', 'exit_nb_visits', 'nb_visits', 'exit_rate') ); - - $exitPageUrlAction = $this->getExitPageUrlActionForLink(); - $view->addRelatedReports(Piwik_Translate('Actions_ExitPageTitles'), array( - 'Actions.getPageTitles' => Piwik_Translate('Actions_SubmenuPageTitles'), - "Actions.$exitPageUrlAction" => Piwik_Translate('Actions_SubmenuPagesExit'), - )); - - $this->configureViewActions($view); - - return $this->renderView($view, $fetch); - } - - /* - * DOWNLOADS - */ - - public function indexDownloads($fetch = false) - { - return Piwik_View::singleReport( - Piwik_Translate('Actions_SubmenuDownloads'), - $this->getDownloads(true), $fetch); - } - - public function getDownloads($fetch = false) - { - $view = Piwik_ViewDataTable::factory(); - $view->init( $this->pluginName, - __FUNCTION__, - 'Actions.getDownloads', - 'getDownloadsSubDataTable' ); - - $this->configureViewDownloads($view); - return $this->renderView($view, $fetch); - } - - public function getDownloadsSubDataTable($fetch = false) - { - $view = Piwik_ViewDataTable::factory(); - $view->init( $this->pluginName, - __FUNCTION__, - 'Actions.getDownloads', - 'getDownloadsSubDataTable'); - $this->configureViewDownloads($view); - return $this->renderView($view, $fetch); - } - - - /* - * OUTLINKS - */ - - public function indexOutlinks($fetch = false) - { - return Piwik_View::singleReport( - Piwik_Translate('Actions_SubmenuOutlinks'), - $this->getOutlinks(true), $fetch); - } - - public function getOutlinks($fetch = false) - { - $view = Piwik_ViewDataTable::factory(); - $view->init( $this->pluginName, - __FUNCTION__, - 'Actions.getOutlinks', - 'getOutlinksSubDataTable' ); - $this->configureViewOutlinks($view); - return $this->renderView($view, $fetch); - } - - public function getOutlinksSubDataTable($fetch = false) - { - $view = Piwik_ViewDataTable::factory(); - $view->init( $this->pluginName, - __FUNCTION__, - 'Actions.getOutlinks', - 'getOutlinksSubDataTable'); - $this->configureViewOutlinks($view); - return $this->renderView($view, $fetch); - } - - /* - * Page titles & Page URLs reports - */ - protected function configureViewActions($view, $doSetTranslations = true) - { - if($doSetTranslations) - { - $view->setColumnTranslation('nb_hits', Piwik_Translate('General_ColumnPageviews')); - $view->setColumnTranslation('nb_visits', Piwik_Translate('General_ColumnUniquePageviews')); - $view->setColumnTranslation('avg_time_on_page', Piwik_Translate('General_ColumnAverageTimeOnPage')); - $view->setColumnTranslation('bounce_rate', Piwik_Translate('General_ColumnBounceRate')); - $view->setColumnTranslation('exit_rate', Piwik_Translate('General_ColumnExitRate')); - $view->setColumnTranslation('avg_time_generation', Piwik_Translate('General_ColumnAverageGenerationTime')); - $view->queueFilter('ColumnCallbackReplace', array('avg_time_on_page', array('Piwik', 'getPrettyTimeFromSeconds'))); - $view->queueFilter('ColumnCallbackReplace', array('avg_time_generation', - create_function('$averageTimeOnSite', 'return $averageTimeOnSite ? Piwik::getPrettyTimeFromSeconds($averageTimeOnSite, true, true, false) : "-";'))); - } - - if(Piwik_Common::getRequestVar('enable_filter_excludelowpop', '0', 'string' ) != '0') - { - // computing minimum value to exclude - $visitsInfo = Piwik_VisitsSummary_Controller::getVisitsSummary(); - $visitsInfo = $visitsInfo->getFirstRow(); - $nbActions = $visitsInfo->getColumn('nb_actions'); - $nbActionsLowPopulationThreshold = floor(0.02 * $nbActions); // 2 percent of the total number of actions - // we remove 1 to make sure some actions/downloads are displayed in the case we have a very few of them - // and each of them has 1 or 2 hits... - $nbActionsLowPopulationThreshold = min($visitsInfo->getColumn('max_actions')-1, $nbActionsLowPopulationThreshold-1); - - $view->setExcludeLowPopulation( 'nb_hits', $nbActionsLowPopulationThreshold ); - } - - $this->configureGenericViewActions($view); - return $view; - } - - /* - * Downloads report - */ - protected function configureViewDownloads($view) - { - $view->setColumnsToDisplay( array('label','nb_visits','nb_hits') ); - $view->setColumnTranslation('label', Piwik_Translate('Actions_ColumnDownloadURL')); - $view->setColumnTranslation('nb_visits', Piwik_Translate('Actions_ColumnUniqueDownloads')); - $view->setColumnTranslation('nb_hits', Piwik_Translate('Actions_ColumnDownloads')); - $view->disableExcludeLowPopulation(); - $this->configureGenericViewActions($view); - } - - /* - * Outlinks report - */ - protected function configureViewOutlinks($view) - { - $view->setColumnsToDisplay( array('label','nb_visits','nb_hits') ); - $view->setColumnTranslation('label', Piwik_Translate('Actions_ColumnClickedURL')); - $view->setColumnTranslation('nb_visits', Piwik_Translate('Actions_ColumnUniqueClicks')); - $view->setColumnTranslation('nb_hits', Piwik_Translate('Actions_ColumnClicks')); - $view->disableExcludeLowPopulation(); - $this->configureGenericViewActions($view); - } - - /* - * Common to all Actions reports, how to use the custom Actions Datatable html - */ - protected function configureGenericViewActions($view) - { - $view->setTemplate('CoreHome/templates/datatable_actions.tpl'); - if(Piwik_Common::getRequestVar('idSubtable', -1) != -1) - { - $view->setTemplate('CoreHome/templates/datatable_actions_subdatable.tpl'); - } - $currentlySearching = $view->setSearchRecursive(); - if($currentlySearching) - { - $view->setTemplate('CoreHome/templates/datatable_actions_recursive.tpl'); - } - // disable Footer icons - $view->disableShowAllViewsIcons(); - $view->disableShowAllColumns(); - - $view->setLimit( self::ACTIONS_REPORT_ROWS_DISPLAY ); - - // if the flat parameter is not provided, make sure it is set to 0 in the URL, - // so users can see that they can set it to 1 (see #3365) - if (Piwik_Common::getRequestVar('flat', false) === false) - { - $view->setCustomParameter('flat', 0); - } - - $view->main(); - // we need to rewrite the phpArray so it contains all the recursive arrays - if($currentlySearching) - { - $phpArrayRecursive = $this->getArrayFromRecursiveDataTable($view->getDataTable()); - $view->getView()->arrayDataTable = $phpArrayRecursive; - } - } - - protected function getArrayFromRecursiveDataTable( $dataTable, $depth = 0 ) - { - $table = array(); - foreach($dataTable->getRows() as $row) - { - $phpArray = array(); - if(($idSubtable = $row->getIdSubDataTable()) !== null) - { - $subTable = Piwik_DataTable_Manager::getInstance()->getTable( $idSubtable ); - - if($subTable->getRowsCount() > 0) - { - $phpArray = $this->getArrayFromRecursiveDataTable( $subTable, $depth + 1 ); - } - } - - $newRow = array( - 'level' => $depth, - 'columns' => $row->getColumns(), - 'metadata' => $row->getMetadata(), - 'idsubdatatable' => $row->getIdSubDataTable() - ); - $table[] = $newRow; - if(count($phpArray) > 0) - { - $table = array_merge( $table, $phpArray); - } - } - return $table; - } - - /** Returns action to use when linking to the exit page URLs report. */ - private function getExitPageUrlActionForLink() - { - // link to the page not, just the report, but only if not a widget - return Piwik_Common::getRequestVar('widget', 0) == 0 ? 'indexExitPageUrls' : 'getExitPageUrls'; - } - - - /** Returns action to use when linking to the entry page URLs report. */ - private function getEntryPageUrlActionForLink() - { - // link to the page not, just the report, but only if not a widget - return Piwik_Common::getRequestVar('widget', 0) == 0 ? 'indexEntryPageUrls' : 'getEntryPageUrls'; - } - - /** Returns action to use when linking to the page titles report. */ - private function getPageTitlesActionForLink() - { - // link to the page not, just the report, but only if not a widget - return Piwik_Common::getRequestVar('widget', 0) == 0 ? 'indexPageTitles' : 'getPageTitles'; - } + const ACTIONS_REPORT_ROWS_DISPLAY = 100; + + protected function getPageUrlsView($currentAction, $controllerActionSubtable, $apiAction) + { + $view = Piwik_ViewDataTable::factory(); + $view->init($this->pluginName, $currentAction, $apiAction, $controllerActionSubtable); + $view->setColumnTranslation('label', Piwik_Translate('Actions_ColumnPageURL')); + return $view; + } + + /** + * PAGES + * @param bool $fetch + * @return string + */ + + public function indexPageUrls($fetch = false) + { + return Piwik_View::singleReport( + Piwik_Translate('Actions_SubmenuPages'), + $this->getPageUrls(true), $fetch); + } + + public function getPageUrls($fetch = false) + { + $view = $this->getPageUrlsView(__FUNCTION__, 'getPageUrls', 'Actions.getPageUrls'); + $this->configureViewPages($view); + $this->configureViewActions($view); + return $this->renderView($view, $fetch); + } + + protected function configureViewPages($view) + { + $view->setColumnsToDisplay(array('label', 'nb_hits', 'nb_visits', 'bounce_rate', 'avg_time_on_page', 'exit_rate', 'avg_time_generation')); + } + + /** + * ENTRY PAGES + * @param bool $fetch + * @return string|void + */ + public function indexEntryPageUrls($fetch = false) + { + return Piwik_View::singleReport( + Piwik_Translate('Actions_SubmenuPagesEntry'), + $this->getEntryPageUrls(true), $fetch); + } + + public function getEntryPageUrls($fetch = false) + { + $view = $this->getPageUrlsView(__FUNCTION__, 'getEntryPageUrls', 'Actions.getEntryPageUrls'); + $this->configureViewEntryPageUrls($view); + $this->configureViewActions($view); + return $this->renderView($view, $fetch); + } + + protected function configureViewEntryPageUrls($view) + { + $view->setSortedColumn('entry_nb_visits'); + $view->setColumnsToDisplay(array('label', 'entry_nb_visits', 'entry_bounce_count', 'bounce_rate')); + $view->setColumnTranslation('label', Piwik_Translate('Actions_ColumnEntryPageURL')); + $view->setColumnTranslation('entry_bounce_count', Piwik_Translate('General_ColumnBounces')); + $view->setColumnTranslation('entry_nb_visits', Piwik_Translate('General_ColumnEntrances')); + $view->addRelatedReports(Piwik_Translate('Actions_SubmenuPagesEntry'), array( + 'Actions.getEntryPageTitles' => Piwik_Translate('Actions_EntryPageTitles') + )); + $view->setReportUrl('Actions', $this->getEntryPageUrlActionForLink()); + } + + /* + * EXIT PAGES + */ + public function indexExitPageUrls($fetch = false) + { + return Piwik_View::singleReport( + Piwik_Translate('Actions_SubmenuPagesExit'), + $this->getExitPageUrls(true), $fetch); + } + + public function getExitPageUrls($fetch = false) + { + $view = $this->getPageUrlsView(__FUNCTION__, 'getExitPageUrls', 'Actions.getExitPageUrls'); + $this->configureViewExitPageUrls($view); + $this->configureViewActions($view); + return $this->renderView($view, $fetch); + } + + protected function configureViewExitPageUrls($view) + { + $view->setSortedColumn('exit_nb_visits'); + $view->setColumnsToDisplay(array('label', 'exit_nb_visits', 'nb_visits', 'exit_rate')); + $view->setColumnTranslation('label', Piwik_Translate('Actions_ColumnExitPageURL')); + $view->setColumnTranslation('exit_nb_visits', Piwik_Translate('General_ColumnExits')); + $view->addRelatedReports(Piwik_Translate('Actions_SubmenuPagesExit'), array( + 'Actions.getExitPageTitles' => Piwik_Translate('Actions_ExitPageTitles') + )); + $view->setReportUrl('Actions', $this->getExitPageUrlActionForLink()); + } + + /* + * SITE SEARCH + */ + public function indexSiteSearch() + { + $view = Piwik_View::factory('indexSiteSearch'); + + $view->keywords = $this->getSiteSearchKeywords(true); + $view->noResultKeywords = $this->getSiteSearchNoResultKeywords(true); + $view->pagesUrlsFollowingSiteSearch = $this->getPageUrlsFollowingSiteSearch(true); + + $categoryTrackingEnabled = Piwik_PluginsManager::getInstance()->isPluginActivated('CustomVariables'); + if ($categoryTrackingEnabled) { + $view->categories = $this->getSiteSearchCategories(true); + } + + echo $view->render(); + } + + public function getSiteSearchKeywords($fetch = false) + { + $view = Piwik_ViewDataTable::factory(); + $view->init($this->pluginName, __FUNCTION__, 'Actions.getSiteSearchKeywords'); + $this->configureViewSiteSearchKeywords($view); + return $this->renderView($view, $fetch); + } + + public function getSiteSearchNoResultKeywords($fetch = false) + { + $view = Piwik_ViewDataTable::factory(); + $view->init($this->pluginName, __FUNCTION__, 'Actions.getSiteSearchNoResultKeywords'); + $this->configureViewSiteSearchKeywords($view); + $view->setColumnsToDisplay(array('label', 'nb_visits', 'exit_rate')); + $view->setColumnTranslation('label', Piwik_Translate('Actions_ColumnNoResultKeyword')); + return $this->renderView($view, $fetch); + } + + public function configureViewSiteSearchKeywords(Piwik_ViewDataTable $view) + { + $view->setColumnTranslation('label', Piwik_Translate('Actions_ColumnSearchKeyword')); + $view->setColumnsToDisplay(array('label', 'nb_visits', 'nb_pages_per_search', 'exit_rate')); + $view->setColumnTranslation('nb_visits', Piwik_Translate('Actions_ColumnSearches')); + $view->setColumnTranslation('exit_rate', str_replace("% ", "% ", Piwik_Translate('Actions_ColumnSearchExits'))); + $view->setColumnTranslation('nb_pages_per_search', Piwik_Translate('Actions_ColumnPagesPerSearch')); + $view->disableShowBarChart(); + $view->disableShowAllColumns(); + } + + public function getSiteSearchCategories($fetch = false) + { + $view = Piwik_ViewDataTable::factory(); + $view->init($this->pluginName, __FUNCTION__, 'Actions.getSiteSearchCategories'); + $view->setColumnTranslation('label', Piwik_Translate('Actions_ColumnSearchCategory')); + $view->setColumnTranslation('nb_visits', Piwik_Translate('Actions_ColumnSearches')); + $view->setColumnsToDisplay(array('label', 'nb_visits', 'nb_pages_per_search')); + $view->setColumnTranslation('nb_pages_per_search', Piwik_Translate('Actions_ColumnPagesPerSearch')); + $view->disableShowAllColumns(); + $view->disableShowBarChart(); + $view->disableRowEvolution(); + return $this->renderView($view, $fetch); + } + + + public function getPageUrlsFollowingSiteSearch($fetch = false) + { + $view = Piwik_ViewDataTable::factory(); + $view->init($this->pluginName, __FUNCTION__, 'Actions.getPageUrlsFollowingSiteSearch', 'getPageUrlsFollowingSiteSearch'); + $view->addRelatedReports(Piwik_Translate('Actions_WidgetPageUrlsFollowingSearch'), array( + 'Actions.getPageTitlesFollowingSiteSearch' => Piwik_Translate('Actions_WidgetPageTitlesFollowingSearch'), + )); + $view = $this->configureViewPagesFollowingSiteSearch($view); + return $this->renderView($view, $fetch); + } + + public function getPageTitlesFollowingSiteSearch($fetch = false) + { + $view = Piwik_ViewDataTable::factory(); + $view->init($this->pluginName, __FUNCTION__, 'Actions.getPageTitlesFollowingSiteSearch', 'getPageTitlesFollowingSiteSearch'); + $view->addRelatedReports(Piwik_Translate('Actions_WidgetPageTitlesFollowingSearch'), array( + 'Actions.getPageUrlsFollowingSiteSearch' => Piwik_Translate('Actions_WidgetPageUrlsFollowingSearch'), + )); + $view = $this->configureViewPagesFollowingSiteSearch($view); + return $this->renderView($view, $fetch); + } + + public function configureViewPagesFollowingSiteSearch($view) + { + $view->setColumnsToDisplay(array('label', 'nb_hits_following_search', 'nb_hits')); + $view->setColumnTranslation('nb_hits_following_search', Piwik_Translate('General_ColumnViewedAfterSearch')); + $view->setColumnTranslation('label', Piwik_Translate('General_ColumnDestinationPage')); + $view->setSortedColumn('nb_hits_following_search'); + $view->setColumnTranslation('nb_hits', Piwik_Translate('General_ColumnTotalPageviews')); + $view->disableExcludeLowPopulation(); + $view = $this->configureViewActions($view, $doSetTranslations = false); + return $view; + } + + /* + * PAGE TITLES + */ + public function indexPageTitles($fetch = false) + { + return Piwik_View::singleReport( + Piwik_Translate('Actions_SubmenuPageTitles'), + $this->getPageTitles(true), $fetch); + } + + public function getPageTitles($fetch = false) + { + $view = Piwik_ViewDataTable::factory(); + $view->init($this->pluginName, + __FUNCTION__, + 'Actions.getPageTitles', + 'getPageTitlesSubDataTable'); + $view->setColumnTranslation('label', Piwik_Translate('Actions_ColumnPageName')); + $view->addRelatedReports(Piwik_Translate('Actions_SubmenuPageTitles'), array( + 'Actions.getEntryPageTitles' => Piwik_Translate('Actions_EntryPageTitles'), + 'Actions.getExitPageTitles' => Piwik_Translate('Actions_ExitPageTitles'), + )); + $view->setReportUrl('Actions', $this->getPageTitlesActionForLink()); + $this->configureViewPages($view); + $this->configureViewActions($view); + return $this->renderView($view, $fetch); + } + + public function getPageTitlesSubDataTable($fetch = false) + { + $view = Piwik_ViewDataTable::factory(); + $view->init($this->pluginName, + __FUNCTION__, + 'Actions.getPageTitles', + 'getPageTitlesSubDataTable'); + $this->configureViewPages($view); + $this->configureViewActions($view); + return $this->renderView($view, $fetch); + } + + /** + * Echos or returns a report displaying analytics data for every unique entry + * page title. + * + * @param bool $fetch True to return the view as a string, false to echo it. + * @return string + */ + public function getEntryPageTitles($fetch = false) + { + $view = Piwik_ViewDataTable::factory(); + $view->init($this->pluginName, __FUNCTION__, 'Actions.getEntryPageTitles', __FUNCTION__); + $view->setColumnTranslation('label', Piwik_Translate('Actions_ColumnEntryPageTitle')); + $view->setColumnTranslation('entry_bounce_count', Piwik_Translate('General_ColumnBounces')); + $view->setColumnTranslation('entry_nb_visits', Piwik_Translate('General_ColumnEntrances')); + $view->setColumnsToDisplay(array('label', 'entry_nb_visits', 'entry_bounce_count', 'bounce_rate')); + + $entryPageUrlAction = $this->getEntryPageUrlActionForLink(); + $view->addRelatedReports(Piwik_Translate('Actions_EntryPageTitles'), array( + 'Actions.getPageTitles' => Piwik_Translate('Actions_SubmenuPageTitles'), + "Actions.$entryPageUrlAction" => Piwik_Translate('Actions_SubmenuPagesEntry'), + )); + + $this->configureViewActions($view); + + return $this->renderView($view, $fetch); + } + + /** + * Echos or returns a report displaying analytics data for every unique exit + * page title. + * + * @param bool $fetch True to return the view as a string, false to echo it. + * @return string + */ + public function getExitPageTitles($fetch = false) + { + $view = Piwik_ViewDataTable::factory(); + $view->init($this->pluginName, __FUNCTION__, 'Actions.getExitPageTitles', __FUNCTION__); + $view->setColumnTranslation('label', Piwik_Translate('Actions_ColumnExitPageTitle')); + $view->setColumnTranslation('exit_nb_visits', Piwik_Translate('General_ColumnExits')); + $view->setColumnsToDisplay(array('label', 'exit_nb_visits', 'nb_visits', 'exit_rate')); + + $exitPageUrlAction = $this->getExitPageUrlActionForLink(); + $view->addRelatedReports(Piwik_Translate('Actions_ExitPageTitles'), array( + 'Actions.getPageTitles' => Piwik_Translate('Actions_SubmenuPageTitles'), + "Actions.$exitPageUrlAction" => Piwik_Translate('Actions_SubmenuPagesExit'), + )); + + $this->configureViewActions($view); + + return $this->renderView($view, $fetch); + } + + /* + * DOWNLOADS + */ + + public function indexDownloads($fetch = false) + { + return Piwik_View::singleReport( + Piwik_Translate('Actions_SubmenuDownloads'), + $this->getDownloads(true), $fetch); + } + + public function getDownloads($fetch = false) + { + $view = Piwik_ViewDataTable::factory(); + $view->init($this->pluginName, + __FUNCTION__, + 'Actions.getDownloads', + 'getDownloadsSubDataTable'); + + $this->configureViewDownloads($view); + return $this->renderView($view, $fetch); + } + + public function getDownloadsSubDataTable($fetch = false) + { + $view = Piwik_ViewDataTable::factory(); + $view->init($this->pluginName, + __FUNCTION__, + 'Actions.getDownloads', + 'getDownloadsSubDataTable'); + $this->configureViewDownloads($view); + return $this->renderView($view, $fetch); + } + + + /* + * OUTLINKS + */ + + public function indexOutlinks($fetch = false) + { + return Piwik_View::singleReport( + Piwik_Translate('Actions_SubmenuOutlinks'), + $this->getOutlinks(true), $fetch); + } + + public function getOutlinks($fetch = false) + { + $view = Piwik_ViewDataTable::factory(); + $view->init($this->pluginName, + __FUNCTION__, + 'Actions.getOutlinks', + 'getOutlinksSubDataTable'); + $this->configureViewOutlinks($view); + return $this->renderView($view, $fetch); + } + + public function getOutlinksSubDataTable($fetch = false) + { + $view = Piwik_ViewDataTable::factory(); + $view->init($this->pluginName, + __FUNCTION__, + 'Actions.getOutlinks', + 'getOutlinksSubDataTable'); + $this->configureViewOutlinks($view); + return $this->renderView($view, $fetch); + } + + /* + * Page titles & Page URLs reports + */ + protected function configureViewActions($view, $doSetTranslations = true) + { + if ($doSetTranslations) { + $view->setColumnTranslation('nb_hits', Piwik_Translate('General_ColumnPageviews')); + $view->setColumnTranslation('nb_visits', Piwik_Translate('General_ColumnUniquePageviews')); + $view->setColumnTranslation('avg_time_on_page', Piwik_Translate('General_ColumnAverageTimeOnPage')); + $view->setColumnTranslation('bounce_rate', Piwik_Translate('General_ColumnBounceRate')); + $view->setColumnTranslation('exit_rate', Piwik_Translate('General_ColumnExitRate')); + $view->setColumnTranslation('avg_time_generation', Piwik_Translate('General_ColumnAverageGenerationTime')); + $view->queueFilter('ColumnCallbackReplace', array('avg_time_on_page', array('Piwik', 'getPrettyTimeFromSeconds'))); + $view->queueFilter('ColumnCallbackReplace', array('avg_time_generation', + create_function('$averageTimeOnSite', 'return $averageTimeOnSite ? Piwik::getPrettyTimeFromSeconds($averageTimeOnSite, true, true, false) : "-";'))); + } + + if (Piwik_Common::getRequestVar('enable_filter_excludelowpop', '0', 'string') != '0') { + // computing minimum value to exclude + $visitsInfo = Piwik_VisitsSummary_Controller::getVisitsSummary(); + $visitsInfo = $visitsInfo->getFirstRow(); + $nbActions = $visitsInfo->getColumn('nb_actions'); + $nbActionsLowPopulationThreshold = floor(0.02 * $nbActions); // 2 percent of the total number of actions + // we remove 1 to make sure some actions/downloads are displayed in the case we have a very few of them + // and each of them has 1 or 2 hits... + $nbActionsLowPopulationThreshold = min($visitsInfo->getColumn('max_actions') - 1, $nbActionsLowPopulationThreshold - 1); + + $view->setExcludeLowPopulation('nb_hits', $nbActionsLowPopulationThreshold); + } + + $this->configureGenericViewActions($view); + return $view; + } + + /* + * Downloads report + */ + protected function configureViewDownloads($view) + { + $view->setColumnsToDisplay(array('label', 'nb_visits', 'nb_hits')); + $view->setColumnTranslation('label', Piwik_Translate('Actions_ColumnDownloadURL')); + $view->setColumnTranslation('nb_visits', Piwik_Translate('Actions_ColumnUniqueDownloads')); + $view->setColumnTranslation('nb_hits', Piwik_Translate('Actions_ColumnDownloads')); + $view->disableExcludeLowPopulation(); + $this->configureGenericViewActions($view); + } + + /* + * Outlinks report + */ + protected function configureViewOutlinks($view) + { + $view->setColumnsToDisplay(array('label', 'nb_visits', 'nb_hits')); + $view->setColumnTranslation('label', Piwik_Translate('Actions_ColumnClickedURL')); + $view->setColumnTranslation('nb_visits', Piwik_Translate('Actions_ColumnUniqueClicks')); + $view->setColumnTranslation('nb_hits', Piwik_Translate('Actions_ColumnClicks')); + $view->disableExcludeLowPopulation(); + $this->configureGenericViewActions($view); + } + + /* + * Common to all Actions reports, how to use the custom Actions Datatable html + */ + protected function configureGenericViewActions($view) + { + $view->setTemplate('CoreHome/templates/datatable_actions.tpl'); + if (Piwik_Common::getRequestVar('idSubtable', -1) != -1) { + $view->setTemplate('CoreHome/templates/datatable_actions_subdatable.tpl'); + } + $currentlySearching = $view->setSearchRecursive(); + if ($currentlySearching) { + $view->setTemplate('CoreHome/templates/datatable_actions_recursive.tpl'); + } + // disable Footer icons + $view->disableShowAllViewsIcons(); + $view->disableShowAllColumns(); + + $view->setLimit(self::ACTIONS_REPORT_ROWS_DISPLAY); + + // if the flat parameter is not provided, make sure it is set to 0 in the URL, + // so users can see that they can set it to 1 (see #3365) + if (Piwik_Common::getRequestVar('flat', false) === false) { + $view->setCustomParameter('flat', 0); + } + + $view->main(); + // we need to rewrite the phpArray so it contains all the recursive arrays + if ($currentlySearching) { + $phpArrayRecursive = $this->getArrayFromRecursiveDataTable($view->getDataTable()); + $view->getView()->arrayDataTable = $phpArrayRecursive; + } + } + + protected function getArrayFromRecursiveDataTable($dataTable, $depth = 0) + { + $table = array(); + foreach ($dataTable->getRows() as $row) { + $phpArray = array(); + if (($idSubtable = $row->getIdSubDataTable()) !== null) { + $subTable = Piwik_DataTable_Manager::getInstance()->getTable($idSubtable); + + if ($subTable->getRowsCount() > 0) { + $phpArray = $this->getArrayFromRecursiveDataTable($subTable, $depth + 1); + } + } + + $newRow = array( + 'level' => $depth, + 'columns' => $row->getColumns(), + 'metadata' => $row->getMetadata(), + 'idsubdatatable' => $row->getIdSubDataTable() + ); + $table[] = $newRow; + if (count($phpArray) > 0) { + $table = array_merge($table, $phpArray); + } + } + return $table; + } + + /** Returns action to use when linking to the exit page URLs report. */ + private function getExitPageUrlActionForLink() + { + // link to the page not, just the report, but only if not a widget + return Piwik_Common::getRequestVar('widget', 0) == 0 ? 'indexExitPageUrls' : 'getExitPageUrls'; + } + + + /** Returns action to use when linking to the entry page URLs report. */ + private function getEntryPageUrlActionForLink() + { + // link to the page not, just the report, but only if not a widget + return Piwik_Common::getRequestVar('widget', 0) == 0 ? 'indexEntryPageUrls' : 'getEntryPageUrls'; + } + + /** Returns action to use when linking to the page titles report. */ + private function getPageTitlesActionForLink() + { + // link to the page not, just the report, but only if not a widget + return Piwik_Common::getRequestVar('widget', 0) == 0 ? 'indexPageTitles' : 'getPageTitles'; + } } diff --git a/plugins/Actions/templates/indexSiteSearch.tpl b/plugins/Actions/templates/indexSiteSearch.tpl index 3af0d5c0a4..a692fdd697 100644 --- a/plugins/Actions/templates/indexSiteSearch.tpl +++ b/plugins/Actions/templates/indexSiteSearch.tpl @@ -1,19 +1,19 @@ <div id='leftcolumn'> - <h2>{'Actions_WidgetSearchKeywords'|translate}</h2> -{$keywords} + <h2>{'Actions_WidgetSearchKeywords'|translate}</h2> + {$keywords} - <h2>{'Actions_WidgetSearchNoResultKeywords'|translate}</h2> -{$noResultKeywords} + <h2>{'Actions_WidgetSearchNoResultKeywords'|translate}</h2> + {$noResultKeywords} -{if isset($categories)} - <h2>{'Actions_WidgetSearchCategories'|translate}</h2> -{$categories} -{/if} + {if isset($categories)} + <h2>{'Actions_WidgetSearchCategories'|translate}</h2> + {$categories} + {/if} </div> <div id='rightcolumn'> - <h2>{'Actions_WidgetPageUrlsFollowingSearch'|translate}</h2> -{$pagesUrlsFollowingSiteSearch} + <h2>{'Actions_WidgetPageUrlsFollowingSearch'|translate}</h2> + {$pagesUrlsFollowingSiteSearch} </div> diff --git a/plugins/Annotations/API.php b/plugins/Annotations/API.php index e03f5f5dba..7750c816e3 100755 --- a/plugins/Annotations/API.php +++ b/plugins/Annotations/API.php @@ -12,365 +12,350 @@ /** * @see plugins/Annotations/AnnotationList.php */ -require_once PIWIK_INCLUDE_PATH.'/plugins/Annotations/AnnotationList.php'; +require_once PIWIK_INCLUDE_PATH . '/plugins/Annotations/AnnotationList.php'; /** * API for annotations plugin. Provides methods to create, modify, delete & query * annotations. - * + * * @package Piwik_Annotations */ class Piwik_Annotations_API { - static private $instance = null; - - /** - * Returns this API's singleton instance. - * - * @return Piwik_Annotations_API - */ - static public function getInstance() - { - if (self::$instance == null) - { - self::$instance = new self; - } - return self::$instance; - } - - /** - * Create a new annotation for a site. - * - * @param string $idSite The site ID to add the annotation to. - * @param string $date The date the annotation is attached to. - * @param string $note The text of the annotation. - * @param string $starred Either 0 or 1. Whether the annotation should be starred. - * @return array Returns an array of two elements. The first element (indexed by - * 'annotation') is the new annotation. The second element (indexed - * by 'idNote' is the new note's ID). - */ - public function add( $idSite, $date, $note, $starred = 0 ) - { - $this->checkSingleIdSite($idSite, $extraMessage = "Note: Cannot add one note to multiple sites."); - $this->checkDateIsValid($date); - $this->checkUserCanAddNotesFor($idSite); - - // add, save & return a new annotation - $annotations = new Piwik_Annotations_AnnotationList($idSite); - - $newAnnotation = $annotations->add($idSite, $date, $note, $starred); - $annotations->save($idSite); - - return $newAnnotation; - } - - /** - * Modifies an annotation for a site and returns the modified annotation - * and its ID. - * - * If the current user is not allowed to modify an annotation, an exception - * will be thrown. A user can modify a note if: - * - the user has admin access for the site, OR - * - the user has view access, is not the anonymous user and is the user that - * created the note - * - * @param string $idSite The site ID to add the annotation to. - * @param string $idNote The ID of the note. - * @param string|null $date The date the annotation is attached to. If null, the annotation's - * date is not modified. - * @param string|null $note The text of the annotation. If null, the annotation's text - * is not modified. - * @param string|null $starred Either 0 or 1. Whether the annotation should be starred. - * If null, the annotation is not starred/un-starred. - * @return array Returns an array of two elements. The first element (indexed by - * 'annotation') is the new annotation. The second element (indexed - * by 'idNote' is the new note's ID). - */ - public function save( $idSite, $idNote, $date = null, $note = null, $starred = null ) - { - $this->checkSingleIdSite($idSite, $extraMessage = "Note: Cannot modify more than one note at a time."); - $this->checkDateIsValid($date, $canBeNull = true); - - // get the annotations for the site - $annotations = new Piwik_Annotations_AnnotationList($idSite); - - // check permissions - $this->checkUserCanModifyOrDelete($idSite, $annotations->get($idSite, $idNote)); - - // modify the annotation, and save the whole list - $annotations->update($idSite, $idNote, $date, $note, $starred); - $annotations->save($idSite); - - return $annotations->get($idSite, $idNote); - } - - /** - * Removes an annotation from a site's list of annotations. - * - * If the current user is not allowed to delete the annotation, an exception - * will be thrown. A user can delete a note if: - * - the user has admin access for the site, OR - * - the user has view access, is not the anonymous user and is the user that - * created the note - * - * @param string $idSite The site ID to add the annotation to. - * @param string $idNote The ID of the note to delete. - */ - public function delete( $idSite, $idNote ) - { - $this->checkSingleIdSite($idSite, $extraMessage = "Note: Cannot delete multiple notes."); - - $annotations = new Piwik_Annotations_AnnotationList($idSite); - - // check permissions - $this->checkUserCanModifyOrDelete($idSite, $annotations->get($idSite, $idNote)); - - // remove the note & save the list - $annotations->remove($idSite, $idNote); - $annotations->save($idSite); - } - - /** - * Returns a single note for one site. - * - * @param string $idSite The site ID to add the annotation to. - * @param string $idNote The ID of the note to get. - * @return array The annotation. It will contain the following properties: - * - date: The date the annotation was recorded for. - * - note: The note text. - * - starred: Whether the note is starred or not. - * - user: The user that created the note. - * - canEditOrDelete: Whether the user that called this method can edit or - * delete the annotation returned. - */ - public function get( $idSite, $idNote ) - { - $this->checkSingleIdSite($idSite, $extraMessage = "Note: Specify only one site ID when getting ONE note."); - Piwik::checkUserHasViewAccess($idSite); - - // get single annotation - $annotations = new Piwik_Annotations_AnnotationList($idSite); - return $annotations->get($idSite, $idNote); - } - - /** - * Returns every annotation for a specific site within a specific date range. - * The date range is specified by a date, the period type (day/week/month/year) - * and an optional number of N periods in the past to include. - * - * @param string $idSite The site ID to add the annotation to. Can be one ID or - * a list of site IDs. - * @param string|false $date The date of the period. - * @param string $period The period type. - * @param int|false $lastN Whether to include the last N number of periods in the - * date range or not. - * @return array An array that indexes arrays of annotations by site ID. ie, - * array( - * 5 => array( - * array(...), // annotation #1 - * array(...), // annotation #2 - * ), - * 8 => array(...) - * ) - */ - public function getAll( $idSite, $date = false, $period = 'day', $lastN = false ) - { - Piwik::checkUserHasViewAccess($idSite); - - $annotations = new Piwik_Annotations_AnnotationList($idSite); - - // if date/period are supplied, determine start/end date for search - list($startDate, $endDate) = self::getDateRangeForPeriod($date, $period, $lastN); - - return $annotations->search($startDate, $endDate); - } - - /** - * Returns the count of annotations for a list of periods, including the count of - * starred annotations. - * - * @param string $idSite The site ID to add the annotation to. - * @param string|false $date The date of the period. - * @param string $period The period type. - * @param int|false $lastN Whether to get counts for the last N number of periods or not. - * @return array An array mapping site IDs to arrays holding dates & the count of - * annotations made for those dates. eg, - * array( - * 5 => array( - * array('2012-01-02', array('count' => 4, 'starred' => 2)), - * array('2012-01-03', array('count' => 0, 'starred' => 0)), - * array('2012-01-04', array('count' => 2, 'starred' => 0)), - * ), - * 6 => array( - * array('2012-01-02', array('count' => 1, 'starred' => 0)), - * array('2012-01-03', array('count' => 4, 'starred' => 3)), - * array('2012-01-04', array('count' => 2, 'starred' => 0)), - * ), - * ... - * ) - */ - public function getAnnotationCountForDates( $idSite, $date, $period, $lastN = false, $getAnnotationText = false ) - { - Piwik::checkUserHasViewAccess($idSite); - - // get start & end date for request. lastN is ignored if $period == 'range' - list($startDate, $endDate) = self::getDateRangeForPeriod($date, $period, $lastN); - if ($period == 'range') - { - $period = 'day'; - } - - // create list of dates - $dates = array(); - for (; $startDate->getTimestamp() <= $endDate->getTimestamp(); $startDate = $startDate->addPeriod(1, $period)) - { - $dates[] = $startDate; - } - // we add one for the end of the last period (used in for loop below to bound annotation dates) - $dates[] = $startDate; - - // get annotations for the site - $annotations = new Piwik_Annotations_AnnotationList($idSite); - - // create result w/ 0-counts - $result = array(); - for ($i = 0; $i != count($dates) - 1; ++$i) - { - $date = $dates[$i]; - $nextDate = $dates[$i + 1]; - $strDate = $date->toString(); - - foreach ($annotations->getIdSites() as $idSite) - { - $result[$idSite][$strDate] = $annotations->count($idSite, $date, $nextDate); - - // if only one annotation, return the one annotation's text w/ the counts - if ($getAnnotationText - && $result[$idSite][$strDate]['count'] == 1) - { - $annotationsForSite = $annotations->search( - $date, Piwik_Date::factory($nextDate->getTimestamp() - 1), $idSite); - $annotation = reset($annotationsForSite[$idSite]); - - $result[$idSite][$strDate]['note'] = $annotation['note']; - } - } - } - - // convert associative array into array of pairs (so it can be traversed by index) - $pairResult = array(); - foreach ($result as $idSite => $counts) - { - foreach ($counts as $date => $count) - { - $pairResult[$idSite][] = array($date, $count); - } - } - return $pairResult; - } - - /** - * Throws if the current user is not allowed to modify or delete an annotation. - * - * @param int $idSite The site ID the annotation belongs to. - * @param array $annotation The annotation. - * @throws Exception if the current user is not allowed to modify/delete $annotation. - */ - private function checkUserCanModifyOrDelete( $idSite, $annotation ) - { - if (!$annotation['canEditOrDelete']) - { - throw new Exception(Piwik_Translate('Annotations_YouCannotModifyThisNote')); - } - } - - /** - * Throws if the current user is not allowed to create annotations for a site. - * - * @param int $idSite The site ID. - * @throws Exception if the current user is anonymous or does not have view access - * for site w/ id=$idSite. - */ - private static function checkUserCanAddNotesFor( $idSite ) - { - if (!Piwik_Annotations_AnnotationList::canUserAddNotesFor($idSite)) - { - throw new Exception("The current user is not allowed to add notes for site #$idSite."); - } - } - - /** - * Returns start & end dates for the range described by a period and optional lastN - * argument. - * - * @param string $date|false The start date of the period (or the date range of a range - * period). - * @param string $period The period type ('day', 'week', 'month', 'year' or 'range'). - * @param int|false $lastN Whether to include the last N periods in the range or not. - * Ignored if period == range. - * - * @ignore - */ - public static function getDateRangeForPeriod( $date, $period, $lastN = false ) - { - if ($date === false) - { - return array(false, false); - } - - // if the range is just a normal period (or the period is a range in which case lastN is ignored) - if ($lastN === false - || $period == 'range') - { - if ($period == 'range') - { - $oPeriod = new Piwik_Period_Range('day', $date); - } - else - { - $oPeriod = Piwik_Period::factory($period, Piwik_Date::factory($date)); - } - - $startDate = $oPeriod->getDateStart(); - $endDate = $oPeriod->getDateEnd(); - } - else // if the range includes the last N periods - { - list($date, $lastN) = - Piwik_ViewDataTable_GenerateGraphHTML_ChartEvolution::getDateRangeAndLastN($period, $date, $lastN); - list($startDate, $endDate) = explode(',', $date); - - $startDate = Piwik_Date::factory($startDate); - $endDate = Piwik_Date::factory($endDate); - } - return array($startDate, $endDate); - } - - /** - * Utility function, makes sure idSite string has only one site ID and throws if - * otherwise. - */ - private function checkSingleIdSite( $idSite, $extraMessage ) - { - // can only add a note to one site - if (!is_numeric($idSite)) - { - throw new Exception("Invalid idSite: '$idSite'. $extraMessage"); - } - } - - /** - * Utility function, makes sure date string is valid date, and throws if - * otherwise. - */ - private function checkDateIsValid( $date, $canBeNull = false ) - { - if ($date === null - && $canBeNull) - { - return; - } - - Piwik_Date::factory($date); - } + static private $instance = null; + + /** + * Returns this API's singleton instance. + * + * @return Piwik_Annotations_API + */ + static public function getInstance() + { + if (self::$instance == null) { + self::$instance = new self; + } + return self::$instance; + } + + /** + * Create a new annotation for a site. + * + * @param string $idSite The site ID to add the annotation to. + * @param string $date The date the annotation is attached to. + * @param string $note The text of the annotation. + * @param string $starred Either 0 or 1. Whether the annotation should be starred. + * @return array Returns an array of two elements. The first element (indexed by + * 'annotation') is the new annotation. The second element (indexed + * by 'idNote' is the new note's ID). + */ + public function add($idSite, $date, $note, $starred = 0) + { + $this->checkSingleIdSite($idSite, $extraMessage = "Note: Cannot add one note to multiple sites."); + $this->checkDateIsValid($date); + $this->checkUserCanAddNotesFor($idSite); + + // add, save & return a new annotation + $annotations = new Piwik_Annotations_AnnotationList($idSite); + + $newAnnotation = $annotations->add($idSite, $date, $note, $starred); + $annotations->save($idSite); + + return $newAnnotation; + } + + /** + * Modifies an annotation for a site and returns the modified annotation + * and its ID. + * + * If the current user is not allowed to modify an annotation, an exception + * will be thrown. A user can modify a note if: + * - the user has admin access for the site, OR + * - the user has view access, is not the anonymous user and is the user that + * created the note + * + * @param string $idSite The site ID to add the annotation to. + * @param string $idNote The ID of the note. + * @param string|null $date The date the annotation is attached to. If null, the annotation's + * date is not modified. + * @param string|null $note The text of the annotation. If null, the annotation's text + * is not modified. + * @param string|null $starred Either 0 or 1. Whether the annotation should be starred. + * If null, the annotation is not starred/un-starred. + * @return array Returns an array of two elements. The first element (indexed by + * 'annotation') is the new annotation. The second element (indexed + * by 'idNote' is the new note's ID). + */ + public function save($idSite, $idNote, $date = null, $note = null, $starred = null) + { + $this->checkSingleIdSite($idSite, $extraMessage = "Note: Cannot modify more than one note at a time."); + $this->checkDateIsValid($date, $canBeNull = true); + + // get the annotations for the site + $annotations = new Piwik_Annotations_AnnotationList($idSite); + + // check permissions + $this->checkUserCanModifyOrDelete($idSite, $annotations->get($idSite, $idNote)); + + // modify the annotation, and save the whole list + $annotations->update($idSite, $idNote, $date, $note, $starred); + $annotations->save($idSite); + + return $annotations->get($idSite, $idNote); + } + + /** + * Removes an annotation from a site's list of annotations. + * + * If the current user is not allowed to delete the annotation, an exception + * will be thrown. A user can delete a note if: + * - the user has admin access for the site, OR + * - the user has view access, is not the anonymous user and is the user that + * created the note + * + * @param string $idSite The site ID to add the annotation to. + * @param string $idNote The ID of the note to delete. + */ + public function delete($idSite, $idNote) + { + $this->checkSingleIdSite($idSite, $extraMessage = "Note: Cannot delete multiple notes."); + + $annotations = new Piwik_Annotations_AnnotationList($idSite); + + // check permissions + $this->checkUserCanModifyOrDelete($idSite, $annotations->get($idSite, $idNote)); + + // remove the note & save the list + $annotations->remove($idSite, $idNote); + $annotations->save($idSite); + } + + /** + * Returns a single note for one site. + * + * @param string $idSite The site ID to add the annotation to. + * @param string $idNote The ID of the note to get. + * @return array The annotation. It will contain the following properties: + * - date: The date the annotation was recorded for. + * - note: The note text. + * - starred: Whether the note is starred or not. + * - user: The user that created the note. + * - canEditOrDelete: Whether the user that called this method can edit or + * delete the annotation returned. + */ + public function get($idSite, $idNote) + { + $this->checkSingleIdSite($idSite, $extraMessage = "Note: Specify only one site ID when getting ONE note."); + Piwik::checkUserHasViewAccess($idSite); + + // get single annotation + $annotations = new Piwik_Annotations_AnnotationList($idSite); + return $annotations->get($idSite, $idNote); + } + + /** + * Returns every annotation for a specific site within a specific date range. + * The date range is specified by a date, the period type (day/week/month/year) + * and an optional number of N periods in the past to include. + * + * @param string $idSite The site ID to add the annotation to. Can be one ID or + * a list of site IDs. + * @param string|false $date The date of the period. + * @param string $period The period type. + * @param int|false $lastN Whether to include the last N number of periods in the + * date range or not. + * @return array An array that indexes arrays of annotations by site ID. ie, + * array( + * 5 => array( + * array(...), // annotation #1 + * array(...), // annotation #2 + * ), + * 8 => array(...) + * ) + */ + public function getAll($idSite, $date = false, $period = 'day', $lastN = false) + { + Piwik::checkUserHasViewAccess($idSite); + + $annotations = new Piwik_Annotations_AnnotationList($idSite); + + // if date/period are supplied, determine start/end date for search + list($startDate, $endDate) = self::getDateRangeForPeriod($date, $period, $lastN); + + return $annotations->search($startDate, $endDate); + } + + /** + * Returns the count of annotations for a list of periods, including the count of + * starred annotations. + * + * @param string $idSite The site ID to add the annotation to. + * @param string|false $date The date of the period. + * @param string $period The period type. + * @param int|false $lastN Whether to get counts for the last N number of periods or not. + * @return array An array mapping site IDs to arrays holding dates & the count of + * annotations made for those dates. eg, + * array( + * 5 => array( + * array('2012-01-02', array('count' => 4, 'starred' => 2)), + * array('2012-01-03', array('count' => 0, 'starred' => 0)), + * array('2012-01-04', array('count' => 2, 'starred' => 0)), + * ), + * 6 => array( + * array('2012-01-02', array('count' => 1, 'starred' => 0)), + * array('2012-01-03', array('count' => 4, 'starred' => 3)), + * array('2012-01-04', array('count' => 2, 'starred' => 0)), + * ), + * ... + * ) + */ + public function getAnnotationCountForDates($idSite, $date, $period, $lastN = false, $getAnnotationText = false) + { + Piwik::checkUserHasViewAccess($idSite); + + // get start & end date for request. lastN is ignored if $period == 'range' + list($startDate, $endDate) = self::getDateRangeForPeriod($date, $period, $lastN); + if ($period == 'range') { + $period = 'day'; + } + + // create list of dates + $dates = array(); + for (; $startDate->getTimestamp() <= $endDate->getTimestamp(); $startDate = $startDate->addPeriod(1, $period)) { + $dates[] = $startDate; + } + // we add one for the end of the last period (used in for loop below to bound annotation dates) + $dates[] = $startDate; + + // get annotations for the site + $annotations = new Piwik_Annotations_AnnotationList($idSite); + + // create result w/ 0-counts + $result = array(); + for ($i = 0; $i != count($dates) - 1; ++$i) { + $date = $dates[$i]; + $nextDate = $dates[$i + 1]; + $strDate = $date->toString(); + + foreach ($annotations->getIdSites() as $idSite) { + $result[$idSite][$strDate] = $annotations->count($idSite, $date, $nextDate); + + // if only one annotation, return the one annotation's text w/ the counts + if ($getAnnotationText + && $result[$idSite][$strDate]['count'] == 1 + ) { + $annotationsForSite = $annotations->search( + $date, Piwik_Date::factory($nextDate->getTimestamp() - 1), $idSite); + $annotation = reset($annotationsForSite[$idSite]); + + $result[$idSite][$strDate]['note'] = $annotation['note']; + } + } + } + + // convert associative array into array of pairs (so it can be traversed by index) + $pairResult = array(); + foreach ($result as $idSite => $counts) { + foreach ($counts as $date => $count) { + $pairResult[$idSite][] = array($date, $count); + } + } + return $pairResult; + } + + /** + * Throws if the current user is not allowed to modify or delete an annotation. + * + * @param int $idSite The site ID the annotation belongs to. + * @param array $annotation The annotation. + * @throws Exception if the current user is not allowed to modify/delete $annotation. + */ + private function checkUserCanModifyOrDelete($idSite, $annotation) + { + if (!$annotation['canEditOrDelete']) { + throw new Exception(Piwik_Translate('Annotations_YouCannotModifyThisNote')); + } + } + + /** + * Throws if the current user is not allowed to create annotations for a site. + * + * @param int $idSite The site ID. + * @throws Exception if the current user is anonymous or does not have view access + * for site w/ id=$idSite. + */ + private static function checkUserCanAddNotesFor($idSite) + { + if (!Piwik_Annotations_AnnotationList::canUserAddNotesFor($idSite)) { + throw new Exception("The current user is not allowed to add notes for site #$idSite."); + } + } + + /** + * Returns start & end dates for the range described by a period and optional lastN + * argument. + * + * @param string $date|false The start date of the period (or the date range of a range + * period). + * @param string $period The period type ('day', 'week', 'month', 'year' or 'range'). + * @param int|false $lastN Whether to include the last N periods in the range or not. + * Ignored if period == range. + * + * @ignore + */ + public static function getDateRangeForPeriod($date, $period, $lastN = false) + { + if ($date === false) { + return array(false, false); + } + + // if the range is just a normal period (or the period is a range in which case lastN is ignored) + if ($lastN === false + || $period == 'range' + ) { + if ($period == 'range') { + $oPeriod = new Piwik_Period_Range('day', $date); + } else { + $oPeriod = Piwik_Period::factory($period, Piwik_Date::factory($date)); + } + + $startDate = $oPeriod->getDateStart(); + $endDate = $oPeriod->getDateEnd(); + } else // if the range includes the last N periods + { + list($date, $lastN) = + Piwik_ViewDataTable_GenerateGraphHTML_ChartEvolution::getDateRangeAndLastN($period, $date, $lastN); + list($startDate, $endDate) = explode(',', $date); + + $startDate = Piwik_Date::factory($startDate); + $endDate = Piwik_Date::factory($endDate); + } + return array($startDate, $endDate); + } + + /** + * Utility function, makes sure idSite string has only one site ID and throws if + * otherwise. + */ + private function checkSingleIdSite($idSite, $extraMessage) + { + // can only add a note to one site + if (!is_numeric($idSite)) { + throw new Exception("Invalid idSite: '$idSite'. $extraMessage"); + } + } + + /** + * Utility function, makes sure date string is valid date, and throws if + * otherwise. + */ + private function checkDateIsValid($date, $canBeNull = false) + { + if ($date === null + && $canBeNull + ) { + return; + } + + Piwik_Date::factory($date); + } } diff --git a/plugins/Annotations/AnnotationList.php b/plugins/Annotations/AnnotationList.php index 47d897f2dc..daed59a554 100755 --- a/plugins/Annotations/AnnotationList.php +++ b/plugins/Annotations/AnnotationList.php @@ -12,440 +12,418 @@ /** * This class can be used to query & modify annotations for multiple sites * at once. - * + * * Example use: * $annotations = new Piwik_Annotations_AnnotationList($idSites = "1,2,5"); * $annotation = $annotations->get($idSite = 1, $idNote = 4); * // do stuff w/ annotation * $annotations->update($idSite = 2, $idNote = 4, $note = "This is the new text."); * $annotations->save($idSite); - * + * * Note: There is a concurrency issue w/ this code. If two users try to save * an annotation for the same site, it's possible one of their changes will * never get made (as it will be overwritten by the other's). - * + * * @package Piwik_Annotations */ class Piwik_Annotations_AnnotationList { - const ANNOTATION_COLLECTION_OPTION_SUFFIX = '_annotations'; - - /** - * List of site IDs this instance holds annotations for. - * - * @var array - */ - private $idSites; - - /** - * Array that associates lists of annotations with site IDs. - * - * @var array - */ - private $annotations; - - /** - * Constructor. Loads annotations from the database. - * - * @param string|int $idSites The list of site IDs to load annotations for. - */ - public function __construct( $idSites ) - { - $this->idSites = Piwik_Site::getIdSitesFromIdSitesString($idSites); - $this->annotations = $this->getAnnotationsForSite(); - } - - /** - * Returns the list of site IDs this list contains annotations for. - * - * @return array - */ - public function getIdSites() - { - return $this->idSites; - } - - /** - * Creates a new annotation for a site. This method does not perist the result. - * To save the new annotation in the database, call $this->save. - * - * @param int $idSite The ID of the site to add an annotation to. - * @param string $date The date the annotation is in reference to. - * @param string $note The text of the new annotation. - * @param int $starred Either 1 or 0. If 1, the new annotation has been starred, - * otherwise it will start out unstarred. - * @return array The added annotation. - * @throws Exception if $idSite is not an ID that was supplied upon construction. - */ - public function add($idSite, $date, $note, $starred = 0) - { - $this->checkIdSiteIsLoaded($idSite); - - $this->annotations[$idSite][] = self::makeAnnotation($date, $note, $starred); - - // get the id of the new annotation - end($this->annotations[$idSite]); - $newNoteId = key($this->annotations[$idSite]); - - return $this->get($idSite, $newNoteId); - } - - /** - * Persists the annotations list for a site, overwriting whatever exists. - * - * @param int $idSite The ID of the site to save annotations for. - * @throws Exception if $idSite is not an ID that was supplied upon construction. - */ - public function save($idSite) - { - $this->checkIdSiteIsLoaded($idSite); - - $optionName = self::getAnnotationCollectionOptionName($idSite); - Piwik_SetOption($optionName, serialize($this->annotations[$idSite])); - } - - /** - * Modifies an annotation in this instance's collection of annotations. - * - * Note: This method does not perist the change in the DB. The save method must - * be called for that. - * - * @param int $idSite The ID of the site whose annotation will be updated. - * @param int $idNote The ID of the note. - * @param string|null $date The new date of the annotation, eg '2012-01-01'. If - * null, no change is made. - * @param string|null $note The new text of the annotation. If null, no change - * is made. - * @param int|null $starred Either 1 or 0, whether the annotation should be - * starred or not. If null, no change is made. - * @throws Exception if $idSite is not an ID that was supplied upon construction. - * @throws Exception if $idNote does not refer to valid note for the site. - */ - public function update( $idSite, $idNote, $date = null, $note = null, $starred = null ) - { - $this->checkIdSiteIsLoaded($idSite); - $this->checkNoteExists($idSite, $idNote); - - $annotation =& $this->annotations[$idSite][$idNote]; - if ($date !== null) - { - $annotation['date'] = $date; - } - if ($note !== null) - { - $annotation['note'] = $note; - } - if ($starred !== null) - { - $annotation['starred'] = $starred; - } - } - - /** - * Removes a note from a site's collection of annotations. - * - * Note: This method does not perist the change in the DB. The save method must - * be called for that. - * - * @param int $idSite The ID of the site whose annotation will be updated. - * @param int $idNote The ID of the note. - * @throws Exception if $idSite is not an ID that was supplied upon construction. - * @throws Exception if $idNote does not refer to valid note for the site. - */ - public function remove( $idSite, $idNote ) - { - $this->checkIdSiteIsLoaded($idSite); - $this->checkNoteExists($idSite, $idNote); - - unset($this->annotations[$idSite][$idNote]); - } - - /** - * Retrieves an annotation by ID. - * - * This function returns an array with the following elements: - * - idNote: The ID of the annotation. - * - date: The date of the annotation. - * - note: The text of the annotation. - * - starred: 1 or 0, whether the annotation is stared; - * - user: (unless current user is anonymous) The user that created the annotation. - * - canEditOrDelete: True if the user can edit/delete the annotation. - * - * @param int $idSite The ID of the site to get an annotation for. - * @param int $idNote The ID of the note to get. - * @param array The annotation. - * @throws Exception if $idSite is not an ID that was supplied upon construction. - * @throws Exception if $idNote does not refer to valid note for the site. - */ - public function get( $idSite, $idNote ) - { - $this->checkIdSiteIsLoaded($idSite); - $this->checkNoteExists($idSite, $idNote); - - $annotation = $this->annotations[$idSite][$idNote]; - $this->augmentAnnotationData($idSite, $idNote, $annotation); - return $annotation; - } - - /** - * Returns all annotations within a specific date range. The result is - * an array that maps site IDs with arrays of annotations within the range. - * - * Note: The date range is inclusive. - * - * @see self::get for info on what attributes stored within annotations. - * - * @param Piwik_Date|false $startDate The start of the date range. - * @param Piwik_Date|false $endDate The end of the date range. - * @param string|int|array|false $idSite IDs of the sites whose annotations to - * search through. - * @return array Array mapping site IDs with arrays of annotations, eg: - * array( - * '5' => array( - * array(...), // annotation - * array(...), // annotation - * ... - * ), - * '6' => array( - * array(...), // annotation - * array(...), // annotation - * ... - * ), - * ) - */ - public function search( $startDate, $endDate, $idSite = false ) - { - if ($idSite) - { - $idSites = Piwik_Site::getIdSitesFromIdSitesString($idSite); - } - else - { - $idSites = array_keys($this->annotations); - } - - // collect annotations that are within the right date range & belong to the right - // site - $result = array(); - foreach ($idSites as $idSite) - { - if (!isset($this->annotations[$idSite])) - { - continue; - } - - foreach ($this->annotations[$idSite] as $idNote => $annotation) - { - if ($startDate !== false) - { - $annotationDate = Piwik_Date::factory($annotation['date']); - if ($annotationDate->getTimestamp() < $startDate->getTimestamp() - || $annotationDate->getTimestamp() > $endDate->getTimestamp()) - { - continue; - } - } - - $this->augmentAnnotationData($idSite, $idNote, $annotation); - $result[$idSite][] = $annotation; - } - - // sort by annotation date - if (!empty($result[$idSite])) - { - uasort($result[$idSite], array($this, 'compareAnnotationDate')); - } - } - return $result; - } - - /** - * Counts annotations & starred annotations within a date range and returns - * the counts. The date range includes the start date, but not the end date. - * - * @param int $idSite The ID of the site to count annotations for. - * @param string|false $startDate The start date of the range or false if no - * range check is desired. - * @param string|false $endDate The end date of the range or false if no - * range check is desired. - * @return array eg, array('count' => 5, 'starred' => 2) - */ - public function count( $idSite, $startDate, $endDate ) - { - $this->checkIdSiteIsLoaded($idSite); - - // search includes end date, and count should not, so subtract one from the timestamp - $annotations = $this->search($startDate, Piwik_Date::factory($endDate->getTimestamp() - 1)); - - // count the annotations - $count = $starred = 0; - if (!empty($annotations[$idSite])) - { - $count = count($annotations[$idSite]); - foreach ($annotations[$idSite] as $annotation) - { - if ($annotation['starred']) - { - ++$starred; - } - } - } - - return array('count' => $count, 'starred' => $starred); - } - - /** - * Utility function. Creates a new annotation. - * - * @param string $date - * @param string $note - * @param int $starred - */ - private function makeAnnotation( $date, $note, $starred = 0 ) - { - return array('date' => $date, - 'note' => $note, - 'starred' => (int)$starred, - 'user' => Piwik::getCurrentUserLogin()); - } - - /** - * Retrieves annotations from the database for the sites supplied to the - * constructor. - * - * @return array Lists of annotations mapped by site ID. - */ - private function getAnnotationsForSite() - { - $result = array(); - foreach ($this->idSites as $id) - { - $optionName = self::getAnnotationCollectionOptionName($id); - $serialized = Piwik_GetOption($optionName); - - if ($serialized !== false) - { - $result[$id] = unserialize($serialized); - } - else - { - $result[$id] = array(); - } - } - return $result; - } - - /** - * Utility function that checks if a site ID was supplied and if not, - * throws an exception. - * - * We can only modify/read annotations for sites that we've actually - * loaded the annotations for. - * - * @param int $idSite - * @throws Exception - */ - private function checkIdSiteIsLoaded( $idSite ) - { - if (!in_array($idSite, $this->idSites)) - { - throw new Exception("This AnnotationList was not initialized with idSite '$idSite'."); - } - } - - /** - * Utility function that checks if a note exists for a site, and if not, - * throws an exception. - * - * @param int $idSite - * @param int $idNote - * @throws Exception - */ - private function checkNoteExists( $idSite, $idNote ) - { - if (empty($this->annotations[$idSite][$idNote])) - { - throw new Exception("There is no note with id '$idNote' for site with id '$idSite'."); - } - } - - /** - * Returns true if the current user can modify or delete a specific annotation. - * - * A user can modify/delete a note if the user has admin access for the site OR - * the user has view access, is not the anonymous user and is the user that - * created the note in question. - * - * @param int $idSite The site ID the annotation belongs to. - * @param array $annotation The annotation. - * @return bool - */ - public static function canUserModifyOrDelete( $idSite, $annotation ) - { - // user can save if user is admin or if has view access, is not anonymous & is user who wrote note - $canEdit = Piwik::isUserHasAdminAccess($idSite) - || (!Piwik::isUserIsAnonymous() - && Piwik::getCurrentUserLogin() == $annotation['user']); - return $canEdit; - } - - /** - * Adds extra data to an annotation, including the annotation's ID and whether - * the current user can edit or delete it. - * - * Also, if the current user is anonymous, the user attribute is removed. - * - * @param int $idSite - * @param int $idNote - * @param array $annotation - */ - private function augmentAnnotationData( $idSite, $idNote, &$annotation ) - { - $annotation['idNote'] = $idNote; - $annotation['canEditOrDelete'] = self::canUserModifyOrDelete($idSite, $annotation); - - // we don't supply user info if the current user is anonymous - if (Piwik::isUserIsAnonymous()) - { - unset($annotation['user']); - } - } - - /** - * Utility function that compares two annotations. - * - * @param array $lhs An annotation. - * @param array $rhs An annotation. - * @return int -1, 0 or 1 - */ - public function compareAnnotationDate( $lhs, $rhs ) - { - if ($lhs['date'] == $rhs['date']) - { - return $lhs['idNote'] <= $rhs['idNote'] ? -1 : 1; - } - - return $lhs['date'] < $rhs['date'] ? -1 : 1; // string comparison works because date format should be YYYY-MM-DD - } - - /** - * Returns true if the current user can add notes for a specific site. - * - * @param int $idSite The site to add notes to. - */ - public static function canUserAddNotesFor( $idSite ) - { - return Piwik::isUserHasViewAccess($idSite) - && !Piwik::isUserIsAnonymous($idSite); - } - - /** - * Returns the option name used to store annotations for a site. - * - * @param int $idSite The site ID. - */ - public static function getAnnotationCollectionOptionName( $idSite ) - { - return $idSite.self::ANNOTATION_COLLECTION_OPTION_SUFFIX; - } + const ANNOTATION_COLLECTION_OPTION_SUFFIX = '_annotations'; + + /** + * List of site IDs this instance holds annotations for. + * + * @var array + */ + private $idSites; + + /** + * Array that associates lists of annotations with site IDs. + * + * @var array + */ + private $annotations; + + /** + * Constructor. Loads annotations from the database. + * + * @param string|int $idSites The list of site IDs to load annotations for. + */ + public function __construct($idSites) + { + $this->idSites = Piwik_Site::getIdSitesFromIdSitesString($idSites); + $this->annotations = $this->getAnnotationsForSite(); + } + + /** + * Returns the list of site IDs this list contains annotations for. + * + * @return array + */ + public function getIdSites() + { + return $this->idSites; + } + + /** + * Creates a new annotation for a site. This method does not perist the result. + * To save the new annotation in the database, call $this->save. + * + * @param int $idSite The ID of the site to add an annotation to. + * @param string $date The date the annotation is in reference to. + * @param string $note The text of the new annotation. + * @param int $starred Either 1 or 0. If 1, the new annotation has been starred, + * otherwise it will start out unstarred. + * @return array The added annotation. + * @throws Exception if $idSite is not an ID that was supplied upon construction. + */ + public function add($idSite, $date, $note, $starred = 0) + { + $this->checkIdSiteIsLoaded($idSite); + + $this->annotations[$idSite][] = self::makeAnnotation($date, $note, $starred); + + // get the id of the new annotation + end($this->annotations[$idSite]); + $newNoteId = key($this->annotations[$idSite]); + + return $this->get($idSite, $newNoteId); + } + + /** + * Persists the annotations list for a site, overwriting whatever exists. + * + * @param int $idSite The ID of the site to save annotations for. + * @throws Exception if $idSite is not an ID that was supplied upon construction. + */ + public function save($idSite) + { + $this->checkIdSiteIsLoaded($idSite); + + $optionName = self::getAnnotationCollectionOptionName($idSite); + Piwik_SetOption($optionName, serialize($this->annotations[$idSite])); + } + + /** + * Modifies an annotation in this instance's collection of annotations. + * + * Note: This method does not perist the change in the DB. The save method must + * be called for that. + * + * @param int $idSite The ID of the site whose annotation will be updated. + * @param int $idNote The ID of the note. + * @param string|null $date The new date of the annotation, eg '2012-01-01'. If + * null, no change is made. + * @param string|null $note The new text of the annotation. If null, no change + * is made. + * @param int|null $starred Either 1 or 0, whether the annotation should be + * starred or not. If null, no change is made. + * @throws Exception if $idSite is not an ID that was supplied upon construction. + * @throws Exception if $idNote does not refer to valid note for the site. + */ + public function update($idSite, $idNote, $date = null, $note = null, $starred = null) + { + $this->checkIdSiteIsLoaded($idSite); + $this->checkNoteExists($idSite, $idNote); + + $annotation =& $this->annotations[$idSite][$idNote]; + if ($date !== null) { + $annotation['date'] = $date; + } + if ($note !== null) { + $annotation['note'] = $note; + } + if ($starred !== null) { + $annotation['starred'] = $starred; + } + } + + /** + * Removes a note from a site's collection of annotations. + * + * Note: This method does not perist the change in the DB. The save method must + * be called for that. + * + * @param int $idSite The ID of the site whose annotation will be updated. + * @param int $idNote The ID of the note. + * @throws Exception if $idSite is not an ID that was supplied upon construction. + * @throws Exception if $idNote does not refer to valid note for the site. + */ + public function remove($idSite, $idNote) + { + $this->checkIdSiteIsLoaded($idSite); + $this->checkNoteExists($idSite, $idNote); + + unset($this->annotations[$idSite][$idNote]); + } + + /** + * Retrieves an annotation by ID. + * + * This function returns an array with the following elements: + * - idNote: The ID of the annotation. + * - date: The date of the annotation. + * - note: The text of the annotation. + * - starred: 1 or 0, whether the annotation is stared; + * - user: (unless current user is anonymous) The user that created the annotation. + * - canEditOrDelete: True if the user can edit/delete the annotation. + * + * @param int $idSite The ID of the site to get an annotation for. + * @param int $idNote The ID of the note to get. + * @param array The annotation. + * @throws Exception if $idSite is not an ID that was supplied upon construction. + * @throws Exception if $idNote does not refer to valid note for the site. + */ + public function get($idSite, $idNote) + { + $this->checkIdSiteIsLoaded($idSite); + $this->checkNoteExists($idSite, $idNote); + + $annotation = $this->annotations[$idSite][$idNote]; + $this->augmentAnnotationData($idSite, $idNote, $annotation); + return $annotation; + } + + /** + * Returns all annotations within a specific date range. The result is + * an array that maps site IDs with arrays of annotations within the range. + * + * Note: The date range is inclusive. + * + * @see self::get for info on what attributes stored within annotations. + * + * @param Piwik_Date|false $startDate The start of the date range. + * @param Piwik_Date|false $endDate The end of the date range. + * @param string|int|array|false $idSite IDs of the sites whose annotations to + * search through. + * @return array Array mapping site IDs with arrays of annotations, eg: + * array( + * '5' => array( + * array(...), // annotation + * array(...), // annotation + * ... + * ), + * '6' => array( + * array(...), // annotation + * array(...), // annotation + * ... + * ), + * ) + */ + public function search($startDate, $endDate, $idSite = false) + { + if ($idSite) { + $idSites = Piwik_Site::getIdSitesFromIdSitesString($idSite); + } else { + $idSites = array_keys($this->annotations); + } + + // collect annotations that are within the right date range & belong to the right + // site + $result = array(); + foreach ($idSites as $idSite) { + if (!isset($this->annotations[$idSite])) { + continue; + } + + foreach ($this->annotations[$idSite] as $idNote => $annotation) { + if ($startDate !== false) { + $annotationDate = Piwik_Date::factory($annotation['date']); + if ($annotationDate->getTimestamp() < $startDate->getTimestamp() + || $annotationDate->getTimestamp() > $endDate->getTimestamp() + ) { + continue; + } + } + + $this->augmentAnnotationData($idSite, $idNote, $annotation); + $result[$idSite][] = $annotation; + } + + // sort by annotation date + if (!empty($result[$idSite])) { + uasort($result[$idSite], array($this, 'compareAnnotationDate')); + } + } + return $result; + } + + /** + * Counts annotations & starred annotations within a date range and returns + * the counts. The date range includes the start date, but not the end date. + * + * @param int $idSite The ID of the site to count annotations for. + * @param string|false $startDate The start date of the range or false if no + * range check is desired. + * @param string|false $endDate The end date of the range or false if no + * range check is desired. + * @return array eg, array('count' => 5, 'starred' => 2) + */ + public function count($idSite, $startDate, $endDate) + { + $this->checkIdSiteIsLoaded($idSite); + + // search includes end date, and count should not, so subtract one from the timestamp + $annotations = $this->search($startDate, Piwik_Date::factory($endDate->getTimestamp() - 1)); + + // count the annotations + $count = $starred = 0; + if (!empty($annotations[$idSite])) { + $count = count($annotations[$idSite]); + foreach ($annotations[$idSite] as $annotation) { + if ($annotation['starred']) { + ++$starred; + } + } + } + + return array('count' => $count, 'starred' => $starred); + } + + /** + * Utility function. Creates a new annotation. + * + * @param string $date + * @param string $note + * @param int $starred + */ + private function makeAnnotation($date, $note, $starred = 0) + { + return array('date' => $date, + 'note' => $note, + 'starred' => (int)$starred, + 'user' => Piwik::getCurrentUserLogin()); + } + + /** + * Retrieves annotations from the database for the sites supplied to the + * constructor. + * + * @return array Lists of annotations mapped by site ID. + */ + private function getAnnotationsForSite() + { + $result = array(); + foreach ($this->idSites as $id) { + $optionName = self::getAnnotationCollectionOptionName($id); + $serialized = Piwik_GetOption($optionName); + + if ($serialized !== false) { + $result[$id] = unserialize($serialized); + } else { + $result[$id] = array(); + } + } + return $result; + } + + /** + * Utility function that checks if a site ID was supplied and if not, + * throws an exception. + * + * We can only modify/read annotations for sites that we've actually + * loaded the annotations for. + * + * @param int $idSite + * @throws Exception + */ + private function checkIdSiteIsLoaded($idSite) + { + if (!in_array($idSite, $this->idSites)) { + throw new Exception("This AnnotationList was not initialized with idSite '$idSite'."); + } + } + + /** + * Utility function that checks if a note exists for a site, and if not, + * throws an exception. + * + * @param int $idSite + * @param int $idNote + * @throws Exception + */ + private function checkNoteExists($idSite, $idNote) + { + if (empty($this->annotations[$idSite][$idNote])) { + throw new Exception("There is no note with id '$idNote' for site with id '$idSite'."); + } + } + + /** + * Returns true if the current user can modify or delete a specific annotation. + * + * A user can modify/delete a note if the user has admin access for the site OR + * the user has view access, is not the anonymous user and is the user that + * created the note in question. + * + * @param int $idSite The site ID the annotation belongs to. + * @param array $annotation The annotation. + * @return bool + */ + public static function canUserModifyOrDelete($idSite, $annotation) + { + // user can save if user is admin or if has view access, is not anonymous & is user who wrote note + $canEdit = Piwik::isUserHasAdminAccess($idSite) + || (!Piwik::isUserIsAnonymous() + && Piwik::getCurrentUserLogin() == $annotation['user']); + return $canEdit; + } + + /** + * Adds extra data to an annotation, including the annotation's ID and whether + * the current user can edit or delete it. + * + * Also, if the current user is anonymous, the user attribute is removed. + * + * @param int $idSite + * @param int $idNote + * @param array $annotation + */ + private function augmentAnnotationData($idSite, $idNote, &$annotation) + { + $annotation['idNote'] = $idNote; + $annotation['canEditOrDelete'] = self::canUserModifyOrDelete($idSite, $annotation); + + // we don't supply user info if the current user is anonymous + if (Piwik::isUserIsAnonymous()) { + unset($annotation['user']); + } + } + + /** + * Utility function that compares two annotations. + * + * @param array $lhs An annotation. + * @param array $rhs An annotation. + * @return int -1, 0 or 1 + */ + public function compareAnnotationDate($lhs, $rhs) + { + if ($lhs['date'] == $rhs['date']) { + return $lhs['idNote'] <= $rhs['idNote'] ? -1 : 1; + } + + return $lhs['date'] < $rhs['date'] ? -1 : 1; // string comparison works because date format should be YYYY-MM-DD + } + + /** + * Returns true if the current user can add notes for a specific site. + * + * @param int $idSite The site to add notes to. + */ + public static function canUserAddNotesFor($idSite) + { + return Piwik::isUserHasViewAccess($idSite) + && !Piwik::isUserIsAnonymous($idSite); + } + + /** + * Returns the option name used to store annotations for a site. + * + * @param int $idSite The site ID. + */ + public static function getAnnotationCollectionOptionName($idSite) + { + return $idSite . self::ANNOTATION_COLLECTION_OPTION_SUFFIX; + } } diff --git a/plugins/Annotations/Annotations.php b/plugins/Annotations/Annotations.php index 1b0503862a..937500053a 100755 --- a/plugins/Annotations/Annotations.php +++ b/plugins/Annotations/Annotations.php @@ -12,58 +12,58 @@ /** * Annotations plugins. Provides the ability to attach text notes to * dates for each sites. Notes can be viewed, modified, deleted or starred. - * + * * @package Piwik_Annotations */ class Piwik_Annotations extends Piwik_Plugin { - /** - * Returns information about this plugin. - * - * @return array - */ - public function getInformation() - { - return array( - 'description' => Piwik_Translate('Annotations_PluginDescription'), - 'author' => 'Piwik', - 'author_homepage' => 'http://piwik.org/', - 'version' => Piwik_Version::VERSION, - ); - } - - /** - * Returns list of event hooks. - * - * @return array - */ - public function getListHooksRegistered() - { - return array( - 'AssetManager.getCssFiles' => 'getCssFiles', - 'AssetManager.getJsFiles' => 'getJsFiles' - ); - } + /** + * Returns information about this plugin. + * + * @return array + */ + public function getInformation() + { + return array( + 'description' => Piwik_Translate('Annotations_PluginDescription'), + 'author' => 'Piwik', + 'author_homepage' => 'http://piwik.org/', + 'version' => Piwik_Version::VERSION, + ); + } + + /** + * Returns list of event hooks. + * + * @return array + */ + public function getListHooksRegistered() + { + return array( + 'AssetManager.getCssFiles' => 'getCssFiles', + 'AssetManager.getJsFiles' => 'getJsFiles' + ); + } - /** - * Adds css files for this plugin to the list in the event notification. - * - * @param Piwik_Event_Notification $notification notification object - */ - function getCssFiles( $notification ) - { - $cssFiles = &$notification->getNotificationObject(); - $cssFiles[] = "plugins/Annotations/templates/styles.css"; - } + /** + * Adds css files for this plugin to the list in the event notification. + * + * @param Piwik_Event_Notification $notification notification object + */ + function getCssFiles($notification) + { + $cssFiles = & $notification->getNotificationObject(); + $cssFiles[] = "plugins/Annotations/templates/styles.css"; + } - /** - * Adds js files for this plugin to the list in the event notification. - * - * @param Piwik_Event_Notification $notification notification object - */ - function getJsFiles( $notification ) - { - $jsFiles = &$notification->getNotificationObject(); - $jsFiles[] = "plugins/Annotations/templates/annotations.js"; - } + /** + * Adds js files for this plugin to the list in the event notification. + * + * @param Piwik_Event_Notification $notification notification object + */ + function getJsFiles($notification) + { + $jsFiles = & $notification->getNotificationObject(); + $jsFiles[] = "plugins/Annotations/templates/annotations.js"; + } } diff --git a/plugins/Annotations/Controller.php b/plugins/Annotations/Controller.php index 0a235a2200..9b9f9e352c 100755 --- a/plugins/Annotations/Controller.php +++ b/plugins/Annotations/Controller.php @@ -11,216 +11,206 @@ /** * Controller for the Annotations plugin. - * + * * @package Piwik_Annotations */ class Piwik_Annotations_Controller extends Piwik_Controller { - /** - * Controller action that returns HTML displaying annotations for a site and - * specific date range. - * - * Query Param Input: - * - idSite: The ID of the site to get annotations for. Only one allowed. - * - date: The date to get annotations for. If lastN is not supplied, this is the start date, - * otherwise the start date in the last period. - * - period: The period type. - * - lastN: If supplied, the last N # of periods will be included w/ the range specified - * by date + period. - * - * Output: - * - HTML displaying annotations for a specific range. - * - * @param bool $fetch True if the annotation manager should be returned as a string, - * false if it should be echo-ed. - * @param string $date Override for 'date' query parameter. - * @param string $period Override for 'period' query parameter. - * @param string $lastN Override for 'lastN' query parameter. - * @return string|void - */ - public function getAnnotationManager( $fetch = false, $date = false, $period = false, $lastN = false ) - { - $idSite = Piwik_Common::getRequestVar('idSite'); - - if ($date === false) - { - $date = Piwik_Common::getRequestVar('date', false); - } - - if ($period === false) - { - $period = Piwik_Common::getRequestVar('period', 'day'); - } - - if ($lastN === false) - { - $lastN = Piwik_Common::getRequestVar('lastN', false); - } - - // create & render the view - $view = Piwik_View::factory('annotationManager'); - - $allAnnotations = Piwik_API_Request::processRequest( - 'Annotations.getAll', array('date' => $date, 'period' => $period, 'lastN' => $lastN)); - $view->annotations = empty($allAnnotations[$idSite]) ? array() : $allAnnotations[$idSite]; - - $view->period = $period; - $view->lastN = $lastN; - - list($startDate, $endDate) = Piwik_Annotations_API::getDateRangeForPeriod($date, $period, $lastN); - $view->startDate = $startDate->toString(); - $view->endDate = $endDate->toString(); - - $dateFormat = Piwik_Translate('CoreHome_ShortDateFormatWithYear'); - $view->startDatePretty = $startDate->getLocalized($dateFormat); - $view->endDatePretty = $endDate->getLocalized($dateFormat); - - $view->canUserAddNotes = Piwik_Annotations_AnnotationList::canUserAddNotesFor($idSite); - - if ($fetch) - { - return $view->render(); - } - else - { - echo $view->render(); - } - } - - /** - * Controller action that modifies an annotation and returns HTML displaying - * the modified annotation. - * - * Query Param Input: - * - idSite: The ID of the site the annotation belongs to. Only one ID is allowed. - * - idNote: The ID of the annotation. - * - date: The new date value for the annotation. (optional) - * - note: The new text for the annotation. (optional) - * - starred: Either 1 or 0. Whether the note should be starred or not. (optional) - * - * Output: - * - HTML displaying modified annotation. - * - * If an optional query param is not supplied, that part of the annotation is - * not modified. - */ - public function saveAnnotation() - { - if ($_SERVER["REQUEST_METHOD"] == "POST") - { - $this->checkTokenInUrl(); - - $view = Piwik_View::factory('annotation'); - - // NOTE: permissions checked in API method - // save the annotation - $view->annotation = Piwik_API_Request::processRequest("Annotations.save"); - - echo $view->render(); - } - } - - /** - * Controller action that adds a new annotation for a site and returns new - * annotation manager HTML for the site and date range. - * - * Query Param Input: - * - idSite: The ID of the site to add an annotation to. - * - date: The date for the new annotation. - * - note: The text of the annotation. - * - starred: Either 1 or 0, whether the annotation should be starred or not. - * Defaults to 0. - * - managerDate: The date for the annotation manager. If a range is given, the start - * date is used for the new annotation. - * - managerPeriod: For rendering the annotation manager. @see self::getAnnotationManager - * for more info. - * - lastN: For rendering the annotation manager. @see self::getAnnotationManager - * for more info. - * Output: - * - @see self::getAnnotationManager - */ - public function addAnnotation() - { - if ($_SERVER["REQUEST_METHOD"] == "POST") - { - $this->checkTokenInUrl(); - - // the date used is for the annotation manager HTML that gets echo'd. we - // use this date for the new annotation, unless it is a date range, in - // which case we use the first date of the range. - $date = Piwik_Common::getRequestVar('date'); - if (strpos($date, ',') !== false) - { - $date = reset(explode(',', $date)); - } - - // add the annotation. NOTE: permissions checked in API method - Piwik_API_Request::processRequest("Annotations.add", array('date' => $date)); - - $managerDate = Piwik_Common::getRequestVar('managerDate', false); - $managerPeriod = Piwik_Common::getRequestVar('managerPeriod', false); - echo $this->getAnnotationManager($fetch = true, $managerDate, $managerPeriod); - } - } - - /** - * Controller action that deletes an annotation and returns new annotation - * manager HTML for the site & date range. - * - * Query Param Input: - * - idSite: The ID of the site this annotation belongs to. - * - idNote: The ID of the annotation to delete. - * - date: For rendering the annotation manager. @see self::getAnnotationManager - * for more info. - * - period: For rendering the annotation manager. @see self::getAnnotationManager - * for more info. - * - lastN: For rendering the annotation manager. @see self::getAnnotationManager - * for more info. - * - * Output: - * - @see self::getAnnotationManager - */ - public function deleteAnnotation() - { - if ($_SERVER["REQUEST_METHOD"] == "POST") - { - $this->checkTokenInUrl(); - - // delete annotation. NOTE: permissions checked in API method - Piwik_API_Request::processRequest("Annotations.delete"); - - echo $this->getAnnotationManager($fetch = true); - } - } - - /** - * Controller action that echo's HTML that displays marker icons for an - * evolution graph's x-axis. The marker icons still need to be positioned - * by the JavaScript. - * - * Query Param Input: - * - idSite: The ID of the site this annotation belongs to. Only one is allowed. - * - date: The date to check for annotations. If lastN is not supplied, this is - * the start of the date range used to check for annotations. If supplied, - * this is the start of the last period in the date range. - * - period: The period type. - * - lastN: If supplied, the last N # of periods are included in the date range - * used to check for annotations. - * - * Output: - * - HTML that displays marker icons for an evolution graph based on the - * number of annotations & starred annotations in the graph's date range. - */ - public function getEvolutionIcons() - { - // get annotation the count - $annotationCounts = Piwik_API_Request::processRequest( - "Annotations.getAnnotationCountForDates", array('getAnnotationText' => 1)); - - // create & render the view - $view = Piwik_View::factory('evolutionAnnotations'); - $view->annotationCounts = reset($annotationCounts); // only one idSite allowed for this action - - echo $view->render(); - } + /** + * Controller action that returns HTML displaying annotations for a site and + * specific date range. + * + * Query Param Input: + * - idSite: The ID of the site to get annotations for. Only one allowed. + * - date: The date to get annotations for. If lastN is not supplied, this is the start date, + * otherwise the start date in the last period. + * - period: The period type. + * - lastN: If supplied, the last N # of periods will be included w/ the range specified + * by date + period. + * + * Output: + * - HTML displaying annotations for a specific range. + * + * @param bool $fetch True if the annotation manager should be returned as a string, + * false if it should be echo-ed. + * @param string $date Override for 'date' query parameter. + * @param string $period Override for 'period' query parameter. + * @param string $lastN Override for 'lastN' query parameter. + * @return string|void + */ + public function getAnnotationManager($fetch = false, $date = false, $period = false, $lastN = false) + { + $idSite = Piwik_Common::getRequestVar('idSite'); + + if ($date === false) { + $date = Piwik_Common::getRequestVar('date', false); + } + + if ($period === false) { + $period = Piwik_Common::getRequestVar('period', 'day'); + } + + if ($lastN === false) { + $lastN = Piwik_Common::getRequestVar('lastN', false); + } + + // create & render the view + $view = Piwik_View::factory('annotationManager'); + + $allAnnotations = Piwik_API_Request::processRequest( + 'Annotations.getAll', array('date' => $date, 'period' => $period, 'lastN' => $lastN)); + $view->annotations = empty($allAnnotations[$idSite]) ? array() : $allAnnotations[$idSite]; + + $view->period = $period; + $view->lastN = $lastN; + + list($startDate, $endDate) = Piwik_Annotations_API::getDateRangeForPeriod($date, $period, $lastN); + $view->startDate = $startDate->toString(); + $view->endDate = $endDate->toString(); + + $dateFormat = Piwik_Translate('CoreHome_ShortDateFormatWithYear'); + $view->startDatePretty = $startDate->getLocalized($dateFormat); + $view->endDatePretty = $endDate->getLocalized($dateFormat); + + $view->canUserAddNotes = Piwik_Annotations_AnnotationList::canUserAddNotesFor($idSite); + + if ($fetch) { + return $view->render(); + } else { + echo $view->render(); + } + } + + /** + * Controller action that modifies an annotation and returns HTML displaying + * the modified annotation. + * + * Query Param Input: + * - idSite: The ID of the site the annotation belongs to. Only one ID is allowed. + * - idNote: The ID of the annotation. + * - date: The new date value for the annotation. (optional) + * - note: The new text for the annotation. (optional) + * - starred: Either 1 or 0. Whether the note should be starred or not. (optional) + * + * Output: + * - HTML displaying modified annotation. + * + * If an optional query param is not supplied, that part of the annotation is + * not modified. + */ + public function saveAnnotation() + { + if ($_SERVER["REQUEST_METHOD"] == "POST") { + $this->checkTokenInUrl(); + + $view = Piwik_View::factory('annotation'); + + // NOTE: permissions checked in API method + // save the annotation + $view->annotation = Piwik_API_Request::processRequest("Annotations.save"); + + echo $view->render(); + } + } + + /** + * Controller action that adds a new annotation for a site and returns new + * annotation manager HTML for the site and date range. + * + * Query Param Input: + * - idSite: The ID of the site to add an annotation to. + * - date: The date for the new annotation. + * - note: The text of the annotation. + * - starred: Either 1 or 0, whether the annotation should be starred or not. + * Defaults to 0. + * - managerDate: The date for the annotation manager. If a range is given, the start + * date is used for the new annotation. + * - managerPeriod: For rendering the annotation manager. @see self::getAnnotationManager + * for more info. + * - lastN: For rendering the annotation manager. @see self::getAnnotationManager + * for more info. + * Output: + * - @see self::getAnnotationManager + */ + public function addAnnotation() + { + if ($_SERVER["REQUEST_METHOD"] == "POST") { + $this->checkTokenInUrl(); + + // the date used is for the annotation manager HTML that gets echo'd. we + // use this date for the new annotation, unless it is a date range, in + // which case we use the first date of the range. + $date = Piwik_Common::getRequestVar('date'); + if (strpos($date, ',') !== false) { + $date = reset(explode(',', $date)); + } + + // add the annotation. NOTE: permissions checked in API method + Piwik_API_Request::processRequest("Annotations.add", array('date' => $date)); + + $managerDate = Piwik_Common::getRequestVar('managerDate', false); + $managerPeriod = Piwik_Common::getRequestVar('managerPeriod', false); + echo $this->getAnnotationManager($fetch = true, $managerDate, $managerPeriod); + } + } + + /** + * Controller action that deletes an annotation and returns new annotation + * manager HTML for the site & date range. + * + * Query Param Input: + * - idSite: The ID of the site this annotation belongs to. + * - idNote: The ID of the annotation to delete. + * - date: For rendering the annotation manager. @see self::getAnnotationManager + * for more info. + * - period: For rendering the annotation manager. @see self::getAnnotationManager + * for more info. + * - lastN: For rendering the annotation manager. @see self::getAnnotationManager + * for more info. + * + * Output: + * - @see self::getAnnotationManager + */ + public function deleteAnnotation() + { + if ($_SERVER["REQUEST_METHOD"] == "POST") { + $this->checkTokenInUrl(); + + // delete annotation. NOTE: permissions checked in API method + Piwik_API_Request::processRequest("Annotations.delete"); + + echo $this->getAnnotationManager($fetch = true); + } + } + + /** + * Controller action that echo's HTML that displays marker icons for an + * evolution graph's x-axis. The marker icons still need to be positioned + * by the JavaScript. + * + * Query Param Input: + * - idSite: The ID of the site this annotation belongs to. Only one is allowed. + * - date: The date to check for annotations. If lastN is not supplied, this is + * the start of the date range used to check for annotations. If supplied, + * this is the start of the last period in the date range. + * - period: The period type. + * - lastN: If supplied, the last N # of periods are included in the date range + * used to check for annotations. + * + * Output: + * - HTML that displays marker icons for an evolution graph based on the + * number of annotations & starred annotations in the graph's date range. + */ + public function getEvolutionIcons() + { + // get annotation the count + $annotationCounts = Piwik_API_Request::processRequest( + "Annotations.getAnnotationCountForDates", array('getAnnotationText' => 1)); + + // create & render the view + $view = Piwik_View::factory('evolutionAnnotations'); + $view->annotationCounts = reset($annotationCounts); // only one idSite allowed for this action + + echo $view->render(); + } } diff --git a/plugins/Annotations/templates/annotation.tpl b/plugins/Annotations/templates/annotation.tpl index e1cd5f6013..cc0d909a3b 100755 --- a/plugins/Annotations/templates/annotation.tpl +++ b/plugins/Annotations/templates/annotation.tpl @@ -1,43 +1,46 @@ <tr class="annotation" data-id="{$annotation.idNote}" data-date="{$annotation.date}"> - <td class="annotation-meta"> - <div class="annotation-star{if $annotation.canEditOrDelete} annotation-star-changeable{/if}" data-starred="{$annotation.starred}" {if $annotation.canEditOrDelete}title="{'Annotations_ClickToStarOrUnstar'|translate}"{/if}> - {if $annotation.starred} - <img src="themes/default/images/star.png"/> - {else} - <img src="themes/default/images/star_empty.png"/> - {/if} - </div> - <div class="annotation-period {if $annotation.canEditOrDelete}annotation-enter-edit-mode{/if}">({$annotation.date})</div> - {if $annotation.canEditOrDelete} - <div class="annotation-period-edit" style="display:none"> - <a href="#">{$annotation.date}</a> - <div class="datepicker" style="display:none"/> - </div> - {/if} - </td> - <td class="annotation-value"> - <div class="annotation-view-mode"> - <span {if $annotation.canEditOrDelete}title="{'Annotations_ClickToEdit'|translate}" class="annotation-enter-edit-mode"{/if}>{$annotation.note|unescape|escape:'html'}</span> - {if $annotation.canEditOrDelete} - <a href="#" class="edit-annotation annotation-enter-edit-mode" title="{'Annotations_ClickToEdit'|translate}">{'General_Edit'|translate}...</a> - {/if} - </div> - {if $annotation.canEditOrDelete} - <div class="annotation-edit-mode" style="display:none"> - <input class="annotation-edit" type="text" value="{$annotation.note|unescape|escape:'html'}"/> - <br/> - <input class="annotation-save submit" type="button" value="{'General_Save'|translate}"/> - <input class="annotation-cancel submit" type="button" value="{'General_Cancel'|translate}"/> - </div> - {/if} - </td> - {if isset($annotation.user) && $userLogin != 'anonymous'} - <td class="annotation-user-cell"> - <span class="annotation-user">{$annotation.user|unescape|escape:'html'}</span><br/> - {if $annotation.canEditOrDelete} - <a href="#" class="delete-annotation" style="display:none" title="{'Annotations_ClickToDelete'|translate}">{'General_Delete'|translate}</a> - {/if} - </td> - {/if} + <td class="annotation-meta"> + <div class="annotation-star{if $annotation.canEditOrDelete} annotation-star-changeable{/if}" data-starred="{$annotation.starred}" + {if $annotation.canEditOrDelete}title="{'Annotations_ClickToStarOrUnstar'|translate}"{/if}> + {if $annotation.starred} + <img src="themes/default/images/star.png"/> + {else} + <img src="themes/default/images/star_empty.png"/> + {/if} + </div> + <div class="annotation-period {if $annotation.canEditOrDelete}annotation-enter-edit-mode{/if}">({$annotation.date})</div> + {if $annotation.canEditOrDelete} + <div class="annotation-period-edit" style="display:none"> + <a href="#">{$annotation.date}</a> + + <div class="datepicker" style="display:none"/> + </div> + {/if} + </td> + <td class="annotation-value"> + <div class="annotation-view-mode"> + <span {if $annotation.canEditOrDelete}title="{'Annotations_ClickToEdit'|translate}" + class="annotation-enter-edit-mode"{/if}>{$annotation.note|unescape|escape:'html'}</span> + {if $annotation.canEditOrDelete} + <a href="#" class="edit-annotation annotation-enter-edit-mode" title="{'Annotations_ClickToEdit'|translate}">{'General_Edit'|translate}...</a> + {/if} + </div> + {if $annotation.canEditOrDelete} + <div class="annotation-edit-mode" style="display:none"> + <input class="annotation-edit" type="text" value="{$annotation.note|unescape|escape:'html'}"/> + <br/> + <input class="annotation-save submit" type="button" value="{'General_Save'|translate}"/> + <input class="annotation-cancel submit" type="button" value="{'General_Cancel'|translate}"/> + </div> + {/if} + </td> + {if isset($annotation.user) && $userLogin != 'anonymous'} + <td class="annotation-user-cell"> + <span class="annotation-user">{$annotation.user|unescape|escape:'html'}</span><br/> + {if $annotation.canEditOrDelete} + <a href="#" class="delete-annotation" style="display:none" title="{'Annotations_ClickToDelete'|translate}">{'General_Delete'|translate}</a> + {/if} + </td> + {/if} </tr> diff --git a/plugins/Annotations/templates/annotationManager.tpl b/plugins/Annotations/templates/annotationManager.tpl index 852c09bbc4..401c2458c9 100755 --- a/plugins/Annotations/templates/annotationManager.tpl +++ b/plugins/Annotations/templates/annotationManager.tpl @@ -1,27 +1,27 @@ <div class="annotation-manager" - {if $startDate neq $endDate}data-date="{$startDate},{$endDate}" data-period="range" - {else}data-date="{$startDate}" data-period="{$period}" - {/if}> + {if $startDate neq $endDate}data-date="{$startDate},{$endDate}" data-period="range" + {else}data-date="{$startDate}" data-period="{$period}" + {/if}> -<div class="annotations-header"> - <span>{'Annotations_Annotations'|translate}</span> -</div> + <div class="annotations-header"> + <span>{'Annotations_Annotations'|translate}</span> + </div> -<div class="annotation-list-range">{$startDatePretty}{if $startDate neq $endDate} — {$endDatePretty}{/if}</div> + <div class="annotation-list-range">{$startDatePretty}{if $startDate neq $endDate} — {$endDatePretty}{/if}</div> -<div class="annotation-list"> -{include file="Annotations/templates/annotations.tpl"} + <div class="annotation-list"> + {include file="Annotations/templates/annotations.tpl"} -<span class="loadingPiwik" style="display:none"><img src="themes/default/images/loading-blue.gif"/>{'General_Loading_js'|translate}</span> + <span class="loadingPiwik" style="display:none"><img src="themes/default/images/loading-blue.gif"/>{'General_Loading_js'|translate}</span> -</div> + </div> -<div class="annotation-controls"> - {if $canUserAddNotes} - <a href="#" class="add-annotation" title="{'Annotations_CreateNewAnnotation'|translate}">{'Annotations_CreateNewAnnotation'|translate}</a> - {elseif $userLogin eq 'anonymous'} - <a href="index.php?module=Login">{'Annotations_LoginToAnnotate'|translate}</a> - {/if} -</div> + <div class="annotation-controls"> + {if $canUserAddNotes} + <a href="#" class="add-annotation" title="{'Annotations_CreateNewAnnotation'|translate}">{'Annotations_CreateNewAnnotation'|translate}</a> + {elseif $userLogin eq 'anonymous'} + <a href="index.php?module=Login">{'Annotations_LoginToAnnotate'|translate}</a> + {/if} + </div> </div> diff --git a/plugins/Annotations/templates/annotations.js b/plugins/Annotations/templates/annotations.js index 62f63a59aa..85097ad1d6 100755 --- a/plugins/Annotations/templates/annotations.js +++ b/plugins/Annotations/templates/annotations.js @@ -5,628 +5,586 @@ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later */ -(function($, piwik) { - -var annotationsApi = { - - // calls Annotations.getAnnotationManager - getAnnotationManager: function(idSite, date, period, lastN, callback) - { - var ajaxParams = - { - module: 'Annotations', - action: 'getAnnotationManager', - idSite: idSite, - date: date, - period: period - }; - if (lastN) - { - ajaxParams.lastN = lastN; - } - - var ajaxRequest = new ajaxHelper(); - ajaxRequest.addParams(ajaxParams, 'get'); - ajaxRequest.setCallback(callback); - ajaxRequest.setFormat('html'); - ajaxRequest.send(false); - }, - - // calls Annotations.addAnnotation - addAnnotation: function(idSite, managerDate, managerPeriod, date, note, callback) - { - var ajaxParams = - { - module: 'Annotations', - action: 'addAnnotation', - idSite: idSite, - date: date, - managerDate: managerDate, - managerPeriod: managerPeriod, - note: note - }; - - 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); - }, - - // calls Annotations.saveAnnotation - saveAnnotation: function(idSite, idNote, date, noteData, callback) - { - var ajaxParams = - { - module: 'Annotations', - action: 'saveAnnotation', - idSite: idSite, - idNote: idNote, - date: date - }; - - for (var key in noteData) - { - ajaxParams[key] = noteData[key]; - } - - 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); - }, - - // calls Annotations.deleteAnnotation - deleteAnnotation: function(idSite, idNote, managerDate, managerPeriod, callback) - { - var ajaxParams = - { - module: 'Annotations', - action: 'deleteAnnotation', - idSite: idSite, - idNote: idNote, - date: managerDate, - period: managerPeriod - }; - - 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); - }, - - // calls Annotations.getEvolutionIcons - getEvolutionIcons: function(idSite, date, period, lastN, callback) - { - var ajaxParams = - { - module: 'Annotations', - action: 'getEvolutionIcons', - idSite: idSite, - date: date, - period: period - }; - if (lastN) - { - ajaxParams.lastN = lastN; - } - - var ajaxRequest = new ajaxHelper(); - ajaxRequest.addParams(ajaxParams, 'get'); - ajaxRequest.setFormat('html'); - ajaxRequest.setCallback(callback); - ajaxRequest.send(false); - } -}; - -var today = new Date(); - -/** - * Returns options to configure an annotation's datepicker shown in edit mode. - * - * @param {Element} annotation The annotation element. - */ -var getDatePickerOptions = function(annotation) -{ - var annotationDateStr = annotation.attr('data-date'), - parts = annotationDateStr.split('-'), - annotationDate = new Date(parts[0], parts[1] - 1, parts[2]); - - var result = piwik.getBaseDatePickerOptions(annotationDate); - - // make sure days before site start & after today cannot be selected - var piwikMinDate = result.minDate; - result.beforeShowDay = function (date) - { - var valid = true; - - // if date is after today or before date of site creation, it cannot be selected - if (date > today - || date < piwikMinDate) - { - valid = false; - } - - return [valid, '']; - }; - - // on select a date, change the text of the edit date link - result.onSelect = function (dateText) - { - $('.annotation-period-edit>a', annotation).text(dateText); - $('.datepicker', annotation).hide(); - }; - - return result; -}; - -/** - * Switches the current mode of an annotation between the view/edit modes. - * - * @param {Element} inAnnotationElement An element within the annotation to toggle the mode of. - * Should be two levels nested in the .annotation-value - * element. - * @return {Element} The .annotation-value element. - */ -var toggleAnnotationMode = function(inAnnotationElement) -{ - var annotation = $(inAnnotationElement).closest('.annotation'); - $('.annotation-period,.annotation-period-edit,.delete-annotation,' + - '.annotation-edit-mode,.annotation-view-mode', annotation).toggle(); - - return $(inAnnotationElement).find('.annotation-value'); -}; - -/** - * Creates the datepicker for an annotation element. - * - * @param {Element} annotation The annotation element. - */ -var createDatePicker = function ( annotation ) -{ - $('.datepicker', annotation).datepicker(getDatePickerOptions(annotation)).hide(); -}; - -/** - * Creates datepickers for every period edit in an annotation manager. - * - * @param {Element} manager The annotation manager element. - */ -var createDatePickers = function ( manager ) -{ - $('.annotation-period-edit', manager).each(function() { - createDatePicker($(this).parent().parent()); - }); -} - -/** - * Replaces the HTML of an annotation manager element, and resets date/period - * attributes. - * - * @param {Element} manager The annotation manager. - * @param {string} tml The HTML of the new annotation manager. - */ -var replaceAnnotationManager = function(manager, html) -{ - var newManager = $(html); - manager.html(newManager.html()) - .attr('data-date', newManager.attr('data-date')) - .attr('data-period', newManager.attr('data-period')); - createDatePickers(manager); -}; - -/** - * Returns true if an annotation element is starred, false if otherwise. - * - * @param {Element} annotation The annotation element. - * @return {bool} - */ -var isAnnotationStarred = function(annotation) -{ - return +$('.annotation-star', annotation).attr('data-starred') == 1 ? true : false; -}; - -/** - * Replaces the HTML of an annotation element with HTML returned from Piwik, and - * makes sure the data attributes are correct. - * - * @param {Element} annotation The annotation element. - * @param {string} html The replacement HTML (or alternatively, the replacement - * element/jQuery object). - */ -var replaceAnnotationHtml = function ( annotation, html ) -{ - var newHtml = $(html); - annotation.html(newHtml.html()).attr('data-date', newHtml.attr('data-date')); - createDatePicker(annotation); -} - -/** - * Binds events to an annotation manager element. - * - * @param {Element} manager The annotation manager. - * @param {int} idSite The site ID the manager is showing annotations for. - * @param {function} onAnnotationCountChange Callback that is called when there is a change - * in the number of annotations and/or starred annotations, - * eg, when a user adds a new one or deletes an existing one. - */ -var bindAnnotationManagerEvents = function(manager, idSite, onAnnotationCountChange) -{ - if (!onAnnotationCountChange) - { - onAnnotationCountChange = function() {}; - } - - // show new annotation row if create new annotation link is clicked - manager.on('click', '.add-annotation', function(e) { - e.preventDefault(); - - $('.new-annotation-row', manager).show(); - $(this).hide(); - - return false; - }); - - // hide new annotation row if cancel button clicked - manager.on('click', '.new-annotation-cancel', function() { - var newAnnotationRow = $(this).parent().parent(); - newAnnotationRow.hide(); - - $('.add-annotation', newAnnotationRow.closest('.annotation-manager')).show(); - }); - - // save new annotation when new annotation row save is clicked - manager.on('click', '.new-annotation-save', function() { - var addRow = $(this).parent().parent(), - addNoteInput = addRow.find('.new-annotation-edit'), - noteDate = addRow.find('.annotation-period-edit>a').text(); - - // do nothing if input is empty - if (!addNoteInput.val()) - { - return; - } - - // disable input & link - addNoteInput.attr('disabled', 'disabled'); - $(this).attr('disabled', 'disabled'); - - // add a new annotation for the site, date & period - annotationsApi.addAnnotation( - idSite, - manager.attr('data-date'), - manager.attr('data-period'), - noteDate, - addNoteInput.val(), - function(response) { - replaceAnnotationManager(manager, response); - - // increment annotation count for this date - onAnnotationCountChange(noteDate, 1, 0); - } - ); - }); - - // add new annotation when enter key pressed on new annotation input - manager.on('keypress', '.new-annotation-edit', function(e) { - if (e.which == 13) - { - $(this).parent().find('.new-annotation-save').click(); - } - }); - - // show annotation editor if edit link, annotation text or period text is clicked - manager.on('click', '.annotation-enter-edit-mode', function(e) { - e.preventDefault(); - - var annotationContent = toggleAnnotationMode(this); - annotationContent.find('.annotation-edit').focus(); - - return false; - }); - - // hide annotation editor if cancel button is clicked - manager.on('click', '.annotation-cancel', function() { - toggleAnnotationMode(this); - }); - - // save annotation if save button clicked - manager.on('click', '.annotation-edit-mode .annotation-save', function() { - var annotation = $(this).parent().parent().parent(), - input = $('.annotation-edit', annotation), - dateEditText = $('.annotation-period-edit>a', annotation).text(); - - // if annotation value/date has not changed, just show the view mode instead of edit - if (input[0].defaultValue == input.val() - && dateEditText == annotation.attr('data-date')) - { - toggleAnnotationMode(this); - return; - } - - // disable input while ajax is happening - input.attr('disabled', 'disabled'); - $(this).attr('disabled', 'disabled'); - - // save the note w/ the new note text & date - annotationsApi.saveAnnotation( - idSite, - annotation.attr('data-id'), - dateEditText, - { - note: input.val() - }, - function(response) { - response = $(response); - - var newDate = response.attr('data-date'), - isStarred = isAnnotationStarred(response), - originalDate = annotation.attr('data-date'); - - replaceAnnotationHtml(annotation, response); - - // if the date has been changed, update the evolution icon counts to reflect the change - if (originalDate != newDate) - { - // reduce count for original date - onAnnotationCountChange(originalDate, -1, isStarred ? -1 : 0); - - // increase count for new date - onAnnotationCountChange(newDate, 1, isStarred ? 1 : 0); - } - } - ); - }); - - // save annotation if 'enter' pressed on input - manager.on('keypress', '.annotation-value input', function(e) { - if (e.which == 13) - { - $(this).parent().find('.annotation-save').click(); - } - }); - - // delete annotation if delete link clicked - manager.on('click', '.delete-annotation', function(e) { - e.preventDefault(); - - var annotation = $(this).parent().parent(); - $(this).attr('disabled', 'disabled'); - - // delete annotation by ajax - annotationsApi.deleteAnnotation( - idSite, - annotation.attr('data-id'), - manager.attr('data-date'), - manager.attr('data-period'), - function (response) { - replaceAnnotationManager(manager, response); - - // update evolution icons - var isStarred = isAnnotationStarred(annotation); - onAnnotationCountChange(annotation.attr('data-date'), -1, isStarred ? -1 : 0); - } - ); - - return false; - }); - - // star/unstar annotation if star clicked - manager.on('click', '.annotation-star-changeable', function(e) { - var annotation = $(this).parent().parent(), - newStarredVal = $(this).attr('data-starred') == 0 ? 1 : 0 // flip existing 'starred' value - ; - - // perform ajax request to star annotation - annotationsApi.saveAnnotation( - idSite, - annotation.attr('data-id'), - annotation.attr('data-date'), - { - starred: newStarredVal - }, - function (response) { - replaceAnnotationHtml(annotation, response); - - // change starred count for this annotation in evolution graph based on what we're - // changing the starred value to - onAnnotationCountChange(annotation.attr('data-date'), 0, newStarredVal == 0 ? -1 : 1); - } - ); - }); - - // when period edit is clicked, show datepicker - manager.on('click', '.annotation-period-edit>a', function(e) { - e.preventDefault(); - $('.datepicker', $(this).parent()).toggle(); - return false; - }); - - // make sure datepicker popups are closed if someone clicks elsewhere - $('body').on('mouseup', function(e) { - var container = $('.annotation-period-edit>.datepicker:visible').parent(); - - if (!container.has(e.target).length) - { - container.find('.datepicker').hide(); - } - }); -}; +(function ($, piwik) { + + var annotationsApi = { + + // calls Annotations.getAnnotationManager + getAnnotationManager: function (idSite, date, period, lastN, callback) { + var ajaxParams = + { + module: 'Annotations', + action: 'getAnnotationManager', + idSite: idSite, + date: date, + period: period + }; + if (lastN) { + ajaxParams.lastN = lastN; + } + + var ajaxRequest = new ajaxHelper(); + ajaxRequest.addParams(ajaxParams, 'get'); + ajaxRequest.setCallback(callback); + ajaxRequest.setFormat('html'); + ajaxRequest.send(false); + }, + + // calls Annotations.addAnnotation + addAnnotation: function (idSite, managerDate, managerPeriod, date, note, callback) { + var ajaxParams = + { + module: 'Annotations', + action: 'addAnnotation', + idSite: idSite, + date: date, + managerDate: managerDate, + managerPeriod: managerPeriod, + note: note + }; + + 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); + }, + + // calls Annotations.saveAnnotation + saveAnnotation: function (idSite, idNote, date, noteData, callback) { + var ajaxParams = + { + module: 'Annotations', + action: 'saveAnnotation', + idSite: idSite, + idNote: idNote, + date: date + }; + + for (var key in noteData) { + ajaxParams[key] = noteData[key]; + } + + 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); + }, + + // calls Annotations.deleteAnnotation + deleteAnnotation: function (idSite, idNote, managerDate, managerPeriod, callback) { + var ajaxParams = + { + module: 'Annotations', + action: 'deleteAnnotation', + idSite: idSite, + idNote: idNote, + date: managerDate, + period: managerPeriod + }; + + 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); + }, + + // calls Annotations.getEvolutionIcons + getEvolutionIcons: function (idSite, date, period, lastN, callback) { + var ajaxParams = + { + module: 'Annotations', + action: 'getEvolutionIcons', + idSite: idSite, + date: date, + period: period + }; + if (lastN) { + ajaxParams.lastN = lastN; + } + + var ajaxRequest = new ajaxHelper(); + ajaxRequest.addParams(ajaxParams, 'get'); + ajaxRequest.setFormat('html'); + ajaxRequest.setCallback(callback); + ajaxRequest.send(false); + } + }; + + var today = new Date(); + + /** + * Returns options to configure an annotation's datepicker shown in edit mode. + * + * @param {Element} annotation The annotation element. + */ + var getDatePickerOptions = function (annotation) { + var annotationDateStr = annotation.attr('data-date'), + parts = annotationDateStr.split('-'), + annotationDate = new Date(parts[0], parts[1] - 1, parts[2]); + + var result = piwik.getBaseDatePickerOptions(annotationDate); + + // make sure days before site start & after today cannot be selected + var piwikMinDate = result.minDate; + result.beforeShowDay = function (date) { + var valid = true; + + // if date is after today or before date of site creation, it cannot be selected + if (date > today + || date < piwikMinDate) { + valid = false; + } + + return [valid, '']; + }; + + // on select a date, change the text of the edit date link + result.onSelect = function (dateText) { + $('.annotation-period-edit>a', annotation).text(dateText); + $('.datepicker', annotation).hide(); + }; + + return result; + }; + + /** + * Switches the current mode of an annotation between the view/edit modes. + * + * @param {Element} inAnnotationElement An element within the annotation to toggle the mode of. + * Should be two levels nested in the .annotation-value + * element. + * @return {Element} The .annotation-value element. + */ + var toggleAnnotationMode = function (inAnnotationElement) { + var annotation = $(inAnnotationElement).closest('.annotation'); + $('.annotation-period,.annotation-period-edit,.delete-annotation,' + + '.annotation-edit-mode,.annotation-view-mode', annotation).toggle(); + + return $(inAnnotationElement).find('.annotation-value'); + }; + + /** + * Creates the datepicker for an annotation element. + * + * @param {Element} annotation The annotation element. + */ + var createDatePicker = function (annotation) { + $('.datepicker', annotation).datepicker(getDatePickerOptions(annotation)).hide(); + }; + + /** + * Creates datepickers for every period edit in an annotation manager. + * + * @param {Element} manager The annotation manager element. + */ + var createDatePickers = function (manager) { + $('.annotation-period-edit', manager).each(function () { + createDatePicker($(this).parent().parent()); + }); + } + + /** + * Replaces the HTML of an annotation manager element, and resets date/period + * attributes. + * + * @param {Element} manager The annotation manager. + * @param {string} tml The HTML of the new annotation manager. + */ + var replaceAnnotationManager = function (manager, html) { + var newManager = $(html); + manager.html(newManager.html()) + .attr('data-date', newManager.attr('data-date')) + .attr('data-period', newManager.attr('data-period')); + createDatePickers(manager); + }; + + /** + * Returns true if an annotation element is starred, false if otherwise. + * + * @param {Element} annotation The annotation element. + * @return {bool} + */ + var isAnnotationStarred = function (annotation) { + return +$('.annotation-star', annotation).attr('data-starred') == 1 ? true : false; + }; + + /** + * Replaces the HTML of an annotation element with HTML returned from Piwik, and + * makes sure the data attributes are correct. + * + * @param {Element} annotation The annotation element. + * @param {string} html The replacement HTML (or alternatively, the replacement + * element/jQuery object). + */ + var replaceAnnotationHtml = function (annotation, html) { + var newHtml = $(html); + annotation.html(newHtml.html()).attr('data-date', newHtml.attr('data-date')); + createDatePicker(annotation); + } + + /** + * Binds events to an annotation manager element. + * + * @param {Element} manager The annotation manager. + * @param {int} idSite The site ID the manager is showing annotations for. + * @param {function} onAnnotationCountChange Callback that is called when there is a change + * in the number of annotations and/or starred annotations, + * eg, when a user adds a new one or deletes an existing one. + */ + var bindAnnotationManagerEvents = function (manager, idSite, onAnnotationCountChange) { + if (!onAnnotationCountChange) { + onAnnotationCountChange = function () {}; + } + + // show new annotation row if create new annotation link is clicked + manager.on('click', '.add-annotation', function (e) { + e.preventDefault(); + + $('.new-annotation-row', manager).show(); + $(this).hide(); + + return false; + }); + + // hide new annotation row if cancel button clicked + manager.on('click', '.new-annotation-cancel', function () { + var newAnnotationRow = $(this).parent().parent(); + newAnnotationRow.hide(); + + $('.add-annotation', newAnnotationRow.closest('.annotation-manager')).show(); + }); + + // save new annotation when new annotation row save is clicked + manager.on('click', '.new-annotation-save', function () { + var addRow = $(this).parent().parent(), + addNoteInput = addRow.find('.new-annotation-edit'), + noteDate = addRow.find('.annotation-period-edit>a').text(); + + // do nothing if input is empty + if (!addNoteInput.val()) { + return; + } + + // disable input & link + addNoteInput.attr('disabled', 'disabled'); + $(this).attr('disabled', 'disabled'); + + // add a new annotation for the site, date & period + annotationsApi.addAnnotation( + idSite, + manager.attr('data-date'), + manager.attr('data-period'), + noteDate, + addNoteInput.val(), + function (response) { + replaceAnnotationManager(manager, response); + + // increment annotation count for this date + onAnnotationCountChange(noteDate, 1, 0); + } + ); + }); + + // add new annotation when enter key pressed on new annotation input + manager.on('keypress', '.new-annotation-edit', function (e) { + if (e.which == 13) { + $(this).parent().find('.new-annotation-save').click(); + } + }); + + // show annotation editor if edit link, annotation text or period text is clicked + manager.on('click', '.annotation-enter-edit-mode', function (e) { + e.preventDefault(); + + var annotationContent = toggleAnnotationMode(this); + annotationContent.find('.annotation-edit').focus(); + + return false; + }); + + // hide annotation editor if cancel button is clicked + manager.on('click', '.annotation-cancel', function () { + toggleAnnotationMode(this); + }); + + // save annotation if save button clicked + manager.on('click', '.annotation-edit-mode .annotation-save', function () { + var annotation = $(this).parent().parent().parent(), + input = $('.annotation-edit', annotation), + dateEditText = $('.annotation-period-edit>a', annotation).text(); + + // if annotation value/date has not changed, just show the view mode instead of edit + if (input[0].defaultValue == input.val() + && dateEditText == annotation.attr('data-date')) { + toggleAnnotationMode(this); + return; + } + + // disable input while ajax is happening + input.attr('disabled', 'disabled'); + $(this).attr('disabled', 'disabled'); + + // save the note w/ the new note text & date + annotationsApi.saveAnnotation( + idSite, + annotation.attr('data-id'), + dateEditText, + { + note: input.val() + }, + function (response) { + response = $(response); + + var newDate = response.attr('data-date'), + isStarred = isAnnotationStarred(response), + originalDate = annotation.attr('data-date'); + + replaceAnnotationHtml(annotation, response); + + // if the date has been changed, update the evolution icon counts to reflect the change + if (originalDate != newDate) { + // reduce count for original date + onAnnotationCountChange(originalDate, -1, isStarred ? -1 : 0); + + // increase count for new date + onAnnotationCountChange(newDate, 1, isStarred ? 1 : 0); + } + } + ); + }); + + // save annotation if 'enter' pressed on input + manager.on('keypress', '.annotation-value input', function (e) { + if (e.which == 13) { + $(this).parent().find('.annotation-save').click(); + } + }); + + // delete annotation if delete link clicked + manager.on('click', '.delete-annotation', function (e) { + e.preventDefault(); + + var annotation = $(this).parent().parent(); + $(this).attr('disabled', 'disabled'); + + // delete annotation by ajax + annotationsApi.deleteAnnotation( + idSite, + annotation.attr('data-id'), + manager.attr('data-date'), + manager.attr('data-period'), + function (response) { + replaceAnnotationManager(manager, response); + + // update evolution icons + var isStarred = isAnnotationStarred(annotation); + onAnnotationCountChange(annotation.attr('data-date'), -1, isStarred ? -1 : 0); + } + ); + + return false; + }); + + // star/unstar annotation if star clicked + manager.on('click', '.annotation-star-changeable', function (e) { + var annotation = $(this).parent().parent(), + newStarredVal = $(this).attr('data-starred') == 0 ? 1 : 0 // flip existing 'starred' value + ; + + // perform ajax request to star annotation + annotationsApi.saveAnnotation( + idSite, + annotation.attr('data-id'), + annotation.attr('data-date'), + { + starred: newStarredVal + }, + function (response) { + replaceAnnotationHtml(annotation, response); + + // change starred count for this annotation in evolution graph based on what we're + // changing the starred value to + onAnnotationCountChange(annotation.attr('data-date'), 0, newStarredVal == 0 ? -1 : 1); + } + ); + }); + + // when period edit is clicked, show datepicker + manager.on('click', '.annotation-period-edit>a', function (e) { + e.preventDefault(); + $('.datepicker', $(this).parent()).toggle(); + return false; + }); + + // make sure datepicker popups are closed if someone clicks elsewhere + $('body').on('mouseup', function (e) { + var container = $('.annotation-period-edit>.datepicker:visible').parent(); + + if (!container.has(e.target).length) { + container.find('.datepicker').hide(); + } + }); + }; // used in below function -var loadingAnnotationManager = false; - -/** - * Shows an annotation manager under a report for a specific site & date range. - * - * @param {Element} domElem The element of the report to show the annotation manger - * under. - * @param {int} idSite The ID of the site to show the annotations of. - * @param {string} date The start date of the period. - * @param {string} period The period type. - * @param {int} Whether to include the last N periods in the date range or not. Can - * be undefined. - */ -var showAnnotationViewer = function(domElem, idSite, date, period, lastN, callback) -{ - var addToAnnotationCount = function(date, amt, starAmt) - { - if (date.indexOf(',') != -1) - { - date = date.split(',')[0]; - } - - $('.evolution-annotations>span', domElem).each(function() { - if ($(this).attr('data-date') == date) - { - // get counts from attributes (and convert them to ints) - var starredCount = +$(this).attr('data-starred'), - annotationCount = +$(this).attr('data-count'); - - // modify the starred count & make sure the correct image is used - var newStarCount = starredCount + starAmt; - if(newStarCount > 0) { - var newImg = 'themes/default/images/yellow_marker.png'; - } else { - var newImg = 'themes/default/images/grey_marker.png'; + var loadingAnnotationManager = false; + + /** + * Shows an annotation manager under a report for a specific site & date range. + * + * @param {Element} domElem The element of the report to show the annotation manger + * under. + * @param {int} idSite The ID of the site to show the annotations of. + * @param {string} date The start date of the period. + * @param {string} period The period type. + * @param {int} Whether to include the last N periods in the date range or not. Can + * be undefined. + */ + var showAnnotationViewer = function (domElem, idSite, date, period, lastN, callback) { + var addToAnnotationCount = function (date, amt, starAmt) { + if (date.indexOf(',') != -1) { + date = date.split(',')[0]; + } + + $('.evolution-annotations>span', domElem).each(function () { + if ($(this).attr('data-date') == date) { + // get counts from attributes (and convert them to ints) + var starredCount = +$(this).attr('data-starred'), + annotationCount = +$(this).attr('data-count'); + + // modify the starred count & make sure the correct image is used + var newStarCount = starredCount + starAmt; + if (newStarCount > 0) { + var newImg = 'themes/default/images/yellow_marker.png'; + } else { + var newImg = 'themes/default/images/grey_marker.png'; + } + $(this).attr('data-starred', newStarCount).find('img').attr('src', newImg); + + // modify the annotation count & hide/show based on new count + var newCount = annotationCount + amt; + $(this).attr('data-count', newCount).css('opacity', newCount > 0 ? 1 : 0); + + return false; + } + }); + }; + + var manager = $('.annotation-manager', domElem); + if (manager.length) { + // if annotations for the requested date + period are already loaded, then just toggle the + // visibility of the annotation viewer. otherwise, we reload the annotations. + if (manager.attr('data-date') == date + && manager.attr('data-period') == period) { + // toggle manager view + if (manager.is(':hidden')) { + manager.slideDown('slow', function () { if (callback) callback(manager) }); + } + else { + manager.slideUp('slow', function () { if (callback) callback(manager) }); + } + } + else { + // show nothing but the loading gif + $('.annotations', manager).html(''); + $('.loadingPiwik', manager).show(); + + // reload annotation manager for new date/period + annotationsApi.getAnnotationManager(idSite, date, period, lastN, function (response) { + replaceAnnotationManager(manager, response); + + createDatePickers(manager); + + // show if hidden + if (manager.is(':hidden')) { + manager.slideDown('slow', function () { if (callback) callback(manager) }); + } + else { + if (callback) { + callback(manager); + } + } + }); + } } - $(this).attr('data-starred', newStarCount).find('img').attr('src', newImg); - - // modify the annotation count & hide/show based on new count - var newCount = annotationCount + amt; - $(this).attr('data-count', newCount).css('opacity', newCount > 0 ? 1 : 0); - - return false; - } - }); - }; - - var manager = $('.annotation-manager', domElem); - if (manager.length) - { - // if annotations for the requested date + period are already loaded, then just toggle the - // visibility of the annotation viewer. otherwise, we reload the annotations. - if (manager.attr('data-date') == date - && manager.attr('data-period') == period) - { - // toggle manager view - if (manager.is(':hidden')) - { - manager.slideDown('slow', function () { if (callback) callback(manager) }); - } - else - { - manager.slideUp('slow', function () { if (callback) callback(manager) }); - } - } - else - { - // show nothing but the loading gif - $('.annotations', manager).html(''); - $('.loadingPiwik', manager).show(); - - // reload annotation manager for new date/period - annotationsApi.getAnnotationManager(idSite, date, period, lastN, function(response) { - replaceAnnotationManager(manager, response); - - createDatePickers(manager); - - // show if hidden - if (manager.is(':hidden')) - { - manager.slideDown('slow', function () { if (callback) callback(manager) }); - } - else - { - if (callback) - { - callback(manager); - } - } - }); - } - } - else - { - // if we are already loading the annotation manager, don't load it again - if (loadingAnnotationManager) - { - return; - } - - loadingAnnotationManager = true; - - var loading = $('.loadingPiwikBelow', domElem).css({display: 'block'}); - - // the annotations for this report have not been retrieved yet, so do an ajax request - // & show the result - annotationsApi.getAnnotationManager(idSite, date, period, lastN, function(response) { - var manager = $(response).hide(); - - // if an error occurred (and response does not contain the annotation manager), do nothing - if (!manager.hasClass('annotation-manager')) - { - return; - } - - // create datepickers for each shown annotation - createDatePickers(manager); - - bindAnnotationManagerEvents(manager, idSite, addToAnnotationCount); - - loading.css('visibility', 'hidden'); - - // add & show annotation manager - $('.dataTableFeatures', domElem).append(manager); - manager.slideDown('slow', function() { - loading.hide().css('visibility', 'visible'); - loadingAnnotationManager = false; - - if (callback) callback(manager) - }); - }); - } -}; - -/** - * Determines the x-coordinates of a set of evolution annotation icons. - * - * @param {Element} annotations The '.evolution-annotations' element. - * @param {Element} graphElem The evolution graph's datatable element. - */ -var placeEvolutionIcons = function (annotations, graphElem) -{ - var canvases = $('.piwik-graph .jqplot-xaxis canvas', graphElem), - noteSize = 16; - - // if no graph available, hide all icons - if (!canvases || canvases.length == 0) { - $('span', annotations).hide(); - return true; - } - - // set position of each individual icon - $('span', annotations).each(function(i) { - var canvas = $(canvases[i]), - canvasCenterX = canvas.position().left + (canvas.width() / 2); - $(this).css({ - left: canvasCenterX - noteSize / 2, - // show if there are annotations for this x-axis tick - opacity: +$(this).attr('data-count') > 0 ? 1 : 0 - }); - }); -}; + else { + // if we are already loading the annotation manager, don't load it again + if (loadingAnnotationManager) { + return; + } + + loadingAnnotationManager = true; + + var loading = $('.loadingPiwikBelow', domElem).css({display: 'block'}); + + // the annotations for this report have not been retrieved yet, so do an ajax request + // & show the result + annotationsApi.getAnnotationManager(idSite, date, period, lastN, function (response) { + var manager = $(response).hide(); + + // if an error occurred (and response does not contain the annotation manager), do nothing + if (!manager.hasClass('annotation-manager')) { + return; + } + + // create datepickers for each shown annotation + createDatePickers(manager); + + bindAnnotationManagerEvents(manager, idSite, addToAnnotationCount); + + loading.css('visibility', 'hidden'); + + // add & show annotation manager + $('.dataTableFeatures', domElem).append(manager); + manager.slideDown('slow', function () { + loading.hide().css('visibility', 'visible'); + loadingAnnotationManager = false; + + if (callback) callback(manager) + }); + }); + } + }; + + /** + * Determines the x-coordinates of a set of evolution annotation icons. + * + * @param {Element} annotations The '.evolution-annotations' element. + * @param {Element} graphElem The evolution graph's datatable element. + */ + var placeEvolutionIcons = function (annotations, graphElem) { + var canvases = $('.piwik-graph .jqplot-xaxis canvas', graphElem), + noteSize = 16; + + // if no graph available, hide all icons + if (!canvases || canvases.length == 0) { + $('span', annotations).hide(); + return true; + } + + // set position of each individual icon + $('span', annotations).each(function (i) { + var canvas = $(canvases[i]), + canvasCenterX = canvas.position().left + (canvas.width() / 2); + $(this).css({ + left: canvasCenterX - noteSize / 2, + // show if there are annotations for this x-axis tick + opacity: +$(this).attr('data-count') > 0 ? 1 : 0 + }); + }); + }; // make showAnnotationViewer, placeEvolutionIcons & annotationsApi globally accessible -piwik.annotations = { - showAnnotationViewer: showAnnotationViewer, - placeEvolutionIcons: placeEvolutionIcons, - api: annotationsApi -}; + piwik.annotations = { + showAnnotationViewer: showAnnotationViewer, + placeEvolutionIcons: placeEvolutionIcons, + api: annotationsApi + }; }(jQuery, piwik)); diff --git a/plugins/Annotations/templates/annotations.tpl b/plugins/Annotations/templates/annotations.tpl index f15750e548..26c1f708f1 100755 --- a/plugins/Annotations/templates/annotations.tpl +++ b/plugins/Annotations/templates/annotations.tpl @@ -1,30 +1,29 @@ <div class="annotations"> -{if empty($annotations)} + {if empty($annotations)} + <div class="empty-annotation-list">{'Annotations_NoAnnotations'|translate}</div> + {/if} -<div class="empty-annotation-list">{'Annotations_NoAnnotations'|translate}</div> + <table> + {foreach from=$annotations item=annotation} + {include file="Annotations/templates/annotation.tpl"} + {/foreach} + <tr class="new-annotation-row" style="display:none" data-date="{$startDate}"> + <td class="annotation-meta"> + <div class="annotation-star"> </div> + <div class="annotation-period-edit"> + <a href="#">{$startDate}</a> -{/if} - -<table> -{foreach from=$annotations item=annotation} -{include file="Annotations/templates/annotation.tpl"} -{/foreach} -<tr class="new-annotation-row" style="display:none" data-date="{$startDate}"> - <td class="annotation-meta"> - <div class="annotation-star"> </div> - <div class="annotation-period-edit"> - <a href="#">{$startDate}</a> - <div class="datepicker" style="display:none"/> - </div> - </td> - <td class="annotation-value"> - <input type="text" value="" class="new-annotation-edit" placeholder="{'Annotations_EnterAnnotationText'|translate}"/><br/> - <input type="button" class="submit new-annotation-save" value="{'General_Save'|translate}"/> - <input type="button" class="submit new-annotation-cancel" value="{'General_Cancel'|translate}"/> - </td> - <td class="annotation-user-cell"><span class="annotation-user">{$userLogin}</span></td> -</tr> -</table> + <div class="datepicker" style="display:none"/> + </div> + </td> + <td class="annotation-value"> + <input type="text" value="" class="new-annotation-edit" placeholder="{'Annotations_EnterAnnotationText'|translate}"/><br/> + <input type="button" class="submit new-annotation-save" value="{'General_Save'|translate}"/> + <input type="button" class="submit new-annotation-cancel" value="{'General_Cancel'|translate}"/> + </td> + <td class="annotation-user-cell"><span class="annotation-user">{$userLogin}</span></td> + </tr> + </table> </div> diff --git a/plugins/Annotations/templates/evolutionAnnotations.tpl b/plugins/Annotations/templates/evolutionAnnotations.tpl index 8da13d35f0..eaf6f9d9d3 100755 --- a/plugins/Annotations/templates/evolutionAnnotations.tpl +++ b/plugins/Annotations/templates/evolutionAnnotations.tpl @@ -1,15 +1,15 @@ <div class="evolution-annotations"> -{foreach from=$annotationCounts item=dateCountPair} - {assign var=date value=$dateCountPair[0]} - {assign var=counts value=$dateCountPair[1]} - <span data-date="{$date}" data-count="{$counts.count}" data-starred="{$counts.starred}" - {if $counts.count eq 0}title="{'Annotations_AddAnnotationsFor_js'|translate:$date}" - {elseif $counts.count eq 1}title="{'Annotations_AnnotationOnDate'|translate:$date:$counts.note} + {foreach from=$annotationCounts item=dateCountPair} + {assign var=date value=$dateCountPair[0]} + {assign var=counts value=$dateCountPair[1]} + <span data-date="{$date}" data-count="{$counts.count}" data-starred="{$counts.starred}" + {if $counts.count eq 0}title="{'Annotations_AddAnnotationsFor_js'|translate:$date}" + {elseif $counts.count eq 1}title="{'Annotations_AnnotationOnDate'|translate:$date:$counts.note} {'Annotations_ClickToEditOrAdd'|translate}" - {else}title="{'Annotations_ViewAndAddAnnotations_js'|translate:$date}" - {/if}> + {else}title="{'Annotations_ViewAndAddAnnotations_js'|translate:$date}" + {/if}> <img src="themes/default/images/{if $counts.starred > 0}yellow_marker.png{else}grey_marker.png{/if}" width="16" height="16"/> </span> -{/foreach} + {/foreach} </div> diff --git a/plugins/Annotations/templates/styles.css b/plugins/Annotations/templates/styles.css index 4831bafcb1..089a8ef6c3 100755 --- a/plugins/Annotations/templates/styles.css +++ b/plugins/Annotations/templates/styles.css @@ -1,194 +1,198 @@ .evolution-annotations { - position: relative; - height: 16px; - width: 100%; - margin-top: 12px; - margin-bottom: -28px; - cursor: pointer; + position: relative; + height: 16px; + width: 100%; + margin-top: 12px; + margin-bottom: -28px; + cursor: pointer; } .evolution-annotations > span { - position: absolute; + position: absolute; } .annotation-manager { - text-align: left; - margin-top: -18px; + text-align: left; + margin-top: -18px; } .annotations-header { - display: inline-block; - width: 128px; - text-align: right; - font-size: 12px; - font-style: italic; - margin-bottom: 8px; - vertical-align: top; - color: #666; + display: inline-block; + width: 128px; + text-align: right; + font-size: 12px; + font-style: italic; + margin-bottom: 8px; + vertical-align: top; + color: #666; } .annotation-controls { - display:inline-block; - margin-left: 132px; + display: inline-block; + margin-left: 132px; } .annotation-controls>a { - font-size: 11px; - font-style: italic; - color: #666; - cursor:pointer; - padding:3px 0 6px 0; - display:inline-block; + font-size: 11px; + font-style: italic; + color: #666; + cursor: pointer; + padding: 3px 0 6px 0; + display: inline-block; } .annotation-controls>a:hover { - text-decoration:none; + text-decoration: none; } .annotation-list { - margin-left: 8px; + margin-left: 8px; } .annotation-list table { - width: 100%; + width: 100%; } .annotation-list-range { - display: inline-block; - font-size: 12px; - font-style: italic; - color: #666; - vertical-align: top; - margin: 0 0 8px 8px; + display: inline-block; + font-size: 12px; + font-style: italic; + color: #666; + vertical-align: top; + margin: 0 0 8px 8px; } -.empty-annotation-list,.annotation-list .loadingPiwik { - display: block; - - font-style: italic; - color: #666; - margin: 0 0 12px 140px; +.empty-annotation-list, .annotation-list .loadingPiwik { + display: block; + + font-style: italic; + color: #666; + margin: 0 0 12px 140px; } .annotation-meta { - width: 128px; - text-align: right; - vertical-align: top; - font-size:14px; + width: 128px; + text-align: right; + vertical-align: top; + font-size: 14px; } .annotation-user { - font-style: italic; - font-size: 11px; - color:#444; + font-style: italic; + font-size: 11px; + color: #444; } .annotation-user-cell { - vertical-align: top; - width: 92px; + vertical-align: top; + width: 92px; } .annotation-period { - display:inline-block; - font-style: italic; - margin: 0 8px 8px 8px; - vertical-align: top; + display: inline-block; + font-style: italic; + margin: 0 8px 8px 8px; + vertical-align: top; } .annotation-value { - margin: 0 12px 12px 8px; - vertical-align: top; - position: relative; - font-size:14px; + margin: 0 12px 12px 8px; + vertical-align: top; + position: relative; + font-size: 14px; } .annotation-enter-edit-mode { - cursor: pointer; + cursor: pointer; } -.annotation-edit,.new-annotation-edit { - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; - width:98%; +.annotation-edit, .new-annotation-edit { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + width: 98%; } .annotation-star { - display: inline-block; - margin: 0 8px 8px 0; - width: 16px; + display: inline-block; + margin: 0 8px 8px 0; + width: 16px; } .annotation-star-changeable { - cursor: pointer; + cursor: pointer; } .delete-annotation { - font-size:12px; - font-style: italic; - color: red; - text-decoration: none; - display: inline-block; + font-size: 12px; + font-style: italic; + color: red; + text-decoration: none; + display: inline-block; } .delete-annotation:hover { - text-decoration: underline; + text-decoration: underline; } .annotation-manager .submit { - float:none; + float: none; } .edit-annotation { - font-size:10px; - color:#666; - font-style:italic; + font-size: 10px; + color: #666; + font-style: italic; } + .edit-annotation:hover { - text-decoration:none; + text-decoration: none; } .annotationView { - float: right; + float: right; margin-left: 5px; position: relative; - cursor:pointer; + cursor: pointer; } .annotationView > span { - font-style: italic; - display: inline-block; - margin: 4px 4px 0 4px; + font-style: italic; + display: inline-block; + margin: 4px 4px 0 4px; } .annotation-period-edit { - display:inline-block; - background:white; - color:#444; - font-size:12px; - border: 1px solid #e4e5e4; - padding:5px 5px 6px 3px; - border-radius:4px; - -moz-border-radius:4px; - -webkit-border-radius:4px; + display: inline-block; + background: white; + color: #444; + font-size: 12px; + border: 1px solid #e4e5e4; + padding: 5px 5px 6px 3px; + border-radius: 4px; + -moz-border-radius: 4px; + -webkit-border-radius: 4px; } + .annotation-period-edit:hover { - background:#f1f0eb; - border-color:#a9a399; + background: #f1f0eb; + border-color: #a9a399; } + .annotation-period-edit>a { - text-decoration:none; - cursor:pointer; - display:block; + text-decoration: none; + cursor: pointer; + display: block; } + .annotation-period-edit>.datepicker { - position:absolute; - margin-top:6px; - margin-left:-5px; - z-index:15; - background:white; - border: 1px solid #e4e5e4; - border-radius:4px; - -moz-border-radius:4px; - -webkit-border-radius:4px; + position: absolute; + margin-top: 6px; + margin-left: -5px; + z-index: 15; + background: white; + border: 1px solid #e4e5e4; + border-radius: 4px; + -moz-border-radius: 4px; + -webkit-border-radius: 4px; } diff --git a/plugins/AnonymizeIP/AnonymizeIP.php b/plugins/AnonymizeIP/AnonymizeIP.php index ebb0c29180..e7b311fc26 100644 --- a/plugins/AnonymizeIP/AnonymizeIP.php +++ b/plugins/AnonymizeIP/AnonymizeIP.php @@ -16,63 +16,61 @@ */ class Piwik_AnonymizeIP extends Piwik_Plugin { - /** - * Get plugin information - * @return array - */ - public function getInformation() - { - return array( - 'description' => Piwik_Translate('AnonymizeIP_PluginDescription'), - 'author' => 'Piwik', - 'author_homepage' => 'http://piwik.org/', - 'version' => Piwik_Version::VERSION, - 'TrackerPlugin' => true, - ); - } + /** + * Get plugin information + * @return array + */ + public function getInformation() + { + return array( + 'description' => Piwik_Translate('AnonymizeIP_PluginDescription'), + 'author' => 'Piwik', + 'author_homepage' => 'http://piwik.org/', + 'version' => Piwik_Version::VERSION, + 'TrackerPlugin' => true, + ); + } - /** - * Get list of hooks to register - * @return array - */ - public function getListHooksRegistered() - { - return array( - 'Tracker.Visit.setVisitorIp' => 'setVisitorIpAddress', - ); - } + /** + * Get list of hooks to register + * @return array + */ + public function getListHooksRegistered() + { + return array( + 'Tracker.Visit.setVisitorIp' => 'setVisitorIpAddress', + ); + } - /** - * Internal function to mask portions of the visitor IP address - * - * @param string $ip IP address in network address format - * @param int $maskLength Number of octets to reset - * @return string - */ - static public function applyIPMask($ip, $maskLength) - { - $i = Piwik_Common::strlen($ip); - if($maskLength > $i) - { - $maskLength = $i; - } + /** + * Internal function to mask portions of the visitor IP address + * + * @param string $ip IP address in network address format + * @param int $maskLength Number of octets to reset + * @return string + */ + static public function applyIPMask($ip, $maskLength) + { + $i = Piwik_Common::strlen($ip); + if ($maskLength > $i) { + $maskLength = $i; + } - while($maskLength-- > 0) - { - $ip[--$i] = chr(0); - } + while ($maskLength-- > 0) { + $ip[--$i] = chr(0); + } - return $ip; - } + return $ip; + } - /** - * Hook on Tracker.Visit.setVisitorIp to anonymize visitor IP addresses - * - * @param Piwik_Event_Notification $notification notification object - */ - function setVisitorIpAddress($notification) - { - $ip =& $notification->getNotificationObject(); - $ip = self::applyIPMask($ip, Piwik_Config::getInstance()->Tracker['ip_address_mask_length']); - } + /** + * Hook on Tracker.Visit.setVisitorIp to anonymize visitor IP addresses + * + * @param Piwik_Event_Notification $notification notification object + */ + function setVisitorIpAddress($notification) + { + $ip =& $notification->getNotificationObject(); + $ip = self::applyIPMask($ip, Piwik_Config::getInstance()->Tracker['ip_address_mask_length']); + } } diff --git a/plugins/CoreAdminHome/API.php b/plugins/CoreAdminHome/API.php index 97842dd943..eded6d451f 100644 --- a/plugins/CoreAdminHome/API.php +++ b/plugins/CoreAdminHome/API.php @@ -1,10 +1,10 @@ <?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_CoreAdminHome */ @@ -12,222 +12,211 @@ /** * @package Piwik_CoreAdminHome */ -class Piwik_CoreAdminHome_API +class Piwik_CoreAdminHome_API { - static private $instance = null; - /** - * @return Piwik_CoreAdminHome_API - */ - static public function getInstance() - { - if (self::$instance == null) - { - self::$instance = new self; - } - return self::$instance; - } - - /** - * Will run all scheduled tasks due to run at this time. - * - * @return array - */ - public function runScheduledTasks() - { - Piwik::checkUserIsSuperUser(); - return Piwik_TaskScheduler::runTasks(); - } - - public function getKnownSegmentsToArchive() - { - Piwik::checkUserIsSuperUser(); - return Piwik::getKnownSegmentsToArchive(); - } - - /* - * stores the list of websites IDs to re-reprocess in archive.php - */ - const OPTION_INVALIDATED_IDSITES = 'InvalidatedOldReports_WebsiteIds'; - - /** - * When tracking data in the past (using Tracking API), this function - * can be used to invalidate reports for the idSites and dates where new data - * was added. - * DEV: If you call this API, the UI should display the data correctly, but will process - * in real time, which could be very slow after large data imports. - * After calling this function via REST, you can manually force all data - * to be reprocessed by visiting the script as the Super User: - * http://example.net/piwik/misc/cron/archive.php?token_auth=$SUPER_USER_TOKEN_AUTH_HERE - * REQUIREMENTS: On large piwik setups, you will need in PHP configuration: max_execution_time = 0 - * We recommend to use an hourly schedule of the script at misc/cron/archive.php - * More information: http://piwik.org/setup-auto-archiving/ - * - * @param string $idSites Comma separated list of idSite that have had data imported for the specified dates - * @param string $dates Comma separated list of dates to invalidate for all these websites - * @return array - */ - public function invalidateArchivedReports($idSites, $dates) - { - $idSites = Piwik_Site::getIdSitesFromIdSitesString($idSites); - if(empty($idSites)) { - throw new Exception("Specify a value for &idSites= as a comma separated list of website IDs, for which your token_auth has 'admin' permission"); - } - Piwik::checkUserHasAdminAccess($idSites); - - // Ensure the specified dates are valid - $toInvalidate = $invalidDates = array(); - $dates = explode(',', $dates); - $dates = array_unique($dates); - foreach($dates as $theDate) - { - try { - $date = Piwik_Date::factory($theDate); - } catch(Exception $e) { - $invalidDates[] = $theDate; - continue; - } - if($date->toString() == $theDate) - { - $toInvalidate[] = $date; - } - else - { - $invalidDates[] = $theDate; - } - } - - // 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']; - $logsDeleteEnabled = Piwik_Config::getInstance()->Deletelogs['delete_logs_enable']; - $minimumDateWithLogs = false; - if($logsDeleteEnabled - && $logsAreDeletedBeforeThisDate) - { - $minimumDateWithLogs = Piwik_Date::factory('today')->subDay($logsAreDeletedBeforeThisDate); - } - - // Given the list of dates, process which tables they should be deleted from - $minDate = false; - $warningDates = $processedDates = array(); - /* @var $date Piwik_Date */ - foreach($toInvalidate as $date) - { - // we should only delete reports for dates that are more recent than N days - if($minimumDateWithLogs - && $date->isEarlier($minimumDateWithLogs)) - { - $warningDates[] = $date->toString(); - } - else - { - $processedDates[] = $date->toString(); - } - - $month = $date->toString('Y_m'); - // For a given date, we must invalidate in the monthly archive table - $datesByMonth[$month][] = $date->toString(); - - // But also the year stored in January - $year = $date->toString('Y_01'); - $datesByMonth[$year][] = $date->toString(); - - // but also weeks overlapping several months stored in the month where the week is starting - /* @var $week Piwik_Period_Week */ - $week = Piwik_Period::factory('week', $date); - $week = $week->getDateStart()->toString('Y_m'); - $datesByMonth[$week][] = $date->toString(); - - // Keep track of the minimum date for each website - if($minDate === false - || $date->isEarlier($minDate)) - { - $minDate = $date; - } - } - - // In each table, invalidate day/week/month/year containing this date - $sqlIdSites = implode(",", $idSites); - foreach($archiveTables as $table) - { - // Extract Y_m from table name - $suffix = str_replace(array('archive_numeric_','archive_blob_'), '', Piwik_Common::unprefixTable($table)); - - if(!isset($datesByMonth[$suffix])) - { - continue; - } - // Dates which are to be deleted from this table - $datesToDeleteInTable = $datesByMonth[$suffix]; - - // Build one statement to delete all dates from the given table - $sql = $bind = array(); - $datesToDeleteInTable = array_unique($datesToDeleteInTable); - foreach($datesToDeleteInTable as $dateToDelete) - { - $sql[] = '(date1 <= ? AND ? <= date2)'; - $bind[] = $dateToDelete; - $bind[] = $dateToDelete; - } - $sql = implode(" OR ", $sql); - - $query = "DELETE FROM $table ". - " WHERE ( $sql ) ". - " AND idsite IN (". $sqlIdSites .")"; - Piwik_Query($query, $bind); - } - - // Update piwik_site.ts_created - $query = "UPDATE " . Piwik_Common::prefixTable("site") . - " SET ts_created = ?". - " WHERE idsite IN ( $sqlIdSites ) + static private $instance = null; + + /** + * @return Piwik_CoreAdminHome_API + */ + static public function getInstance() + { + if (self::$instance == null) { + self::$instance = new self; + } + return self::$instance; + } + + /** + * Will run all scheduled tasks due to run at this time. + * + * @return array + */ + public function runScheduledTasks() + { + Piwik::checkUserIsSuperUser(); + return Piwik_TaskScheduler::runTasks(); + } + + public function getKnownSegmentsToArchive() + { + Piwik::checkUserIsSuperUser(); + return Piwik::getKnownSegmentsToArchive(); + } + + /* + * stores the list of websites IDs to re-reprocess in archive.php + */ + const OPTION_INVALIDATED_IDSITES = 'InvalidatedOldReports_WebsiteIds'; + + /** + * When tracking data in the past (using Tracking API), this function + * can be used to invalidate reports for the idSites and dates where new data + * was added. + * DEV: If you call this API, the UI should display the data correctly, but will process + * in real time, which could be very slow after large data imports. + * After calling this function via REST, you can manually force all data + * to be reprocessed by visiting the script as the Super User: + * http://example.net/piwik/misc/cron/archive.php?token_auth=$SUPER_USER_TOKEN_AUTH_HERE + * REQUIREMENTS: On large piwik setups, you will need in PHP configuration: max_execution_time = 0 + * We recommend to use an hourly schedule of the script at misc/cron/archive.php + * More information: http://piwik.org/setup-auto-archiving/ + * + * @param string $idSites Comma separated list of idSite that have had data imported for the specified dates + * @param string $dates Comma separated list of dates to invalidate for all these websites + * @return array + */ + public function invalidateArchivedReports($idSites, $dates) + { + $idSites = Piwik_Site::getIdSitesFromIdSitesString($idSites); + if (empty($idSites)) { + throw new Exception("Specify a value for &idSites= as a comma separated list of website IDs, for which your token_auth has 'admin' permission"); + } + Piwik::checkUserHasAdminAccess($idSites); + + // Ensure the specified dates are valid + $toInvalidate = $invalidDates = array(); + $dates = explode(',', $dates); + $dates = array_unique($dates); + foreach ($dates as $theDate) { + try { + $date = Piwik_Date::factory($theDate); + } catch (Exception $e) { + $invalidDates[] = $theDate; + continue; + } + if ($date->toString() == $theDate) { + $toInvalidate[] = $date; + } else { + $invalidDates[] = $theDate; + } + } + + // 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']; + $logsDeleteEnabled = Piwik_Config::getInstance()->Deletelogs['delete_logs_enable']; + $minimumDateWithLogs = false; + if ($logsDeleteEnabled + && $logsAreDeletedBeforeThisDate + ) { + $minimumDateWithLogs = Piwik_Date::factory('today')->subDay($logsAreDeletedBeforeThisDate); + } + + // Given the list of dates, process which tables they should be deleted from + $minDate = false; + $warningDates = $processedDates = array(); + /* @var $date Piwik_Date */ + foreach ($toInvalidate as $date) { + // we should only delete reports for dates that are more recent than N days + if ($minimumDateWithLogs + && $date->isEarlier($minimumDateWithLogs) + ) { + $warningDates[] = $date->toString(); + } else { + $processedDates[] = $date->toString(); + } + + $month = $date->toString('Y_m'); + // For a given date, we must invalidate in the monthly archive table + $datesByMonth[$month][] = $date->toString(); + + // But also the year stored in January + $year = $date->toString('Y_01'); + $datesByMonth[$year][] = $date->toString(); + + // but also weeks overlapping several months stored in the month where the week is starting + /* @var $week Piwik_Period_Week */ + $week = Piwik_Period::factory('week', $date); + $week = $week->getDateStart()->toString('Y_m'); + $datesByMonth[$week][] = $date->toString(); + + // Keep track of the minimum date for each website + if ($minDate === false + || $date->isEarlier($minDate) + ) { + $minDate = $date; + } + } + + // In each table, invalidate day/week/month/year containing this date + $sqlIdSites = implode(",", $idSites); + foreach ($archiveTables as $table) { + // Extract Y_m from table name + $suffix = str_replace(array('archive_numeric_', 'archive_blob_'), '', Piwik_Common::unprefixTable($table)); + + if (!isset($datesByMonth[$suffix])) { + continue; + } + // Dates which are to be deleted from this table + $datesToDeleteInTable = $datesByMonth[$suffix]; + + // Build one statement to delete all dates from the given table + $sql = $bind = array(); + $datesToDeleteInTable = array_unique($datesToDeleteInTable); + foreach ($datesToDeleteInTable as $dateToDelete) { + $sql[] = '(date1 <= ? AND ? <= date2)'; + $bind[] = $dateToDelete; + $bind[] = $dateToDelete; + } + $sql = implode(" OR ", $sql); + + $query = "DELETE FROM $table " . + " WHERE ( $sql ) " . + " AND idsite IN (" . $sqlIdSites . ")"; + Piwik_Query($query, $bind); + } + + // Update piwik_site.ts_created + $query = "UPDATE " . Piwik_Common::prefixTable("site") . + " SET ts_created = ?" . + " WHERE idsite IN ( $sqlIdSites ) AND ts_created > ?"; - $minDateSql = $minDate->subDay(1)->getDatetime(); - $bind = array($minDateSql,$minDateSql); - Piwik_Query($query, $bind); - - // Force to re-process data for these websites in the next archive.php cron run - $invalidatedIdSites = Piwik_CoreAdminHome_API::getWebsiteIdsToInvalidate(); - $invalidatedIdSites = array_merge($invalidatedIdSites, $idSites); - $invalidatedIdSites = array_unique($invalidatedIdSites); - $invalidatedIdSites = array_values($invalidatedIdSites); - Piwik_SetOption(self::OPTION_INVALIDATED_IDSITES, serialize($invalidatedIdSites)); - - Piwik_Site::clearCache(); - - $output = array(); - // output logs - if($warningDates) - { - $output[] = 'Warning: the following Dates have not been invalidated, because they are earlier than your Log Deletion limit: '. - implode(", ", $warningDates). - "\n The last day with logs is " . $minimumDateWithLogs. ". ". - "\n Please disable 'Delete old Logs' or set it to a higher deletion threshold (eg. 180 days or 365 years).'."; - } - $output[] = "Success. The following dates were invalidated successfully: ". - implode(", ", $processedDates); - return $output; - } - - /** - * Returns array of idSites to force re-process next time archive.php runs - * - * @ignore - * @return mixed - */ - static public function getWebsiteIdsToInvalidate() - { - Piwik::checkUserHasSomeAdminAccess(); - $invalidatedIdSites = Piwik_GetOption(self::OPTION_INVALIDATED_IDSITES); - if($invalidatedIdSites - && ($invalidatedIdSites = unserialize($invalidatedIdSites)) - && count($invalidatedIdSites)) - { - return $invalidatedIdSites; - } - return array(); - } + $minDateSql = $minDate->subDay(1)->getDatetime(); + $bind = array($minDateSql, $minDateSql); + Piwik_Query($query, $bind); + + // Force to re-process data for these websites in the next archive.php cron run + $invalidatedIdSites = Piwik_CoreAdminHome_API::getWebsiteIdsToInvalidate(); + $invalidatedIdSites = array_merge($invalidatedIdSites, $idSites); + $invalidatedIdSites = array_unique($invalidatedIdSites); + $invalidatedIdSites = array_values($invalidatedIdSites); + Piwik_SetOption(self::OPTION_INVALIDATED_IDSITES, serialize($invalidatedIdSites)); + + Piwik_Site::clearCache(); + + $output = array(); + // output logs + if ($warningDates) { + $output[] = 'Warning: the following Dates have not been invalidated, because they are earlier than your Log Deletion limit: ' . + implode(", ", $warningDates) . + "\n The last day with logs is " . $minimumDateWithLogs . ". " . + "\n Please disable 'Delete old Logs' or set it to a higher deletion threshold (eg. 180 days or 365 years).'."; + } + $output[] = "Success. The following dates were invalidated successfully: " . + implode(", ", $processedDates); + return $output; + } + + /** + * Returns array of idSites to force re-process next time archive.php runs + * + * @ignore + * @return mixed + */ + static public function getWebsiteIdsToInvalidate() + { + Piwik::checkUserHasSomeAdminAccess(); + $invalidatedIdSites = Piwik_GetOption(self::OPTION_INVALIDATED_IDSITES); + if ($invalidatedIdSites + && ($invalidatedIdSites = unserialize($invalidatedIdSites)) + && count($invalidatedIdSites) + ) { + return $invalidatedIdSites; + } + return array(); + } } diff --git a/plugins/CoreAdminHome/Controller.php b/plugins/CoreAdminHome/Controller.php index f5e5d83a9c..c915205bc2 100644 --- a/plugins/CoreAdminHome/Controller.php +++ b/plugins/CoreAdminHome/Controller.php @@ -1,10 +1,10 @@ <?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_CoreAdminHome */ @@ -15,228 +15,218 @@ */ class Piwik_CoreAdminHome_Controller extends Piwik_Controller_Admin { - const LOGO_HEIGHT = 300; - const LOGO_SMALL_HEIGHT = 100; - - public function index() - { - return $this->redirectToIndex('UsersManager', 'userSettings'); - } - - public function generalSettings() - { - Piwik::checkUserHasSomeAdminAccess(); - $view = Piwik_View::factory('generalSettings'); - - if(Piwik::isUserIsSuperUser()) - { - $enableBrowserTriggerArchiving = Piwik_ArchiveProcessing::isBrowserTriggerArchivingEnabled(); - $todayArchiveTimeToLive = Piwik_ArchiveProcessing::getTodayArchiveTimeToLive(); - $showWarningCron = false; - if(!$enableBrowserTriggerArchiving - && $todayArchiveTimeToLive < 3600) - { - $showWarningCron = true; - } - $view->showWarningCron = $showWarningCron; - $view->todayArchiveTimeToLive = $todayArchiveTimeToLive; - $view->enableBrowserTriggerArchiving = $enableBrowserTriggerArchiving; - - $config = Piwik_Config::getInstance(); - - if(!$config->isFileWritable()) - { - $view->configFileNotWritable = true; - } - - $debug = $config->Debug; - $view->enableBetaReleaseCheck = $debug['allow_upgrades_to_beta']; - - $view->mail = $config->mail; - - $view->branding = $config->branding; - - $directoryWritable = is_writable(PIWIK_DOCUMENT_ROOT.'/themes/'); - $logoFilesWriteable = is_writeable(PIWIK_DOCUMENT_ROOT.'/themes/logo.png') && is_writeable(PIWIK_DOCUMENT_ROOT.'/themes/logo-header.png'); - $view->logosWriteable = ($logoFilesWriteable || $directoryWritable) && ini_get('file_uploads') == 1; - - $trustedHosts = array(); - if (isset($config->General['trusted_hosts'])) - { - $trustedHosts = $config->General['trusted_hosts']; - } - $view->trustedHosts = $trustedHosts; - } - - $view->language = Piwik_LanguagesManager::getLanguageCodeForCurrentUser(); - $this->setBasicVariablesView($view); - $view->topMenu = Piwik_GetTopMenu(); - $view->menu = Piwik_GetAdminMenu(); - echo $view->render(); - } - - public function setGeneralSettings() - { - Piwik::checkUserIsSuperUser(); - $response = new Piwik_API_ResponseBuilder(Piwik_Common::getRequestVar('format')); - try { - $this->checkTokenInUrl(); - $enableBrowserTriggerArchiving = Piwik_Common::getRequestVar('enableBrowserTriggerArchiving'); - $todayArchiveTimeToLive = Piwik_Common::getRequestVar('todayArchiveTimeToLive'); - - Piwik_ArchiveProcessing::setBrowserTriggerArchiving((bool)$enableBrowserTriggerArchiving); - Piwik_ArchiveProcessing::setTodayArchiveTimeToLive($todayArchiveTimeToLive); - - // Update email settings - $mail = array(); - $mail['transport'] = (Piwik_Common::getRequestVar('mailUseSmtp') == '1') ? 'smtp' : ''; - $mail['port'] = Piwik_Common::getRequestVar('mailPort', ''); - $mail['host'] = Piwik_Common::unsanitizeInputValue(Piwik_Common::getRequestVar('mailHost', '')); - $mail['type'] = Piwik_Common::getRequestVar('mailType', ''); - $mail['username'] = Piwik_Common::unsanitizeInputValue(Piwik_Common::getRequestVar('mailUsername', '')); - $mail['password'] = Piwik_Common::unsanitizeInputValue(Piwik_Common::getRequestVar('mailPassword', '')); - $mail['encryption'] = Piwik_Common::getRequestVar('mailEncryption', ''); - - $config = Piwik_Config::getInstance(); - $config->mail = $mail; - - // update branding settings - $branding = $config->branding; - $branding['use_custom_logo'] = Piwik_Common::getRequestVar('useCustomLogo', '0'); - $config->branding = $branding; - - // update beta channel setting - $debug = $config->Debug; - $debug['allow_upgrades_to_beta'] = Piwik_Common::getRequestVar('enableBetaReleaseCheck', '0', 'int'); - $config->Debug = $debug; - // update trusted host settings - $trustedHosts = Piwik_Common::getRequestVar('trustedHosts', false, 'json'); - if ($trustedHosts !== false) - { - Piwik_Url::saveTrustedHostnameInConfig($trustedHosts); - } - - $config->forceSave(); - - $toReturn = $response->getResponse(); - } catch(Exception $e ) { - $toReturn = $response->getResponseException( $e ); - } - echo $toReturn; - } - - /** - * Renders and echo's an admin page that lets users generate custom JavaScript - * tracking code and custom image tracker links. - */ - public function trackingCodeGenerator() - { - $view = Piwik_View::factory('jsTrackingGenerator'); - $this->setBasicVariablesView($view); - $view->topMenu = Piwik_GetTopMenu(); - $view->menu = Piwik_GetAdminMenu(); - - $viewableIdSites = Piwik_SitesManager_API::getInstance()->getSitesIdWithAtLeastViewAccess(); - - $defaultIdSite = reset($viewableIdSites); - $view->idSite = Piwik_Common::getRequestVar('idSite', $defaultIdSite, 'int'); - - $view->defaultReportSiteName = Piwik_Site::getNameFor($view->idSite); - $view->defaultSiteRevenue = Piwik::getCurrency($view->idSite); - - $allUrls = Piwik_SitesManager_API::getInstance()->getSiteUrlsFromId($view->idSite); - if (isset($allUrls[1])) - { - $aliasUrl = $allUrls[1]; - } - else - { - $aliasUrl = 'x.domain.com'; - } - $view->defaultReportSiteAlias = $aliasUrl; - - $mainUrl = Piwik_Site::getMainUrlFor($view->idSite); - $view->defaultReportSiteDomain = @parse_url($mainUrl, PHP_URL_HOST); - - // get currencies for each viewable site - $view->currencySymbols = Piwik_SitesManager_API::getInstance()->getCurrencySymbols(); - - $view->serverSideDoNotTrackEnabled = Piwik_PrivacyManager_Controller::isDntSupported(); - - echo $view->render(); - } - - /** - * Shows the "Track Visits" checkbox. - */ - public function optOut() - { - $trackVisits = !Piwik_Tracker_IgnoreCookie::isIgnoreCookieFound(); - - $nonce = Piwik_Common::getRequestVar('nonce', false); - $language = Piwik_Common::getRequestVar('language', ''); - if($nonce !== false && Piwik_Nonce::verifyNonce('Piwik_OptOut', $nonce)) - { - Piwik_Nonce::discardNonce('Piwik_OptOut'); - Piwik_Tracker_IgnoreCookie::setIgnoreCookie(); - $trackVisits = !$trackVisits; - } - - $view = Piwik_View::factory('optOut'); - $view->trackVisits = $trackVisits; - $view->nonce = Piwik_Nonce::getNonce('Piwik_OptOut', 3600); - $view->language = Piwik_LanguagesManager_API::getInstance()->isLanguageAvailable($language) - ? $language - : Piwik_LanguagesManager::getLanguageCodeForCurrentUser(); - echo $view->render(); - } - - public function uploadCustomLogo() - { - Piwik::checkUserIsSuperUser(); - if(empty($_FILES['customLogo']) - || !empty($_FILES['customLogo']['error']) - ) - { - echo '0'; - return; - } - - $file = $_FILES['customLogo']['tmp_name']; - if(!file_exists($file)) - { - echo '0'; - return; - } - $error = false; - - list($width, $height) = getimagesize($file); - switch($_FILES['customLogo']['type']) { - case 'image/jpeg': - $image = imagecreatefromjpeg($file); - break; - case 'image/png': - $image = imagecreatefrompng($file); - break; - case 'image/gif': - $image = imagecreatefromgif($file); - break; - default: - echo '0'; - return; - } - - $widthExpected = round($width * self::LOGO_HEIGHT / $height); - $smallWidthExpected = round($width * self::LOGO_SMALL_HEIGHT / $height); - - $logo = imagecreatetruecolor($widthExpected, self::LOGO_HEIGHT); - $logoSmall = imagecreatetruecolor($smallWidthExpected, self::LOGO_SMALL_HEIGHT); - imagecopyresized($logo, $image, 0, 0, 0, 0, $widthExpected, self::LOGO_HEIGHT, $width, $height); - imagecopyresized($logoSmall, $image, 0, 0, 0, 0, $smallWidthExpected, self::LOGO_SMALL_HEIGHT, $width, $height); - - imagepng($logo, PIWIK_DOCUMENT_ROOT.'/themes/logo.png', 3); - imagepng($logoSmall, PIWIK_DOCUMENT_ROOT.'/themes/logo-header.png', 3); - echo '1'; - return; - } + const LOGO_HEIGHT = 300; + const LOGO_SMALL_HEIGHT = 100; + + public function index() + { + return $this->redirectToIndex('UsersManager', 'userSettings'); + } + + public function generalSettings() + { + Piwik::checkUserHasSomeAdminAccess(); + $view = Piwik_View::factory('generalSettings'); + + if (Piwik::isUserIsSuperUser()) { + $enableBrowserTriggerArchiving = Piwik_ArchiveProcessing::isBrowserTriggerArchivingEnabled(); + $todayArchiveTimeToLive = Piwik_ArchiveProcessing::getTodayArchiveTimeToLive(); + $showWarningCron = false; + if (!$enableBrowserTriggerArchiving + && $todayArchiveTimeToLive < 3600 + ) { + $showWarningCron = true; + } + $view->showWarningCron = $showWarningCron; + $view->todayArchiveTimeToLive = $todayArchiveTimeToLive; + $view->enableBrowserTriggerArchiving = $enableBrowserTriggerArchiving; + + $config = Piwik_Config::getInstance(); + + if (!$config->isFileWritable()) { + $view->configFileNotWritable = true; + } + + $debug = $config->Debug; + $view->enableBetaReleaseCheck = $debug['allow_upgrades_to_beta']; + + $view->mail = $config->mail; + + $view->branding = $config->branding; + + $directoryWritable = is_writable(PIWIK_DOCUMENT_ROOT . '/themes/'); + $logoFilesWriteable = is_writeable(PIWIK_DOCUMENT_ROOT . '/themes/logo.png') && is_writeable(PIWIK_DOCUMENT_ROOT . '/themes/logo-header.png'); + $view->logosWriteable = ($logoFilesWriteable || $directoryWritable) && ini_get('file_uploads') == 1; + + $trustedHosts = array(); + if (isset($config->General['trusted_hosts'])) { + $trustedHosts = $config->General['trusted_hosts']; + } + $view->trustedHosts = $trustedHosts; + } + + $view->language = Piwik_LanguagesManager::getLanguageCodeForCurrentUser(); + $this->setBasicVariablesView($view); + $view->topMenu = Piwik_GetTopMenu(); + $view->menu = Piwik_GetAdminMenu(); + echo $view->render(); + } + + public function setGeneralSettings() + { + Piwik::checkUserIsSuperUser(); + $response = new Piwik_API_ResponseBuilder(Piwik_Common::getRequestVar('format')); + try { + $this->checkTokenInUrl(); + $enableBrowserTriggerArchiving = Piwik_Common::getRequestVar('enableBrowserTriggerArchiving'); + $todayArchiveTimeToLive = Piwik_Common::getRequestVar('todayArchiveTimeToLive'); + + Piwik_ArchiveProcessing::setBrowserTriggerArchiving((bool)$enableBrowserTriggerArchiving); + Piwik_ArchiveProcessing::setTodayArchiveTimeToLive($todayArchiveTimeToLive); + + // Update email settings + $mail = array(); + $mail['transport'] = (Piwik_Common::getRequestVar('mailUseSmtp') == '1') ? 'smtp' : ''; + $mail['port'] = Piwik_Common::getRequestVar('mailPort', ''); + $mail['host'] = Piwik_Common::unsanitizeInputValue(Piwik_Common::getRequestVar('mailHost', '')); + $mail['type'] = Piwik_Common::getRequestVar('mailType', ''); + $mail['username'] = Piwik_Common::unsanitizeInputValue(Piwik_Common::getRequestVar('mailUsername', '')); + $mail['password'] = Piwik_Common::unsanitizeInputValue(Piwik_Common::getRequestVar('mailPassword', '')); + $mail['encryption'] = Piwik_Common::getRequestVar('mailEncryption', ''); + + $config = Piwik_Config::getInstance(); + $config->mail = $mail; + + // update branding settings + $branding = $config->branding; + $branding['use_custom_logo'] = Piwik_Common::getRequestVar('useCustomLogo', '0'); + $config->branding = $branding; + + // update beta channel setting + $debug = $config->Debug; + $debug['allow_upgrades_to_beta'] = Piwik_Common::getRequestVar('enableBetaReleaseCheck', '0', 'int'); + $config->Debug = $debug; + // update trusted host settings + $trustedHosts = Piwik_Common::getRequestVar('trustedHosts', false, 'json'); + if ($trustedHosts !== false) { + Piwik_Url::saveTrustedHostnameInConfig($trustedHosts); + } + + $config->forceSave(); + + $toReturn = $response->getResponse(); + } catch (Exception $e) { + $toReturn = $response->getResponseException($e); + } + echo $toReturn; + } + + /** + * Renders and echo's an admin page that lets users generate custom JavaScript + * tracking code and custom image tracker links. + */ + public function trackingCodeGenerator() + { + $view = Piwik_View::factory('jsTrackingGenerator'); + $this->setBasicVariablesView($view); + $view->topMenu = Piwik_GetTopMenu(); + $view->menu = Piwik_GetAdminMenu(); + + $viewableIdSites = Piwik_SitesManager_API::getInstance()->getSitesIdWithAtLeastViewAccess(); + + $defaultIdSite = reset($viewableIdSites); + $view->idSite = Piwik_Common::getRequestVar('idSite', $defaultIdSite, 'int'); + + $view->defaultReportSiteName = Piwik_Site::getNameFor($view->idSite); + $view->defaultSiteRevenue = Piwik::getCurrency($view->idSite); + + $allUrls = Piwik_SitesManager_API::getInstance()->getSiteUrlsFromId($view->idSite); + if (isset($allUrls[1])) { + $aliasUrl = $allUrls[1]; + } else { + $aliasUrl = 'x.domain.com'; + } + $view->defaultReportSiteAlias = $aliasUrl; + + $mainUrl = Piwik_Site::getMainUrlFor($view->idSite); + $view->defaultReportSiteDomain = @parse_url($mainUrl, PHP_URL_HOST); + + // get currencies for each viewable site + $view->currencySymbols = Piwik_SitesManager_API::getInstance()->getCurrencySymbols(); + + $view->serverSideDoNotTrackEnabled = Piwik_PrivacyManager_Controller::isDntSupported(); + + echo $view->render(); + } + + /** + * Shows the "Track Visits" checkbox. + */ + public function optOut() + { + $trackVisits = !Piwik_Tracker_IgnoreCookie::isIgnoreCookieFound(); + + $nonce = Piwik_Common::getRequestVar('nonce', false); + $language = Piwik_Common::getRequestVar('language', ''); + if ($nonce !== false && Piwik_Nonce::verifyNonce('Piwik_OptOut', $nonce)) { + Piwik_Nonce::discardNonce('Piwik_OptOut'); + Piwik_Tracker_IgnoreCookie::setIgnoreCookie(); + $trackVisits = !$trackVisits; + } + + $view = Piwik_View::factory('optOut'); + $view->trackVisits = $trackVisits; + $view->nonce = Piwik_Nonce::getNonce('Piwik_OptOut', 3600); + $view->language = Piwik_LanguagesManager_API::getInstance()->isLanguageAvailable($language) + ? $language + : Piwik_LanguagesManager::getLanguageCodeForCurrentUser(); + echo $view->render(); + } + + public function uploadCustomLogo() + { + Piwik::checkUserIsSuperUser(); + if (empty($_FILES['customLogo']) + || !empty($_FILES['customLogo']['error']) + ) { + echo '0'; + return; + } + + $file = $_FILES['customLogo']['tmp_name']; + if (!file_exists($file)) { + echo '0'; + return; + } + $error = false; + + list($width, $height) = getimagesize($file); + switch ($_FILES['customLogo']['type']) { + case 'image/jpeg': + $image = imagecreatefromjpeg($file); + break; + case 'image/png': + $image = imagecreatefrompng($file); + break; + case 'image/gif': + $image = imagecreatefromgif($file); + break; + default: + echo '0'; + return; + } + + $widthExpected = round($width * self::LOGO_HEIGHT / $height); + $smallWidthExpected = round($width * self::LOGO_SMALL_HEIGHT / $height); + + $logo = imagecreatetruecolor($widthExpected, self::LOGO_HEIGHT); + $logoSmall = imagecreatetruecolor($smallWidthExpected, self::LOGO_SMALL_HEIGHT); + imagecopyresized($logo, $image, 0, 0, 0, 0, $widthExpected, self::LOGO_HEIGHT, $width, $height); + imagecopyresized($logoSmall, $image, 0, 0, 0, 0, $smallWidthExpected, self::LOGO_SMALL_HEIGHT, $width, $height); + + imagepng($logo, PIWIK_DOCUMENT_ROOT . '/themes/logo.png', 3); + imagepng($logoSmall, PIWIK_DOCUMENT_ROOT . '/themes/logo-header.png', 3); + echo '1'; + return; + } } diff --git a/plugins/CoreAdminHome/CoreAdminHome.php b/plugins/CoreAdminHome/CoreAdminHome.php index 7dbf1b6ee9..3164811cca 100644 --- a/plugins/CoreAdminHome/CoreAdminHome.php +++ b/plugins/CoreAdminHome/CoreAdminHome.php @@ -1,10 +1,10 @@ <?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_CoreAdminHome */ @@ -15,114 +15,112 @@ */ class Piwik_CoreAdminHome extends Piwik_Plugin { - public function getInformation() - { - return array( - 'description' => Piwik_Translate('CoreAdminHome_PluginDescription'), - 'author' => 'Piwik', - 'author_homepage' => 'http://piwik.org/', - 'version' => Piwik_Version::VERSION, - ); - } + public function getInformation() + { + return array( + 'description' => Piwik_Translate('CoreAdminHome_PluginDescription'), + 'author' => 'Piwik', + 'author_homepage' => 'http://piwik.org/', + 'version' => Piwik_Version::VERSION, + ); + } + + public function getListHooksRegistered() + { + return array( + 'AssetManager.getCssFiles' => 'getCssFiles', + 'AssetManager.getJsFiles' => 'getJsFiles', + 'AdminMenu.add' => 'addMenu', + 'TaskScheduler.getScheduledTasks' => 'getScheduledTasks', + ); + } + + /** + * @param Piwik_Event_Notification $notification notification object + */ + function getScheduledTasks($notification) + { + $tasks = & $notification->getNotificationObject(); + + // general data purge on older archive tables, executed daily + $purgeArchiveTablesTask = new Piwik_ScheduledTask ($this, + 'purgeOutdatedArchives', + null, + new Piwik_ScheduledTime_Daily(), + Piwik_ScheduledTask::HIGH_PRIORITY); + $tasks[] = $purgeArchiveTablesTask; + + // lowest priority since tables should be optimized after they are modified + $optimizeArchiveTableTask = new Piwik_ScheduledTask ($this, + 'optimizeArchiveTable', + null, + new Piwik_ScheduledTime_Daily(), + Piwik_ScheduledTask::LOWEST_PRIORITY); + $tasks[] = $optimizeArchiveTableTask; + } + + /** + * @param Piwik_Event_Notification $notification notification object + */ + function getCssFiles($notification) + { + $cssFiles = & $notification->getNotificationObject(); + + $cssFiles[] = "libs/jquery/themes/base/jquery-ui.css"; + $cssFiles[] = "plugins/CoreAdminHome/templates/menu.css"; + $cssFiles[] = "themes/default/common.css"; + $cssFiles[] = "plugins/CoreAdminHome/templates/styles.css"; + $cssFiles[] = "plugins/CoreHome/templates/donate.css"; + } + + /** + * @param Piwik_Event_Notification $notification notification object + */ + function getJsFiles($notification) + { + $jsFiles = & $notification->getNotificationObject(); + + $jsFiles[] = "libs/jquery/jquery.js"; + $jsFiles[] = "libs/jquery/jquery-ui.js"; + $jsFiles[] = "libs/javascript/sprintf.js"; + $jsFiles[] = "themes/default/common.js"; + $jsFiles[] = "themes/default/ajaxHelper.js"; + $jsFiles[] = "libs/jquery/jquery.history.js"; + $jsFiles[] = "plugins/CoreHome/templates/broadcast.js"; + $jsFiles[] = "plugins/CoreAdminHome/templates/generalSettings.js"; + $jsFiles[] = "plugins/CoreHome/templates/donate.js"; + } - public function getListHooksRegistered() - { - return array( - 'AssetManager.getCssFiles' => 'getCssFiles', - 'AssetManager.getJsFiles' => 'getJsFiles', - 'AdminMenu.add' => 'addMenu', - 'TaskScheduler.getScheduledTasks' => 'getScheduledTasks', - ); - } + function addMenu() + { + Piwik_AddAdminSubMenu('CoreAdminHome_MenuManage', NULL, "", Piwik::isUserHasSomeAdminAccess(), $order = 1); + Piwik_AddAdminSubMenu('CoreAdminHome_MenuCommunity', NULL, "", Piwik::isUserHasSomeAdminAccess(), $order = 3); + Piwik_AddAdminSubMenu('CoreAdminHome_MenuDiagnostic', NULL, "", Piwik::isUserHasSomeAdminAccess(), $order = 20); + Piwik_AddAdminSubMenu('General_Settings', NULL, "", Piwik::isUserHasSomeAdminAccess(), $order = 5); + Piwik_AddAdminSubMenu('General_Settings', 'CoreAdminHome_MenuGeneralSettings', + array('module' => 'CoreAdminHome', 'action' => 'generalSettings'), + Piwik::isUserHasSomeAdminAccess(), + $order = 6); + Piwik_AddAdminSubMenu('CoreAdminHome_MenuManage', 'CoreAdminHome_TrackingCode', + array('module' => 'CoreAdminHome', 'action' => 'trackingCodeGenerator'), + Piwik::isUserHasSomeAdminAccess(), + $order = 4); - /** - * @param Piwik_Event_Notification $notification notification object - */ - function getScheduledTasks ( $notification ) - { - $tasks = &$notification->getNotificationObject(); - - // general data purge on older archive tables, executed daily - $purgeArchiveTablesTask = new Piwik_ScheduledTask ( $this, - 'purgeOutdatedArchives', - null, - new Piwik_ScheduledTime_Daily(), - Piwik_ScheduledTask::HIGH_PRIORITY); - $tasks[] = $purgeArchiveTablesTask; - - // lowest priority since tables should be optimized after they are modified - $optimizeArchiveTableTask = new Piwik_ScheduledTask ( $this, - 'optimizeArchiveTable', - null, - new Piwik_ScheduledTime_Daily(), - Piwik_ScheduledTask::LOWEST_PRIORITY); - $tasks[] = $optimizeArchiveTableTask; - } + } - /** - * @param Piwik_Event_Notification $notification notification object - */ - function getCssFiles( $notification ) - { - $cssFiles = &$notification->getNotificationObject(); - - $cssFiles[] = "libs/jquery/themes/base/jquery-ui.css"; - $cssFiles[] = "plugins/CoreAdminHome/templates/menu.css"; - $cssFiles[] = "themes/default/common.css"; - $cssFiles[] = "plugins/CoreAdminHome/templates/styles.css"; - $cssFiles[] = "plugins/CoreHome/templates/donate.css"; - } + function purgeOutdatedArchives() + { + $archiveTables = Piwik::getTablesArchivesInstalled(); + foreach ($archiveTables as $table) { + if (strpos($table, 'numeric') !== false) { + Piwik_ArchiveProcessing_Period::doPurgeOutdatedArchives($table); + } + } + } - /** - * @param Piwik_Event_Notification $notification notification object - */ - function getJsFiles ( $notification ) - { - $jsFiles = &$notification->getNotificationObject(); - - $jsFiles[] = "libs/jquery/jquery.js"; - $jsFiles[] = "libs/jquery/jquery-ui.js"; - $jsFiles[] = "libs/javascript/sprintf.js"; - $jsFiles[] = "themes/default/common.js"; - $jsFiles[] = "themes/default/ajaxHelper.js"; - $jsFiles[] = "libs/jquery/jquery.history.js"; - $jsFiles[] = "plugins/CoreHome/templates/broadcast.js"; - $jsFiles[] = "plugins/CoreAdminHome/templates/generalSettings.js"; - $jsFiles[] = "plugins/CoreHome/templates/donate.js"; - } - - function addMenu() - { - Piwik_AddAdminSubMenu('CoreAdminHome_MenuManage', NULL, "", Piwik::isUserHasSomeAdminAccess(), $order = 1); - Piwik_AddAdminSubMenu('CoreAdminHome_MenuCommunity', NULL, "", Piwik::isUserHasSomeAdminAccess(), $order = 3); - Piwik_AddAdminSubMenu('CoreAdminHome_MenuDiagnostic', NULL, "", Piwik::isUserHasSomeAdminAccess(), $order = 20); - Piwik_AddAdminSubMenu('General_Settings', NULL, "", Piwik::isUserHasSomeAdminAccess(), $order = 5); - Piwik_AddAdminSubMenu('General_Settings', 'CoreAdminHome_MenuGeneralSettings', - array('module' => 'CoreAdminHome', 'action' => 'generalSettings'), - Piwik::isUserHasSomeAdminAccess(), - $order = 6); - Piwik_AddAdminSubMenu('CoreAdminHome_MenuManage', 'CoreAdminHome_TrackingCode', - array('module' => 'CoreAdminHome', 'action' => 'trackingCodeGenerator'), - Piwik::isUserHasSomeAdminAccess(), - $order = 4); - - } - - function purgeOutdatedArchives() - { - $archiveTables = Piwik::getTablesArchivesInstalled(); - foreach($archiveTables as $table) - { - if(strpos($table, 'numeric') !== false) - { - Piwik_ArchiveProcessing_Period::doPurgeOutdatedArchives($table); - } - } - } - - function optimizeArchiveTable() - { - $archiveTables = Piwik::getTablesArchivesInstalled(); - Piwik_OptimizeTables($archiveTables); - } + function optimizeArchiveTable() + { + $archiveTables = Piwik::getTablesArchivesInstalled(); + Piwik_OptimizeTables($archiveTables); + } } diff --git a/plugins/CoreAdminHome/templates/generalSettings.js b/plugins/CoreAdminHome/templates/generalSettings.js index 8adcad271d..6a493626c7 100644 --- a/plugins/CoreAdminHome/templates/generalSettings.js +++ b/plugins/CoreAdminHome/templates/generalSettings.js @@ -5,33 +5,32 @@ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later */ -function sendGeneralSettingsAJAX() -{ - var enableBrowserTriggerArchiving = $('input[name=enableBrowserTriggerArchiving]:checked').val(); - var enableBetaReleaseCheck = $('input[name=enableBetaReleaseCheck]:checked').val(); - var todayArchiveTimeToLive = $('#todayArchiveTimeToLive').val(); +function sendGeneralSettingsAJAX() { + var enableBrowserTriggerArchiving = $('input[name=enableBrowserTriggerArchiving]:checked').val(); + var enableBetaReleaseCheck = $('input[name=enableBetaReleaseCheck]:checked').val(); + var todayArchiveTimeToLive = $('#todayArchiveTimeToLive').val(); - var trustedHosts = []; - $('input[name=trusted_host]').each(function () { - trustedHosts.push($(this).val()); - }); + var trustedHosts = []; + $('input[name=trusted_host]').each(function () { + trustedHosts.push($(this).val()); + }); var ajaxHandler = new ajaxHelper(); ajaxHandler.setLoadingElement(); ajaxHandler.addParams({ - format: 'json', - enableBrowserTriggerArchiving: enableBrowserTriggerArchiving, - enableBetaReleaseCheck: enableBetaReleaseCheck, - todayArchiveTimeToLive: todayArchiveTimeToLive, - mailUseSmtp: isSmtpEnabled(), - mailPort: $('#mailPort').val(), - mailHost: $('#mailHost').val(), - mailType: $('#mailType').val(), - mailUsername: $('#mailUsername').val(), - mailPassword: $('#mailPassword').val(), - mailEncryption: $('#mailEncryption').val(), - useCustomLogo: isCustomLogoEnabled(), - trustedHosts: JSON.stringify(trustedHosts) + format: 'json', + enableBrowserTriggerArchiving: enableBrowserTriggerArchiving, + enableBetaReleaseCheck: enableBetaReleaseCheck, + todayArchiveTimeToLive: todayArchiveTimeToLive, + mailUseSmtp: isSmtpEnabled(), + mailPort: $('#mailPort').val(), + mailHost: $('#mailHost').val(), + mailType: $('#mailType').val(), + mailUsername: $('#mailUsername').val(), + mailPassword: $('#mailPassword').val(), + mailEncryption: $('#mailEncryption').val(), + useCustomLogo: isCustomLogoEnabled(), + trustedHosts: JSON.stringify(trustedHosts) }, 'POST'); ajaxHandler.addParams({ module: 'CoreAdminHome', @@ -40,111 +39,103 @@ function sendGeneralSettingsAJAX() ajaxHandler.redirectOnSuccess(); ajaxHandler.send(true); } -function showSmtpSettings(value) -{ - $('#smtpSettings').toggle(value==1); +function showSmtpSettings(value) { + $('#smtpSettings').toggle(value == 1); } -function isSmtpEnabled() -{ - return $('input[name="mailUseSmtp"]:checked').val(); +function isSmtpEnabled() { + return $('input[name="mailUseSmtp"]:checked').val(); } -function showCustomLogoSettings(value) -{ - $('#logoSettings').toggle(value==1); +function showCustomLogoSettings(value) { + $('#logoSettings').toggle(value == 1); } -function isCustomLogoEnabled() -{ - return $('input[name="useCustomLogo"]:checked').val(); +function isCustomLogoEnabled() { + return $('input[name="useCustomLogo"]:checked').val(); } function refreshCustomLogo() { - var imageDiv = $("#currentLogo"); - if(imageDiv && imageDiv.attr("src")) { - var logoUrl = imageDiv.attr("src").split("?")[0]; - imageDiv.attr("src", logoUrl+"?"+ (new Date()).getTime()); - } + var imageDiv = $("#currentLogo"); + if (imageDiv && imageDiv.attr("src")) { + var logoUrl = imageDiv.attr("src").split("?")[0]; + imageDiv.attr("src", logoUrl + "?" + (new Date()).getTime()); + } } -$(document).ready( function() { - var originalTrustedHostCount = $('input[name=trusted_host]').length; - - showSmtpSettings(isSmtpEnabled()); - showCustomLogoSettings(isCustomLogoEnabled()); - $('#generalSettingsSubmit').click( function() { - var doSubmit = function() - { - sendGeneralSettingsAJAX(); - }; - - var hasTrustedHostsChanged = false, - hosts = $('input[name=trusted_host]'); - if (hosts.length != originalTrustedHostCount) - { - hasTrustedHostsChanged = true; - } - else - { - hosts.each(function() { - hasTrustedHostsChanged |= this.defaultValue != this.value; - }); - } - - // if trusted hosts have changed, make sure to ask for confirmation - if (hasTrustedHostsChanged) - { - piwikHelper.modalConfirm('#confirmTrustedHostChange', {yes: doSubmit}); - } - else - { - doSubmit(); - } - }); +$(document).ready(function () { + var originalTrustedHostCount = $('input[name=trusted_host]').length; - $('input[name=mailUseSmtp]').click(function(){ - showSmtpSettings($(this).val()); - }); - $('input[name=useCustomLogo]').click(function(){ - refreshCustomLogo(); - showCustomLogoSettings($(this).val()); - }); - $('input').keypress( function(e) { - var key=e.keyCode || e.which; - if (key==13) { - $('#generalSettingsSubmit').click(); - } - } - ); - - $("#logoUploadForm").submit( function(data) { - var submittingForm = $( this ); - var frameName = "upload"+(new Date()).getTime(); - var uploadFrame = $("<iframe name=\""+frameName+"\" />"); - uploadFrame.css("display", "none"); - uploadFrame.load(function(data){ - setTimeout(function(){ - refreshCustomLogo(); - uploadFrame.remove();},1000); - }); - $("body:first").append(uploadFrame); - submittingForm.attr("target", frameName); - }); - - $('#customLogo').change(function(){$("#logoUploadForm").submit()}); - - // trusted hosts event handling - $('#trustedHostSettings .adminTable').on('click', '.remove-trusted-host', function(e) { - e.preventDefault(); - $(this).parent().parent().remove(); - return false; - }); - $('#trustedHostSettings .add-trusted-host').click(function(e) { - e.preventDefault(); - - // append new row to the table - $('#trustedHostSettings tbody').append('<tr>' - + '<td><input name="trusted_host" type="text" value=""/></td>' - + '<td><a href="#" class="remove-trusted-host">x</a></td>' - + '</tr>'); - return false; - }); + showSmtpSettings(isSmtpEnabled()); + showCustomLogoSettings(isCustomLogoEnabled()); + $('#generalSettingsSubmit').click(function () { + var doSubmit = function () { + sendGeneralSettingsAJAX(); + }; + + var hasTrustedHostsChanged = false, + hosts = $('input[name=trusted_host]'); + if (hosts.length != originalTrustedHostCount) { + hasTrustedHostsChanged = true; + } + else { + hosts.each(function () { + hasTrustedHostsChanged |= this.defaultValue != this.value; + }); + } + + // if trusted hosts have changed, make sure to ask for confirmation + if (hasTrustedHostsChanged) { + piwikHelper.modalConfirm('#confirmTrustedHostChange', {yes: doSubmit}); + } + else { + doSubmit(); + } + }); + + $('input[name=mailUseSmtp]').click(function () { + showSmtpSettings($(this).val()); + }); + $('input[name=useCustomLogo]').click(function () { + refreshCustomLogo(); + showCustomLogoSettings($(this).val()); + }); + $('input').keypress(function (e) { + var key = e.keyCode || e.which; + if (key == 13) { + $('#generalSettingsSubmit').click(); + } + } + ); + + $("#logoUploadForm").submit(function (data) { + var submittingForm = $(this); + var frameName = "upload" + (new Date()).getTime(); + var uploadFrame = $("<iframe name=\"" + frameName + "\" />"); + uploadFrame.css("display", "none"); + uploadFrame.load(function (data) { + setTimeout(function () { + refreshCustomLogo(); + uploadFrame.remove(); + }, 1000); + }); + $("body:first").append(uploadFrame); + submittingForm.attr("target", frameName); + }); + + $('#customLogo').change(function () {$("#logoUploadForm").submit()}); + + // trusted hosts event handling + $('#trustedHostSettings .adminTable').on('click', '.remove-trusted-host', function (e) { + e.preventDefault(); + $(this).parent().parent().remove(); + return false; + }); + $('#trustedHostSettings .add-trusted-host').click(function (e) { + e.preventDefault(); + + // append new row to the table + $('#trustedHostSettings tbody').append('<tr>' + + '<td><input name="trusted_host" type="text" value=""/></td>' + + '<td><a href="#" class="remove-trusted-host">x</a></td>' + + '</tr>'); + return false; + }); }); diff --git a/plugins/CoreAdminHome/templates/generalSettings.tpl b/plugins/CoreAdminHome/templates/generalSettings.tpl index b4aa4c28d4..5a97d6fe91 100644 --- a/plugins/CoreAdminHome/templates/generalSettings.tpl +++ b/plugins/CoreAdminHome/templates/generalSettings.tpl @@ -2,245 +2,247 @@ {loadJavascriptTranslations plugins='UsersManager'} {if $isSuperUser} -<h2>{'General_GeneralSettings'|translate}</h2> - -{ajaxErrorDiv id=ajaxError} -{ajaxLoadingDiv id=ajaxLoading} - -<table class="adminTable" style='width:900px;'> -<tr> - <td style='width:400px'>{'General_AllowPiwikArchivingToTriggerBrowser'|translate}</td> - <td style='width:220px'> - <fieldset> - <label><input type="radio" value="1" name="enableBrowserTriggerArchiving"{if $enableBrowserTriggerArchiving==1} checked="checked"{/if} /> - {'General_Yes'|translate} <br /> - <span class="form-description">{'General_Default'|translate}</span> - </label><br /><br /> - - <label><input type="radio" value="0" name="enableBrowserTriggerArchiving"{if $enableBrowserTriggerArchiving==0} checked="checked"{/if} /> - {'General_No'|translate} <br /> - <span class="form-description">{'General_ArchivingTriggerDescription'|translate:"<a href='?module=Proxy&action=redirect&url=http://piwik.org/docs/setup-auto-archiving/' target='_blank'>":"</a>"}</span> - </label> - </fieldset> - <td> - {capture assign=browserArchivingHelp} - {'General_ArchivingInlineHelp'|translate}<br /> - {'General_SeeTheOfficialDocumentationForMoreInformation'|translate:"<a href='?module=Proxy&action=redirect&url=http://piwik.org/docs/setup-auto-archiving/' target='_blank'>":"</a>"} - {/capture} - {$browserArchivingHelp|inlineHelp} - </td> -</tr> -<tr> - <td><label for="todayArchiveTimeToLive">{'General_ReportsContainingTodayWillBeProcessedAtMostEvery'|translate}</label></td> - <td> - {'General_NSeconds'|translate:"<input size='3' value='$todayArchiveTimeToLive' id='todayArchiveTimeToLive' />"} - </td> - <td width='450px'> - {capture assign=archiveTodayTTLHelp} - {if $showWarningCron} - <strong> - {'General_NewReportsWillBeProcessedByCron'|translate}<br/> - {'General_ReportsWillBeProcessedAtMostEveryHour'|translate} - {'General_IfArchivingIsFastYouCanSetupCronRunMoreOften'|translate}<br/> - </strong> - {/if} - {'General_SmallTrafficYouCanLeaveDefault'|translate:10}<br /> - {'General_MediumToHighTrafficItIsRecommendedTo'|translate:1800:3600} - {/capture} - {$archiveTodayTTLHelp|inlineHelp} - </td> -</tr> -<tr> - <td style='width:400px'>{'CoreAdminHome_CheckReleaseGetVersion'|translate}</td> - <td style='width:220px'> - <fieldset> - <label><input type="radio" value="0" name="enableBetaReleaseCheck"{if $enableBetaReleaseCheck==0} checked="checked"{/if} /> - {'CoreAdminHome_LatestStableRelease'|translate} <br /> - <span class="form-description">{'General_Recommended'|translate}</span> - </label><br /><br /> - - <label><input type="radio" value="1" name="enableBetaReleaseCheck"{if $enableBetaReleaseCheck==1} checked="checked"{/if} /> - {'CoreAdminHome_LatestBetaRelease'|translate} <br /> - <span class="form-description">{'CoreAdminHome_ForBetaTestersOnly'|translate}</span> - </label> - </fieldset> - <td> - {capture assign=checkReleaseHelp} - {'CoreAdminHome_DevelopmentProcess'|translate:"<a href='?module=Proxy&action=redirect&url=http://piwik.org/participate/development-process/' target='_blank'>":"</a>"}<br /> - {'CoreAdminHome_StableReleases'|translate:"<a href='?module=Proxy&action=redirect&url=http://piwik.org/participate/user-feedback/' target='_blank'>":"</a>"} - {/capture} - {$checkReleaseHelp|inlineHelp} - </td> -</tr> -</table> + <h2>{'General_GeneralSettings'|translate}</h2> + {ajaxErrorDiv id=ajaxError} + {ajaxLoadingDiv id=ajaxLoading} + <table class="adminTable" style='width:900px;'> + <tr> + <td style='width:400px'>{'General_AllowPiwikArchivingToTriggerBrowser'|translate}</td> + <td style='width:220px'> + <fieldset> + <label><input type="radio" value="1" name="enableBrowserTriggerArchiving"{if $enableBrowserTriggerArchiving==1} checked="checked"{/if} /> + {'General_Yes'|translate} <br/> + <span class="form-description">{'General_Default'|translate}</span> + </label><br/><br/> -<h2>{'CoreAdminHome_EmailServerSettings'|translate}</h2> -<div id='emailSettings'> -<table class="adminTable" style='width:600px;'> - <tr> - <td>{'General_UseSMTPServerForEmail'|translate}<br /> - <span class="form-description">{'General_SelectYesIfYouWantToSendEmailsViaServer'|translate}</span> - </td> - <td style='width:200px'> - <label><input type="radio" name="mailUseSmtp" value="1" {if $mail.transport eq 'smtp'} checked {/if}/> {'General_Yes'|translate}</label> - <label><input type="radio" name="mailUseSmtp" value="0" style ="margin-left:20px;" {if $mail.transport eq ''} checked {/if}/> {'General_No'|translate}</label> - </td> - </tr> -</table> -</div> + <label><input type="radio" value="0" name="enableBrowserTriggerArchiving"{if $enableBrowserTriggerArchiving==0} checked="checked"{/if} /> + {'General_No'|translate} <br/> + <span class="form-description">{'General_ArchivingTriggerDescription'|translate:"<a href='?module=Proxy&action=redirect&url=http://piwik.org/docs/setup-auto-archiving/' target='_blank'>":"</a>"}</span> + </label> + </fieldset> + <td> + {capture assign=browserArchivingHelp} + {'General_ArchivingInlineHelp'|translate} + <br/> + {'General_SeeTheOfficialDocumentationForMoreInformation'|translate:"<a href='?module=Proxy&action=redirect&url=http://piwik.org/docs/setup-auto-archiving/' target='_blank'>":"</a>"} + {/capture} + {$browserArchivingHelp|inlineHelp} + </td> + </tr> + <tr> + <td><label for="todayArchiveTimeToLive">{'General_ReportsContainingTodayWillBeProcessedAtMostEvery'|translate}</label></td> + <td> + {'General_NSeconds'|translate:"<input size='3' value='$todayArchiveTimeToLive' id='todayArchiveTimeToLive' />"} + </td> + <td width='450px'> + {capture assign=archiveTodayTTLHelp} + {if $showWarningCron} + <strong> + {'General_NewReportsWillBeProcessedByCron'|translate}<br/> + {'General_ReportsWillBeProcessedAtMostEveryHour'|translate} + {'General_IfArchivingIsFastYouCanSetupCronRunMoreOften'|translate}<br/> + </strong> + {/if} + {'General_SmallTrafficYouCanLeaveDefault'|translate:10} + <br/> + {'General_MediumToHighTrafficItIsRecommendedTo'|translate:1800:3600} + {/capture} + {$archiveTodayTTLHelp|inlineHelp} + </td> + </tr> + <tr> + <td style='width:400px'>{'CoreAdminHome_CheckReleaseGetVersion'|translate}</td> + <td style='width:220px'> + <fieldset> + <label><input type="radio" value="0" name="enableBetaReleaseCheck"{if $enableBetaReleaseCheck==0} checked="checked"{/if} /> + {'CoreAdminHome_LatestStableRelease'|translate} <br/> + <span class="form-description">{'General_Recommended'|translate}</span> + </label><br/><br/> -<div id='smtpSettings'> - <table class="adminTable" style='width:550px;'> - <tr> - <td><label for="mailHost">{'General_SmtpServerAddress'|translate}</label></td> - <td style='width:200px'><input type="text" id="mailHost" value="{$mail.host|escape}"></td> - </tr> - <tr> - <td><label for="mailPort">{'General_SmtpPort'|translate}</label><br /> - <span class="form-description">{'General_OptionalSmtpPort'|translate}</span></td> - <td><input type="text" id="mailPort" value="{$mail.port}"></td> - </tr> - <tr> - <td><label for="mailType">{'General_AuthenticationMethodSmtp'|translate}</label><br /> - <span class="form-description">{'General_OnlyUsedIfUserPwdIsSet'|translate}</span> - </td> - <td> - <select id="mailType"> - <option value="" {if $mail.type eq ''} selected="selected" {/if}></option> - <option id="plain" {if $mail.type eq 'Plain'} selected="selected" {/if} value="Plain">Plain</option> - <option id="login" {if $mail.type eq 'Login'} selected="selected" {/if} value="Login"> Login</option> - <option id="cram-md5" {if $mail.type eq 'Crammd5'} selected="selected" {/if} value="Crammd5"> Crammd5</option> - </select> - </td> - </tr> - <tr> - <td><label for="mailUsername">{'General_SmtpUsername'|translate}</label><br /> - <span class="form-description">{'General_OnlyEnterIfRequired'|translate}</span></td> - <td> - <input type="text" id="mailUsername" value = "{$mail.username|escape}" /> - </td> - </tr> - <tr> - <td><label for="mailPassword">{'General_SmtpPassword'|translate}</label><br /> + <label><input type="radio" value="1" name="enableBetaReleaseCheck"{if $enableBetaReleaseCheck==1} checked="checked"{/if} /> + {'CoreAdminHome_LatestBetaRelease'|translate} <br/> + <span class="form-description">{'CoreAdminHome_ForBetaTestersOnly'|translate}</span> + </label> + </fieldset> + <td> + {capture assign=checkReleaseHelp} + {'CoreAdminHome_DevelopmentProcess'|translate:"<a href='?module=Proxy&action=redirect&url=http://piwik.org/participate/development-process/' target='_blank'>":"</a>"} + <br/> + {'CoreAdminHome_StableReleases'|translate:"<a href='?module=Proxy&action=redirect&url=http://piwik.org/participate/user-feedback/' target='_blank'>":"</a>"} + {/capture} + {$checkReleaseHelp|inlineHelp} + </td> + </tr> + </table> + <h2>{'CoreAdminHome_EmailServerSettings'|translate}</h2> + <div id='emailSettings'> + <table class="adminTable" style='width:600px;'> + <tr> + <td>{'General_UseSMTPServerForEmail'|translate}<br/> + <span class="form-description">{'General_SelectYesIfYouWantToSendEmailsViaServer'|translate}</span> + </td> + <td style='width:200px'> + <label><input type="radio" name="mailUseSmtp" value="1" {if $mail.transport eq 'smtp'} checked {/if}/> {'General_Yes'|translate}</label> + <label><input type="radio" name="mailUseSmtp" value="0" + style="margin-left:20px;" {if $mail.transport eq ''} checked {/if}/> {'General_No'|translate}</label> + </td> + </tr> + </table> + </div> + <div id='smtpSettings'> + <table class="adminTable" style='width:550px;'> + <tr> + <td><label for="mailHost">{'General_SmtpServerAddress'|translate}</label></td> + <td style='width:200px'><input type="text" id="mailHost" value="{$mail.host|escape}"></td> + </tr> + <tr> + <td><label for="mailPort">{'General_SmtpPort'|translate}</label><br/> + <span class="form-description">{'General_OptionalSmtpPort'|translate}</span></td> + <td><input type="text" id="mailPort" value="{$mail.port}"></td> + </tr> + <tr> + <td><label for="mailType">{'General_AuthenticationMethodSmtp'|translate}</label><br/> + <span class="form-description">{'General_OnlyUsedIfUserPwdIsSet'|translate}</span> + </td> + <td> + <select id="mailType"> + <option value="" {if $mail.type eq ''} selected="selected" {/if}></option> + <option id="plain" {if $mail.type eq 'Plain'} selected="selected" {/if} value="Plain">Plain</option> + <option id="login" {if $mail.type eq 'Login'} selected="selected" {/if} value="Login"> Login</option> + <option id="cram-md5" {if $mail.type eq 'Crammd5'} selected="selected" {/if} value="Crammd5"> Crammd5</option> + </select> + </td> + </tr> + <tr> + <td><label for="mailUsername">{'General_SmtpUsername'|translate}</label><br/> + <span class="form-description">{'General_OnlyEnterIfRequired'|translate}</span></td> + <td> + <input type="text" id="mailUsername" value="{$mail.username|escape}"/> + </td> + </tr> + <tr> + <td><label for="mailPassword">{'General_SmtpPassword'|translate}</label><br/> <span class="form-description">{'General_OnlyEnterIfRequiredPassword'|translate}<br/> - {'General_WarningPasswordStored'|translate:"<strong>":"</strong>"}</span> - </td> - <td> - <input type="password" id="mailPassword" value = "{$mail.password|escape}" /> - </td> - </tr> - <tr> - <td><label for="mailEncryption">{'General_SmtpEncryption'|translate}</label><br /> - <span class="form-description">{'General_EncryptedSmtpTransport'|translate}</span></td> - <td> - <select id="mailEncryption"> - <option value="" {if $mail.encryption eq ''} selected="selected" {/if}></option> - <option id="ssl" {if $mail.encryption eq 'ssl'} selected="selected" {/if} value="ssl">SSL</option> - <option id="tls" {if $mail.encryption eq 'tls'} selected="selected" {/if} value="tls">TLS</option> - </select> - </td> - </tr> - </table> -</div> - -<div class="ui-confirm" id="confirmTrustedHostChange"> - <h2>{'CoreAdminHome_TrustedHostConfirm'|translate}</h2> - <input role="yes" type="button" value="{'General_Yes'|translate}" /> - <input role="no" type="button" value="{'General_No'|translate}" /> -</div> - -<h2 id="trustedHostsSection">{'CoreAdminHome_TrustedHostSettings'|translate}</h2> -<div id='trustedHostSettings'> -{* untrusted host warning (display again) *} - {if isset($isValidHost) && isset($invalidHostMessage) && !$isValidHost} - <div class="ajaxSuccess"> - <a style="float:right" href="http://piwik.org/faq/troubleshooting/#faq_171" target="_blank"><img src="themes/default/images/help_grey.png" /></a> - <strong>{'General_Warning'|translate}: </strong>{$invalidHostMessage} - </div> - {/if} - {if count($trustedHosts) eq 1 && (!isset($isValidHost) || $isValidHost)} - {'CoreAdminHome_PiwikIsInstalledAt'|translate}:  <input name="trusted_host" type="text" value="{$trustedHosts[0]}"/> - {else} - <p>{'CoreAdminHome_PiwikIsInstalledAt'|translate}:</p> - <table class="adminTable"> + {'General_WarningPasswordStored'|translate:"<strong>":"</strong>"}</span> + </td> + <td> + <input type="password" id="mailPassword" value="{$mail.password|escape}"/> + </td> + </tr> <tr> - <th style="width:250px">{'CoreAdminHome_ValidPiwikHostname'|translate}</th> - <th style="width:10px"> </th> + <td><label for="mailEncryption">{'General_SmtpEncryption'|translate}</label><br/> + <span class="form-description">{'General_EncryptedSmtpTransport'|translate}</span></td> + <td> + <select id="mailEncryption"> + <option value="" {if $mail.encryption eq ''} selected="selected" {/if}></option> + <option id="ssl" {if $mail.encryption eq 'ssl'} selected="selected" {/if} value="ssl">SSL</option> + <option id="tls" {if $mail.encryption eq 'tls'} selected="selected" {/if} value="tls">TLS</option> + </select> + </td> </tr> - {foreach from=$trustedHosts item=host key=hostIdx} + </table> + </div> + <div class="ui-confirm" id="confirmTrustedHostChange"> + <h2>{'CoreAdminHome_TrustedHostConfirm'|translate}</h2> + <input role="yes" type="button" value="{'General_Yes'|translate}"/> + <input role="no" type="button" value="{'General_No'|translate}"/> + </div> + <h2 id="trustedHostsSection">{'CoreAdminHome_TrustedHostSettings'|translate}</h2> + <div id='trustedHostSettings'> + {* untrusted host warning (display again) *} + {if isset($isValidHost) && isset($invalidHostMessage) && !$isValidHost} + <div class="ajaxSuccess"> + <a style="float:right" href="http://piwik.org/faq/troubleshooting/#faq_171" target="_blank"><img src="themes/default/images/help_grey.png"/></a> + <strong>{'General_Warning'|translate}: </strong>{$invalidHostMessage} + </div> + {/if} + {if count($trustedHosts) eq 1 && (!isset($isValidHost) || $isValidHost)} + {'CoreAdminHome_PiwikIsInstalledAt'|translate}:   + <input name="trusted_host" type="text" value="{$trustedHosts[0]}"/> + {else} + <p>{'CoreAdminHome_PiwikIsInstalledAt'|translate}:</p> + <table class="adminTable"> <tr> - <td><input name="trusted_host" type="text" value="{$host}"/></td> - <td> - <a href="#" class="remove-trusted-host">x</a> - </td> + <th style="width:250px">{'CoreAdminHome_ValidPiwikHostname'|translate}</th> + <th style="width:10px"> </th> </tr> - {/foreach} + {foreach from=$trustedHosts item=host key=hostIdx} + <tr> + <td><input name="trusted_host" type="text" value="{$host}"/></td> + <td> + <a href="#" class="remove-trusted-host">x</a> + </td> + </tr> + {/foreach} + </table> + <div class="adminTable add-trusted-host-container"> + <a href="#" class="add-trusted-host"><em>{'General_Add'|translate}</em></a> + </div> + {/if} + </div> + <h2>{'CoreAdminHome_BrandingSettings'|translate}</h2> + <div id='brandSettings'> + {'CoreAdminHome_CustomLogoHelpText'|translate} + <table class="adminTable" style='width:600px;'> + <tr> + <td> {'CoreAdminHome_UseCustomLogo'|translate}</td> + <td style='width:200px'> + <label><input type="radio" name="useCustomLogo" value="1" {if $branding.use_custom_logo == 1} checked {/if}/> {'General_Yes'|translate} + </label> + <label><input type="radio" name="useCustomLogo" value="0" + style="margin-left:20px;" {if $branding.use_custom_logo == 0} checked {/if}/> {'General_No'|translate}</label> + </td> + </tr> </table> - <div class="adminTable add-trusted-host-container"> - <a href="#" class="add-trusted-host"><em>{'General_Add'|translate}</em></a> - </div> - {/if} -</div> - -<h2>{'CoreAdminHome_BrandingSettings'|translate}</h2> -<div id='brandSettings'> -{'CoreAdminHome_CustomLogoHelpText'|translate} -<table class="adminTable" style='width:600px;'> - <tr> - <td> {'CoreAdminHome_UseCustomLogo'|translate}</td> - <td style='width:200px'> - <label><input type="radio" name="useCustomLogo" value="1" {if $branding.use_custom_logo == 1} checked {/if}/> {'General_Yes'|translate}</label> - <label><input type="radio" name="useCustomLogo" value="0" style ="margin-left:20px;" {if $branding.use_custom_logo == 0} checked {/if}/> {'General_No'|translate}</label> - </td> - </tr> -</table> -</div> - -<div id='logoSettings'> - {capture assign=giveUsFeedbackText}"{'General_GiveUsYourFeedback'|translate}"{/capture} - {capture assign=customLogoHelp} - {'CoreAdminHome_CustomLogoFeedbackInfo'|translate:$giveUsFeedbackText:"<a href='?module=CorePluginsAdmin&action=index' target='_blank'>":"</a>"} - {/capture} - {$customLogoHelp|inlineHelp} - <form id="logoUploadForm" method="post" enctype="multipart/form-data" action="index.php?module=CoreAdminHome&format=json&action=uploadCustomLogo"> - <table class="adminTable" style='width:550px;'> - <tr> - {if $logosWriteable} - <td><label for="customLogo">{'CoreAdminHome_LogoUpload'|translate}:<br /> - <span class="form-description">{'CoreAdminHome_LogoUploadDescription'|translate:"JPG / PNG / GIF":110}</span></label></td> - <td style='width:200px'> - <input name="customLogo" type="file" id="customLogo" /><img src="themes/logo.png?r={math equation='rand(10,1000)'}" id="currentLogo" height="150"/> - </td> - {else} - <td><span class="ajaxSuccess">{'CoreAdminHome_LogoNotWriteable'|translate:"<ul style='list-style: disc inside;'><li>/themes/</li><li>/themes/logo.png</li><li>/themes/logo-header.png</li></ul>"}</span></td> - {/if} - </tr> - </table> - </form> -</div> - -<input type="submit" value="{'General_Save'|translate}" id="generalSettingsSubmit" class="submit" /> -<br /><br /> - -{capture assign=clickDeleteLogSettings}{'PrivacyManager_DeleteDataSettings'|translate}{/capture} -<h2>{'PrivacyManager_DeleteDataSettings'|translate}</h2> -<p> - {'PrivacyManager_DeleteDataDescription'|translate} {'PrivacyManager_DeleteDataDescription2'|translate} -<br/> - <a href='{url module="PrivacyManager" action="privacySettings"}#deleteLogsAnchor'> - {'PrivacyManager_ClickHereSettings'|translate:"'$clickDeleteLogSettings'"} - </a> -</p> + </div> + <div id='logoSettings'> + {capture assign=giveUsFeedbackText}"{'General_GiveUsYourFeedback'|translate}"{/capture} + {capture assign=customLogoHelp} + {'CoreAdminHome_CustomLogoFeedbackInfo'|translate:$giveUsFeedbackText:"<a href='?module=CorePluginsAdmin&action=index' target='_blank'>":"</a>"} + {/capture} + {$customLogoHelp|inlineHelp} + <form id="logoUploadForm" method="post" enctype="multipart/form-data" action="index.php?module=CoreAdminHome&format=json&action=uploadCustomLogo"> + <table class="adminTable" style='width:550px;'> + <tr> + {if $logosWriteable} + <td><label for="customLogo">{'CoreAdminHome_LogoUpload'|translate}:<br/> + <span class="form-description">{'CoreAdminHome_LogoUploadDescription'|translate:"JPG / PNG / GIF":110}</span></label></td> + <td style='width:200px'> + <input name="customLogo" type="file" id="customLogo"/><img src="themes/logo.png?r={math equation='rand(10,1000)'}" id="currentLogo" + height="150"/> + </td> + {else} + <td> + <span class="ajaxSuccess">{'CoreAdminHome_LogoNotWriteable'|translate:"<ul style='list-style: disc inside;'><li>/themes/</li><li>/themes/logo.png</li><li>/themes/logo-header.png</li></ul>"}</span> + </td> + {/if} + </tr> + </table> + </form> + </div> + <input type="submit" value="{'General_Save'|translate}" id="generalSettingsSubmit" class="submit"/> + <br/> + <br/> + {capture assign=clickDeleteLogSettings}{'PrivacyManager_DeleteDataSettings'|translate}{/capture} + <h2>{'PrivacyManager_DeleteDataSettings'|translate}</h2> + <p> + {'PrivacyManager_DeleteDataDescription'|translate} {'PrivacyManager_DeleteDataDescription2'|translate} + <br/> + <a href='{url module="PrivacyManager" action="privacySettings"}#deleteLogsAnchor'> + {'PrivacyManager_ClickHereSettings'|translate:"'$clickDeleteLogSettings'"} + </a> + </p> {/if} <h2>{'CoreAdminHome_OptOutForYourVisitors'|translate}</h2> <p>{'CoreAdminHome_OptOutExplanation'|translate} -{capture name=optOutUrl}{$piwikUrl}index.php?module=CoreAdminHome&action=optOut&language={$language}{/capture} -{assign var=optOutUrl value=$smarty.capture.optOutUrl} -{capture name=iframeOptOut}<iframe frameborder="no" width="600px" height="200px" src="{$smarty.capture.optOutUrl}"></iframe>{/capture} -<code>{$smarty.capture.iframeOptOut|escape:'html'}</code> -<br/> -{'CoreAdminHome_OptOutExplanationBis'|translate:"<a href='$optOutUrl' target='_blank'>":"</a>"} + {capture name=optOutUrl}{$piwikUrl}index.php?module=CoreAdminHome&action=optOut&language={$language}{/capture} + {assign var=optOutUrl value=$smarty.capture.optOutUrl} + {capture name=iframeOptOut} + <iframe frameborder="no" width="600px" height="200px" src="{$smarty.capture.optOutUrl}"></iframe>{/capture} + <code>{$smarty.capture.iframeOptOut|escape:'html'}</code> + <br/> + {'CoreAdminHome_OptOutExplanationBis'|translate:"<a href='$optOutUrl' target='_blank'>":"</a>"} </p> {include file="CoreAdminHome/templates/footer.tpl"} diff --git a/plugins/CoreAdminHome/templates/header.tpl b/plugins/CoreAdminHome/templates/header.tpl index acfa47e3d4..84a8407b0a 100644 --- a/plugins/CoreAdminHome/templates/header.tpl +++ b/plugins/CoreAdminHome/templates/header.tpl @@ -1,80 +1,83 @@ <!DOCTYPE html> -<!--[if lt IE 9 ]><html class="old-ie"> <![endif]--> -<!--[if (gte IE 9)|!(IE)]><!--><html><!--<![endif]--> +<!--[if lt IE 9 ]> +<html class="old-ie"> <![endif]--> +<!--[if (gte IE 9)|!(IE)]><!--> +<html><!--<![endif]--> <head> -<title>{if !$isCustomLogo}Piwik › {/if}{'CoreAdminHome_Administration'|translate} - - - + {if !$isCustomLogo}Piwik › {/if}{'CoreAdminHome_Administration'|translate} + + + -{loadJavascriptTranslations plugins='CoreAdminHome CoreHome'} + {loadJavascriptTranslations plugins='CoreAdminHome CoreHome'} -{include file="CoreHome/templates/js_global_variables.tpl"} -{include file="CoreHome/templates/js_css_includes.tpl"} - -{include file="CoreHome/templates/iframe_buster_header.tpl"} + {include file="CoreHome/templates/js_global_variables.tpl"} + {include file="CoreHome/templates/js_css_includes.tpl"} + + {include file="CoreHome/templates/iframe_buster_header.tpl"} {include file="CoreHome/templates/iframe_buster_body.tpl"}

-{if !isset($showTopMenu) || $showTopMenu} -{assign var=showSitesSelection value=false} -{assign var=showPeriodSelection value=false} -{include file="CoreHome/templates/top_bar.tpl"} -{/if} + {if !isset($showTopMenu) || $showTopMenu} + {assign var=showSitesSelection value=false} + {assign var=showPeriodSelection value=false} + {include file="CoreHome/templates/top_bar.tpl"} + {/if} - + -{ajaxRequestErrorDiv} -
-{if !isset($showMenu) || $showMenu} - {include file="CoreAdminHome/templates/menu.tpl"} -{/if} + {ajaxRequestErrorDiv} +
+ {if !isset($showMenu) || $showMenu} + {include file="CoreAdminHome/templates/menu.tpl"} + {/if} -
+
-{include file="CoreHome/templates/header_message.tpl"} + {include file="CoreHome/templates/header_message.tpl"} -{if !empty($configFileNotWritable)} -
- {'General_ConfigFileIsNotWritable'|translate:"(config/config.ini.php)":"
"} -
-{elseif preg_match('/updated=[1-9]/', $url)} -
- {'General_YourChangesHaveBeenSaved'|translate} -
-{/if} + {if !empty($configFileNotWritable)} +
+ {'General_ConfigFileIsNotWritable'|translate:"(config/config.ini.php)":"
"} +
+ {elseif preg_match('/updated=[1-9]/', $url)} +
+ {'General_YourChangesHaveBeenSaved'|translate} +
+ {/if} -{if !empty($statisticsNotRecorded)} -
- {'General_StatisticsAreNotRecorded'|translate} -
-{/if} + {if !empty($statisticsNotRecorded)} +
+ {'General_StatisticsAreNotRecorded'|translate} +
+ {/if} -
-

- -
+
+

+ +
-{include file="CoreHome/templates/warning_invalid_host.tpl"} + {include file="CoreHome/templates/warning_invalid_host.tpl"} -{* missing plugins warning *} -{if $isSuperUser && !empty($missingPluginsWarning)} -
- {'General_Warning'|translate}: {$missingPluginsWarning} -
-{/if} + {* missing plugins warning *} + {if $isSuperUser && !empty($missingPluginsWarning)} +
+ {'General_Warning'|translate}: {$missingPluginsWarning} +
+ {/if} -{* old GeoIP plugin warning *} -{if $isSuperUser && !empty($usingOldGeoIPPlugin)} -
- {'General_Warning'|translate}: {'UserCountry_OldGeoIPWarning'|translate:'':'':'':'':'':'':'':''} -
-{/if} + {* old GeoIP plugin warning *} + {if $isSuperUser && !empty($usingOldGeoIPPlugin)} +
+ {'General_Warning'|translate} + : {'UserCountry_OldGeoIPWarning'|translate:'':'':'':'':'':'':'':''} +
+ {/if} diff --git a/plugins/CoreAdminHome/templates/jsTrackingGenerator.css b/plugins/CoreAdminHome/templates/jsTrackingGenerator.css index 7f242ddcc1..2aeafbbe6b 100644 --- a/plugins/CoreAdminHome/templates/jsTrackingGenerator.css +++ b/plugins/CoreAdminHome/templates/jsTrackingGenerator.css @@ -1,8 +1,8 @@ -#javascript-output-section textarea,#image-link-output-section textarea { - width:100%; - display: block; - color: #111; - font-family: "Courier New", Courier, monospace; +#javascript-output-section textarea, #image-link-output-section textarea { + width: 100%; + display: block; + color: #111; + font-family: "Courier New", Courier, monospace; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; @@ -10,67 +10,70 @@ } #javascript-output-section textarea { - height: 256px; + height: 256px; } #image-link-output-section textarea { - height: 128px; + height: 128px; } label { - margin-right: .35em; - display:inline-block; + margin-right: .35em; + display: inline-block; } -#optional-js-tracking-options>tbody>tr>td,#image-tracking-section>tbody>tr>td { - width:488px; - max-width:488px; +#optional-js-tracking-options>tbody>tr>td, #image-tracking-section>tbody>tr>td { + width: 488px; + max-width: 488px; } -.custom-variable-name,.custom-variable-value { - width:100px; +.custom-variable-name, .custom-variable-value { + width: 100px; } h3 { - margin-top:0; + margin-top: 0; } .small-form-description { - color:#666; - font-size:1em; - font-style:italic; - margin-left:4em; + color: #666; + font-size: 1em; + font-style: italic; + margin-left: 4em; } .tracking-option-section { - margin-bottom:1.5em; + margin-bottom: 1.5em; } -#javascript-output-section,#image-link-output-section { - padding-top:1em; +#javascript-output-section, #image-link-output-section { + padding-top: 1em; } -#optional-js-tracking-options th,#image-tracking-section th { - text-align:left; +#optional-js-tracking-options th, #image-tracking-section th { + text-align: left; } -#js-visitor-cv-extra,#js-page-cv-extra,#js-campaign-query-param-extra { - margin-left:1em; +#js-visitor-cv-extra, #js-page-cv-extra, #js-campaign-query-param-extra { + margin-left: 1em; } -#js-visitor-cv-extra td,#js-page-cv-extra td,#js-campaign-query-param-extra td { - vertical-align:middle; + +#js-visitor-cv-extra td, #js-page-cv-extra td, #js-campaign-query-param-extra td { + vertical-align: middle; } .revenue { - width:32px; + width: 32px; } + .goal-picker { - height:1.2em; + height: 1.2em; } + .goal-picker select { - width:128px; + width: 128px; } #js-campaign-query-param-extra input { - width:72px; + width: 72px; } diff --git a/plugins/CoreAdminHome/templates/jsTrackingGenerator.js b/plugins/CoreAdminHome/templates/jsTrackingGenerator.js index 10080fd814..d94a037356 100644 --- a/plugins/CoreAdminHome/templates/jsTrackingGenerator.js +++ b/plugins/CoreAdminHome/templates/jsTrackingGenerator.js @@ -5,228 +5,204 @@ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later */ -(function($) { - -$(document).ready(function() { - - var piwikHost = window.location.host, - piwikPath = location.pathname.substring(0, location.pathname.lastIndexOf('/')); - - // - // utility methods - // - - // returns JavaScript code for tracking custom variables based on an array of - // custom variable name-value pairs (so an array of 2-element arrays) and - // a scope (either 'visit' or 'page') - var getCustomVariableJS = function(customVariables, scope) - { - var result = ''; - for (var i = 0; i != 5; ++i) - { - if (customVariables[i]) - { - var key = customVariables[i][0], - value = customVariables[i][1]; - result += ' _paq.push(["setCustomVariable", ' + (i + 1) + ', ' + JSON.stringify(key) + ', ' - + JSON.stringify(value) + ', ' + JSON.stringify(scope) + ']);\n'; - } - } - return result; - }; - - // gets the list of custom variables entered by the user in a custom variable - // section - var getCustomVariables = function (sectionId) - { - var customVariableNames = $('.custom-variable-name', '#' + sectionId), - customVariableValues = $('.custom-variable-value', '#' + sectionId); - - var result = []; - if ($('.section-toggler-link', '#' + sectionId).is(':checked')) - { - for (var i = 0; i != customVariableNames.length; ++i) - { - var name = $(customVariableNames[i]).val(); - - result[i] = null; - if (name) - { - result[i] = [name, $(customVariableValues[i]).val()]; - } - } - } - return result; - }; - - // quickly gets the host + port from a url - var getHostNameFromUrl = function(url) - { - var element = $('')[0]; - element.href = url; - return element.hostname; - }; - - // get preloaded server-side data necessary for code generation - var dataElement = $('#js-tracking-generator-data'), - currencySymbols = JSON.parse(dataElement.attr('data-currencies')), - siteUrls = {}, - siteCurrencies = {}, - allGoals = {}, - noneText = $('#image-tracker-goal>option').text(); - - // queries Piwik for needed site info for one site - var getSiteData = function (idSite, sectionSelect, callback) - { - // if data is already loaded, don't do an AJAX request - if (siteUrls[idSite] - && siteCurrencies[idSite] - && typeof allGoals[idSite] !== 'undefined') - { - callback(); - return; - } - - // disable section - $(sectionSelect).find('input,select,textarea').attr('disabled', 'disabled'); - - var ajaxRequest = new ajaxHelper(); - ajaxRequest.setBulkRequests( - // get site info (for currency) - { - module: 'API', - method: 'SitesManager.getSiteFromId', - idSite: idSite - }, - - // get site urls - { - module: 'API', - method: 'SitesManager.getSiteUrlsFromId', - idSite: idSite - }, - - // get site goals - { - module: 'API', - method: 'Goals.getGoals', - idSite: idSite - } - ); - 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] || []; - allGoals[idSite] = data[2] || []; - - // re-enable controls - $(sectionSelect).find('input,select,textarea').removeAttr('disabled'); - - callback(); - }); - ajaxRequest.setFormat('json'); - ajaxRequest.send(false); - }; - - // resets the select options of a goal select using a site ID - var resetGoalSelectItems = function (idsite, id) - { - var selectElement = $('#' + id).html(''); - - selectElement.append($('').text(noneText)); - - var goals = allGoals[idsite] || []; - for (var key in goals) - { - var goal = goals[key]; - selectElement.append($('').text(noneText)); + + var goals = allGoals[idsite] || []; + for (var key in goals) { + var goal = goals[key]; + selectElement.append($('
+ + {/foreach} diff --git a/plugins/Live/templates/live.css b/plugins/Live/templates/live.css index e4fab30625..43ef43e2bd 100644 --- a/plugins/Live/templates/live.css +++ b/plugins/Live/templates/live.css @@ -1,99 +1,123 @@ #visitsLive { - text-align:left; - font-size:90%; - color:#444; + text-align: left; + font-size: 90%; + color: #444; } + #visitsLive .datetime, #visitsLive .country, #visitsLive .referer, #visitsLive .settings, #visitsLive .returning { - border-bottom: 1px solid #d3d1c5; - border-right:1px solid #d3d1c5; - padding:5px 5px 5px 12px; + border-bottom: 1px solid #d3d1c5; + border-right: 1px solid #d3d1c5; + padding: 5px 5px 5px 12px; } + #visitsLive .datetime { - background:#E4E2D7; - border-top:1px solid #d3d1c5; - margin:0; - text-align:left; + background: #E4E2D7; + border-top: 1px solid #d3d1c5; + margin: 0; + text-align: left; } + #visitsLive .country { - background:#FFF url(../../../plugins/CoreHome/templates/images/bullet1.gif) no-repeat scroll 0 0; + background: #FFF url(../../../plugins/CoreHome/templates/images/bullet1.gif) no-repeat scroll 0 0; } + #visitsLive .referer { - background:#F9FAFA none repeat scroll 0 0; + background: #F9FAFA none repeat scroll 0 0; } + #visitsLive .referer:hover { - background:#FFFFF7; + background: #FFFFF7; } + #visitsLive .pagesTitle { - display:block; - float:left; + display: block; + float: left; } + #visitsLive .settings { - background:#FFF none repeat scroll 0 0; + background: #FFF none repeat scroll 0 0; } -#visitsLive .settings a{ - text-decoration:none; + +#visitsLive .settings a { + text-decoration: none; } + #visitsLive .returning { - background:#F9FAFA none repeat scroll 0 0; + background: #F9FAFA none repeat scroll 0 0; } + .visitsLiveFooter img { - vertical-align:middle; + vertical-align: middle; } + .visitsLiveFooter { - line-height:2.5em; + line-height: 2.5em; } + .visitorLog table img { - margin:0 3px 0 0; + margin: 0 3px 0 0; } -.visitsLiveFooter a.rightLink{ - float:right; - padding-right:20px; + +.visitsLiveFooter a.rightLink { + float: right; + padding-right: 20px; } -#visitsLive .datetime a{ + +#visitsLive .datetime a { text-decoration: none; } + table.dataTable td.highlightField { - background-color:#FFFFCB !important; + background-color: #FFFFCB !important; } + ol.visitorLog { - list-style:decimal inside none; + list-style: decimal inside none; } + ol.visitorLog li { - margin-bottom:4px; + margin-bottom: 4px; } + #visitsLive img { - vertical-align:middle; + vertical-align: middle; } + .visitorRank img { - vertical-align:text-bottom; + vertical-align: text-bottom; } + .iconPadding { - margin-left:4px; - margin-right:4px; + margin-left: 4px; + margin-right: 4px; } + .visitorRank { - margin-left:15px; - border:1px solid #D8D8D8; - color:#474747; - border-radius:3px; - -moz-border-radius:3px; - -webkit-border-radius:3px; + margin-left: 15px; + border: 1px solid #D8D8D8; + color: #474747; + border-radius: 3px; + -moz-border-radius: 3px; + -webkit-border-radius: 3px; padding: 3px 5px; } + #visitsLive .visitorRank { - padding:2px; - border:none; - margin-left:5px; + padding: 2px; + border: none; + margin-left: 5px; } + .hash { color: #BBB; font-size: 9pt; - margin-right:2px; + margin-right: 2px; } -.repeat { - font-weight: bold; + +.repeat { + font-weight: bold; border: 1px solid #444; - border-radius: 3px 3px 3px 3px; + border-radius: 3px 3px 3px 3px; -moz-border-radius: 3px 3px 3px 3px; -webkit-border-radius: 3px 3px 3px 3px; padding: 2px; diff --git a/plugins/Live/templates/scripts/live.js b/plugins/Live/templates/scripts/live.js index 13d34cce2f..bad5c82ee5 100644 --- a/plugins/Live/templates/scripts/live.js +++ b/plugins/Live/templates/scripts/live.js @@ -9,95 +9,95 @@ * jQuery Plugin for Live visitors widget */ -(function($) { +(function ($) { $.extend({ - liveWidget: new function() { - + liveWidget: new function () { + /** * Default settings for widgetPreview */ var settings = { - // Maximum numbers of rows to display in widget - maxRows: 10, - // minimal time in microseconds to wait between updates - interval: 3000, - // maximum time to wait between requests - maxInterval: 300000, - // url params to use for data request - dataUrlParams: null, - // callback triggered on a successfull update (content of widget changed) - onUpdate: null, - // speed for fade animation - fadeInSpeed: 'slow' + // Maximum numbers of rows to display in widget + maxRows: 10, + // minimal time in microseconds to wait between updates + interval: 3000, + // maximum time to wait between requests + maxInterval: 300000, + // url params to use for data request + dataUrlParams: null, + // callback triggered on a successfull update (content of widget changed) + onUpdate: null, + // speed for fade animation + fadeInSpeed: 'slow' }; - + var currentInterval, updated, updateInterval, liveWidget; var isStarted = true; - + /** * Update the widget */ function update() { - + // is content updated (eg new visits/views) updated = false; var ajaxRequest = new ajaxHelper(); ajaxRequest.addParams(settings.dataUrlParams, 'GET'); ajaxRequest.setFormat('html'); - ajaxRequest.setCallback( function(r) { + ajaxRequest.setCallback(function (r) { parseResponse(r); - + // add default interval to last interval if not updated or reset to default if so - if(!updated) { + if (!updated) { currentInterval += settings.interval; } else { currentInterval = settings.interval; - if(settings.onUpdate) settings.onUpdate(); + if (settings.onUpdate) settings.onUpdate(); } - + // check new interval doesn't reach the defined maximum - if(settings.maxInterval < currentInterval) { + if (settings.maxInterval < currentInterval) { currentInterval = settings.maxInterval; } - - if(isStarted) { + + if (isStarted) { window.clearTimeout(updateInterval); - if($(liveWidget).closest('body').length) { + if ($(liveWidget).closest('body').length) { updateInterval = window.setTimeout(update, currentInterval); } } }); ajaxRequest.send(false); }; - + /** * Parses the given response and updates the widget if newer content is available */ function parseResponse(data) { - if(!data || !data.length) { + if (!data || !data.length) { updated = false; return; } - + var items = $('li', $(data)); - for(var i=items.length;i--;){ + for (var i = items.length; i--;) { parseItem(items[i]); } }; - + /** * Parses the given item and updates or adds an entry to the list - * + * * @param item to parse */ function parseItem(item) { var visitId = $(item).attr('id'); - if($('#'+visitId, liveWidget).length) { - if($('#'+visitId, liveWidget).html() != $(item).html()) { + if ($('#' + visitId, liveWidget).length) { + if ($('#' + visitId, liveWidget).html() != $(item).html()) { updated = true; } - $('#'+visitId, liveWidget).remove(); + $('#' + visitId, liveWidget).remove(); $(liveWidget).prepend(item); } else { updated = true; @@ -106,65 +106,65 @@ $(item).fadeIn(settings.fadeInSpeed); } // remove rows if there are more than the maximum - $('li:gt('+(settings.maxRows-1)+')', liveWidget).remove(); + $('li:gt(' + (settings.maxRows - 1) + ')', liveWidget).remove(); }; - + /** * Constructor - * + * * @param object userSettings Settings to be used * @return void */ - this.construct = function(userSettings) { + this.construct = function (userSettings) { settings = jQuery.extend(settings, userSettings); - - if(!settings.dataUrlParams) { + + if (!settings.dataUrlParams) { console && console.error('liveWidget error: dataUrlParams needs to be defined in settings.'); return; } - + liveWidget = this; - + currentInterval = settings.interval; - + updateInterval = window.setTimeout(update, currentInterval); }; - + /** * Triggers an update for the widget - * + * * @return void */ - this.update = function() { + this.update = function () { update(); }; - + /** * Starts the automatic update cycle */ - this.start = function() { + this.start = function () { isStarted = true; currentInterval = 0; update(); }; - + /** * Stops the automatic update cycle */ - this.stop = function() { + this.stop = function () { isStarted = false; window.clearTimeout(updateInterval); }; - + /** * Set the interval for refresh */ - this.setInterval = function(interval) { + this.setInterval = function (interval) { currentInterval = interval; }; } }); - + /** * Makes liveWidget available with $().liveWidget() */ @@ -178,15 +178,13 @@ var pauseImage = "plugins/Live/templates/images/pause.gif"; var pauseDisabledImage = "plugins/Live/templates/images/pause_disabled.gif"; var playImage = "plugins/Live/templates/images/play.gif"; var playDisabledImage = "plugins/Live/templates/images/play_disabled.gif"; -function onClickPause() -{ - $('#pauseImage').attr('src', pauseImage); - $('#playImage').attr('src', playDisabledImage); - return $.liveWidget.stop(); +function onClickPause() { + $('#pauseImage').attr('src', pauseImage); + $('#playImage').attr('src', playDisabledImage); + return $.liveWidget.stop(); } -function onClickPlay() -{ - $('#playImage').attr('src', playImage); - $('#pauseImage').attr('src', pauseDisabledImage); - return $.liveWidget.start(); +function onClickPlay() { + $('#playImage').attr('src', playImage); + $('#pauseImage').attr('src', pauseDisabledImage); + return $.liveWidget.start(); } diff --git a/plugins/Live/templates/simpleLastVisitCount.tpl b/plugins/Live/templates/simpleLastVisitCount.tpl index bc893a93b0..65e2ae731e 100644 --- a/plugins/Live/templates/simpleLastVisitCount.tpl +++ b/plugins/Live/templates/simpleLastVisitCount.tpl @@ -1,121 +1,123 @@ {literal} - + {/literal}
-
-
{$visitors}
-
-
-
- {capture assign="visitsMessage"}{if $visits eq 1}{'General_OneVisit'|translate}{else}{'General_NVisits'|translate:$visits}{/if}{/capture} - {capture assign="actionsMessage"}{if $actions eq 1}{'General_OneAction'|translate}{else}{'VisitsSummary_NbActionsDescription'|translate:$actions}{/if}{/capture} - {capture assign="minutesMessage"}{if $lastMinutes eq 1}{'General_OneMinute'|translate}{else}{'General_NMinutes'|translate:$lastMinutes}{/if}{/capture} - - {'Live_SimpleRealTimeWidget_Message'|translate:$visitsMessage:$actionsMessage:$minutesMessage} -
+
+
{$visitors}
+
+
+ +
+ {capture assign="visitsMessage"}{if $visits eq 1}{'General_OneVisit'|translate}{else}{'General_NVisits'|translate:$visits}{/if}{/capture} + {capture assign="actionsMessage"}{if $actions eq 1}{'General_OneAction'|translate}{else}{'VisitsSummary_NbActionsDescription'|translate:$actions}{/if}{/capture} + {capture assign="minutesMessage"}{if $lastMinutes eq 1}{'General_OneMinute'|translate}{else}{'General_NMinutes'|translate:$lastMinutes}{/if}{/capture} + + {'Live_SimpleRealTimeWidget_Message'|translate:$visitsMessage:$actionsMessage:$minutesMessage} +
diff --git a/plugins/Live/templates/totalVisits.tpl b/plugins/Live/templates/totalVisits.tpl index 36d8558eb3..27c12873a0 100644 --- a/plugins/Live/templates/totalVisits.tpl +++ b/plugins/Live/templates/totalVisits.tpl @@ -1,26 +1,29 @@
- - - - - - - - - - - - - - - - - - - - -
-
{'General_Date'|translate}
-
{'General_ColumnNbVisits'|translate}
-
{'General_ColumnPageviews'|translate}
{'Live_LastHours'|translate:24}{$visitorsCountToday}{$pisToday}
{'Live_LastMinutes'|translate:30}{$visitorsCountHalfHour}{$pisHalfhour}
+ + + + + + + + + + + + + + + + + + + + +
+
{'General_Date'|translate}
+
+
{'General_ColumnNbVisits'|translate}
+
+
{'General_ColumnPageviews'|translate}
+
{'Live_LastHours'|translate:24}{$visitorsCountToday}{$pisToday}
{'Live_LastMinutes'|translate:30}{$visitorsCountHalfHour}{$pisHalfhour}
diff --git a/plugins/Live/templates/visitorLog.tpl b/plugins/Live/templates/visitorLog.tpl index f8b9183738..ff39c60179 100644 --- a/plugins/Live/templates/visitorLog.tpl +++ b/plugins/Live/templates/visitorLog.tpl @@ -1,316 +1,347 @@
{if !$isWidget} -

{if $javascriptVariablesToSet.filterEcommerce}{'Goals_EcommerceLog'|translate}{else}{'Live_VisitorLog'|translate}{/if}

- - {if !empty($reportDocumentation)} -

{$reportDocumentation}

- {/if} +

{if $javascriptVariablesToSet.filterEcommerce}{'Goals_EcommerceLog'|translate}{else}{'Live_VisitorLog'|translate}{/if}

+ {if !empty($reportDocumentation)} +

{$reportDocumentation}

+ {/if} {/if} {capture assign='displayVisitorsInOwnColumn'}{if $isWidget}0{else}1{/if}{/capture} {if isset($arrayDataTable.result) and $arrayDataTable.result == 'error'} - {$arrayDataTable.message} - {else} - {if count($arrayDataTable) == 0} - -
{'CoreHome_ThereIsNoDataForThisReport'|translate}
- {else} - + {$arrayDataTable.message} +{else} +{if count($arrayDataTable) == 0} + +
{'CoreHome_ThereIsNoDataForThisReport'|translate}
+{else} + + + + + + + {if $displayVisitorsInOwnColumn} + + {/if} + + + + + -
+
{'General_Date'|translate} +
+
+
{'General_Visitors'|translate} +
+
+
{'Live_Referrer_URL'|translate} +
+
+
{'General_ColumnNbActions'|translate} +
+
- - - - - {if $displayVisitorsInOwnColumn} - - {/if} - - - - - + {foreach from=$arrayDataTable item=visitor} -{foreach from=$arrayDataTable item=visitor} + {capture assign='visitorColumnContent'} +   + +   + +   + + {if $visitor.columns.visitorTypeIcon} + {if !empty($visitor.columns.visitorId)} + + {/if} +  - + + {if !empty($visitor.columns.visitorId)}{/if} + {/if} + + {if !$displayVisitorsInOwnColumn}

{/if} - {capture assign='visitorColumnContent'} -   -   -   - {if $visitor.columns.visitorTypeIcon} - {if !empty($visitor.columns.visitorId)} - +  {if $visitor.columns.visitConverted} + + + # + {$visitor.columns.goalConversions} + {if $visitor.columns.visitEcommerceStatusIcon} +  - + {/if} -  - - {if !empty($visitor.columns.visitorId)}{/if} - {/if} - - {if !$displayVisitorsInOwnColumn}

{/if} - -  {if $visitor.columns.visitConverted} - - - #{$visitor.columns.goalConversions} - {if $visitor.columns.visitEcommerceStatusIcon} -  - - {/if} - {/if} -
- {if $displayVisitorsInOwnColumn} - {if count($visitor.columns.pluginsIcons) > 0} -
- {'UserSettings_Plugins'|translate}: - {foreach from=$visitor.columns.pluginsIcons item=pluginIcon name=plugins} - {$pluginIcon.pluginName|capitalize:true} - {/foreach} - {/if} - {/if} - {/capture} - - {capture assign='visitorRow'} - - - + + + + {if $displayVisitorsInOwnColumn} + + {/if} - GPS (lat/long): {$visitor.columns.latitude|escape:'html'},{$visitor.columns.longitude|escape:'html'}{/if}">IP: {$visitor.columns.visitIp}{/if} - - {if (isset($visitor.columns.provider)&&$visitor.columns.provider!='IP')} -
- {'Provider_ColumnProvider'|translate}: - - {$visitor.columns.provider} - - {/if} - {if !empty($visitor.columns.customVariables)} -
- {foreach from=$visitor.columns.customVariables item=customVariable key=id} - {capture assign=name}customVariableName{$id}{/capture} - {capture assign=value}customVariableValue{$id}{/capture} -
{$customVariable.$name|truncate:30:"...":true|escape:'html'}{if strlen($customVariable.$value)>0}: {$customVariable.$value|truncate:50:"...":true|escape:'html'}{/if} - {/foreach} - {/if} - {if !$displayVisitorsInOwnColumn} -
- {$visitorColumnContent} - {/if} - - - {if $displayVisitorsInOwnColumn} - - {/if} - - - + - - {/capture} - - {if !$javascriptVariablesToSet.filterEcommerce - || !empty($visitorHasSomeEcommerceActivity)} - {$visitorRow} - {/if} -{/foreach} + {elseif empty($action.goalName)} + {* Page view / Download / Outlink *} + {if !empty($action.pageTitle)} + {if $action.type == 'search'}{/if} + {$action.pageTitle|unescape|urldecode|escape:'html'|truncate:80:"...":true} + {/if} + {if !empty($action.url)} + {if $action.type == 'action' && !empty($action.pageTitle)}
{/if} + {if $action.type == 'download' + || $action.type == 'outlink'} + + {/if} + {$action.url|escape:'html'|truncate:80:"...":true} + {elseif $action.type!='search'} +
+ {$javascriptVariablesToSet.pageUrlNotDefined} + {/if} + {else} + {* Goal conversion *} + + {$action.goalName|escape:'html'} + {if $action.revenue > 0}, {'Live_GoalRevenue'|translate}: + {$action.revenue|money:$javascriptVariablesToSet.idSite}{/if} + {/if} + + {/if} + {/foreach} + + + + {/capture} - -
-
{'General_Date'|translate}
-
{'General_Visitors'|translate}
-
{'Live_Referrer_URL'|translate}
-
{'General_ColumnNbActions'|translate}
- - {$visitor.columns.serverDatePrettyFirstAction} - {if $isWidget}
{else}-{/if} {$visitor.columns.serverTimePrettyFirstAction}
- {if !empty($visitor.columns.visitIp)}
+ {/foreach} + {/if} + {/if} + {/capture} + + {capture assign='visitorRow'} +
+ + {$visitor.columns.serverDatePrettyFirstAction} + {if $isWidget}
{else}-{/if} {$visitor.columns.serverTimePrettyFirstAction}
+ {if !empty($visitor.columns.visitIp)} +
+ + IP: {$visitor.columns.visitIp}{/if} + + {if (isset($visitor.columns.provider)&&$visitor.columns.provider!='IP')} +
+ {'Provider_ColumnProvider'|translate}: + + {$visitor.columns.provider} + + {/if} + {if !empty($visitor.columns.customVariables)} +
+ {foreach from=$visitor.columns.customVariables item=customVariable key=id} + {capture assign=name}customVariableName{$id}{/capture} + {capture assign=value}customVariableValue{$id}{/capture} +
+ {$customVariable.$name|truncate:30:"...":true|escape:'html'}{if strlen($customVariable.$value)>0}: {$customVariable.$value|truncate:50:"...":true|escape:'html'}{/if} + {/foreach} + {/if} + {if !$displayVisitorsInOwnColumn} +
+ {$visitorColumnContent} + {/if} +
+ {$visitorColumnContent} + - {$visitorColumnContent} - -
- {if $visitor.columns.referrerType == 'website'} - {'Referers_ColumnWebsite'|translate}: - - {$visitor.columns.referrerName|escape:'html'} - - {/if} - {if $visitor.columns.referrerType == 'campaign'} - {'Referers_ColumnCampaign'|translate} -
- {$visitor.columns.referrerName|escape:'html'} - {if !empty($visitor.columns.referrerKeyword)} - {$visitor.columns.referrerKeyword|escape:'html'}{/if} - {/if} - {if $visitor.columns.referrerType == 'search'} - {if !empty($visitor.columns.searchEngineIcon)} - {$visitor.columns.referrerName|escape:'html'} - {/if} - {$visitor.columns.referrerName|escape:'html'} - {if !empty($visitor.columns.referrerKeyword)}{'Referers_Keywords'|translate}: -
- - "{$visitor.columns.referrerKeyword|escape:'html'}" - {/if} - {capture assign='keyword'}{$visitor.columns.referrerKeyword|escape:'html'}{/capture} - {capture assign='searchName'}{$visitor.columns.referrerName|escape:"html"}{/capture} - {capture assign='position'}#{$visitor.columns.referrerKeywordPosition}{/capture} - {if !empty($visitor.columns.referrerKeywordPosition)}#{$visitor.columns.referrerKeywordPosition}{/if} - {/if} - {if $visitor.columns.referrerType == 'direct'}{'Referers_DirectEntry'|translate}{/if} -
-
- - {$visitor.columns.actionDetails|@count} - {if $visitor.columns.actionDetails|@count <= 1} - {'Live_Action'|translate} - {else} - {'Live_Actions'|translate} - {/if} - {if $visitor.columns.visitDuration > 0}- {$visitor.columns.visitDurationPretty}{/if} - -
-
    - {capture assign='visitorHasSomeEcommerceActivity'}0{/capture} - {foreach from=$visitor.columns.actionDetails item=action} - {capture assign='customVariablesTooltip'}{if !empty($action.customVariables)}{'CustomVariables_CustomVariables'|translate} - {foreach from=$action.customVariables item=customVariable key=id}{capture assign=name}customVariableName{$id}{/capture}{capture assign=value}customVariableValue{$id}{/capture} +
+
+ {if $visitor.columns.referrerType == 'website'} + {'Referers_ColumnWebsite'|translate}: + + {$visitor.columns.referrerName|escape:'html'} + + {/if} + {if $visitor.columns.referrerType == 'campaign'} + {'Referers_ColumnCampaign'|translate} +
+ {$visitor.columns.referrerName|escape:'html'} + {if !empty($visitor.columns.referrerKeyword)} - {$visitor.columns.referrerKeyword|escape:'html'}{/if} + {/if} + {if $visitor.columns.referrerType == 'search'} + {if !empty($visitor.columns.searchEngineIcon)} + {$visitor.columns.referrerName|escape:'html'} + {/if} + {$visitor.columns.referrerName|escape:'html'} + {if !empty($visitor.columns.referrerKeyword)}{'Referers_Keywords'|translate}: +
+ + "{$visitor.columns.referrerKeyword|escape:'html'}" + {/if} + {capture assign='keyword'}{$visitor.columns.referrerKeyword|escape:'html'}{/capture} + {capture assign='searchName'}{$visitor.columns.referrerName|escape:"html"}{/capture} + {capture assign='position'}#{$visitor.columns.referrerKeywordPosition}{/capture} + {if !empty($visitor.columns.referrerKeywordPosition)} + # + {$visitor.columns.referrerKeywordPosition}{/if} + {/if} + {if $visitor.columns.referrerType == 'direct'}{'Referers_DirectEntry'|translate}{/if} +
+
+ + {$visitor.columns.actionDetails|@count} + {if $visitor.columns.actionDetails|@count <= 1} + {'Live_Action'|translate} + {else} + {'Live_Actions'|translate} + {/if} + {if $visitor.columns.visitDuration > 0}- {$visitor.columns.visitDurationPretty}{/if} + +
+
    + {capture assign='visitorHasSomeEcommerceActivity'}0{/capture} + {foreach from=$visitor.columns.actionDetails item=action} + {capture assign='customVariablesTooltip'}{if !empty($action.customVariables)}{'CustomVariables_CustomVariables'|translate} + {foreach from=$action.customVariables item=customVariable key=id}{capture assign=name}customVariableName{$id}{/capture}{capture assign=value}customVariableValue{$id}{/capture} -- {$customVariable.$name|escape:'html'} {if strlen($customVariable.$value) > 0} = {$customVariable.$value|escape:'html'}{/if} - {/foreach}{/if} - {/capture} - {if !$javascriptVariablesToSet.filterEcommerce - || $action.type == 'ecommerceOrder' - || $action.type == 'ecommerceAbandonedCart'} -
  1. - {if $action.type == 'ecommerceOrder' || $action.type == 'ecommerceAbandonedCart'} - {* Ecommerce Abandoned Cart / Ecommerce Order *} - - - {if $action.type == 'ecommerceOrder'} - {capture assign='visitorHasSomeEcommerceActivity'}1{/capture} - {'Goals_EcommerceOrder'|translate} ({$action.orderId}) - {else}{'Goals_AbandonedCart'|translate} - - {* TODO: would be nice to have the icons Orders / Cart in the ecommerce log footer *} - {if $javascriptVariablesToSet.filterEcommerce == 2}{capture assign='visitorHasSomeEcommerceActivity'}1{/capture}{/if} - - {/if}
    - + {if $action.type == 'ecommerceOrder' || $action.type == 'ecommerceAbandonedCart'} + {* Ecommerce Abandoned Cart / Ecommerce Order *} + + {if $action.type == 'ecommerceOrder'} + {capture assign='visitorHasSomeEcommerceActivity'}1{/capture} + {'Goals_EcommerceOrder'|translate} + ({$action.orderId}) + {else}{'Goals_AbandonedCart'|translate} + + {* TODO: would be nice to have the icons Orders / Cart in the ecommerce log footer *} + {if $javascriptVariablesToSet.filterEcommerce == 2}{capture assign='visitorHasSomeEcommerceActivity'}1{/capture}{/if} + + {/if} +
    + {if $action.type == 'ecommerceOrder'} - {'Live_GoalRevenue'|translate}: - {else} - {capture assign='revenueLeft'}{'Live_GoalRevenue'|translate}{/capture}{'Goals_LeftInCart'|translate:$revenueLeft}: - {/if} - {$action.revenue|money:$javascriptVariablesToSet.idSite}{if $action.type == 'ecommerceOrder'}{/if}, - {'General_Quantity'|translate}: {$action.items} - - {* Ecommerce items in Cart/Order *} - {if !empty($action.itemDetails)} -
      - {foreach from=$action.itemDetails item=product} -
    • {$product.itemSKU|escape}{if !empty($product.itemName)}: {$product.itemName|escape}{/if}{if !empty($product.itemCategory)} ({$product.itemCategory|escape}){/if}, - {'General_Quantity'|translate}: {$product.quantity}, - {'General_Price'|translate}: {$product.price|money:$javascriptVariablesToSet.idSite} -
    • - {/foreach} -
    - {/if} + {else} + {capture assign='revenueLeft'}{'Live_GoalRevenue'|translate}{/capture}{'Goals_LeftInCart'|translate:$revenueLeft} + : + {/if} + {$action.revenue|money:$javascriptVariablesToSet.idSite}{if $action.type == 'ecommerceOrder'} + {/if}, + {'General_Quantity'|translate}: {$action.items} + + {* Ecommerce items in Cart/Order *} + {if !empty($action.itemDetails)} +
      + {foreach from=$action.itemDetails item=product} +
    • {$product.itemSKU|escape}{if !empty($product.itemName)}: {$product.itemName|escape}{/if}{if !empty($product.itemCategory)} ({$product.itemCategory|escape}){/if} + , + {'General_Quantity'|translate}: {$product.quantity}, + {'General_Price'|translate}: {$product.price|money:$javascriptVariablesToSet.idSite} +
    • + {/foreach} +
    + {/if}
    - - {elseif empty($action.goalName)} - {* Page view / Download / Outlink *} - {if !empty($action.pageTitle)} - {if $action.type == 'search'}{/if} - {$action.pageTitle|unescape|urldecode|escape:'html'|truncate:80:"...":true} - {/if} - {if !empty($action.url)} - {if $action.type == 'action' && !empty($action.pageTitle)}
    {/if} - {if $action.type == 'download' - || $action.type == 'outlink'} - - {/if} - {$action.url|escape:'html'|truncate:80:"...":true} - {elseif $action.type!='search'} -
    - {$javascriptVariablesToSet.pageUrlNotDefined} - {/if} - {else} - {* Goal conversion *} - - {$action.goalName|escape:'html'} - {if $action.revenue > 0}, {'Live_GoalRevenue'|translate}: {$action.revenue|money:$javascriptVariablesToSet.idSite}{/if} - {/if} -
  2. - {/if} - {/foreach} -
-
-{/if} + {if !$javascriptVariablesToSet.filterEcommerce + || !empty($visitorHasSomeEcommerceActivity)} + {$visitorRow} + {/if} + {/foreach} -{if $properties.show_footer} - {include file="CoreHome/templates/datatable_footer.tpl"} + + {/if} -{include file="CoreHome/templates/datatable_js.tpl"} - + {/literal} + {/if} {literal} - + {/literal}
diff --git a/plugins/Login/Auth.php b/plugins/Login/Auth.php index c8c58e7aaf..a7a8a2e490 100644 --- a/plugins/Login/Auth.php +++ b/plugins/Login/Auth.php @@ -15,106 +15,101 @@ */ class Piwik_Login_Auth implements Piwik_Auth { - protected $login = null; - protected $token_auth = null; + protected $login = null; + protected $token_auth = null; - /** - * Authentication module's name, e.g., "Login" - * - * @return string - */ - public function getName() - { - return 'Login'; - } + /** + * Authentication module's name, e.g., "Login" + * + * @return string + */ + public function getName() + { + return 'Login'; + } - /** - * Authenticates user - * - * @return Piwik_Auth_Result - */ - public function authenticate() - { - $rootLogin = Piwik_Config::getInstance()->superuser['login']; - $rootPassword = Piwik_Config::getInstance()->superuser['password']; - $rootToken = Piwik_UsersManager_API::getInstance()->getTokenAuth($rootLogin, $rootPassword); + /** + * Authenticates user + * + * @return Piwik_Auth_Result + */ + public function authenticate() + { + $rootLogin = Piwik_Config::getInstance()->superuser['login']; + $rootPassword = Piwik_Config::getInstance()->superuser['password']; + $rootToken = Piwik_UsersManager_API::getInstance()->getTokenAuth($rootLogin, $rootPassword); - if(is_null($this->login)) - { - if($this->token_auth === $rootToken) - { - return new Piwik_Auth_Result(Piwik_Auth_Result::SUCCESS_SUPERUSER_AUTH_CODE, $rootLogin, $this->token_auth ); - } + if (is_null($this->login)) { + if ($this->token_auth === $rootToken) { + return new Piwik_Auth_Result(Piwik_Auth_Result::SUCCESS_SUPERUSER_AUTH_CODE, $rootLogin, $this->token_auth); + } - $login = Piwik_FetchOne( - 'SELECT login - FROM '.Piwik_Common::prefixTable('user').' + $login = Piwik_FetchOne( + 'SELECT login + FROM ' . Piwik_Common::prefixTable('user') . ' WHERE token_auth = ?', - array($this->token_auth) - ); - if(!empty($login)) - { - return new Piwik_Auth_Result(Piwik_Auth_Result::SUCCESS, $login, $this->token_auth ); - } - } - else if(!empty($this->login)) - { - if($this->login === $rootLogin - && ($this->getHashTokenAuth($rootLogin, $rootToken) === $this->token_auth) - || $rootToken === $this->token_auth) - { - $this->setTokenAuth($rootToken); - return new Piwik_Auth_Result(Piwik_Auth_Result::SUCCESS_SUPERUSER_AUTH_CODE, $rootLogin, $this->token_auth ); - } + array($this->token_auth) + ); + if (!empty($login)) { + return new Piwik_Auth_Result(Piwik_Auth_Result::SUCCESS, $login, $this->token_auth); + } + } else if (!empty($this->login)) { + if ($this->login === $rootLogin + && ($this->getHashTokenAuth($rootLogin, $rootToken) === $this->token_auth) + || $rootToken === $this->token_auth + ) { + $this->setTokenAuth($rootToken); + return new Piwik_Auth_Result(Piwik_Auth_Result::SUCCESS_SUPERUSER_AUTH_CODE, $rootLogin, $this->token_auth); + } - $login = $this->login; - $userToken = Piwik_FetchOne( - 'SELECT token_auth - FROM '.Piwik_Common::prefixTable('user').' + $login = $this->login; + $userToken = Piwik_FetchOne( + 'SELECT token_auth + FROM ' . Piwik_Common::prefixTable('user') . ' WHERE login = ?', - array($login) - ); - if(!empty($userToken) - && (($this->getHashTokenAuth($login, $userToken) === $this->token_auth) - || $userToken === $this->token_auth)) - { - $this->setTokenAuth($userToken); - return new Piwik_Auth_Result(Piwik_Auth_Result::SUCCESS, $login, $userToken ); - } - } + array($login) + ); + if (!empty($userToken) + && (($this->getHashTokenAuth($login, $userToken) === $this->token_auth) + || $userToken === $this->token_auth) + ) { + $this->setTokenAuth($userToken); + return new Piwik_Auth_Result(Piwik_Auth_Result::SUCCESS, $login, $userToken); + } + } - return new Piwik_Auth_Result( Piwik_Auth_Result::FAILURE, $this->login, $this->token_auth ); - } + return new Piwik_Auth_Result(Piwik_Auth_Result::FAILURE, $this->login, $this->token_auth); + } - /** - * Accessor to set login name - * - * @param string $login user login - */ - public function setLogin($login) - { - $this->login = $login; - } + /** + * Accessor to set login name + * + * @param string $login user login + */ + public function setLogin($login) + { + $this->login = $login; + } - /** - * Accessor to set authentication token - * - * @param string $token_auth authentication token - */ - public function setTokenAuth($token_auth) - { - $this->token_auth = $token_auth; - } + /** + * Accessor to set authentication token + * + * @param string $token_auth authentication token + */ + public function setTokenAuth($token_auth) + { + $this->token_auth = $token_auth; + } - /** - * Accessor to compute the hashed authentication token - * - * @param string $login user login - * @param string $token_auth authentication token - * @return string hashed authentication token - */ - public function getHashTokenAuth($login, $token_auth) - { - return md5($login . $token_auth); - } + /** + * Accessor to compute the hashed authentication token + * + * @param string $login user login + * @param string $token_auth authentication token + * @return string hashed authentication token + */ + public function getHashTokenAuth($login, $token_auth) + { + return md5($login . $token_auth); + } } diff --git a/plugins/Login/Controller.php b/plugins/Login/Controller.php index 7031ca4307..912c5f441d 100644 --- a/plugins/Login/Controller.php +++ b/plugins/Login/Controller.php @@ -16,510 +16,470 @@ */ class Piwik_Login_Controller extends Piwik_Controller { - /** - * Generate hash on user info and password - * - * @param string $userinfo User name, email, etc - * @param string $password - * @return string - */ - private function generateHash($userInfo, $password) - { - // mitigate rainbow table attack - $passwordLen = strlen($password) / 2; - $hash = Piwik_Common::hash( - $userInfo . substr($password, 0, $passwordLen) - . Piwik_Common::getSalt() . substr($password, $passwordLen) - ); - return $hash; - } - - /** - * Default action - * - * @param none - * @return void - */ - function index() - { - $this->login(); - } - - /** - * Login form - * - * @param string $messageNoAccess Access error message - * @param string $currentUrl Current URL - * @return void - */ - function login($messageNoAccess = null, $infoMessage = false) - { - self::checkForceSslLogin(); - - $form = new Piwik_Login_FormLogin(); - if($form->validate()) - { - $nonce = $form->getSubmitValue('form_nonce'); - if(Piwik_Nonce::verifyNonce('Piwik_Login.login', $nonce)) - { - $login = $form->getSubmitValue('form_login'); - $password = $form->getSubmitValue('form_password'); - $rememberMe = $form->getSubmitValue('form_rememberme') == '1'; - $md5Password = md5($password); - try { - $this->authenticateAndRedirect($login, $md5Password, $rememberMe); - } catch(Exception $e) { - $messageNoAccess = $e->getMessage(); - } - } - else - { - $messageNoAccess = $this->getMessageExceptionNoAccess(); - } - } - - $view = Piwik_View::factory('login'); - $view->AccessErrorString = $messageNoAccess; - $view->infoMessage = nl2br($infoMessage); - $view->addForm( $form ); - $this->configureView($view); - self::setHostValidationVariablesView($view); - echo $view->render(); - } - - /** - * Configure common view properties - * - * @param Piwik_View $view - */ - private function configureView($view) - { - $this->setBasicVariablesView($view); - - $view->linkTitle = Piwik::getRandomTitle(); - - $view->forceSslLogin = Piwik_Config::getInstance()->General['force_ssl_login']; - - // crsf token: don't trust the submitted value; generate/fetch it from session data - $view->nonce = Piwik_Nonce::getNonce('Piwik_Login.login'); - } - - /** - * Form-less login - * @see how to use it on http://piwik.org/faq/how-to/#faq_30 - * @throws Exception - * @return void - */ - function logme() - { - self::checkForceSslLogin(); - - $password = Piwik_Common::getRequestVar('password', null, 'string'); - if(strlen($password) != 32) - { - throw new Exception(Piwik_TranslateException('Login_ExceptionPasswordMD5HashExpected')); - } - - $login = Piwik_Common::getRequestVar('login', null, 'string'); - if($login == Piwik_Config::getInstance()->superuser['login']) - { - throw new Exception(Piwik_TranslateException('Login_ExceptionInvalidSuperUserAuthenticationMethod', array("logme"))); - } - - $currentUrl = 'index.php'; - - if(($idSite = Piwik_Common::getRequestVar('idSite', false, 'int')) !== false) - { - $currentUrl .= '?idSite='.$idSite; - } - - $urlToRedirect = Piwik_Common::getRequestVar('url', $currentUrl, 'string'); - $urlToRedirect = Piwik_Common::unsanitizeInputValue($urlToRedirect); - - $this->authenticateAndRedirect($login, $password, false, $urlToRedirect); - } - - /** - * Authenticate user and password. Redirect if successful. - * - * @param string $login user name - * @param string $md5Password md5 hash of password - * @param bool $rememberMe Remember me? - * @param string $urlToRedirect URL to redirect to, if successfully authenticated - * @return string failure message if unable to authenticate - */ - protected function authenticateAndRedirect($login, $md5Password, $rememberMe, $urlToRedirect = 'index.php') - { - $info = array( 'login' => $login, - 'md5Password' => $md5Password, - 'rememberMe' => $rememberMe, - ); - Piwik_Nonce::discardNonce('Piwik_Login.login'); - Piwik_PostEvent('Login.initSession', $info); - Piwik_Url::redirectToUrl($urlToRedirect); - } - - protected function getMessageExceptionNoAccess() - { - $message = Piwik_Translate('Login_InvalidNonceOrHeadersOrReferer', array('', '')); - // Should mention trusted_hosts or link to FAQ - return $message; - } - - /** - * Reset password action. Stores new password as hash and sends email - * to confirm use. - * - * @param none - * @return void - */ - function resetPassword() - { - self::checkForceSslLogin(); - - $infoMessage = null; - $formErrors = null; - - $form = new Piwik_Login_FormResetPassword(); - if($form->validate()) - { - $nonce = $form->getSubmitValue('form_nonce'); - if(Piwik_Nonce::verifyNonce('Piwik_Login.login', $nonce)) - { - $formErrors = $this->resetPasswordFirstStep($form); - if (empty($formErrors)) - { - $infoMessage = Piwik_Translate('Login_ConfirmationLinkSent'); - } - } - else - { - $formErrors = array($this->getMessageExceptionNoAccess()); - } - } - else - { - // if invalid, display error - $formData = $form->getFormData(); - $formErrors = $formData['errors']; - } - - $view = Piwik_View::factory('message'); - $view->infoMessage = $infoMessage; - $view->formErrors = $formErrors; - echo $view->render(); - } - - /** - * Saves password reset info and sends confirmation email. - * - * @return array Error message(s) if an error occurs. - */ - private function resetPasswordFirstStep( $form ) - { - $loginMail = $form->getSubmitValue('form_login'); - $token = $form->getSubmitValue('form_token'); - $password = $form->getSubmitValue('form_password'); - - // check the password - try - { - Piwik_UsersManager::checkPassword($password); - } - catch (Exception $ex) - { - return array($ex->getMessage()); - } - - // get the user's login - if ($loginMail === 'anonymous') - { - return array(Piwik_Translate('Login_InvalidUsernameEmail')); - } - - $user = self::getUserInformation($loginMail); - if ($user === null) - { - return array(Piwik_Translate('Login_InvalidUsernameEmail')); - } - - $login = $user['login']; - - // if valid, store password information in options table, then... - Piwik_Login::savePasswordResetInfo($login, $password); - - // ... send email with confirmation link - try - { - $this->sendEmailConfirmationLink($user); - } - catch (Exception $ex) - { - // remove password reset info - Piwik_Login::removePasswordResetInfo($login); - - return array($ex->getMessage().'
'.Piwik_Translate('Login_ContactAdmin')); - } - - return null; - } - - /** - * Sends email confirmation link for a password reset request. - * - * @param array $user User info for the requested password reset. - */ - private function sendEmailConfirmationLink( $user ) - { - $login = $user['login']; - $email = $user['email']; - - // construct a password reset token from user information - $resetToken = self::generatePasswordResetToken($user); - - $ip = Piwik_IP::getIpFromHeader(); - $url = Piwik_Url::getCurrentUrlWithoutQueryString() - . "?module=Login&action=confirmResetPassword&login=".urlencode($login) - . "&resetToken=".urlencode($resetToken); - - // send email with new password - $mail = new Piwik_Mail(); - $mail->addTo($email, $login); - $mail->setSubject(Piwik_Translate('Login_MailTopicPasswordChange')); - $bodyText = str_replace( - '\n', - "\n", - sprintf(Piwik_Translate('Login_MailPasswordChangeBody'), $login, $ip, $url) - ) . "\n"; - $mail->setBodyText($bodyText); - - $fromEmailName = Piwik_Config::getInstance()->General['login_password_recovery_email_name']; - $fromEmailAddress = Piwik_Config::getInstance()->General['login_password_recovery_email_address']; - $mail->setFrom($fromEmailAddress, $fromEmailName); - @$mail->send(); - } - - /** - * Password reset confirmation action. Finishes the password reset process. - * Users visit this action from a link supplied in an email. - */ - public function confirmResetPassword() - { - $errorMessage = null; - - $login = Piwik_Common::getRequestVar('login', ''); - $resetToken = Piwik_Common::getRequestVar('resetToken', ''); - - try - { - // get password reset info & user info - $user = self::getUserInformation($login); - if ($user === null) - { - throw new Exception(Piwik_Translate('Login_InvalidUsernameEmail')); - } - - // check that the reset token is valid - $resetPassword = Piwik_Login::getPasswordToResetTo($login); - if ($resetPassword === false || !self::isValidToken($resetToken, $user)) - { - throw new Exception(Piwik_Translate('Login_InvalidOrExpiredToken')); - } - - // reset password of user - $this->setNewUserPassword($user, $resetPassword); - } - catch (Exception $ex) - { - $errorMessage = $ex->getMessage(); - } - - if (is_null($errorMessage)) // if success, show login w/ success message - { - $this->redirectToIndex('Login', 'resetPasswordSuccess'); - } - else - { - // show login page w/ error. this will keep the token in the URL - return $this->login($errorMessage); - } - } - - /** - * Sets the password for a user. - * - * @param array $user User info. - * @param string $passwordHash The hashed password to use. - */ - private function setNewUserPassword( $user, $passwordHash ) - { - if (strlen($passwordHash) !== 32) // sanity check - { - throw new Exception( - "setNewUserPassword called w/ incorrect password hash. Something has gone terribly wrong."); - } - - if( $user['email'] == Piwik::getSuperUserEmail() ) - { - if(!Piwik_Config::getInstance()->isFileWritable()) - { - throw new Exception(Piwik_Translate('General_ConfigFileIsNotWritable', array("(config/config.ini.php)","
"))); - } - - $user['password'] = $passwordHash; - Piwik_Config::getInstance()->superuser = $user; - Piwik_Config::getInstance()->forceSave(); - } - else - { - Piwik_UsersManager_API::getInstance()->updateUser( - $user['login'], $passwordHash, $email = false, $alias = false, $isPasswordHashed = true); - } - } - - /** - * The action used after a password is successfully reset. Displays the login - * screen with an extra message. A separate action is used instead of returning - * the HTML in confirmResetPassword so the resetToken won't be in the URL. - */ - public function resetPasswordSuccess() - { - return $this->login($errorMessage = null, $infoMessage = Piwik_Translate('Login_PasswordChanged')); - } - - /** - * Get user information - * - * @param string $loginMail user login or email address - * @return array ("login" => '...', "email" => '...', "password" => '...') or null, if user not found - */ - protected function getUserInformation($loginMail) - { - Piwik::setUserIsSuperUser(); - - $user = null; - if( $loginMail == Piwik::getSuperUserEmail() - || $loginMail == Piwik_Config::getInstance()->superuser['login'] ) - { - $user = array( - 'login' => Piwik_Config::getInstance()->superuser['login'], - 'email' => Piwik::getSuperUserEmail(), - 'password' => Piwik_Config::getInstance()->superuser['password'], - ); - } - else if( Piwik_UsersManager_API::getInstance()->userExists($loginMail) ) - { - $user = Piwik_UsersManager_API::getInstance()->getUser($loginMail); - } - else if( Piwik_UsersManager_API::getInstance()->userEmailExists($loginMail) ) - { - $user = Piwik_UsersManager_API::getInstance()->getUserByEmail($loginMail); - } - - return $user; - } - - /** - * Generate a password reset token. Expires in (roughly) 24 hours. - * - * @param array user information - * @param int $timestamp Unix timestamp - * @return string generated token - */ - protected function generatePasswordResetToken($user, $timestamp = null) - { - /* - * Piwik does not store the generated password reset token. - * This avoids a database schema change and SQL queries to store, retrieve, and purge (expired) tokens. - */ - if(!$timestamp) - { - $timestamp = time() + 24*60*60; /* +24 hrs */ - } - - $expiry = strftime('%Y%m%d%H', $timestamp); - $token = $this->generateHash( - $expiry . $user['login'] . $user['email'], - $user['password'] - ); - return $token; - } - - /** - * Validate token. - * - * @param string $token - * @param array $user user information - * @return bool true if valid, false otherwise - */ - protected function isValidToken($token, $user) - { - $now = time(); - - // token valid for 24 hrs (give or take, due to the coarse granularity in our strftime format string) - for($i = 0; $i <= 24; $i++) - { - $generatedToken = self::generatePasswordResetToken($user, $now + $i*60*60); - if($generatedToken === $token) - { - return true; - } - } - - // fails if token is invalid, expired, password already changed, other user information has changed, ... - return false; - } - - /** - * Clear session information - * - * @param none - * @return void - */ - static public function clearSession() - { - $authCookieName = Piwik_Config::getInstance()->General['login_cookie_name']; - $cookie = new Piwik_Cookie($authCookieName); - $cookie->delete(); - - Piwik_Session::expireSessionCookie(); - } - - /** - * Logout current user - * - * @param none - * @return void - */ - public function logout() - { - self::clearSession(); - - $logoutUrl = @Piwik_Config::getInstance()->General['login_logout_url']; - if(empty($logoutUrl)) { - Piwik::redirectToModule('CoreHome'); - } else { - Piwik_Url::redirectToUrl($logoutUrl); - } - } - - /** - * Check force_ssl_login and redirect if connection isn't secure and not using a reverse proxy - * - * @param none - * @return void - */ - protected function checkForceSslLogin() - { - $forceSslLogin = Piwik_Config::getInstance()->General['force_ssl_login']; - if($forceSslLogin - && !Piwik::isHttps()) - { - $url = 'https://' - . Piwik_Url::getCurrentHost() - . Piwik_Url::getCurrentScriptName() - . Piwik_Url::getCurrentQueryString(); - Piwik_Url::redirectToUrl($url); - } - } + /** + * Generate hash on user info and password + * + * @param string $userinfo User name, email, etc + * @param string $password + * @return string + */ + private function generateHash($userInfo, $password) + { + // mitigate rainbow table attack + $passwordLen = strlen($password) / 2; + $hash = Piwik_Common::hash( + $userInfo . substr($password, 0, $passwordLen) + . Piwik_Common::getSalt() . substr($password, $passwordLen) + ); + return $hash; + } + + /** + * Default action + * + * @param none + * @return void + */ + function index() + { + $this->login(); + } + + /** + * Login form + * + * @param string $messageNoAccess Access error message + * @param string $currentUrl Current URL + * @return void + */ + function login($messageNoAccess = null, $infoMessage = false) + { + self::checkForceSslLogin(); + + $form = new Piwik_Login_FormLogin(); + if ($form->validate()) { + $nonce = $form->getSubmitValue('form_nonce'); + if (Piwik_Nonce::verifyNonce('Piwik_Login.login', $nonce)) { + $login = $form->getSubmitValue('form_login'); + $password = $form->getSubmitValue('form_password'); + $rememberMe = $form->getSubmitValue('form_rememberme') == '1'; + $md5Password = md5($password); + try { + $this->authenticateAndRedirect($login, $md5Password, $rememberMe); + } catch (Exception $e) { + $messageNoAccess = $e->getMessage(); + } + } else { + $messageNoAccess = $this->getMessageExceptionNoAccess(); + } + } + + $view = Piwik_View::factory('login'); + $view->AccessErrorString = $messageNoAccess; + $view->infoMessage = nl2br($infoMessage); + $view->addForm($form); + $this->configureView($view); + self::setHostValidationVariablesView($view); + echo $view->render(); + } + + /** + * Configure common view properties + * + * @param Piwik_View $view + */ + private function configureView($view) + { + $this->setBasicVariablesView($view); + + $view->linkTitle = Piwik::getRandomTitle(); + + $view->forceSslLogin = Piwik_Config::getInstance()->General['force_ssl_login']; + + // crsf token: don't trust the submitted value; generate/fetch it from session data + $view->nonce = Piwik_Nonce::getNonce('Piwik_Login.login'); + } + + /** + * Form-less login + * @see how to use it on http://piwik.org/faq/how-to/#faq_30 + * @throws Exception + * @return void + */ + function logme() + { + self::checkForceSslLogin(); + + $password = Piwik_Common::getRequestVar('password', null, 'string'); + if (strlen($password) != 32) { + throw new Exception(Piwik_TranslateException('Login_ExceptionPasswordMD5HashExpected')); + } + + $login = Piwik_Common::getRequestVar('login', null, 'string'); + if ($login == Piwik_Config::getInstance()->superuser['login']) { + throw new Exception(Piwik_TranslateException('Login_ExceptionInvalidSuperUserAuthenticationMethod', array("logme"))); + } + + $currentUrl = 'index.php'; + + if (($idSite = Piwik_Common::getRequestVar('idSite', false, 'int')) !== false) { + $currentUrl .= '?idSite=' . $idSite; + } + + $urlToRedirect = Piwik_Common::getRequestVar('url', $currentUrl, 'string'); + $urlToRedirect = Piwik_Common::unsanitizeInputValue($urlToRedirect); + + $this->authenticateAndRedirect($login, $password, false, $urlToRedirect); + } + + /** + * Authenticate user and password. Redirect if successful. + * + * @param string $login user name + * @param string $md5Password md5 hash of password + * @param bool $rememberMe Remember me? + * @param string $urlToRedirect URL to redirect to, if successfully authenticated + * @return string failure message if unable to authenticate + */ + protected function authenticateAndRedirect($login, $md5Password, $rememberMe, $urlToRedirect = 'index.php') + { + $info = array('login' => $login, + 'md5Password' => $md5Password, + 'rememberMe' => $rememberMe, + ); + Piwik_Nonce::discardNonce('Piwik_Login.login'); + Piwik_PostEvent('Login.initSession', $info); + Piwik_Url::redirectToUrl($urlToRedirect); + } + + protected function getMessageExceptionNoAccess() + { + $message = Piwik_Translate('Login_InvalidNonceOrHeadersOrReferer', array('', '')); + // Should mention trusted_hosts or link to FAQ + return $message; + } + + /** + * Reset password action. Stores new password as hash and sends email + * to confirm use. + * + * @param none + * @return void + */ + function resetPassword() + { + self::checkForceSslLogin(); + + $infoMessage = null; + $formErrors = null; + + $form = new Piwik_Login_FormResetPassword(); + if ($form->validate()) { + $nonce = $form->getSubmitValue('form_nonce'); + if (Piwik_Nonce::verifyNonce('Piwik_Login.login', $nonce)) { + $formErrors = $this->resetPasswordFirstStep($form); + if (empty($formErrors)) { + $infoMessage = Piwik_Translate('Login_ConfirmationLinkSent'); + } + } else { + $formErrors = array($this->getMessageExceptionNoAccess()); + } + } else { + // if invalid, display error + $formData = $form->getFormData(); + $formErrors = $formData['errors']; + } + + $view = Piwik_View::factory('message'); + $view->infoMessage = $infoMessage; + $view->formErrors = $formErrors; + echo $view->render(); + } + + /** + * Saves password reset info and sends confirmation email. + * + * @return array Error message(s) if an error occurs. + */ + private function resetPasswordFirstStep($form) + { + $loginMail = $form->getSubmitValue('form_login'); + $token = $form->getSubmitValue('form_token'); + $password = $form->getSubmitValue('form_password'); + + // check the password + try { + Piwik_UsersManager::checkPassword($password); + } catch (Exception $ex) { + return array($ex->getMessage()); + } + + // get the user's login + if ($loginMail === 'anonymous') { + return array(Piwik_Translate('Login_InvalidUsernameEmail')); + } + + $user = self::getUserInformation($loginMail); + if ($user === null) { + return array(Piwik_Translate('Login_InvalidUsernameEmail')); + } + + $login = $user['login']; + + // if valid, store password information in options table, then... + Piwik_Login::savePasswordResetInfo($login, $password); + + // ... send email with confirmation link + try { + $this->sendEmailConfirmationLink($user); + } catch (Exception $ex) { + // remove password reset info + Piwik_Login::removePasswordResetInfo($login); + + return array($ex->getMessage() . '
' . Piwik_Translate('Login_ContactAdmin')); + } + + return null; + } + + /** + * Sends email confirmation link for a password reset request. + * + * @param array $user User info for the requested password reset. + */ + private function sendEmailConfirmationLink($user) + { + $login = $user['login']; + $email = $user['email']; + + // construct a password reset token from user information + $resetToken = self::generatePasswordResetToken($user); + + $ip = Piwik_IP::getIpFromHeader(); + $url = Piwik_Url::getCurrentUrlWithoutQueryString() + . "?module=Login&action=confirmResetPassword&login=" . urlencode($login) + . "&resetToken=" . urlencode($resetToken); + + // send email with new password + $mail = new Piwik_Mail(); + $mail->addTo($email, $login); + $mail->setSubject(Piwik_Translate('Login_MailTopicPasswordChange')); + $bodyText = str_replace( + '\n', + "\n", + sprintf(Piwik_Translate('Login_MailPasswordChangeBody'), $login, $ip, $url) + ) . "\n"; + $mail->setBodyText($bodyText); + + $fromEmailName = Piwik_Config::getInstance()->General['login_password_recovery_email_name']; + $fromEmailAddress = Piwik_Config::getInstance()->General['login_password_recovery_email_address']; + $mail->setFrom($fromEmailAddress, $fromEmailName); + @$mail->send(); + } + + /** + * Password reset confirmation action. Finishes the password reset process. + * Users visit this action from a link supplied in an email. + */ + public function confirmResetPassword() + { + $errorMessage = null; + + $login = Piwik_Common::getRequestVar('login', ''); + $resetToken = Piwik_Common::getRequestVar('resetToken', ''); + + try { + // get password reset info & user info + $user = self::getUserInformation($login); + if ($user === null) { + throw new Exception(Piwik_Translate('Login_InvalidUsernameEmail')); + } + + // check that the reset token is valid + $resetPassword = Piwik_Login::getPasswordToResetTo($login); + if ($resetPassword === false || !self::isValidToken($resetToken, $user)) { + throw new Exception(Piwik_Translate('Login_InvalidOrExpiredToken')); + } + + // reset password of user + $this->setNewUserPassword($user, $resetPassword); + } catch (Exception $ex) { + $errorMessage = $ex->getMessage(); + } + + if (is_null($errorMessage)) // if success, show login w/ success message + { + $this->redirectToIndex('Login', 'resetPasswordSuccess'); + } else { + // show login page w/ error. this will keep the token in the URL + return $this->login($errorMessage); + } + } + + /** + * Sets the password for a user. + * + * @param array $user User info. + * @param string $passwordHash The hashed password to use. + */ + private function setNewUserPassword($user, $passwordHash) + { + if (strlen($passwordHash) !== 32) // sanity check + { + throw new Exception( + "setNewUserPassword called w/ incorrect password hash. Something has gone terribly wrong."); + } + + if ($user['email'] == Piwik::getSuperUserEmail()) { + if (!Piwik_Config::getInstance()->isFileWritable()) { + throw new Exception(Piwik_Translate('General_ConfigFileIsNotWritable', array("(config/config.ini.php)", "
"))); + } + + $user['password'] = $passwordHash; + Piwik_Config::getInstance()->superuser = $user; + Piwik_Config::getInstance()->forceSave(); + } else { + Piwik_UsersManager_API::getInstance()->updateUser( + $user['login'], $passwordHash, $email = false, $alias = false, $isPasswordHashed = true); + } + } + + /** + * The action used after a password is successfully reset. Displays the login + * screen with an extra message. A separate action is used instead of returning + * the HTML in confirmResetPassword so the resetToken won't be in the URL. + */ + public function resetPasswordSuccess() + { + return $this->login($errorMessage = null, $infoMessage = Piwik_Translate('Login_PasswordChanged')); + } + + /** + * Get user information + * + * @param string $loginMail user login or email address + * @return array ("login" => '...', "email" => '...', "password" => '...') or null, if user not found + */ + protected function getUserInformation($loginMail) + { + Piwik::setUserIsSuperUser(); + + $user = null; + if ($loginMail == Piwik::getSuperUserEmail() + || $loginMail == Piwik_Config::getInstance()->superuser['login'] + ) { + $user = array( + 'login' => Piwik_Config::getInstance()->superuser['login'], + 'email' => Piwik::getSuperUserEmail(), + 'password' => Piwik_Config::getInstance()->superuser['password'], + ); + } else if (Piwik_UsersManager_API::getInstance()->userExists($loginMail)) { + $user = Piwik_UsersManager_API::getInstance()->getUser($loginMail); + } else if (Piwik_UsersManager_API::getInstance()->userEmailExists($loginMail)) { + $user = Piwik_UsersManager_API::getInstance()->getUserByEmail($loginMail); + } + + return $user; + } + + /** + * Generate a password reset token. Expires in (roughly) 24 hours. + * + * @param array user information + * @param int $timestamp Unix timestamp + * @return string generated token + */ + protected function generatePasswordResetToken($user, $timestamp = null) + { + /* + * Piwik does not store the generated password reset token. + * This avoids a database schema change and SQL queries to store, retrieve, and purge (expired) tokens. + */ + if (!$timestamp) { + $timestamp = time() + 24 * 60 * 60; /* +24 hrs */ + } + + $expiry = strftime('%Y%m%d%H', $timestamp); + $token = $this->generateHash( + $expiry . $user['login'] . $user['email'], + $user['password'] + ); + return $token; + } + + /** + * Validate token. + * + * @param string $token + * @param array $user user information + * @return bool true if valid, false otherwise + */ + protected function isValidToken($token, $user) + { + $now = time(); + + // token valid for 24 hrs (give or take, due to the coarse granularity in our strftime format string) + for ($i = 0; $i <= 24; $i++) { + $generatedToken = self::generatePasswordResetToken($user, $now + $i * 60 * 60); + if ($generatedToken === $token) { + return true; + } + } + + // fails if token is invalid, expired, password already changed, other user information has changed, ... + return false; + } + + /** + * Clear session information + * + * @param none + * @return void + */ + static public function clearSession() + { + $authCookieName = Piwik_Config::getInstance()->General['login_cookie_name']; + $cookie = new Piwik_Cookie($authCookieName); + $cookie->delete(); + + Piwik_Session::expireSessionCookie(); + } + + /** + * Logout current user + * + * @param none + * @return void + */ + public function logout() + { + self::clearSession(); + + $logoutUrl = @Piwik_Config::getInstance()->General['login_logout_url']; + if (empty($logoutUrl)) { + Piwik::redirectToModule('CoreHome'); + } else { + Piwik_Url::redirectToUrl($logoutUrl); + } + } + + /** + * Check force_ssl_login and redirect if connection isn't secure and not using a reverse proxy + * + * @param none + * @return void + */ + protected function checkForceSslLogin() + { + $forceSslLogin = Piwik_Config::getInstance()->General['force_ssl_login']; + if ($forceSslLogin + && !Piwik::isHttps() + ) { + $url = 'https://' + . Piwik_Url::getCurrentHost() + . Piwik_Url::getCurrentScriptName() + . Piwik_Url::getCurrentQueryString(); + Piwik_Url::redirectToUrl($url); + } + } } diff --git a/plugins/Login/FormLogin.php b/plugins/Login/FormLogin.php index 46c02f2f91..a90146f47e 100644 --- a/plugins/Login/FormLogin.php +++ b/plugins/Login/FormLogin.php @@ -15,28 +15,28 @@ */ class Piwik_Login_FormLogin extends Piwik_QuickForm2 { - function __construct( $id = 'login_form', $method = 'post', $attributes = null, $trackSubmit = false) - { - parent::__construct($id, $method, $attributes, $trackSubmit); - } + function __construct($id = 'login_form', $method = 'post', $attributes = null, $trackSubmit = false) + { + parent::__construct($id, $method, $attributes, $trackSubmit); + } - function init() - { - $this->addElement('text', 'form_login') - ->addRule('required', Piwik_Translate('General_Required', Piwik_Translate('General_Username'))); + function init() + { + $this->addElement('text', 'form_login') + ->addRule('required', Piwik_Translate('General_Required', Piwik_Translate('General_Username'))); - $this->addElement('password', 'form_password') - ->addRule('required', Piwik_Translate('General_Required', Piwik_Translate('Login_Password'))); + $this->addElement('password', 'form_password') + ->addRule('required', Piwik_Translate('General_Required', Piwik_Translate('Login_Password'))); - $this->addElement('hidden', 'form_nonce'); + $this->addElement('hidden', 'form_nonce'); - $this->addElement('checkbox', 'form_rememberme'); + $this->addElement('checkbox', 'form_rememberme'); - $this->addElement('submit', 'submit'); + $this->addElement('submit', 'submit'); - // default values - $this->addDataSource(new HTML_QuickForm2_DataSource_Array(array( - 'form_rememberme' => 0, - ))); - } + // default values + $this->addDataSource(new HTML_QuickForm2_DataSource_Array(array( + 'form_rememberme' => 0, + ))); + } } diff --git a/plugins/Login/FormResetPassword.php b/plugins/Login/FormResetPassword.php index 88791060d4..0411beddbb 100644 --- a/plugins/Login/FormResetPassword.php +++ b/plugins/Login/FormResetPassword.php @@ -15,25 +15,25 @@ */ class Piwik_Login_FormResetPassword extends Piwik_QuickForm2 { - function __construct( $id = 'resetpasswordform', $method = 'post', $attributes = null, $trackSubmit = false) - { - parent::__construct($id, $method, $attributes, $trackSubmit); - } + function __construct($id = 'resetpasswordform', $method = 'post', $attributes = null, $trackSubmit = false) + { + parent::__construct($id, $method, $attributes, $trackSubmit); + } - function init() - { - $this->addElement('text', 'form_login') - ->addRule('required', Piwik_Translate('General_Required', Piwik_Translate('General_Username'))); + function init() + { + $this->addElement('text', 'form_login') + ->addRule('required', Piwik_Translate('General_Required', Piwik_Translate('General_Username'))); - $password = $this->addElement('password', 'form_password'); - $password->addRule('required', Piwik_Translate('General_Required', Piwik_Translate('Login_Password'))); + $password = $this->addElement('password', 'form_password'); + $password->addRule('required', Piwik_Translate('General_Required', Piwik_Translate('Login_Password'))); - $passwordBis = $this->addElement('password', 'form_password_bis'); - $passwordBis->addRule('required', Piwik_Translate('General_Required', Piwik_Translate('Login_PasswordRepeat'))); - $passwordBis->addRule('eq', Piwik_Translate( 'Login_PasswordsDoNotMatch'), $password); + $passwordBis = $this->addElement('password', 'form_password_bis'); + $passwordBis->addRule('required', Piwik_Translate('General_Required', Piwik_Translate('Login_PasswordRepeat'))); + $passwordBis->addRule('eq', Piwik_Translate('Login_PasswordsDoNotMatch'), $password); - $this->addElement('hidden', 'form_nonce'); + $this->addElement('hidden', 'form_nonce'); - $this->addElement('submit', 'submit'); - } + $this->addElement('submit', 'submit'); + } } diff --git a/plugins/Login/Login.php b/plugins/Login/Login.php index e56c4560e2..5b92eaca87 100644 --- a/plugins/Login/Login.php +++ b/plugins/Login/Login.php @@ -15,183 +15,180 @@ */ class Piwik_Login extends Piwik_Plugin { - public function getInformation() - { - $info = array( - 'description' => Piwik_Translate('Login_PluginDescription'), - 'author' => 'Piwik', - 'author_homepage' => 'http://piwik.org/', - 'version' => Piwik_Version::VERSION, - ); - return $info; - } - - function getListHooksRegistered() - { - $hooks = array( - 'FrontController.initAuthenticationObject' => 'initAuthenticationObject', - 'FrontController.NoAccessException' => 'noAccess', - 'API.Request.authenticate' => 'ApiRequestAuthenticate', - 'Login.initSession' => 'initSession', - ); - return $hooks; - } - - /** - * Redirects to Login form with error message. - * Listens to FrontController.NoAccessException hook. - * - * @param Piwik_Event_Notification $notification notification object - */ - function noAccess( $notification ) - { - /* @var Exception $exception */ - $exception = $notification->getNotificationObject(); - $exceptionMessage = $exception->getMessage(); - - $controller = new Piwik_Login_Controller(); - $controller->login($exceptionMessage, '' /* $exception->getTraceAsString() */ ); - } - - /** - * Set login name and autehntication token for authentication request. - * Listens to API.Request.authenticate hook. - * - * @param Piwik_Event_Notification $notification notification object - */ - function ApiRequestAuthenticate($notification) - { - $tokenAuth = $notification->getNotificationObject(); - Zend_Registry::get('auth')->setLogin($login = null); - Zend_Registry::get('auth')->setTokenAuth($tokenAuth); - } - - /** - * Initializes the authentication object. - * Listens to FrontController.initAuthenticationObject hook. - * - * @param Piwik_Event_Notification $notification notification object - */ - function initAuthenticationObject($notification) - { - $auth = new Piwik_Login_Auth(); - Zend_Registry::set('auth', $auth); - - $allowCookieAuthentication = $notification->getNotificationInfo(); - - $action = Piwik::getAction(); - if (Piwik::getModule() === 'API' - && (empty($action) || $action == 'index') - && $allowCookieAuthentication !== true) - { - return; - } - - $authCookieName = Piwik_Config::getInstance()->General['login_cookie_name']; - $authCookieExpiry = 0; - $authCookiePath = Piwik_Config::getInstance()->General['login_cookie_path']; - $authCookie = new Piwik_Cookie($authCookieName, $authCookieExpiry, $authCookiePath); - $defaultLogin = 'anonymous'; - $defaultTokenAuth = 'anonymous'; - if($authCookie->isCookieFound()) - { - $defaultLogin = $authCookie->get('login'); - $defaultTokenAuth = $authCookie->get('token_auth'); - } - $auth->setLogin($defaultLogin); - $auth->setTokenAuth($defaultTokenAuth); - } - - /** - * Authenticate user and initializes the session. - * Listens to Login.initSession hook. - * - * @param Piwik_Event_Notification $notification notification object - * @throws Exception - */ - function initSession($notification) - { - $info = $notification->getNotificationObject(); - $login = $info['login']; - $md5Password = $info['md5Password']; - $rememberMe = $info['rememberMe']; - - $tokenAuth = Piwik_UsersManager_API::getInstance()->getTokenAuth($login, $md5Password); - - $auth = Zend_Registry::get('auth'); - $auth->setLogin($login); - $auth->setTokenAuth($tokenAuth); - $authResult = $auth->authenticate(); - - $authCookieName = Piwik_Config::getInstance()->General['login_cookie_name']; - $authCookieExpiry = $rememberMe ? time() + Piwik_Config::getInstance()->General['login_cookie_expire'] : 0; - $authCookiePath = Piwik_Config::getInstance()->General['login_cookie_path']; - $cookie = new Piwik_Cookie($authCookieName, $authCookieExpiry, $authCookiePath); - if(!$authResult->isValid()) - - { - $cookie->delete(); - throw new Exception(Piwik_Translate('Login_LoginPasswordNotCorrect')); - } - - $cookie->set('login', $login); - $cookie->set('token_auth', $auth->getHashTokenAuth($login, $authResult->getTokenAuth())); - $cookie->setSecure(Piwik::isHttps()); - $cookie->setHttpOnly(true); - $cookie->save(); - - @Piwik_Session::regenerateId(); - - // remove password reset entry if it exists - self::removePasswordResetInfo($login); - } - - /** - * Stores password reset info for a specific login. - * - * @param string $login The user login for whom a password change was requested. - * @param string $password The new password to set. - */ - public static function savePasswordResetInfo( $login, $password ) - { - $optionName = self::getPasswordResetInfoOptionName($login); - $optionData = Piwik_UsersManager::getPasswordHash($password); - - Piwik_SetOption($optionName, $optionData); - } - - /** - * Removes stored password reset info if it exists. - * - * @param string $login The user login to check for. - */ - public static function removePasswordResetInfo( $login ) - { - $optionName = self::getPasswordResetInfoOptionName($login); - Piwik_Option::getInstance()->delete($optionName); - } - - /** - * Gets password hash stored in password reset info. - * - * @param string $login The user login to check for. - * @return string|false The hashed password or false if no reset info exists. - */ - public static function getPasswordToResetTo( $login ) - { - $optionName = self::getPasswordResetInfoOptionName($login); - return Piwik_GetOption($optionName); - } - - /** - * Gets the option name for the option that will store a user's password change - * request. - * - * @param string $login The user login for whom a password change was requested. - * @return string - */ - public static function getPasswordResetInfoOptionName( $login ) - { - return $login.'_reset_password_info'; - } + public function getInformation() + { + $info = array( + 'description' => Piwik_Translate('Login_PluginDescription'), + 'author' => 'Piwik', + 'author_homepage' => 'http://piwik.org/', + 'version' => Piwik_Version::VERSION, + ); + return $info; + } + + function getListHooksRegistered() + { + $hooks = array( + 'FrontController.initAuthenticationObject' => 'initAuthenticationObject', + 'FrontController.NoAccessException' => 'noAccess', + 'API.Request.authenticate' => 'ApiRequestAuthenticate', + 'Login.initSession' => 'initSession', + ); + return $hooks; + } + + /** + * Redirects to Login form with error message. + * Listens to FrontController.NoAccessException hook. + * + * @param Piwik_Event_Notification $notification notification object + */ + function noAccess($notification) + { + /* @var Exception $exception */ + $exception = $notification->getNotificationObject(); + $exceptionMessage = $exception->getMessage(); + + $controller = new Piwik_Login_Controller(); + $controller->login($exceptionMessage, '' /* $exception->getTraceAsString() */); + } + + /** + * Set login name and autehntication token for authentication request. + * Listens to API.Request.authenticate hook. + * + * @param Piwik_Event_Notification $notification notification object + */ + function ApiRequestAuthenticate($notification) + { + $tokenAuth = $notification->getNotificationObject(); + Zend_Registry::get('auth')->setLogin($login = null); + Zend_Registry::get('auth')->setTokenAuth($tokenAuth); + } + + /** + * Initializes the authentication object. + * Listens to FrontController.initAuthenticationObject hook. + * + * @param Piwik_Event_Notification $notification notification object + */ + function initAuthenticationObject($notification) + { + $auth = new Piwik_Login_Auth(); + Zend_Registry::set('auth', $auth); + + $allowCookieAuthentication = $notification->getNotificationInfo(); + + $action = Piwik::getAction(); + if (Piwik::getModule() === 'API' + && (empty($action) || $action == 'index') + && $allowCookieAuthentication !== true + ) { + return; + } + + $authCookieName = Piwik_Config::getInstance()->General['login_cookie_name']; + $authCookieExpiry = 0; + $authCookiePath = Piwik_Config::getInstance()->General['login_cookie_path']; + $authCookie = new Piwik_Cookie($authCookieName, $authCookieExpiry, $authCookiePath); + $defaultLogin = 'anonymous'; + $defaultTokenAuth = 'anonymous'; + if ($authCookie->isCookieFound()) { + $defaultLogin = $authCookie->get('login'); + $defaultTokenAuth = $authCookie->get('token_auth'); + } + $auth->setLogin($defaultLogin); + $auth->setTokenAuth($defaultTokenAuth); + } + + /** + * Authenticate user and initializes the session. + * Listens to Login.initSession hook. + * + * @param Piwik_Event_Notification $notification notification object + * @throws Exception + */ + function initSession($notification) + { + $info = $notification->getNotificationObject(); + $login = $info['login']; + $md5Password = $info['md5Password']; + $rememberMe = $info['rememberMe']; + + $tokenAuth = Piwik_UsersManager_API::getInstance()->getTokenAuth($login, $md5Password); + + $auth = Zend_Registry::get('auth'); + $auth->setLogin($login); + $auth->setTokenAuth($tokenAuth); + $authResult = $auth->authenticate(); + + $authCookieName = Piwik_Config::getInstance()->General['login_cookie_name']; + $authCookieExpiry = $rememberMe ? time() + Piwik_Config::getInstance()->General['login_cookie_expire'] : 0; + $authCookiePath = Piwik_Config::getInstance()->General['login_cookie_path']; + $cookie = new Piwik_Cookie($authCookieName, $authCookieExpiry, $authCookiePath); + if (!$authResult->isValid()) { + $cookie->delete(); + throw new Exception(Piwik_Translate('Login_LoginPasswordNotCorrect')); + } + + $cookie->set('login', $login); + $cookie->set('token_auth', $auth->getHashTokenAuth($login, $authResult->getTokenAuth())); + $cookie->setSecure(Piwik::isHttps()); + $cookie->setHttpOnly(true); + $cookie->save(); + + @Piwik_Session::regenerateId(); + + // remove password reset entry if it exists + self::removePasswordResetInfo($login); + } + + /** + * Stores password reset info for a specific login. + * + * @param string $login The user login for whom a password change was requested. + * @param string $password The new password to set. + */ + public static function savePasswordResetInfo($login, $password) + { + $optionName = self::getPasswordResetInfoOptionName($login); + $optionData = Piwik_UsersManager::getPasswordHash($password); + + Piwik_SetOption($optionName, $optionData); + } + + /** + * Removes stored password reset info if it exists. + * + * @param string $login The user login to check for. + */ + public static function removePasswordResetInfo($login) + { + $optionName = self::getPasswordResetInfoOptionName($login); + Piwik_Option::getInstance()->delete($optionName); + } + + /** + * Gets password hash stored in password reset info. + * + * @param string $login The user login to check for. + * @return string|false The hashed password or false if no reset info exists. + */ + public static function getPasswordToResetTo($login) + { + $optionName = self::getPasswordResetInfoOptionName($login); + return Piwik_GetOption($optionName); + } + + /** + * Gets the option name for the option that will store a user's password change + * request. + * + * @param string $login The user login for whom a password change was requested. + * @return string + */ + public static function getPasswordResetInfoOptionName($login) + { + return $login . '_reset_password_info'; + } } diff --git a/plugins/Login/templates/header.tpl b/plugins/Login/templates/header.tpl index 6b27f66710..e4e1919633 100644 --- a/plugins/Login/templates/header.tpl +++ b/plugins/Login/templates/header.tpl @@ -1,60 +1,64 @@ - - + + + - {if !$isCustomLogo}Piwik › {/if}{'Login_LogIn'|translate} - - - - - - - -{if isset($forceSslLogin) && $forceSslLogin} -{literal} - -{/literal} -{/if} -{literal} - -{/literal} - -{if 'General_LayoutDirection'|translate =='rtl'} - -{/if} -{include file="CoreHome/templates/iframe_buster_header.tpl"} + {if !$isCustomLogo}Piwik › {/if}{'Login_LogIn'|translate} + + + + + + + + {if isset($forceSslLogin) && $forceSslLogin} + {literal} + + {/literal} + {/if} + {literal} + + {/literal} + + {if 'General_LayoutDirection'|translate =='rtl'} + + {/if} + {include file="CoreHome/templates/iframe_buster_header.tpl"} {include file="CoreHome/templates/iframe_buster_body.tpl"} - + diff --git a/plugins/Login/templates/login.css b/plugins/Login/templates/login.css index d8c3f3d87f..7d62cea47a 100644 --- a/plugins/Login/templates/login.css +++ b/plugins/Login/templates/login.css @@ -1,227 +1,224 @@ /* shamelessly taken from wordpress 2.5 - thank you guys!!! */ * { - margin: 0; - padding: 0; + margin: 0; + padding: 0; } body { - font: 12px "Lucida Grande", "Lucida Sans Unicode", Tahoma, Verdana, sans-serif; + font: 12px "Lucida Grande", "Lucida Sans Unicode", Tahoma, Verdana, sans-serif; } form { - padding: 16px 16px 16px 16px; - border-radius: 5px; + padding: 16px 16px 16px 16px; + border-radius: 5px; } #login form input.submit { - font-family: "Lucida Grande", "Lucida Sans Unicode", Tahoma, Verdana, sans-serif; - -moz-border-radius: 3px; - -webkit-border-radius: 3px; - float: right; - height: 35px; - padding: 0 20px; - cursor: pointer; - font: bold 15px Arial, Helvetica; + font-family: "Lucida Grande", "Lucida Sans Unicode", Tahoma, Verdana, sans-serif; + -moz-border-radius: 3px; + -webkit-border-radius: 3px; + float: right; + height: 35px; + padding: 0 20px; + cursor: pointer; + font: bold 15px Arial, Helvetica; } #login form div { - margin-bottom: 24px; + margin-bottom: 24px; } -.updated,.login #login_error,.login .message { - background-color: #ffffe0; - border-color: #e6db55; - margin: 0 auto; - width: 330px; +.updated, .login #login_error, .login .message { + background-color: #ffffe0; + border-color: #e6db55; + margin: 0 auto; + width: 330px; } #login fieldset { - border: 0; + border: 0; } #login fieldset.actions { - line-height: 35px; - width: 315px; - margin-top: 10px; -} - -#login h1 -{ - text-align: center; - color: #666; - margin: 0 0 30px 0; - font: normal 26px/1 Verdana, Helvetica; - position: relative; -} - -#login -{ - background-color: #fafafa; - width: 360px; - padding: 30px; - margin: 50px auto 0 auto; - z-index: 0; - -moz-border-radius: 3px; - -webkit-border-radius: 3px; - border-radius: 3px; - -webkit-box-shadow: - 0 0 2px rgba(0, 0, 0, 0.2), - 0 1px 1px rgba(0, 0, 0, .2); - -moz-box-shadow: - 0 0 2px rgba(0, 0, 0, 0.2), - 1px 1px 0 rgba(0, 0, 0, .1); - box-shadow: - 0 0 2px rgba(0, 0, 0, 0.2), - 0 1px 1px rgba(0, 0, 0, .2); -} - -#login form { margin: 0 5px; position: relative; } + line-height: 35px; + width: 315px; + margin-top: 10px; +} + +#login h1 { + text-align: center; + color: #666; + margin: 0 0 30px 0; + font: normal 26px/1 Verdana, Helvetica; + position: relative; +} + +#login { + background-color: #fafafa; + width: 360px; + padding: 30px; + margin: 50px auto 0 auto; + z-index: 0; + -moz-border-radius: 3px; + -webkit-border-radius: 3px; + border-radius: 3px; + -webkit-box-shadow: 0 0 2px rgba(0, 0, 0, 0.2), 0 1px 1px rgba(0, 0, 0, .2); + -moz-box-shadow: 0 0 2px rgba(0, 0, 0, 0.2), 1px 1px 0 rgba(0, 0, 0, .1); + box-shadow: 0 0 2px rgba(0, 0, 0, 0.2), 0 1px 1px rgba(0, 0, 0, .2); +} + +#login form { + margin: 0 5px; + position: relative; +} + #login form input[type="text"], #login form input[type="password"] { - padding: 10px 15px 10px 45px; - margin: 0 0 15px 0; - width: 253px; /* 258 + 2 + 55 = 315 */ - border: 1px solid #ccc; - -moz-border-radius: 5px; - -webkit-border-radius: 5px; - border-radius: 5px; - -moz-box-shadow: 0 1px 1px #ccc inset, 0 1px 0 #fff; - -webkit-box-shadow: 0 1px 1px #ccc inset, 0 1px 0 #fff; - box-shadow: 0 1px 1px #ccc inset, 0 1px 0 #fff; + padding: 10px 15px 10px 45px; + margin: 0 0 15px 0; + width: 253px; /* 258 + 2 + 55 = 315 */ + border: 1px solid #ccc; + -moz-border-radius: 5px; + -webkit-border-radius: 5px; + border-radius: 5px; + -moz-box-shadow: 0 1px 1px #ccc inset, 0 1px 0 #fff; + -webkit-box-shadow: 0 1px 1px #ccc inset, 0 1px 0 #fff; + box-shadow: 0 1px 1px #ccc inset, 0 1px 0 #fff; } #login_form_rememberme { - vertical-align: middle; + vertical-align: middle; } -#login_error,.message { - margin: 0 0 16px 8px; - border: 1px solid; - padding: 12px; +#login_error, .message { + margin: 0 0 16px 8px; + border: 1px solid; + padding: 12px; } #nav, #piwik { - margin: 0 0 0 8px; - padding: 16px; + margin: 0 0 0 8px; + padding: 16px; } #nav { - text-align: center; + text-align: center; } #nav a:hover { - text-decoration: underline; + text-decoration: underline; } -#login_form_password, #reset_form_password, +#login_form_password, #reset_form_password, #reset_form_password_bis, #login_form_login, #reset_form_login { - background: #fff url(../../../themes/default/images/login-sprite.png) no-repeat; + background: #fff url(../../../themes/default/images/login-sprite.png) no-repeat; } #login_form_password, #reset_form_password, #reset_form_password_bis { - background-position: 10px -51px !important; + background-position: 10px -51px !important; } #login_form_login, #reset_form_login { - background-position: 10px 11px !important; + background-position: 10px 11px !important; } -#login_form_password,#reset_form_password,#reset_form_password_bis, -#login_form_login,#reset_form_login { - font-size: 20px; - width: 97%; - padding: 3px; - margin-right: 6px; +#login_form_password, #reset_form_password, #reset_form_password_bis, +#login_form_login, #reset_form_login { + font-size: 20px; + width: 97%; + padding: 3px; + margin-right: 6px; } #login #login_error { - background-color: #ffebe8; - border-color: #c00; + background-color: #ffebe8; + border-color: #c00; } #login form input.submit { - background-color: #e5e5e5; + background-color: #e5e5e5; } #login form input.submit:hover { - background-color: #eee; + background-color: #eee; } .login #login_error { - background-color: #ffffe0; - border-color: #e6db55; + background-color: #ffffe0; + border-color: #e6db55; } .login #nav a { - color: #777; + color: #777; } + .login #piwik a { - color: #CDCDCD; + color: #CDCDCD; } body.login { - border-top-color: #464646; + border-top-color: #464646; } #login form input { - color: #555; + color: #555; } a { - text-decoration: none; + text-decoration: none; } #logo { - margin: 100px auto 0 auto; - width: 240px; - position: relative; + margin: 100px auto 0 auto; + width: 240px; + position: relative; } #logo .description a { - font:16px/16px 'Patrick Hand'; - color:#666666; - right: auto; - text-decoration: none; + font: 16px/16px 'Patrick Hand'; + color: #666666; + right: auto; + text-decoration: none; } #logo .description { - position: absolute; - left: -40px !important; - top: -30px !important; - -webkit-transform:rotate(-6deg); - -moz-transform:rotate(-6deg); - -ms-transform:rotate(-6deg); - -o-transform:rotate(-6deg); + position: absolute; + left: -40px !important; + top: -30px !important; + -webkit-transform: rotate(-6deg); + -moz-transform: rotate(-6deg); + -ms-transform: rotate(-6deg); + -o-transform: rotate(-6deg); } #logo .description .arrow { - background:url(../../../themes/default/images/affix-arrow.png); - width:50px; - height:68px; - position:absolute; - left:-35px; + background: url(../../../themes/default/images/affix-arrow.png); + width: 50px; + height: 68px; + position: absolute; + left: -35px; } #logo img { - border:0; - vertical-align: bottom; - width: 260px; + border: 0; + vertical-align: bottom; + width: 260px; } #logo .h1 { - font-family: Georgia, "Times New Roman", Times, serif; - font-weight: normal; - color: #136F8B; - font-size: 45pt; - text-transform: none; + font-family: Georgia, "Times New Roman", Times, serif; + font-weight: normal; + color: #136F8B; + font-size: 45pt; + text-transform: none; } .loadingPiwik { - float: left; - margin-left: 16px; + float: left; + margin-left: 16px; } /* IE < 9 will use this */ -html.old-ie .ie-hide { - display: none; +html.old-ie .ie-hide { + display: none; } diff --git a/plugins/Login/templates/login.js b/plugins/Login/templates/login.js index 28171b47ab..5458942fef 100755 --- a/plugins/Login/templates/login.js +++ b/plugins/Login/templates/login.js @@ -4,104 +4,97 @@ * @link http://piwik.org * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later */ -(function($) { - -$(document).ready(function() { - var switchForm = function(fromFormId, toFormId, message, callback) { - var fromLoginInputId = '#'+fromFormId+'_login', - toLoginInputId = '#'+toFormId+'_login', - toPasswordInputId = '#'+toFormId+'_password', - fromLoginNavId = '#'+fromFormId+'_nav', - toLoginNavId = '#'+toFormId+'_nav'; - - if ($(toLoginInputId).val() === '') - { - $(toLoginInputId).val($(fromLoginInputId).val()); - } - - // hide the bottom portion of the login screen & show the password reset bits - $('#'+fromFormId+',#message_container').fadeOut(500, function() { - // show lost password instructions - $('#message_container').html(message); - - $(fromLoginNavId).hide(); - $(toLoginNavId).show(); - $('#'+toFormId+',#message_container').fadeIn(500, function() { - // focus on login or password control based on whether a login exists - if ($(toLoginInputId).val() === '') - { - $(toLoginInputId).focus(); - } - else - { - $(toPasswordInputId).focus(); - } - - if (callback) - { - callback(); - } - }); - }); - }; - - // 'lost your password?' on click - $('#login_form_nav').click(function(e) { - e.preventDefault(); - switchForm('login_form', 'reset_form', $('#lost_password_instructions').html()); - return false; - }); - - // 'cancel' on click - $('#reset_form_nav,#alternate_reset_nav').click(function(e) { - e.preventDefault(); - $('#alternate_reset_nav').hide(); - switchForm('reset_form', 'login_form', ''); - return false; - }); - - // password reset on submit - $('#reset_form_submit').click(function(e) { - e.preventDefault(); - - var ajaxDone = function(response) - { - $('.loadingPiwik').hide(); - - var isSuccess = response.indexOf('id="login_error"') === -1, - fadeOutIds = '#message_container'; - if (isSuccess) - { - fadeOutIds += ',#reset_form,#reset_form_nav'; - } - - $(fadeOutIds).fadeOut(300, function() { - if (isSuccess) - { - $('#alternate_reset_nav').show(); - } - - $('#message_container').html(response).fadeIn(300); - }); - }; - - $('.loadingPiwik').show(); - - // perform reset password request - $.ajax({ - type: 'POST', - url: 'index.php', - dataType: 'html', - async: true, - error: function() { ajaxDone('
HTTP Error
'); }, - success: ajaxDone, // Callback when the request succeeds - data: $('#reset_form').serialize() - }); - - return false; - }); - - $('#login_form_login').focus(); -}); +(function ($) { + + $(document).ready(function () { + var switchForm = function (fromFormId, toFormId, message, callback) { + var fromLoginInputId = '#' + fromFormId + '_login', + toLoginInputId = '#' + toFormId + '_login', + toPasswordInputId = '#' + toFormId + '_password', + fromLoginNavId = '#' + fromFormId + '_nav', + toLoginNavId = '#' + toFormId + '_nav'; + + if ($(toLoginInputId).val() === '') { + $(toLoginInputId).val($(fromLoginInputId).val()); + } + + // hide the bottom portion of the login screen & show the password reset bits + $('#' + fromFormId + ',#message_container').fadeOut(500, function () { + // show lost password instructions + $('#message_container').html(message); + + $(fromLoginNavId).hide(); + $(toLoginNavId).show(); + $('#' + toFormId + ',#message_container').fadeIn(500, function () { + // focus on login or password control based on whether a login exists + if ($(toLoginInputId).val() === '') { + $(toLoginInputId).focus(); + } + else { + $(toPasswordInputId).focus(); + } + + if (callback) { + callback(); + } + }); + }); + }; + + // 'lost your password?' on click + $('#login_form_nav').click(function (e) { + e.preventDefault(); + switchForm('login_form', 'reset_form', $('#lost_password_instructions').html()); + return false; + }); + + // 'cancel' on click + $('#reset_form_nav,#alternate_reset_nav').click(function (e) { + e.preventDefault(); + $('#alternate_reset_nav').hide(); + switchForm('reset_form', 'login_form', ''); + return false; + }); + + // password reset on submit + $('#reset_form_submit').click(function (e) { + e.preventDefault(); + + var ajaxDone = function (response) { + $('.loadingPiwik').hide(); + + var isSuccess = response.indexOf('id="login_error"') === -1, + fadeOutIds = '#message_container'; + if (isSuccess) { + fadeOutIds += ',#reset_form,#reset_form_nav'; + } + + $(fadeOutIds).fadeOut(300, function () { + if (isSuccess) { + $('#alternate_reset_nav').show(); + } + + $('#message_container').html(response).fadeIn(300); + }); + }; + + $('.loadingPiwik').show(); + + // perform reset password request + $.ajax({ + type: 'POST', + url: 'index.php', + dataType: 'html', + async: true, + error: function () { ajaxDone('
HTTP Error
'); }, + success: ajaxDone, // Callback when the request succeeds + data: $('#reset_form').serialize() + }); + + return false; + }); + + $('#login_form_login').focus(); + }); }(jQuery)); diff --git a/plugins/Login/templates/login.tpl b/plugins/Login/templates/login.tpl index 956f8d4bc8..ad85960ec5 100644 --- a/plugins/Login/templates/login.tpl +++ b/plugins/Login/templates/login.tpl @@ -2,84 +2,89 @@
-{* untrusted host warning *} -{if isset($isValidHost) && isset($invalidHostMessage) && !$isValidHost} -
- {'General_Warning'|translate}: {$invalidHostMessage} + {* untrusted host warning *} + {if isset($isValidHost) && isset($invalidHostMessage) && !$isValidHost} +
+ {'General_Warning'|translate}: {$invalidHostMessage} -

{$invalidHostMessageHowToFix} -

{'General_Help'|translate}
+

{$invalidHostMessageHowToFix} +

{'General_Help'|translate}
-
-{else} -
- {if $form_data.errors} -
- {foreach from=$form_data.errors item=data} - {'General_Error'|translate}: {$data}
- {/foreach} -
- {/if} +
+ {else} +
+ {if $form_data.errors} +
+ {foreach from=$form_data.errors item=data} + {'General_Error'|translate} + : {$data} +
+ {/foreach} +
+ {/if} - {if $AccessErrorString} -
{'General_Error'|translate}: {$AccessErrorString}
- {/if} + {if $AccessErrorString} +
{'General_Error'|translate}: {$AccessErrorString}
+ {/if} - {if $infoMessage} -

{$infoMessage}

- {/if} -
+ {if $infoMessage} +

{$infoMessage}

+ {/if} +
+
+

{'Login_LogIn'|translate}

+
+ + + +
- -

{'Login_LogIn'|translate}

-
- - - -
+
+ + + +
+
+ - -
- - -
- - - - - - -{if isset($smarty.capture.poweredByPiwik)} -

- {$smarty.capture.poweredByPiwik} -

-{/if} - - -{/if} + + + + + {if isset($smarty.capture.poweredByPiwik)} +

+ {$smarty.capture.poweredByPiwik} +

+ {/if} + + {/if}
diff --git a/plugins/Login/templates/message.tpl b/plugins/Login/templates/message.tpl index 7b85147d8f..ff2e9d21e3 100755 --- a/plugins/Login/templates/message.tpl +++ b/plugins/Login/templates/message.tpl @@ -1,11 +1,13 @@ {if isset($infoMessage)} -

{$infoMessage}

+

{$infoMessage}

{/if} {if isset($formErrors)} -

- {foreach from=$formErrors item=data} - {'General_Error'|translate}: {$data}
- {/foreach} -

+

+ {foreach from=$formErrors item=data} + {'General_Error'|translate} + : {$data} +
+ {/foreach} +

{/if} diff --git a/plugins/MobileMessaging/API.php b/plugins/MobileMessaging/API.php index df212a662f..ae80979245 100644 --- a/plugins/MobileMessaging/API.php +++ b/plugins/MobileMessaging/API.php @@ -19,459 +19,442 @@ */ class Piwik_MobileMessaging_API { - const VERIFICATION_CODE_LENGTH = 5; - const SMS_FROM = 'Piwik'; - - static private $instance = null; - - /** - * @return Piwik_MobileMessaging_API - */ - static public function getInstance() - { - if (self::$instance == null) - { - self::$instance = new self; - } - return self::$instance; - } - - /** - * @return Piwik_MobileMessaging_SMSProvider - */ - static private function getSMSProviderInstance($provider) - { - return Piwik_MobileMessaging_SMSProvider::factory($provider); - } - - /** - * determine if SMS API credential are available for the current user - * - * @return bool true if SMS API credential are available for the current user - */ - public function areSMSAPICredentialProvided() - { - Piwik::checkUserHasSomeViewAccess(); - - $credential = $this->getSMSAPICredential(); - return isset($credential[Piwik_MobileMessaging::API_KEY_OPTION]); - } - - private function getSMSAPICredential() - { - $settings = $this->getCredentialManagerSettings(); - return array( - Piwik_MobileMessaging::PROVIDER_OPTION => - isset($settings[Piwik_MobileMessaging::PROVIDER_OPTION]) ? $settings[Piwik_MobileMessaging::PROVIDER_OPTION] : null, - Piwik_MobileMessaging::API_KEY_OPTION => - isset($settings[Piwik_MobileMessaging::API_KEY_OPTION]) ? $settings[Piwik_MobileMessaging::API_KEY_OPTION] : null, - ); - } - - /** - * return the SMS API Provider for the current user - * - * @return string SMS API Provider - */ - public function getSMSProvider() - { - $this->checkCredentialManagementRights(); - $credential = $this->getSMSAPICredential(); - return $credential[Piwik_MobileMessaging::PROVIDER_OPTION]; - } - - /** - * set the SMS API credential - * - * @param string $provider SMS API provider - * @param string $apiKey API Key - * - * @return bool true if SMS API credential were validated and saved, false otherwise - */ - public function setSMSAPICredential($provider, $apiKey) - { - $this->checkCredentialManagementRights(); - - $smsProviderInstance = self::getSMSProviderInstance($provider); - $smsProviderInstance->verifyCredential($apiKey); - - $settings = $this->getCredentialManagerSettings(); - - $settings[Piwik_MobileMessaging::PROVIDER_OPTION] = $provider; - $settings[Piwik_MobileMessaging::API_KEY_OPTION] = $apiKey; - - $this->setCredentialManagerSettings($settings); - - return true; - } - - /** - * add phone number - * - * @param string $phoneNumber - * - * @return bool true - */ - public function addPhoneNumber($phoneNumber) - { - Piwik::checkUserIsNotAnonymous(); - - $phoneNumber = self::sanitizePhoneNumber($phoneNumber); - - $verificationCode = ""; - for($i = 0; $i < self::VERIFICATION_CODE_LENGTH; $i++) - { - $verificationCode .= mt_rand(0,9); - } - - $smsText = Piwik_Translate( - 'MobileMessaging_VerificationText', - array( - $verificationCode, - Piwik_Translate('UserSettings_SubmenuSettings'), - Piwik_Translate('MobileMessaging_SettingsMenu') - ) - ); - - $this->sendSMS($smsText, $phoneNumber, self::SMS_FROM); - - $phoneNumbers = $this->retrievePhoneNumbers(); - $phoneNumbers[$phoneNumber] = $verificationCode; - $this->savePhoneNumbers($phoneNumbers); - - $this->increaseCount(Piwik_MobileMessaging::PHONE_NUMBER_VALIDATION_REQUEST_COUNT_OPTION, $phoneNumber); - - return true; - } - - /** - * sanitize phone number - * - * @param string $phoneNumber - * - * @return string sanitized phone number - */ - public static function sanitizePhoneNumber($phoneNumber) - { - return str_replace(' ', '', $phoneNumber); - } - - /** - * send a SMS - * - * @param string $phoneNumber - * @return bool true - * @ignore - */ - public function sendSMS($content, $phoneNumber, $from) - { - Piwik::checkUserIsNotAnonymous(); - - $credential = $this->getSMSAPICredential(); - $SMSProvider = self::getSMSProviderInstance($credential[Piwik_MobileMessaging::PROVIDER_OPTION]); - $SMSProvider->sendSMS( - $credential[Piwik_MobileMessaging::API_KEY_OPTION], - $content, - $phoneNumber, - $from - ); - - $this->increaseCount(Piwik_MobileMessaging::SMS_SENT_COUNT_OPTION, $phoneNumber); - - return true; - } - - /** - * get remaining credit - * - * @return string remaining credit - */ - public function getCreditLeft() - { - $this->checkCredentialManagementRights(); - - $credential = $this->getSMSAPICredential(); - $SMSProvider = self::getSMSProviderInstance($credential[Piwik_MobileMessaging::PROVIDER_OPTION]); - return $SMSProvider->getCreditLeft( - $credential[Piwik_MobileMessaging::API_KEY_OPTION] - ); - } - - /** - * remove phone number - * - * @param string $phoneNumber - * - * @return bool true - */ - public function removePhoneNumber($phoneNumber) - { - Piwik::checkUserIsNotAnonymous(); - - $phoneNumbers = $this->retrievePhoneNumbers(); - unset($phoneNumbers[$phoneNumber]); - $this->savePhoneNumbers($phoneNumbers); - - // remove phone number from reports - $pdfReportsAPIInstance = Piwik_PDFReports_API::getInstance(); - $reports = $pdfReportsAPIInstance->getReports( - $idSite = false, - $period = false, - $idReport = false, - $ifSuperUserReturnOnlySuperUserReports = $this->getDelegatedManagement() - ); - - foreach($reports as $report) - { - if ($report['type'] == Piwik_MobileMessaging::MOBILE_TYPE) - { - $reportParameters = $report['parameters']; - $reportPhoneNumbers = $reportParameters[Piwik_MobileMessaging::PHONE_NUMBERS_PARAMETER]; - $updatedPhoneNumbers = array(); - foreach($reportPhoneNumbers as $reportPhoneNumber) - { - if($reportPhoneNumber != $phoneNumber) - { - $updatedPhoneNumbers[] = $reportPhoneNumber; - } - } - - if(count($updatedPhoneNumbers) != count($reportPhoneNumbers)) - { - $reportParameters[Piwik_MobileMessaging::PHONE_NUMBERS_PARAMETER] = $updatedPhoneNumbers; - - // note: reports can end up without any recipients - $pdfReportsAPIInstance->updateReport( - $report['idreport'], - $report['idsite'], - $report['description'], - $report['period'], - $report['type'], - $report['format'], - $report['reports'], - $reportParameters - ); - } - } - } - - return true; - } - - private function retrievePhoneNumbers() - { - $settings = $this->getCurrentUserSettings(); - - $phoneNumbers = array(); - if(isset($settings[Piwik_MobileMessaging::PHONE_NUMBERS_OPTION])) - { - $phoneNumbers = $settings[Piwik_MobileMessaging::PHONE_NUMBERS_OPTION]; - } - - return $phoneNumbers; - } - - private function savePhoneNumbers($phoneNumbers) - { - $settings = $this->getCurrentUserSettings(); - - $settings[Piwik_MobileMessaging::PHONE_NUMBERS_OPTION] = $phoneNumbers; - - $this->setCurrentUserSettings($settings); - } - - private function increaseCount($option, $phoneNumber) - { - $settings = $this->getCurrentUserSettings(); - - $counts = array(); - if(isset($settings[$option])) - { - $counts = $settings[$option]; - } - - $countToUpdate = 0; - if(isset($counts[$phoneNumber])) - { - $countToUpdate = $counts[$phoneNumber]; - } - - $counts[$phoneNumber] = $countToUpdate + 1; - - $settings[$option] = $counts; - - $this->setCurrentUserSettings($settings); - } - - /** - * validate phone number - * - * @param string $phoneNumber - * @param string $verificationCode - * - * @return bool true if validation code is correct, false otherwise - */ - public function validatePhoneNumber($phoneNumber, $verificationCode) - { - Piwik::checkUserIsNotAnonymous(); - - $phoneNumbers = $this->retrievePhoneNumbers(); - - if(isset($phoneNumbers[$phoneNumber])) - { - if($verificationCode == $phoneNumbers[$phoneNumber]) { - - $phoneNumbers[$phoneNumber] = null; - $this->savePhoneNumbers($phoneNumbers); - return true; - } - } - - return false; - } - - /** - * get phone number list - * - * @return array $phoneNumber => $isValidated - * @ignore - */ - public function getPhoneNumbers() - { - Piwik::checkUserIsNotAnonymous(); - - $rawPhoneNumbers = $this->retrievePhoneNumbers(); - - $phoneNumbers = array(); - foreach($rawPhoneNumbers as $phoneNumber => $verificationCode) - { - $phoneNumbers[$phoneNumber] = self::isActivated($verificationCode); - } - - return $phoneNumbers; - } - - /** - * get activated phone number list - * - * @return array $phoneNumber - * @ignore - */ - public function getActivatedPhoneNumbers() - { - Piwik::checkUserIsNotAnonymous(); - - $phoneNumbers = $this->retrievePhoneNumbers(); - - $activatedPhoneNumbers = array(); - foreach($phoneNumbers as $phoneNumber => $verificationCode) - { - if(self::isActivated($verificationCode)) - { - $activatedPhoneNumbers[] = $phoneNumber; - } - } - - return $activatedPhoneNumbers; - } - - private static function isActivated($verificationCode) - { - return $verificationCode === null; - } - - /** - * delete the SMS API credential - * - * @return bool true - */ - public function deleteSMSAPICredential() - { - $this->checkCredentialManagementRights(); - - $settings = $this->getCredentialManagerSettings(); - - $settings[Piwik_MobileMessaging::API_KEY_OPTION] = null; - - $this->setCredentialManagerSettings($settings); - - return true; - } - - private function checkCredentialManagementRights() - { - $this->getDelegatedManagement() ? Piwik::checkUserIsNotAnonymous() : Piwik::checkUserIsSuperUser(); - } - - private function setUserSettings($user, $settings) - { - Piwik_SetOption( - $user . Piwik_MobileMessaging::USER_SETTINGS_POSTFIX_OPTION, - Piwik_Common::json_encode($settings) - ); - } - - private function setCurrentUserSettings($settings) - { - $this->setUserSettings(Piwik::getCurrentUserLogin(), $settings); - } - - private function setCredentialManagerSettings($settings) - { - $this->setUserSettings($this->getCredentialManagerLogin(), $settings); - } - - private function getCredentialManagerLogin() - { - return $this->getDelegatedManagement() ? Piwik::getCurrentUserLogin() : Piwik::getSuperUserLogin(); - } - - private function getUserSettings($user) - { - $optionIndex = $user . Piwik_MobileMessaging::USER_SETTINGS_POSTFIX_OPTION; - $userSettings = Piwik_GetOption($optionIndex); - - if(empty($userSettings)) - { - $userSettings = array(); - } - else - { - $userSettings = Piwik_Common::json_decode($userSettings, true); - } - - return $userSettings; - } - - private function getCredentialManagerSettings() - { - return $this->getUserSettings($this->getCredentialManagerLogin()); - } - - private function getCurrentUserSettings() - { - return $this->getUserSettings(Piwik::getCurrentUserLogin()); - } - - /** - * Specify if normal users can manage their own SMS API credential - * - * @param bool $delegatedManagement false if SMS API credential only manageable by super admin, true otherwise - */ - public function setDelegatedManagement($delegatedManagement) - { - Piwik::checkUserIsSuperUser(); - Piwik_SetOption(Piwik_MobileMessaging::DELEGATED_MANAGEMENT_OPTION, $delegatedManagement); - } - - /** - * Determine if normal users can manage their own SMS API credential - * - * @return bool false if SMS API credential only manageable by super admin, true otherwise - */ - public function getDelegatedManagement() - { - Piwik::checkUserHasSomeViewAccess(); - return Piwik_GetOption(Piwik_MobileMessaging::DELEGATED_MANAGEMENT_OPTION) == 'true'; - } + const VERIFICATION_CODE_LENGTH = 5; + const SMS_FROM = 'Piwik'; + + static private $instance = null; + + /** + * @return Piwik_MobileMessaging_API + */ + static public function getInstance() + { + if (self::$instance == null) { + self::$instance = new self; + } + return self::$instance; + } + + /** + * @return Piwik_MobileMessaging_SMSProvider + */ + static private function getSMSProviderInstance($provider) + { + return Piwik_MobileMessaging_SMSProvider::factory($provider); + } + + /** + * determine if SMS API credential are available for the current user + * + * @return bool true if SMS API credential are available for the current user + */ + public function areSMSAPICredentialProvided() + { + Piwik::checkUserHasSomeViewAccess(); + + $credential = $this->getSMSAPICredential(); + return isset($credential[Piwik_MobileMessaging::API_KEY_OPTION]); + } + + private function getSMSAPICredential() + { + $settings = $this->getCredentialManagerSettings(); + return array( + Piwik_MobileMessaging::PROVIDER_OPTION => + isset($settings[Piwik_MobileMessaging::PROVIDER_OPTION]) ? $settings[Piwik_MobileMessaging::PROVIDER_OPTION] : null, + Piwik_MobileMessaging::API_KEY_OPTION => + isset($settings[Piwik_MobileMessaging::API_KEY_OPTION]) ? $settings[Piwik_MobileMessaging::API_KEY_OPTION] : null, + ); + } + + /** + * return the SMS API Provider for the current user + * + * @return string SMS API Provider + */ + public function getSMSProvider() + { + $this->checkCredentialManagementRights(); + $credential = $this->getSMSAPICredential(); + return $credential[Piwik_MobileMessaging::PROVIDER_OPTION]; + } + + /** + * set the SMS API credential + * + * @param string $provider SMS API provider + * @param string $apiKey API Key + * + * @return bool true if SMS API credential were validated and saved, false otherwise + */ + public function setSMSAPICredential($provider, $apiKey) + { + $this->checkCredentialManagementRights(); + + $smsProviderInstance = self::getSMSProviderInstance($provider); + $smsProviderInstance->verifyCredential($apiKey); + + $settings = $this->getCredentialManagerSettings(); + + $settings[Piwik_MobileMessaging::PROVIDER_OPTION] = $provider; + $settings[Piwik_MobileMessaging::API_KEY_OPTION] = $apiKey; + + $this->setCredentialManagerSettings($settings); + + return true; + } + + /** + * add phone number + * + * @param string $phoneNumber + * + * @return bool true + */ + public function addPhoneNumber($phoneNumber) + { + Piwik::checkUserIsNotAnonymous(); + + $phoneNumber = self::sanitizePhoneNumber($phoneNumber); + + $verificationCode = ""; + for ($i = 0; $i < self::VERIFICATION_CODE_LENGTH; $i++) { + $verificationCode .= mt_rand(0, 9); + } + + $smsText = Piwik_Translate( + 'MobileMessaging_VerificationText', + array( + $verificationCode, + Piwik_Translate('UserSettings_SubmenuSettings'), + Piwik_Translate('MobileMessaging_SettingsMenu') + ) + ); + + $this->sendSMS($smsText, $phoneNumber, self::SMS_FROM); + + $phoneNumbers = $this->retrievePhoneNumbers(); + $phoneNumbers[$phoneNumber] = $verificationCode; + $this->savePhoneNumbers($phoneNumbers); + + $this->increaseCount(Piwik_MobileMessaging::PHONE_NUMBER_VALIDATION_REQUEST_COUNT_OPTION, $phoneNumber); + + return true; + } + + /** + * sanitize phone number + * + * @param string $phoneNumber + * + * @return string sanitized phone number + */ + public static function sanitizePhoneNumber($phoneNumber) + { + return str_replace(' ', '', $phoneNumber); + } + + /** + * send a SMS + * + * @param string $phoneNumber + * @return bool true + * @ignore + */ + public function sendSMS($content, $phoneNumber, $from) + { + Piwik::checkUserIsNotAnonymous(); + + $credential = $this->getSMSAPICredential(); + $SMSProvider = self::getSMSProviderInstance($credential[Piwik_MobileMessaging::PROVIDER_OPTION]); + $SMSProvider->sendSMS( + $credential[Piwik_MobileMessaging::API_KEY_OPTION], + $content, + $phoneNumber, + $from + ); + + $this->increaseCount(Piwik_MobileMessaging::SMS_SENT_COUNT_OPTION, $phoneNumber); + + return true; + } + + /** + * get remaining credit + * + * @return string remaining credit + */ + public function getCreditLeft() + { + $this->checkCredentialManagementRights(); + + $credential = $this->getSMSAPICredential(); + $SMSProvider = self::getSMSProviderInstance($credential[Piwik_MobileMessaging::PROVIDER_OPTION]); + return $SMSProvider->getCreditLeft( + $credential[Piwik_MobileMessaging::API_KEY_OPTION] + ); + } + + /** + * remove phone number + * + * @param string $phoneNumber + * + * @return bool true + */ + public function removePhoneNumber($phoneNumber) + { + Piwik::checkUserIsNotAnonymous(); + + $phoneNumbers = $this->retrievePhoneNumbers(); + unset($phoneNumbers[$phoneNumber]); + $this->savePhoneNumbers($phoneNumbers); + + // remove phone number from reports + $pdfReportsAPIInstance = Piwik_PDFReports_API::getInstance(); + $reports = $pdfReportsAPIInstance->getReports( + $idSite = false, + $period = false, + $idReport = false, + $ifSuperUserReturnOnlySuperUserReports = $this->getDelegatedManagement() + ); + + foreach ($reports as $report) { + if ($report['type'] == Piwik_MobileMessaging::MOBILE_TYPE) { + $reportParameters = $report['parameters']; + $reportPhoneNumbers = $reportParameters[Piwik_MobileMessaging::PHONE_NUMBERS_PARAMETER]; + $updatedPhoneNumbers = array(); + foreach ($reportPhoneNumbers as $reportPhoneNumber) { + if ($reportPhoneNumber != $phoneNumber) { + $updatedPhoneNumbers[] = $reportPhoneNumber; + } + } + + if (count($updatedPhoneNumbers) != count($reportPhoneNumbers)) { + $reportParameters[Piwik_MobileMessaging::PHONE_NUMBERS_PARAMETER] = $updatedPhoneNumbers; + + // note: reports can end up without any recipients + $pdfReportsAPIInstance->updateReport( + $report['idreport'], + $report['idsite'], + $report['description'], + $report['period'], + $report['type'], + $report['format'], + $report['reports'], + $reportParameters + ); + } + } + } + + return true; + } + + private function retrievePhoneNumbers() + { + $settings = $this->getCurrentUserSettings(); + + $phoneNumbers = array(); + if (isset($settings[Piwik_MobileMessaging::PHONE_NUMBERS_OPTION])) { + $phoneNumbers = $settings[Piwik_MobileMessaging::PHONE_NUMBERS_OPTION]; + } + + return $phoneNumbers; + } + + private function savePhoneNumbers($phoneNumbers) + { + $settings = $this->getCurrentUserSettings(); + + $settings[Piwik_MobileMessaging::PHONE_NUMBERS_OPTION] = $phoneNumbers; + + $this->setCurrentUserSettings($settings); + } + + private function increaseCount($option, $phoneNumber) + { + $settings = $this->getCurrentUserSettings(); + + $counts = array(); + if (isset($settings[$option])) { + $counts = $settings[$option]; + } + + $countToUpdate = 0; + if (isset($counts[$phoneNumber])) { + $countToUpdate = $counts[$phoneNumber]; + } + + $counts[$phoneNumber] = $countToUpdate + 1; + + $settings[$option] = $counts; + + $this->setCurrentUserSettings($settings); + } + + /** + * validate phone number + * + * @param string $phoneNumber + * @param string $verificationCode + * + * @return bool true if validation code is correct, false otherwise + */ + public function validatePhoneNumber($phoneNumber, $verificationCode) + { + Piwik::checkUserIsNotAnonymous(); + + $phoneNumbers = $this->retrievePhoneNumbers(); + + if (isset($phoneNumbers[$phoneNumber])) { + if ($verificationCode == $phoneNumbers[$phoneNumber]) { + + $phoneNumbers[$phoneNumber] = null; + $this->savePhoneNumbers($phoneNumbers); + return true; + } + } + + return false; + } + + /** + * get phone number list + * + * @return array $phoneNumber => $isValidated + * @ignore + */ + public function getPhoneNumbers() + { + Piwik::checkUserIsNotAnonymous(); + + $rawPhoneNumbers = $this->retrievePhoneNumbers(); + + $phoneNumbers = array(); + foreach ($rawPhoneNumbers as $phoneNumber => $verificationCode) { + $phoneNumbers[$phoneNumber] = self::isActivated($verificationCode); + } + + return $phoneNumbers; + } + + /** + * get activated phone number list + * + * @return array $phoneNumber + * @ignore + */ + public function getActivatedPhoneNumbers() + { + Piwik::checkUserIsNotAnonymous(); + + $phoneNumbers = $this->retrievePhoneNumbers(); + + $activatedPhoneNumbers = array(); + foreach ($phoneNumbers as $phoneNumber => $verificationCode) { + if (self::isActivated($verificationCode)) { + $activatedPhoneNumbers[] = $phoneNumber; + } + } + + return $activatedPhoneNumbers; + } + + private static function isActivated($verificationCode) + { + return $verificationCode === null; + } + + /** + * delete the SMS API credential + * + * @return bool true + */ + public function deleteSMSAPICredential() + { + $this->checkCredentialManagementRights(); + + $settings = $this->getCredentialManagerSettings(); + + $settings[Piwik_MobileMessaging::API_KEY_OPTION] = null; + + $this->setCredentialManagerSettings($settings); + + return true; + } + + private function checkCredentialManagementRights() + { + $this->getDelegatedManagement() ? Piwik::checkUserIsNotAnonymous() : Piwik::checkUserIsSuperUser(); + } + + private function setUserSettings($user, $settings) + { + Piwik_SetOption( + $user . Piwik_MobileMessaging::USER_SETTINGS_POSTFIX_OPTION, + Piwik_Common::json_encode($settings) + ); + } + + private function setCurrentUserSettings($settings) + { + $this->setUserSettings(Piwik::getCurrentUserLogin(), $settings); + } + + private function setCredentialManagerSettings($settings) + { + $this->setUserSettings($this->getCredentialManagerLogin(), $settings); + } + + private function getCredentialManagerLogin() + { + return $this->getDelegatedManagement() ? Piwik::getCurrentUserLogin() : Piwik::getSuperUserLogin(); + } + + private function getUserSettings($user) + { + $optionIndex = $user . Piwik_MobileMessaging::USER_SETTINGS_POSTFIX_OPTION; + $userSettings = Piwik_GetOption($optionIndex); + + if (empty($userSettings)) { + $userSettings = array(); + } else { + $userSettings = Piwik_Common::json_decode($userSettings, true); + } + + return $userSettings; + } + + private function getCredentialManagerSettings() + { + return $this->getUserSettings($this->getCredentialManagerLogin()); + } + + private function getCurrentUserSettings() + { + return $this->getUserSettings(Piwik::getCurrentUserLogin()); + } + + /** + * Specify if normal users can manage their own SMS API credential + * + * @param bool $delegatedManagement false if SMS API credential only manageable by super admin, true otherwise + */ + public function setDelegatedManagement($delegatedManagement) + { + Piwik::checkUserIsSuperUser(); + Piwik_SetOption(Piwik_MobileMessaging::DELEGATED_MANAGEMENT_OPTION, $delegatedManagement); + } + + /** + * Determine if normal users can manage their own SMS API credential + * + * @return bool false if SMS API credential only manageable by super admin, true otherwise + */ + public function getDelegatedManagement() + { + Piwik::checkUserHasSomeViewAccess(); + return Piwik_GetOption(Piwik_MobileMessaging::DELEGATED_MANAGEMENT_OPTION) == 'true'; + } } diff --git a/plugins/MobileMessaging/Controller.php b/plugins/MobileMessaging/Controller.php index 11efa0817c..ee027c4505 100644 --- a/plugins/MobileMessaging/Controller.php +++ b/plugins/MobileMessaging/Controller.php @@ -17,60 +17,57 @@ require_once PIWIK_INCLUDE_PATH . '/plugins/UserCountry/functions.php'; */ class Piwik_MobileMessaging_Controller extends Piwik_Controller_Admin { - /* - * Mobile Messaging Settings tab : - * - set delegated management - * - provide & validate SMS API credential - * - add & activate phone numbers - * - check remaining credits - */ - function index() - { - Piwik::checkUserIsNotAnonymous(); + /* + * Mobile Messaging Settings tab : + * - set delegated management + * - provide & validate SMS API credential + * - add & activate phone numbers + * - check remaining credits + */ + function index() + { + Piwik::checkUserIsNotAnonymous(); - $view = Piwik_View::factory('Settings'); + $view = Piwik_View::factory('Settings'); - $view->isSuperUser = Piwik::isUserIsSuperUser(); + $view->isSuperUser = Piwik::isUserIsSuperUser(); - $mobileMessagingAPI = Piwik_MobileMessaging_API::getInstance(); - $view->delegatedManagement = $mobileMessagingAPI->getDelegatedManagement(); - $view->credentialSupplied = $mobileMessagingAPI->areSMSAPICredentialProvided(); - $view->accountManagedByCurrentUser = $view->isSuperUser || $view->delegatedManagement; - $view->strHelpAddPhone = Piwik_Translate('MobileMessaging_Settings_PhoneNumbers_HelpAdd', array( Piwik_Translate('UserSettings_SubmenuSettings'), Piwik_Translate('MobileMessaging_SettingsMenu') ) ); - if($view->credentialSupplied && $view->accountManagedByCurrentUser) - { - $view->provider = $mobileMessagingAPI->getSMSProvider(); - $view->creditLeft = $mobileMessagingAPI->getCreditLeft(); - } + $mobileMessagingAPI = Piwik_MobileMessaging_API::getInstance(); + $view->delegatedManagement = $mobileMessagingAPI->getDelegatedManagement(); + $view->credentialSupplied = $mobileMessagingAPI->areSMSAPICredentialProvided(); + $view->accountManagedByCurrentUser = $view->isSuperUser || $view->delegatedManagement; + $view->strHelpAddPhone = Piwik_Translate('MobileMessaging_Settings_PhoneNumbers_HelpAdd', array(Piwik_Translate('UserSettings_SubmenuSettings'), Piwik_Translate('MobileMessaging_SettingsMenu'))); + if ($view->credentialSupplied && $view->accountManagedByCurrentUser) { + $view->provider = $mobileMessagingAPI->getSMSProvider(); + $view->creditLeft = $mobileMessagingAPI->getCreditLeft(); + } - $view->smsProviders = Piwik_MobileMessaging_SMSProvider::$availableSMSProviders; + $view->smsProviders = Piwik_MobileMessaging_SMSProvider::$availableSMSProviders; - // construct the list of countries from the lang files - $countries = array(); - foreach(Piwik_Common::getCountriesList() as $countryCode => $continentCode) - { - if(isset(Piwik_MobileMessaging_CountryCallingCodes::$countryCallingCodes[$countryCode])) - { - $countries[$countryCode] = - array( - 'countryName' => Piwik_CountryTranslate($countryCode), - 'countryCallingCode' => Piwik_MobileMessaging_CountryCallingCodes::$countryCallingCodes[$countryCode], - ); - } - } - $view->countries = $countries; + // construct the list of countries from the lang files + $countries = array(); + foreach (Piwik_Common::getCountriesList() as $countryCode => $continentCode) { + if (isset(Piwik_MobileMessaging_CountryCallingCodes::$countryCallingCodes[$countryCode])) { + $countries[$countryCode] = + array( + 'countryName' => Piwik_CountryTranslate($countryCode), + 'countryCallingCode' => Piwik_MobileMessaging_CountryCallingCodes::$countryCallingCodes[$countryCode], + ); + } + } + $view->countries = $countries; - $view->defaultCountry = Piwik_Common::getCountry( - Piwik_LanguagesManager::getLanguageCodeForCurrentUser(), - true, - Piwik_IP::getIpFromHeader() - ); + $view->defaultCountry = Piwik_Common::getCountry( + Piwik_LanguagesManager::getLanguageCodeForCurrentUser(), + true, + Piwik_IP::getIpFromHeader() + ); - $view->phoneNumbers = $mobileMessagingAPI->getPhoneNumbers(); + $view->phoneNumbers = $mobileMessagingAPI->getPhoneNumbers(); - $this->setBasicVariablesView($view); + $this->setBasicVariablesView($view); - $view->menu = Piwik_GetAdminMenu(); - echo $view->render(); - } + $view->menu = Piwik_GetAdminMenu(); + echo $view->render(); + } } diff --git a/plugins/MobileMessaging/CountryCallingCodes.php b/plugins/MobileMessaging/CountryCallingCodes.php index a6c72bc86f..b1097220f8 100644 --- a/plugins/MobileMessaging/CountryCallingCodes.php +++ b/plugins/MobileMessaging/CountryCallingCodes.php @@ -15,257 +15,257 @@ */ class Piwik_MobileMessaging_CountryCallingCodes { - // list taken from core/DataFiles/Countries.php - public static $countryCallingCodes = array( - 'ad' => '376', - 'ae' => '971', - 'af' => '93', - 'ag' => '1268', // @wikipedia original value: 1 268 - 'ai' => '1264', // @wikipedia original value: 1 264 - 'al' => '355', - 'am' => '374', - 'ao' => '244', + // list taken from core/DataFiles/Countries.php + public static $countryCallingCodes = array( + 'ad' => '376', + 'ae' => '971', + 'af' => '93', + 'ag' => '1268', // @wikipedia original value: 1 268 + 'ai' => '1264', // @wikipedia original value: 1 264 + 'al' => '355', + 'am' => '374', + 'ao' => '244', // 'aq' => 'MISSING CODE', // @wikipedia In Antarctica dialing is dependent on the parent country of each base - 'ar' => '54', - 'as' => '1684', // @wikipedia original value: 1 684 - 'at' => '43', - 'au' => '61', - 'aw' => '297', - 'ax' => '358', - 'az' => '994', - 'ba' => '387', - 'bb' => '1246', // @wikipedia original value: 1 246 - 'bd' => '880', - 'be' => '32', - 'bf' => '226', - 'bg' => '359', - 'bh' => '973', - 'bi' => '257', - 'bj' => '229', - 'bl' => '590', - 'bm' => '1441', // @wikipedia original value: 1 441 - 'bn' => '673', - 'bo' => '591', - 'bq' => '5997', // @wikipedia original value: 599 7 - 'br' => '55', - 'bs' => '1242', // @wikipedia original value: 1 242 - 'bt' => '975', + 'ar' => '54', + 'as' => '1684', // @wikipedia original value: 1 684 + 'at' => '43', + 'au' => '61', + 'aw' => '297', + 'ax' => '358', + 'az' => '994', + 'ba' => '387', + 'bb' => '1246', // @wikipedia original value: 1 246 + 'bd' => '880', + 'be' => '32', + 'bf' => '226', + 'bg' => '359', + 'bh' => '973', + 'bi' => '257', + 'bj' => '229', + 'bl' => '590', + 'bm' => '1441', // @wikipedia original value: 1 441 + 'bn' => '673', + 'bo' => '591', + 'bq' => '5997', // @wikipedia original value: 599 7 + 'br' => '55', + 'bs' => '1242', // @wikipedia original value: 1 242 + 'bt' => '975', // 'bv' => 'MISSING CODE', - 'bw' => '267', - 'by' => '375', - 'bz' => '501', - 'ca' => '1', - 'cc' => '61', - 'cd' => '243', - 'cf' => '236', - 'cg' => '242', - 'ch' => '41', - 'ci' => '225', - 'ck' => '682', - 'cl' => '56', - 'cm' => '237', - 'cn' => '86', - 'co' => '57', - 'cr' => '506', - 'cu' => '53', - 'cv' => '238', - 'cw' => '5999', // @wikipedia original value: 599 9 - 'cx' => '61', - 'cy' => '357', - 'cz' => '420', - 'de' => '49', - 'dj' => '253', - 'dk' => '45', - 'dm' => '1767', // @wikipedia original value: 1 767 + 'bw' => '267', + 'by' => '375', + 'bz' => '501', + 'ca' => '1', + 'cc' => '61', + 'cd' => '243', + 'cf' => '236', + 'cg' => '242', + 'ch' => '41', + 'ci' => '225', + 'ck' => '682', + 'cl' => '56', + 'cm' => '237', + 'cn' => '86', + 'co' => '57', + 'cr' => '506', + 'cu' => '53', + 'cv' => '238', + 'cw' => '5999', // @wikipedia original value: 599 9 + 'cx' => '61', + 'cy' => '357', + 'cz' => '420', + 'de' => '49', + 'dj' => '253', + 'dk' => '45', + 'dm' => '1767', // @wikipedia original value: 1 767 // 'do' => 'MISSING CODE', // @wikipedia original values: 1 809, 1 829, 1 849 - 'dz' => '213', - 'ec' => '593', - 'ee' => '372', - 'eg' => '20', - 'eh' => '212', - 'er' => '291', - 'es' => '34', - 'et' => '251', - 'fi' => '358', - 'fj' => '679', - 'fk' => '500', - 'fm' => '691', - 'fo' => '298', - 'fr' => '33', - 'ga' => '241', - 'gb' => '44', - 'gd' => '1473', // @wikipedia original value: 1 473 - 'ge' => '995', - 'gf' => '594', - 'gg' => '44', - 'gh' => '233', - 'gi' => '350', - 'gl' => '299', - 'gm' => '220', - 'gn' => '224', - 'gp' => '590', - 'gq' => '240', - 'gr' => '30', - 'gs' => '500', - 'gt' => '502', - 'gu' => '1671', // @wikipedia original value: 1 671 - 'gw' => '245', - 'gy' => '592', - 'hk' => '852', + 'dz' => '213', + 'ec' => '593', + 'ee' => '372', + 'eg' => '20', + 'eh' => '212', + 'er' => '291', + 'es' => '34', + 'et' => '251', + 'fi' => '358', + 'fj' => '679', + 'fk' => '500', + 'fm' => '691', + 'fo' => '298', + 'fr' => '33', + 'ga' => '241', + 'gb' => '44', + 'gd' => '1473', // @wikipedia original value: 1 473 + 'ge' => '995', + 'gf' => '594', + 'gg' => '44', + 'gh' => '233', + 'gi' => '350', + 'gl' => '299', + 'gm' => '220', + 'gn' => '224', + 'gp' => '590', + 'gq' => '240', + 'gr' => '30', + 'gs' => '500', + 'gt' => '502', + 'gu' => '1671', // @wikipedia original value: 1 671 + 'gw' => '245', + 'gy' => '592', + 'hk' => '852', // 'hm' => 'MISSING CODE', - 'hn' => '504', - 'hr' => '385', - 'ht' => '509', - 'hu' => '36', - 'id' => '62', - 'ie' => '353', - 'il' => '972', - 'im' => '44', - 'in' => '91', - 'io' => '246', - 'iq' => '964', - 'ir' => '98', - 'is' => '354', - 'it' => '39', - 'je' => '44', - 'jm' => '1876', // @wikipedia original value: 1 876 - 'jo' => '962', - 'jp' => '81', - 'ke' => '254', - 'kg' => '996', - 'kh' => '855', - 'ki' => '686', - 'km' => '269', - 'kn' => '1869', // @wikipedia original value: 1 869 - 'kp' => '850', - 'kr' => '82', - 'kw' => '965', - 'ky' => '1345', // @wikipedia original value: 1 345 + 'hn' => '504', + 'hr' => '385', + 'ht' => '509', + 'hu' => '36', + 'id' => '62', + 'ie' => '353', + 'il' => '972', + 'im' => '44', + 'in' => '91', + 'io' => '246', + 'iq' => '964', + 'ir' => '98', + 'is' => '354', + 'it' => '39', + 'je' => '44', + 'jm' => '1876', // @wikipedia original value: 1 876 + 'jo' => '962', + 'jp' => '81', + 'ke' => '254', + 'kg' => '996', + 'kh' => '855', + 'ki' => '686', + 'km' => '269', + 'kn' => '1869', // @wikipedia original value: 1 869 + 'kp' => '850', + 'kr' => '82', + 'kw' => '965', + 'ky' => '1345', // @wikipedia original value: 1 345 // 'kz' => 'MISSING CODE', // @wikipedia original values: 7 6, 7 7 - 'la' => '856', - 'lb' => '961', - 'lc' => '1758', // @wikipedia original value: 1 758 - 'li' => '423', - 'lk' => '94', - 'lr' => '231', - 'ls' => '266', - 'lt' => '370', - 'lu' => '352', - 'lv' => '371', - 'ly' => '218', - 'ma' => '212', - 'mc' => '377', - 'md' => '373', - 'me' => '382', - 'mf' => '590', - 'mg' => '261', - 'mh' => '692', - 'mk' => '389', - 'ml' => '223', - 'mm' => '95', - 'mn' => '976', - 'mo' => '853', - 'mp' => '1670', // @wikipedia original value: 1 670 - 'mq' => '596', - 'mr' => '222', - 'ms' => '1664', // @wikipedia original value: 1 664 - 'mt' => '356', - 'mu' => '230', - 'mv' => '960', - 'mw' => '265', - 'mx' => '52', - 'my' => '60', - 'mz' => '258', - 'na' => '264', - 'nc' => '687', - 'ne' => '227', - 'nf' => '672', - 'ng' => '234', - 'ni' => '505', - 'nl' => '31', - 'no' => '47', - 'np' => '977', - 'nr' => '674', - 'nu' => '683', - 'nz' => '64', - 'om' => '968', - 'pa' => '507', - 'pe' => '51', - 'pf' => '689', - 'pg' => '675', - 'ph' => '63', - 'pk' => '92', - 'pl' => '48', - 'pm' => '508', - 'pn' => '672', + 'la' => '856', + 'lb' => '961', + 'lc' => '1758', // @wikipedia original value: 1 758 + 'li' => '423', + 'lk' => '94', + 'lr' => '231', + 'ls' => '266', + 'lt' => '370', + 'lu' => '352', + 'lv' => '371', + 'ly' => '218', + 'ma' => '212', + 'mc' => '377', + 'md' => '373', + 'me' => '382', + 'mf' => '590', + 'mg' => '261', + 'mh' => '692', + 'mk' => '389', + 'ml' => '223', + 'mm' => '95', + 'mn' => '976', + 'mo' => '853', + 'mp' => '1670', // @wikipedia original value: 1 670 + 'mq' => '596', + 'mr' => '222', + 'ms' => '1664', // @wikipedia original value: 1 664 + 'mt' => '356', + 'mu' => '230', + 'mv' => '960', + 'mw' => '265', + 'mx' => '52', + 'my' => '60', + 'mz' => '258', + 'na' => '264', + 'nc' => '687', + 'ne' => '227', + 'nf' => '672', + 'ng' => '234', + 'ni' => '505', + 'nl' => '31', + 'no' => '47', + 'np' => '977', + 'nr' => '674', + 'nu' => '683', + 'nz' => '64', + 'om' => '968', + 'pa' => '507', + 'pe' => '51', + 'pf' => '689', + 'pg' => '675', + 'ph' => '63', + 'pk' => '92', + 'pl' => '48', + 'pm' => '508', + 'pn' => '672', // 'pr' => 'MISSING CODE', // @wikipedia original values: 1 787, 1 939 - 'ps' => '970', - 'pt' => '351', - 'pw' => '680', - 'py' => '595', - 'qa' => '974', - 're' => '262', - 'ro' => '40', - 'rs' => '381', - 'ru' => '7', - 'rw' => '250', - 'sa' => '966', - 'sb' => '677', - 'sc' => '248', - 'sd' => '249', - 'se' => '46', - 'sg' => '65', - 'sh' => '290', - 'si' => '386', - 'sj' => '47', - 'sk' => '421', - 'sl' => '232', - 'sm' => '378', - 'sn' => '221', - 'so' => '252', - 'sr' => '597', - 'ss' => '211', - 'st' => '239', - 'sv' => '503', - 'sx' => '1721', //@wikipedia original value: 1 721 - 'sy' => '963', - 'sz' => '268', - 'tc' => '1649', // @wikipedia original value: 1 649 - 'td' => '235', + 'ps' => '970', + 'pt' => '351', + 'pw' => '680', + 'py' => '595', + 'qa' => '974', + 're' => '262', + 'ro' => '40', + 'rs' => '381', + 'ru' => '7', + 'rw' => '250', + 'sa' => '966', + 'sb' => '677', + 'sc' => '248', + 'sd' => '249', + 'se' => '46', + 'sg' => '65', + 'sh' => '290', + 'si' => '386', + 'sj' => '47', + 'sk' => '421', + 'sl' => '232', + 'sm' => '378', + 'sn' => '221', + 'so' => '252', + 'sr' => '597', + 'ss' => '211', + 'st' => '239', + 'sv' => '503', + 'sx' => '1721', //@wikipedia original value: 1 721 + 'sy' => '963', + 'sz' => '268', + 'tc' => '1649', // @wikipedia original value: 1 649 + 'td' => '235', // 'tf' => 'MISSING CODE', - 'tg' => '228', - 'th' => '66', + 'tg' => '228', + 'th' => '66', // 'ti' => 'MISSING CODE', - 'tj' => '992', - 'tk' => '690', - 'tl' => '670', - 'tm' => '993', - 'tn' => '216', - 'to' => '676', - 'tr' => '90', - 'tt' => '1868', // @wikipedia original value: 1 868 - 'tv' => '688', - 'tw' => '886', - 'tz' => '255', - 'ua' => '380', - 'ug' => '256', + 'tj' => '992', + 'tk' => '690', + 'tl' => '670', + 'tm' => '993', + 'tn' => '216', + 'to' => '676', + 'tr' => '90', + 'tt' => '1868', // @wikipedia original value: 1 868 + 'tv' => '688', + 'tw' => '886', + 'tz' => '255', + 'ua' => '380', + 'ug' => '256', // 'um' => 'MISSING CODE', - 'us' => '1', - 'uy' => '598', - 'uz' => '998', + 'us' => '1', + 'uy' => '598', + 'uz' => '998', // 'va' => 'MISSING CODE', // @wikipedia original values: 39 066, assigned 379 - 'vc' => '1784', // @wikipedia original value: 1 784 - 've' => '58', - 'vg' => '1284', // @wikipedia original value: 1 284 - 'vi' => '1340', // @wikipedia original value: 1 340 - 'vn' => '84', - 'vu' => '678', - 'wf' => '681', - 'ws' => '685', - 'ye' => '967', - 'yt' => '262', - 'za' => '27', - 'zm' => '260', - 'zw' => '263' - ); + 'vc' => '1784', // @wikipedia original value: 1 784 + 've' => '58', + 'vg' => '1284', // @wikipedia original value: 1 284 + 'vi' => '1340', // @wikipedia original value: 1 340 + 'vn' => '84', + 'vu' => '678', + 'wf' => '681', + 'ws' => '685', + 'ye' => '967', + 'yt' => '262', + 'za' => '27', + 'zm' => '260', + 'zw' => '263' + ); } diff --git a/plugins/MobileMessaging/GSMCharset.php b/plugins/MobileMessaging/GSMCharset.php index 22278facb2..4d58c96580 100644 --- a/plugins/MobileMessaging/GSMCharset.php +++ b/plugins/MobileMessaging/GSMCharset.php @@ -16,144 +16,144 @@ */ class Piwik_MobileMessaging_GSMCharset { - public static $GSMCharset = array( + public static $GSMCharset = array( - // Standard GSM Characters, weight = 1 - '@' => 1, - '£' => 1, - '$' => 1, - '¥' => 1, - 'è' => 1, - 'é' => 1, - 'ù' => 1, - 'ì' => 1, - 'ò' => 1, - 'Ç' => 1, - 'Ø' => 1, - 'ø' => 1, - 'Å' => 1, - 'å' => 1, - '∆' => 1, - '_' => 1, - 'Φ' => 1, - 'Γ' => 1, - 'Λ' => 1, - 'Ω' => 1, - 'Π' => 1, - 'Ψ' => 1, - 'Σ' => 1, - 'Θ' => 1, - 'Ξ' => 1, - 'Æ' => 1, - 'æ' => 1, - 'ß' => 1, - 'É' => 1, - ' ' => 1, - '!' => 1, - '"' => 1, - '#' => 1, - '¤' => 1, - '%' => 1, - '&' => 1, - '\'' => 1, - '(' => 1, - ')' => 1, - '*' => 1, - '+' => 1, - ',' => 1, - '-' => 1, - '.' => 1, - '/' => 1, - '0' => 1, - '1' => 1, - '2' => 1, - '3' => 1, - '4' => 1, - '5' => 1, - '6' => 1, - '7' => 1, - '8' => 1, - '9' => 1, - ':' => 1, - ';' => 1, - '<' => 1, - '=' => 1, - '>' => 1, - '?' => 1, - '¡' => 1, - 'A' => 1, - 'B' => 1, - 'C' => 1, - 'D' => 1, - 'E' => 1, - 'F' => 1, - 'G' => 1, - 'H' => 1, - 'I' => 1, - 'J' => 1, - 'K' => 1, - 'L' => 1, - 'M' => 1, - 'N' => 1, - 'O' => 1, - 'P' => 1, - 'Q' => 1, - 'R' => 1, - 'S' => 1, - 'T' => 1, - 'U' => 1, - 'V' => 1, - 'W' => 1, - 'X' => 1, - 'Y' => 1, - 'Z' => 1, - 'Ä' => 1, - 'Ö' => 1, - 'Ñ' => 1, - 'Ü' => 1, - '§' => 1, - '¿' => 1, - 'a' => 1, - 'b' => 1, - 'c' => 1, - 'd' => 1, - 'e' => 1, - 'f' => 1, - 'g' => 1, - 'h' => 1, - 'i' => 1, - 'j' => 1, - 'k' => 1, - 'l' => 1, - 'm' => 1, - 'n' => 1, - 'o' => 1, - 'p' => 1, - 'q' => 1, - 'r' => 1, - 's' => 1, - 't' => 1, - 'u' => 1, - 'v' => 1, - 'w' => 1, - 'x' => 1, - 'y' => 1, - 'z' => 1, - 'ä' => 1, - 'ö' => 1, - 'ñ' => 1, - 'ü' => 1, - 'à' => 1, + // Standard GSM Characters, weight = 1 + '@' => 1, + '£' => 1, + '$' => 1, + '¥' => 1, + 'è' => 1, + 'é' => 1, + 'ù' => 1, + 'ì' => 1, + 'ò' => 1, + 'Ç' => 1, + 'Ø' => 1, + 'ø' => 1, + 'Å' => 1, + 'å' => 1, + '∆' => 1, + '_' => 1, + 'Φ' => 1, + 'Γ' => 1, + 'Λ' => 1, + 'Ω' => 1, + 'Π' => 1, + 'Ψ' => 1, + 'Σ' => 1, + 'Θ' => 1, + 'Ξ' => 1, + 'Æ' => 1, + 'æ' => 1, + 'ß' => 1, + 'É' => 1, + ' ' => 1, + '!' => 1, + '"' => 1, + '#' => 1, + '¤' => 1, + '%' => 1, + '&' => 1, + '\'' => 1, + '(' => 1, + ')' => 1, + '*' => 1, + '+' => 1, + ',' => 1, + '-' => 1, + '.' => 1, + '/' => 1, + '0' => 1, + '1' => 1, + '2' => 1, + '3' => 1, + '4' => 1, + '5' => 1, + '6' => 1, + '7' => 1, + '8' => 1, + '9' => 1, + ':' => 1, + ';' => 1, + '<' => 1, + '=' => 1, + '>' => 1, + '?' => 1, + '¡' => 1, + 'A' => 1, + 'B' => 1, + 'C' => 1, + 'D' => 1, + 'E' => 1, + 'F' => 1, + 'G' => 1, + 'H' => 1, + 'I' => 1, + 'J' => 1, + 'K' => 1, + 'L' => 1, + 'M' => 1, + 'N' => 1, + 'O' => 1, + 'P' => 1, + 'Q' => 1, + 'R' => 1, + 'S' => 1, + 'T' => 1, + 'U' => 1, + 'V' => 1, + 'W' => 1, + 'X' => 1, + 'Y' => 1, + 'Z' => 1, + 'Ä' => 1, + 'Ö' => 1, + 'Ñ' => 1, + 'Ü' => 1, + '§' => 1, + '¿' => 1, + 'a' => 1, + 'b' => 1, + 'c' => 1, + 'd' => 1, + 'e' => 1, + 'f' => 1, + 'g' => 1, + 'h' => 1, + 'i' => 1, + 'j' => 1, + 'k' => 1, + 'l' => 1, + 'm' => 1, + 'n' => 1, + 'o' => 1, + 'p' => 1, + 'q' => 1, + 'r' => 1, + 's' => 1, + 't' => 1, + 'u' => 1, + 'v' => 1, + 'w' => 1, + 'x' => 1, + 'y' => 1, + 'z' => 1, + 'ä' => 1, + 'ö' => 1, + 'ñ' => 1, + 'ü' => 1, + 'à' => 1, - // Extended GSM Characters, weight = 2 - '^' => 2, - '{' => 2, - '}' => 2, - '\\' => 2, - '[' => 2, - '~' => 2, - ']' => 2, - '|' => 2, - '€' => 2, - ); + // Extended GSM Characters, weight = 2 + '^' => 2, + '{' => 2, + '}' => 2, + '\\' => 2, + '[' => 2, + '~' => 2, + ']' => 2, + '|' => 2, + '€' => 2, + ); } diff --git a/plugins/MobileMessaging/MobileMessaging.php b/plugins/MobileMessaging/MobileMessaging.php index e090411562..62028ba3e4 100644 --- a/plugins/MobileMessaging/MobileMessaging.php +++ b/plugins/MobileMessaging/MobileMessaging.php @@ -1,10 +1,10 @@ true, - ); - - static private $managedReportTypes = array( - self::MOBILE_TYPE => 'plugins/MobileMessaging/images/phone.png' - ); - - static private $managedReportFormats = array( - self::SMS_FORMAT => 'plugins/MobileMessaging/images/phone.png' - ); - - static private $availableReports = array( - array( - 'module' => 'MultiSites', - 'action' => 'getAll', - ), - array( - 'module' => 'MultiSites', - 'action' => 'getOne', - ), - ); - - /** - * Return information about this plugin. - * - * @see Piwik_Plugin - * - * @return array - */ - public function getInformation() - { - return array( - 'name' => 'Mobile Messaging Plugin', - 'description' => Piwik_Translate('MobileMessaging_PluginDescription'), - 'homepage' => 'http://piwik.org/', - 'author' => 'Piwik', - 'author_homepage' => 'http://piwik.org/', - 'license' => 'GPL v3 or later', - 'license_homepage' => 'http://www.gnu.org/licenses/gpl.html', - 'version' => Piwik_Version::VERSION, - ); - } - - function getListHooksRegistered() - { - return array( - 'AdminMenu.add' => 'addMenu', - '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.allowMultipleReports' => 'allowMultipleReports', - 'PDFReports.sendReport' => 'sendReport', - 'template_reportParametersPDFReports' => 'template_reportParametersPDFReports', - ); - } - - function addMenu() - { - Piwik_AddAdminMenu( - 'MobileMessaging_SettingsMenu', - array('module' => 'MobileMessaging', 'action' => 'index'), - true - ); - } - - /** - * Get JavaScript files - * - * @param Piwik_Event_Notification $notification notification object - */ - function getJsFiles( $notification ) - { - $jsFiles = &$notification->getNotificationObject(); - - $jsFiles[] = "plugins/MobileMessaging/scripts/MobileMessagingSettings.js"; - } - - /** - * @param Piwik_Event_Notification $notification notification object - */ - function validateReportParameters( $notification ) - { - if(self::manageEvent($notification)) - { - $parameters = &$notification->getNotificationObject(); - - // phone number validation - $availablePhoneNumbers = Piwik_MobileMessaging_API::getInstance()->getActivatedPhoneNumbers(); - - $phoneNumbers = $parameters[self::PHONE_NUMBERS_PARAMETER]; - foreach($phoneNumbers as $key => $phoneNumber) - { - //when a wrong phone number is supplied we silently discard it - if(!in_array($phoneNumber, $availablePhoneNumbers)) - { - unset($phoneNumbers[$key]); - } - } - - // 'unset' seems to transform the array to an associative array - $parameters[self::PHONE_NUMBERS_PARAMETER] = array_values($phoneNumbers); - } - } - - /** - * @param Piwik_Event_Notification $notification notification object - */ - function getReportMetadata( $notification ) - { - if(self::manageEvent($notification)) - { - $availableReportMetadata = &$notification->getNotificationObject(); - - $notificationInfo = $notification->getNotificationInfo(); - $idSite = $notificationInfo[Piwik_PDFReports_API::ID_SITE_INFO_KEY]; - - foreach(self::$availableReports as $availableReport) - { - $reportMetadata = Piwik_API_API::getInstance()->getMetadata( - $idSite, - $availableReport['module'], - $availableReport['action'] - ); - - if($reportMetadata != null) - { - $reportMetadata = reset($reportMetadata); - $availableReportMetadata[] = $reportMetadata; - } - } - } - } - - /** - * @param Piwik_Event_Notification $notification notification object - */ - function getReportTypes( $notification ) - { - $reportTypes = &$notification->getNotificationObject(); - $reportTypes = array_merge($reportTypes, self::$managedReportTypes); - } - - /** - * @param Piwik_Event_Notification $notification notification object - */ - function getReportFormats( $notification ) - { - if(self::manageEvent($notification)) - { - $reportFormats = &$notification->getNotificationObject(); - $reportFormats = array_merge($reportFormats, self::$managedReportFormats); - } - } - - /** - * @param Piwik_Event_Notification $notification notification object - */ - function getReportParameters( $notification ) - { - if(self::manageEvent($notification)) - { - $availableParameters = &$notification->getNotificationObject(); - $availableParameters = self::$availableParameters; - } - } - - /** - * @param Piwik_Event_Notification $notification notification object - */ - function getRendererInstance( $notification ) - { - if(self::manageEvent($notification)) - { - $reportRenderer = &$notification->getNotificationObject(); - - if(Piwik_PluginsManager::getInstance()->isPluginActivated('MultiSites')) - { - $reportRenderer = new Piwik_MobileMessaging_ReportRenderer_Sms(); - } - else - { - $reportRenderer = new Piwik_MobileMessaging_ReportRenderer_Exception( - Piwik_Translate('MobileMessaging_MultiSites_Must_Be_Activated') - ); - } - } - } - - /** - * @param Piwik_Event_Notification $notification notification object - */ - function allowMultipleReports( $notification ) - { - if(self::manageEvent($notification)) - { - $allowMultipleReports = &$notification->getNotificationObject(); - $allowMultipleReports = false; - } - } - - function getReportRecipients( $notification ) - { - if(self::manageEvent($notification)) - { - $recipients = &$notification->getNotificationObject(); - $notificationInfo = $notification->getNotificationInfo(); - - $report = $notificationInfo[Piwik_PDFReports_API::REPORT_KEY]; - $recipients = $report['parameters'][self::PHONE_NUMBERS_PARAMETER]; - } - } - - /** - * @param Piwik_Event_Notification $notification notification object - */ - function sendReport( $notification ) - { - if(self::manageEvent($notification)) - { - $notificationInfo = $notification->getNotificationInfo(); - $report = $notificationInfo[Piwik_PDFReports_API::REPORT_KEY]; - $contents = $notificationInfo[Piwik_PDFReports_API::REPORT_CONTENT_KEY]; - - $parameters = $report['parameters']; - $phoneNumbers = $parameters[self::PHONE_NUMBERS_PARAMETER]; - - if(in_array('MultiSites_getAll', $report['reports'])) - { - $from = Piwik_Translate('General_Reports'); - } - else - { - $from = $notificationInfo[Piwik_PDFReports_API::WEBSITE_NAME_KEY]; - } - - $mobileMessagingAPI = Piwik_MobileMessaging_API::getInstance(); - foreach($phoneNumbers as $phoneNumber) - { - $mobileMessagingAPI->sendSMS( - $contents, - $phoneNumber, - $from - ); - } - } - } - - /** - * @param Piwik_Event_Notification $notification notification object - */ - static public function template_reportParametersPDFReports($notification) - { - if(Piwik::isUserIsAnonymous()) - { - return; - } - - $out =& $notification->getNotificationObject(); - $view = Piwik_View::factory('ReportParameters'); - $view->reportType = self::MOBILE_TYPE; - $view->phoneNumbers = Piwik_MobileMessaging_API::getInstance()->getActivatedPhoneNumbers(); - $out .= $view->render(); - } - - private static function manageEvent($notification) - { - $notificationInfo = $notification->getNotificationInfo(); - return in_array($notificationInfo[Piwik_PDFReports_API::REPORT_TYPE_INFO_KEY], array_keys(self::$managedReportTypes)); - } - - function install() - { - $delegatedManagement = Piwik_GetOption(self::DELEGATED_MANAGEMENT_OPTION); - if (empty($delegatedManagement)) - { - Piwik_SetOption(self::DELEGATED_MANAGEMENT_OPTION, self::DELEGATED_MANAGEMENT_OPTION_DEFAULT); - } - } - - function deactivate() - { - // delete all mobile reports - $pdfReportsAPIInstance = Piwik_PDFReports_API::getInstance(); - $reports = $pdfReportsAPIInstance->getReports(); - - foreach($reports as $report) - { - if ($report['type'] == Piwik_MobileMessaging::MOBILE_TYPE) - { - $pdfReportsAPIInstance->deleteReport($report['idreport']); - } - } - } - - public function uninstall() - { - // currently the UI does not allow to delete a plugin - // when it becomes available, all the MobileMessaging settings (API credentials, phone numbers, etc..) should be removed from the option table - return; - } + const DELEGATED_MANAGEMENT_OPTION = 'MobileMessaging_DelegatedManagement'; + const PROVIDER_OPTION = 'Provider'; + const API_KEY_OPTION = 'APIKey'; + const PHONE_NUMBERS_OPTION = 'PhoneNumbers'; + const PHONE_NUMBER_VALIDATION_REQUEST_COUNT_OPTION = 'PhoneNumberValidationRequestCount'; + const SMS_SENT_COUNT_OPTION = 'SMSSentCount'; + const DELEGATED_MANAGEMENT_OPTION_DEFAULT = 'false'; + const USER_SETTINGS_POSTFIX_OPTION = '_MobileMessagingSettings'; + + const PHONE_NUMBERS_PARAMETER = 'phoneNumbers'; + + const MOBILE_TYPE = 'mobile'; + const SMS_FORMAT = 'sms'; + + static private $availableParameters = array( + self::PHONE_NUMBERS_PARAMETER => true, + ); + + static private $managedReportTypes = array( + self::MOBILE_TYPE => 'plugins/MobileMessaging/images/phone.png' + ); + + static private $managedReportFormats = array( + self::SMS_FORMAT => 'plugins/MobileMessaging/images/phone.png' + ); + + static private $availableReports = array( + array( + 'module' => 'MultiSites', + 'action' => 'getAll', + ), + array( + 'module' => 'MultiSites', + 'action' => 'getOne', + ), + ); + + /** + * Return information about this plugin. + * + * @see Piwik_Plugin + * + * @return array + */ + public function getInformation() + { + return array( + 'name' => 'Mobile Messaging Plugin', + 'description' => Piwik_Translate('MobileMessaging_PluginDescription'), + 'homepage' => 'http://piwik.org/', + 'author' => 'Piwik', + 'author_homepage' => 'http://piwik.org/', + 'license' => 'GPL v3 or later', + 'license_homepage' => 'http://www.gnu.org/licenses/gpl.html', + 'version' => Piwik_Version::VERSION, + ); + } + + function getListHooksRegistered() + { + return array( + 'AdminMenu.add' => 'addMenu', + '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.allowMultipleReports' => 'allowMultipleReports', + 'PDFReports.sendReport' => 'sendReport', + 'template_reportParametersPDFReports' => 'template_reportParametersPDFReports', + ); + } + + function addMenu() + { + Piwik_AddAdminMenu( + 'MobileMessaging_SettingsMenu', + array('module' => 'MobileMessaging', 'action' => 'index'), + true + ); + } + + /** + * Get JavaScript files + * + * @param Piwik_Event_Notification $notification notification object + */ + function getJsFiles($notification) + { + $jsFiles = & $notification->getNotificationObject(); + + $jsFiles[] = "plugins/MobileMessaging/scripts/MobileMessagingSettings.js"; + } + + /** + * @param Piwik_Event_Notification $notification notification object + */ + function validateReportParameters($notification) + { + if (self::manageEvent($notification)) { + $parameters = & $notification->getNotificationObject(); + + // phone number validation + $availablePhoneNumbers = Piwik_MobileMessaging_API::getInstance()->getActivatedPhoneNumbers(); + + $phoneNumbers = $parameters[self::PHONE_NUMBERS_PARAMETER]; + foreach ($phoneNumbers as $key => $phoneNumber) { + //when a wrong phone number is supplied we silently discard it + if (!in_array($phoneNumber, $availablePhoneNumbers)) { + unset($phoneNumbers[$key]); + } + } + + // 'unset' seems to transform the array to an associative array + $parameters[self::PHONE_NUMBERS_PARAMETER] = array_values($phoneNumbers); + } + } + + /** + * @param Piwik_Event_Notification $notification notification object + */ + function getReportMetadata($notification) + { + if (self::manageEvent($notification)) { + $availableReportMetadata = & $notification->getNotificationObject(); + + $notificationInfo = $notification->getNotificationInfo(); + $idSite = $notificationInfo[Piwik_PDFReports_API::ID_SITE_INFO_KEY]; + + foreach (self::$availableReports as $availableReport) { + $reportMetadata = Piwik_API_API::getInstance()->getMetadata( + $idSite, + $availableReport['module'], + $availableReport['action'] + ); + + if ($reportMetadata != null) { + $reportMetadata = reset($reportMetadata); + $availableReportMetadata[] = $reportMetadata; + } + } + } + } + + /** + * @param Piwik_Event_Notification $notification notification object + */ + function getReportTypes($notification) + { + $reportTypes = & $notification->getNotificationObject(); + $reportTypes = array_merge($reportTypes, self::$managedReportTypes); + } + + /** + * @param Piwik_Event_Notification $notification notification object + */ + function getReportFormats($notification) + { + if (self::manageEvent($notification)) { + $reportFormats = & $notification->getNotificationObject(); + $reportFormats = array_merge($reportFormats, self::$managedReportFormats); + } + } + + /** + * @param Piwik_Event_Notification $notification notification object + */ + function getReportParameters($notification) + { + if (self::manageEvent($notification)) { + $availableParameters = & $notification->getNotificationObject(); + $availableParameters = self::$availableParameters; + } + } + + /** + * @param Piwik_Event_Notification $notification notification object + */ + function getRendererInstance($notification) + { + if (self::manageEvent($notification)) { + $reportRenderer = & $notification->getNotificationObject(); + + if (Piwik_PluginsManager::getInstance()->isPluginActivated('MultiSites')) { + $reportRenderer = new Piwik_MobileMessaging_ReportRenderer_Sms(); + } else { + $reportRenderer = new Piwik_MobileMessaging_ReportRenderer_Exception( + Piwik_Translate('MobileMessaging_MultiSites_Must_Be_Activated') + ); + } + } + } + + /** + * @param Piwik_Event_Notification $notification notification object + */ + function allowMultipleReports($notification) + { + if (self::manageEvent($notification)) { + $allowMultipleReports = & $notification->getNotificationObject(); + $allowMultipleReports = false; + } + } + + function getReportRecipients($notification) + { + if (self::manageEvent($notification)) { + $recipients = & $notification->getNotificationObject(); + $notificationInfo = $notification->getNotificationInfo(); + + $report = $notificationInfo[Piwik_PDFReports_API::REPORT_KEY]; + $recipients = $report['parameters'][self::PHONE_NUMBERS_PARAMETER]; + } + } + + /** + * @param Piwik_Event_Notification $notification notification object + */ + function sendReport($notification) + { + if (self::manageEvent($notification)) { + $notificationInfo = $notification->getNotificationInfo(); + $report = $notificationInfo[Piwik_PDFReports_API::REPORT_KEY]; + $contents = $notificationInfo[Piwik_PDFReports_API::REPORT_CONTENT_KEY]; + + $parameters = $report['parameters']; + $phoneNumbers = $parameters[self::PHONE_NUMBERS_PARAMETER]; + + if (in_array('MultiSites_getAll', $report['reports'])) { + $from = Piwik_Translate('General_Reports'); + } else { + $from = $notificationInfo[Piwik_PDFReports_API::WEBSITE_NAME_KEY]; + } + + $mobileMessagingAPI = Piwik_MobileMessaging_API::getInstance(); + foreach ($phoneNumbers as $phoneNumber) { + $mobileMessagingAPI->sendSMS( + $contents, + $phoneNumber, + $from + ); + } + } + } + + /** + * @param Piwik_Event_Notification $notification notification object + */ + static public function template_reportParametersPDFReports($notification) + { + if (Piwik::isUserIsAnonymous()) { + return; + } + + $out =& $notification->getNotificationObject(); + $view = Piwik_View::factory('ReportParameters'); + $view->reportType = self::MOBILE_TYPE; + $view->phoneNumbers = Piwik_MobileMessaging_API::getInstance()->getActivatedPhoneNumbers(); + $out .= $view->render(); + } + + private static function manageEvent($notification) + { + $notificationInfo = $notification->getNotificationInfo(); + return in_array($notificationInfo[Piwik_PDFReports_API::REPORT_TYPE_INFO_KEY], array_keys(self::$managedReportTypes)); + } + + function install() + { + $delegatedManagement = Piwik_GetOption(self::DELEGATED_MANAGEMENT_OPTION); + if (empty($delegatedManagement)) { + Piwik_SetOption(self::DELEGATED_MANAGEMENT_OPTION, self::DELEGATED_MANAGEMENT_OPTION_DEFAULT); + } + } + + function deactivate() + { + // delete all mobile reports + $pdfReportsAPIInstance = Piwik_PDFReports_API::getInstance(); + $reports = $pdfReportsAPIInstance->getReports(); + + foreach ($reports as $report) { + if ($report['type'] == Piwik_MobileMessaging::MOBILE_TYPE) { + $pdfReportsAPIInstance->deleteReport($report['idreport']); + } + } + } + + public function uninstall() + { + // currently the UI does not allow to delete a plugin + // when it becomes available, all the MobileMessaging settings (API credentials, phone numbers, etc..) should be removed from the option table + return; + } } diff --git a/plugins/MobileMessaging/ReportRenderer/Exception.php b/plugins/MobileMessaging/ReportRenderer/Exception.php index af2352150a..a1eac80d8f 100644 --- a/plugins/MobileMessaging/ReportRenderer/Exception.php +++ b/plugins/MobileMessaging/ReportRenderer/Exception.php @@ -15,57 +15,57 @@ */ class Piwik_MobileMessaging_ReportRenderer_Exception extends Piwik_ReportRenderer { - private $rendering = ""; + private $rendering = ""; - function __construct($exception) - { - $this->rendering = $exception; - } + function __construct($exception) + { + $this->rendering = $exception; + } - public function setLocale($locale) - { - // nothing to do - } + public function setLocale($locale) + { + // nothing to do + } - public function sendToDisk($filename) - { - return Piwik_ReportRenderer::writeFile( - $filename, - Piwik_MobileMessaging_ReportRenderer_Sms::SMS_FILE_EXTENSION, - $this->rendering - ); - } + public function sendToDisk($filename) + { + return Piwik_ReportRenderer::writeFile( + $filename, + Piwik_MobileMessaging_ReportRenderer_Sms::SMS_FILE_EXTENSION, + $this->rendering + ); + } - public function sendToBrowserDownload($filename) - { - Piwik_ReportRenderer::sendToBrowser( - $filename, - Piwik_MobileMessaging_ReportRenderer_Sms::SMS_FILE_EXTENSION, - Piwik_MobileMessaging_ReportRenderer_Sms::SMS_CONTENT_TYPE, - $this->rendering - ); - } + public function sendToBrowserDownload($filename) + { + Piwik_ReportRenderer::sendToBrowser( + $filename, + Piwik_MobileMessaging_ReportRenderer_Sms::SMS_FILE_EXTENSION, + Piwik_MobileMessaging_ReportRenderer_Sms::SMS_CONTENT_TYPE, + $this->rendering + ); + } - public function sendToBrowserInline($filename) - { - Piwik_ReportRenderer::inlineToBrowser( - Piwik_MobileMessaging_ReportRenderer_Sms::SMS_CONTENT_TYPE, - $this->rendering - ); - } + public function sendToBrowserInline($filename) + { + Piwik_ReportRenderer::inlineToBrowser( + Piwik_MobileMessaging_ReportRenderer_Sms::SMS_CONTENT_TYPE, + $this->rendering + ); + } - public function getRenderedReport() - { - return $this->rendering; - } + public function getRenderedReport() + { + return $this->rendering; + } - public function renderFrontPage($websiteName, $prettyDate, $description, $reportMetadata) - { - // nothing to do - } + public function renderFrontPage($websiteName, $prettyDate, $description, $reportMetadata) + { + // nothing to do + } - public function renderReport($processedReport) - { - // nothing to do - } + public function renderReport($processedReport) + { + // nothing to do + } } diff --git a/plugins/MobileMessaging/ReportRenderer/Sms.php b/plugins/MobileMessaging/ReportRenderer/Sms.php index 47186f3ff6..96f2999d9f 100644 --- a/plugins/MobileMessaging/ReportRenderer/Sms.php +++ b/plugins/MobileMessaging/ReportRenderer/Sms.php @@ -16,65 +16,64 @@ */ class Piwik_MobileMessaging_ReportRenderer_Sms extends Piwik_ReportRenderer { - const FLOAT_REGEXP = '/[-+]?[0-9]*[\.,]?[0-9]+/'; - const SMS_CONTENT_TYPE = 'text/plain'; - const SMS_FILE_EXTENSION = 'sms'; - - private $rendering = ""; - - public function setLocale($locale) - { - // nothing to do - } - - public function sendToDisk($filename) - { - return Piwik_ReportRenderer::writeFile($filename, self::SMS_FILE_EXTENSION, $this->rendering); - } - - public function sendToBrowserDownload($filename) - { - Piwik_ReportRenderer::sendToBrowser($filename, self::SMS_FILE_EXTENSION, self::SMS_CONTENT_TYPE, $this->rendering); - } - - public function sendToBrowserInline($filename) - { - Piwik_ReportRenderer::inlineToBrowser(self::SMS_CONTENT_TYPE, $this->rendering); - } - - public function getRenderedReport() - { - return $this->rendering; - } - - public function renderFrontPage($websiteName, $prettyDate, $description, $reportMetadata) - { - // nothing to do - } - - public function renderReport($processedReport) - { - $isGoalPluginEnabled = Piwik_Common::isGoalPluginEnabled(); - $prettyDate = $processedReport['prettyDate']; - $reportData = $processedReport['reportData']; - - $evolutionMetrics = array(); - $multiSitesAPIMetrics = Piwik_MultiSites_API::getApiMetrics($enhanced = true); - foreach($multiSitesAPIMetrics as $metricSettings) - { - $evolutionMetrics[] = $metricSettings[Piwik_MultiSites_API::METRIC_EVOLUTION_COL_NAME_KEY]; - } - - // no decimal for all metrics to shorten SMS content (keeps the monetary sign for revenue metrics) - $reportData->filter( - 'ColumnCallbackReplace', - array( - array_merge(array_keys($multiSitesAPIMetrics),$evolutionMetrics), - create_function ( - '$value', - ' - return preg_replace_callback ( - "'.self::FLOAT_REGEXP.'", + const FLOAT_REGEXP = '/[-+]?[0-9]*[\.,]?[0-9]+/'; + const SMS_CONTENT_TYPE = 'text/plain'; + const SMS_FILE_EXTENSION = 'sms'; + + private $rendering = ""; + + public function setLocale($locale) + { + // nothing to do + } + + public function sendToDisk($filename) + { + return Piwik_ReportRenderer::writeFile($filename, self::SMS_FILE_EXTENSION, $this->rendering); + } + + public function sendToBrowserDownload($filename) + { + Piwik_ReportRenderer::sendToBrowser($filename, self::SMS_FILE_EXTENSION, self::SMS_CONTENT_TYPE, $this->rendering); + } + + public function sendToBrowserInline($filename) + { + Piwik_ReportRenderer::inlineToBrowser(self::SMS_CONTENT_TYPE, $this->rendering); + } + + public function getRenderedReport() + { + return $this->rendering; + } + + public function renderFrontPage($websiteName, $prettyDate, $description, $reportMetadata) + { + // nothing to do + } + + public function renderReport($processedReport) + { + $isGoalPluginEnabled = Piwik_Common::isGoalPluginEnabled(); + $prettyDate = $processedReport['prettyDate']; + $reportData = $processedReport['reportData']; + + $evolutionMetrics = array(); + $multiSitesAPIMetrics = Piwik_MultiSites_API::getApiMetrics($enhanced = true); + foreach ($multiSitesAPIMetrics as $metricSettings) { + $evolutionMetrics[] = $metricSettings[Piwik_MultiSites_API::METRIC_EVOLUTION_COL_NAME_KEY]; + } + + // no decimal for all metrics to shorten SMS content (keeps the monetary sign for revenue metrics) + $reportData->filter( + 'ColumnCallbackReplace', + array( + array_merge(array_keys($multiSitesAPIMetrics), $evolutionMetrics), + create_function( + '$value', + ' + return preg_replace_callback ( + "' . self::FLOAT_REGEXP . '", create_function ( \'$matches\', \'return round($matches[0]);\' @@ -82,47 +81,46 @@ class Piwik_MobileMessaging_ReportRenderer_Sms extends Piwik_ReportRenderer $value ); ' - ) - ) - ); - - // evolution metrics formatting : - // - remove monetary, percentage and white spaces to shorten SMS content - // (this is also needed to be able to test $value != 0 and see if there is an evolution at all in SMSReport.tpl) - // - adds a plus sign - $reportData->filter( - 'ColumnCallbackReplace', - array( - $evolutionMetrics, - create_function ( - '$value', - ' - $matched = preg_match("'.self::FLOAT_REGEXP.'", $value, $matches); + ) + ) + ); + + // evolution metrics formatting : + // - remove monetary, percentage and white spaces to shorten SMS content + // (this is also needed to be able to test $value != 0 and see if there is an evolution at all in SMSReport.tpl) + // - adds a plus sign + $reportData->filter( + 'ColumnCallbackReplace', + array( + $evolutionMetrics, + create_function( + '$value', + ' + $matched = preg_match("' . self::FLOAT_REGEXP . '", $value, $matches); return $matched ? sprintf("%+d",$matches[0]) : $value; ' - ) - ) - ); - - $dataRows = $reportData->getRows(); - $reportMetadata = $processedReport['reportMetadata']; - $reportRowsMetadata = $reportMetadata->getRows(); - - $siteHasECommerce = array(); - foreach($reportRowsMetadata as $rowMetadata) - { - $idSite = $rowMetadata->getColumn('idsite'); - $siteHasECommerce[$idSite] = Piwik_Site::isEcommerceEnabledFor($idSite); - } - - $smarty = new Piwik_Smarty(); - $smarty->assign("isGoalPluginEnabled", $isGoalPluginEnabled); - $smarty->assign("reportRows", $dataRows); - $smarty->assign("reportRowsMetadata", $reportRowsMetadata); - $smarty->assign("prettyDate", $prettyDate); - $smarty->assign("siteHasECommerce", $siteHasECommerce); - $smarty->assign("displaySiteName", $processedReport['metadata']['action'] == 'getAll'); - - $this->rendering .= $smarty->fetch(PIWIK_USER_PATH . '/plugins/MobileMessaging/templates/SMSReport.tpl'); - } + ) + ) + ); + + $dataRows = $reportData->getRows(); + $reportMetadata = $processedReport['reportMetadata']; + $reportRowsMetadata = $reportMetadata->getRows(); + + $siteHasECommerce = array(); + foreach ($reportRowsMetadata as $rowMetadata) { + $idSite = $rowMetadata->getColumn('idsite'); + $siteHasECommerce[$idSite] = Piwik_Site::isEcommerceEnabledFor($idSite); + } + + $smarty = new Piwik_Smarty(); + $smarty->assign("isGoalPluginEnabled", $isGoalPluginEnabled); + $smarty->assign("reportRows", $dataRows); + $smarty->assign("reportRowsMetadata", $reportRowsMetadata); + $smarty->assign("prettyDate", $prettyDate); + $smarty->assign("siteHasECommerce", $siteHasECommerce); + $smarty->assign("displaySiteName", $processedReport['metadata']['action'] == 'getAll'); + + $this->rendering .= $smarty->fetch(PIWIK_USER_PATH . '/plugins/MobileMessaging/templates/SMSReport.tpl'); + } } diff --git a/plugins/MobileMessaging/SMSProvider.php b/plugins/MobileMessaging/SMSProvider.php index 12bd7f0933..9f17311349 100644 --- a/plugins/MobileMessaging/SMSProvider.php +++ b/plugins/MobileMessaging/SMSProvider.php @@ -17,13 +17,13 @@ */ abstract class Piwik_MobileMessaging_SMSProvider { - const MAX_GSM_CHARS_IN_ONE_UNIQUE_SMS = 160; - const MAX_GSM_CHARS_IN_ONE_CONCATENATED_SMS = 153; - const MAX_UCS2_CHARS_IN_ONE_UNIQUE_SMS = 70; - const MAX_UCS2_CHARS_IN_ONE_CONCATENATED_SMS = 67; + const MAX_GSM_CHARS_IN_ONE_UNIQUE_SMS = 160; + const MAX_GSM_CHARS_IN_ONE_CONCATENATED_SMS = 153; + const MAX_UCS2_CHARS_IN_ONE_UNIQUE_SMS = 70; + const MAX_UCS2_CHARS_IN_ONE_CONCATENATED_SMS = 67; - static public $availableSMSProviders = array( - 'Clockwork' => 'You can use to send SMS Reports from Piwik.
+ static public $availableSMSProviders = array( + 'Clockwork' => 'You can use to send SMS Reports from Piwik.
  • First, get an API Key from Clockwork (Signup is free!)
  • Enter your Clockwork API Key on this page.
  • @@ -35,141 +35,138 @@ abstract class Piwik_MobileMessaging_SMSProvider
', - ); - - /** - * Return the SMSProvider associated to the provider name $providerName - * - * @throws exception If the provider is unknown - * @param string $providerName - * @return Piwik_MobileMessaging_SMSProvider - */ - static public function factory($providerName) - { - $className = 'Piwik_MobileMessaging_SMSProvider_' . $providerName; - - try { - Piwik_Loader::loadClass($className); - return new $className; - } catch(Exception $e) { - throw new Exception( - Piwik_TranslateException( - 'MobileMessaging_Exception_UnknownProvider', - array($providerName, implode(', ', array_keys(self::$availableSMSProviders))) - ) - ); - } - } - - /** - * Assert whether a given String contains UCS2 characters - * - * @param string $string - * @return bool true if $string contains UCS2 characters - */ - static public function containsUCS2Characters($string) - { - $GSMCharsetAsString = implode(array_keys(Piwik_MobileMessaging_GSMCharset::$GSMCharset)); - - foreach(self::mb_str_split($string) as $char) - { - if(mb_strpos($GSMCharsetAsString, $char) === false) { - return true; - } - } - - return false; - } - - /** - * Truncate $string and append $appendedString at the end if $string can not fit the - * the $maximumNumberOfConcatenatedSMS. - * - * @param string $string String to truncate - * @param int $maximumNumberOfConcatenatedSMS - * @param string $appendedString - * @return string original $string or truncated $string appended with $appendedString - */ - static public function truncate($string, $maximumNumberOfConcatenatedSMS, $appendedString = 'MobileMessaging_SMS_Content_Too_Long') - { - $appendedString = Piwik_Translate($appendedString); - - $smsContentContainsUCS2Chars = self::containsUCS2Characters($string); - $maxCharsAllowed = self::maxCharsAllowed($maximumNumberOfConcatenatedSMS, $smsContentContainsUCS2Chars); - $sizeOfSMSContent = self::sizeOfSMSContent($string, $smsContentContainsUCS2Chars); - - if($sizeOfSMSContent <= $maxCharsAllowed) return $string; - - $smsContentContainsUCS2Chars = $smsContentContainsUCS2Chars || self::containsUCS2Characters($appendedString); - $maxCharsAllowed = self::maxCharsAllowed($maximumNumberOfConcatenatedSMS, $smsContentContainsUCS2Chars); - $sizeOfSMSContent = self::sizeOfSMSContent($string . $appendedString, $smsContentContainsUCS2Chars); - - $sizeToTruncate = $sizeOfSMSContent - $maxCharsAllowed; - - $subStrToTruncate = ''; - $subStrSize = 0; - $reversedStringChars = array_reverse(self::mb_str_split($string)); - for($i = 0; $subStrSize < $sizeToTruncate; $i++) - { - $subStrToTruncate = $reversedStringChars[$i] . $subStrToTruncate; - $subStrSize = self::sizeOfSMSContent($subStrToTruncate, $smsContentContainsUCS2Chars); - } - - return preg_replace('/' . preg_quote($subStrToTruncate) . '$/', $appendedString, $string); - } - - static private function mb_str_split($string) - { - return preg_split('//u',$string, -1, PREG_SPLIT_NO_EMPTY); - } - - static private function sizeOfSMSContent($smsContent, $containsUCS2Chars) - { - if($containsUCS2Chars) return mb_strlen($smsContent, 'UTF-8'); - - $sizeOfSMSContent = 0; - foreach(self::mb_str_split($smsContent) as $char) - { - $sizeOfSMSContent += Piwik_MobileMessaging_GSMCharset::$GSMCharset[$char]; - } - return $sizeOfSMSContent; - } - - static private function maxCharsAllowed($maximumNumberOfConcatenatedSMS, $containsUCS2Chars) - { - $maxCharsInOneUniqueSMS = $containsUCS2Chars ? self::MAX_UCS2_CHARS_IN_ONE_UNIQUE_SMS : self::MAX_GSM_CHARS_IN_ONE_UNIQUE_SMS; - $maxCharsInOneConcatenatedSMS = $containsUCS2Chars ? self::MAX_UCS2_CHARS_IN_ONE_CONCATENATED_SMS : self::MAX_GSM_CHARS_IN_ONE_CONCATENATED_SMS; - - $uniqueSMS = $maximumNumberOfConcatenatedSMS == 1; - - return $uniqueSMS ? - $maxCharsInOneUniqueSMS : - $maxCharsInOneConcatenatedSMS * $maximumNumberOfConcatenatedSMS; - } - - /** - * verify the SMS API credential - * - * @param string $apiKey API Key - * @return bool true if SMS API credential are valid, false otherwise - */ - abstract public function verifyCredential($apiKey); - - /** - * get remaining credits - * - * @param string $apiKey API Key - * @return string remaining credits - */ - abstract public function getCreditLeft($apiKey); - - /** - * send SMS - * - * @param string $apiKey - * @param string $smsText - * @param string $phoneNumber - * @return bool true - */ - abstract public function sendSMS($apiKey, $smsText, $phoneNumber, $from); + ); + + /** + * Return the SMSProvider associated to the provider name $providerName + * + * @throws exception If the provider is unknown + * @param string $providerName + * @return Piwik_MobileMessaging_SMSProvider + */ + static public function factory($providerName) + { + $className = 'Piwik_MobileMessaging_SMSProvider_' . $providerName; + + try { + Piwik_Loader::loadClass($className); + return new $className; + } catch (Exception $e) { + throw new Exception( + Piwik_TranslateException( + 'MobileMessaging_Exception_UnknownProvider', + array($providerName, implode(', ', array_keys(self::$availableSMSProviders))) + ) + ); + } + } + + /** + * Assert whether a given String contains UCS2 characters + * + * @param string $string + * @return bool true if $string contains UCS2 characters + */ + static public function containsUCS2Characters($string) + { + $GSMCharsetAsString = implode(array_keys(Piwik_MobileMessaging_GSMCharset::$GSMCharset)); + + foreach (self::mb_str_split($string) as $char) { + if (mb_strpos($GSMCharsetAsString, $char) === false) { + return true; + } + } + + return false; + } + + /** + * Truncate $string and append $appendedString at the end if $string can not fit the + * the $maximumNumberOfConcatenatedSMS. + * + * @param string $string String to truncate + * @param int $maximumNumberOfConcatenatedSMS + * @param string $appendedString + * @return string original $string or truncated $string appended with $appendedString + */ + static public function truncate($string, $maximumNumberOfConcatenatedSMS, $appendedString = 'MobileMessaging_SMS_Content_Too_Long') + { + $appendedString = Piwik_Translate($appendedString); + + $smsContentContainsUCS2Chars = self::containsUCS2Characters($string); + $maxCharsAllowed = self::maxCharsAllowed($maximumNumberOfConcatenatedSMS, $smsContentContainsUCS2Chars); + $sizeOfSMSContent = self::sizeOfSMSContent($string, $smsContentContainsUCS2Chars); + + if ($sizeOfSMSContent <= $maxCharsAllowed) return $string; + + $smsContentContainsUCS2Chars = $smsContentContainsUCS2Chars || self::containsUCS2Characters($appendedString); + $maxCharsAllowed = self::maxCharsAllowed($maximumNumberOfConcatenatedSMS, $smsContentContainsUCS2Chars); + $sizeOfSMSContent = self::sizeOfSMSContent($string . $appendedString, $smsContentContainsUCS2Chars); + + $sizeToTruncate = $sizeOfSMSContent - $maxCharsAllowed; + + $subStrToTruncate = ''; + $subStrSize = 0; + $reversedStringChars = array_reverse(self::mb_str_split($string)); + for ($i = 0; $subStrSize < $sizeToTruncate; $i++) { + $subStrToTruncate = $reversedStringChars[$i] . $subStrToTruncate; + $subStrSize = self::sizeOfSMSContent($subStrToTruncate, $smsContentContainsUCS2Chars); + } + + return preg_replace('/' . preg_quote($subStrToTruncate) . '$/', $appendedString, $string); + } + + static private function mb_str_split($string) + { + return preg_split('//u', $string, -1, PREG_SPLIT_NO_EMPTY); + } + + static private function sizeOfSMSContent($smsContent, $containsUCS2Chars) + { + if ($containsUCS2Chars) return mb_strlen($smsContent, 'UTF-8'); + + $sizeOfSMSContent = 0; + foreach (self::mb_str_split($smsContent) as $char) { + $sizeOfSMSContent += Piwik_MobileMessaging_GSMCharset::$GSMCharset[$char]; + } + return $sizeOfSMSContent; + } + + static private function maxCharsAllowed($maximumNumberOfConcatenatedSMS, $containsUCS2Chars) + { + $maxCharsInOneUniqueSMS = $containsUCS2Chars ? self::MAX_UCS2_CHARS_IN_ONE_UNIQUE_SMS : self::MAX_GSM_CHARS_IN_ONE_UNIQUE_SMS; + $maxCharsInOneConcatenatedSMS = $containsUCS2Chars ? self::MAX_UCS2_CHARS_IN_ONE_CONCATENATED_SMS : self::MAX_GSM_CHARS_IN_ONE_CONCATENATED_SMS; + + $uniqueSMS = $maximumNumberOfConcatenatedSMS == 1; + + return $uniqueSMS ? + $maxCharsInOneUniqueSMS : + $maxCharsInOneConcatenatedSMS * $maximumNumberOfConcatenatedSMS; + } + + /** + * verify the SMS API credential + * + * @param string $apiKey API Key + * @return bool true if SMS API credential are valid, false otherwise + */ + abstract public function verifyCredential($apiKey); + + /** + * get remaining credits + * + * @param string $apiKey API Key + * @return string remaining credits + */ + abstract public function getCreditLeft($apiKey); + + /** + * send SMS + * + * @param string $apiKey + * @param string $smsText + * @param string $phoneNumber + * @return bool true + */ + abstract public function sendSMS($apiKey, $smsText, $phoneNumber, $from); } diff --git a/plugins/MobileMessaging/SMSProvider/Clockwork.php b/plugins/MobileMessaging/SMSProvider/Clockwork.php index dfb7854fd3..b7a50dfe02 100644 --- a/plugins/MobileMessaging/SMSProvider/Clockwork.php +++ b/plugins/MobileMessaging/SMSProvider/Clockwork.php @@ -16,86 +16,85 @@ require_once PIWIK_INCLUDE_PATH . "/plugins/MobileMessaging/APIException.php"; */ class Piwik_MobileMessaging_SMSProvider_Clockwork extends Piwik_MobileMessaging_SMSProvider { - const SOCKET_TIMEOUT = 15; - - const BASE_API_URL = 'https://api.mediaburst.co.uk/http'; - const CHECK_CREDIT_RESOURCE = '/credit.aspx'; - const SEND_SMS_RESOURCE = '/send.aspx'; - - const ERROR_STRING = 'Error'; - - const MAXIMUM_FROM_LENGTH = 11; - const MAXIMUM_CONCATENATED_SMS = 3; - - public function verifyCredential($apiKey) - { - $this->getCreditLeft($apiKey); - - return true; - } - - public function sendSMS($apiKey, $smsText, $phoneNumber, $from) - { - $from = substr($from, 0, self::MAXIMUM_FROM_LENGTH); - - $smsText = self::truncate($smsText, self::MAXIMUM_CONCATENATED_SMS); - - $additionalParameters = array( - 'To' => str_replace('+','', $phoneNumber), - 'Content' => $smsText, - 'From' => $from, - 'Long' => 1, - 'MsgType' => self::containsUCS2Characters($smsText) ? 'UCS2' : 'TEXT', - ); - - $this->issueApiCall( - $apiKey, - self::SEND_SMS_RESOURCE, - $additionalParameters - ); - } - - private function issueApiCall($apiKey, $resource, $additionalParameters = array()) - { - $accountParameters = array( - 'Key' => $apiKey, - ); - - $parameters = array_merge($accountParameters, $additionalParameters); - - $url = self::BASE_API_URL - . $resource - . '?' . http_build_query($parameters, '', '&'); - - $timeout = self::SOCKET_TIMEOUT; - - $result = Piwik_Http::sendHttpRequestBy( - Piwik_Http::getTransportMethod(), - $url, - $timeout, - $userAgent = null, - $destinationPath = null, - $file = null, - $followDepth = 0, - $acceptLanguage = false, - $acceptInvalidSslCertificate = true - ); - - if(strpos($result, self::ERROR_STRING) !== false) - { - throw new Piwik_MobileMessaging_APIException( - 'Clockwork API returned the following error message : ' . $result - ); - } - - return $result; - } - - public function getCreditLeft($apiKey) - { - return $this->issueApiCall( - $apiKey, - self::CHECK_CREDIT_RESOURCE - ); - } + const SOCKET_TIMEOUT = 15; + + const BASE_API_URL = 'https://api.mediaburst.co.uk/http'; + const CHECK_CREDIT_RESOURCE = '/credit.aspx'; + const SEND_SMS_RESOURCE = '/send.aspx'; + + const ERROR_STRING = 'Error'; + + const MAXIMUM_FROM_LENGTH = 11; + const MAXIMUM_CONCATENATED_SMS = 3; + + public function verifyCredential($apiKey) + { + $this->getCreditLeft($apiKey); + + return true; + } + + public function sendSMS($apiKey, $smsText, $phoneNumber, $from) + { + $from = substr($from, 0, self::MAXIMUM_FROM_LENGTH); + + $smsText = self::truncate($smsText, self::MAXIMUM_CONCATENATED_SMS); + + $additionalParameters = array( + 'To' => str_replace('+', '', $phoneNumber), + 'Content' => $smsText, + 'From' => $from, + 'Long' => 1, + 'MsgType' => self::containsUCS2Characters($smsText) ? 'UCS2' : 'TEXT', + ); + + $this->issueApiCall( + $apiKey, + self::SEND_SMS_RESOURCE, + $additionalParameters + ); + } + + private function issueApiCall($apiKey, $resource, $additionalParameters = array()) + { + $accountParameters = array( + 'Key' => $apiKey, + ); + + $parameters = array_merge($accountParameters, $additionalParameters); + + $url = self::BASE_API_URL + . $resource + . '?' . http_build_query($parameters, '', '&'); + + $timeout = self::SOCKET_TIMEOUT; + + $result = Piwik_Http::sendHttpRequestBy( + Piwik_Http::getTransportMethod(), + $url, + $timeout, + $userAgent = null, + $destinationPath = null, + $file = null, + $followDepth = 0, + $acceptLanguage = false, + $acceptInvalidSslCertificate = true + ); + + if (strpos($result, self::ERROR_STRING) !== false) { + throw new Piwik_MobileMessaging_APIException( + 'Clockwork API returned the following error message : ' . $result + ); + } + + return $result; + } + + public function getCreditLeft($apiKey) + { + return $this->issueApiCall( + $apiKey, + self::CHECK_CREDIT_RESOURCE + ); + } } diff --git a/plugins/MobileMessaging/SMSProvider/StubbedProvider.php b/plugins/MobileMessaging/SMSProvider/StubbedProvider.php index 57f89e73d2..8402eefc51 100644 --- a/plugins/MobileMessaging/SMSProvider/StubbedProvider.php +++ b/plugins/MobileMessaging/SMSProvider/StubbedProvider.php @@ -17,18 +17,18 @@ class Piwik_MobileMessaging_SMSProvider_StubbedProvider extends Piwik_MobileMessaging_SMSProvider { - public function verifyCredential($apiKey) - { - return true; - } + public function verifyCredential($apiKey) + { + return true; + } - public function sendSMS($apiKey, $smsText, $phoneNumber, $from) - { - // nothing to do - } + public function sendSMS($apiKey, $smsText, $phoneNumber, $from) + { + // nothing to do + } - public function getCreditLeft($apiKey) - { - return 1; - } + public function getCreditLeft($apiKey) + { + return 1; + } } \ No newline at end of file diff --git a/plugins/MobileMessaging/scripts/MobileMessagingSettings.js b/plugins/MobileMessaging/scripts/MobileMessagingSettings.js index 840578a1c1..207d4c725d 100644 --- a/plugins/MobileMessaging/scripts/MobileMessagingSettings.js +++ b/plugins/MobileMessaging/scripts/MobileMessagingSettings.js @@ -8,103 +8,93 @@ // TODO when UI stabilized, factorize ajax boiler plate accros MobileMessagingSettings javascript functions var MobileMessagingSettings = MobileMessagingSettings || (function () { - /************************************************************ - * Private data - ************************************************************/ - var - delegatedManagementSelector = 'input[name=delegatedManagement]', - apiAccountSubmitSelector = '#apiAccountSubmit', - addPhoneNumberSubmitSelector = '#addPhoneNumberSubmit', - providersSelector = '#smsProviders', - providerDescriptionsSelector = '.providerDescription', - apiKeySelector = '#apiKey', - countriesSelector = '#countries', - countryCallingCodeSelector = '#countryCallingCode', - newPhoneNumberSelector = '#newPhoneNumber', - suspiciousPhoneNumberSelector = '#suspiciousPhoneNumber', - validatePhoneNumberSubmitSelector = '.validatePhoneNumberSubmit', - formDescriptionSelector = '.form-description', - removePhoneNumberSubmitSelector = '.removePhoneNumberSubmit', - verificationCodeSelector = '.verificationCode', - phoneNumberSelector = '.phoneNumber', - deleteAcountSelector = '#deleteAccount', - confirmDeleteAccountSelector = '#confirmDeleteAccount', - accountFormSelector = '#accountForm', - displayAccountFormSelector = '#displayAccountForm', - phoneNumberActivatedSelector = '#phoneNumberActivated', - invalidActivationCodeMsgSelector = '#invalidActivationCode', - ajaxErrorsSelector = '#ajaxErrorMobileMessagingSettings', - invalidVerificationCodeAjaxErrorSelector = '#invalidVerificationCodeAjaxError', - ajaxLoadingSelector = '#ajaxLoadingMobileMessagingSettings'; - - /************************************************************ - * Private methods - ************************************************************/ - - function initUIEvents() { - - $(delegatedManagementSelector).change(updateDelegatedManagement); - $(apiAccountSubmitSelector).click(updateApiAccount); - $(deleteAcountSelector).click(confirmDeleteApiAccount); - $(displayAccountFormSelector).click(displayAccountForm); - $(addPhoneNumberSubmitSelector).click(addPhoneNumber); - $(newPhoneNumberSelector).keyup(updateSuspiciousPhoneNumberMessage); - $(validatePhoneNumberSubmitSelector).click(validatePhoneNumber); - $(removePhoneNumberSubmitSelector).click(removePhoneNumber); - $(countryCallingCodeSelector).keyup(updateCountry); - $(countriesSelector).change(updateCountryCallingCode); - updateCountryCallingCode(); - $(providersSelector).change(updateProviderDescription); - updateProviderDescription(); - } - - function updateCountry() - { - var countryCallingCode = $(countryCallingCodeSelector).val(); - if(countryCallingCode != null && countryCallingCode != '') - { - var countryToSelect = $(countriesSelector + ' option[value='+countryCallingCode+']'); - if(countryToSelect.size() > 0) - { - countryToSelect.attr('selected', 'selected'); - } - else - { - $(countriesSelector + ' option:selected').removeAttr('selected'); - } - } - } - - function displayAccountForm() - { - $(accountFormSelector).show(); - } - - function validatePhoneNumber(event) - { - var phoneNumberContainer = $(event.target).parent(); - var verificationCodeContainer = phoneNumberContainer.find(verificationCodeSelector); - var verificationCode = verificationCodeContainer.val(); - var phoneNumber = phoneNumberContainer.find(phoneNumberSelector).html(); - - if(verificationCode != null && verificationCode != '') - { - var success = - function(response) - { - $(phoneNumberActivatedSelector).hide(); - if(!response.value) - { - $(invalidVerificationCodeAjaxErrorSelector).html($(invalidActivationCodeMsgSelector).html()).fadeIn(); - } - else - { - $(phoneNumberActivatedSelector).show(); - $(verificationCodeContainer).remove(); - $(phoneNumberContainer).find(validatePhoneNumberSubmitSelector).remove(); - $(phoneNumberContainer).find(formDescriptionSelector).remove(); - } - }; + /************************************************************ + * Private data + ************************************************************/ + var + delegatedManagementSelector = 'input[name=delegatedManagement]', + apiAccountSubmitSelector = '#apiAccountSubmit', + addPhoneNumberSubmitSelector = '#addPhoneNumberSubmit', + providersSelector = '#smsProviders', + providerDescriptionsSelector = '.providerDescription', + apiKeySelector = '#apiKey', + countriesSelector = '#countries', + countryCallingCodeSelector = '#countryCallingCode', + newPhoneNumberSelector = '#newPhoneNumber', + suspiciousPhoneNumberSelector = '#suspiciousPhoneNumber', + validatePhoneNumberSubmitSelector = '.validatePhoneNumberSubmit', + formDescriptionSelector = '.form-description', + removePhoneNumberSubmitSelector = '.removePhoneNumberSubmit', + verificationCodeSelector = '.verificationCode', + phoneNumberSelector = '.phoneNumber', + deleteAcountSelector = '#deleteAccount', + confirmDeleteAccountSelector = '#confirmDeleteAccount', + accountFormSelector = '#accountForm', + displayAccountFormSelector = '#displayAccountForm', + phoneNumberActivatedSelector = '#phoneNumberActivated', + invalidActivationCodeMsgSelector = '#invalidActivationCode', + ajaxErrorsSelector = '#ajaxErrorMobileMessagingSettings', + invalidVerificationCodeAjaxErrorSelector = '#invalidVerificationCodeAjaxError', + ajaxLoadingSelector = '#ajaxLoadingMobileMessagingSettings'; + + /************************************************************ + * Private methods + ************************************************************/ + + function initUIEvents() { + + $(delegatedManagementSelector).change(updateDelegatedManagement); + $(apiAccountSubmitSelector).click(updateApiAccount); + $(deleteAcountSelector).click(confirmDeleteApiAccount); + $(displayAccountFormSelector).click(displayAccountForm); + $(addPhoneNumberSubmitSelector).click(addPhoneNumber); + $(newPhoneNumberSelector).keyup(updateSuspiciousPhoneNumberMessage); + $(validatePhoneNumberSubmitSelector).click(validatePhoneNumber); + $(removePhoneNumberSubmitSelector).click(removePhoneNumber); + $(countryCallingCodeSelector).keyup(updateCountry); + $(countriesSelector).change(updateCountryCallingCode); + updateCountryCallingCode(); + $(providersSelector).change(updateProviderDescription); + updateProviderDescription(); + } + + function updateCountry() { + var countryCallingCode = $(countryCallingCodeSelector).val(); + if (countryCallingCode != null && countryCallingCode != '') { + var countryToSelect = $(countriesSelector + ' option[value=' + countryCallingCode + ']'); + if (countryToSelect.size() > 0) { + countryToSelect.attr('selected', 'selected'); + } + else { + $(countriesSelector + ' option:selected').removeAttr('selected'); + } + } + } + + function displayAccountForm() { + $(accountFormSelector).show(); + } + + function validatePhoneNumber(event) { + var phoneNumberContainer = $(event.target).parent(); + var verificationCodeContainer = phoneNumberContainer.find(verificationCodeSelector); + var verificationCode = verificationCodeContainer.val(); + var phoneNumber = phoneNumberContainer.find(phoneNumberSelector).html(); + + if (verificationCode != null && verificationCode != '') { + var success = + function (response) { + $(phoneNumberActivatedSelector).hide(); + if (!response.value) { + $(invalidVerificationCodeAjaxErrorSelector).html($(invalidActivationCodeMsgSelector).html()).fadeIn(); + } + else { + $(phoneNumberActivatedSelector).show(); + $(verificationCodeContainer).remove(); + $(phoneNumberContainer).find(validatePhoneNumberSubmitSelector).remove(); + $(phoneNumberContainer).find(formDescriptionSelector).remove(); + } + }; var ajaxHandler = new ajaxHelper(); ajaxHandler.addParams({ @@ -117,13 +107,12 @@ var MobileMessagingSettings = MobileMessagingSettings || (function () { ajaxHandler.setLoadingElement(ajaxLoadingSelector); ajaxHandler.setErrorElement(invalidVerificationCodeAjaxErrorSelector); ajaxHandler.send(true); - } - } + } + } - function removePhoneNumber(event) - { - var phoneNumberContainer = $(event.target).parent(); - var phoneNumber = phoneNumberContainer.find(phoneNumberSelector).html(); + function removePhoneNumber(event) { + var phoneNumberContainer = $(event.target).parent(); + var phoneNumber = phoneNumberContainer.find(phoneNumberSelector).html(); var ajaxHandler = new ajaxHelper(); ajaxHandler.addParams({ @@ -136,32 +125,27 @@ var MobileMessagingSettings = MobileMessagingSettings || (function () { ajaxHandler.setLoadingElement(ajaxLoadingSelector); ajaxHandler.setErrorElement(ajaxErrorsSelector); ajaxHandler.send(true); - } - - function updateSuspiciousPhoneNumberMessage() - { - var newPhoneNumber = $(newPhoneNumberSelector).val(); - - // check if number starts with 0 - if($.trim(newPhoneNumber).lastIndexOf('0', 0) === 0) - { - $(suspiciousPhoneNumberSelector).show(); - } - else - { - $(suspiciousPhoneNumberSelector).hide(); - } - } - - function addPhoneNumber() - { - var newPhoneNumber = $(newPhoneNumberSelector).val(); - var countryCallingCode = $(countryCallingCodeSelector).val(); - - var phoneNumber = '+' + countryCallingCode + newPhoneNumber; - - if(newPhoneNumber != null && newPhoneNumber != '') - { + } + + function updateSuspiciousPhoneNumberMessage() { + var newPhoneNumber = $(newPhoneNumberSelector).val(); + + // check if number starts with 0 + if ($.trim(newPhoneNumber).lastIndexOf('0', 0) === 0) { + $(suspiciousPhoneNumberSelector).show(); + } + else { + $(suspiciousPhoneNumberSelector).hide(); + } + } + + function addPhoneNumber() { + var newPhoneNumber = $(newPhoneNumberSelector).val(); + var countryCallingCode = $(countryCallingCodeSelector).val(); + + var phoneNumber = '+' + countryCallingCode + newPhoneNumber; + + if (newPhoneNumber != null && newPhoneNumber != '') { var ajaxHandler = new ajaxHelper(); ajaxHandler.addParams({ module: 'API', @@ -173,30 +157,27 @@ var MobileMessagingSettings = MobileMessagingSettings || (function () { ajaxHandler.setLoadingElement(ajaxLoadingSelector); ajaxHandler.setErrorElement(ajaxErrorsSelector); ajaxHandler.send(true); - } - } - - function updateCountryCallingCode() - { - $(countryCallingCodeSelector).val($(countriesSelector + ' option:selected').val()); - } - - function updateProviderDescription() - { - $(providerDescriptionsSelector).hide(); - $('#' + $(providersSelector + ' option:selected').val() + providerDescriptionsSelector).show(); - } - - function updateDelegatedManagement() { - setDelegatedManagement(getDelegatedManagement()); - } - - function confirmDeleteApiAccount() - { - piwikHelper.modalConfirm(confirmDeleteAccountSelector, {yes: deleteApiAccount}); - } - - function deleteApiAccount() { + } + } + + function updateCountryCallingCode() { + $(countryCallingCodeSelector).val($(countriesSelector + ' option:selected').val()); + } + + function updateProviderDescription() { + $(providerDescriptionsSelector).hide(); + $('#' + $(providersSelector + ' option:selected').val() + providerDescriptionsSelector).show(); + } + + function updateDelegatedManagement() { + setDelegatedManagement(getDelegatedManagement()); + } + + function confirmDeleteApiAccount() { + piwikHelper.modalConfirm(confirmDeleteAccountSelector, {yes: deleteApiAccount}); + } + + function deleteApiAccount() { var ajaxHandler = new ajaxHelper(); ajaxHandler.addParams({ module: 'API', @@ -207,14 +188,14 @@ var MobileMessagingSettings = MobileMessagingSettings || (function () { ajaxHandler.setLoadingElement(ajaxLoadingSelector); ajaxHandler.setErrorElement(ajaxErrorsSelector); ajaxHandler.send(true); - } + } - function updateApiAccount() { + function updateApiAccount() { - var provider = $(providersSelector + ' option:selected').val(); - var apiKey = $(apiKeySelector).val(); + var provider = $(providersSelector + ' option:selected').val(); + var apiKey = $(apiKeySelector).val(); - if(apiKey != '') { + if (apiKey != '') { var ajaxHandler = new ajaxHelper(); ajaxHandler.addParams({ module: 'API', @@ -226,10 +207,10 @@ var MobileMessagingSettings = MobileMessagingSettings || (function () { ajaxHandler.setLoadingElement(ajaxLoadingSelector); ajaxHandler.setErrorElement(ajaxErrorsSelector); ajaxHandler.send(true); - } - } + } + } - function setDelegatedManagement(delegatedManagement) { + function setDelegatedManagement(delegatedManagement) { var ajaxHandler = new ajaxHelper(); ajaxHandler.addParams({ module: 'API', @@ -241,28 +222,28 @@ var MobileMessagingSettings = MobileMessagingSettings || (function () { ajaxHandler.setLoadingElement(ajaxLoadingSelector); ajaxHandler.setErrorElement(ajaxErrorsSelector); ajaxHandler.send(true); - } + } - function getDelegatedManagement() { - return $(delegatedManagementSelector + ':checked').val(); - } + function getDelegatedManagement() { + return $(delegatedManagementSelector + ':checked').val(); + } - /************************************************************ - * Public data and methods - ************************************************************/ + /************************************************************ + * Public data and methods + ************************************************************/ - return { + return { - /* - * Initialize UI events - */ - initUIEvents: function () { - initUIEvents(); - } - }; + /* + * Initialize UI events + */ + initUIEvents: function () { + initUIEvents(); + } + }; }()); -$(document).ready( function() { - MobileMessagingSettings.initUIEvents(); +$(document).ready(function () { + MobileMessagingSettings.initUIEvents(); }); diff --git a/plugins/MobileMessaging/templates/ReportParameters.tpl b/plugins/MobileMessaging/templates/ReportParameters.tpl index dceecaf490..24a6154667 100644 --- a/plugins/MobileMessaging/templates/ReportParameters.tpl +++ b/plugins/MobileMessaging/templates/ReportParameters.tpl @@ -1,64 +1,71 @@ - - {'MobileMessaging_MobileReport_PhoneNumbers'|translate} - - - {if $phoneNumbers|@count eq 0} -
- {'MobileMessaging_MobileReport_NoPhoneNumbers'|translate} - {else} - {foreach from=$phoneNumbers item=phoneNumber} -
- {/foreach} -
- {'MobileMessaging_MobileReport_AdditionalPhoneNumbers'|translate} - {/if} - {'MobileMessaging_MobileReport_MobileMessagingSettingsLink'|translate} -
- + + {'MobileMessaging_MobileReport_PhoneNumbers'|translate} + + + {if $phoneNumbers|@count eq 0} +
+ {'MobileMessaging_MobileReport_NoPhoneNumbers'|translate} + {else} + {foreach from=$phoneNumbers item=phoneNumber} + +
+ {/foreach} +
+ {'MobileMessaging_MobileReport_AdditionalPhoneNumbers'|translate} + {/if} + {'MobileMessaging_MobileReport_MobileMessagingSettingsLink'|translate} +
+ diff --git a/plugins/MobileMessaging/templates/SMSReport.tpl b/plugins/MobileMessaging/templates/SMSReport.tpl index 2084ce426f..c74ea165c4 100644 --- a/plugins/MobileMessaging/templates/SMSReport.tpl +++ b/plugins/MobileMessaging/templates/SMSReport.tpl @@ -1,71 +1,71 @@ {strip} - {$prettyDate}.{literal} {/literal} - - {if empty($reportRows)} - {'CoreHome_ThereIsNoDataForThisReport'|translate} - {/if} - - {foreach name=reportRows from=$reportRows item=row key=rowId} - - {assign var=rowMetrics value=$row->getColumns()} - {assign var=rowMetadata value=$reportRowsMetadata[$rowId]->getColumns()} - - {if $displaySiteName} - {$rowMetrics.label}:{literal} {/literal} - {/if} - - {*visits*} - {$rowMetrics.nb_visits} {'General_ColumnNbVisits'|translate} - {if $rowMetrics.visits_evolution != 0} - {literal} {/literal}({$rowMetrics.visits_evolution}%) - {/if} - - {if $rowMetrics.nb_visits != 0} - - {*actions*} - ,{literal} {/literal} - {$rowMetrics.nb_actions} {'General_ColumnNbActions'|translate} - {if $rowMetrics.actions_evolution != 0} - {literal} {/literal}({$rowMetrics.actions_evolution}%) - {/if} - - {if $isGoalPluginEnabled} - - {*goal metrics*} - {if $rowMetrics.nb_conversions != 0} - - ,{literal} {/literal} - {'Goals_ColumnRevenue'|translate}:{literal} {/literal}{$rowMetrics.revenue} - {if $rowMetrics.revenue_evolution != 0} - {literal} {/literal}({$rowMetrics.revenue_evolution}%) - {/if} - - ,{literal} {/literal} - {$rowMetrics.nb_conversions} {'Goals_GoalConversions'|translate} - {if $rowMetrics.nb_conversions_evolution != 0} - {literal} {/literal}({$rowMetrics.nb_conversions_evolution}%) - {/if} - {/if} - - {*eCommerce metrics*} - {if $siteHasECommerce[$rowMetadata.idsite]} - - ,{literal} {/literal} - {'General_ProductRevenue'|translate}:{literal} {/literal}{$rowMetrics.ecommerce_revenue} - {if $rowMetrics.ecommerce_revenue_evolution != 0} - {literal} {/literal}({$rowMetrics.ecommerce_revenue_evolution}%) - {/if} - - ,{literal} {/literal} - {$rowMetrics.orders} {'General_EcommerceOrders'|translate} - {if $rowMetrics.orders_evolution != 0} - {literal} {/literal}({$rowMetrics.orders_evolution}%) - {/if} - {/if} - {/if} - - {/if} - - {if !$smarty.foreach.reportRows.last}.{literal} {/literal}{/if} - {/foreach} + {$prettyDate}.{literal} {/literal} + + {if empty($reportRows)} + {'CoreHome_ThereIsNoDataForThisReport'|translate} + {/if} + + {foreach name=reportRows from=$reportRows item=row key=rowId} + + {assign var=rowMetrics value=$row->getColumns()} + {assign var=rowMetadata value=$reportRowsMetadata[$rowId]->getColumns()} + + {if $displaySiteName} + {$rowMetrics.label}:{literal} {/literal} + {/if} + + {*visits*} + {$rowMetrics.nb_visits} {'General_ColumnNbVisits'|translate} + {if $rowMetrics.visits_evolution != 0} + {literal} {/literal}({$rowMetrics.visits_evolution}%) + {/if} + + {if $rowMetrics.nb_visits != 0} + + {*actions*} + ,{literal} {/literal} + {$rowMetrics.nb_actions} {'General_ColumnNbActions'|translate} + {if $rowMetrics.actions_evolution != 0} + {literal} {/literal}({$rowMetrics.actions_evolution}%) + {/if} + + {if $isGoalPluginEnabled} + + {*goal metrics*} + {if $rowMetrics.nb_conversions != 0} + + ,{literal} {/literal} + {'Goals_ColumnRevenue'|translate}:{literal} {/literal}{$rowMetrics.revenue} + {if $rowMetrics.revenue_evolution != 0} + {literal} {/literal}({$rowMetrics.revenue_evolution}%) + {/if} + + ,{literal} {/literal} + {$rowMetrics.nb_conversions} {'Goals_GoalConversions'|translate} + {if $rowMetrics.nb_conversions_evolution != 0} + {literal} {/literal}({$rowMetrics.nb_conversions_evolution}%) + {/if} + {/if} + + {*eCommerce metrics*} + {if $siteHasECommerce[$rowMetadata.idsite]} + + ,{literal} {/literal} + {'General_ProductRevenue'|translate}:{literal} {/literal}{$rowMetrics.ecommerce_revenue} + {if $rowMetrics.ecommerce_revenue_evolution != 0} + {literal} {/literal}({$rowMetrics.ecommerce_revenue_evolution}%) + {/if} + + ,{literal} {/literal} + {$rowMetrics.orders} {'General_EcommerceOrders'|translate} + {if $rowMetrics.orders_evolution != 0} + {literal} {/literal}({$rowMetrics.orders_evolution}%) + {/if} + {/if} + {/if} + + {/if} + + {if !$smarty.foreach.reportRows.last}.{literal} {/literal}{/if} + {/foreach} {/strip} diff --git a/plugins/MobileMessaging/templates/Settings.tpl b/plugins/MobileMessaging/templates/Settings.tpl index 77809a0186..547e637eb8 100644 --- a/plugins/MobileMessaging/templates/Settings.tpl +++ b/plugins/MobileMessaging/templates/Settings.tpl @@ -2,194 +2,201 @@ {loadJavascriptTranslations plugins='MobileMessaging'} {literal} - + {/literal} {if $accountManagedByCurrentUser} -

{'MobileMessaging_Settings_SMSAPIAccount'|translate}

- {if $credentialSupplied} - {'MobileMessaging_Settings_CredentialProvided'|translate:$provider} - {$creditLeft} -
- {'MobileMessaging_Settings_UpdateOrDeleteAccount'|translate:"":"":"":""} - {else} - {'MobileMessaging_Settings_PleaseSignUp'|translate} - {/if} - -
-
- {'MobileMessaging_Settings_SMSProvider'|translate} - - - {'MobileMessaging_Settings_APIKey'|translate} - - - - - {foreach from=$smsProviders key=smsProvider item=description} -
- {$description} -
- {/foreach} - -
+

{'MobileMessaging_Settings_SMSAPIAccount'|translate}

+ {if $credentialSupplied} + {'MobileMessaging_Settings_CredentialProvided'|translate:$provider} + {$creditLeft} +
+ {'MobileMessaging_Settings_UpdateOrDeleteAccount'|translate:"":"":"":""} + {else} + {'MobileMessaging_Settings_PleaseSignUp'|translate} + {/if} +
+
+ {'MobileMessaging_Settings_SMSProvider'|translate} + + + {'MobileMessaging_Settings_APIKey'|translate} + + + + + {foreach from=$smsProviders key=smsProvider item=description} +
+ {$description} +
+ {/foreach} + +
{/if} {ajaxErrorDiv id=ajaxErrorMobileMessagingSettings}

{'MobileMessaging_Settings_PhoneNumbers'|translate}

{if !$credentialSupplied} - {if $accountManagedByCurrentUser} - {'MobileMessaging_Settings_CredentialNotProvided'|translate} - {else} - {'MobileMessaging_Settings_CredentialNotProvidedByAdmin'|translate} - {/if} + {if $accountManagedByCurrentUser} + {'MobileMessaging_Settings_CredentialNotProvided'|translate} + {else} + {'MobileMessaging_Settings_CredentialNotProvidedByAdmin'|translate} + {/if} {else} - {'MobileMessaging_Settings_PhoneNumbers_Help'|translate}

- - - - + + + + + + +
- {'MobileMessaging_Settings_PhoneNumbers_Add'|translate}

+ {'MobileMessaging_Settings_PhoneNumbers_Help'|translate} +
+
+ + + + - - - -
+ {'MobileMessaging_Settings_PhoneNumbers_Add'|translate}

- - +   - - - -
+ + +   + + + +
{'MobileMessaging_Settings_CountryCode'|translate} {'MobileMessaging_Settings_PhoneNumber'|translate} -

- - {'MobileMessaging_Settings_PhoneNumbers_CountryCode_Help'|translate} - - - -
- {$strHelpAddPhone|inlineHelp} - -
- - {if $phoneNumbers|@count gt 0} -
-
- {'MobileMessaging_Settings_ManagePhoneNumbers'|translate}

- {/if} - - {ajaxErrorDiv id=invalidVerificationCodeAjaxError} - - - - - -
    - {foreach from=$phoneNumbers key=phoneNumber item=validated} -
  • - {$phoneNumber} - {if !$validated} - - - {/if} - - {if !$validated} -
    - {'MobileMessaging_Settings_VerificationCodeJustSent'|translate} - {/if} -
    -
    -
  • - {/foreach} -
- -
+

+ + {'MobileMessaging_Settings_PhoneNumbers_CountryCode_Help'|translate} + + + +
+ {$strHelpAddPhone|inlineHelp} + +
+ + {if $phoneNumbers|@count gt 0} +
+
+ {'MobileMessaging_Settings_ManagePhoneNumbers'|translate} +
+
+ {/if} + + {ajaxErrorDiv id=invalidVerificationCodeAjaxError} + + + + + +
    + {foreach from=$phoneNumbers key=phoneNumber item=validated} +
  • + {$phoneNumber} + {if !$validated} + + + {/if} + + {if !$validated} +
    + {'MobileMessaging_Settings_VerificationCodeJustSent'|translate} + {/if} +
    +
    +
  • + {/foreach} +
+ +
{/if} {if $isSuperUser} -

{'MobileMessaging_Settings_SuperAdmin'|translate}

- - - - - -
{'MobileMessaging_Settings_LetUsersManageAPICredential'|translate} -
- -
-
- - -
-
+

{'MobileMessaging_Settings_SuperAdmin'|translate}

+ + + + +
{'MobileMessaging_Settings_LetUsersManageAPICredential'|translate} +
+ +
+
+ + +
+
{/if} {ajaxLoadingDiv id=ajaxLoadingMobileMessagingSettings} @@ -197,8 +204,8 @@ {include file='CoreAdminHome/templates/footer.tpl'}
-

{'MobileMessaging_Settings_DeleteAccountConfirm'|translate}

- - +

{'MobileMessaging_Settings_DeleteAccountConfirm'|translate}

+ +
diff --git a/plugins/MultiSites/API.php b/plugins/MultiSites/API.php index 3f4176bbc0..2277291598 100755 --- a/plugins/MultiSites/API.php +++ b/plugins/MultiSites/API.php @@ -1,10 +1,10 @@ array ( - self::METRIC_TRANSLATION_KEY => 'General_ColumnNbVisits', - self::METRIC_EVOLUTION_COL_NAME_KEY => 'visits_evolution', - self::METRIC_RECORD_NAME_KEY => self::NB_VISITS_METRIC, - self::METRIC_IS_ECOMMERCE_KEY => false, - ), - self::NB_ACTIONS_METRIC => array ( - self::METRIC_TRANSLATION_KEY => 'General_ColumnNbActions', - self::METRIC_EVOLUTION_COL_NAME_KEY => 'actions_evolution', - self::METRIC_RECORD_NAME_KEY => self::NB_ACTIONS_METRIC, - self::METRIC_IS_ECOMMERCE_KEY => false, - ), - self::NB_PAGEVIEWS_LABEL => array ( - self::METRIC_TRANSLATION_KEY => 'General_ColumnPageviews', - self::METRIC_EVOLUTION_COL_NAME_KEY => 'pageviews_evolution', - self::METRIC_RECORD_NAME_KEY => self::NB_PAGEVIEWS_METRIC, - self::METRIC_IS_ECOMMERCE_KEY => false, - ) - ); - - /** - * The singleton instance of this class. - */ - static private $instance = null; - - /** - * Returns the singleton instance of this class. The instance is created - * if it hasn't been already. - * - * @return Piwik_MultiSites_API - */ - static public function getInstance() - { - if (self::$instance == null) - { - self::$instance = new self; - } - - return self::$instance; - } - - /** - * Returns a report displaying the total visits, actions and revenue, as - * well as the evolution of these values, of all existing sites over a - * specified period of time. - * - * If the specified period is not a 'range', this function will calculcate - * evolution metrics. Evolution metrics are metrics that display the - * percent increase/decrease of another metric since the last period. - * - * This function will merge the result of the archive query so each - * row in the result DataTable will correspond to the metrics of a single - * site. If a date range is specified, the result will be a - * DataTable_Array, but it will still be merged. - * - * @param string $period The period type to get data for. - * @param string $date The date(s) to get data for. - * @param bool|string $segment The segments to get data for. - * @param bool|string $_restrictSitesToLogin Hack used to enforce we restrict the returned data to the specified username - * Only used when a scheduled task is running - * @param bool|string $enhanced When true, return additional goal & ecommerce metrics - * @param bool|string $pattern If specified, only the website which names (or site ID) match the pattern will be returned using SitesManager.getPatternMatchSites - * @return Piwik_DataTable - */ - public function getAll($period, $date, $segment = false, $_restrictSitesToLogin = false, $enhanced = false, $pattern = false) - { - Piwik::checkUserHasSomeViewAccess(); - - $idSites = $this->getSitesIdFromPattern($pattern); - - return $this->buildDataTable( - $idSites, - $period, - $date, - $segment, - $_restrictSitesToLogin, - $enhanced, - $multipleWebsitesRequested = true - ); - } - - /** - * Fetches the list of sites which names match the string pattern - * - * @param $pattern - * @return array|string - */ - private function getSitesIdFromPattern($pattern) - { - $idSites = 'all'; - if (!empty($pattern)) { - $sites = Piwik_API_Request::processRequest('SitesManager.getPatternMatchSites', - array('pattern' => $pattern, - // added because caller could overwrite these - 'serialize' => 0, - 'format' => 'original')); - if (!empty($sites)) { - $idSites = array(); - foreach ($sites as $site) { - $idSites[] = $site['idsite']; - } - } - } - return $idSites; - } - - /** - * Same as getAll but for a unique Piwik site - * @see Piwik_MultiSites_API::getAll() - * - * @param int $idSite Id of the Piwik site - * @param string $period The period type to get data for. - * @param string $date The date(s) to get data for. - * @param bool|string $segment The segments to get data for. - * @param bool|string $_restrictSitesToLogin Hack used to enforce we restrict the returned data to the specified username - * Only used when a scheduled task is running - * @param bool|string $enhanced When true, return additional goal & ecommerce metrics - * @return Piwik_DataTable - */ - public function getOne($idSite, $period, $date, $segment = false, $_restrictSitesToLogin = false, $enhanced = false) - { - Piwik::checkUserHasViewAccess($idSite); - return $this->buildDataTable( - $idSite, - $period, - $date, - $segment, - $_restrictSitesToLogin, - $enhanced, - $multipleWebsitesRequested = false - ); - } - - private function buildDataTable($sites, $period, $date, $segment, $_restrictSitesToLogin, $enhanced, $multipleWebsitesRequested) - { - $allWebsitesRequested = ($sites == 'all'); - if($allWebsitesRequested) - { - if (Piwik::isUserIsSuperUser() - // Hack: when this API function is called as a Scheduled Task, Super User status is enforced. - // This means this function would return ALL websites in all cases. - // Instead, we make sure that only the right set of data is returned - && !Piwik_TaskScheduler::isTaskBeingExecuted()) - { - Piwik_Site::setSites( - Piwik_SitesManager_API::getInstance()->getAllSites() - ); - } - else - { - Piwik_Site::setSitesFromArray( - Piwik_SitesManager_API::getInstance()->getSitesWithAtLeastViewAccess($limit = false, $_restrictSitesToLogin) - ); - } - } - - // build the archive type used to query archive data - $archive = Piwik_Archive::build( - $sites, - $period, - $date, - $segment, - $_restrictSitesToLogin - ); - - // determine what data will be displayed - $fieldsToGet = array(); - $columnNameRewrites = array(); - $apiECommerceMetrics = array(); - $apiMetrics = Piwik_MultiSites_API::getApiMetrics($enhanced); - foreach($apiMetrics as $metricName => $metricSettings) - { - $fieldsToGet[] = $metricSettings[self::METRIC_RECORD_NAME_KEY]; - $columnNameRewrites[$metricSettings[self::METRIC_RECORD_NAME_KEY]] = $metricName; - - if($metricSettings[self::METRIC_IS_ECOMMERCE_KEY]) - { - $apiECommerceMetrics[$metricName] = $metricSettings; - } - } - - // get the data - // $dataTable instanceOf Piwik_DataTable_Array - $dataTable = $archive->getDataTableFromNumeric($fieldsToGet); - - // get rid of the DataTable_Array that is created by the IndexedBySite archive type - if($dataTable instanceof Piwik_DataTable_Array - && $multipleWebsitesRequested) - { - $dataTable = $dataTable->mergeChildren(); - } - else - { - if(!$dataTable instanceof Piwik_DataTable_Array) - { - $firstDataTableRow = $dataTable->getFirstRow(); - $firstDataTableRow->setColumn('label', $sites); - } - } - - // calculate total visits/actions/revenue - $this->setMetricsTotalsMetadata($dataTable, $apiMetrics); - - // if the period isn't a range & a lastN/previousN date isn't used, we get the same - // data for the last period to show the evolution of visits/actions/revenue - list($strLastDate, $lastPeriod) = Piwik_Period_Range::getLastDate($date, $period); - if ($strLastDate !== false) - { - if ($lastPeriod !== false) - { - // NOTE: no easy way to set last period date metadata when range of dates is requested. - // will be easier if DataTable_Array::metadata is removed, and metadata that is - // put there is put directly in Piwik_DataTable::metadata. - $dataTable->setMetadata(self::getLastPeriodMetadataName('date'), $lastPeriod); - } - - $pastArchive = Piwik_Archive::build('all', $period, $strLastDate, $segment, $_restrictSitesToLogin); - $pastData = $pastArchive->getDataTableFromNumeric($fieldsToGet); - - $pastData = $pastData->mergeChildren(); - - // use past data to calculate evolution percentages - $this->calculateEvolutionPercentages($dataTable, $pastData, $apiMetrics); - - $this->setPastDataMetadata($dataTable, $pastData, $apiMetrics); - } - - // remove eCommerce related metrics on non eCommerce Piwik sites - // note: this is not optimal in terms of performance: those metrics should not be retrieved in the first place - if($enhanced) - { - // $dataTableRows instanceOf Piwik_DataTable_Row[] - $dataTableRows = $dataTable->getRows(); - - foreach($dataTableRows as $dataTableRow) - { - $siteId = $dataTableRow->getColumn('label'); - if(!Piwik_Site::isEcommerceEnabledFor($siteId)) - { - foreach($apiECommerceMetrics as $metricSettings) - { - $dataTableRow->deleteColumn($metricSettings[self::METRIC_RECORD_NAME_KEY]); - $dataTableRow->deleteColumn($metricSettings[self::METRIC_EVOLUTION_COL_NAME_KEY]); - } - } - } - } - - // move the site id to a metadata column - $dataTable->filter('ColumnCallbackAddMetadata', array('label', 'idsite')); - - // set the label of each row to the site name - if($multipleWebsitesRequested) - { - $getNameFor = array('Piwik_Site', 'getNameFor'); - $dataTable->filter('ColumnCallbackReplace', array('label', $getNameFor)); - } - else - { - $dataTable->filter('ColumnDelete', array('label')); - } - - // replace record names with user friendly metric names - $dataTable->filter('ReplaceColumnNames', array($columnNameRewrites)); - - // Ensures data set sorted, for Metadata output - $dataTable->filter('Sort', array(self::NB_VISITS_METRIC, 'desc', $naturalSort = false)); - - // filter rows without visits - // note: if only one website is queried and there are no visits, we can not remove the row otherwise Piwik_API_ResponseBuilder throws 'Call to a member function getColumns() on a non-object' - if($multipleWebsitesRequested) - { - $dataTable->filter( - 'ColumnCallbackDeleteRow', - array( - self::NB_VISITS_METRIC, - create_function ( '$value', 'return $value != 0;') - ) - ); - } - - return $dataTable; - } - - /** - * Performs a binary filter of two - * DataTables in order to correctly calculate evolution metrics. - * - * @param Piwik_DataTable|Piwik_DataTable_Array $currentData - * @param Piwik_DataTable|Piwik_DataTable_Array $pastData - * @param array $fields The array of string fields to calculate evolution - * metrics for. - */ - private function calculateEvolutionPercentages($currentData, $pastData, $apiMetrics) - { - if ($currentData instanceof Piwik_DataTable_Array) - { - $pastArray = $pastData->getArray(); - foreach ($currentData->getArray() as $subTable) - { - $this->calculateEvolutionPercentages($subTable, current($pastArray), $apiMetrics); - next($pastArray); - } - } - else - { - foreach ($apiMetrics as $metricSettings) - { - $currentData->filter( - 'CalculateEvolutionFilter', - array( - $pastData, - $metricSettings[self::METRIC_EVOLUTION_COL_NAME_KEY], - $metricSettings[self::METRIC_RECORD_NAME_KEY], - $quotientPrecision = 1) - ); - } - } - } - - /** - * Sets the total visits, actions & revenue for a DataTable returned by - * $this->buildDataTable. - * - * @param Piwik_DataTable $dataTable - * @param array $apiMetrics Metrics info. - * @return array Array of three values: total visits, total actions, total revenue - */ - private function setMetricsTotalsMetadata( $dataTable, $apiMetrics ) - { - if ($dataTable instanceof Piwik_DataTable_Array) - { - foreach ($dataTable->getArray() as $table) - { - $this->setMetricsTotalsMetadata($table, $apiMetrics); - } - } - else - { - $revenueMetric = ''; - if (Piwik_Common::isGoalPluginEnabled()) - { - $revenueMetric = Piwik_Goals::getRecordName(self::GOAL_REVENUE_METRIC); - } - - $totals = array(); - foreach ($apiMetrics as $label => $metricInfo) - { - $totalMetadataName = self::getTotalMetadataName($label); - $totals[$totalMetadataName] = 0; - } - - foreach ($dataTable->getRows() as $row) - { - foreach ($apiMetrics as $label => $metricInfo) - { - $totalMetadataName = self::getTotalMetadataName($label); - $totals[$totalMetadataName] += $row->getColumn($metricInfo[self::METRIC_RECORD_NAME_KEY]); - } - } - - foreach ($totals as $name => $value) - { - $dataTable->setMetadata($name, $value); - } - } - } - - /** - * Sets the total evolution metadata for a datatable returned by $this->buildDataTable - * given data for the last period. - * - * @param Piwik_DataTable $dataTable - * @param Piwik_DataTable $pastData - * @param array $apiMetrics Metrics info. - */ - private function setPastDataMetadata( $dataTable, $pastData, $apiMetrics ) - { - if ($dataTable instanceof Piwik_DataTable_Array) - { - $pastArray = $pastData->getArray(); - foreach ($dataTable->getArray() as $subTable) - { - $this->setPastDataMetadata($subTable, current($pastArray), $apiMetrics); - next($pastArray); - } - } - else - { - // calculate total visits/actions/revenue for past data - $this->setMetricsTotalsMetadata($pastData, $apiMetrics); - - foreach ($apiMetrics as $label => $metricInfo) - { - // get the names of metadata to set - $totalMetadataName = self::getTotalMetadataName($label); - $lastPeriodTotalMetadataName = self::getLastPeriodMetadataName($totalMetadataName); - $totalEvolutionMetadataName = - self::getTotalMetadataName($metricInfo[self::METRIC_EVOLUTION_COL_NAME_KEY]); - - // set last period total - $pastTotal = $pastData->getMetadata($totalMetadataName); - $dataTable->setMetadata($lastPeriodTotalMetadataName, $pastTotal); - - // calculate & set evolution - $currentTotal = $dataTable->getMetadata($totalMetadataName); - $evolution = Piwik_DataTable_Filter_CalculateEvolutionFilter::calculate($currentTotal, $pastTotal); - $dataTable->setMetadata($totalEvolutionMetadataName, $evolution); - } - } - } - - /** - * @ignore - */ - public static function getApiMetrics($enhanced) - { - $metrics = self::$baseMetrics; - if (Piwik_Common::isGoalPluginEnabled()) - { - // goal revenue metric - $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_IS_ECOMMERCE_KEY => false, - ); - - if($enhanced) - { - // number of goal conversions metric - $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_IS_ECOMMERCE_KEY => false, - ); - - // number of orders - $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_IS_ECOMMERCE_KEY => true, - ); - - // eCommerce revenue - $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_IS_ECOMMERCE_KEY => true, - ); - } - } - - return $metrics; - } - - private static function getTotalMetadataName( $name ) - { - return 'total_'.$name; - } - - private static function getLastPeriodMetadataName( $name ) - { - return 'last_period_'.$name; - } + const METRIC_TRANSLATION_KEY = 'translation'; + const METRIC_EVOLUTION_COL_NAME_KEY = 'evolution_column_name'; + const METRIC_RECORD_NAME_KEY = 'record_name'; + const METRIC_IS_ECOMMERCE_KEY = 'is_ecommerce'; + + const NB_VISITS_METRIC = 'nb_visits'; + const NB_ACTIONS_METRIC = 'nb_actions'; + const NB_PAGEVIEWS_LABEL = 'nb_pageviews'; + const NB_PAGEVIEWS_METRIC = 'Actions_nb_pageviews'; + const GOAL_REVENUE_METRIC = 'revenue'; + const GOAL_CONVERSION_METRIC = 'nb_conversions'; + const ECOMMERCE_ORDERS_METRIC = 'orders'; + const ECOMMERCE_REVENUE_METRIC = 'ecommerce_revenue'; + + static private $baseMetrics = array( + self::NB_VISITS_METRIC => array( + self::METRIC_TRANSLATION_KEY => 'General_ColumnNbVisits', + self::METRIC_EVOLUTION_COL_NAME_KEY => 'visits_evolution', + self::METRIC_RECORD_NAME_KEY => self::NB_VISITS_METRIC, + self::METRIC_IS_ECOMMERCE_KEY => false, + ), + self::NB_ACTIONS_METRIC => array( + self::METRIC_TRANSLATION_KEY => 'General_ColumnNbActions', + self::METRIC_EVOLUTION_COL_NAME_KEY => 'actions_evolution', + self::METRIC_RECORD_NAME_KEY => self::NB_ACTIONS_METRIC, + self::METRIC_IS_ECOMMERCE_KEY => false, + ), + self::NB_PAGEVIEWS_LABEL => array( + self::METRIC_TRANSLATION_KEY => 'General_ColumnPageviews', + self::METRIC_EVOLUTION_COL_NAME_KEY => 'pageviews_evolution', + self::METRIC_RECORD_NAME_KEY => self::NB_PAGEVIEWS_METRIC, + self::METRIC_IS_ECOMMERCE_KEY => false, + ) + ); + + /** + * The singleton instance of this class. + */ + static private $instance = null; + + /** + * Returns the singleton instance of this class. The instance is created + * if it hasn't been already. + * + * @return Piwik_MultiSites_API + */ + static public function getInstance() + { + if (self::$instance == null) { + self::$instance = new self; + } + + return self::$instance; + } + + /** + * Returns a report displaying the total visits, actions and revenue, as + * well as the evolution of these values, of all existing sites over a + * specified period of time. + * + * If the specified period is not a 'range', this function will calculcate + * evolution metrics. Evolution metrics are metrics that display the + * percent increase/decrease of another metric since the last period. + * + * This function will merge the result of the archive query so each + * row in the result DataTable will correspond to the metrics of a single + * site. If a date range is specified, the result will be a + * DataTable_Array, but it will still be merged. + * + * @param string $period The period type to get data for. + * @param string $date The date(s) to get data for. + * @param bool|string $segment The segments to get data for. + * @param bool|string $_restrictSitesToLogin Hack used to enforce we restrict the returned data to the specified username + * Only used when a scheduled task is running + * @param bool|string $enhanced When true, return additional goal & ecommerce metrics + * @param bool|string $pattern If specified, only the website which names (or site ID) match the pattern will be returned using SitesManager.getPatternMatchSites + * @return Piwik_DataTable + */ + public function getAll($period, $date, $segment = false, $_restrictSitesToLogin = false, $enhanced = false, $pattern = false) + { + Piwik::checkUserHasSomeViewAccess(); + + $idSites = $this->getSitesIdFromPattern($pattern); + + return $this->buildDataTable( + $idSites, + $period, + $date, + $segment, + $_restrictSitesToLogin, + $enhanced, + $multipleWebsitesRequested = true + ); + } + + /** + * Fetches the list of sites which names match the string pattern + * + * @param $pattern + * @return array|string + */ + private function getSitesIdFromPattern($pattern) + { + $idSites = 'all'; + if (!empty($pattern)) { + $sites = Piwik_API_Request::processRequest('SitesManager.getPatternMatchSites', + array('pattern' => $pattern, + // added because caller could overwrite these + 'serialize' => 0, + 'format' => 'original')); + if (!empty($sites)) { + $idSites = array(); + foreach ($sites as $site) { + $idSites[] = $site['idsite']; + } + } + } + return $idSites; + } + + /** + * Same as getAll but for a unique Piwik site + * @see Piwik_MultiSites_API::getAll() + * + * @param int $idSite Id of the Piwik site + * @param string $period The period type to get data for. + * @param string $date The date(s) to get data for. + * @param bool|string $segment The segments to get data for. + * @param bool|string $_restrictSitesToLogin Hack used to enforce we restrict the returned data to the specified username + * Only used when a scheduled task is running + * @param bool|string $enhanced When true, return additional goal & ecommerce metrics + * @return Piwik_DataTable + */ + public function getOne($idSite, $period, $date, $segment = false, $_restrictSitesToLogin = false, $enhanced = false) + { + Piwik::checkUserHasViewAccess($idSite); + return $this->buildDataTable( + $idSite, + $period, + $date, + $segment, + $_restrictSitesToLogin, + $enhanced, + $multipleWebsitesRequested = false + ); + } + + private function buildDataTable($sites, $period, $date, $segment, $_restrictSitesToLogin, $enhanced, $multipleWebsitesRequested) + { + $allWebsitesRequested = ($sites == 'all'); + if ($allWebsitesRequested) { + if (Piwik::isUserIsSuperUser() + // Hack: when this API function is called as a Scheduled Task, Super User status is enforced. + // This means this function would return ALL websites in all cases. + // Instead, we make sure that only the right set of data is returned + && !Piwik_TaskScheduler::isTaskBeingExecuted() + ) { + Piwik_Site::setSites( + Piwik_SitesManager_API::getInstance()->getAllSites() + ); + } else { + Piwik_Site::setSitesFromArray( + Piwik_SitesManager_API::getInstance()->getSitesWithAtLeastViewAccess($limit = false, $_restrictSitesToLogin) + ); + } + } + + // build the archive type used to query archive data + $archive = Piwik_Archive::build( + $sites, + $period, + $date, + $segment, + $_restrictSitesToLogin + ); + + // determine what data will be displayed + $fieldsToGet = array(); + $columnNameRewrites = array(); + $apiECommerceMetrics = array(); + $apiMetrics = Piwik_MultiSites_API::getApiMetrics($enhanced); + foreach ($apiMetrics as $metricName => $metricSettings) { + $fieldsToGet[] = $metricSettings[self::METRIC_RECORD_NAME_KEY]; + $columnNameRewrites[$metricSettings[self::METRIC_RECORD_NAME_KEY]] = $metricName; + + if ($metricSettings[self::METRIC_IS_ECOMMERCE_KEY]) { + $apiECommerceMetrics[$metricName] = $metricSettings; + } + } + + // get the data + // $dataTable instanceOf Piwik_DataTable_Array + $dataTable = $archive->getDataTableFromNumeric($fieldsToGet); + + // get rid of the DataTable_Array that is created by the IndexedBySite archive type + if ($dataTable instanceof Piwik_DataTable_Array + && $multipleWebsitesRequested + ) { + $dataTable = $dataTable->mergeChildren(); + } else { + if (!$dataTable instanceof Piwik_DataTable_Array) { + $firstDataTableRow = $dataTable->getFirstRow(); + $firstDataTableRow->setColumn('label', $sites); + } + } + + // calculate total visits/actions/revenue + $this->setMetricsTotalsMetadata($dataTable, $apiMetrics); + + // if the period isn't a range & a lastN/previousN date isn't used, we get the same + // data for the last period to show the evolution of visits/actions/revenue + list($strLastDate, $lastPeriod) = Piwik_Period_Range::getLastDate($date, $period); + if ($strLastDate !== false) { + if ($lastPeriod !== false) { + // NOTE: no easy way to set last period date metadata when range of dates is requested. + // will be easier if DataTable_Array::metadata is removed, and metadata that is + // put there is put directly in Piwik_DataTable::metadata. + $dataTable->setMetadata(self::getLastPeriodMetadataName('date'), $lastPeriod); + } + + $pastArchive = Piwik_Archive::build('all', $period, $strLastDate, $segment, $_restrictSitesToLogin); + $pastData = $pastArchive->getDataTableFromNumeric($fieldsToGet); + + $pastData = $pastData->mergeChildren(); + + // use past data to calculate evolution percentages + $this->calculateEvolutionPercentages($dataTable, $pastData, $apiMetrics); + + $this->setPastDataMetadata($dataTable, $pastData, $apiMetrics); + } + + // remove eCommerce related metrics on non eCommerce Piwik sites + // note: this is not optimal in terms of performance: those metrics should not be retrieved in the first place + if ($enhanced) { + // $dataTableRows instanceOf Piwik_DataTable_Row[] + $dataTableRows = $dataTable->getRows(); + + foreach ($dataTableRows as $dataTableRow) { + $siteId = $dataTableRow->getColumn('label'); + if (!Piwik_Site::isEcommerceEnabledFor($siteId)) { + foreach ($apiECommerceMetrics as $metricSettings) { + $dataTableRow->deleteColumn($metricSettings[self::METRIC_RECORD_NAME_KEY]); + $dataTableRow->deleteColumn($metricSettings[self::METRIC_EVOLUTION_COL_NAME_KEY]); + } + } + } + } + + // move the site id to a metadata column + $dataTable->filter('ColumnCallbackAddMetadata', array('label', 'idsite')); + + // set the label of each row to the site name + if ($multipleWebsitesRequested) { + $getNameFor = array('Piwik_Site', 'getNameFor'); + $dataTable->filter('ColumnCallbackReplace', array('label', $getNameFor)); + } else { + $dataTable->filter('ColumnDelete', array('label')); + } + + // replace record names with user friendly metric names + $dataTable->filter('ReplaceColumnNames', array($columnNameRewrites)); + + // Ensures data set sorted, for Metadata output + $dataTable->filter('Sort', array(self::NB_VISITS_METRIC, 'desc', $naturalSort = false)); + + // filter rows without visits + // note: if only one website is queried and there are no visits, we can not remove the row otherwise Piwik_API_ResponseBuilder throws 'Call to a member function getColumns() on a non-object' + if ($multipleWebsitesRequested) { + $dataTable->filter( + 'ColumnCallbackDeleteRow', + array( + self::NB_VISITS_METRIC, + create_function('$value', 'return $value != 0;') + ) + ); + } + + return $dataTable; + } + + /** + * Performs a binary filter of two + * DataTables in order to correctly calculate evolution metrics. + * + * @param Piwik_DataTable|Piwik_DataTable_Array $currentData + * @param Piwik_DataTable|Piwik_DataTable_Array $pastData + * @param array $fields The array of string fields to calculate evolution + * metrics for. + */ + private function calculateEvolutionPercentages($currentData, $pastData, $apiMetrics) + { + if ($currentData instanceof Piwik_DataTable_Array) { + $pastArray = $pastData->getArray(); + foreach ($currentData->getArray() as $subTable) { + $this->calculateEvolutionPercentages($subTable, current($pastArray), $apiMetrics); + next($pastArray); + } + } else { + foreach ($apiMetrics as $metricSettings) { + $currentData->filter( + 'CalculateEvolutionFilter', + array( + $pastData, + $metricSettings[self::METRIC_EVOLUTION_COL_NAME_KEY], + $metricSettings[self::METRIC_RECORD_NAME_KEY], + $quotientPrecision = 1) + ); + } + } + } + + /** + * Sets the total visits, actions & revenue for a DataTable returned by + * $this->buildDataTable. + * + * @param Piwik_DataTable $dataTable + * @param array $apiMetrics Metrics info. + * @return array Array of three values: total visits, total actions, total revenue + */ + private function setMetricsTotalsMetadata($dataTable, $apiMetrics) + { + if ($dataTable instanceof Piwik_DataTable_Array) { + foreach ($dataTable->getArray() as $table) { + $this->setMetricsTotalsMetadata($table, $apiMetrics); + } + } else { + $revenueMetric = ''; + if (Piwik_Common::isGoalPluginEnabled()) { + $revenueMetric = Piwik_Goals::getRecordName(self::GOAL_REVENUE_METRIC); + } + + $totals = array(); + foreach ($apiMetrics as $label => $metricInfo) { + $totalMetadataName = self::getTotalMetadataName($label); + $totals[$totalMetadataName] = 0; + } + + foreach ($dataTable->getRows() as $row) { + foreach ($apiMetrics as $label => $metricInfo) { + $totalMetadataName = self::getTotalMetadataName($label); + $totals[$totalMetadataName] += $row->getColumn($metricInfo[self::METRIC_RECORD_NAME_KEY]); + } + } + + foreach ($totals as $name => $value) { + $dataTable->setMetadata($name, $value); + } + } + } + + /** + * Sets the total evolution metadata for a datatable returned by $this->buildDataTable + * given data for the last period. + * + * @param Piwik_DataTable $dataTable + * @param Piwik_DataTable $pastData + * @param array $apiMetrics Metrics info. + */ + private function setPastDataMetadata($dataTable, $pastData, $apiMetrics) + { + if ($dataTable instanceof Piwik_DataTable_Array) { + $pastArray = $pastData->getArray(); + foreach ($dataTable->getArray() as $subTable) { + $this->setPastDataMetadata($subTable, current($pastArray), $apiMetrics); + next($pastArray); + } + } else { + // calculate total visits/actions/revenue for past data + $this->setMetricsTotalsMetadata($pastData, $apiMetrics); + + foreach ($apiMetrics as $label => $metricInfo) { + // get the names of metadata to set + $totalMetadataName = self::getTotalMetadataName($label); + $lastPeriodTotalMetadataName = self::getLastPeriodMetadataName($totalMetadataName); + $totalEvolutionMetadataName = + self::getTotalMetadataName($metricInfo[self::METRIC_EVOLUTION_COL_NAME_KEY]); + + // set last period total + $pastTotal = $pastData->getMetadata($totalMetadataName); + $dataTable->setMetadata($lastPeriodTotalMetadataName, $pastTotal); + + // calculate & set evolution + $currentTotal = $dataTable->getMetadata($totalMetadataName); + $evolution = Piwik_DataTable_Filter_CalculateEvolutionFilter::calculate($currentTotal, $pastTotal); + $dataTable->setMetadata($totalEvolutionMetadataName, $evolution); + } + } + } + + /** + * @ignore + */ + public static function getApiMetrics($enhanced) + { + $metrics = self::$baseMetrics; + if (Piwik_Common::isGoalPluginEnabled()) { + // goal revenue metric + $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_IS_ECOMMERCE_KEY => false, + ); + + if ($enhanced) { + // number of goal conversions metric + $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_IS_ECOMMERCE_KEY => false, + ); + + // number of orders + $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_IS_ECOMMERCE_KEY => true, + ); + + // eCommerce revenue + $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_IS_ECOMMERCE_KEY => true, + ); + } + } + + return $metrics; + } + + private static function getTotalMetadataName($name) + { + return 'total_' . $name; + } + + private static function getLastPeriodMetadataName($name) + { + return 'last_period_' . $name; + } } diff --git a/plugins/MultiSites/Controller.php b/plugins/MultiSites/Controller.php index 46ae9c73d9..cb63dc5746 100644 --- a/plugins/MultiSites/Controller.php +++ b/plugins/MultiSites/Controller.php @@ -1,10 +1,10 @@ limit = Piwik_Config::getInstance()->General['all_websites_website_per_page']; - } - - function index() - { - $this->getSitesInfo($isWidgetized = false); - } - - function standalone() - { - $this->getSitesInfo($isWidgetized = true); - } - - - public function getSitesInfo($isWidgetized) - { - Piwik::checkUserHasSomeViewAccess(); - $displayRevenueColumn = Piwik_Common::isGoalPluginEnabled(); - - $date = Piwik_Common::getRequestVar('date', 'today'); - $period = Piwik_Common::getRequestVar('period', 'day'); - $siteIds = Piwik_SitesManager_API::getInstance()->getSitesIdWithAtLeastViewAccess(); - list($minDate, $maxDate) = $this->getMinMaxDateAcrossWebsites($siteIds); - - // overwrites the default Date set in the parent controller - // Instead of the default current website's local date, - // we set "today" or "yesterday" based on the default Piwik timezone - $piwikDefaultTimezone = Piwik_SitesManager_API::getInstance()->getDefaultTimezone(); - if($period != 'range') - { - $date = $this->getDateParameterInTimezone($date, $piwikDefaultTimezone); - $this->setDate($date); - $date = $date->toString(); - } - $dataTable = Piwik_MultiSites_API::getInstance()->getAll($period, $date, $segment = false); - - - // put data into a form the template will understand better - $digestableData = array(); - 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 - ); - - if ($period != 'range') - { - $digestableData[$idSite]['visits_evolution'] = 0; - $digestableData[$idSite]['pageviews_evolution'] = 0; - } - - if ($displayRevenueColumn) - { - $revenueDefault = $isEcommerceEnabled ? 0 : "'-'"; - - if ($period != 'range') - { - $digestableData[$idSite]['revenue_evolution'] = $revenueDefault; - } - } - } - - foreach($dataTable->getRows() as $row) - { - $idsite = (int)$row->getMetadata('idsite'); - - $site = &$digestableData[$idsite]; - - $site['visits'] = (int)$row->getColumn('nb_visits'); - $site['pageviews'] = (int)$row->getColumn('nb_pageviews'); - - if ($displayRevenueColumn) - { - if ($row->getColumn('revenue') !== false) - { - $site['revenue'] = $row->getColumn('revenue'); - } - } - - if ($period != 'range') - { - $site['visits_evolution'] = $row->getColumn('visits_evolution'); - $site['pageviews_evolution'] = $row->getColumn('pageviews_evolution'); - - if ($displayRevenueColumn) - { - $site['revenue_evolution'] = $row->getColumn('revenue_evolution'); - } - } - } - - $this->applyPrettyMoney($digestableData); - - $view = new Piwik_View("MultiSites/templates/index.tpl"); - $view->isWidgetized = $isWidgetized; - $view->sitesData = array_values($digestableData); - $view->evolutionBy = $this->evolutionBy; - $view->period = $period; - $view->page = $this->page; - $view->limit = $this->limit; - $view->orderBy = $this->orderBy; - $view->order = $this->order; - $view->totalVisits = $dataTable->getMetadata('total_nb_visits'); - $view->totalRevenue = $dataTable->getMetadata('total_revenue'); - - $view->displayRevenueColumn = $displayRevenueColumn; - $view->totalPageviews = $dataTable->getMetadata('total_nb_pageviews'); - $view->pastTotalVisits = $dataTable->getMetadata('last_period_total_nb_visits'); - $view->totalVisitsEvolution = $dataTable->getMetadata('total_visits_evolution'); - if ($view->totalVisitsEvolution > 0) - { - $view->totalVisitsEvolution = '+'.$view->totalVisitsEvolution; - } - - if ($period != 'range') - { - $lastPeriod = Piwik_Period::factory($period, $dataTable->getMetadata('last_period_date')); - $view->pastPeriodPretty = self::getCalendarPrettyDate($lastPeriod); - } - - $params = $this->getGraphParamsModified(); - $view->dateSparkline = $period == 'range' ? $date : $params['date']; - - $view->autoRefreshTodayReport = false; - // if the current date is today, or yesterday, - // in case the website is set to UTC-12), or today in UTC+14, we refresh the page every 5min - if(in_array($date, array( 'today', date('Y-m-d'), - 'yesterday', Piwik_Date::factory('yesterday')->toString('Y-m-d'), - Piwik_Date::factory('now', 'UTC+14')->toString('Y-m-d')))) - { - - $view->autoRefreshTodayReport = Piwik_Config::getInstance()->General['multisites_refresh_after_seconds']; - } - $this->setGeneralVariablesView($view); - $this->setMinDateView($minDate, $view); - $this->setMaxDateView($maxDate, $view); - $view->show_sparklines = Piwik_Config::getInstance()->General['show_multisites_sparklines']; - - echo $view->render(); - } - - /** - * The Multisites reports displays the first calendar date as the earliest day available for all websites. - * Also, today is the later "today" available across all timezones. - * @param array $siteIds Array of IDs for each site being displayed. - * @return array of two Piwik_Date instances. First is the min-date & the second - * is the max date. - */ - private function getMinMaxDateAcrossWebsites($siteIds) - { - $now = Piwik_Date::now(); - - $minDate = null; - $maxDate = $now->subDay(1)->getTimestamp(); - foreach($siteIds as $idsite) - { - // look for 'now' in the website's timezone - $timezone = Piwik_Site::getTimezoneFor($idsite); - $date = Piwik_Date::adjustForTimezone($now->getTimestamp(), $timezone); - if($date > $maxDate) - { - $maxDate = $date; - } - - // look for the absolute minimum date - $creationDate = Piwik_Site::getCreationDateFor($idsite); - $date = Piwik_Date::adjustForTimezone(strtotime($creationDate), $timezone); - if(is_null($minDate) || $date < $minDate) - { - $minDate = $date; - } - } - - return array(Piwik_Date::factory($minDate), Piwik_Date::factory($maxDate)); - } - - protected function applyPrettyMoney(&$sites) - { - foreach($sites as $idsite => &$site) - { - $revenue = "-"; - if(!empty($site['revenue'])) - { - $revenue = Piwik::getPrettyMoney($site['revenue'], $site['idsite'], $htmlAllowed = false); - } - $site['revenue'] = '"'. $revenue . '"'; - } - } - - public function getEvolutionGraph( $fetch = false, $columns = false) - { - if(empty($columns)) - { - $columns = Piwik_Common::getRequestVar('columns'); - } - $api = "API.get"; - - if($columns == 'revenue') - { - $api = "Goals.get"; - } - $view = $this->getLastUnitGraph($this->pluginName, __FUNCTION__, $api); - $view->setColumnsToDisplay($columns); - return $this->renderView($view, $fetch); - } + protected $orderBy = 'visits'; + protected $order = 'desc'; + protected $evolutionBy = 'visits'; + protected $page = 1; + protected $limit = 0; + protected $period; + protected $date; + + function __construct() + { + parent::__construct(); + + $this->limit = Piwik_Config::getInstance()->General['all_websites_website_per_page']; + } + + function index() + { + $this->getSitesInfo($isWidgetized = false); + } + + function standalone() + { + $this->getSitesInfo($isWidgetized = true); + } + + + public function getSitesInfo($isWidgetized) + { + Piwik::checkUserHasSomeViewAccess(); + $displayRevenueColumn = Piwik_Common::isGoalPluginEnabled(); + + $date = Piwik_Common::getRequestVar('date', 'today'); + $period = Piwik_Common::getRequestVar('period', 'day'); + $siteIds = Piwik_SitesManager_API::getInstance()->getSitesIdWithAtLeastViewAccess(); + list($minDate, $maxDate) = $this->getMinMaxDateAcrossWebsites($siteIds); + + // overwrites the default Date set in the parent controller + // Instead of the default current website's local date, + // we set "today" or "yesterday" based on the default Piwik timezone + $piwikDefaultTimezone = Piwik_SitesManager_API::getInstance()->getDefaultTimezone(); + if ($period != 'range') { + $date = $this->getDateParameterInTimezone($date, $piwikDefaultTimezone); + $this->setDate($date); + $date = $date->toString(); + } + $dataTable = Piwik_MultiSites_API::getInstance()->getAll($period, $date, $segment = false); + + + // put data into a form the template will understand better + $digestableData = array(); + 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 + ); + + if ($period != 'range') { + $digestableData[$idSite]['visits_evolution'] = 0; + $digestableData[$idSite]['pageviews_evolution'] = 0; + } + + if ($displayRevenueColumn) { + $revenueDefault = $isEcommerceEnabled ? 0 : "'-'"; + + if ($period != 'range') { + $digestableData[$idSite]['revenue_evolution'] = $revenueDefault; + } + } + } + + foreach ($dataTable->getRows() as $row) { + $idsite = (int)$row->getMetadata('idsite'); + + $site = & $digestableData[$idsite]; + + $site['visits'] = (int)$row->getColumn('nb_visits'); + $site['pageviews'] = (int)$row->getColumn('nb_pageviews'); + + if ($displayRevenueColumn) { + if ($row->getColumn('revenue') !== false) { + $site['revenue'] = $row->getColumn('revenue'); + } + } + + if ($period != 'range') { + $site['visits_evolution'] = $row->getColumn('visits_evolution'); + $site['pageviews_evolution'] = $row->getColumn('pageviews_evolution'); + + if ($displayRevenueColumn) { + $site['revenue_evolution'] = $row->getColumn('revenue_evolution'); + } + } + } + + $this->applyPrettyMoney($digestableData); + + $view = new Piwik_View("MultiSites/templates/index.tpl"); + $view->isWidgetized = $isWidgetized; + $view->sitesData = array_values($digestableData); + $view->evolutionBy = $this->evolutionBy; + $view->period = $period; + $view->page = $this->page; + $view->limit = $this->limit; + $view->orderBy = $this->orderBy; + $view->order = $this->order; + $view->totalVisits = $dataTable->getMetadata('total_nb_visits'); + $view->totalRevenue = $dataTable->getMetadata('total_revenue'); + + $view->displayRevenueColumn = $displayRevenueColumn; + $view->totalPageviews = $dataTable->getMetadata('total_nb_pageviews'); + $view->pastTotalVisits = $dataTable->getMetadata('last_period_total_nb_visits'); + $view->totalVisitsEvolution = $dataTable->getMetadata('total_visits_evolution'); + if ($view->totalVisitsEvolution > 0) { + $view->totalVisitsEvolution = '+' . $view->totalVisitsEvolution; + } + + if ($period != 'range') { + $lastPeriod = Piwik_Period::factory($period, $dataTable->getMetadata('last_period_date')); + $view->pastPeriodPretty = self::getCalendarPrettyDate($lastPeriod); + } + + $params = $this->getGraphParamsModified(); + $view->dateSparkline = $period == 'range' ? $date : $params['date']; + + $view->autoRefreshTodayReport = false; + // if the current date is today, or yesterday, + // in case the website is set to UTC-12), or today in UTC+14, we refresh the page every 5min + if (in_array($date, array('today', date('Y-m-d'), + 'yesterday', Piwik_Date::factory('yesterday')->toString('Y-m-d'), + Piwik_Date::factory('now', 'UTC+14')->toString('Y-m-d'))) + ) { + + $view->autoRefreshTodayReport = Piwik_Config::getInstance()->General['multisites_refresh_after_seconds']; + } + $this->setGeneralVariablesView($view); + $this->setMinDateView($minDate, $view); + $this->setMaxDateView($maxDate, $view); + $view->show_sparklines = Piwik_Config::getInstance()->General['show_multisites_sparklines']; + + echo $view->render(); + } + + /** + * The Multisites reports displays the first calendar date as the earliest day available for all websites. + * Also, today is the later "today" available across all timezones. + * @param array $siteIds Array of IDs for each site being displayed. + * @return array of two Piwik_Date instances. First is the min-date & the second + * is the max date. + */ + private function getMinMaxDateAcrossWebsites($siteIds) + { + $now = Piwik_Date::now(); + + $minDate = null; + $maxDate = $now->subDay(1)->getTimestamp(); + foreach ($siteIds as $idsite) { + // look for 'now' in the website's timezone + $timezone = Piwik_Site::getTimezoneFor($idsite); + $date = Piwik_Date::adjustForTimezone($now->getTimestamp(), $timezone); + if ($date > $maxDate) { + $maxDate = $date; + } + + // look for the absolute minimum date + $creationDate = Piwik_Site::getCreationDateFor($idsite); + $date = Piwik_Date::adjustForTimezone(strtotime($creationDate), $timezone); + if (is_null($minDate) || $date < $minDate) { + $minDate = $date; + } + } + + return array(Piwik_Date::factory($minDate), Piwik_Date::factory($maxDate)); + } + + protected function applyPrettyMoney(&$sites) + { + foreach ($sites as $idsite => &$site) { + $revenue = "-"; + if (!empty($site['revenue'])) { + $revenue = Piwik::getPrettyMoney($site['revenue'], $site['idsite'], $htmlAllowed = false); + } + $site['revenue'] = '"' . $revenue . '"'; + } + } + + public function getEvolutionGraph($fetch = false, $columns = false) + { + if (empty($columns)) { + $columns = Piwik_Common::getRequestVar('columns'); + } + $api = "API.get"; + + if ($columns == 'revenue') { + $api = "Goals.get"; + } + $view = $this->getLastUnitGraph($this->pluginName, __FUNCTION__, $api); + $view->setColumnsToDisplay($columns); + return $this->renderView($view, $fetch); + } } diff --git a/plugins/MultiSites/MultiSites.php b/plugins/MultiSites/MultiSites.php index 967300f9a4..43797132c7 100644 --- a/plugins/MultiSites/MultiSites.php +++ b/plugins/MultiSites/MultiSites.php @@ -15,91 +15,90 @@ */ class Piwik_MultiSites extends Piwik_Plugin { - public function getInformation() - { - return array( - 'description' => Piwik_Translate('MultiSites_PluginDescription'), - 'author' => 'ClearCode.cc', - 'author_homepage' => "http://clearcode.cc/", - 'version' => Piwik_Version::VERSION, - ); - } + public function getInformation() + { + return array( + 'description' => Piwik_Translate('MultiSites_PluginDescription'), + 'author' => 'ClearCode.cc', + 'author_homepage' => "http://clearcode.cc/", + 'version' => Piwik_Version::VERSION, + ); + } - public function getListHooksRegistered() - { - return array( - 'AssetManager.getCssFiles' => 'getCssFiles', - 'AssetManager.getJsFiles' => 'getJsFiles', - 'TopMenu.add' => 'addTopMenu', - 'API.getReportMetadata' => 'getReportMetadata', - ); - } + public function getListHooksRegistered() + { + return array( + 'AssetManager.getCssFiles' => 'getCssFiles', + 'AssetManager.getJsFiles' => 'getJsFiles', + 'TopMenu.add' => 'addTopMenu', + 'API.getReportMetadata' => 'getReportMetadata', + ); + } - /** - * @param Piwik_Event_Notification $notification notification object - */ - public function getReportMetadata($notification) - { - $metadataMetrics = array(); - foreach(Piwik_MultiSites_API::getApiMetrics($enhanced = true) as $metricName => $metricSettings) - { - $metadataMetrics[$metricName] = - Piwik_Translate($metricSettings[Piwik_MultiSites_API::METRIC_TRANSLATION_KEY]); - $metadataMetrics[$metricSettings[Piwik_MultiSites_API::METRIC_EVOLUTION_COL_NAME_KEY]] = - Piwik_Translate($metricSettings[Piwik_MultiSites_API::METRIC_TRANSLATION_KEY]) . " " . Piwik_Translate('MultiSites_Evolution'); - } + /** + * @param Piwik_Event_Notification $notification notification object + */ + public function getReportMetadata($notification) + { + $metadataMetrics = array(); + foreach (Piwik_MultiSites_API::getApiMetrics($enhanced = true) as $metricName => $metricSettings) { + $metadataMetrics[$metricName] = + Piwik_Translate($metricSettings[Piwik_MultiSites_API::METRIC_TRANSLATION_KEY]); + $metadataMetrics[$metricSettings[Piwik_MultiSites_API::METRIC_EVOLUTION_COL_NAME_KEY]] = + Piwik_Translate($metricSettings[Piwik_MultiSites_API::METRIC_TRANSLATION_KEY]) . " " . Piwik_Translate('MultiSites_Evolution'); + } - $reports = &$notification->getNotificationObject(); + $reports = & $notification->getNotificationObject(); - $reports[] = array( - 'category' => Piwik_Translate('General_MultiSitesSummary'), - 'name' => Piwik_Translate('General_AllWebsitesDashboard'), - 'module' => 'MultiSites', - 'action' => 'getAll', - 'dimension' => Piwik_Translate('General_Website'), // re-using translation - 'metrics' => $metadataMetrics, - 'processedMetrics' => false, - 'constantRowsCount' => false, - 'order' => 5 - ); + $reports[] = array( + 'category' => Piwik_Translate('General_MultiSitesSummary'), + 'name' => Piwik_Translate('General_AllWebsitesDashboard'), + 'module' => 'MultiSites', + 'action' => 'getAll', + 'dimension' => Piwik_Translate('General_Website'), // re-using translation + 'metrics' => $metadataMetrics, + 'processedMetrics' => false, + 'constantRowsCount' => false, + 'order' => 5 + ); - $reports[] = array( - 'category' => Piwik_Translate('General_MultiSitesSummary'), - 'name' => Piwik_Translate('General_SingleWebsitesDashboard'), - 'module' => 'MultiSites', - 'action' => 'getOne', - 'dimension' => Piwik_Translate('General_Website'), // re-using translation - 'metrics' => $metadataMetrics, - 'processedMetrics' => false, - 'constantRowsCount' => false, - 'order' => 5 - ); - } + $reports[] = array( + 'category' => Piwik_Translate('General_MultiSitesSummary'), + 'name' => Piwik_Translate('General_SingleWebsitesDashboard'), + 'module' => 'MultiSites', + 'action' => 'getOne', + 'dimension' => Piwik_Translate('General_Website'), // re-using translation + 'metrics' => $metadataMetrics, + 'processedMetrics' => false, + 'constantRowsCount' => false, + 'order' => 5 + ); + } - public function addTopMenu() - { - $urlParams = array('module' => 'MultiSites', 'action' => 'index'); - $tooltip = Piwik_Translate('MultiSites_TopLinkTooltip'); - Piwik_AddTopMenu('General_MultiSitesSummary', $urlParams, true, 3, $isHTML = false, $tooltip); - } + public function addTopMenu() + { + $urlParams = array('module' => 'MultiSites', 'action' => 'index'); + $tooltip = Piwik_Translate('MultiSites_TopLinkTooltip'); + Piwik_AddTopMenu('General_MultiSitesSummary', $urlParams, true, 3, $isHTML = false, $tooltip); + } - /** - * @param Piwik_Event_Notification $notification notification object - */ - function getJsFiles( $notification ) - { - $jsFiles = &$notification->getNotificationObject(); - - $jsFiles[] = "plugins/MultiSites/templates/common.js"; - } + /** + * @param Piwik_Event_Notification $notification notification object + */ + function getJsFiles($notification) + { + $jsFiles = & $notification->getNotificationObject(); - /** - * @param Piwik_Event_Notification $notification notification object - */ - function getCssFiles( $notification ) - { - $cssFiles = &$notification->getNotificationObject(); - - $cssFiles[] = "plugins/MultiSites/templates/styles.css"; - } + $jsFiles[] = "plugins/MultiSites/templates/common.js"; + } + + /** + * @param Piwik_Event_Notification $notification notification object + */ + function getCssFiles($notification) + { + $cssFiles = & $notification->getNotificationObject(); + + $cssFiles[] = "plugins/MultiSites/templates/styles.css"; + } } diff --git a/plugins/MultiSites/templates/common.js b/plugins/MultiSites/templates/common.js index 4ef9c82002..29439401d0 100644 --- a/plugins/MultiSites/templates/common.js +++ b/plugins/MultiSites/templates/common.js @@ -5,248 +5,217 @@ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later */ -function setRowData (idsite, visits, pageviews, revenue, name, url, visitsSummaryValue, pageviewsSummaryValue, revenueSummaryValue) -{ - this.idsite = idsite; - this.visits = visits; - this.revenue = revenue; - this.name = name; - this.url = url; - this.pageviews = pageviews; - this.visitsSummaryValue = parseFloat(visitsSummaryValue); - this.pageviewsSummaryValue = parseFloat(pageviewsSummaryValue); - this.revenueSummaryValue = parseFloat(revenueSummaryValue) || 0; +function setRowData(idsite, visits, pageviews, revenue, name, url, visitsSummaryValue, pageviewsSummaryValue, revenueSummaryValue) { + this.idsite = idsite; + this.visits = visits; + this.revenue = revenue; + this.name = name; + this.url = url; + this.pageviews = pageviews; + this.visitsSummaryValue = parseFloat(visitsSummaryValue); + this.pageviewsSummaryValue = parseFloat(pageviewsSummaryValue); + this.revenueSummaryValue = parseFloat(revenueSummaryValue) || 0; } -function setOrderBy(self, allSites, params, mOrderBy) -{ - if(params['mOrderBy'] == mOrderBy) { - if(params['order'] == 'desc') { - params['order'] = 'asc'; - } else { - params['order'] = 'desc'; - } - } - params['mOrderBy'] = mOrderBy; - prepareRows(allSites, params); - - $('.arrow').removeClass('multisites_desc multisites_asc'); - if($(self).attr('class') == 'evolution') - { - mOrderBy = 'evolution'; - } - $('#' + mOrderBy + ' .arrow').addClass('multisites_' + params['order']); - - return params; +function setOrderBy(self, allSites, params, mOrderBy) { + if (params['mOrderBy'] == mOrderBy) { + if (params['order'] == 'desc') { + params['order'] = 'asc'; + } else { + params['order'] = 'desc'; + } + } + params['mOrderBy'] = mOrderBy; + prepareRows(allSites, params); + + $('.arrow').removeClass('multisites_desc multisites_asc'); + if ($(self).attr('class') == 'evolution') { + mOrderBy = 'evolution'; + } + $('#' + mOrderBy + ' .arrow').addClass('multisites_' + params['order']); + + return params; } -function prepareRows(allUnsortedSites, params) -{ - var allSites; - $("#tb").find("tr").remove(); - $("#next").html(''); - $("#prev").html(''); - var mOrderBy = params['mOrderBy']; - - allSites = orderBy(allUnsortedSites, params); - - if(allSites.length > params['limit']) - { - allSites = limitBy(allSites, params); - } - - displayRows(allSites, params); - - showPagination(allUnsortedSites, params); - params['sitesVisible'] = allSites; +function prepareRows(allUnsortedSites, params) { + var allSites; + $("#tb").find("tr").remove(); + $("#next").html(''); + $("#prev").html(''); + var mOrderBy = params['mOrderBy']; + + allSites = orderBy(allUnsortedSites, params); + + if (allSites.length > params['limit']) { + allSites = limitBy(allSites, params); + } + + displayRows(allSites, params); + + showPagination(allUnsortedSites, params); + params['sitesVisible'] = allSites; } -function orderBy(allSites, params) -{ - if(params['mOrderBy'] == 'names') - { - allSites.sort(function(a,b) { - if (a['name'].toLowerCase() == b['name'].toLowerCase()) - { - return 0; - } - return (a['name'].toLowerCase() < b['name'].toLowerCase()) ? -1 : 1; - }); - } - else if(params['mOrderBy'] == 'visits') - { - allSites.sort(function(a,b) { - if (a['visits'] == b['visits']) { - return 0; - } - return (a['visits'] < b['visits']) ? -1 : 1; - }); - } - else if(params['mOrderBy'] == 'pageviews') - { - allSites.sort(function (a,b) { - if (a['pageviews'] == b['pageviews']) { - return 0; - } - return (a['pageviews'] < b['pageviews']) ? -1 : 1; - }); - } - else if(params['mOrderBy'] == 'revenue') - { - allSites.sort(function (a,b) { - var lhs = parseFloat(a['revenue'].replace(/[^0-9\.]+/g,"")) || 0, - rhs = parseFloat(b['revenue'].replace(/[^0-9\.]+/g,"")) || 0; - - return lhs === rhs ? 0 : ((lhs < rhs) ? -1 : 1); - }); - } - else if(params['mOrderBy'] == 'revenueSummary') - { - allSites.sort(function (a,b) { - if (a['revenueSummaryValue'] == b['revenueSummaryValue']) { - return 0; - } - return (a['revenueSummaryValue'] - b['revenueSummaryValue'] <= 0.01) ? -1 : 1; - }); - } - else if(params['mOrderBy'] == 'pageviewsSummary') - { - allSites.sort(function (a,b) { - if (a['pageviewsSummaryValue'] == b['pageviewsSummaryValue']) { - return 0; - } - return (a['pageviewsSummaryValue'] - b['pageviewsSummaryValue'] <= 0.01) ? -1 : 1; - }); - } - else if(params['mOrderBy'] == 'visitsSummary') - { - allSites.sort(function (a,b) { - if (a['visitsSummaryValue'] == b['visitsSummaryValue']) { - return 0; - } - return (a['visitsSummaryValue'] - b['visitsSummaryValue'] <= 0.01) ? -1 : 1; - }); - } - - if(params['order'] == 'desc') - { - allSites.reverse(); - } - return allSites; +function orderBy(allSites, params) { + if (params['mOrderBy'] == 'names') { + allSites.sort(function (a, b) { + if (a['name'].toLowerCase() == b['name'].toLowerCase()) { + return 0; + } + return (a['name'].toLowerCase() < b['name'].toLowerCase()) ? -1 : 1; + }); + } + else if (params['mOrderBy'] == 'visits') { + allSites.sort(function (a, b) { + if (a['visits'] == b['visits']) { + return 0; + } + return (a['visits'] < b['visits']) ? -1 : 1; + }); + } + else if (params['mOrderBy'] == 'pageviews') { + allSites.sort(function (a, b) { + if (a['pageviews'] == b['pageviews']) { + return 0; + } + return (a['pageviews'] < b['pageviews']) ? -1 : 1; + }); + } + else if (params['mOrderBy'] == 'revenue') { + allSites.sort(function (a, b) { + var lhs = parseFloat(a['revenue'].replace(/[^0-9\.]+/g, "")) || 0, + rhs = parseFloat(b['revenue'].replace(/[^0-9\.]+/g, "")) || 0; + + return lhs === rhs ? 0 : ((lhs < rhs) ? -1 : 1); + }); + } + else if (params['mOrderBy'] == 'revenueSummary') { + allSites.sort(function (a, b) { + if (a['revenueSummaryValue'] == b['revenueSummaryValue']) { + return 0; + } + return (a['revenueSummaryValue'] - b['revenueSummaryValue'] <= 0.01) ? -1 : 1; + }); + } + else if (params['mOrderBy'] == 'pageviewsSummary') { + allSites.sort(function (a, b) { + if (a['pageviewsSummaryValue'] == b['pageviewsSummaryValue']) { + return 0; + } + return (a['pageviewsSummaryValue'] - b['pageviewsSummaryValue'] <= 0.01) ? -1 : 1; + }); + } + else if (params['mOrderBy'] == 'visitsSummary') { + allSites.sort(function (a, b) { + if (a['visitsSummaryValue'] == b['visitsSummaryValue']) { + return 0; + } + return (a['visitsSummaryValue'] - b['visitsSummaryValue'] <= 0.01) ? -1 : 1; + }); + } + + if (params['order'] == 'desc') { + allSites.reverse(); + } + return allSites; } -function limitBy(allSites, params) -{ - var begin = (params['page'] - 1) * params['limit']; - var end = (params['page'] * params['limit']); - return allSites.slice(begin, end); +function limitBy(allSites, params) { + var begin = (params['page'] - 1) * params['limit']; + var end = (params['page'] * params['limit']); + return allSites.slice(begin, end); } -function switchEvolution(params) -{ - $('.pageviews').hide(); - $('.revenue').hide(); - $('.visits').hide(); - $('.' + params['evolutionBy']).show(); - sitesVisible = params['sitesVisible']; - for(i = 0; i < allSites.length; i++) - { - $('#sparkline_' + allSites[i].idsite).html(getSparklineImg(allSites[i].idsite, params['evolutionBy'], params)); - } +function switchEvolution(params) { + $('.pageviews').hide(); + $('.revenue').hide(); + $('.visits').hide(); + $('.' + params['evolutionBy']).show(); + sitesVisible = params['sitesVisible']; + for (i = 0; i < allSites.length; i++) { + $('#sparkline_' + allSites[i].idsite).html(getSparklineImg(allSites[i].idsite, params['evolutionBy'], params)); + } } -function displayRows(allSites, params) -{ - for(var i = 0; i < allSites.length; i++) - { - var str = params['row']; - str = str.replace(/%revenueSummary%/g, getImageForSummary(allSites[i].revenueSummaryValue)); - str = str.replace(/%pageviewsSummary%/g, getImageForSummary(allSites[i].pageviewsSummaryValue)); - str = str.replace(/%visitsSummary%/g, getImageForSummary(allSites[i].visitsSummaryValue)); - str = str.replace(/%sparkline%/g, getSparklineImg(allSites[i].idsite, params['evolutionBy'], params)); - str = str.replace(/%pageviews%/g, allSites[i].pageviews); - str = str.replace(/%idsite%/g, allSites[i].idsite); - str = str.replace(/%visits%/g, allSites[i].visits); - str = str.replace(/%name%/g, allSites[i].name); - str = str.replace(/%revenue%/g, allSites[i].revenue); - str = str.replace(/%main_url%/g, allSites[i].url); - str = str.replace(/%date%/g, params['date'] || params['dateSparkline']); // For period=range, dateSparkline only is set - str = str.replace(/%period%/g, params['period']); - - $('#tb').append('' + str + ''); - } - - $(".table_row").show(); - $('.pageviews').hide(); - $('.revenue').hide(); - $('.visits').hide(); - $('#main_indicator').hide(); - $('.' + params['evolutionBy']).show(); - $("#main_indicator").hide(); +function displayRows(allSites, params) { + for (var i = 0; i < allSites.length; i++) { + var str = params['row']; + str = str.replace(/%revenueSummary%/g, getImageForSummary(allSites[i].revenueSummaryValue)); + str = str.replace(/%pageviewsSummary%/g, getImageForSummary(allSites[i].pageviewsSummaryValue)); + str = str.replace(/%visitsSummary%/g, getImageForSummary(allSites[i].visitsSummaryValue)); + str = str.replace(/%sparkline%/g, getSparklineImg(allSites[i].idsite, params['evolutionBy'], params)); + str = str.replace(/%pageviews%/g, allSites[i].pageviews); + str = str.replace(/%idsite%/g, allSites[i].idsite); + str = str.replace(/%visits%/g, allSites[i].visits); + str = str.replace(/%name%/g, allSites[i].name); + str = str.replace(/%revenue%/g, allSites[i].revenue); + str = str.replace(/%main_url%/g, allSites[i].url); + str = str.replace(/%date%/g, params['date'] || params['dateSparkline']); // For period=range, dateSparkline only is set + str = str.replace(/%period%/g, params['period']); + + $('#tb').append('' + str + ''); + } + + $(".table_row").show(); + $('.pageviews').hide(); + $('.revenue').hide(); + $('.visits').hide(); + $('#main_indicator').hide(); + $('.' + params['evolutionBy']).show(); + $("#main_indicator").hide(); } -function getSparklineImg(id, column, params) -{ - if(column != 'revenue') { - column = 'nb_' + column; - } - var append = ''; - var token_auth = broadcast.getValueFromUrl('token_auth'); - if(token_auth.length) { - append = '&token_auth=' + token_auth; - } - return ''; +function getSparklineImg(id, column, params) { + if (column != 'revenue') { + column = 'nb_' + column; + } + var append = ''; + var token_auth = broadcast.getValueFromUrl('token_auth'); + if (token_auth.length) { + append = '&token_auth=' + token_auth; + } + return ''; } -function showPagination(allSites, params) -{ - if ((params['page'] * params['limit']) < allSites.length) - { - var html = '' + params['next'] + ' »'; - $("#next").html(html); - } - if(params['page'] > 1) - { - html = '« ' + params['prev'] + '' - $("#prev").html(html); - } - var start = (params['page'] - 1) * params['limit'] + 1; - var count = allSites.length; - var end = parseInt(start) + parseInt(params['limit']) - 1; - if(end > count) end = count; - html = '' + (start ) + ' - ' + end + ' of ' + count + ''; - $("#counter").html(html); +function showPagination(allSites, params) { + if ((params['page'] * params['limit']) < allSites.length) { + var html = '' + params['next'] + ' »'; + $("#next").html(html); + } + if (params['page'] > 1) { + html = '« ' + params['prev'] + '' + $("#prev").html(html); + } + var start = (params['page'] - 1) * params['limit'] + 1; + var count = allSites.length; + var end = parseInt(start) + parseInt(params['limit']) - 1; + if (end > count) end = count; + html = '' + (start ) + ' - ' + end + ' of ' + count + ''; + $("#counter").html(html); } -function changePage(allSites, params, kind) -{ - if(kind == 'next') - { - params['page']++; - } - else - { - params['page']--; - } - prepareRows(allSites, params); - return params; +function changePage(allSites, params, kind) { + if (kind == 'next') { + params['page']++; + } + else { + params['page']--; + } + prepareRows(allSites, params); + return params; } -function getImageForSummary(value) -{ - if(value > 0) - { - return ' ' + value + ' %'; - } - else if(value == 0) - { - return ' ' + value + '%'; - } - else - { - return ' ' + value +' %'; - } +function getImageForSummary(value) { + if (value > 0) { + return ' ' + value + ' %'; + } + else if (value == 0) { + return ' ' + value + '%'; + } + else { + return ' ' + value + ' %'; + } } diff --git a/plugins/MultiSites/templates/index.tpl b/plugins/MultiSites/templates/index.tpl index 8bb928fabb..f130594d6e 100644 --- a/plugins/MultiSites/templates/index.tpl +++ b/plugins/MultiSites/templates/index.tpl @@ -1,114 +1,121 @@ {assign var=showSitesSelection value=false} {if !$isWidgetized} -{include file="CoreHome/templates/header.tpl"} + {include file="CoreHome/templates/header.tpl"} {/if}
-
-{include file="MultiSites/templates/row.tpl" assign="row"} - +
+ {include file="MultiSites/templates/row.tpl" assign="row"} + -{postEvent name="template_headerMultiSites"} + {postEvent name="template_headerMultiSites"} -{if !$isWidgetized} -
- {include file="CoreHome/templates/period_select.tpl"} - {include file="CoreHome/templates/header_message.tpl"} -
-{/if} + {if !$isWidgetized} +
+ {include file="CoreHome/templates/period_select.tpl"} + {include file="CoreHome/templates/header_message.tpl"} +
+ {/if} -
+
-

{'General_AllWebsitesDashboard'|translate} - {capture assign=nVisits}{'General_NVisits'|translate:$totalVisits}{/capture} - {capture assign=nVisitsLast}{'General_NVisits'|translate:$pastTotalVisits}{/capture} - +

{'General_AllWebsitesDashboard'|translate} + {capture assign=nVisits}{'General_NVisits'|translate:$totalVisits}{/capture} + {capture assign=nVisitsLast}{'General_NVisits'|translate:$pastTotalVisits}{/capture} + {'General_TotalVisitsPageviewsRevenue'|translate:"$totalVisits":"$totalPageviews":"$totalRevenue"} -

+

- - - - - - - {if $displayRevenueColumn} - - {/if} - - - +
- {'General_Website'|translate} - - - {'General_ColumnNbVisits'|translate} - - - {'General_ColumnPageviews'|translate} - - - {'Goals_ColumnRevenue'|translate} - - - - {'MultiSites_Evolution'|translate} - -
+ + + + + + {if $displayRevenueColumn} + + {/if} + + + - - + + - - {if $isSuperUser} - - - - {/if} - - + {if $isSuperUser} + + + + {/if} + + - - -
+ {'General_Website'|translate} + + + {'General_ColumnNbVisits'|translate} + + + {'General_ColumnPageviews'|translate} + + + {'Goals_ColumnRevenue'|translate} + + + + {'MultiSites_Evolution'|translate} + +
- {'SitesManager_AddSite'|translate} -
- +
+ {'SitesManager_AddSite'|translate} +
+ - -
-
- -
+ {if $autoRefreshTodayReport} + piwikHelper.refreshAfter({$autoRefreshTodayReport} * 1000 + ) + ; + {/if} + +
{include file="CoreHome/templates/footer.tpl"} diff --git a/plugins/MultiSites/templates/row.tpl b/plugins/MultiSites/templates/row.tpl index f651af317e..b49457a5f7 100644 --- a/plugins/MultiSites/templates/row.tpl +++ b/plugins/MultiSites/templates/row.tpl @@ -1,8 +1,8 @@ - + %name% - + @@ -12,22 +12,23 @@ %pageviews% {if $displayRevenueColumn} - - %revenue% - + + %revenue% + {/if} {if $period!='range'} - - - - {if $displayRevenueColumn} - - {/if} -{/if} -{if $show_sparklines} - - - + + + + {if $displayRevenueColumn} + + {/if} + {/if} + {if $show_sparklines} + + + {/if} diff --git a/plugins/MultiSites/templates/styles.css b/plugins/MultiSites/templates/styles.css index e217f0b468..796a2b091a 100644 --- a/plugins/MultiSites/templates/styles.css +++ b/plugins/MultiSites/templates/styles.css @@ -1,67 +1,77 @@ -#multisites { - border:0; - padding:0 15px; - font-size:14px; +#multisites { + border: 0; + padding: 0 15px; + font-size: 14px; } -#multisites .top_controls_inner { - height:10px; +#multisites .top_controls_inner { + height: 10px; } + .smallTitle { - font-size:15px; + font-size: 15px; } + .indicator { - background: url(../images/loading-blue.gif) no-repeat center; - height: 20px; - width: 60px; - margin: auto; - border: 0 !important; + background: url(../images/loading-blue.gif) no-repeat center; + height: 20px; + width: 60px; + margin: auto; + border: 0 !important; } + .clean { border: 0 !important; } + #multisites th { - cursor:pointer; + cursor: pointer; } + #multisites td, #multisites tr, #multisites .sparkline { text-align: center; vertical-align: middle; padding: 1px; margin: 0; } + #multisites td.multisites-label { - padding-left: 15px ; - text-align:left; + padding-left: 15px; + text-align: left; width: 250px; } + #multisites td.multisites-label a:hover { text-decoration: underline; } -#multisites td.multisites-column, #multisites th.multisites-column { - width:70px; - white-space:nowrap; + +#multisites td.multisites-column, #multisites th.multisites-column { + width: 70px; + white-space: nowrap; } -#multisites td.multisites-column-evolution, #multisites th.multisites-column-evolution { - width:70px; + +#multisites td.multisites-column-evolution, #multisites th.multisites-column-evolution { + width: 70px; } -.multisites_desc -{ - width: 16px; - height: 13px; - display: inline-block; - background-image: url(../../../themes/default/images/sortdesc.png); + +.multisites_desc { + width: 16px; + height: 13px; + display: inline-block; + background-image: url(../../../themes/default/images/sortdesc.png); } -.multisites_asc -{ - width: 16px; - height: 13px; - display: inline-block; - background-image: url(../../../themes/default/images/sortasc.png); + +.multisites_asc { + width: 16px; + height: 13px; + display: inline-block; + background-image: url(../../../themes/default/images/sortasc.png); } #mt thead { - line-height:2.5em; + line-height: 2.5em; } + #mt thead :first-child { -moz-border-radius-topleft: 7px; -webkit-border-top-left-radius: 7px; diff --git a/plugins/Overlay/API.php b/plugins/Overlay/API.php index 391b54082f..413406fe43 100644 --- a/plugins/Overlay/API.php +++ b/plugins/Overlay/API.php @@ -11,118 +11,111 @@ class Piwik_Overlay_API { - - private static $instance = null; - - /** - * Get Singleton instance - * @return Piwik_Overlay_API - */ - public static function getInstance() - { - if (self::$instance == null) - { - self::$instance = new self; - } - return self::$instance; - } - - /** - * Get translation strings - */ - public function getTranslations($idSite) - { - $this->authenticate($idSite); - - $translations = array( - 'oneClick' => 'Overlay_OneClick', - 'clicks' => 'Overlay_Clicks', - 'clicksFromXLinks' => 'Overlay_ClicksFromXLinks', - 'link' => 'Overlay_Link' - ); - - return array_map('Piwik_Translate', $translations); - } - - /** - * Get excluded query parameters for a site. - * This information is used for client side url normalization. - */ - public function getExcludedQueryParameters($idSite) - { - $this->authenticate($idSite); - - $sitesManager = Piwik_SitesManager_API::getInstance(); - $site = $sitesManager->getSiteFromId($idSite); - - try { - return Piwik_SitesManager::getTrackerExcludedQueryParameters($site); - } catch(Exception $e) { - // an exception is thrown when the user has no view access. - // do not throw the exception to the outside. - return array(); - } - } - - /** - * Get following pages of a url. - * This is done on the logs - not the archives! - * - * Note: if you use this method via the regular API, the number of results will be limited. - * Make sure, you set filter_limit=-1 in the request. - */ - public function getFollowingPages($url, $idSite, $period, $date, $segment = false) - { - $this->authenticate($idSite); - - $url = Piwik_Tracker_Action::excludeQueryParametersFromUrl($url, $idSite); - // we don't unsanitize $url here. it will be done in the Transitions plugin. - - $resultDataTable = new Piwik_DataTable; - - try - { - $limitBeforeGrouping = Piwik_Config::getInstance()->General['overlay_following_pages_limit']; - $transitionsReport = Piwik_Transitions_API::getInstance()->getTransitionsForAction( - $url, $type = 'url', $idSite, $period, $date, $segment, $limitBeforeGrouping, - $part = 'followingActions', $returnNormalizedUrls = true); - } - catch(Exception $e) - { - return $resultDataTable; - } - - $reports = array('followingPages', 'outlinks', 'downloads'); - foreach ($reports as $reportName) - { - if (!isset($transitionsReport[$reportName])) - { - continue; - } - foreach ($transitionsReport[$reportName]->getRows() as $row) - { - // don't touch the row at all for performance reasons - $resultDataTable->addRow($row); - } - } - - return $resultDataTable; - } - - /** Do cookie authentication. This way, the token can remain secret. */ - private function authenticate($idSite) - { - $notification = null; - Piwik_PostEvent('FrontController.initAuthenticationObject', $notification, $allowCookieAuthentication = true); - - $auth = Zend_Registry::get('auth'); - $success = Zend_Registry::get('access')->reloadAccess($auth); - - if (!$success) { - throw new Exception('Authentication failed'); - } - - Piwik::checkUserHasViewAccess($idSite); - } + + private static $instance = null; + + /** + * Get Singleton instance + * @return Piwik_Overlay_API + */ + public static function getInstance() + { + if (self::$instance == null) { + self::$instance = new self; + } + return self::$instance; + } + + /** + * Get translation strings + */ + public function getTranslations($idSite) + { + $this->authenticate($idSite); + + $translations = array( + 'oneClick' => 'Overlay_OneClick', + 'clicks' => 'Overlay_Clicks', + 'clicksFromXLinks' => 'Overlay_ClicksFromXLinks', + 'link' => 'Overlay_Link' + ); + + return array_map('Piwik_Translate', $translations); + } + + /** + * Get excluded query parameters for a site. + * This information is used for client side url normalization. + */ + public function getExcludedQueryParameters($idSite) + { + $this->authenticate($idSite); + + $sitesManager = Piwik_SitesManager_API::getInstance(); + $site = $sitesManager->getSiteFromId($idSite); + + try { + return Piwik_SitesManager::getTrackerExcludedQueryParameters($site); + } catch (Exception $e) { + // an exception is thrown when the user has no view access. + // do not throw the exception to the outside. + return array(); + } + } + + /** + * Get following pages of a url. + * This is done on the logs - not the archives! + * + * Note: if you use this method via the regular API, the number of results will be limited. + * Make sure, you set filter_limit=-1 in the request. + */ + public function getFollowingPages($url, $idSite, $period, $date, $segment = false) + { + $this->authenticate($idSite); + + $url = Piwik_Tracker_Action::excludeQueryParametersFromUrl($url, $idSite); + // we don't unsanitize $url here. it will be done in the Transitions plugin. + + $resultDataTable = new Piwik_DataTable; + + try { + $limitBeforeGrouping = Piwik_Config::getInstance()->General['overlay_following_pages_limit']; + $transitionsReport = Piwik_Transitions_API::getInstance()->getTransitionsForAction( + $url, $type = 'url', $idSite, $period, $date, $segment, $limitBeforeGrouping, + $part = 'followingActions', $returnNormalizedUrls = true); + } catch (Exception $e) { + return $resultDataTable; + } + + $reports = array('followingPages', 'outlinks', 'downloads'); + foreach ($reports as $reportName) { + if (!isset($transitionsReport[$reportName])) { + continue; + } + foreach ($transitionsReport[$reportName]->getRows() as $row) { + // don't touch the row at all for performance reasons + $resultDataTable->addRow($row); + } + } + + return $resultDataTable; + } + + /** Do cookie authentication. This way, the token can remain secret. */ + private function authenticate($idSite) + { + $notification = null; + Piwik_PostEvent('FrontController.initAuthenticationObject', $notification, $allowCookieAuthentication = true); + + $auth = Zend_Registry::get('auth'); + $success = Zend_Registry::get('access')->reloadAccess($auth); + + if (!$success) { + throw new Exception('Authentication failed'); + } + + Piwik::checkUserHasViewAccess($idSite); + } } diff --git a/plugins/Overlay/Controller.php b/plugins/Overlay/Controller.php index 6b938b9443..636683fb36 100644 --- a/plugins/Overlay/Controller.php +++ b/plugins/Overlay/Controller.php @@ -11,129 +11,123 @@ class Piwik_Overlay_Controller extends Piwik_Controller { - - /** The index of the plugin */ - public function index() - { - Piwik::checkUserHasViewAccess($this->idSite); - - $template = 'index'; - if (Piwik_Config::getInstance()->General['overlay_disable_framed_mode']) { - $template = 'index_noframe'; - } - - $view = Piwik_View::factory($template); - - $this->setGeneralVariablesView($view); - $view->showTopMenu = false; - $view->showSitesSelection = false; - $view->addToHead = '' - . ''; - - $view->idSite = $this->idSite; - $view->date = Piwik_Common::getRequestVar('date', 'today'); - $view->period = Piwik_Common::getRequestVar('period', 'day'); - - $view->ssl = Piwik::isHttps(); - - echo $view->render(); - } - - /** Render the area left of the iframe */ - public function renderSidebar() - { - $idSite = Piwik_Common::getRequestVar('idSite'); - $period = Piwik_Common::getRequestVar('period'); - $date = Piwik_Common::getRequestVar('date'); - $currentUrl = Piwik_Common::getRequestVar('currentUrl'); - $currentUrl = Piwik_Common::unsanitizeInputValue($currentUrl); - - $normalizedCurrentUrl = Piwik_Tracker_Action::excludeQueryParametersFromUrl($currentUrl, $idSite); - $normalizedCurrentUrl = Piwik_Common::unsanitizeInputValue($normalizedCurrentUrl); - - // load the appropriate row of the page urls report using the label filter - Piwik_Actions_ArchivingHelper::reloadConfig(); - $path = Piwik_Actions_ArchivingHelper::getActionExplodedNames($normalizedCurrentUrl, Piwik_Tracker_Action::TYPE_ACTION_URL); - $path = array_map('urlencode', $path); - $label = implode('>', $path); - $request = new Piwik_API_Request( - 'method=Actions.getPageUrls' - .'&idSite='.urlencode($idSite) - .'&date='.urlencode($date) - .'&period='.urlencode($period) - .'&label='.urlencode($label) - .'&format=original' - ); - $dataTable = $request->process(); - - $data = array(); - if ($dataTable->getRowsCount() > 0) - { - $row = $dataTable->getFirstRow(); - - $translations = Piwik_API_API::getDefaultMetricTranslations(); - $showMetrics = array('nb_hits', 'nb_visits', 'nb_uniq_visitors', - 'bounce_rate', 'exit_rate', 'avg_time_on_page'); - - - foreach ($showMetrics as $metric) - { - $value = $row->getColumn($metric); - if ($value === false) - { - // skip unique visitors for period != day - continue; - } - if ($metric == 'avg_time_on_page') - { - $value = Piwik::getPrettyTimeFromSeconds($value); - } - $data[] = array( - 'name' => $translations[$metric], - 'value' => $value - ); - } - } - - // generate page url string - foreach ($path as &$part) - { - $part = preg_replace(';^/;', '', urldecode($part)); - } - $page = '/'.implode('/', $path); - $page = preg_replace(';/index$;', '/', $page); - if ($page == '/') - { - $page = '/index'; - } - - // render template - $view = Piwik_View::factory('sidebar'); - $view->data = $data; - $view->location = $page; - $view->normalizedUrl = $normalizedCurrentUrl; - $view->label = $label; - $view->idSite = $idSite; - $view->period = $period; - $view->date = $date; - echo $view->render(); - } - - /** - * Start an Overlay session: Redirect to the tracked website. The Piwik - * tracker will recognize this referrer and start the session. - */ - public function startOverlaySession() - { - $idSite = Piwik_Common::getRequestVar('idsite', 0, 'int'); - Piwik::checkUserHasViewAccess($idSite); - - $sitesManager = Piwik_SitesManager_API::getInstance(); - $site = $sitesManager->getSiteFromId($idSite); - $urls = $sitesManager->getSiteUrlsFromId($idSite); - - @header('Content-Type: text/html; charset=UTF-8'); - echo ' + + /** The index of the plugin */ + public function index() + { + Piwik::checkUserHasViewAccess($this->idSite); + + $template = 'index'; + if (Piwik_Config::getInstance()->General['overlay_disable_framed_mode']) { + $template = 'index_noframe'; + } + + $view = Piwik_View::factory($template); + + $this->setGeneralVariablesView($view); + $view->showTopMenu = false; + $view->showSitesSelection = false; + $view->addToHead = '' + . ''; + + $view->idSite = $this->idSite; + $view->date = Piwik_Common::getRequestVar('date', 'today'); + $view->period = Piwik_Common::getRequestVar('period', 'day'); + + $view->ssl = Piwik::isHttps(); + + echo $view->render(); + } + + /** Render the area left of the iframe */ + public function renderSidebar() + { + $idSite = Piwik_Common::getRequestVar('idSite'); + $period = Piwik_Common::getRequestVar('period'); + $date = Piwik_Common::getRequestVar('date'); + $currentUrl = Piwik_Common::getRequestVar('currentUrl'); + $currentUrl = Piwik_Common::unsanitizeInputValue($currentUrl); + + $normalizedCurrentUrl = Piwik_Tracker_Action::excludeQueryParametersFromUrl($currentUrl, $idSite); + $normalizedCurrentUrl = Piwik_Common::unsanitizeInputValue($normalizedCurrentUrl); + + // load the appropriate row of the page urls report using the label filter + Piwik_Actions_ArchivingHelper::reloadConfig(); + $path = Piwik_Actions_ArchivingHelper::getActionExplodedNames($normalizedCurrentUrl, Piwik_Tracker_Action::TYPE_ACTION_URL); + $path = array_map('urlencode', $path); + $label = implode('>', $path); + $request = new Piwik_API_Request( + 'method=Actions.getPageUrls' + . '&idSite=' . urlencode($idSite) + . '&date=' . urlencode($date) + . '&period=' . urlencode($period) + . '&label=' . urlencode($label) + . '&format=original' + ); + $dataTable = $request->process(); + + $data = array(); + if ($dataTable->getRowsCount() > 0) { + $row = $dataTable->getFirstRow(); + + $translations = Piwik_API_API::getDefaultMetricTranslations(); + $showMetrics = array('nb_hits', 'nb_visits', 'nb_uniq_visitors', + 'bounce_rate', 'exit_rate', 'avg_time_on_page'); + + + foreach ($showMetrics as $metric) { + $value = $row->getColumn($metric); + if ($value === false) { + // skip unique visitors for period != day + continue; + } + if ($metric == 'avg_time_on_page') { + $value = Piwik::getPrettyTimeFromSeconds($value); + } + $data[] = array( + 'name' => $translations[$metric], + 'value' => $value + ); + } + } + + // generate page url string + foreach ($path as &$part) { + $part = preg_replace(';^/;', '', urldecode($part)); + } + $page = '/' . implode('/', $path); + $page = preg_replace(';/index$;', '/', $page); + if ($page == '/') { + $page = '/index'; + } + + // render template + $view = Piwik_View::factory('sidebar'); + $view->data = $data; + $view->location = $page; + $view->normalizedUrl = $normalizedCurrentUrl; + $view->label = $label; + $view->idSite = $idSite; + $view->period = $period; + $view->date = $date; + echo $view->render(); + } + + /** + * Start an Overlay session: Redirect to the tracked website. The Piwik + * tracker will recognize this referrer and start the session. + */ + public function startOverlaySession() + { + $idSite = Piwik_Common::getRequestVar('idsite', 0, 'int'); + Piwik::checkUserHasViewAccess($idSite); + + $sitesManager = Piwik_SitesManager_API::getInstance(); + $site = $sitesManager->getSiteFromId($idSite); + $urls = $sitesManager->getSiteUrlsFromId($idSite); + + @header('Content-Type: text/html; charset=UTF-8'); + echo ' '; - } - - /** - * This method is called when the JS from startOverlaySession() detects that the target domain - * is not configured for the current site. - */ - public function showErrorWrongDomain() - { - $idSite = Piwik_Common::getRequestVar('idSite', 0, 'int'); - Piwik::checkUserHasViewAccess($idSite); - - $url = Piwik_Common::getRequestVar('url', ''); - $url = Piwik_Common::unsanitizeInputValue($url); - - $message = Piwik_Translate('Overlay_RedirectUrlError', array($url, "\n")); - $message = nl2br(htmlentities($message)); - - $view = Piwik_View::factory('error_wrong_domain'); - $view->message = $message; - - if (Piwik::isUserHasAdminAccess($idSite)) { - // TODO use $idSite to link to the correct row. This is tricky because the #rowX ids don't match - // the site ids when sites have been deleted. - $url = 'index.php?module=SitesManager&action=index'; - $troubleshoot = htmlentities(Piwik_Translate('Overlay_RedirectUrlErrorAdmin')); - $troubleshoot = sprintf($troubleshoot, '', ''); - $view->troubleshoot = $troubleshoot; - } else { - $view->troubleshoot = htmlentities(Piwik_Translate('Overlay_RedirectUrlErrorUser')); - } - - echo $view->render(); - } - - /** - * This method is used to pass information from the iframe back to Piwik. - * Due to the same origin policy, we can't do that directly, so we inject - * an additional iframe in the Overlay session that calls this controller - * method. - * The rendered iframe is from the same origin as the Piwik window so we - * can bypass the same origin policy and call the parent. - */ - public function notifyParentIframe() - { - $view = Piwik_View::factory('notify_parent_iframe'); - echo $view->render(); - } - + } + + /** + * This method is called when the JS from startOverlaySession() detects that the target domain + * is not configured for the current site. + */ + public function showErrorWrongDomain() + { + $idSite = Piwik_Common::getRequestVar('idSite', 0, 'int'); + Piwik::checkUserHasViewAccess($idSite); + + $url = Piwik_Common::getRequestVar('url', ''); + $url = Piwik_Common::unsanitizeInputValue($url); + + $message = Piwik_Translate('Overlay_RedirectUrlError', array($url, "\n")); + $message = nl2br(htmlentities($message)); + + $view = Piwik_View::factory('error_wrong_domain'); + $view->message = $message; + + if (Piwik::isUserHasAdminAccess($idSite)) { + // TODO use $idSite to link to the correct row. This is tricky because the #rowX ids don't match + // the site ids when sites have been deleted. + $url = 'index.php?module=SitesManager&action=index'; + $troubleshoot = htmlentities(Piwik_Translate('Overlay_RedirectUrlErrorAdmin')); + $troubleshoot = sprintf($troubleshoot, '', ''); + $view->troubleshoot = $troubleshoot; + } else { + $view->troubleshoot = htmlentities(Piwik_Translate('Overlay_RedirectUrlErrorUser')); + } + + echo $view->render(); + } + + /** + * This method is used to pass information from the iframe back to Piwik. + * Due to the same origin policy, we can't do that directly, so we inject + * an additional iframe in the Overlay session that calls this controller + * method. + * The rendered iframe is from the same origin as the Piwik window so we + * can bypass the same origin policy and call the parent. + */ + public function notifyParentIframe() + { + $view = Piwik_View::factory('notify_parent_iframe'); + echo $view->render(); + } + } diff --git a/plugins/Overlay/Overlay.php b/plugins/Overlay/Overlay.php index 7e800884f3..d7b950491e 100644 --- a/plugins/Overlay/Overlay.php +++ b/plugins/Overlay/Overlay.php @@ -11,28 +11,28 @@ class Piwik_Overlay extends Piwik_Plugin { - public function getInformation() - { - return array( - 'description' => Piwik_Translate('Overlay_PluginDescription'), - 'author' => 'Piwik', - 'author_homepage' => 'http://piwik.org/', - 'version' => Piwik_Version::VERSION, - ); - } - - function getListHooksRegistered() - { - return array( - 'AssetManager.getJsFiles' => 'getJsFiles' - ); - } - - public function getJsFiles($notification) - { - $jsFiles = &$notification->getNotificationObject(); - $jsFiles[] = 'plugins/Overlay/templates/rowaction.js'; - $jsFiles[] = 'plugins/Overlay/templates/helper.js'; - } + public function getInformation() + { + return array( + 'description' => Piwik_Translate('Overlay_PluginDescription'), + 'author' => 'Piwik', + 'author_homepage' => 'http://piwik.org/', + 'version' => Piwik_Version::VERSION, + ); + } + + function getListHooksRegistered() + { + return array( + 'AssetManager.getJsFiles' => 'getJsFiles' + ); + } + + public function getJsFiles($notification) + { + $jsFiles = & $notification->getNotificationObject(); + $jsFiles[] = 'plugins/Overlay/templates/rowaction.js'; + $jsFiles[] = 'plugins/Overlay/templates/helper.js'; + } } diff --git a/plugins/Overlay/client/client.css b/plugins/Overlay/client/client.css index 05e2e87169..ec00ea71da 100644 --- a/plugins/Overlay/client/client.css +++ b/plugins/Overlay/client/client.css @@ -1,4 +1,3 @@ - /** * Reset styles */ @@ -11,84 +10,80 @@ .PIS_LinkHighlightBoxRight, .PIS_LinkHighlightBoxLeft, .PIS_LinkHighlightBoxText { - margin: 0; - padding: 0; - border: 0; - outline: 0; - font-weight: normal; - font-style: normal; - font-size: 11px; - font-family: Arial, Helvetica, sans-serif; - vertical-align: baseline; - line-height: 1.4em; - text-indent: 0; - text-decoration: none; - text-transform: none; - cursor: default; - text-align: left; - float: none; - color: #333; + margin: 0; + padding: 0; + border: 0; + outline: 0; + font-weight: normal; + font-style: normal; + font-size: 11px; + font-family: Arial, Helvetica, sans-serif; + vertical-align: baseline; + line-height: 1.4em; + text-indent: 0; + text-decoration: none; + text-transform: none; + cursor: default; + text-align: left; + float: none; + color: #333; } - - /** * Link Tags */ .PIS_LinkTag { - position: absolute; - z-index: 9999; - width: 36px; - height: 21px; - text-align: left; - background: url(./linktags_lessshadow.png) no-repeat 0 -21px; - overflow: hidden; + position: absolute; + z-index: 9999; + width: 36px; + height: 21px; + text-align: left; + background: url(./linktags_lessshadow.png) no-repeat 0 -21px; + overflow: hidden; } .PIS_LinkTag span { - position: absolute; - width: 31px; - height: 14px; - font-size: 10px; - text-align: center; - font-weight: bold; - line-height: 14px; - margin-left: 1px; + position: absolute; + width: 31px; + height: 14px; + font-size: 10px; + text-align: center; + font-weight: bold; + line-height: 14px; + margin-left: 1px; } .PIS_LinkTag.PIS_Highlighted { - z-index: 10002; + z-index: 10002; } .PIS_LinkTag.PIS_Highlighted span { - color: #E87500; + color: #E87500; } .PIS_LinkTag.PIS_Right { - background-position: -36px -21px; + background-position: -36px -21px; } .PIS_LinkTag.PIS_Right span, .PIS_LinkTag.PIS_BottomRight span { - margin-left: 5px; + margin-left: 5px; } .PIS_LinkTag.PIS_Bottom { - background-position: 0 0; + background-position: 0 0; } .PIS_LinkTag.PIS_Bottom span, .PIS_LinkTag.PIS_BottomRight span { - margin-top: 4px; + margin-top: 4px; } .PIS_LinkTag.PIS_BottomRight { - background-position: -36px 0; + background-position: -36px 0; } - - /** * Link Highlights */ @@ -97,60 +92,58 @@ .PIS_LinkHighlightBoxRight, .PIS_LinkHighlightBoxText, .PIS_LinkHighlightBoxLeft { - position: absolute; - z-index: 10001; - overflow: hidden; - width: 2px; - height: 2px; - background: #E87500; + position: absolute; + z-index: 10001; + overflow: hidden; + width: 2px; + height: 2px; + background: #E87500; } .PIS_LinkHighlightBoxText { - line-height: 20px; - height: 20px; - font-size: 11px; - color: white; - width: auto; + line-height: 20px; + height: 20px; + font-size: 11px; + color: white; + width: auto; } - - /** * Status bar */ #PIS_StatusBar { - padding: 10px 0; - position: fixed; - z-index: 10020; - bottom: 0; - right: 0; - border-top: 1px solid #ccc; - border-left: 1px solid #ccc; - background: #fbfbfb; - -webkit-border-top-left-radius: 6px; - -webkit-border-top-right-radius: 0px; - -webkit-border-bottom-right-radius: 0px; - -webkit-border-bottom-left-radius: 0px; - -moz-border-radius-topleft: 6px; - -moz-border-radius-topright: 0px; - -moz-border-radius-bottomright: 0px; - -moz-border-radius-bottomleft: 0px; - border-top-left-radius: 6px; - border-top-right-radius: 0px; - border-bottom-right-radius: 0px; - border-bottom-left-radius: 0px; + padding: 10px 0; + position: fixed; + z-index: 10020; + bottom: 0; + right: 0; + border-top: 1px solid #ccc; + border-left: 1px solid #ccc; + background: #fbfbfb; + -webkit-border-top-left-radius: 6px; + -webkit-border-top-right-radius: 0px; + -webkit-border-bottom-right-radius: 0px; + -webkit-border-bottom-left-radius: 0px; + -moz-border-radius-topleft: 6px; + -moz-border-radius-topright: 0px; + -moz-border-radius-bottomright: 0px; + -moz-border-radius-bottomleft: 0px; + border-top-left-radius: 6px; + border-top-right-radius: 0px; + border-bottom-right-radius: 0px; + border-bottom-left-radius: 0px; } #PIS_StatusBar .PIS_Item { - text-align: right; - padding: 3px 5px 0 0; - margin: 0 15px 0 20px; - - font-weight: bold; + text-align: right; + padding: 3px 5px 0 0; + margin: 0 15px 0 20px; + + font-weight: bold; } #PIS_StatusBar .PIS_Loading { - background: url(./loading.gif) no-repeat right center; - padding-right: 30px; + background: url(./loading.gif) no-repeat right center; + padding-right: 30px; } \ No newline at end of file diff --git a/plugins/Overlay/client/client.js b/plugins/Overlay/client/client.js index 93d790f059..f791f8beb5 100644 --- a/plugins/Overlay/client/client.js +++ b/plugins/Overlay/client/client.js @@ -1,261 +1,261 @@ +var Piwik_Overlay_Client = (function () { + + /** jQuery */ + var $; + + /** Url of the Piwik root */ + var piwikRoot; + + /** Piwik idsite */ + var idSite; + + /** The current period and date */ + var period, date; + + /** Reference to the status bar DOM element */ + var statusBar; + + /** Load the client CSS */ + function loadCss() { + var css = c('link').attr({ + rel: 'stylesheet', + type: 'text/css', + href: piwikRoot + 'plugins/Overlay/client/client.css' + }); + $('head').append(css); + } + + /** + * This method loads jQuery, if it is not there yet. + * The callback is triggered after jQuery is loaded. + */ + function loadJQuery(callback) { + if (typeof jQuery != 'undefined') { + $ = jQuery; + callback(); + } + else { + Piwik_Overlay_Client.loadScript('libs/jquery/jquery.js', function () { + $ = jQuery; + jQuery.noConflict(); + callback(); + }); + } + } + + /** + * Notify Piwik of the current iframe location. + * This way, we can display additional metrics on the side of the iframe. + */ + function notifyPiwikOfLocation() { + // check whether the session has been opened in a new tab (instead of an iframe) + if (window != window.top) { + var iframe = c('iframe', false, { + src: piwikRoot + 'index.php?module=Overlay&action=notifyParentIframe#' + window.location.href + }).css({width: 0, height: 0, border: 0}); + + // in some cases, calling append right away doesn't work in IE8 + $(document).ready(function () { + $('body').append(iframe); + }); + } + } + + /** Create a jqueryfied DOM element */ + function c(tagName, className, attributes) { + var el = $(document.createElement(tagName)); + + if (className) { + if (className.substring(0, 1) == '#') { + var id = className.substring(1, className.length); + id = 'PIS_' + id; + el.attr('id', id); + } + else { + className = 'PIS_' + className; + el.addClass(className); + } + } + + if (attributes) { + el.attr(attributes); + } + + return el; + } + + /** Special treatment for some internet explorers */ + var ieStatusBarEventsBound = false; + + function handleIEStatusBar() { + if (navigator.appVersion.indexOf("MSIE 7.") == -1 + && navigator.appVersion.indexOf("MSIE 8.") == -1) { + // this is not IE8 or lower + return; + } + + // IE7/8 can't handle position:fixed so we need to do it by hand + statusBar.css({ + position: 'absolute', + right: 'auto', + bottom: 'auto', + left: 0, + top: 0 + }); + + var position = function () { + var scrollY = document.body.parentElement.scrollTop; + var scrollX = document.body.parentElement.scrollLeft; + statusBar.css({ + top: (scrollY + $(window).height() - statusBar.outerHeight()) + 'px', + left: (scrollX + $(window).width() - statusBar.outerWidth()) + 'px' + }); + }; + + position(); + + statusBar.css({width: 'auto'}); + if (statusBar.width() < 350) { + statusBar.width(350); + } else { + statusBar.width(statusBar.width()); + } + + if (!ieStatusBarEventsBound) { + ieStatusBarEventsBound = true; + $(window).resize(position); + $(window).scroll(position); + } + } + + return { + + /** Initialize in-site analytics */ + initialize: function (pPiwikRoot, pIdSite, pPeriod, pDate) { + piwikRoot = pPiwikRoot; + idSite = pIdSite; + period = pPeriod; + date = pDate; + + var load = this.loadScript; + var loading = this.loadingNotification; + + loadJQuery(function () { + notifyPiwikOfLocation(); + loadCss(); + + // translations + load('plugins/Overlay/client/translations.js', function () { + Piwik_Overlay_Translations.initialize(function () { + // following pages + var finishPages = loading('Loading following pages'); + load('plugins/Overlay/client/followingpages.js', function () { + Piwik_Overlay_FollowingPages.initialize(finishPages); + }); + + }); + }); + }); + }, + + /** Create a jqueryfied DOM element */ + createElement: function (tagName, className, attributes) { + return c(tagName, className, attributes); + }, + + /** Load a script and wait for it to be loaded */ + loadScript: function (relativePath, callback) { + var loaded = false; + var onLoad = function () { + if (!loaded) { + loaded = true; + callback(); + } + }; + + var head = document.getElementsByTagName('head')[0]; + var script = document.createElement('script'); + script.type = 'text/javascript'; + + script.onreadystatechange = function () { + if (this.readyState == 'loaded' || this.readyState == 'complete') { + onLoad(); + } + }; + script.onload = onLoad; + + script.src = piwikRoot + relativePath + '?v=1'; + head.appendChild(script); + }, + + /** Piwik Overlay API Request */ + api: function (method, callback, additionalParams) { + var url = piwikRoot + 'index.php?module=API&method=Overlay.' + method + + '&idSite=' + idSite + '&period=' + period + '&date=' + date + '&format=JSON&filter_limit=-1'; + + if (additionalParams) { + url += '&' + additionalParams; + } + + $.getJSON(url + "&jsoncallback=?", function (data) { + if (typeof data.result != 'undefined' && data.result == 'error') { + alert('Error: ' + data.message); + } + else { + callback(data); + } + }); + }, + + /** + * Initialize a notification + * To hide the notification use the returned callback + */ + notification: function (message, addClass) { + if (!statusBar) { + statusBar = c('div', '#StatusBar').css('opacity', .8); + $('body').prepend(statusBar); + } + + var item = c('div', 'Item').html(message); + + if (addClass) { + item.addClass('PIS_' + addClass); + } + + statusBar.show().append(item); + + handleIEStatusBar(); + window.setTimeout(handleIEStatusBar, 100); + + return function () { + item.remove(); + if (statusBar.children().size() == 0) { + statusBar.hide(); + } else { + handleIEStatusBar(); + } + }; + }, + + /** Hide all notifications with a certain class */ + hideNotifications: function (className) { + statusBar.find('.PIS_' + className).remove(); + if (statusBar.children().size() == 0) { + statusBar.hide(); + } else { + handleIEStatusBar(); + } + }, + + /** + * Initialize a loading notification + * To hide the notification use the returned callback + */ + loadingNotification: function (message) { + return Piwik_Overlay_Client.notification(message, 'Loading'); + } + + }; -var Piwik_Overlay_Client = (function() { - - /** jQuery */ - var $; - - /** Url of the Piwik root */ - var piwikRoot; - - /** Piwik idsite */ - var idSite; - - /** The current period and date */ - var period, date; - - /** Reference to the status bar DOM element */ - var statusBar; - - /** Load the client CSS */ - function loadCss() { - var css = c('link').attr({ - rel: 'stylesheet', - type: 'text/css', - href: piwikRoot + 'plugins/Overlay/client/client.css' - }); - $('head').append(css); - } - - /** - * This method loads jQuery, if it is not there yet. - * The callback is triggered after jQuery is loaded. - */ - function loadJQuery(callback) { - if (typeof jQuery != 'undefined') { - $ = jQuery; - callback(); - } - else { - Piwik_Overlay_Client.loadScript('libs/jquery/jquery.js', function() { - $ = jQuery; - jQuery.noConflict(); - callback(); - }); - } - } - - /** - * Notify Piwik of the current iframe location. - * This way, we can display additional metrics on the side of the iframe. - */ - function notifyPiwikOfLocation() { - // check whether the session has been opened in a new tab (instead of an iframe) - if (window != window.top) { - var iframe = c('iframe', false, { - src: piwikRoot + 'index.php?module=Overlay&action=notifyParentIframe#' + window.location.href - }).css({width: 0, height: 0, border: 0}); - - // in some cases, calling append right away doesn't work in IE8 - $(document).ready(function() { - $('body').append(iframe); - }); - } - } - - /** Create a jqueryfied DOM element */ - function c(tagName, className, attributes) { - var el = $(document.createElement(tagName)); - - if (className) { - if (className.substring(0, 1) == '#') { - var id = className.substring(1, className.length); - id = 'PIS_' + id; - el.attr('id', id); - } - else { - className = 'PIS_' + className; - el.addClass(className); - } - } - - if (attributes) { - el.attr(attributes); - } - - return el; - } - - /** Special treatment for some internet explorers */ - var ieStatusBarEventsBound = false; - function handleIEStatusBar() { - if (navigator.appVersion.indexOf("MSIE 7.") == -1 - && navigator.appVersion.indexOf("MSIE 8.") == -1) { - // this is not IE8 or lower - return; - } - - // IE7/8 can't handle position:fixed so we need to do it by hand - statusBar.css({ - position: 'absolute', - right: 'auto', - bottom: 'auto', - left: 0, - top: 0 - }); - - var position = function() { - var scrollY = document.body.parentElement.scrollTop; - var scrollX = document.body.parentElement.scrollLeft; - statusBar.css({ - top: (scrollY + $(window).height() - statusBar.outerHeight()) + 'px', - left: (scrollX + $(window).width() - statusBar.outerWidth()) + 'px' - }); - }; - - position(); - - statusBar.css({width: 'auto'}); - if (statusBar.width() < 350) { - statusBar.width(350); - } else { - statusBar.width(statusBar.width()); - } - - if (!ieStatusBarEventsBound) { - ieStatusBarEventsBound = true; - $(window).resize(position); - $(window).scroll(position); - } - } - - return { - - /** Initialize in-site analytics */ - initialize: function(pPiwikRoot, pIdSite, pPeriod, pDate) { - piwikRoot = pPiwikRoot; - idSite = pIdSite; - period = pPeriod; - date = pDate; - - var load = this.loadScript; - var loading = this.loadingNotification; - - loadJQuery(function() { - notifyPiwikOfLocation(); - loadCss(); - - // translations - load('plugins/Overlay/client/translations.js', function() { - Piwik_Overlay_Translations.initialize(function() { - // following pages - var finishPages = loading('Loading following pages'); - load('plugins/Overlay/client/followingpages.js', function() { - Piwik_Overlay_FollowingPages.initialize(finishPages); - }); - - }); - }); - }); - }, - - /** Create a jqueryfied DOM element */ - createElement: function(tagName, className, attributes) { - return c(tagName, className, attributes); - }, - - /** Load a script and wait for it to be loaded */ - loadScript: function(relativePath, callback) { - var loaded = false; - var onLoad = function() { - if (!loaded) { - loaded = true; - callback(); - } - }; - - var head = document.getElementsByTagName('head')[0]; - var script = document.createElement('script'); - script.type = 'text/javascript'; - - script.onreadystatechange = function () { - if (this.readyState == 'loaded' || this.readyState == 'complete') { - onLoad(); - } - }; - script.onload = onLoad; - - script.src = piwikRoot+relativePath+'?v=1'; - head.appendChild(script); - }, - - /** Piwik Overlay API Request */ - api: function(method, callback, additionalParams) { - var url = piwikRoot+'index.php?module=API&method=Overlay.'+method - +'&idSite='+idSite+'&period='+period+'&date='+date+'&format=JSON&filter_limit=-1'; - - if (additionalParams) { - url += '&' + additionalParams; - } - - $.getJSON(url+"&jsoncallback=?", function(data) { - if (typeof data.result != 'undefined' && data.result == 'error') { - alert('Error: ' + data.message); - } - else { - callback(data); - } - }); - }, - - /** - * Initialize a notification - * To hide the notification use the returned callback - */ - notification: function(message, addClass) { - if (!statusBar) { - statusBar = c('div', '#StatusBar').css('opacity', .8); - $('body').prepend(statusBar); - } - - var item = c('div', 'Item').html(message); - - if (addClass) { - item.addClass('PIS_' + addClass); - } - - statusBar.show().append(item); - - handleIEStatusBar(); - window.setTimeout(handleIEStatusBar, 100); - - return function() { - item.remove(); - if (statusBar.children().size() == 0) { - statusBar.hide(); - } else { - handleIEStatusBar(); - } - }; - }, - - /** Hide all notifications with a certain class */ - hideNotifications: function(className) { - statusBar.find('.PIS_' + className).remove(); - if (statusBar.children().size() == 0) { - statusBar.hide(); - } else { - handleIEStatusBar(); - } - }, - - /** - * Initialize a loading notification - * To hide the notification use the returned callback - */ - loadingNotification: function(message) { - return Piwik_Overlay_Client.notification(message, 'Loading'); - } - - }; - })(); diff --git a/plugins/Overlay/client/followingpages.js b/plugins/Overlay/client/followingpages.js index edb9c40e1e..e20252463f 100644 --- a/plugins/Overlay/client/followingpages.js +++ b/plugins/Overlay/client/followingpages.js @@ -1,511 +1,511 @@ -var Piwik_Overlay_FollowingPages = (function() { - - /** jQuery */ - var $ = jQuery; - - /** Info about the following pages */ - var followingPages = []; - - /** List of excluded get parameters */ - var excludedParams = []; - - /** Index of the links on the page */ - var linksOnPage = {}; - - /** Reference to create element function */ - var c; - - /** Load the following pages */ - function load(callback) { - // normalize current location - var location = window.location.href; - location = Piwik_Overlay_UrlNormalizer.normalize(location); - location = (("https:" == document.location.protocol) ? 'https' : 'http') + '://' + location; - - var excludedParamsLoaded = false; - var followingPagesLoaded = false; - - // load excluded params - Piwik_Overlay_Client.api('getExcludedQueryParameters', function(data) { - for (var i = 0; i < data.length; i++) { - if (typeof data[i] == 'object') { - data[i] = data[i][0]; - } - } - excludedParams = data; - - excludedParamsLoaded = true; - if (followingPagesLoaded) { - callback(); - } - }); - - // load following pages - Piwik_Overlay_Client.api('getFollowingPages', function(data) { - followingPages = data; - processFollowingPages(); - - followingPagesLoaded = true; - if (excludedParamsLoaded) { - callback(); - } - }, 'url=' + encodeURIComponent(location)); - } - - /** Normalize the URLs of following pages and aggregate some stats */ - function processFollowingPages() { - var totalClicks = 0; - for (var i = 0; i < followingPages.length; i++) { - var page = followingPages[i]; - // though the following pages are returned without the prefix, downloads - // and outlinks still have it. - page.label = Piwik_Overlay_UrlNormalizer.removeUrlPrefix(page.label); - totalClicks += followingPages[i].referrals; - } - for (i = 0; i < followingPages.length; i++) { - followingPages[i].clickRate = followingPages[i].referrals / totalClicks * 100; - } - } - - /** - * Build an index of links on the page. - * This function is passed to $('a').each() - */ - var processLinkDelta = false; - - function processLink() { - var a = $(this); - a[0].piwikDiscovered = true; - - var href = a.attr('href'); - href = Piwik_Overlay_UrlNormalizer.normalize(href); - - if (href) { - if (typeof linksOnPage[href] == 'undefined') { - linksOnPage[href] = [a]; - } - else { - linksOnPage[href].push(a); - } - } - - if (href && processLinkDelta !== false) { - if (typeof processLinkDelta[href] == 'undefined') { - processLinkDelta[href] = [a]; - } - else { - processLinkDelta[href].push(a); - } - } - } - - var repositionTimeout = false; - var resizeTimeout = false; - - function build(callback) { - // build an index of all links on the page - $('a').each(processLink); - - // add tags to known following pages - createLinkTags(linksOnPage); - - // position the tags - positionLinkTags(); - - callback(); - - // check on a regular basis whether new links have appeared. - // we use a timeout instead of an interval to make sure one call is done before - // the next one is triggered - var repositionAfterTimeout; - repositionAfterTimeout = function() { - repositionTimeout = window.setTimeout(function() { - findNewLinks(); - positionLinkTags(repositionAfterTimeout); - }, 1800); - }; - repositionAfterTimeout(); - - // reposition link tags on window resize - $(window).resize(function() { - if (repositionTimeout) { - window.clearTimeout(repositionTimeout); - } - if (resizeTimeout) { - window.clearTimeout(resizeTimeout); - } - resizeTimeout = window.setTimeout(function() { - positionLinkTags(); - repositionAfterTimeout(); - }, 70); - }); - } - - /** Create a batch of link tags */ - function createLinkTags(links) { - var body = $('body'); - for (var i = 0; i < followingPages.length; i++) { - var url = followingPages[i].label; - if (typeof links[url] != 'undefined') { - for (var j = 0; j < links[url].length; j++) { - createLinkTag(links[url][j], url, followingPages[i], body); - } - } - } - } - - /** Create the link tag element */ - function createLinkTag(linkTag, linkUrl, data, body) { - if (typeof linkTag[0].piwikTagElement != 'undefined' && linkTag[0].piwikTagElement !== null) { - // this link tag already has a tag element. happens in rare cases. - return; - } - - linkTag[0].piwikTagElement = true; - - var rate = data.clickRate; - if (rate < 10) { - rate = Math.round(rate * 10) / 10; - } else { - rate = Math.round(rate); - } - - var span = c('span').html(rate + '%'); - var tagElement = c('div', 'LinkTag').append(span).hide(); - body.prepend(tagElement); - - linkTag.add(tagElement).hover(function() { - highlightLink(linkTag, linkUrl, data); - }, function() { - unHighlightLink(linkTag, linkUrl); - }); - - // attach the tag element to the link element. we can't use .data() because jquery - // would remove it when removing the link from the dom. but we still need to find - // the tag element to remove it as well. - linkTag[0].piwikTagElement = tagElement; - } - - /** Position the link tags next to the links */ - function positionLinkTags(callback) { - var url, linkTag, tagElement, offset, top, left, isRight, hasOneChild, inlineChild; - var tagWidth = 36, tagHeight = 21; - var tagsToRemove = []; - - for (var i = 0; i < followingPages.length; i++) { - url = followingPages[i].label; - if (typeof linksOnPage[url] != 'undefined') { - for (var j = 0; j < linksOnPage[url].length; j++) { - linkTag = linksOnPage[url][j]; - tagElement = linkTag[0].piwikTagElement; - - if (linkTag.closest('html').length == 0 || !tagElement) { - // the link has been removed from the dom - if (tagElement) { - tagElement.hide(); - } - // mark for deletion. don't delete it now because we - // are iterating of the array it's in. it will be deleted - // below this for loop. - tagsToRemove.push({ - index1: url, - index2: j - }); - continue; - } - - hasOneChild = checkHasOneChild(linkTag); - inlineChild = false; - if (hasOneChild && linkTag.css('display') != 'block') { - inlineChild = linkTag.children().eq(0); - } - - if (getVisibility(linkTag) == 'hidden' || ( - // in case of hasOneChild: jquery always returns linkTag.is(':visible')=false - !linkTag.is(':visible') && !(hasOneChild && inlineChild && inlineChild.is(':visible')) - )) { - // link is not visible - tagElement.hide(); - continue; - } - - tagElement.attr('class', 'PIS_LinkTag'); // reset class - if (tagElement[0].piwikHighlighted) { - tagElement.addClass('PIS_Highlighted'); - } - - // see comment in highlightLink() - if (hasOneChild && linkTag.find('> img').size() == 1) { - offset = linkTag.find('> img').offset(); - if (offset.left == 0 && offset.top == 0) { - offset = linkTag.offset(); - } - } else if (inlineChild !== false) { - offset = inlineChild.offset(); - } else { - offset = linkTag.offset(); - } - - top = offset.top - tagHeight + 6; - left = offset.left - tagWidth + 10; - - if (isRight = (left < 2)) { - tagElement.addClass('PIS_Right'); - left = offset.left + linkTag.outerWidth() - 10; - } - - if (top < 2) { - tagElement.addClass(isRight ? 'PIS_BottomRight' : 'PIS_Bottom'); - top = offset.top + linkTag.outerHeight() - 6; - } - - tagElement.css({ - top: top + 'px', - left: left + 'px' - }).show(); - - } - } - } - - // walk tagsToRemove from back to front because it contains the indexes in ascending - // order. removing something from the front will impact the indexes that come after- - // wards. this can be avoided by starting in the back. - for (var k = tagsToRemove.length - 1; k >= 0; k--) { - var tagToRemove = tagsToRemove[k]; - linkTag = linksOnPage[tagToRemove.index1][tagToRemove.index2]; - // remove the tag element from the dom - if (linkTag && linkTag[0] && linkTag[0].piwikTagElement) { - tagElement = linkTag[0].piwikTagElement; - if (tagElement[0].piwikHighlighted) { - unHighlightLink(linkTag, tagToRemove.index1); - } - tagElement.remove(); - linkTag[0].piwikTagElement = null; - } - // remove the link from the index - linksOnPage[tagToRemove.index1].splice(tagToRemove.index2, 1); - if (linksOnPage[tagToRemove.index1].length == 0) { - delete linksOnPage[tagToRemove.index1]; - } - } - - if (typeof callback == 'function') { - callback(); - } - } - - /** Get the visibility of an element */ - function getVisibility(el) { - var visibility = el.css('visibility'); - if (visibility == 'inherit') { - el = el.parent(); - if (el.size() > 0) { - return getVisibility(el); - } - } - return visibility; - } - - /** - * Find out whether a link has only one child. Using .children().size() == 1 doesn't work - * because it doesn't take additional text nodes into account. - */ - function checkHasOneChild(linkTag) { - var hasOneChild = (linkTag.children().size() == 1); - if (hasOneChild) { - // if the element contains one tag and some text, hasOneChild is set incorrectly - var contents = linkTag.contents(); - if (contents.size() > 1) { - // find non-empty text nodes - contents = contents.filter(function() { - return this.nodeType == 3 && // text node - $.trim(this.data).length > 0; // contains more than whitespaces - }); - if (contents.size() > 0) { - hasOneChild = false; - } - } - } - return hasOneChild; - } - - /** Check whether new links have been added to the dom */ - function findNewLinks() { - var newLinks = $('a').filter(function() { - return typeof this.piwikDiscovered == 'undefined' || this.piwikDiscovered === null; - }); - - if (newLinks.size() == 0) { - return; - } - - processLinkDelta = {}; - newLinks.each(processLink); - createLinkTags(processLinkDelta); - processLinkDelta = false; - } - - /** Dom elements used for drawing a box around the link */ - var highlightElements = []; - - /** Highlight a link on hover */ - function highlightLink(linkTag, linkUrl, data) { - if (highlightElements.length == 0) { - highlightElements.push(c('div', 'LinkHighlightBoxTop')); - highlightElements.push(c('div', 'LinkHighlightBoxRight')); - highlightElements.push(c('div', 'LinkHighlightBoxLeft')); - - highlightElements.push(c('div', 'LinkHighlightBoxText')); - - var body = $('body'); - for (var i = 0; i < highlightElements.length; i++) { - body.prepend(highlightElements[i].css({display: 'none'})); - } - } - - var width = linkTag.outerWidth(); - - var offset, height; - var hasOneChild = checkHasOneChild(linkTag); - if (hasOneChild && linkTag.find('img').size() == 1) { - // if the tag contains only an , the offset and height methods don't work properly. - // as a result, the box around the image link would be wrong. we use the image to derive - // the offset and height instead of the link to get correct values. - var img = linkTag.find('img'); - offset = img.offset(); - height = img.outerHeight(); - } - if (hasOneChild && linkTag.css('display') != 'block') { - // if the tag is not displayed as block and has only one child, using the child to - // derive the offset and dimensions is more robust. - var child = linkTag.children().eq(0); - offset = child.offset(); - height = child.outerHeight(); - width = child.outerWidth(); - } else { - offset = linkTag.offset(); - height = linkTag.outerHeight(); - } - - highlightElements[0].width(width).css({top: offset.top - 2, left: offset.left}).show(); - highlightElements[1].height(height + 4).css({top: offset.top - 2, left: offset.left + width}).show(); - highlightElements[2].height(height + 4).css({top: offset.top - 2, left: offset.left - 2}).show(); - - var numLinks = linksOnPage[linkUrl].length; - var text; - if (numLinks > 1) { - text = Piwik_Overlay_Translations.get('clicksFromXLinks') - .replace(/%1\$s/, data.referrals) - .replace(/%2\$s/, numLinks); - } else if (data.referrals == 1) { - text = Piwik_Overlay_Translations.get('oneClick'); - } else { - text = Piwik_Overlay_Translations.get('clicks') - .replace(/%s/, data.referrals); - } - - var padding = '  '; - highlightElements[3].html(padding + text + padding).css({ - width: 'auto', - top: offset.top + height, - left: offset.left - 2 - }).show(); - if (highlightElements[3].width() < width + 4) { - // we cannot use minWidth because of IE7 - highlightElements[3].width(width + 4); - } - - for (var j = 0; j < numLinks; j++) { - var tag = linksOnPage[linkUrl][j][0].piwikTagElement; - tag.addClass('PIS_Highlighted'); - tag[0].piwikHighlighted = true; - } - - // Sometimes it fails to remove the notification when the hovered element is removed. - // To make sure we don't display more than one location at a time, we hide all before showing the new one. - Piwik_Overlay_Client.hideNotifications('LinkLocation'); - - // we don't use .data() because jquery would remove the callback when the link tag is removed - linkTag[0].piwikHideNotification = Piwik_Overlay_Client.notification( - Piwik_Overlay_Translations.get('link') + ': ' + linkUrl, 'LinkLocation'); - } - - /** Remove highlight from link */ - function unHighlightLink(linkTag, linkUrl) { - for (var i = 0; i < highlightElements.length; i++) { - highlightElements[i].hide(); - } - - var numLinks = linksOnPage[linkUrl].length; - for (var j = 0; j < numLinks; j++) { - var tag = linksOnPage[linkUrl][j][0].piwikTagElement; - if (tag) { - tag.removeClass('PIS_Highlighted'); - tag[0].piwikHighlighted = false; - } - } - - if ((typeof linkTag[0].piwikHideNotification) == 'function') { - linkTag[0].piwikHideNotification(); - linkTag[0].piwikHideNotification = null; - } - } - - - return { - - /** - * The main method - */ - initialize: function(finishCallback) { - c = Piwik_Overlay_Client.createElement; - Piwik_Overlay_Client.loadScript('plugins/Overlay/client/urlnormalizer.js', function() { - Piwik_Overlay_UrlNormalizer.initialize(); - load(function() { - Piwik_Overlay_UrlNormalizer.setExcludedParameters(excludedParams); - build(function() { - finishCallback(); - }) - }); - }); - }, - - /** - * Remove everything from the dom and terminate timeouts. - * This can be used from the console in order to load a new implementation for debugging afterwards. - * If you add `Piwik_Overlay_FollowingPages.remove();` to the beginning and - * `Piwik_Overlay_FollowingPages.initialize(function(){});` to the end of this file, you can just - * paste it into the console to inject the new implementation. - */ - remove: function() { - for (var i = 0; i < followingPages.length; i++) { - var url = followingPages[i].label; - if (typeof linksOnPage[url] != 'undefined') { - for (var j = 0; j < linksOnPage[url].length; j++) { - var linkTag = linksOnPage[url][j]; - var tagElement = linkTag[0].piwikTagElement; - if (tagElement) { - tagElement.remove(); - } - linkTag[0].piwikTagElement = null; - - $(linkTag).unbind('mouseenter').unbind('mouseleave'); - } - } - } - for (i = 0; i < highlightElements.length; i++) { - highlightElements[i].remove(); - } - if (repositionTimeout) { - window.clearTimeout(repositionTimeout); - } - if (resizeTimeout) { - window.clearTimeout(resizeTimeout); - } - $(window).unbind('resize'); - } - - }; +var Piwik_Overlay_FollowingPages = (function () { + + /** jQuery */ + var $ = jQuery; + + /** Info about the following pages */ + var followingPages = []; + + /** List of excluded get parameters */ + var excludedParams = []; + + /** Index of the links on the page */ + var linksOnPage = {}; + + /** Reference to create element function */ + var c; + + /** Load the following pages */ + function load(callback) { + // normalize current location + var location = window.location.href; + location = Piwik_Overlay_UrlNormalizer.normalize(location); + location = (("https:" == document.location.protocol) ? 'https' : 'http') + '://' + location; + + var excludedParamsLoaded = false; + var followingPagesLoaded = false; + + // load excluded params + Piwik_Overlay_Client.api('getExcludedQueryParameters', function (data) { + for (var i = 0; i < data.length; i++) { + if (typeof data[i] == 'object') { + data[i] = data[i][0]; + } + } + excludedParams = data; + + excludedParamsLoaded = true; + if (followingPagesLoaded) { + callback(); + } + }); + + // load following pages + Piwik_Overlay_Client.api('getFollowingPages', function (data) { + followingPages = data; + processFollowingPages(); + + followingPagesLoaded = true; + if (excludedParamsLoaded) { + callback(); + } + }, 'url=' + encodeURIComponent(location)); + } + + /** Normalize the URLs of following pages and aggregate some stats */ + function processFollowingPages() { + var totalClicks = 0; + for (var i = 0; i < followingPages.length; i++) { + var page = followingPages[i]; + // though the following pages are returned without the prefix, downloads + // and outlinks still have it. + page.label = Piwik_Overlay_UrlNormalizer.removeUrlPrefix(page.label); + totalClicks += followingPages[i].referrals; + } + for (i = 0; i < followingPages.length; i++) { + followingPages[i].clickRate = followingPages[i].referrals / totalClicks * 100; + } + } + + /** + * Build an index of links on the page. + * This function is passed to $('a').each() + */ + var processLinkDelta = false; + + function processLink() { + var a = $(this); + a[0].piwikDiscovered = true; + + var href = a.attr('href'); + href = Piwik_Overlay_UrlNormalizer.normalize(href); + + if (href) { + if (typeof linksOnPage[href] == 'undefined') { + linksOnPage[href] = [a]; + } + else { + linksOnPage[href].push(a); + } + } + + if (href && processLinkDelta !== false) { + if (typeof processLinkDelta[href] == 'undefined') { + processLinkDelta[href] = [a]; + } + else { + processLinkDelta[href].push(a); + } + } + } + + var repositionTimeout = false; + var resizeTimeout = false; + + function build(callback) { + // build an index of all links on the page + $('a').each(processLink); + + // add tags to known following pages + createLinkTags(linksOnPage); + + // position the tags + positionLinkTags(); + + callback(); + + // check on a regular basis whether new links have appeared. + // we use a timeout instead of an interval to make sure one call is done before + // the next one is triggered + var repositionAfterTimeout; + repositionAfterTimeout = function () { + repositionTimeout = window.setTimeout(function () { + findNewLinks(); + positionLinkTags(repositionAfterTimeout); + }, 1800); + }; + repositionAfterTimeout(); + + // reposition link tags on window resize + $(window).resize(function () { + if (repositionTimeout) { + window.clearTimeout(repositionTimeout); + } + if (resizeTimeout) { + window.clearTimeout(resizeTimeout); + } + resizeTimeout = window.setTimeout(function () { + positionLinkTags(); + repositionAfterTimeout(); + }, 70); + }); + } + + /** Create a batch of link tags */ + function createLinkTags(links) { + var body = $('body'); + for (var i = 0; i < followingPages.length; i++) { + var url = followingPages[i].label; + if (typeof links[url] != 'undefined') { + for (var j = 0; j < links[url].length; j++) { + createLinkTag(links[url][j], url, followingPages[i], body); + } + } + } + } + + /** Create the link tag element */ + function createLinkTag(linkTag, linkUrl, data, body) { + if (typeof linkTag[0].piwikTagElement != 'undefined' && linkTag[0].piwikTagElement !== null) { + // this link tag already has a tag element. happens in rare cases. + return; + } + + linkTag[0].piwikTagElement = true; + + var rate = data.clickRate; + if (rate < 10) { + rate = Math.round(rate * 10) / 10; + } else { + rate = Math.round(rate); + } + + var span = c('span').html(rate + '%'); + var tagElement = c('div', 'LinkTag').append(span).hide(); + body.prepend(tagElement); + + linkTag.add(tagElement).hover(function () { + highlightLink(linkTag, linkUrl, data); + }, function () { + unHighlightLink(linkTag, linkUrl); + }); + + // attach the tag element to the link element. we can't use .data() because jquery + // would remove it when removing the link from the dom. but we still need to find + // the tag element to remove it as well. + linkTag[0].piwikTagElement = tagElement; + } + + /** Position the link tags next to the links */ + function positionLinkTags(callback) { + var url, linkTag, tagElement, offset, top, left, isRight, hasOneChild, inlineChild; + var tagWidth = 36, tagHeight = 21; + var tagsToRemove = []; + + for (var i = 0; i < followingPages.length; i++) { + url = followingPages[i].label; + if (typeof linksOnPage[url] != 'undefined') { + for (var j = 0; j < linksOnPage[url].length; j++) { + linkTag = linksOnPage[url][j]; + tagElement = linkTag[0].piwikTagElement; + + if (linkTag.closest('html').length == 0 || !tagElement) { + // the link has been removed from the dom + if (tagElement) { + tagElement.hide(); + } + // mark for deletion. don't delete it now because we + // are iterating of the array it's in. it will be deleted + // below this for loop. + tagsToRemove.push({ + index1: url, + index2: j + }); + continue; + } + + hasOneChild = checkHasOneChild(linkTag); + inlineChild = false; + if (hasOneChild && linkTag.css('display') != 'block') { + inlineChild = linkTag.children().eq(0); + } + + if (getVisibility(linkTag) == 'hidden' || ( + // in case of hasOneChild: jquery always returns linkTag.is(':visible')=false + !linkTag.is(':visible') && !(hasOneChild && inlineChild && inlineChild.is(':visible')) + )) { + // link is not visible + tagElement.hide(); + continue; + } + + tagElement.attr('class', 'PIS_LinkTag'); // reset class + if (tagElement[0].piwikHighlighted) { + tagElement.addClass('PIS_Highlighted'); + } + + // see comment in highlightLink() + if (hasOneChild && linkTag.find('> img').size() == 1) { + offset = linkTag.find('> img').offset(); + if (offset.left == 0 && offset.top == 0) { + offset = linkTag.offset(); + } + } else if (inlineChild !== false) { + offset = inlineChild.offset(); + } else { + offset = linkTag.offset(); + } + + top = offset.top - tagHeight + 6; + left = offset.left - tagWidth + 10; + + if (isRight = (left < 2)) { + tagElement.addClass('PIS_Right'); + left = offset.left + linkTag.outerWidth() - 10; + } + + if (top < 2) { + tagElement.addClass(isRight ? 'PIS_BottomRight' : 'PIS_Bottom'); + top = offset.top + linkTag.outerHeight() - 6; + } + + tagElement.css({ + top: top + 'px', + left: left + 'px' + }).show(); + + } + } + } + + // walk tagsToRemove from back to front because it contains the indexes in ascending + // order. removing something from the front will impact the indexes that come after- + // wards. this can be avoided by starting in the back. + for (var k = tagsToRemove.length - 1; k >= 0; k--) { + var tagToRemove = tagsToRemove[k]; + linkTag = linksOnPage[tagToRemove.index1][tagToRemove.index2]; + // remove the tag element from the dom + if (linkTag && linkTag[0] && linkTag[0].piwikTagElement) { + tagElement = linkTag[0].piwikTagElement; + if (tagElement[0].piwikHighlighted) { + unHighlightLink(linkTag, tagToRemove.index1); + } + tagElement.remove(); + linkTag[0].piwikTagElement = null; + } + // remove the link from the index + linksOnPage[tagToRemove.index1].splice(tagToRemove.index2, 1); + if (linksOnPage[tagToRemove.index1].length == 0) { + delete linksOnPage[tagToRemove.index1]; + } + } + + if (typeof callback == 'function') { + callback(); + } + } + + /** Get the visibility of an element */ + function getVisibility(el) { + var visibility = el.css('visibility'); + if (visibility == 'inherit') { + el = el.parent(); + if (el.size() > 0) { + return getVisibility(el); + } + } + return visibility; + } + + /** + * Find out whether a link has only one child. Using .children().size() == 1 doesn't work + * because it doesn't take additional text nodes into account. + */ + function checkHasOneChild(linkTag) { + var hasOneChild = (linkTag.children().size() == 1); + if (hasOneChild) { + // if the element contains one tag and some text, hasOneChild is set incorrectly + var contents = linkTag.contents(); + if (contents.size() > 1) { + // find non-empty text nodes + contents = contents.filter(function () { + return this.nodeType == 3 && // text node + $.trim(this.data).length > 0; // contains more than whitespaces + }); + if (contents.size() > 0) { + hasOneChild = false; + } + } + } + return hasOneChild; + } + + /** Check whether new links have been added to the dom */ + function findNewLinks() { + var newLinks = $('a').filter(function () { + return typeof this.piwikDiscovered == 'undefined' || this.piwikDiscovered === null; + }); + + if (newLinks.size() == 0) { + return; + } + + processLinkDelta = {}; + newLinks.each(processLink); + createLinkTags(processLinkDelta); + processLinkDelta = false; + } + + /** Dom elements used for drawing a box around the link */ + var highlightElements = []; + + /** Highlight a link on hover */ + function highlightLink(linkTag, linkUrl, data) { + if (highlightElements.length == 0) { + highlightElements.push(c('div', 'LinkHighlightBoxTop')); + highlightElements.push(c('div', 'LinkHighlightBoxRight')); + highlightElements.push(c('div', 'LinkHighlightBoxLeft')); + + highlightElements.push(c('div', 'LinkHighlightBoxText')); + + var body = $('body'); + for (var i = 0; i < highlightElements.length; i++) { + body.prepend(highlightElements[i].css({display: 'none'})); + } + } + + var width = linkTag.outerWidth(); + + var offset, height; + var hasOneChild = checkHasOneChild(linkTag); + if (hasOneChild && linkTag.find('img').size() == 1) { + // if the tag contains only an , the offset and height methods don't work properly. + // as a result, the box around the image link would be wrong. we use the image to derive + // the offset and height instead of the link to get correct values. + var img = linkTag.find('img'); + offset = img.offset(); + height = img.outerHeight(); + } + if (hasOneChild && linkTag.css('display') != 'block') { + // if the tag is not displayed as block and has only one child, using the child to + // derive the offset and dimensions is more robust. + var child = linkTag.children().eq(0); + offset = child.offset(); + height = child.outerHeight(); + width = child.outerWidth(); + } else { + offset = linkTag.offset(); + height = linkTag.outerHeight(); + } + + highlightElements[0].width(width).css({top: offset.top - 2, left: offset.left}).show(); + highlightElements[1].height(height + 4).css({top: offset.top - 2, left: offset.left + width}).show(); + highlightElements[2].height(height + 4).css({top: offset.top - 2, left: offset.left - 2}).show(); + + var numLinks = linksOnPage[linkUrl].length; + var text; + if (numLinks > 1) { + text = Piwik_Overlay_Translations.get('clicksFromXLinks') + .replace(/%1\$s/, data.referrals) + .replace(/%2\$s/, numLinks); + } else if (data.referrals == 1) { + text = Piwik_Overlay_Translations.get('oneClick'); + } else { + text = Piwik_Overlay_Translations.get('clicks') + .replace(/%s/, data.referrals); + } + + var padding = '  '; + highlightElements[3].html(padding + text + padding).css({ + width: 'auto', + top: offset.top + height, + left: offset.left - 2 + }).show(); + if (highlightElements[3].width() < width + 4) { + // we cannot use minWidth because of IE7 + highlightElements[3].width(width + 4); + } + + for (var j = 0; j < numLinks; j++) { + var tag = linksOnPage[linkUrl][j][0].piwikTagElement; + tag.addClass('PIS_Highlighted'); + tag[0].piwikHighlighted = true; + } + + // Sometimes it fails to remove the notification when the hovered element is removed. + // To make sure we don't display more than one location at a time, we hide all before showing the new one. + Piwik_Overlay_Client.hideNotifications('LinkLocation'); + + // we don't use .data() because jquery would remove the callback when the link tag is removed + linkTag[0].piwikHideNotification = Piwik_Overlay_Client.notification( + Piwik_Overlay_Translations.get('link') + ': ' + linkUrl, 'LinkLocation'); + } + + /** Remove highlight from link */ + function unHighlightLink(linkTag, linkUrl) { + for (var i = 0; i < highlightElements.length; i++) { + highlightElements[i].hide(); + } + + var numLinks = linksOnPage[linkUrl].length; + for (var j = 0; j < numLinks; j++) { + var tag = linksOnPage[linkUrl][j][0].piwikTagElement; + if (tag) { + tag.removeClass('PIS_Highlighted'); + tag[0].piwikHighlighted = false; + } + } + + if ((typeof linkTag[0].piwikHideNotification) == 'function') { + linkTag[0].piwikHideNotification(); + linkTag[0].piwikHideNotification = null; + } + } + + + return { + + /** + * The main method + */ + initialize: function (finishCallback) { + c = Piwik_Overlay_Client.createElement; + Piwik_Overlay_Client.loadScript('plugins/Overlay/client/urlnormalizer.js', function () { + Piwik_Overlay_UrlNormalizer.initialize(); + load(function () { + Piwik_Overlay_UrlNormalizer.setExcludedParameters(excludedParams); + build(function () { + finishCallback(); + }) + }); + }); + }, + + /** + * Remove everything from the dom and terminate timeouts. + * This can be used from the console in order to load a new implementation for debugging afterwards. + * If you add `Piwik_Overlay_FollowingPages.remove();` to the beginning and + * `Piwik_Overlay_FollowingPages.initialize(function(){});` to the end of this file, you can just + * paste it into the console to inject the new implementation. + */ + remove: function () { + for (var i = 0; i < followingPages.length; i++) { + var url = followingPages[i].label; + if (typeof linksOnPage[url] != 'undefined') { + for (var j = 0; j < linksOnPage[url].length; j++) { + var linkTag = linksOnPage[url][j]; + var tagElement = linkTag[0].piwikTagElement; + if (tagElement) { + tagElement.remove(); + } + linkTag[0].piwikTagElement = null; + + $(linkTag).unbind('mouseenter').unbind('mouseleave'); + } + } + } + for (i = 0; i < highlightElements.length; i++) { + highlightElements[i].remove(); + } + if (repositionTimeout) { + window.clearTimeout(repositionTimeout); + } + if (resizeTimeout) { + window.clearTimeout(resizeTimeout); + } + $(window).unbind('resize'); + } + + }; })(); \ No newline at end of file diff --git a/plugins/Overlay/client/translations.js b/plugins/Overlay/client/translations.js index 1f9e466e6b..9665b920a9 100644 --- a/plugins/Overlay/client/translations.js +++ b/plugins/Overlay/client/translations.js @@ -1,32 +1,30 @@ +var Piwik_Overlay_Translations = (function () { + /** Translations strings */ + var translations = []; + + return { + + /** + * Initialize translations module. + * Callback is triggered when data is available. + */ + initialize: function (callback) { + // Load translation data + Piwik_Overlay_Client.api('getTranslations', function (data) { + translations = data[0]; + callback(); + }); + }, + + /** Get translation string */ + get: function (identifier) { + if (typeof translations[identifier] == 'undefined') { + return identifier; + } + return translations[identifier]; + } + + }; -var Piwik_Overlay_Translations = (function() { - - /** Translations strings */ - var translations = []; - - return { - - /** - * Initialize translations module. - * Callback is triggered when data is available. - */ - initialize: function(callback) { - // Load translation data - Piwik_Overlay_Client.api('getTranslations', function(data) { - translations = data[0]; - callback(); - }); - }, - - /** Get translation string */ - get: function(identifier) { - if (typeof translations[identifier] == 'undefined') { - return identifier; - } - return translations[identifier]; - } - - }; - })(); diff --git a/plugins/Overlay/client/urlnormalizer.js b/plugins/Overlay/client/urlnormalizer.js index 38423bab60..72db61269b 100644 --- a/plugins/Overlay/client/urlnormalizer.js +++ b/plugins/Overlay/client/urlnormalizer.js @@ -1,199 +1,198 @@ - /** * URL NORMALIZER * This utility preprocesses both the URLs in the document and * from the Piwik logs in order to make matching possible. */ -var Piwik_Overlay_UrlNormalizer = (function() { - - /** Base href of the current document */ - var baseHref = false; - - /** Url of current folder */ - var currentFolder; - - /** The current domain */ - var currentDomain; - - /** Regular expressions for parameters to be excluded when matching links on the page */ - var excludedParamsRegEx = []; - - /** - * Basic normalizations for domain names - * - remove protocol and www from absolute urls - * - add a trailing slash to urls without a path - * - * Returns array - * 0: normalized url - * 1: true, if url was absolute (if not, no normalization was performed) - */ - function normalizeDomain(url) { - if (url === null) { - return ''; - } - - var absolute = false; - - // remove protocol - if (url.substring(0, 7) == 'http://') { - absolute = true; - url = url.substring(7, url.length); - } else if (url.substring(0, 8) == 'https://') { - absolute = true; - url = url.substring(8, url.length); - } - - if (absolute) { - // remove www. - url = removeWww(url); - - // add slash to domain names - if (url.indexOf('/') == -1) { - url += '/'; - } - } - - return [url, absolute]; - } - - /** Remove www. from a domain */ - function removeWww(domain) { - if (domain.substring(0, 4) == 'www.') { - return domain.substring(4, domain.length); - } - return domain; - } - - return { - - initialize: function() { - this.setCurrentDomain(document.location.hostname); - this.setCurrentUrl(window.location.href); - - var head = document.getElementsByTagName('head'); - if (head.length) { - var base = head[0].getElementsByTagName('base'); - if (base.length && base[0].href) { - this.setBaseHref(base[0].href); - } - } - }, - - /** - * Explicitly set domain (for testing) - */ - setCurrentDomain: function(pCurrentDomain) { - currentDomain = removeWww(pCurrentDomain); - }, - - /** - * Explicitly set current url (for testing) - */ - setCurrentUrl: function(url) { - var index = url.lastIndexOf('/'); - if (index != url.length - 1) { - currentFolder = url.substring(0, index + 1); - } else { - currentFolder = url; - } - currentFolder = normalizeDomain(currentFolder)[0]; - }, - - /** - * Explicitly set base href (for testing) - */ - setBaseHref: function(pBaseHref) { - if (!pBaseHref) { - baseHref = false; - } else { - baseHref = normalizeDomain(pBaseHref)[0]; - } - }, - - /** - * Set the parameters to be excluded when matching links on the page - */ - setExcludedParameters: function(pExcludedParams) { - excludedParamsRegEx = []; - for (var i = 0; i < pExcludedParams.length; i++) { - var paramString = pExcludedParams[i]; - excludedParamsRegEx.push(new RegExp('&' + paramString + '=([^&#]*)', 'ig')); - } - }, - - /** - * Remove the protocol and the prefix of a URL - */ - removeUrlPrefix: function(url) { - return normalizeDomain(url)[0]; - }, - - /** - * Normalize URL - * Can be an absolute or a relative URL - */ - normalize: function(url) { - if (!url) { - return ''; - } - - // ignore urls starting with # - if (url.substring(0, 1) == '#') { - return ''; - } - - // basic normalizations for absolute urls - var normalized = normalizeDomain(url); - url = normalized[0]; - - var absolute = normalized[1]; - - if (!absolute) { - /** relative url */ - if (url.substring(0, 1) == '/') { - // relative to domain root - url = currentDomain + url; - } else if (baseHref) { - // relative to base href - url = baseHref + url; - } else { - // relative to current folder - url = currentFolder + url; - } - } - - // replace multiple / with a single / - url = url.replace(/\/\/+/g, '/'); - - // handle ./ and ../ - var parts = url.split('/'); - var urlArr = []; - for (var i = 0; i < parts.length; i++) { - if (parts[i] == '.') { - // ignore - } - else if (parts[i] == '..') { - urlArr.pop(); - } - else { - urlArr.push(parts[i]); - } - } - url = urlArr.join('/'); - - // remove ignored parameters - url = url.replace(/\?/, '?&'); - for (i = 0; i < excludedParamsRegEx.length; i++) { - var regEx = excludedParamsRegEx[i]; - url = url.replace(regEx, ''); - } - url = url.replace(/\?&/, '?'); - url = url.replace(/\?#/, '#'); - url = url.replace(/\?$/, ''); - - return url; - } - - }; - +var Piwik_Overlay_UrlNormalizer = (function () { + + /** Base href of the current document */ + var baseHref = false; + + /** Url of current folder */ + var currentFolder; + + /** The current domain */ + var currentDomain; + + /** Regular expressions for parameters to be excluded when matching links on the page */ + var excludedParamsRegEx = []; + + /** + * Basic normalizations for domain names + * - remove protocol and www from absolute urls + * - add a trailing slash to urls without a path + * + * Returns array + * 0: normalized url + * 1: true, if url was absolute (if not, no normalization was performed) + */ + function normalizeDomain(url) { + if (url === null) { + return ''; + } + + var absolute = false; + + // remove protocol + if (url.substring(0, 7) == 'http://') { + absolute = true; + url = url.substring(7, url.length); + } else if (url.substring(0, 8) == 'https://') { + absolute = true; + url = url.substring(8, url.length); + } + + if (absolute) { + // remove www. + url = removeWww(url); + + // add slash to domain names + if (url.indexOf('/') == -1) { + url += '/'; + } + } + + return [url, absolute]; + } + + /** Remove www. from a domain */ + function removeWww(domain) { + if (domain.substring(0, 4) == 'www.') { + return domain.substring(4, domain.length); + } + return domain; + } + + return { + + initialize: function () { + this.setCurrentDomain(document.location.hostname); + this.setCurrentUrl(window.location.href); + + var head = document.getElementsByTagName('head'); + if (head.length) { + var base = head[0].getElementsByTagName('base'); + if (base.length && base[0].href) { + this.setBaseHref(base[0].href); + } + } + }, + + /** + * Explicitly set domain (for testing) + */ + setCurrentDomain: function (pCurrentDomain) { + currentDomain = removeWww(pCurrentDomain); + }, + + /** + * Explicitly set current url (for testing) + */ + setCurrentUrl: function (url) { + var index = url.lastIndexOf('/'); + if (index != url.length - 1) { + currentFolder = url.substring(0, index + 1); + } else { + currentFolder = url; + } + currentFolder = normalizeDomain(currentFolder)[0]; + }, + + /** + * Explicitly set base href (for testing) + */ + setBaseHref: function (pBaseHref) { + if (!pBaseHref) { + baseHref = false; + } else { + baseHref = normalizeDomain(pBaseHref)[0]; + } + }, + + /** + * Set the parameters to be excluded when matching links on the page + */ + setExcludedParameters: function (pExcludedParams) { + excludedParamsRegEx = []; + for (var i = 0; i < pExcludedParams.length; i++) { + var paramString = pExcludedParams[i]; + excludedParamsRegEx.push(new RegExp('&' + paramString + '=([^&#]*)', 'ig')); + } + }, + + /** + * Remove the protocol and the prefix of a URL + */ + removeUrlPrefix: function (url) { + return normalizeDomain(url)[0]; + }, + + /** + * Normalize URL + * Can be an absolute or a relative URL + */ + normalize: function (url) { + if (!url) { + return ''; + } + + // ignore urls starting with # + if (url.substring(0, 1) == '#') { + return ''; + } + + // basic normalizations for absolute urls + var normalized = normalizeDomain(url); + url = normalized[0]; + + var absolute = normalized[1]; + + if (!absolute) { + /** relative url */ + if (url.substring(0, 1) == '/') { + // relative to domain root + url = currentDomain + url; + } else if (baseHref) { + // relative to base href + url = baseHref + url; + } else { + // relative to current folder + url = currentFolder + url; + } + } + + // replace multiple / with a single / + url = url.replace(/\/\/+/g, '/'); + + // handle ./ and ../ + var parts = url.split('/'); + var urlArr = []; + for (var i = 0; i < parts.length; i++) { + if (parts[i] == '.') { + // ignore + } + else if (parts[i] == '..') { + urlArr.pop(); + } + else { + urlArr.push(parts[i]); + } + } + url = urlArr.join('/'); + + // remove ignored parameters + url = url.replace(/\?/, '?&'); + for (i = 0; i < excludedParamsRegEx.length; i++) { + var regEx = excludedParamsRegEx[i]; + url = url.replace(regEx, ''); + } + url = url.replace(/\?&/, '?'); + url = url.replace(/\?#/, '#'); + url = url.replace(/\?$/, ''); + + return url; + } + + }; + })(); \ No newline at end of file diff --git a/plugins/Overlay/templates/error_wrong_domain.tpl b/plugins/Overlay/templates/error_wrong_domain.tpl index 49bd01ed20..4cb400ecce 100644 --- a/plugins/Overlay/templates/error_wrong_domain.tpl +++ b/plugins/Overlay/templates/error_wrong_domain.tpl @@ -1,36 +1,38 @@ - - - - - - - + + {/literal} + + -

{$message}

-

{$troubleshoot}

+

{$message}

+ +

{$troubleshoot}

\ No newline at end of file diff --git a/plugins/Overlay/templates/helper.js b/plugins/Overlay/templates/helper.js index ee62cd60aa..1342bf79f0 100644 --- a/plugins/Overlay/templates/helper.js +++ b/plugins/Overlay/templates/helper.js @@ -6,26 +6,26 @@ */ var Overlay_Helper = { - - /** Encode the iframe url to put it behind the hash in sidebar mode */ - encodeFrameUrl: function(url) { - // url encode + replace % with $ to make sure that browsers don't break the encoding - return encodeURIComponent(url).replace(/%/g, '$') - }, - - /** Decode the url after reading it from the hash */ - decodeFrameUrl: function(url) { - // reverse encodeFrameUrl() - return decodeURIComponent(url.replace(/\$/g, '%')); - }, - - /** Get the url to launch overlay */ - getOverlayLink: function(idSite, period, date, link) { - var url = 'index.php?module=Overlay&period=' + period + '&date=' + date + '&idSite=' + idSite; - if (link) { - url += '#l=' + Overlay_Helper.encodeFrameUrl(link); - } - return url; - } - + + /** Encode the iframe url to put it behind the hash in sidebar mode */ + encodeFrameUrl: function (url) { + // url encode + replace % with $ to make sure that browsers don't break the encoding + return encodeURIComponent(url).replace(/%/g, '$') + }, + + /** Decode the url after reading it from the hash */ + decodeFrameUrl: function (url) { + // reverse encodeFrameUrl() + return decodeURIComponent(url.replace(/\$/g, '%')); + }, + + /** Get the url to launch overlay */ + getOverlayLink: function (idSite, period, date, link) { + var url = 'index.php?module=Overlay&period=' + period + '&date=' + date + '&idSite=' + idSite; + if (link) { + url += '#l=' + Overlay_Helper.encodeFrameUrl(link); + } + return url; + } + }; \ No newline at end of file diff --git a/plugins/Overlay/templates/index.css b/plugins/Overlay/templates/index.css index 154546eeeb..f48a94ec35 100644 --- a/plugins/Overlay/templates/index.css +++ b/plugins/Overlay/templates/index.css @@ -1,144 +1,143 @@ - html { - width: 100%; - height: 100%; - /** hide scroll bar in ie7 */ - *overflow: auto; + width: 100%; + height: 100%; + /** hide scroll bar in ie7 */ + *overflow: auto; } body { - width: 100%; - height: 100%; - overflow: hidden; + width: 100%; + height: 100%; + overflow: hidden; } body #header { - margin-left: -12px; + margin-left: -12px; } body #logo { - margin-top: 5px; + margin-top: 5px; } body h1 { - font-size: 15px; - font-weight: normal; - margin: -3px 0 0 0; - padding: 0 0 0 5px; + font-size: 15px; + font-weight: normal; + margin: -3px 0 0 0; + padding: 0 0 0 5px; } #Overlay_DateRangeSelection { - padding: 30px 0 20px 23px; - background: url(../../../themes/default/images/icon-calendar.gif) 2px 33px no-repeat; - margin-left: 5px; + padding: 30px 0 20px 23px; + background: url(../../../themes/default/images/icon-calendar.gif) 2px 33px no-repeat; + margin-left: 5px; } #Overlay_Location { - width: 200px; - padding: 0 0 30px 0; - margin-left: 5px; - font-size: 12px; + width: 200px; + padding: 0 0 30px 0; + margin-left: 5px; + font-size: 12px; } #Overlay_Loading { - background: url(../../../themes/default/images/loading-blue.gif) no-repeat center 10px; - width: 190px; - padding-top: 30px; - margin-top: 30px; - text-align: center; - display: none; + background: url(../../../themes/default/images/loading-blue.gif) no-repeat center 10px; + width: 190px; + padding-top: 30px; + margin-top: 30px; + text-align: center; + display: none; } #Overlay_Sidebar { - width: 200px; - margin-left: 5px; + width: 200px; + margin-left: 5px; } #Overlay_Sidebar h2 { - font-size: 15px; - margin: 0; - padding: 0 0 8px 23px; - color: #255792; + font-size: 15px; + margin: 0; + padding: 0 0 8px 23px; + color: #255792; } a#Overlay_FullScreen, a#Overlay_RowEvolution, a#Overlay_Transitions { - display: block; - color: #255792; - font-size: 13px; - line-height: 15px; - margin: 0 0 0 5px; - padding: 8px 0 3px 25px; - text-decoration: none; + display: block; + color: #255792; + font-size: 13px; + line-height: 15px; + margin: 0 0 0 5px; + padding: 8px 0 3px 25px; + text-decoration: none; } a#Overlay_RowEvolution { - margin-top: 20px; - background: url(../../../themes/default/images/row_evolution_hover.png) no-repeat 0 7px; + margin-top: 20px; + background: url(../../../themes/default/images/row_evolution_hover.png) no-repeat 0 7px; } a#Overlay_Transitions { - background: url(../../Transitions/templates/transitions_icon_hover.png) no-repeat 0 6px; + background: url(../../Transitions/templates/transitions_icon_hover.png) no-repeat 0 6px; } a#Overlay_FullScreen { - margin-top: 20px; - background: url(../../../themes/default/images/fullscreen.png) no-repeat 3px 8px; + margin-top: 20px; + background: url(../../../themes/default/images/fullscreen.png) no-repeat 3px 8px; } a#Overlay_FullScreen:hover, a#Overlay_RowEvolution:hover, a#Overlay_Transitions:hover { - color: #E87500; + color: #E87500; } #Overlay_Main { - margin-left: 220px; - position: absolute; - top: 0; + margin-left: 220px; + position: absolute; + top: 0; } #Overlay_Iframe { - border-left: 2px solid #ddd; + border-left: 2px solid #ddd; } .Overlay_Metric { - font-size: 12px; - padding-bottom: 4px; + font-size: 12px; + padding-bottom: 4px; } .Overlay_MetricValue { - font-size: 14px; - font-weight: bold; + font-size: 14px; + font-weight: bold; } .Overlay_NoData { - font-size: 12px; - color: #7E7363; + font-size: 12px; + color: #7E7363; } h2.Overlay_MainMetrics { - background: url(./info.png) 0 1px no-repeat + background: url(./info.png) 0 1px no-repeat } body .piwik-tooltip.Overlay_Tooltip { - font-size: 11px; - padding: 3px 5px 3px 6px; + font-size: 11px; + padding: 3px 5px 3px 6px; } #Overlay_NoFrame { - padding: 20px 0 40px 2px + padding: 20px 0 40px 2px } #Overlay_Error_NotLoading { - width: 190px; - display: none; - margin: 20px 0 0 5px; - font-size: 14px; + width: 190px; + display: none; + margin: 20px 0 0 5px; + font-size: 14px; } #Overlay_Error_NotLoading span { - color: #E87500; - font-weight: bold; + color: #E87500; + font-weight: bold; } \ No newline at end of file diff --git a/plugins/Overlay/templates/index.js b/plugins/Overlay/templates/index.js index 62ff3bb513..6ff4ee8ed5 100644 --- a/plugins/Overlay/templates/index.js +++ b/plugins/Overlay/templates/index.js @@ -5,37 +5,37 @@ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later */ -var Piwik_Overlay = (function() { +var Piwik_Overlay = (function () { - var $body, $iframe, $sidebar, $main, $location, $loading, $errorNotLoading; - var $rowEvolutionLink, $transitionsLink, $fullScreenLink; + var $body, $iframe, $sidebar, $main, $location, $loading, $errorNotLoading; + var $rowEvolutionLink, $transitionsLink, $fullScreenLink; - var idSite, period, date; + var idSite, period, date; - var errorTimeout = false; + var errorTimeout = false; - var iframeSrcBase; - var iframeDomain = ''; - var iframeCurrentPage = ''; - var iframeCurrentPageNormalized = ''; - var iframeCurrentActionLabel = ''; - var updateComesFromInsideFrame = false; + var iframeSrcBase; + var iframeDomain = ''; + var iframeCurrentPage = ''; + var iframeCurrentPageNormalized = ''; + var iframeCurrentActionLabel = ''; + var updateComesFromInsideFrame = false; - /** Load the sidebar for a url */ - function loadSidebar(currentUrl) { - showLoading(); + /** Load the sidebar for a url */ + function loadSidebar(currentUrl) { + showLoading(); - $location.html(' ').unbind('mouseenter').unbind('mouseleave'); + $location.html(' ').unbind('mouseenter').unbind('mouseleave'); - iframeCurrentPage = currentUrl; - iframeDomain = currentUrl.match(/http(s)?:\/\/(www\.)?([^\/]*)/i)[3]; + iframeCurrentPage = currentUrl; + iframeDomain = currentUrl.match(/http(s)?:\/\/(www\.)?([^\/]*)/i)[3]; globalAjaxQueue.abort(); var ajaxRequest = new ajaxHelper(); ajaxRequest.addParams({ - module: 'Overlay', - action: 'renderSidebar', + module: 'Overlay', + action: 'renderSidebar', currentUrl: currentUrl }, 'get'); ajaxRequest.setCallback( @@ -52,9 +52,9 @@ var Piwik_Overlay = (function() { $location.html($responseLocation.html()).show(); $responseLocation.remove(); - var $locationSpan = $location.find('span'); - $locationSpan.html(piwikHelper.addBreakpointsToUrl($locationSpan.text())); - $locationSpan.hover(function () { + var $locationSpan = $location.find('span'); + $locationSpan.html(piwikHelper.addBreakpointsToUrl($locationSpan.text())); + $locationSpan.hover(function () { if (iframeDomain) { // use addBreakpointsToUrl because it also encoded html entities Piwik_Tooltip.show('' + Piwik_Overlay_Translations.domain + ': ' + @@ -74,187 +74,187 @@ var Piwik_Overlay = (function() { ); ajaxRequest.setFormat('html'); ajaxRequest.send(false); - } - - /** Adjust the dimensions of the iframe */ - function adjustDimensions() { - $iframe.height($(window).height()); - $iframe.width($body.width() - $iframe.offset().left - 2); // -2 because of 2px border - } - - /** Display the loading message and hide other containers */ - function showLoading() { - $loading.show(); - - $sidebar.hide(); - $location.hide(); - - $fullScreenLink.hide(); - $rowEvolutionLink.hide(); - $transitionsLink.hide(); - - $errorNotLoading.hide(); - - // Start a timeout that shows an error when nothing is loaded - if (errorTimeout) { - window.clearTimeout(errorTimeout); - } - errorTimeout = window.setTimeout(function() { - hideLoading(); - $errorNotLoading.show(); - }, 9000); - } - - /** Hide the loading message */ - function hideLoading() { - if (errorTimeout) { - window.clearTimeout(errorTimeout); - errorTimeout = false; - } - $loading.hide(); - $fullScreenLink.show(); - } - - /** $.history callback for hash change */ - function hashChangeCallback(urlHash) { - var location = broadcast.getParamValue('l', urlHash); - location = Overlay_Helper.decodeFrameUrl(location); - - if (!updateComesFromInsideFrame) { - var iframeUrl = iframeSrcBase; - if (location) { - iframeUrl += '#' + location; - } - $iframe.attr('src', iframeUrl); - showLoading(); - } else { - loadSidebar(location); - } - - updateComesFromInsideFrame = false; - } - - return { - - /** This method is called when Overlay loads (from index.tpl) */ - init: function(iframeSrc, pIdSite, pPeriod, pDate) { - iframeSrcBase = iframeSrc; - idSite = pIdSite; - period = pPeriod; - date = pDate; - - $body = $('body'); - $iframe = $('#Overlay_Iframe'); - $sidebar = $('#Overlay_Sidebar'); - $location = $('#Overlay_Location'); - $main = $('#Overlay_Main'); - $loading = $('#Overlay_Loading'); - $errorNotLoading = $('#Overlay_Error_NotLoading'); - - $rowEvolutionLink = $('#Overlay_RowEvolution'); - $transitionsLink = $('#Overlay_Transitions'); - $fullScreenLink = $('#Overlay_FullScreen'); - - adjustDimensions(); - - showLoading(); - - // apply initial dimensions - window.setTimeout(function() { - adjustDimensions(); - }, 50); - - // handle window resize - // we manipulate broadcast.pageload because it unbinds all resize events on window - var originalPageload = broadcast.pageload; - broadcast.pageload = function(hash) { - originalPageload(hash); - $(window).resize(function() { - adjustDimensions(); - }); - }; - $(window).resize(function() { - adjustDimensions(); - }); - - // handle hash change - broadcast.loadAjaxContent = hashChangeCallback; - broadcast.init(); - - if (window.location.href.split('#').length == 1) { - // if there's no hash, broadcast won't trigger the callback - we have to do it here - hashChangeCallback(''); - } - - // handle date selection - var $select = $('select#Overlay_DateRangeSelect').change(function() { - var parts = $(this).val().split(';'); - if (parts.length == 2) { - period = parts[0]; - date = parts[1]; - window.location.href = Overlay_Helper.getOverlayLink(idSite, period, date, iframeCurrentPage); - } - }); - - var optionMatchFound = false; - $select.find('option').each(function() { - if ($(this).val() == period + ';' + date) { - $(this).attr('selected', 'selected'); - optionMatchFound = true; - } - }); - - if (!optionMatchFound) { - $select.prepend('
 
@@ -45,17 +47,17 @@
- +
diff --git a/plugins/Overlay/templates/index_noframe.tpl b/plugins/Overlay/templates/index_noframe.tpl index 0897f7cd9c..af2d08ab3d 100644 --- a/plugins/Overlay/templates/index_noframe.tpl +++ b/plugins/Overlay/templates/index_noframe.tpl @@ -2,23 +2,23 @@

{'Overlay_Overlay'|translate|escape:'html'}

- - + + window.location.href = newLocation; + + {/literal} +
diff --git a/plugins/Overlay/templates/notify_parent_iframe.tpl b/plugins/Overlay/templates/notify_parent_iframe.tpl index 4e4dbede4e..19327445a1 100644 --- a/plugins/Overlay/templates/notify_parent_iframe.tpl +++ b/plugins/Overlay/templates/notify_parent_iframe.tpl @@ -1,14 +1,14 @@ - + - + \ No newline at end of file diff --git a/plugins/Overlay/templates/rowaction.js b/plugins/Overlay/templates/rowaction.js index b73cd27c57..ff525c1e38 100644 --- a/plugins/Overlay/templates/rowaction.js +++ b/plugins/Overlay/templates/rowaction.js @@ -10,52 +10,52 @@ */ function DataTable_RowActions_Overlay(dataTable) { - this.dataTable = dataTable; + this.dataTable = dataTable; } DataTable_RowActions_Overlay.prototype = new DataTable_RowAction; -DataTable_RowActions_Overlay.prototype.onClick = function(actionA, tr, e) { - if (!actionA.data('overlay-manipulated')) { - actionA.data('overlay-manipulated', 1); - - var link = tr.find('> td:first > a').attr('href'); - link = $(' -
- {'PDFReports_DescriptionOnFirstPage'|translate} -
- - - - {'PDFReports_EmailSchedule'|translate} - - - -
- {'PDFReports_WeeklyScheduleHelp'|translate} -
- {'PDFReports_MonthlyScheduleHelp'|translate} -
- {'PDFReports_ReportHour'|translate} - - {'PDFReports_OClock'|translate} -
- - +
+ {'PDFReports_CancelAndReturnToReports'|translate:"":""} +
+
+
+ + + + + + + + + + + + + + - - - +
+ {'PDFReports_DescriptionOnFirstPage'|translate} +
+ + + + + - +
+ {'PDFReports_WeeklyScheduleHelp'|translate} +
+ {'PDFReports_MonthlyScheduleHelp'|translate} +
+ {'PDFReports_ReportHour'|translate} + + {'PDFReports_OClock'|translate} +
+ + - - + + + + - {postEvent name="template_reportParametersPDFReports"} + + - - - + - {if $allowMultipleReportsByReportType[$reportType]} - {assign var=reportInputType value='checkbox'} - {else} - {assign var=reportInputType value='radio'} - {/if} + {postEvent name="template_reportParametersPDFReports"} - {assign var=countCategory value=0} + + + - - - -
{'PDFReports_CreateAndScheduleReport'|translate}
{'General_Website'|translate} + {$siteName} +
{'General_Description'|translate} + -
- {'PDFReports_ReportType'|translate} - - -
{'PDFReports_EmailSchedule'|translate} + -
- {'PDFReports_ReportFormat'|translate} -
- {foreach from=$reportFormatsByReportType key=reportType item=reportFormats} - - {/foreach} -
+ {'PDFReports_ReportType'|translate} + + +
+ {'PDFReports_ReportFormat'|translate} +
{'PDFReports_ReportsIncluded'|translate} - {foreach from=$reportsByCategoryByReportType key=reportType item=reportsByCategory} -
+
+ {foreach from=$reportFormatsByReportType key=reportType item=reportFormats} + + {/foreach} +
{'PDFReports_ReportsIncluded'|translate} + {foreach from=$reportsByCategoryByReportType key=reportType item=reportsByCategory} +
- {math - equation="ceil (reportsByCategoryCount / 2)" - reportsByCategoryCount=$reportsByCategory|@count - assign=newColumnAfter - } + {if $allowMultipleReportsByReportType[$reportType]} + {assign var=reportInputType value='checkbox'} + {else} + {assign var=reportInputType value='radio'} + {/if} -
- {foreach from=$reportsByCategory item=reports key=category name=reports} - {if $countCategory >= $newColumnAfter && $newColumnAfter != 0} - {assign var=newColumnAfter value=0} -
- {/if} -
{$category}
    - {foreach from=$reports item=report} -
  • - - -
  • - {/foreach} - {assign var=countCategory value=$countCategory+1} -
-
- {/foreach} -
-
- {/foreach} -
+ {assign var=countCategory value=0} - - + {math + equation="ceil (reportsByCategoryCount / 2)" + reportsByCategoryCount=$reportsByCategory|@count + assign=newColumnAfter + } -
-
- {'General_OrCancel'|translate:"":""} -
+
+ {foreach from=$reportsByCategory item=reports key=category name=reports} + {if $countCategory >= $newColumnAfter && $newColumnAfter != 0} + {assign var=newColumnAfter value=0} +
+
+ {/if} +
{$category}
+
    + {foreach from=$reports item=report} +
  • + + +
  • + {/foreach} + {assign var=countCategory value=$countCategory+1} +
+
+ {/foreach} +
+
+ {/foreach} + + + + + + + + + + +
+ {'General_OrCancel'|translate:"":""} +
\ No newline at end of file diff --git a/plugins/PDFReports/templates/index.tpl b/plugins/PDFReports/templates/index.tpl index 1479c8d6cc..e47b41e665 100644 --- a/plugins/PDFReports/templates/index.tpl +++ b/plugins/PDFReports/templates/index.tpl @@ -7,41 +7,41 @@
-

{'PDFReports_ManageEmailReports'|translate}

- -
- {ajaxErrorDiv} - {ajaxLoadingDiv} - {include file="PDFReports/templates/list.tpl"} - {include file="PDFReports/templates/add.tpl"} - -
+

{'PDFReports_ManageEmailReports'|translate}

+ +
+ {ajaxErrorDiv} + {ajaxLoadingDiv} + {include file="PDFReports/templates/list.tpl"} + {include file="PDFReports/templates/add.tpl"} + +
-

{'PDFReports_AreYouSureDeleteReport'|translate}

- - -
+

{'PDFReports_AreYouSureDeleteReport'|translate}

+ + +
- + {/literal} diff --git a/plugins/PDFReports/templates/list.tpl b/plugins/PDFReports/templates/list.tpl index d21ef65b92..c67c5ac024 100644 --- a/plugins/PDFReports/templates/list.tpl +++ b/plugins/PDFReports/templates/list.tpl @@ -1,90 +1,96 @@
- - - - - - - - - - - - - - {if $userLogin=='anonymous'} - -
{'General_Description'|translate}{'PDFReports_EmailSchedule'|translate}{'PDFReports_ReportFormat'|translate}{'PDFReports_SendReportTo'|translate}{'General_Download'|translate}{'General_Edit'|translate}{'General_Delete'|translate}
-
- {'PDFReports_MustBeLoggedIn'|translate} -
{'Login_LogIn'|translate} -

-
- {elseif empty($reports)} - -
- {'PDFReports_ThereIsNoReportToManage'|translate:$siteName}. -

- › {'PDFReports_CreateAndScheduleReport'|translate} -

- - - {else} - {foreach from=$reports item=report} - - {$report.description} - {$periods[$report.period]} - - - - {if !empty($report.format)} - {$report.format|upper} - {/if} - - - {*report recipients*} - {if $report.recipients|@count eq 0} - {'PDFReports_NoRecipients'|translate} - {else} - {foreach name=recipients from=$report.recipients item=recipient} - {$recipient}
- {/foreach} - {*send now link*} - - - {'PDFReports_SendReportNow'|translate} - - {/if} - - - {*download link*} - - - {'General_Download'|translate} - - - - {*edit link*} - - - {'General_Edit'|translate} - - - - {*delete link *} - - - {'General_Delete'|translate} - - - - {/foreach} - - {if $userLogin != 'anonymous'} -
- › {'PDFReports_CreateAndScheduleReport'|translate} -

- {/if} - {/if} + + + + + + + + + + + + + + {if $userLogin=='anonymous'} + + + +
{'General_Description'|translate}{'PDFReports_EmailSchedule'|translate}{'PDFReports_ReportFormat'|translate}{'PDFReports_SendReportTo'|translate}{'General_Download'|translate}{'General_Edit'|translate}{'General_Delete'|translate}
+
+ {'PDFReports_MustBeLoggedIn'|translate} +
{'Login_LogIn'|translate} +

+
+ {elseif empty($reports)} + + +
+ {'PDFReports_ThereIsNoReportToManage'|translate:$siteName}. +

+ › {'PDFReports_CreateAndScheduleReport'|translate} +

+ + + + {else} + {foreach from=$reports item=report} + + {$report.description} + {$periods[$report.period]} + + + + {if !empty($report.format)} + {$report.format|upper} + {/if} + + + {*report recipients*} + {if $report.recipients|@count eq 0} + {'PDFReports_NoRecipients'|translate} + {else} + {foreach name=recipients from=$report.recipients item=recipient} + {$recipient} +
+ {/foreach} + {*send now link*} + + + {'PDFReports_SendReportNow'|translate} + + {/if} + + + {*download link*} + + + {'General_Download'|translate} + + + + {*edit link*} + + + {'General_Edit'|translate} + + + + {*delete link *} + + + {'General_Delete'|translate} + + + + {/foreach} + + {if $userLogin != 'anonymous'} +
+ › {'PDFReports_CreateAndScheduleReport'|translate} +
+
+ {/if} + {/if}
diff --git a/plugins/PDFReports/templates/pdf.js b/plugins/PDFReports/templates/pdf.js index 221418f318..4115dcc3f1 100644 --- a/plugins/PDFReports/templates/pdf.js +++ b/plugins/PDFReports/templates/pdf.js @@ -9,92 +9,83 @@ var getReportParametersFunctions = Object(); var updateReportParametersFunctions = Object(); var resetReportParametersFunctions = Object(); -function formSetEditReport(idReport) -{ - var report = { - 'type' : ReportPlugin.defaultReportType, - 'format' : ReportPlugin.defaultReportFormat, - 'description' : '', - 'period' : ReportPlugin.defaultPeriod, - 'hour' : ReportPlugin.defaultHour, - 'reports' : [] - }; - - if(idReport > 0) - { - report = ReportPlugin.reportList[idReport]; - $('#report_submit').val(ReportPlugin.updateReportString); - } - else - { - $('#report_submit').val(ReportPlugin.createReportString); - } - - 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_hour').val(report.hour); - $('[name=report_format].'+report.type+' option[value='+report.format+']').prop('selected', 'selected'); - - $('[name=reportsList] input').prop('checked', false); - - var key; - for(key in report.reports) - { - $('.' + report.type + ' [report-unique-id=' + report.reports[key] + ']').prop('checked','checked'); - } - - updateReportParametersFunctions[report.type](report.parameters); - - $('#report_idreport').val(idReport); +function formSetEditReport(idReport) { + var report = { + 'type': ReportPlugin.defaultReportType, + 'format': ReportPlugin.defaultReportFormat, + 'description': '', + 'period': ReportPlugin.defaultPeriod, + 'hour': ReportPlugin.defaultHour, + 'reports': [] + }; + + if (idReport > 0) { + report = ReportPlugin.reportList[idReport]; + $('#report_submit').val(ReportPlugin.updateReportString); + } + else { + $('#report_submit').val(ReportPlugin.createReportString); + } + + 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_hour').val(report.hour); + $('[name=report_format].' + report.type + ' option[value=' + report.format + ']').prop('selected', 'selected'); + + $('[name=reportsList] input').prop('checked', false); + + var key; + for (key in report.reports) { + $('.' + report.type + ' [report-unique-id=' + report.reports[key] + ']').prop('checked', 'checked'); + } + + updateReportParametersFunctions[report.type](report.parameters); + + $('#report_idreport').val(idReport); } -function getReportAjaxRequest(idReport, defaultApiMethod) -{ - var parameters = {}; - piwikHelper.lazyScrollTo(".entityContainer", 400); - parameters.module = 'API'; - parameters.method = defaultApiMethod; - if(idReport == 0) - { - parameters.method = 'PDFReports.addReport'; - } - parameters.format = 'json'; - return parameters; +function getReportAjaxRequest(idReport, defaultApiMethod) { + var parameters = {}; + piwikHelper.lazyScrollTo(".entityContainer", 400); + parameters.module = 'API'; + parameters.method = defaultApiMethod; + if (idReport == 0) { + parameters.method = 'PDFReports.addReport'; + } + parameters.format = 'json'; + return parameters; } -function toggleReportType(reportType) -{ - resetReportParametersFunctions[reportType](); - $('#report_type option').each(function(index, type) { - $('.'+$(type).val()).hide(); - }); - $('.'+reportType).show(); +function toggleReportType(reportType) { + resetReportParametersFunctions[reportType](); + $('#report_type option').each(function (index, type) { + $('.' + $(type).val()).hide(); + }); + $('.' + reportType).show(); } -function initManagePdf() -{ - // Click Add/Update Submit - $('#addEditReport').submit( function() { - var idReport = $('#report_idreport').val(); - var apiParameters = getReportAjaxRequest(idReport, 'PDFReports.updateReport'); - apiParameters.idReport = idReport; - apiParameters.description = $('#report_description').val(); - apiParameters.reportType = $('#report_type option:selected').val(); - apiParameters.reportFormat = $('[name=report_format].'+apiParameters.reportType+' option:selected').val(); - - var reports = []; - $('[name=reportsList].'+apiParameters.reportType+' input:checked').each(function() { - reports.push($(this).attr('report-unique-id')); - }); - if(reports.length > 0) - { - apiParameters.reports = reports; - } - - apiParameters.parameters = getReportParametersFunctions[apiParameters.reportType](); +function initManagePdf() { + // Click Add/Update Submit + $('#addEditReport').submit(function () { + var idReport = $('#report_idreport').val(); + var apiParameters = getReportAjaxRequest(idReport, 'PDFReports.updateReport'); + apiParameters.idReport = idReport; + apiParameters.description = $('#report_description').val(); + apiParameters.reportType = $('#report_type option:selected').val(); + apiParameters.reportFormat = $('[name=report_format].' + apiParameters.reportType + ' option:selected').val(); + + var reports = []; + $('[name=reportsList].' + apiParameters.reportType + ' input:checked').each(function () { + reports.push($(this).attr('report-unique-id')); + }); + if (reports.length > 0) { + apiParameters.reports = reports; + } + + apiParameters.parameters = getReportParametersFunctions[apiParameters.reportType](); var ajaxHandler = new ajaxHelper(); ajaxHandler.addParams(apiParameters, 'POST'); @@ -104,27 +95,27 @@ function initManagePdf() ajaxHandler.setLoadingElement(); ajaxHandler.send(true); return false; - }); - - // Email now - $('a[name=linkSendNow]').click(function(){ - var idReport = $(this).attr('idreport'); - var parameters = getReportAjaxRequest(idReport, 'PDFReports.sendReport'); - parameters.idReport = idReport; + }); + + // Email now + $('a[name=linkSendNow]').click(function () { + var idReport = $(this).attr('idreport'); + var parameters = getReportAjaxRequest(idReport, 'PDFReports.sendReport'); + parameters.idReport = idReport; var ajaxHandler = new ajaxHelper(); ajaxHandler.addParams(parameters, 'POST'); ajaxHandler.setLoadingElement(); ajaxHandler.send(true); }); - - // Delete Report - $('a[name=linkDeleteReport]').click(function(){ - var idReport = $(this).attr('id'); - function onDelete() - { - var parameters = getReportAjaxRequest(idReport, 'PDFReports.deleteReport'); - parameters.idReport = idReport; + + // Delete Report + $('a[name=linkDeleteReport]').click(function () { + var idReport = $(this).attr('id'); + + function onDelete() { + var parameters = getReportAjaxRequest(idReport, 'PDFReports.deleteReport'); + parameters.idReport = idReport; var ajaxHandler = new ajaxHelper(); ajaxHandler.addParams(parameters, 'POST'); @@ -132,34 +123,35 @@ function initManagePdf() ajaxHandler.setLoadingElement(); ajaxHandler.send(true); } - piwikHelper.modalConfirm( '#confirm', {yes: onDelete}); - }); - - // Edit Report click - $('a[name=linkEditReport]').click(function(){ - var idReport = $(this).attr('id'); - formSetEditReport( idReport ); - $('.entityAddContainer').show(); - $('#entityEditContainer').hide(); - }); - - // Switch Report Type - $('#report_type').change(function(){ - var reportType = $(this).val(); - toggleReportType(reportType); - }); - - // Add a Report click - $('#linkAddReport').click(function(){ - $('.entityAddContainer').show(); - $('#entityEditContainer').hide(); - formSetEditReport( idReport = 0 ); - }); - - // Cancel click - $('.entityCancelLink').click(function(){ - $('.entityAddContainer').hide(); - $('#entityEditContainer').show(); - piwikHelper.hideAjaxError(); - }).click(); + + piwikHelper.modalConfirm('#confirm', {yes: onDelete}); + }); + + // Edit Report click + $('a[name=linkEditReport]').click(function () { + var idReport = $(this).attr('id'); + formSetEditReport(idReport); + $('.entityAddContainer').show(); + $('#entityEditContainer').hide(); + }); + + // Switch Report Type + $('#report_type').change(function () { + var reportType = $(this).val(); + toggleReportType(reportType); + }); + + // Add a Report click + $('#linkAddReport').click(function () { + $('.entityAddContainer').show(); + $('#entityEditContainer').hide(); + formSetEditReport(idReport = 0); + }); + + // Cancel click + $('.entityCancelLink').click(function () { + $('.entityAddContainer').hide(); + $('#entityEditContainer').show(); + piwikHelper.hideAjaxError(); + }).click(); } diff --git a/plugins/PDFReports/templates/report_parameters.tpl b/plugins/PDFReports/templates/report_parameters.tpl index 053a686bed..1d2cb3c04a 100644 --- a/plugins/PDFReports/templates/report_parameters.tpl +++ b/plugins/PDFReports/templates/report_parameters.tpl @@ -1,99 +1,104 @@ - {'PDFReports_SendReportTo'|translate} - - - - -

- {'PDFReports_AlsoSendReportToTheseEmails'|translate}
- - + {'PDFReports_SendReportTo'|translate} + + + + +

+ {'PDFReports_AlsoSendReportToTheseEmails'|translate}
+ + - - {*PDFReports_AggregateReportsFormat should be named PDFReports_DisplayFormat*} - {'PDFReports_AggregateReportsFormat'|translate} - - - -
-
- - -
- + + {*PDFReports_AggregateReportsFormat should be named PDFReports_DisplayFormat*} + {'PDFReports_AggregateReportsFormat'|translate} + + + + +
+
+ + +
+ diff --git a/plugins/PrivacyManager/Controller.php b/plugins/PrivacyManager/Controller.php index f2cb8f099e..bb25d40173 100644 --- a/plugins/PrivacyManager/Controller.php +++ b/plugins/PrivacyManager/Controller.php @@ -16,290 +16,275 @@ class Piwik_PrivacyManager_Controller extends Piwik_Controller_Admin { - const ANONYMIZE_IP_PLUGIN_NAME = "AnonymizeIP"; - const OPTION_LAST_DELETE_PIWIK_LOGS = "lastDelete_piwik_logs"; - - public function saveSettings() - { - Piwik::checkUserIsSuperUser(); - if ($_SERVER["REQUEST_METHOD"] == "POST") - { - $this->checkTokenInUrl(); - switch (Piwik_Common::getRequestVar('form')) - { - case("formMaskLength"): - $this->handlePluginState(Piwik_Common::getRequestVar("anonymizeIPEnable", 0)); - $trackerConfig = Piwik_Config::getInstance()->Tracker; - $trackerConfig['ip_address_mask_length'] = Piwik_Common::getRequestVar("maskLength", 1); - Piwik_Config::getInstance()->Tracker = $trackerConfig; - Piwik_Config::getInstance()->forceSave(); - break; - - case("formDeleteSettings"): - $settings = $this->getPurgeSettingsFromRequest(); - Piwik_PrivacyManager::savePurgeDataSettings($settings); - break; - - default: //do nothing - break; - } - } - - return $this->redirectToIndex('PrivacyManager', 'privacySettings', null, null, null, array('updated' => 1)); - } - - /** - * Utility function. Gets the delete logs/reports settings from the request and uses - * them to populate config arrays. - * - * @return array An array containing the data deletion settings. - */ - private function getPurgeSettingsFromRequest() - { - $settings = array(); - - // delete logs settings - $settings['delete_logs_enable'] = Piwik_Common::getRequestVar("deleteEnable", 0); - $settings['delete_logs_schedule_lowest_interval'] = Piwik_Common::getRequestVar("deleteLowestInterval", 7); - $settings['delete_logs_older_than'] = ((int)Piwik_Common::getRequestVar("deleteOlderThan", 180) < 1) ? - 1 : Piwik_Common::getRequestVar("deleteOlderThan", 180); - - // delete reports settings - $settings['delete_reports_enable'] = Piwik_Common::getRequestVar("deleteReportsEnable", 0); - $deleteReportsOlderThan = Piwik_Common::getRequestVar("deleteReportsOlderThan", 3); - $settings['delete_reports_older_than'] = $deleteReportsOlderThan < 3 ? 3 : $deleteReportsOlderThan; - $settings['delete_reports_keep_basic_metrics'] = Piwik_Common::getRequestVar("deleteReportsKeepBasic", 0); - $settings['delete_reports_keep_day_reports'] = Piwik_Common::getRequestVar("deleteReportsKeepDay", 0); - $settings['delete_reports_keep_week_reports'] = Piwik_Common::getRequestVar("deleteReportsKeepWeek", 0); - $settings['delete_reports_keep_month_reports'] = Piwik_Common::getRequestVar("deleteReportsKeepMonth", 0); - $settings['delete_reports_keep_year_reports'] = Piwik_Common::getRequestVar("deleteReportsKeepYear", 0); - $settings['delete_reports_keep_range_reports'] = Piwik_Common::getRequestVar("deleteReportsKeepRange", 0); - $settings['delete_reports_keep_segment_reports'] = Piwik_Common::getRequestVar("deleteReportsKeepSegments", 0); - - $settings['delete_logs_max_rows_per_query'] = Piwik_PrivacyManager::DEFAULT_MAX_ROWS_PER_QUERY; - - return $settings; - } - - /** - * Echo's an HTML chunk describing the current database size, and the estimated space - * savings after the scheduled data purge is run. - */ - public function getDatabaseSize() - { - Piwik::checkUserIsSuperUser(); - $view = Piwik_View::factory('databaseSize'); - - $forceEstimate = Piwik_Common::getRequestVar('forceEstimate', 0); - $view->dbStats = $this->getDeleteDBSizeEstimate($getSettingsFromQuery = true, $forceEstimate); - $view->language = Piwik_LanguagesManager::getLanguageCodeForCurrentUser(); - - echo $view->render(); - } - - /** - * Returns true if server side DoNotTrack support is enabled, false if otherwise. - * - * @return bool - */ - public static function isDntSupported() - { - return Piwik_PluginsManager::getInstance()->isPluginActivated('DoNotTrack'); - } - - public function privacySettings() - { - Piwik::checkUserHasSomeAdminAccess(); - $view = Piwik_View::factory('privacySettings'); - - if (Piwik::isUserIsSuperUser()) - { - $view->deleteData = $this->getDeleteDataInfo(); - $view->anonymizeIP = $this->getAnonymizeIPInfo(); - $view->dntSupport = self::isDntSupported(); - $view->canDeleteLogActions = Piwik::isLockPrivilegeGranted(); - $view->dbUser = Piwik_Config::getInstance()->database['username']; - } - $view->language = Piwik_LanguagesManager::getLanguageCodeForCurrentUser(); - - if (!Piwik_Config::getInstance()->isFileWritable()) - { - $view->configFileNotWritable = true; - } - - $this->setBasicVariablesView($view); - $view->menu = Piwik_GetAdminMenu(); - - echo $view->render(); - } - - /** - * Executes a data purge, deleting log data and report data using the current config - * options. Echo's the result of getDatabaseSize after purging. - */ - public function executeDataPurge() - { - Piwik::checkUserIsSuperUser(); - $this->checkTokenInUrl(); - - // if the request isn't a POST, redirect to index - if ($_SERVER["REQUEST_METHOD"] != "POST" - && !Piwik_Common::isPhpCliMode()) - { - return $this->redirectToIndex('PrivacyManager', 'privacySettings'); - } - - $settings = Piwik_PrivacyManager::getPurgeDataSettings(); - if ($settings['delete_logs_enable']) - { - $logDataPurger = Piwik_PrivacyManager_LogDataPurger::make($settings); - $logDataPurger->purgeData(); - } - if ($settings['delete_reports_enable']) - { - $reportsPurger = Piwik_PrivacyManager_ReportsPurger::make( - $settings, Piwik_PrivacyManager::getAllMetricsToKeep()); - $reportsPurger->purgeData(true); - } - } - - protected function getDeleteDBSizeEstimate( $getSettingsFromQuery = false, $forceEstimate = false ) - { - // get the purging settings & create two purger instances - if ($getSettingsFromQuery) - { - $settings = $this->getPurgeSettingsFromRequest(); - } - else - { - $settings = Piwik_PrivacyManager::getPurgeDataSettings(); - } - - $doDatabaseSizeEstimate = Piwik_Config::getInstance()->Deletelogs['enable_auto_database_size_estimate']; - - // determine the DB size & purged DB size - $metadataProvider = new Piwik_DBStats_MySQLMetadataProvider(); - $tableStatuses = $metadataProvider->getAllTablesStatus(); - - $totalBytes = 0; - foreach ($tableStatuses as $status) - { - $totalBytes += $status['Data_length'] + $status['Index_length']; - } - - $result = array( - 'currentSize' => Piwik::getPrettySizeFromBytes($totalBytes) - ); - - // if the db size estimate feature is enabled, get the estimate - if ($doDatabaseSizeEstimate || $forceEstimate == 1) - { - // maps tables whose data will be deleted with number of rows that will be deleted - // if a value is -1, it means the table will be dropped. - $deletedDataSummary = Piwik_PrivacyManager::getPurgeEstimate($settings); - - $totalAfterPurge = $totalBytes; - foreach ($tableStatuses as $status) - { - $tableName = $status['Name']; - if (isset($deletedDataSummary[$tableName])) - { - $tableTotalBytes = $status['Data_length'] + $status['Index_length']; - - // if dropping the table - if ($deletedDataSummary[$tableName] === Piwik_PrivacyManager_ReportsPurger::DROP_TABLE) - { - $totalAfterPurge -= $tableTotalBytes; - } - else // if just deleting rows - { - if($status['Rows'] > 0) { - $totalAfterPurge -= ($tableTotalBytes / $status['Rows']) * $deletedDataSummary[$tableName]; - } - } - } - } - - $result['sizeAfterPurge'] = Piwik::getPrettySizeFromBytes($totalAfterPurge); - $result['spaceSaved'] = Piwik::getPrettySizeFromBytes($totalBytes - $totalAfterPurge); - } - - return $result; - } - - protected function getAnonymizeIPInfo() - { - Piwik::checkUserIsSuperUser(); - $anonymizeIP = array(); - - Piwik_PluginsManager::getInstance()->loadPlugin(self::ANONYMIZE_IP_PLUGIN_NAME); - - $anonymizeIP["name"] = self::ANONYMIZE_IP_PLUGIN_NAME; - $anonymizeIP["enabled"] = Piwik_PluginsManager::getInstance()->isPluginActivated(self::ANONYMIZE_IP_PLUGIN_NAME); - $anonymizeIP["maskLength"] = Piwik_Config::getInstance()->Tracker['ip_address_mask_length']; - $anonymizeIP["info"] = Piwik_PluginsManager::getInstance()->getLoadedPlugin(self::ANONYMIZE_IP_PLUGIN_NAME)->getInformation(); - - return $anonymizeIP; - } - - protected function getDeleteDataInfo() - { - Piwik::checkUserIsSuperUser(); - $deleteDataInfos = array(); - $taskScheduler = new Piwik_TaskScheduler(); - $deleteDataInfos["config"] = Piwik_PrivacyManager::getPurgeDataSettings(); - $deleteDataInfos["deleteTables"] = - "
".implode(", ", Piwik_PrivacyManager_LogDataPurger::getDeleteTableLogTables()); - - $scheduleTimetable = $taskScheduler->getScheduledTimeForMethod("Piwik_PrivacyManager", "deleteLogTables"); - - $optionTable = Piwik_GetOption(self::OPTION_LAST_DELETE_PIWIK_LOGS); - - //If task was already rescheduled, read time from taskTimetable. Else, calculate next possible runtime. - if (!empty($scheduleTimetable) && ($scheduleTimetable - time() > 0)) { - $nextPossibleSchedule = (int)$scheduleTimetable; - } else { - $date = Piwik_Date::factory("today"); - $nextPossibleSchedule = $date->addDay(1)->getTimestamp(); - } - - //deletion schedule did not run before - if (empty($optionTable)) { - $deleteDataInfos["lastRun"] = false; - - //next run ASAP (with next schedule run) - $date = Piwik_Date::factory("today"); - $deleteDataInfos["nextScheduleTime"] = $nextPossibleSchedule; - } else { - $deleteDataInfos["lastRun"] = $optionTable; - $deleteDataInfos["lastRunPretty"] = Piwik_Date::factory((int)$optionTable)->getLocalized('%day% %shortMonth% %longYear%'); - - //Calculate next run based on last run + interval - $nextScheduleRun = (int)($deleteDataInfos["lastRun"] + $deleteDataInfos["config"]["delete_logs_schedule_lowest_interval"] * 24 * 60 * 60); - - //is the calculated next run in the past? (e.g. plugin was disabled in the meantime or something) -> run ASAP - if (($nextScheduleRun - time()) <= 0) { - $deleteDataInfos["nextScheduleTime"] = $nextPossibleSchedule; - } else { - $deleteDataInfos["nextScheduleTime"] = $nextScheduleRun; - } - } - - $deleteDataInfos["nextRunPretty"] = Piwik::getPrettyTimeFromSeconds($deleteDataInfos["nextScheduleTime"] - time()); - - return $deleteDataInfos; - } - - protected function handlePluginState($state = 0) - { - $pluginController = new Piwik_CorePluginsAdmin_Controller(); - - if ($state == 1 && !Piwik_PluginsManager::getInstance()->isPluginActivated(self::ANONYMIZE_IP_PLUGIN_NAME)) { - $pluginController->activate($redirectAfter = false); - } elseif ($state == 0 && Piwik_PluginsManager::getInstance()->isPluginActivated(self::ANONYMIZE_IP_PLUGIN_NAME)) { - $pluginController->deactivate($redirectAfter = false); - } else { - //nothing to do - } - } + const ANONYMIZE_IP_PLUGIN_NAME = "AnonymizeIP"; + const OPTION_LAST_DELETE_PIWIK_LOGS = "lastDelete_piwik_logs"; + + public function saveSettings() + { + Piwik::checkUserIsSuperUser(); + if ($_SERVER["REQUEST_METHOD"] == "POST") { + $this->checkTokenInUrl(); + switch (Piwik_Common::getRequestVar('form')) { + case("formMaskLength"): + $this->handlePluginState(Piwik_Common::getRequestVar("anonymizeIPEnable", 0)); + $trackerConfig = Piwik_Config::getInstance()->Tracker; + $trackerConfig['ip_address_mask_length'] = Piwik_Common::getRequestVar("maskLength", 1); + Piwik_Config::getInstance()->Tracker = $trackerConfig; + Piwik_Config::getInstance()->forceSave(); + break; + + case("formDeleteSettings"): + $settings = $this->getPurgeSettingsFromRequest(); + Piwik_PrivacyManager::savePurgeDataSettings($settings); + break; + + default: //do nothing + break; + } + } + + return $this->redirectToIndex('PrivacyManager', 'privacySettings', null, null, null, array('updated' => 1)); + } + + /** + * Utility function. Gets the delete logs/reports settings from the request and uses + * them to populate config arrays. + * + * @return array An array containing the data deletion settings. + */ + private function getPurgeSettingsFromRequest() + { + $settings = array(); + + // delete logs settings + $settings['delete_logs_enable'] = Piwik_Common::getRequestVar("deleteEnable", 0); + $settings['delete_logs_schedule_lowest_interval'] = Piwik_Common::getRequestVar("deleteLowestInterval", 7); + $settings['delete_logs_older_than'] = ((int)Piwik_Common::getRequestVar("deleteOlderThan", 180) < 1) ? + 1 : Piwik_Common::getRequestVar("deleteOlderThan", 180); + + // delete reports settings + $settings['delete_reports_enable'] = Piwik_Common::getRequestVar("deleteReportsEnable", 0); + $deleteReportsOlderThan = Piwik_Common::getRequestVar("deleteReportsOlderThan", 3); + $settings['delete_reports_older_than'] = $deleteReportsOlderThan < 3 ? 3 : $deleteReportsOlderThan; + $settings['delete_reports_keep_basic_metrics'] = Piwik_Common::getRequestVar("deleteReportsKeepBasic", 0); + $settings['delete_reports_keep_day_reports'] = Piwik_Common::getRequestVar("deleteReportsKeepDay", 0); + $settings['delete_reports_keep_week_reports'] = Piwik_Common::getRequestVar("deleteReportsKeepWeek", 0); + $settings['delete_reports_keep_month_reports'] = Piwik_Common::getRequestVar("deleteReportsKeepMonth", 0); + $settings['delete_reports_keep_year_reports'] = Piwik_Common::getRequestVar("deleteReportsKeepYear", 0); + $settings['delete_reports_keep_range_reports'] = Piwik_Common::getRequestVar("deleteReportsKeepRange", 0); + $settings['delete_reports_keep_segment_reports'] = Piwik_Common::getRequestVar("deleteReportsKeepSegments", 0); + + $settings['delete_logs_max_rows_per_query'] = Piwik_PrivacyManager::DEFAULT_MAX_ROWS_PER_QUERY; + + return $settings; + } + + /** + * Echo's an HTML chunk describing the current database size, and the estimated space + * savings after the scheduled data purge is run. + */ + public function getDatabaseSize() + { + Piwik::checkUserIsSuperUser(); + $view = Piwik_View::factory('databaseSize'); + + $forceEstimate = Piwik_Common::getRequestVar('forceEstimate', 0); + $view->dbStats = $this->getDeleteDBSizeEstimate($getSettingsFromQuery = true, $forceEstimate); + $view->language = Piwik_LanguagesManager::getLanguageCodeForCurrentUser(); + + echo $view->render(); + } + + /** + * Returns true if server side DoNotTrack support is enabled, false if otherwise. + * + * @return bool + */ + public static function isDntSupported() + { + return Piwik_PluginsManager::getInstance()->isPluginActivated('DoNotTrack'); + } + + public function privacySettings() + { + Piwik::checkUserHasSomeAdminAccess(); + $view = Piwik_View::factory('privacySettings'); + + if (Piwik::isUserIsSuperUser()) { + $view->deleteData = $this->getDeleteDataInfo(); + $view->anonymizeIP = $this->getAnonymizeIPInfo(); + $view->dntSupport = self::isDntSupported(); + $view->canDeleteLogActions = Piwik::isLockPrivilegeGranted(); + $view->dbUser = Piwik_Config::getInstance()->database['username']; + } + $view->language = Piwik_LanguagesManager::getLanguageCodeForCurrentUser(); + + if (!Piwik_Config::getInstance()->isFileWritable()) { + $view->configFileNotWritable = true; + } + + $this->setBasicVariablesView($view); + $view->menu = Piwik_GetAdminMenu(); + + echo $view->render(); + } + + /** + * Executes a data purge, deleting log data and report data using the current config + * options. Echo's the result of getDatabaseSize after purging. + */ + public function executeDataPurge() + { + Piwik::checkUserIsSuperUser(); + $this->checkTokenInUrl(); + + // if the request isn't a POST, redirect to index + if ($_SERVER["REQUEST_METHOD"] != "POST" + && !Piwik_Common::isPhpCliMode() + ) { + return $this->redirectToIndex('PrivacyManager', 'privacySettings'); + } + + $settings = Piwik_PrivacyManager::getPurgeDataSettings(); + if ($settings['delete_logs_enable']) { + $logDataPurger = Piwik_PrivacyManager_LogDataPurger::make($settings); + $logDataPurger->purgeData(); + } + if ($settings['delete_reports_enable']) { + $reportsPurger = Piwik_PrivacyManager_ReportsPurger::make( + $settings, Piwik_PrivacyManager::getAllMetricsToKeep()); + $reportsPurger->purgeData(true); + } + } + + protected function getDeleteDBSizeEstimate($getSettingsFromQuery = false, $forceEstimate = false) + { + // get the purging settings & create two purger instances + if ($getSettingsFromQuery) { + $settings = $this->getPurgeSettingsFromRequest(); + } else { + $settings = Piwik_PrivacyManager::getPurgeDataSettings(); + } + + $doDatabaseSizeEstimate = Piwik_Config::getInstance()->Deletelogs['enable_auto_database_size_estimate']; + + // determine the DB size & purged DB size + $metadataProvider = new Piwik_DBStats_MySQLMetadataProvider(); + $tableStatuses = $metadataProvider->getAllTablesStatus(); + + $totalBytes = 0; + foreach ($tableStatuses as $status) { + $totalBytes += $status['Data_length'] + $status['Index_length']; + } + + $result = array( + 'currentSize' => Piwik::getPrettySizeFromBytes($totalBytes) + ); + + // if the db size estimate feature is enabled, get the estimate + if ($doDatabaseSizeEstimate || $forceEstimate == 1) { + // maps tables whose data will be deleted with number of rows that will be deleted + // if a value is -1, it means the table will be dropped. + $deletedDataSummary = Piwik_PrivacyManager::getPurgeEstimate($settings); + + $totalAfterPurge = $totalBytes; + foreach ($tableStatuses as $status) { + $tableName = $status['Name']; + if (isset($deletedDataSummary[$tableName])) { + $tableTotalBytes = $status['Data_length'] + $status['Index_length']; + + // if dropping the table + if ($deletedDataSummary[$tableName] === Piwik_PrivacyManager_ReportsPurger::DROP_TABLE) { + $totalAfterPurge -= $tableTotalBytes; + } else // if just deleting rows + { + if ($status['Rows'] > 0) { + $totalAfterPurge -= ($tableTotalBytes / $status['Rows']) * $deletedDataSummary[$tableName]; + } + } + } + } + + $result['sizeAfterPurge'] = Piwik::getPrettySizeFromBytes($totalAfterPurge); + $result['spaceSaved'] = Piwik::getPrettySizeFromBytes($totalBytes - $totalAfterPurge); + } + + return $result; + } + + protected function getAnonymizeIPInfo() + { + Piwik::checkUserIsSuperUser(); + $anonymizeIP = array(); + + Piwik_PluginsManager::getInstance()->loadPlugin(self::ANONYMIZE_IP_PLUGIN_NAME); + + $anonymizeIP["name"] = self::ANONYMIZE_IP_PLUGIN_NAME; + $anonymizeIP["enabled"] = Piwik_PluginsManager::getInstance()->isPluginActivated(self::ANONYMIZE_IP_PLUGIN_NAME); + $anonymizeIP["maskLength"] = Piwik_Config::getInstance()->Tracker['ip_address_mask_length']; + $anonymizeIP["info"] = Piwik_PluginsManager::getInstance()->getLoadedPlugin(self::ANONYMIZE_IP_PLUGIN_NAME)->getInformation(); + + return $anonymizeIP; + } + + protected function getDeleteDataInfo() + { + Piwik::checkUserIsSuperUser(); + $deleteDataInfos = array(); + $taskScheduler = new Piwik_TaskScheduler(); + $deleteDataInfos["config"] = Piwik_PrivacyManager::getPurgeDataSettings(); + $deleteDataInfos["deleteTables"] = + "
" . implode(", ", Piwik_PrivacyManager_LogDataPurger::getDeleteTableLogTables()); + + $scheduleTimetable = $taskScheduler->getScheduledTimeForMethod("Piwik_PrivacyManager", "deleteLogTables"); + + $optionTable = Piwik_GetOption(self::OPTION_LAST_DELETE_PIWIK_LOGS); + + //If task was already rescheduled, read time from taskTimetable. Else, calculate next possible runtime. + if (!empty($scheduleTimetable) && ($scheduleTimetable - time() > 0)) { + $nextPossibleSchedule = (int)$scheduleTimetable; + } else { + $date = Piwik_Date::factory("today"); + $nextPossibleSchedule = $date->addDay(1)->getTimestamp(); + } + + //deletion schedule did not run before + if (empty($optionTable)) { + $deleteDataInfos["lastRun"] = false; + + //next run ASAP (with next schedule run) + $date = Piwik_Date::factory("today"); + $deleteDataInfos["nextScheduleTime"] = $nextPossibleSchedule; + } else { + $deleteDataInfos["lastRun"] = $optionTable; + $deleteDataInfos["lastRunPretty"] = Piwik_Date::factory((int)$optionTable)->getLocalized('%day% %shortMonth% %longYear%'); + + //Calculate next run based on last run + interval + $nextScheduleRun = (int)($deleteDataInfos["lastRun"] + $deleteDataInfos["config"]["delete_logs_schedule_lowest_interval"] * 24 * 60 * 60); + + //is the calculated next run in the past? (e.g. plugin was disabled in the meantime or something) -> run ASAP + if (($nextScheduleRun - time()) <= 0) { + $deleteDataInfos["nextScheduleTime"] = $nextPossibleSchedule; + } else { + $deleteDataInfos["nextScheduleTime"] = $nextScheduleRun; + } + } + + $deleteDataInfos["nextRunPretty"] = Piwik::getPrettyTimeFromSeconds($deleteDataInfos["nextScheduleTime"] - time()); + + return $deleteDataInfos; + } + + protected function handlePluginState($state = 0) + { + $pluginController = new Piwik_CorePluginsAdmin_Controller(); + + if ($state == 1 && !Piwik_PluginsManager::getInstance()->isPluginActivated(self::ANONYMIZE_IP_PLUGIN_NAME)) { + $pluginController->activate($redirectAfter = false); + } elseif ($state == 0 && Piwik_PluginsManager::getInstance()->isPluginActivated(self::ANONYMIZE_IP_PLUGIN_NAME)) { + $pluginController->deactivate($redirectAfter = false); + } else { + //nothing to do + } + } } diff --git a/plugins/PrivacyManager/LogDataPurger.php b/plugins/PrivacyManager/LogDataPurger.php index da1b48591e..440f8f5e42 100755 --- a/plugins/PrivacyManager/LogDataPurger.php +++ b/plugins/PrivacyManager/LogDataPurger.php @@ -14,334 +14,313 @@ */ class Piwik_PrivacyManager_LogDataPurger { - const TEMP_TABLE_NAME = 'tmp_log_actions_to_keep'; - - /** - * The max set of rows each table scan select should query at one time. - */ - public static $selectSegmentSize = 100000; - - /** - * The number of days after which log entries are considered old. - */ - private $deleteLogsOlderThan; - - /** - * The number of rows to delete per DELETE query. - */ - private $maxRowsToDeletePerQuery; - - /** - * Constructor. - * - * @param int $deleteLogsOlderThan The number of days after which log entires are considered old. - * Visits and related data whose age is greater than this number - * will be purged. - * @param int $maxRowsToDeletePerQuery The maximum number of rows to delete in one query. Used to - * make sure log tables aren't locked for too long. - */ - public function __construct( $deleteLogsOlderThan, $maxRowsToDeletePerQuery ) - { - $this->deleteLogsOlderThan = $deleteLogsOlderThan; - $this->maxRowsToDeletePerQuery = $maxRowsToDeletePerQuery; - } - - /** - * Purges old data from the following tables: - * - log_visit - * - log_link_visit_action - * - log_conversion - * - log_conversion_item - * - log_action - */ - public function purgeData() - { - $maxIdVisit = $this->getDeleteIdVisitOffset(); - - // break if no ID was found (nothing to delete for given period) - if (empty($maxIdVisit)) - { - return; - } - - $logTables = self::getDeleteTableLogTables(); - - // delete data from log tables - $where = "WHERE idvisit <= ?"; - foreach ($logTables as $logTable) - { - // deleting from log_action must be handled differently, so we do it later - if ($logTable != Piwik_Common::prefixTable('log_action')) - { - Piwik_DeleteAllRows($logTable, $where, $this->maxRowsToDeletePerQuery, array($maxIdVisit)); - } - } - - // delete unused actions from the log_action table (but only if we can lock tables) - if (Piwik::isLockPrivilegeGranted()) - { - $this->purgeUnusedLogActions(); - } - else - { - $logMessage = get_class($this).": LOCK TABLES privilege not granted; skipping unused actions purge"; - Piwik::log($logMessage); - } - - // optimize table overhead after deletion - Piwik_OptimizeTables($logTables); - } - - /** - * Returns an array describing what data would be purged if purging were invoked. - * - * This function returns an array that maps table names with the number of rows - * that will be deleted. - * - * @return array - */ - public function getPurgeEstimate() - { - $result = array(); - - // deal w/ log tables that will be purged - $maxIdVisit = $this->getDeleteIdVisitOffset(); - if (!empty($maxIdVisit)) - { - foreach ($this->getDeleteTableLogTables() as $table) - { - // getting an estimate for log_action is not supported since it can take too long - if ($table != Piwik_Common::prefixTable('log_action')) - { - $rowCount = $this->getLogTableDeleteCount($table, $maxIdVisit); - if ($rowCount > 0) - { - $result[$table] = $rowCount; - } - } - } - } - - return $result; - } - - /** - * Safely delete all unused log_action rows. - */ - private function purgeUnusedLogActions() - { - $this->createTempTable(); - - // get current max ID in log tables w/ idaction references. - $maxIds = $this->getMaxIdsInLogTables(); - - // do large insert (inserting everything before maxIds) w/o locking tables... - $this->insertActionsToKeep($maxIds, $deleteOlderThanMax = true); - - // ... then do small insert w/ locked tables to minimize the amount of time tables are locked. - $this->lockLogTables(); - $this->insertActionsToKeep($maxIds, $deleteOlderThanMax = false); - - // delete before unlocking tables so there's no chance a new log row that references an - // unused action will be inserted. - $this->deleteUnusedActions(); - $this->unlockLogTables(); - } - - /** - * get highest idVisit to delete rows from - * @return string - */ - private function getDeleteIdVisitOffset() - { - $logVisit = Piwik_Common::prefixTable("log_visit"); - - // get max idvisit - $maxIdVisit = Piwik_FetchOne("SELECT MAX(idvisit) FROM $logVisit"); - if (empty($maxIdVisit)) - { - return false; - } - - // select highest idvisit to delete from - $dateStart = Piwik_Date::factory("today")->subDay($this->deleteLogsOlderThan); - $sql = "SELECT idvisit + const TEMP_TABLE_NAME = 'tmp_log_actions_to_keep'; + + /** + * The max set of rows each table scan select should query at one time. + */ + public static $selectSegmentSize = 100000; + + /** + * The number of days after which log entries are considered old. + */ + private $deleteLogsOlderThan; + + /** + * The number of rows to delete per DELETE query. + */ + private $maxRowsToDeletePerQuery; + + /** + * Constructor. + * + * @param int $deleteLogsOlderThan The number of days after which log entires are considered old. + * Visits and related data whose age is greater than this number + * will be purged. + * @param int $maxRowsToDeletePerQuery The maximum number of rows to delete in one query. Used to + * make sure log tables aren't locked for too long. + */ + public function __construct($deleteLogsOlderThan, $maxRowsToDeletePerQuery) + { + $this->deleteLogsOlderThan = $deleteLogsOlderThan; + $this->maxRowsToDeletePerQuery = $maxRowsToDeletePerQuery; + } + + /** + * Purges old data from the following tables: + * - log_visit + * - log_link_visit_action + * - log_conversion + * - log_conversion_item + * - log_action + */ + public function purgeData() + { + $maxIdVisit = $this->getDeleteIdVisitOffset(); + + // break if no ID was found (nothing to delete for given period) + if (empty($maxIdVisit)) { + return; + } + + $logTables = self::getDeleteTableLogTables(); + + // delete data from log tables + $where = "WHERE idvisit <= ?"; + foreach ($logTables as $logTable) { + // deleting from log_action must be handled differently, so we do it later + if ($logTable != Piwik_Common::prefixTable('log_action')) { + Piwik_DeleteAllRows($logTable, $where, $this->maxRowsToDeletePerQuery, array($maxIdVisit)); + } + } + + // delete unused actions from the log_action table (but only if we can lock tables) + if (Piwik::isLockPrivilegeGranted()) { + $this->purgeUnusedLogActions(); + } else { + $logMessage = get_class($this) . ": LOCK TABLES privilege not granted; skipping unused actions purge"; + Piwik::log($logMessage); + } + + // optimize table overhead after deletion + Piwik_OptimizeTables($logTables); + } + + /** + * Returns an array describing what data would be purged if purging were invoked. + * + * This function returns an array that maps table names with the number of rows + * that will be deleted. + * + * @return array + */ + public function getPurgeEstimate() + { + $result = array(); + + // deal w/ log tables that will be purged + $maxIdVisit = $this->getDeleteIdVisitOffset(); + if (!empty($maxIdVisit)) { + foreach ($this->getDeleteTableLogTables() as $table) { + // getting an estimate for log_action is not supported since it can take too long + if ($table != Piwik_Common::prefixTable('log_action')) { + $rowCount = $this->getLogTableDeleteCount($table, $maxIdVisit); + if ($rowCount > 0) { + $result[$table] = $rowCount; + } + } + } + } + + return $result; + } + + /** + * Safely delete all unused log_action rows. + */ + private function purgeUnusedLogActions() + { + $this->createTempTable(); + + // get current max ID in log tables w/ idaction references. + $maxIds = $this->getMaxIdsInLogTables(); + + // do large insert (inserting everything before maxIds) w/o locking tables... + $this->insertActionsToKeep($maxIds, $deleteOlderThanMax = true); + + // ... then do small insert w/ locked tables to minimize the amount of time tables are locked. + $this->lockLogTables(); + $this->insertActionsToKeep($maxIds, $deleteOlderThanMax = false); + + // delete before unlocking tables so there's no chance a new log row that references an + // unused action will be inserted. + $this->deleteUnusedActions(); + $this->unlockLogTables(); + } + + /** + * get highest idVisit to delete rows from + * @return string + */ + private function getDeleteIdVisitOffset() + { + $logVisit = Piwik_Common::prefixTable("log_visit"); + + // get max idvisit + $maxIdVisit = Piwik_FetchOne("SELECT MAX(idvisit) FROM $logVisit"); + if (empty($maxIdVisit)) { + return false; + } + + // select highest idvisit to delete from + $dateStart = Piwik_Date::factory("today")->subDay($this->deleteLogsOlderThan); + $sql = "SELECT idvisit FROM $logVisit - WHERE '".$dateStart->toString('Y-m-d H:i:s')."' > visit_last_action_time + WHERE '" . $dateStart->toString('Y-m-d H:i:s') . "' > visit_last_action_time AND idvisit <= ? AND idvisit > ? ORDER BY idvisit DESC LIMIT 1"; - - return Piwik_SegmentedFetchFirst($sql, $maxIdVisit, 0, -self::$selectSegmentSize); - } - - private function getLogTableDeleteCount( $table, $maxIdVisit ) - { - $sql = "SELECT COUNT(*) FROM $table WHERE idvisit <= ?"; - return (int)Piwik_FetchOne($sql, array($maxIdVisit)); - } - - private function createTempTable() - { - $sql = "CREATE TEMPORARY TABLE ".Piwik_Common::prefixTable(self::TEMP_TABLE_NAME)." ( + + return Piwik_SegmentedFetchFirst($sql, $maxIdVisit, 0, -self::$selectSegmentSize); + } + + private function getLogTableDeleteCount($table, $maxIdVisit) + { + $sql = "SELECT COUNT(*) FROM $table WHERE idvisit <= ?"; + return (int)Piwik_FetchOne($sql, array($maxIdVisit)); + } + + private function createTempTable() + { + $sql = "CREATE TEMPORARY TABLE " . Piwik_Common::prefixTable(self::TEMP_TABLE_NAME) . " ( idaction INT(11), PRIMARY KEY (idaction) )"; - Piwik_Query($sql); - } - - private function getMaxIdsInLogTables() - { - $tables = array('log_conversion', 'log_link_visit_action', 'log_visit', 'log_conversion_item'); - $idColumns = $this->getTableIdColumns(); - - $result = array(); - foreach ($tables as $table) - { - $idCol = $idColumns[$table]; - $result[$table] = Piwik_FetchOne("SELECT MAX($idCol) FROM ".Piwik_Common::prefixTable($table)); - } - - return $result; - } - - private function insertActionsToKeep( $maxIds, $olderThan = true ) - { - $tempTableName = Piwik_Common::prefixTable(self::TEMP_TABLE_NAME); - - $idColumns = $this->getTableIdColumns(); - foreach ($this->getIdActionColumns() as $table => $columns) - { - $idCol = $idColumns[$table]; - - foreach ($columns as $col) - { - $select = "SELECT $col FROM ".Piwik_Common::prefixTable($table)." WHERE $idCol >= ? AND $idCol < ?"; - $sql = "INSERT IGNORE INTO $tempTableName $select"; - - if ($olderThan) - { - $start = 0; - $finish = $maxIds[$table]; - } - else - { - $start = $maxIds[$table]; - $finish = Piwik_FetchOne("SELECT MAX($idCol) FROM ".Piwik_Common::prefixTable($table)); - } - - Piwik_SegmentedQuery($sql, $start, $finish, self::$selectSegmentSize); - } - } - - // allow code to be executed after data is inserted. for concurrency testing purposes. - if ($olderThan) - { - Piwik_PostEvent("LogDataPurger.actionsToKeepInserted.olderThan"); - } - else - { - Piwik_PostEvent("LogDataPurger.actionsToKeepInserted.newerThan"); - } - } - - private function lockLogTables() - { - Piwik_LockTables( - $readLocks = Piwik_Common::prefixTables('log_conversion', - 'log_link_visit_action', - 'log_visit', - 'log_conversion_item'), - $writeLocks = Piwik_Common::prefixTables('log_action') - ); - } - - private function unlockLogTables() - { - Piwik_UnlockAllTables(); - } - - private function deleteUnusedActions() - { - list($logActionTable, $tempTableName) = Piwik_Common::prefixTables("log_action", self::TEMP_TABLE_NAME); - - $deleteSql = "DELETE LOW_PRIORITY QUICK IGNORE $logActionTable + Piwik_Query($sql); + } + + private function getMaxIdsInLogTables() + { + $tables = array('log_conversion', 'log_link_visit_action', 'log_visit', 'log_conversion_item'); + $idColumns = $this->getTableIdColumns(); + + $result = array(); + foreach ($tables as $table) { + $idCol = $idColumns[$table]; + $result[$table] = Piwik_FetchOne("SELECT MAX($idCol) FROM " . Piwik_Common::prefixTable($table)); + } + + return $result; + } + + private function insertActionsToKeep($maxIds, $olderThan = true) + { + $tempTableName = Piwik_Common::prefixTable(self::TEMP_TABLE_NAME); + + $idColumns = $this->getTableIdColumns(); + foreach ($this->getIdActionColumns() as $table => $columns) { + $idCol = $idColumns[$table]; + + foreach ($columns as $col) { + $select = "SELECT $col FROM " . Piwik_Common::prefixTable($table) . " WHERE $idCol >= ? AND $idCol < ?"; + $sql = "INSERT IGNORE INTO $tempTableName $select"; + + if ($olderThan) { + $start = 0; + $finish = $maxIds[$table]; + } else { + $start = $maxIds[$table]; + $finish = Piwik_FetchOne("SELECT MAX($idCol) FROM " . Piwik_Common::prefixTable($table)); + } + + Piwik_SegmentedQuery($sql, $start, $finish, self::$selectSegmentSize); + } + } + + // allow code to be executed after data is inserted. for concurrency testing purposes. + if ($olderThan) { + Piwik_PostEvent("LogDataPurger.actionsToKeepInserted.olderThan"); + } else { + Piwik_PostEvent("LogDataPurger.actionsToKeepInserted.newerThan"); + } + } + + private function lockLogTables() + { + Piwik_LockTables( + $readLocks = Piwik_Common::prefixTables('log_conversion', + 'log_link_visit_action', + 'log_visit', + 'log_conversion_item'), + $writeLocks = Piwik_Common::prefixTables('log_action') + ); + } + + private function unlockLogTables() + { + Piwik_UnlockAllTables(); + } + + private function deleteUnusedActions() + { + list($logActionTable, $tempTableName) = Piwik_Common::prefixTables("log_action", self::TEMP_TABLE_NAME); + + $deleteSql = "DELETE LOW_PRIORITY QUICK IGNORE $logActionTable FROM $logActionTable LEFT JOIN $tempTableName tmp ON tmp.idaction = $logActionTable.idaction WHERE tmp.idaction IS NULL"; - - Piwik_Query($deleteSql); - } - - private function getIdActionColumns() - { - return array( - 'log_link_visit_action' => array( 'idaction_url', - 'idaction_url_ref', - 'idaction_name', - 'idaction_name_ref' ), - - 'log_conversion' => array( 'idaction_url' ), - - 'log_visit' => array( 'visit_exit_idaction_url', - 'visit_exit_idaction_name', - 'visit_entry_idaction_url', - 'visit_entry_idaction_name' ), - - 'log_conversion_item' => array( 'idaction_sku', - 'idaction_name', - 'idaction_category', - 'idaction_category2', - 'idaction_category3', - 'idaction_category4', - 'idaction_category5' ) - ); - } - - private function getTableIdColumns() - { - return array( - 'log_link_visit_action' => 'idlink_va', - 'log_conversion' => 'idvisit', - 'log_visit' => 'idvisit', - 'log_conversion_item' => 'idvisit' - ); - } - - // let's hardcode, since these are not dynamically created tables - public static function getDeleteTableLogTables() - { - $result = Piwik_Common::prefixTables('log_conversion', - 'log_link_visit_action', - 'log_visit', - 'log_conversion_item'); - if (Piwik::isLockPrivilegeGranted()) - { - $result[] = Piwik_Common::prefixTable('log_action'); - } - return $result; - } - - /** - * Utility function. Creates a new instance of LogDataPurger with the supplied array - * of settings. - * - * $settings must contain values for the following keys: - * - 'delete_logs_older_than': The number of days after which log entries are considered - * old. - * - 'delete_logs_max_rows_per_query': Max number of rows to DELETE in one query. - * - * @param array $settings Array of settings - * @param bool $useRealTable - * @return Piwik_PrivacyManager_LogDataPurger - */ - public static function make( $settings, $useRealTable = false ) - { - return new Piwik_PrivacyManager_LogDataPurger( - $settings['delete_logs_older_than'], - $settings['delete_logs_max_rows_per_query'] - ); - } + + Piwik_Query($deleteSql); + } + + private function getIdActionColumns() + { + return array( + 'log_link_visit_action' => array('idaction_url', + 'idaction_url_ref', + 'idaction_name', + 'idaction_name_ref'), + + 'log_conversion' => array('idaction_url'), + + 'log_visit' => array('visit_exit_idaction_url', + 'visit_exit_idaction_name', + 'visit_entry_idaction_url', + 'visit_entry_idaction_name'), + + 'log_conversion_item' => array('idaction_sku', + 'idaction_name', + 'idaction_category', + 'idaction_category2', + 'idaction_category3', + 'idaction_category4', + 'idaction_category5') + ); + } + + private function getTableIdColumns() + { + return array( + 'log_link_visit_action' => 'idlink_va', + 'log_conversion' => 'idvisit', + 'log_visit' => 'idvisit', + 'log_conversion_item' => 'idvisit' + ); + } + + // let's hardcode, since these are not dynamically created tables + public static function getDeleteTableLogTables() + { + $result = Piwik_Common::prefixTables('log_conversion', + 'log_link_visit_action', + 'log_visit', + 'log_conversion_item'); + if (Piwik::isLockPrivilegeGranted()) { + $result[] = Piwik_Common::prefixTable('log_action'); + } + return $result; + } + + /** + * Utility function. Creates a new instance of LogDataPurger with the supplied array + * of settings. + * + * $settings must contain values for the following keys: + * - 'delete_logs_older_than': The number of days after which log entries are considered + * old. + * - 'delete_logs_max_rows_per_query': Max number of rows to DELETE in one query. + * + * @param array $settings Array of settings + * @param bool $useRealTable + * @return Piwik_PrivacyManager_LogDataPurger + */ + public static function make($settings, $useRealTable = false) + { + return new Piwik_PrivacyManager_LogDataPurger( + $settings['delete_logs_older_than'], + $settings['delete_logs_max_rows_per_query'] + ); + } } diff --git a/plugins/PrivacyManager/PrivacyManager.php b/plugins/PrivacyManager/PrivacyManager.php index 85c7a8f5ce..840fe5cfc2 100644 --- a/plugins/PrivacyManager/PrivacyManager.php +++ b/plugins/PrivacyManager/PrivacyManager.php @@ -29,70 +29,70 @@ class Piwik_PrivacyManager extends Piwik_Plugin const OPTION_LAST_DELETE_PIWIK_REPORTS = 'lastDelete_piwik_reports'; const OPTION_LAST_DELETE_PIWIK_LOGS_INITIAL = "lastDelete_piwik_logs_initial"; const DEFAULT_MAX_ROWS_PER_QUERY = 100000; - + // default config options for data purging feature public static $defaultPurgeDataOptions = array( - 'delete_logs_enable' => 0, - 'delete_logs_schedule_lowest_interval' => 7, - 'delete_logs_older_than' => 180, - 'delete_logs_max_rows_per_query' => self::DEFAULT_MAX_ROWS_PER_QUERY, - 'delete_reports_enable' => 0, - 'delete_reports_older_than' => 12, - 'delete_reports_keep_basic_metrics' => 1, - 'delete_reports_keep_day_reports' => 0, - 'delete_reports_keep_week_reports' => 0, - 'delete_reports_keep_month_reports' => 1, - 'delete_reports_keep_year_reports' => 1, - 'delete_reports_keep_range_reports' => 0, - 'delete_reports_keep_segment_reports' => 0, - ); - + 'delete_logs_enable' => 0, + 'delete_logs_schedule_lowest_interval' => 7, + 'delete_logs_older_than' => 180, + 'delete_logs_max_rows_per_query' => self::DEFAULT_MAX_ROWS_PER_QUERY, + 'delete_reports_enable' => 0, + 'delete_reports_older_than' => 12, + 'delete_reports_keep_basic_metrics' => 1, + 'delete_reports_keep_day_reports' => 0, + 'delete_reports_keep_week_reports' => 0, + 'delete_reports_keep_month_reports' => 1, + 'delete_reports_keep_year_reports' => 1, + 'delete_reports_keep_range_reports' => 0, + 'delete_reports_keep_segment_reports' => 0, + ); + public function getInformation() { return array( - 'description' => Piwik_Translate('PrivacyManager_PluginDescription'), - 'author' => 'Piwik', + 'description' => Piwik_Translate('PrivacyManager_PluginDescription'), + 'author' => 'Piwik', 'author_homepage' => 'http://piwik.org/', - 'version' => Piwik_Version::VERSION, + 'version' => Piwik_Version::VERSION, ); } public function getListHooksRegistered() { return array( - 'AssetManager.getJsFiles' => 'getJsFiles', - 'AdminMenu.add' => 'addMenu', + 'AssetManager.getJsFiles' => 'getJsFiles', + 'AdminMenu.add' => 'addMenu', 'TaskScheduler.getScheduledTasks' => 'getScheduledTasks', ); } - /** - * @param Piwik_Event_Notification $notification notification object - */ - function getScheduledTasks($notification) - { - $tasks = &$notification->getNotificationObject(); - - // both tasks are low priority so they will execute after most others, but not lowest, so - // they will execute before the optimize tables task - - $purgeReportDataTask = new Piwik_ScheduledTask( - $this, 'deleteReportData', null, new Piwik_ScheduledTime_Daily(), Piwik_ScheduledTask::LOW_PRIORITY - ); - $tasks[] = $purgeReportDataTask; - - $purgeLogDataTask = new Piwik_ScheduledTask( - $this, 'deleteLogData', null, new Piwik_ScheduledTime_Daily(), Piwik_ScheduledTask::LOW_PRIORITY - ); - $tasks[] = $purgeLogDataTask; - } - - /** - * @param Piwik_Event_Notification $notification notification object - */ + /** + * @param Piwik_Event_Notification $notification notification object + */ + function getScheduledTasks($notification) + { + $tasks = & $notification->getNotificationObject(); + + // both tasks are low priority so they will execute after most others, but not lowest, so + // they will execute before the optimize tables task + + $purgeReportDataTask = new Piwik_ScheduledTask( + $this, 'deleteReportData', null, new Piwik_ScheduledTime_Daily(), Piwik_ScheduledTask::LOW_PRIORITY + ); + $tasks[] = $purgeReportDataTask; + + $purgeLogDataTask = new Piwik_ScheduledTask( + $this, 'deleteLogData', null, new Piwik_ScheduledTime_Daily(), Piwik_ScheduledTask::LOW_PRIORITY + ); + $tasks[] = $purgeLogDataTask; + } + + /** + * @param Piwik_Event_Notification $notification notification object + */ function getJsFiles($notification) { - $jsFiles = &$notification->getNotificationObject(); + $jsFiles = & $notification->getNotificationObject(); $jsFiles[] = "plugins/PrivacyManager/templates/privacySettings.js"; } @@ -100,131 +100,118 @@ class Piwik_PrivacyManager extends Piwik_Plugin function addMenu() { Piwik_AddAdminMenu('PrivacyManager_MenuPrivacySettings', - array('module' => 'PrivacyManager', 'action' => 'privacySettings'), - Piwik::isUserHasSomeAdminAccess(), - $order = 7); + array('module' => 'PrivacyManager', 'action' => 'privacySettings'), + Piwik::isUserHasSomeAdminAccess(), + $order = 7); + } + + /** + * Returns the settings for the data purging feature. + * + * @return array + */ + public static function getPurgeDataSettings() + { + $settings = array(); + + // load settings from ini config + try { + $oldSettings = array( + 'enable_auto_database_size_estimate', + + // backwards compatibility: load old values in ini config if present + 'delete_logs_enable', + 'delete_logs_schedule_lowest_interval', + 'delete_logs_older_than', + ); + + $deleteLogsSettings = Piwik_Config::getInstance()->Deletelogs; + foreach ($oldSettings as $settingName) { + $settings[$settingName] = $deleteLogsSettings[$settingName]; + } + } catch (Exception $e) { + // ignore + } + + // load the settings for the data purging settings + foreach (self::$defaultPurgeDataOptions as $optionName => $defaultValue) { + $value = Piwik_GetOption($optionName); + if ($value !== false) { + $settings[$optionName] = $value; + } else { + // if the option hasn't been set/created, use the default value + if (!isset($settings[$optionName])) { + $settings[$optionName] = $defaultValue; + } + + // option is not saved in the DB, so save it now + Piwik_SetOption($optionName, $settings[$optionName]); + } + } + + return $settings; + } + + /** + * Saves the supplied data purging settings. + * + * @param array $settings The settings to save. + */ + public static function savePurgeDataSettings($settings) + { + $plugin = Piwik_PluginsManager::getInstance()->getLoadedPlugin('PrivacyManager'); + + foreach (self::$defaultPurgeDataOptions as $optionName => $defaultValue) { + if (isset($settings[$optionName])) { + Piwik_SetOption($optionName, $settings[$optionName]); + } + } } - - /** - * Returns the settings for the data purging feature. - * - * @return array - */ - public static function getPurgeDataSettings() - { - $settings = array(); - - // load settings from ini config - try - { - $oldSettings = array( - 'enable_auto_database_size_estimate', - - // backwards compatibility: load old values in ini config if present - 'delete_logs_enable', - 'delete_logs_schedule_lowest_interval', - 'delete_logs_older_than', - ); - - $deleteLogsSettings = Piwik_Config::getInstance()->Deletelogs; - foreach ($oldSettings as $settingName) - { - $settings[$settingName] = $deleteLogsSettings[$settingName]; - } - } - catch (Exception $e) - { - // ignore - } - - // load the settings for the data purging settings - foreach (self::$defaultPurgeDataOptions as $optionName => $defaultValue) - { - $value = Piwik_GetOption($optionName); - if ($value !== false) - { - $settings[$optionName] = $value; - } - else - { - // if the option hasn't been set/created, use the default value - if (!isset($settings[$optionName])) - { - $settings[$optionName] = $defaultValue; - } - - // option is not saved in the DB, so save it now - Piwik_SetOption($optionName, $settings[$optionName]); - } - } - - return $settings; - } - - /** - * Saves the supplied data purging settings. - * - * @param array $settings The settings to save. - */ - public static function savePurgeDataSettings( $settings ) - { - $plugin = Piwik_PluginsManager::getInstance()->getLoadedPlugin('PrivacyManager'); - - foreach (self::$defaultPurgeDataOptions as $optionName => $defaultValue) - { - if (isset($settings[$optionName])) - { - Piwik_SetOption($optionName, $settings[$optionName]); - } - } - } - + /** - * Deletes old archived data (reports & metrics). - * - * Archive tables are not optimized after, as that is handled by a separate scheduled task - * in CoreAdminHome. This is a scheduled task and will only execute every N days. The number + * Deletes old archived data (reports & metrics). + * + * Archive tables are not optimized after, as that is handled by a separate scheduled task + * in CoreAdminHome. This is a scheduled task and will only execute every N days. The number * of days is determined by the delete_logs_schedule_lowest_interval config option. - * + * * If delete_reports_enable is set to 1, old archive data is deleted. The following * config options can tweak this behavior: * - delete_reports_older_than: The number of months after which archive data is considered * old. The current month is not considered when applying this * value. - * - delete_reports_keep_basic_metrics: If set to 1, keeps certain metric data. Right now, + * - delete_reports_keep_basic_metrics: If set to 1, keeps certain metric data. Right now, * all metric data is kept. * - delete_reports_keep_day_reports: If set to 1, keeps old daily reports. * - delete_reports_keep_week_reports: If set to 1, keeps old weekly reports. * - delete_reports_keep_month_reports: If set to 1, keeps old monthly reports. * - delete_reports_keep_year_reports: If set to 1, keeps old yearly reports. */ - public function deleteReportData() - { + public function deleteReportData() + { $settings = self::getPurgeDataSettings(); - + // Make sure, data deletion is enabled - if ($settings['delete_reports_enable'] == 0) - { + if ($settings['delete_reports_enable'] == 0) { return; } - + // make sure purging should run at this time (unless this is a forced purge) - if (!$this->shouldPurgeData($settings, self::OPTION_LAST_DELETE_PIWIK_REPORTS)) - { - return; + if (!$this->shouldPurgeData($settings, self::OPTION_LAST_DELETE_PIWIK_REPORTS)) { + return; } - + // set last run time Piwik_SetOption(self::OPTION_LAST_DELETE_PIWIK_REPORTS, Piwik_Date::factory('today')->getTimestamp()); - - Piwik_PrivacyManager_ReportsPurger::make($settings, self::getAllMetricsToKeep())->purgeData(); - } - + + Piwik_PrivacyManager_ReportsPurger::make($settings, self::getAllMetricsToKeep())->purgeData(); + } + /** * Deletes old log data based on the options set in the Deletelogs config * section. This is a scheduled task and will only execute every N days. The number * of days is determined by the delete_logs_schedule_lowest_interval config option. - * + * * If delete_logs_enable is set to 1, old data in the log_visit, log_conversion, * log_conversion_item and log_link_visit_action tables is deleted. The following * options can tweak this behavior: @@ -236,19 +223,17 @@ class Piwik_PrivacyManager extends Piwik_Plugin public function deleteLogData() { $settings = self::getPurgeDataSettings(); - + // Make sure, data deletion is enabled - if ($settings['delete_logs_enable'] == 0) - { + if ($settings['delete_logs_enable'] == 0) { return; } - + // make sure purging should run at this time - if (!$this->shouldPurgeData($settings, self::OPTION_LAST_DELETE_PIWIK_LOGS)) - { - return; + if (!$this->shouldPurgeData($settings, self::OPTION_LAST_DELETE_PIWIK_LOGS)) { + return; } - + /* * Tell the DB that log deletion has run BEFORE deletion is executed; * If deletion / table optimization exceeds execution time, other tasks maybe prevented of being executed @@ -256,169 +241,158 @@ class Piwik_PrivacyManager extends Piwik_Plugin */ $lastDeleteDate = Piwik_Date::factory("today")->getTimestamp(); Piwik_SetOption(self::OPTION_LAST_DELETE_PIWIK_LOGS, $lastDeleteDate); - + // execute the purge Piwik_PrivacyManager_LogDataPurger::make($settings)->purgeData(); } - + /** * Returns an array describing what data would be purged if both log data & report * purging is invoked. - * + * * The returned array maps table names with the number of rows that will be deleted. * If the table name is mapped with -1, the table will be dropped. - * + * * @param array $settings The config options to use in the estimate. If null, the real * options are used. * @return array */ - public static function getPurgeEstimate( $settings = null ) + public static function getPurgeEstimate($settings = null) + { + if (is_null($settings)) { + $settings = self::getPurgeDataSettings(); + } + + $result = array(); + + if ($settings['delete_logs_enable']) { + $logDataPurger = Piwik_PrivacyManager_LogDataPurger::make($settings); + $result = array_merge($result, $logDataPurger->getPurgeEstimate()); + } + + if ($settings['delete_reports_enable']) { + $reportsPurger = Piwik_PrivacyManager_ReportsPurger::make($settings, self::getAllMetricsToKeep()); + $result = array_merge($result, $reportsPurger->getPurgeEstimate()); + } + + return $result; + } + + /** + * Returns true if a report with the given year & month should be purged or not. + * + * If reportsOlderThan is set to null or not supplied, this function will check if + * a report should be purged, based on existing configuration. In this case, if + * delete_reports_enable is set to 0, this function will return false. + * + * @param int $reportDateYear The year of the report in question. + * @param int $reportDateMonth The month of the report in question. + * @param int|Piwik_Date $reportsOlderThan If an int, the number of months a report must be older than + * in order to be purged. If a date, the date a report must be + * older than in order to be purged. + * @return bool + */ + public static function shouldReportBePurged($reportDateYear, $reportDateMonth, $reportsOlderThan = null) + { + // if no 'older than' value/date was supplied, use existing config + if (is_null($reportsOlderThan)) { + // if report deletion is not enabled, the report shouldn't be purged + $settings = self::getPurgeDataSettings(); + if ($settings['delete_reports_enable'] == 0) { + return false; + } + + $reportsOlderThan = $settings['delete_reports_older_than']; + } + + // if a integer was supplied, assume it is the number of months a report must be older than + if (!($reportsOlderThan instanceof Piwik_Date)) { + $reportsOlderThan = Piwik_Date::factory('today')->subMonth(1 + $reportsOlderThan); + } + + return Piwik_PrivacyManager_ReportsPurger::shouldReportBePurged( + $reportDateYear, $reportDateMonth, $reportsOlderThan); + } + + /** + * Returns the general metrics to keep when the 'delete_reports_keep_basic_metrics' + * config is set to 1. + */ + private static function getMetricsToKeep() + { + return array('nb_uniq_visitors', 'nb_visits', 'nb_actions', 'max_actions', + 'sum_visit_length', 'bounce_count', 'nb_visits_converted', 'nb_conversions', + 'revenue', 'quantity', 'price', 'orders'); + } + + /** + * Returns the goal metrics to keep when the 'delete_reports_keep_basic_metrics' + * config is set to 1. + */ + private static function getGoalMetricsToKeep() + { + // keep all goal metrics + return array_values(Piwik_Archive::$mappingFromIdToNameGoal); + } + + /** + * Returns the names of metrics that should be kept when purging as they appear in + * archive tables. + */ + public static function getAllMetricsToKeep() { - if (is_null($settings)) - { - $settings = self::getPurgeDataSettings(); - } - - $result = array(); - - if ($settings['delete_logs_enable']) - { - $logDataPurger = Piwik_PrivacyManager_LogDataPurger::make($settings); - $result = array_merge($result, $logDataPurger->getPurgeEstimate()); - } - - if ($settings['delete_reports_enable']) - { - $reportsPurger = Piwik_PrivacyManager_ReportsPurger::make($settings, self::getAllMetricsToKeep()); - $result = array_merge($result, $reportsPurger->getPurgeEstimate()); - } - - return $result; + $metricsToKeep = self::getMetricsToKeep(); + + // convert goal metric names to correct archive names + if (Piwik_Common::isGoalPluginEnabled()) { + $goalMetricsToKeep = self::getGoalMetricsToKeep(); + + $maxGoalId = self::getMaxGoalId(); + + // for each goal metric, there's a different name for each goal, including the overview, + // the order report & cart report + foreach ($goalMetricsToKeep as $metric) { + for ($i = 1; $i <= $maxGoalId; ++$i) // maxGoalId can be 0 + { + $metricsToKeep[] = Piwik_Goals::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); + } + } + + return $metricsToKeep; } - - /** - * Returns true if a report with the given year & month should be purged or not. - * - * If reportsOlderThan is set to null or not supplied, this function will check if - * a report should be purged, based on existing configuration. In this case, if - * delete_reports_enable is set to 0, this function will return false. - * - * @param int $reportDateYear The year of the report in question. - * @param int $reportDateMonth The month of the report in question. - * @param int|Piwik_Date $reportsOlderThan If an int, the number of months a report must be older than - * in order to be purged. If a date, the date a report must be - * older than in order to be purged. - * @return bool - */ - public static function shouldReportBePurged( $reportDateYear, $reportDateMonth, $reportsOlderThan = null ) - { - // if no 'older than' value/date was supplied, use existing config - if (is_null($reportsOlderThan)) - { - // if report deletion is not enabled, the report shouldn't be purged - $settings = self::getPurgeDataSettings(); - if ($settings['delete_reports_enable'] == 0) - { - return false; - } - - $reportsOlderThan = $settings['delete_reports_older_than']; - } - - // if a integer was supplied, assume it is the number of months a report must be older than - if (!($reportsOlderThan instanceof Piwik_Date)) - { - $reportsOlderThan = Piwik_Date::factory('today')->subMonth(1 + $reportsOlderThan); - } - - return Piwik_PrivacyManager_ReportsPurger::shouldReportBePurged( - $reportDateYear, $reportDateMonth, $reportsOlderThan); - } - - /** - * Returns the general metrics to keep when the 'delete_reports_keep_basic_metrics' - * config is set to 1. - */ - private static function getMetricsToKeep() - { - return array('nb_uniq_visitors', 'nb_visits', 'nb_actions', 'max_actions', - 'sum_visit_length', 'bounce_count', 'nb_visits_converted', 'nb_conversions', - 'revenue', 'quantity', 'price', 'orders'); - } - - /** - * Returns the goal metrics to keep when the 'delete_reports_keep_basic_metrics' - * config is set to 1. - */ - private static function getGoalMetricsToKeep() - { - // keep all goal metrics - return array_values(Piwik_Archive::$mappingFromIdToNameGoal); - } - - /** - * Returns the names of metrics that should be kept when purging as they appear in - * archive tables. - */ - public static function getAllMetricsToKeep() - { - $metricsToKeep = self::getMetricsToKeep(); - - // convert goal metric names to correct archive names - if (Piwik_Common::isGoalPluginEnabled()) - { - $goalMetricsToKeep = self::getGoalMetricsToKeep(); - - $maxGoalId = self::getMaxGoalId(); - - // for each goal metric, there's a different name for each goal, including the overview, - // the order report & cart report - foreach ($goalMetricsToKeep as $metric) - { - for ($i = 1; $i <= $maxGoalId; ++$i) // maxGoalId can be 0 - { - $metricsToKeep[] = Piwik_Goals::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); - } - } - - return $metricsToKeep; - } - - /** - * Returns true if one of the purge data tasks should run now, false if it shouldn't. - */ - private function shouldPurgeData( $settings, $lastRanOption ) - { + + /** + * Returns true if one of the purge data tasks should run now, false if it shouldn't. + */ + private function shouldPurgeData($settings, $lastRanOption) + { // Log deletion may not run until it is once rescheduled (initial run). This is the // only way to guarantee the calculated next scheduled deletion time. $initialDelete = Piwik_GetOption(self::OPTION_LAST_DELETE_PIWIK_LOGS_INITIAL); - if (empty($initialDelete)) - { + if (empty($initialDelete)) { Piwik_SetOption(self::OPTION_LAST_DELETE_PIWIK_LOGS_INITIAL, 1); return false; } - + // Make sure, log purging is allowed to run now $lastDelete = Piwik_GetOption($lastRanOption); $deleteIntervalDays = $settings['delete_logs_schedule_lowest_interval']; $deleteIntervalSeconds = $this->getDeleteIntervalInSeconds($deleteIntervalDays); - - if ($lastDelete === false || - ($lastDelete !== false && ((int)$lastDelete + $deleteIntervalSeconds) <= time()) - ) - { - return true; - } - else // not time to run data purge - { - return false; - } - } + + if ($lastDelete === false || + ($lastDelete !== false && ((int)$lastDelete + $deleteIntervalSeconds) <= time()) + ) { + return true; + } else // not time to run data purge + { + return false; + } + } function getDeleteIntervalInSeconds($deleteInterval) { @@ -427,7 +401,7 @@ class Piwik_PrivacyManager extends Piwik_Plugin private static function getMaxGoalId() { - return Piwik_FetchOne("SELECT MAX(idgoal) FROM ".Piwik_Common::prefixTable('goal')); + return Piwik_FetchOne("SELECT MAX(idgoal) FROM " . Piwik_Common::prefixTable('goal')); } } diff --git a/plugins/PrivacyManager/ReportsPurger.php b/plugins/PrivacyManager/ReportsPurger.php index 88cd043b08..61c650a613 100755 --- a/plugins/PrivacyManager/ReportsPurger.php +++ b/plugins/PrivacyManager/ReportsPurger.php @@ -16,404 +16,367 @@ class Piwik_PrivacyManager_ReportsPurger { // constant used in database purging estimate to signify a table should be dropped const DROP_TABLE = -1; - - /** - * The max set of rows each table scan select should query at one time. - */ - public static $selectSegmentSize = 100000; - - /** + + /** + * The max set of rows each table scan select should query at one time. + */ + public static $selectSegmentSize = 100000; + + /** * The number of months after which report/metric data is considered old. */ - private $deleteReportsOlderThan; - - /** - * Whether to keep basic metrics or not. - */ - private $keepBasicMetrics; - - /** - * Array of period types. Reports for these periods will not be purged. - */ - private $reportPeriodsToKeep; - - /** - * Whether to keep reports for segments or not. - */ - private $keepSegmentReports; - - /** - * The maximum number of rows to delete per DELETE query. - */ - private $maxRowsToDeletePerQuery; - - /** - * List of metrics that should be kept when purging. If $keepBasicMetrics is true, - * these metrics will be saved. - */ - private $metricsToKeep; - - /** - * Array that maps a year and month ('2012_01') with lists of archive IDs for segmented - * archives. Used to keep segmented reports when purging. - */ - private $segmentArchiveIds = null; - - /** - * Constructor. - * - * @param int $deleteReportsOlderThan The number of months after which report/metric data - * is considered old. - * @param bool $keepBasicMetrics Whether to keep basic metrics or not. - * @param array $reportPeriodsToKeep Array of period types. Reports for these periods will not - * be purged. - * @param bool $keepSegmentReports Whether to keep reports for segments or not. - * @param array $metricsToKeep List of metrics that should be kept. if $keepBasicMetrics - * is true, these metrics will be saved. - * @param int $maxRowsToDeletePerQuery The maximum number of rows to delete per DELETE query. - */ - public function __construct( $deleteReportsOlderThan, $keepBasicMetrics, $reportPeriodsToKeep, - $keepSegmentReports, $metricsToKeep, $maxRowsToDeletePerQuery ) - { - $this->deleteReportsOlderThan = $deleteReportsOlderThan; - $this->keepBasicMetrics = $keepBasicMetrics; - $this->reportPeriodsToKeep = $reportPeriodsToKeep; - $this->keepSegmentReports = $keepSegmentReports; - $this->metricsToKeep = $metricsToKeep; - $this->maxRowsToDeletePerQuery = $maxRowsToDeletePerQuery; - } - - /** - * Purges old report/metric data. - * - * If $keepBasicMetrics is false, old numeric tables will be dropped, otherwise only - * the metrics not in $metricsToKeep will be deleted. - * - * If $reportPeriodsToKeep is an empty array, old blob tables will be dropped. Otherwise, - * specific reports will be deleted, except reports for periods in $reportPeriodsToKeep. - * - * @param bool $optimize If tables should be optimized after rows are deleted. Normally, - * this is handled by a scheduled task. - */ - public function purgeData($optimize = false) - { - // find archive tables to purge - list($oldNumericTables, $oldBlobTables) = $this->getArchiveTablesToPurge(); - - // process blob tables first, since archive status is stored in the numeric archives - if (!empty($oldBlobTables)) - { - // if no reports should be kept, drop tables, otherwise drop individual reports - if (empty($this->reportPeriodsToKeep) && !$this->keepSegmentReports) - { - Piwik_DropTables($oldBlobTables); - } - else - { - foreach ($oldBlobTables as $table) - { - $where = $this->getBlobTableWhereExpr($oldNumericTables, $table); - if (!empty($where)) - { - $where = "WHERE $where"; - } - Piwik_DeleteAllRows($table, $where, $this->maxRowsToDeletePerQuery); - } - - if ($optimize) - { - Piwik_OptimizeTables($oldBlobTables); - } - } - } - - // deal with numeric tables - if (!empty($oldNumericTables)) - { - // if keep_basic_metrics is set, empty all numeric tables of metrics to purge - if ($this->keepBasicMetrics == 1 && !empty($this->metricsToKeep)) - { - $where = "WHERE name NOT IN ('".implode("','", $this->metricsToKeep)."') AND name NOT LIKE 'done%'"; - foreach ($oldNumericTables as $table) - { - Piwik_DeleteAllRows($table, $where, $this->maxRowsToDeletePerQuery); - } - - if ($optimize) - { - Piwik_OptimizeTables($oldNumericTables); - } - } - else // drop numeric tables - { - Piwik_DropTables($oldNumericTables); - } - } - } - - /** - * Returns an array describing what data would be purged if purging were invoked. - * - * This function returns an array that maps table names with the number of rows - * that will be deleted. If a table name is mapped with self::DROP_TABLE, the table - * will be dropped. - * - * @return array - */ - public function getPurgeEstimate() - { - $result = array(); - - // get archive tables that will be purged - list($oldNumericTables, $oldBlobTables) = $this->getArchiveTablesToPurge(); - - // process blob tables first, since archive status is stored in the numeric archives - if (empty($this->reportPeriodsToKeep) && !$this->keepSegmentReports) - { - // not keeping any reports, so drop all tables - foreach ($oldBlobTables as $table) - { - $result[$table] = self::DROP_TABLE; - } - } - else - { - // figure out which rows will be deleted - foreach ($oldBlobTables as $table) - { - $rowCount = $this->getBlobTableDeleteCount($oldNumericTables, $table); - if ($rowCount > 0) - { - $result[$table] = $rowCount; - } - } - } - - // deal w/ numeric tables - if ($this->keepBasicMetrics == 1) - { - // figure out which rows will be deleted - foreach ($oldNumericTables as $table) - { - $rowCount = $this->getNumericTableDeleteCount($table); - if ($rowCount > 0) - { - $result[$table] = $rowCount; - } - } - } - else - { - // not keeping any metrics, so drop the entire table - foreach ($oldNumericTables as $table) - { - $result[$table] = self::DROP_TABLE; - } - } - - return $result; - } - - /** - * Utility function that finds every archive table whose reports are considered - * old. - * - * @return array An array of two arrays. The first holds the numeric archive table - * names, and the second holds the blob archive table names. - */ - private function getArchiveTablesToPurge() - { - // get month for which reports as old or older than, should be deleted - // 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; - } - } - } - } - - return array($oldNumericTables, $oldBlobTables); - } - - /** - * Returns true if a report with the given year & month should be purged or not. - * - * @param int $reportDateYear The year of the report in question. - * @param int $reportDateMonth The month of the report in question. - * @param Piwik_Date $toRemoveDate The date a report must be older than in order to be purged. - * @return bool - */ - public static function shouldReportBePurged( $reportDateYear, $reportDateMonth, $toRemoveDate ) - { - $toRemoveYear = (int)$toRemoveDate->toString('Y'); - $toRemoveMonth = (int)$toRemoveDate->toString('m'); - - return $reportDateYear < $toRemoveYear - || ($reportDateYear == $toRemoveYear && $reportDateMonth <= $toRemoveMonth); - } - - private function getNumericTableDeleteCount( $table ) - { - $maxIdArchive = Piwik_FetchOne("SELECT MAX(idarchive) FROM $table"); - - $sql = "SELECT COUNT(*) + private $deleteReportsOlderThan; + + /** + * Whether to keep basic metrics or not. + */ + private $keepBasicMetrics; + + /** + * Array of period types. Reports for these periods will not be purged. + */ + private $reportPeriodsToKeep; + + /** + * Whether to keep reports for segments or not. + */ + private $keepSegmentReports; + + /** + * The maximum number of rows to delete per DELETE query. + */ + private $maxRowsToDeletePerQuery; + + /** + * List of metrics that should be kept when purging. If $keepBasicMetrics is true, + * these metrics will be saved. + */ + private $metricsToKeep; + + /** + * Array that maps a year and month ('2012_01') with lists of archive IDs for segmented + * archives. Used to keep segmented reports when purging. + */ + private $segmentArchiveIds = null; + + /** + * Constructor. + * + * @param int $deleteReportsOlderThan The number of months after which report/metric data + * is considered old. + * @param bool $keepBasicMetrics Whether to keep basic metrics or not. + * @param array $reportPeriodsToKeep Array of period types. Reports for these periods will not + * be purged. + * @param bool $keepSegmentReports Whether to keep reports for segments or not. + * @param array $metricsToKeep List of metrics that should be kept. if $keepBasicMetrics + * is true, these metrics will be saved. + * @param int $maxRowsToDeletePerQuery The maximum number of rows to delete per DELETE query. + */ + public function __construct($deleteReportsOlderThan, $keepBasicMetrics, $reportPeriodsToKeep, + $keepSegmentReports, $metricsToKeep, $maxRowsToDeletePerQuery) + { + $this->deleteReportsOlderThan = $deleteReportsOlderThan; + $this->keepBasicMetrics = $keepBasicMetrics; + $this->reportPeriodsToKeep = $reportPeriodsToKeep; + $this->keepSegmentReports = $keepSegmentReports; + $this->metricsToKeep = $metricsToKeep; + $this->maxRowsToDeletePerQuery = $maxRowsToDeletePerQuery; + } + + /** + * Purges old report/metric data. + * + * If $keepBasicMetrics is false, old numeric tables will be dropped, otherwise only + * the metrics not in $metricsToKeep will be deleted. + * + * If $reportPeriodsToKeep is an empty array, old blob tables will be dropped. Otherwise, + * specific reports will be deleted, except reports for periods in $reportPeriodsToKeep. + * + * @param bool $optimize If tables should be optimized after rows are deleted. Normally, + * this is handled by a scheduled task. + */ + public function purgeData($optimize = false) + { + // find archive tables to purge + list($oldNumericTables, $oldBlobTables) = $this->getArchiveTablesToPurge(); + + // process blob tables first, since archive status is stored in the numeric archives + if (!empty($oldBlobTables)) { + // if no reports should be kept, drop tables, otherwise drop individual reports + if (empty($this->reportPeriodsToKeep) && !$this->keepSegmentReports) { + Piwik_DropTables($oldBlobTables); + } else { + foreach ($oldBlobTables as $table) { + $where = $this->getBlobTableWhereExpr($oldNumericTables, $table); + if (!empty($where)) { + $where = "WHERE $where"; + } + Piwik_DeleteAllRows($table, $where, $this->maxRowsToDeletePerQuery); + } + + if ($optimize) { + Piwik_OptimizeTables($oldBlobTables); + } + } + } + + // deal with numeric tables + if (!empty($oldNumericTables)) { + // if keep_basic_metrics is set, empty all numeric tables of metrics to purge + if ($this->keepBasicMetrics == 1 && !empty($this->metricsToKeep)) { + $where = "WHERE name NOT IN ('" . implode("','", $this->metricsToKeep) . "') AND name NOT LIKE 'done%'"; + foreach ($oldNumericTables as $table) { + Piwik_DeleteAllRows($table, $where, $this->maxRowsToDeletePerQuery); + } + + if ($optimize) { + Piwik_OptimizeTables($oldNumericTables); + } + } else // drop numeric tables + { + Piwik_DropTables($oldNumericTables); + } + } + } + + /** + * Returns an array describing what data would be purged if purging were invoked. + * + * This function returns an array that maps table names with the number of rows + * that will be deleted. If a table name is mapped with self::DROP_TABLE, the table + * will be dropped. + * + * @return array + */ + public function getPurgeEstimate() + { + $result = array(); + + // get archive tables that will be purged + list($oldNumericTables, $oldBlobTables) = $this->getArchiveTablesToPurge(); + + // process blob tables first, since archive status is stored in the numeric archives + if (empty($this->reportPeriodsToKeep) && !$this->keepSegmentReports) { + // not keeping any reports, so drop all tables + foreach ($oldBlobTables as $table) { + $result[$table] = self::DROP_TABLE; + } + } else { + // figure out which rows will be deleted + foreach ($oldBlobTables as $table) { + $rowCount = $this->getBlobTableDeleteCount($oldNumericTables, $table); + if ($rowCount > 0) { + $result[$table] = $rowCount; + } + } + } + + // deal w/ numeric tables + if ($this->keepBasicMetrics == 1) { + // figure out which rows will be deleted + foreach ($oldNumericTables as $table) { + $rowCount = $this->getNumericTableDeleteCount($table); + if ($rowCount > 0) { + $result[$table] = $rowCount; + } + } + } else { + // not keeping any metrics, so drop the entire table + foreach ($oldNumericTables as $table) { + $result[$table] = self::DROP_TABLE; + } + } + + return $result; + } + + /** + * Utility function that finds every archive table whose reports are considered + * old. + * + * @return array An array of two arrays. The first holds the numeric archive table + * names, and the second holds the blob archive table names. + */ + private function getArchiveTablesToPurge() + { + // get month for which reports as old or older than, should be deleted + // 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; + } + } + } + } + + return array($oldNumericTables, $oldBlobTables); + } + + /** + * Returns true if a report with the given year & month should be purged or not. + * + * @param int $reportDateYear The year of the report in question. + * @param int $reportDateMonth The month of the report in question. + * @param Piwik_Date $toRemoveDate The date a report must be older than in order to be purged. + * @return bool + */ + public static function shouldReportBePurged($reportDateYear, $reportDateMonth, $toRemoveDate) + { + $toRemoveYear = (int)$toRemoveDate->toString('Y'); + $toRemoveMonth = (int)$toRemoveDate->toString('m'); + + return $reportDateYear < $toRemoveYear + || ($reportDateYear == $toRemoveYear && $reportDateMonth <= $toRemoveMonth); + } + + private function getNumericTableDeleteCount($table) + { + $maxIdArchive = Piwik_FetchOne("SELECT MAX(idarchive) FROM $table"); + + $sql = "SELECT COUNT(*) FROM $table - WHERE name NOT IN ('".implode("','", $this->metricsToKeep)."') + WHERE name NOT IN ('" . implode("','", $this->metricsToKeep) . "') AND name NOT LIKE 'done%' AND idarchive >= ? AND idarchive < ?"; - - $segments = Piwik_SegmentedFetchOne($sql, 0, $maxIdArchive, self::$selectSegmentSize); - return array_sum($segments); - } - - private function getBlobTableDeleteCount( $oldNumericTables, $table ) - { - $maxIdArchive = Piwik_FetchOne("SELECT MAX(idarchive) FROM $table"); - - $sql = "SELECT COUNT(*) + + $segments = Piwik_SegmentedFetchOne($sql, 0, $maxIdArchive, self::$selectSegmentSize); + return array_sum($segments); + } + + private function getBlobTableDeleteCount($oldNumericTables, $table) + { + $maxIdArchive = Piwik_FetchOne("SELECT MAX(idarchive) FROM $table"); + + $sql = "SELECT COUNT(*) FROM $table - WHERE ".$this->getBlobTableWhereExpr($oldNumericTables, $table)." + WHERE " . $this->getBlobTableWhereExpr($oldNumericTables, $table) . " AND idarchive >= ? AND idarchive < ?"; - - $segments = Piwik_SegmentedFetchOne($sql, 0, $maxIdArchive, self::$selectSegmentSize); - return array_sum($segments); - } - - /** Returns SQL WHERE expression used to find reports that should be purged. */ - private function getBlobTableWhereExpr( $oldNumericTables, $table ) - { - $where = ""; - if (!empty($this->reportPeriodsToKeep)) // if keeping reports - { - $where = "period NOT IN (".implode(',', $this->reportPeriodsToKeep).")"; - - // 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)]; - - if (!empty($archiveIds)) - { - $where .= " OR idarchive IN (".implode(',', $archiveIds).")"; - } - } - - $where = "($where)"; - } - return $where; - } - - /** - * If we're going to keep segmented reports, we need to know which archives are - * for segments. This info is only in the numeric tables, so we must query them. - */ - private function findSegmentArchives( $numericTables ) - { - if (!is_null($this->segmentArchiveIds)) - { - return; - } - - foreach ($numericTables as $table) - { - $tableDate = $this->getArchiveTableDate($table); - - $maxIdArchive = Piwik_FetchOne("SELECT MAX(idarchive) FROM $table"); - - $sql = "SELECT idarchive + + $segments = Piwik_SegmentedFetchOne($sql, 0, $maxIdArchive, self::$selectSegmentSize); + return array_sum($segments); + } + + /** Returns SQL WHERE expression used to find reports that should be purged. */ + private function getBlobTableWhereExpr($oldNumericTables, $table) + { + $where = ""; + if (!empty($this->reportPeriodsToKeep)) // if keeping reports + { + $where = "period NOT IN (" . implode(',', $this->reportPeriodsToKeep) . ")"; + + // 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)]; + + if (!empty($archiveIds)) { + $where .= " OR idarchive IN (" . implode(',', $archiveIds) . ")"; + } + } + + $where = "($where)"; + } + return $where; + } + + /** + * If we're going to keep segmented reports, we need to know which archives are + * for segments. This info is only in the numeric tables, so we must query them. + */ + private function findSegmentArchives($numericTables) + { + if (!is_null($this->segmentArchiveIds)) { + return; + } + + foreach ($numericTables as $table) { + $tableDate = $this->getArchiveTableDate($table); + + $maxIdArchive = Piwik_FetchOne("SELECT MAX(idarchive) FROM $table"); + + $sql = "SELECT idarchive FROM $table WHERE name != 'done' AND name LIKE 'done_%.%' AND idarchive >= ? AND idarchive < ?"; - - $this->segmentArchiveIds[$tableDate] = array(); - foreach (Piwik_SegmentedFetchAll($sql, 0, $maxIdArchive, self::$selectSegmentSize) as $row) - { - $this->segmentArchiveIds[$tableDate][] = $row['idarchive']; - } - } - } - - 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. - * - * $settings must contain the following keys: - * -'delete_reports_older_than': The number of months after which reports/metrics are - * considered old. - * -'delete_reports_keep_basic_metrics': 1 if basic metrics should be kept, 0 if otherwise. - * -'delete_reports_keep_day_reports': 1 if daily reports should be kept, 0 if otherwise. - * -'delete_reports_keep_week_reports': 1 if weekly reports should be kept, 0 if otherwise. - * -'delete_reports_keep_month_reports': 1 if monthly reports should be kept, 0 if otherwise. - * -'delete_reports_keep_year_reports': 1 if yearly reports should be kept, 0 if otherwise. - * -'delete_reports_keep_range_reports': 1 if range reports should be kept, 0 if otherwise. - * -'delete_reports_keep_segment_reports': 1 if reports for segments should be kept, 0 if otherwise. - * -'delete_logs_max_rows_per_query': Maximum number of rows to delete in one DELETE query. - */ - public static function make( $settings, $metricsToKeep ) - { - return new Piwik_PrivacyManager_ReportsPurger( - $settings['delete_reports_older_than'], - $settings['delete_reports_keep_basic_metrics'] == 1, - self::getReportPeriodsToKeep($settings), - $settings['delete_reports_keep_segment_reports'] == 1, - $metricsToKeep, - $settings['delete_logs_max_rows_per_query'] - ); - } - - /** - * Utility function that returns an array period values based on the 'delete_reports_keep_*' - * settings. The period values returned are the integer values stored in the DB. - * - * @param array $deleteReportSettings The settings to use. - * @return array An array of period values that should be kept when purging old data. - */ - private static function getReportPeriodsToKeep( $settings ) - { - $keepReportPeriods = array(); - foreach (Piwik::$idPeriods as $strPeriod => $intPeriod) - { - $optionName = "delete_reports_keep_{$strPeriod}_reports"; - if ($settings[$optionName] == 1) - { - $keepReportPeriods[] = $intPeriod; - } - } - return $keepReportPeriods; - } + + $this->segmentArchiveIds[$tableDate] = array(); + foreach (Piwik_SegmentedFetchAll($sql, 0, $maxIdArchive, self::$selectSegmentSize) as $row) { + $this->segmentArchiveIds[$tableDate][] = $row['idarchive']; + } + } + } + + 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. + * + * $settings must contain the following keys: + * -'delete_reports_older_than': The number of months after which reports/metrics are + * considered old. + * -'delete_reports_keep_basic_metrics': 1 if basic metrics should be kept, 0 if otherwise. + * -'delete_reports_keep_day_reports': 1 if daily reports should be kept, 0 if otherwise. + * -'delete_reports_keep_week_reports': 1 if weekly reports should be kept, 0 if otherwise. + * -'delete_reports_keep_month_reports': 1 if monthly reports should be kept, 0 if otherwise. + * -'delete_reports_keep_year_reports': 1 if yearly reports should be kept, 0 if otherwise. + * -'delete_reports_keep_range_reports': 1 if range reports should be kept, 0 if otherwise. + * -'delete_reports_keep_segment_reports': 1 if reports for segments should be kept, 0 if otherwise. + * -'delete_logs_max_rows_per_query': Maximum number of rows to delete in one DELETE query. + */ + public static function make($settings, $metricsToKeep) + { + return new Piwik_PrivacyManager_ReportsPurger( + $settings['delete_reports_older_than'], + $settings['delete_reports_keep_basic_metrics'] == 1, + self::getReportPeriodsToKeep($settings), + $settings['delete_reports_keep_segment_reports'] == 1, + $metricsToKeep, + $settings['delete_logs_max_rows_per_query'] + ); + } + + /** + * Utility function that returns an array period values based on the 'delete_reports_keep_*' + * settings. The period values returned are the integer values stored in the DB. + * + * @param array $deleteReportSettings The settings to use. + * @return array An array of period values that should be kept when purging old data. + */ + private static function getReportPeriodsToKeep($settings) + { + $keepReportPeriods = array(); + foreach (Piwik::$idPeriods as $strPeriod => $intPeriod) { + $optionName = "delete_reports_keep_{$strPeriod}_reports"; + if ($settings[$optionName] == 1) { + $keepReportPeriods[] = $intPeriod; + } + } + return $keepReportPeriods; + } } diff --git a/plugins/PrivacyManager/templates/databaseSize.tpl b/plugins/PrivacyManager/templates/databaseSize.tpl index 2093a01e82..fba6612d71 100755 --- a/plugins/PrivacyManager/templates/databaseSize.tpl +++ b/plugins/PrivacyManager/templates/databaseSize.tpl @@ -1,7 +1,7 @@

{'PrivacyManager_CurrentDBSize'|translate}: {$dbStats.currentSize}

{if isset($dbStats.sizeAfterPurge)} -

{'PrivacyManager_EstimatedDBSizeAfterPurge'|translate}: {$dbStats.sizeAfterPurge}

+

{'PrivacyManager_EstimatedDBSizeAfterPurge'|translate}: {$dbStats.sizeAfterPurge}

{/if} {if isset($dbStats.spaceSaved)} -

{'PrivacyManager_EstimatedSpaceSaved'|translate}: {$dbStats.spaceSaved}

+

{'PrivacyManager_EstimatedSpaceSaved'|translate}: {$dbStats.spaceSaved}

{/if} diff --git a/plugins/PrivacyManager/templates/privacySettings.js b/plugins/PrivacyManager/templates/privacySettings.js index 509ef85941..75de9b8caa 100644 --- a/plugins/PrivacyManager/templates/privacySettings.js +++ b/plugins/PrivacyManager/templates/privacySettings.js @@ -5,49 +5,49 @@ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later */ -$(document).ready(function() { - function toggleBlock(id, value) { - $('#' + id).toggle(value == 1); - } - - function isEitherDeleteSectionEnabled() { - return ($('input[name=deleteEnable]:checked').val() == 1) - || ($('input[name=deleteReportsEnable]:checked').val() == 1); - } - - function toggleOtherDeleteSections() { - var showSection = isEitherDeleteSectionEnabled(); - toggleBlock('deleteDataEstimateSect', showSection); - toggleBlock('deleteSchedulingSettings', showSection); - } - - // reloads purged database size estimate - var currentRequest; - function reloadDbStats(forceEstimate) { - if (currentRequest) { - currentRequest.abort(); - } - - // if the section isn't visible or the manual estimate link is showing, abort - // (unless on first load or forcing) - var isFirstLoad = $('#deleteDataEstimate').html() == ''; - if (!isFirstLoad - && forceEstimate !== true - && (!isEitherDeleteSectionEnabled() || $('#getPurgeEstimateLink').length > 0)) - { - return; - } - - $('#deleteDataEstimate').hide(); - - var data = $('#formDeleteSettings').serializeArray(); +$(document).ready(function () { + function toggleBlock(id, value) { + $('#' + id).toggle(value == 1); + } + + function isEitherDeleteSectionEnabled() { + return ($('input[name=deleteEnable]:checked').val() == 1) + || ($('input[name=deleteReportsEnable]:checked').val() == 1); + } + + function toggleOtherDeleteSections() { + var showSection = isEitherDeleteSectionEnabled(); + toggleBlock('deleteDataEstimateSect', showSection); + toggleBlock('deleteSchedulingSettings', showSection); + } + + // reloads purged database size estimate + var currentRequest; + + function reloadDbStats(forceEstimate) { + if (currentRequest) { + currentRequest.abort(); + } + + // if the section isn't visible or the manual estimate link is showing, abort + // (unless on first load or forcing) + var isFirstLoad = $('#deleteDataEstimate').html() == ''; + if (!isFirstLoad + && forceEstimate !== true + && (!isEitherDeleteSectionEnabled() || $('#getPurgeEstimateLink').length > 0)) { + return; + } + + $('#deleteDataEstimate').hide(); + + var data = $('#formDeleteSettings').serializeArray(); var formData = {}; - for(var i=0; ih2').each(function() { - $(this).hide(); - }); - - if (deletingLogs) - { - confirm_id = deletingReports ? "deleteBothConfirm" : "deleteLogsConfirm"; - } - else if (deletingReports) - { - confirm_id = "deleteReportsConfirm"; - } - - if (confirm_id) - { - $("#" + confirm_id).show(); - e.preventDefault(); - - piwikHelper.modalConfirm('#confirmDeleteSettings', { - yes: function() { - $('#formDeleteSettings').submit(); - } - }); - } - else - { - $('#formDeleteSettings').submit(); - } - }); - - // execute purge now link click - $('#purgeDataNowLink').click(function(e) { - e.preventDefault(); - - var link = this; - - // if any option has been modified, abort purging and instruct user to save first - var modified = false; - $('#formDeleteSettings input').each(function() { - if (this.type === 'checkbox' || this.type === 'radio') { - modified |= this.defaultChecked !== this.checked; - } else { - modified |= this.defaultValue !== this.value; - } - }); - - if (modified) { - piwikHelper.modalConfirm('#saveSettingsBeforePurge', {yes: function() {}}); - return; - } - - // ask user if they really want to delete their old data - piwikHelper.modalConfirm('#confirmPurgeNow', { - yes: function() { - $(link).hide(); - - // execute a data purge + } + + // make sure certain sections only display if their corresponding features are enabled + $('input[name=anonymizeIPEnable]').click(function () { + toggleBlock("anonymizeIPenabled", $(this).val()); + }); + + $('input[name=deleteEnable]').click(function () { + toggleBlock("deleteLogSettings", $(this).val()); + toggleOtherDeleteSections(); + }).change(reloadDbStats); + + $('input[name=deleteReportsEnable]').click(function () { + toggleBlock("deleteReportsSettings", $(this).val()); + toggleBlock("deleteOldReportsMoreInfo", $(this).val()); + toggleOtherDeleteSections(); + }).change(reloadDbStats); + + // initial toggling calls + $(function () { + toggleBlock("deleteLogSettings", $("input[name=deleteEnable]:checked").val()); + toggleBlock("anonymizeIPenabled", $("input[name=anonymizeIPEnable]:checked").val()); + toggleBlock("deleteReportsSettings", $("input[name=deleteReportsEnable]:checked").val()); + toggleBlock("deleteOldReportsMoreInfo", $("input[name=deleteReportsEnable]:checked").val()); + toggleOtherDeleteSections(); + }); + + // make sure the DB size estimate is reloaded every time a delete logs/reports setting is changed + $('#formDeleteSettings input[type=text]').each(function () { + $(this).change(reloadDbStats); + }); + $('#formDeleteSettings input[type=checkbox]').each(function () { + $(this).click(reloadDbStats); + }); + + // make sure when the delete log/report settings are submitted, a confirmation popup is + // displayed first + $('#deleteLogSettingsSubmit').click(function (e) { + var deletingLogs = $("input[name=deleteEnable]:checked").val() == 1, + deletingReports = $("input[name=deleteReportsEnable]:checked").val() == 1, + confirm_id; + + // hide all confirmation texts, then show the correct one based on what + // type of deletion is enabled. + $('#confirmDeleteSettings>h2').each(function () { + $(this).hide(); + }); + + if (deletingLogs) { + confirm_id = deletingReports ? "deleteBothConfirm" : "deleteLogsConfirm"; + } + else if (deletingReports) { + confirm_id = "deleteReportsConfirm"; + } + + if (confirm_id) { + $("#" + confirm_id).show(); + e.preventDefault(); + + piwikHelper.modalConfirm('#confirmDeleteSettings', { + yes: function () { + $('#formDeleteSettings').submit(); + } + }); + } + else { + $('#formDeleteSettings').submit(); + } + }); + + // execute purge now link click + $('#purgeDataNowLink').click(function (e) { + e.preventDefault(); + + var link = this; + + // if any option has been modified, abort purging and instruct user to save first + var modified = false; + $('#formDeleteSettings input').each(function () { + if (this.type === 'checkbox' || this.type === 'radio') { + modified |= this.defaultChecked !== this.checked; + } else { + modified |= this.defaultValue !== this.value; + } + }); + + if (modified) { + piwikHelper.modalConfirm('#saveSettingsBeforePurge', {yes: function () {}}); + return; + } + + // ask user if they really want to delete their old data + piwikHelper.modalConfirm('#confirmPurgeNow', { + yes: function () { + $(link).hide(); + + // execute a data purge var ajaxRequest = new ajaxHelper(); ajaxRequest.setLoadingElement('#deleteSchedulingSettings .loadingPiwik'); ajaxRequest.addParams({ @@ -192,16 +188,16 @@ $(document).ready(function() { ); ajaxRequest.setFormat('html'); ajaxRequest.send(false); - } - }); - }); - - // get estimate link click - $('#getPurgeEstimateLink').click(function(e) { - e.preventDefault(); - reloadDbStats(true); - }); - - // load initial db size estimate - reloadDbStats(); + } + }); + }); + + // get estimate link click + $('#getPurgeEstimateLink').click(function (e) { + e.preventDefault(); + reloadDbStats(true); + }); + + // load initial db size estimate + reloadDbStats(); }); diff --git a/plugins/PrivacyManager/templates/privacySettings.tpl b/plugins/PrivacyManager/templates/privacySettings.tpl index 1550ffd472..f1478cb997 100644 --- a/plugins/PrivacyManager/templates/privacySettings.tpl +++ b/plugins/PrivacyManager/templates/privacySettings.tpl @@ -1,251 +1,266 @@ {include file="CoreAdminHome/templates/header.tpl"} {if $isSuperUser} +

{'PrivacyManager_TeaserHeadline'|translate}

+

{'PrivacyManager_Teaser'|translate:'':"":'':"":'':""} + See also our official guide Web Analytics Privacy

+ +

{'PrivacyManager_UseAnonymizeIp'|translate}

+
+
+ + + + + + +
{'PrivacyManager_UseAnonymizeIp'|translate}
+ {'PrivacyManager_AnonymizeIpDescription'|translate} +
+ + + + + + {'AnonymizeIP_PluginDescription'|translate|inlineHelp} +
+
+
+ + + + + + +
{'PrivacyManager_AnonymizeIpMaskLengtDescription'|translate} +
+
+ +
+ {'PrivacyManager_GeolocationAnonymizeIpNote'|translate|inlineHelp} +
+
+ +
+
+

{'PrivacyManager_DeleteLogsConfirm'|translate}

-

{'PrivacyManager_TeaserHeadline'|translate}

-

{'PrivacyManager_Teaser'|translate:'':"":'':"":'':""} -See also our official guide Web Analytics Privacy

+

{'PrivacyManager_DeleteReportsConfirm'|translate}

- -

{'PrivacyManager_UseAnonymizeIp'|translate}

-
-
- - - - - - -
{'PrivacyManager_UseAnonymizeIp'|translate}
- {'PrivacyManager_AnonymizeIpDescription'|translate} -
- - - - - - {'AnonymizeIP_PluginDescription'|translate|inlineHelp} -
-
-
- - - - - - -
{'PrivacyManager_AnonymizeIpMaskLengtDescription'|translate} -
-
- -
- {'PrivacyManager_GeolocationAnonymizeIpNote'|translate|inlineHelp} -
-
- -
+

{'PrivacyManager_DeleteBothConfirm'|translate}

+ + +
+
+

{'PrivacyManager_SaveSettingsBeforePurge'|translate}

+ +
+
+

{'PrivacyManager_PurgeNowConfirm'|translate}

+ + +
+ +

{'PrivacyManager_DeleteDataSettings'|translate}

+

{'PrivacyManager_DeleteDataDescription'|translate} {'PrivacyManager_DeleteDataDescription2'|translate}

+
+ + + + + + + + + + + +
{'PrivacyManager_UseDeleteLog'|translate}
-
-

{'PrivacyManager_DeleteLogsConfirm'|translate}

-

{'PrivacyManager_DeleteReportsConfirm'|translate}

-

{'PrivacyManager_DeleteBothConfirm'|translate}

- - -
- -
-

{'PrivacyManager_SaveSettingsBeforePurge'|translate}

- -
- -
-

{'PrivacyManager_PurgeNowConfirm'|translate}

- - -
- - -

{'PrivacyManager_DeleteDataSettings'|translate}

-

{'PrivacyManager_DeleteDataDescription'|translate} {'PrivacyManager_DeleteDataDescription2'|translate}

- - - - - + - - - - - - + + + + + + - - - - + + + + - - - - - - - - - - - - - - - - - -
{'PrivacyManager_UseDeleteLog'|translate}
- -
- - + + + {'PrivacyManager_DeleteLogDescription2'|translate} - - {'General_ClickHere'|translate} - + + {'General_ClickHere'|translate} + - - {capture assign=deleteLogInfo} - {'PrivacyManager_DeleteLogInfo'|translate:$deleteData.deleteTables} - {if !$canDeleteLogActions} -

{'PrivacyManager_CannotLockSoDeleteLogActions'|translate:$dbUser} - {/if} - {/capture} - {$deleteLogInfo|inlineHelp} -
  -
- {'PrivacyManager_LeastDaysInput'|translate:"1"} -
+ + {capture assign=deleteLogInfo} + {'PrivacyManager_DeleteLogInfo'|translate:$deleteData.deleteTables} + {if !$canDeleteLogActions} +
+
+ {'PrivacyManager_CannotLockSoDeleteLogActions'|translate:$dbUser} + {/if} + {/capture} + {$deleteLogInfo|inlineHelp} +
  +
+ {'PrivacyManager_LeastDaysInput'|translate:"1"} +
-
{'PrivacyManager_UseDeleteReports'|translate} - - - +
{'PrivacyManager_UseDeleteReports'|translate} + + + {capture assign=deleteOldLogs}{'PrivacyManager_UseDeleteLog'|translate}{/capture} - {'PrivacyManager_DeleteReportsInfo'|translate:'':''} -

- {'PrivacyManager_DeleteReportsInfo2'|translate:$deleteOldLogs}

- {'PrivacyManager_DeleteReportsInfo3'|translate:$deleteOldLogs}
+ {'PrivacyManager_DeleteReportsInfo'|translate:'':''} +

+ {'PrivacyManager_DeleteReportsInfo2'|translate:$deleteOldLogs}

+ {'PrivacyManager_DeleteReportsInfo3'|translate:$deleteOldLogs}
-
- {'PrivacyManager_DeleteReportsDetailedInfo'|translate:'archive_numeric_*':'archive_blob_*'|inlineHelp} -
  -
- {'PrivacyManager_LeastMonthsInput'|translate:"3"}

-

- {'PrivacyManager_KeepDataFor'|translate}
-
-
-
-
-

-
-
- -
{'PrivacyManager_ReportsDataSavedEstimate'|translate}
-
- -
- {if $deleteData.config.enable_auto_database_size_estimate eq '0'} - {capture assign=manualEstimate} - {'PrivacyManager_GetPurgeEstimate'|translate} - {/capture} - {$manualEstimate|inlineHelp} - {/if} -
{'PrivacyManager_DeleteSchedulingSettings'|translate}
-

-
- {capture assign=purgeStats} - {if $deleteData.lastRun}{'PrivacyManager_LastDelete'|translate}: - {$deleteData.lastRunPretty} -

{/if} - {'PrivacyManager_NextDelete'|translate}: - {$deleteData.nextRunPretty} -

{'PrivacyManager_PurgeNow'|translate} - - - {/capture} - {$purgeStats|inlineHelp} -
- - +
+ {'PrivacyManager_DeleteReportsDetailedInfo'|translate:'archive_numeric_*':'archive_blob_*'|inlineHelp} +
  +
+ {'PrivacyManager_LeastMonthsInput'|translate:"3"}

+

+ {'PrivacyManager_KeepDataFor'|translate}
+
+
+
+
+

+
+
- - - -

{'PrivacyManager_DoNotTrack_SupportDNTPreference'|translate}

- - - - + + + + + + + + + + + +
-

{if $dntSupport} - {assign var=action value=deactivate} - {'PrivacyManager_DoNotTrack_Enabled'|translate}
{'PrivacyManager_DoNotTrack_EnabledMoreInfo'|translate} - {else} - {assign var=action value=activate} - {'PrivacyManager_DoNotTrack_Disabled'|translate} {'PrivacyManager_DoNotTrack_DisabledMoreInfo'|translate} - {/if}

+
{'PrivacyManager_ReportsDataSavedEstimate'|translate}
+
+ +
+ {if $deleteData.config.enable_auto_database_size_estimate eq '0'} + {capture assign=manualEstimate} + {'PrivacyManager_GetPurgeEstimate'|translate} + {/capture} + {$manualEstimate|inlineHelp} + {/if} +
{'PrivacyManager_DeleteSchedulingSettings'|translate}
+

+
+ {capture assign=purgeStats} + {if $deleteData.lastRun}{'PrivacyManager_LastDelete'|translate}: + {$deleteData.lastRunPretty} +
+
+ {/if} + {'PrivacyManager_NextDelete'|translate}: + {$deleteData.nextRunPretty} +
+
+ {'PrivacyManager_PurgeNow'|translate} + + + {/capture} + {$purgeStats|inlineHelp} +
+ + + +

{'PrivacyManager_DoNotTrack_SupportDNTPreference'|translate}

+ + + - - -
+

{if $dntSupport} + {assign var=action value=deactivate} + {'PrivacyManager_DoNotTrack_Enabled'|translate} +
+ {'PrivacyManager_DoNotTrack_EnabledMoreInfo'|translate} + {else} + {assign var=action value=activate} + {'PrivacyManager_DoNotTrack_Disabled'|translate} {'PrivacyManager_DoNotTrack_DisabledMoreInfo'|translate} + {/if}

- › - {if $dntSupport}{'PrivacyManager_DoNotTrack_Disable'|translate} {'General_NotRecommended'|translate} - {else}{'PrivacyManager_DoNotTrack_Enable'|translate} {'General_Recommended'|translate}{/if} -
-
-
- {'PrivacyManager_DoNotTrack_Description'|translate|inlineHelp} -
- + › + {if $dntSupport}{'PrivacyManager_DoNotTrack_Disable'|translate} {'General_NotRecommended'|translate} + {else}{'PrivacyManager_DoNotTrack_Enable'|translate} {'General_Recommended'|translate}{/if} +
+
+
+ {'PrivacyManager_DoNotTrack_Description'|translate|inlineHelp} +
{/if}

{'CoreAdminHome_OptOutForYourVisitors'|translate}

{'CoreAdminHome_OptOutExplanation'|translate} -{capture name=optOutUrl}{$piwikUrl}index.php?module=CoreAdminHome&action=optOut&language={$language}{/capture} -{assign var=optOutUrl value=$smarty.capture.optOutUrl} -{capture name=iframeOptOut} -{/capture} -{$smarty.capture.iframeOptOut|escape:'html'} -
-{'CoreAdminHome_OptOutExplanationBis'|translate:"":""} + {capture name=optOutUrl}{$piwikUrl}index.php?module=CoreAdminHome&action=optOut&language={$language}{/capture} + {assign var=optOutUrl value=$smarty.capture.optOutUrl} + {capture name=iframeOptOut} + {/capture} + {$smarty.capture.iframeOptOut|escape:'html'} +
+ {'CoreAdminHome_OptOutExplanationBis'|translate:"":""}

diff --git a/plugins/Provider/API.php b/plugins/Provider/API.php index d2fe4b3bee..8ce5a43703 100644 --- a/plugins/Provider/API.php +++ b/plugins/Provider/API.php @@ -1,10 +1,10 @@ getDataTable('Provider_hostnameExt'); - $dataTable->filter('Sort', array(Piwik_Archive::INDEX_NB_VISITS)); - $dataTable->queueFilter('ColumnCallbackAddMetadata', array('label', 'url', 'Piwik_getHostnameUrl')); - $dataTable->queueFilter('ColumnCallbackReplace', array('label', 'Piwik_getHostnameName')); - $dataTable->queueFilter('ReplaceColumnNames'); - $dataTable->queueFilter('ReplaceSummaryRowLabel'); - return $dataTable; - } + static public function getInstance() + { + if (self::$instance == null) { + self::$instance = new self; + } + return self::$instance; + } + + public function getProvider($idSite, $period, $date, $segment = false) + { + 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->queueFilter('ColumnCallbackAddMetadata', array('label', 'url', 'Piwik_getHostnameUrl')); + $dataTable->queueFilter('ColumnCallbackReplace', array('label', 'Piwik_getHostnameName')); + $dataTable->queueFilter('ReplaceColumnNames'); + $dataTable->queueFilter('ReplaceSummaryRowLabel'); + return $dataTable; + } } diff --git a/plugins/Provider/Controller.php b/plugins/Provider/Controller.php index 76ae424841..f59b8d1b29 100644 --- a/plugins/Provider/Controller.php +++ b/plugins/Provider/Controller.php @@ -1,10 +1,10 @@ init( $this->pluginName, __FUNCTION__, "Provider.getProvider" ); - - $this->setPeriodVariablesView($view); - $column = 'nb_visits'; - if($view->period == 'day') - { - $column = 'nb_uniq_visitors'; - } - $view->setColumnsToDisplay( array('label',$column) ); - $view->setColumnTranslation('label', Piwik_Translate('Provider_ColumnProvider')); - $view->setSortedColumn( $column ); - $view->setLimit( 5 ); - $this->setMetricsVariablesView($view); - return $this->renderView($view, $fetch); - } - + /** + * Provider + * @param bool $fetch + * @return string|void + */ + function getProvider($fetch = false) + { + $view = Piwik_ViewDataTable::factory(); + $view->init($this->pluginName, __FUNCTION__, "Provider.getProvider"); + + $this->setPeriodVariablesView($view); + $column = 'nb_visits'; + if ($view->period == 'day') { + $column = 'nb_uniq_visitors'; + } + $view->setColumnsToDisplay(array('label', $column)); + $view->setColumnTranslation('label', Piwik_Translate('Provider_ColumnProvider')); + $view->setSortedColumn($column); + $view->setLimit(5); + $this->setMetricsVariablesView($view); + return $this->renderView($view, $fetch); + } + } diff --git a/plugins/Provider/Provider.php b/plugins/Provider/Provider.php index 8dba1153ae..64d18ace74 100644 --- a/plugins/Provider/Provider.php +++ b/plugins/Provider/Provider.php @@ -15,252 +15,239 @@ */ class Piwik_Provider extends Piwik_Plugin { - public function getInformation() - { - $info = array( - 'description' => Piwik_Translate('Provider_PluginDescription'), - 'author' => 'Piwik', - 'author_homepage' => 'http://piwik.org/', - 'version' => Piwik_Version::VERSION, - 'TrackerPlugin' => true, // this plugin must be loaded during the stats logging - ); - - return $info; - } - - public function getListHooksRegistered() - { - $hooks = array( - 'ArchiveProcessing_Day.compute' => 'archiveDay', - 'ArchiveProcessing_Period.compute' => 'archivePeriod', - 'Tracker.newVisitorInformation' => 'logProviderInfo', - 'WidgetsList.add' => 'addWidget', - 'Menu.add' => 'addMenu', - 'API.getReportMetadata' => 'getReportMetadata', - 'API.getSegmentsMetadata' => 'getSegmentsMetadata', - ); - return $hooks; - } - - /** - * @param Piwik_Event_Notification $notification notification object - */ - public function getReportMetadata($notification) - { - $reports = &$notification->getNotificationObject(); - $reports[] = array( - 'category' => Piwik_Translate('General_Visitors'), - 'name' => Piwik_Translate('Provider_ColumnProvider'), - 'module' => 'Provider', - 'action' => 'getProvider', - 'dimension' => Piwik_Translate('Provider_ColumnProvider'), - 'documentation' => Piwik_Translate('Provider_ProviderReportDocumentation', '
'), - 'order' => 50 - ); - } - - /** - * @param Piwik_Event_Notification $notification notification object - */ - public function getSegmentsMetadata($notification) - { - $segments =& $notification->getNotificationObject(); - $segments[] = array( - 'type' => 'dimension', - 'category' => 'Visit Location', - 'name' => Piwik_Translate('Provider_ColumnProvider'), - 'segment' => 'provider', - 'acceptedValues' => 'comcast.net, proxad.net, etc.', - 'sqlSegment' => 'log_visit.location_provider' - ); - } - - function install() - { - // add column hostname / hostname ext in the visit table - $query = "ALTER IGNORE TABLE `".Piwik_Common::prefixTable('log_visit')."` ADD `location_provider` VARCHAR( 100 ) NULL"; - - // if the column already exist do not throw error. Could be installed twice... - try { - Piwik_Exec($query); - } - catch(Exception $e) { - if(!Zend_Registry::get('db')->isErrNo($e, '1060')) - { - throw $e; - } - } - - } - - function uninstall() - { - // add column hostname / hostname ext in the visit table - $query = "ALTER TABLE `".Piwik_Common::prefixTable('log_visit')."` DROP `location_provider`"; - Piwik_Exec($query); - } - - function addWidget() - { - Piwik_AddWidget( 'General_Visitors', 'Provider_WidgetProviders', 'Provider', 'getProvider'); - } - - function addMenu() - { - Piwik_RenameMenuEntry( 'General_Visitors', 'UserCountry_SubmenuLocations', - 'General_Visitors', 'Provider_SubmenuLocationsProvider'); - } - - function postLoad() - { - Piwik_AddAction('template_footerUserCountry', array('Piwik_Provider','footerUserCountry')); - } - - /** - * @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 - */ - public function logProviderInfo($notification) - { - $visitorInfo =& $notification->getNotificationObject(); - - // if provider info has already been set, abort - if (!empty($visitorInfo['location_provider'])) - { - return; - } - - $ip = Piwik_IP::N2P($visitorInfo['location_ip']); - - // In case the IP was anonymized, we should not continue since the DNS reverse lookup will fail and this will slow down tracking - if(substr($ip, -2, 2) == '.0') - { - printDebug("IP Was anonymized so we skip the Provider DNS reverse lookup..."); - return; - } - - $hostname = $this->getHost($ip); - $hostnameExtension = $this->getCleanHostname($hostname); - - // add the provider value in the table log_visit - $visitorInfo['location_provider'] = $hostnameExtension; - $visitorInfo['location_provider'] = substr($visitorInfo['location_provider'], 0, 100); - - // improve the country using the provider extension if valid - $hostnameDomain = substr($hostnameExtension, 1 + strrpos($hostnameExtension, '.')); - if($hostnameDomain == 'uk') - { - $hostnameDomain = 'gb'; - } - if(array_key_exists($hostnameDomain, Piwik_Common::getCountriesList())) - { - $visitorInfo['location_country'] = $hostnameDomain; - } - } - - /** - * Returns the hostname extension (site.co.jp in fvae.VARG.ceaga.site.co.jp) - * given the full hostname looked up from the IP - * - * @param string $hostname - * - * @return string - */ - private function getCleanHostname($hostname) - { - $extToExclude = array( - 'com', 'net', 'org', 'co' - ); - - $off = strrpos($hostname, '.'); - $ext = substr($hostname, $off); - - if(empty($off) || is_numeric($ext) || strlen($hostname) < 5) - { - return 'Ip'; - } - else - { - $cleanHostname = null; - Piwik_PostEvent('Provider.getCleanHostname', $cleanHostname, $hostname); - if($cleanHostname !== null) - { - return $cleanHostname; - } - - $e = explode('.', $hostname); - $s = sizeof($e); - - // if extension not correct - if(isset($e[$s-2]) && in_array($e[$s-2], $extToExclude)) - { - return $e[$s-3].".".$e[$s-2].".".$e[$s-1]; - } - else - { - return $e[$s-2].".".$e[$s-1]; - } - } - } - - /** - * Returns the hostname given the IP address string - * - * @param string $ip IP Address - * @return string hostname (or human-readable IP address) - */ - private function getHost($ip) - { - return trim(strtolower(@Piwik_IP::getHostByAddr($ip))); - } - - /** - * @param Piwik_Event_Notification $notification notification object - */ - static public function footerUserCountry($notification) - { - $out =& $notification->getNotificationObject(); - $out = '
-

'.Piwik_Translate('Provider_WidgetProviders').'

'; - $out .= Piwik_FrontController::getInstance()->fetchDispatch('Provider','getProvider'); - $out .= '
'; - } + public function getInformation() + { + $info = array( + 'description' => Piwik_Translate('Provider_PluginDescription'), + 'author' => 'Piwik', + 'author_homepage' => 'http://piwik.org/', + 'version' => Piwik_Version::VERSION, + 'TrackerPlugin' => true, // this plugin must be loaded during the stats logging + ); + + return $info; + } + + public function getListHooksRegistered() + { + $hooks = array( + 'ArchiveProcessing_Day.compute' => 'archiveDay', + 'ArchiveProcessing_Period.compute' => 'archivePeriod', + 'Tracker.newVisitorInformation' => 'logProviderInfo', + 'WidgetsList.add' => 'addWidget', + 'Menu.add' => 'addMenu', + 'API.getReportMetadata' => 'getReportMetadata', + 'API.getSegmentsMetadata' => 'getSegmentsMetadata', + ); + return $hooks; + } + + /** + * @param Piwik_Event_Notification $notification notification object + */ + public function getReportMetadata($notification) + { + $reports = & $notification->getNotificationObject(); + $reports[] = array( + 'category' => Piwik_Translate('General_Visitors'), + 'name' => Piwik_Translate('Provider_ColumnProvider'), + 'module' => 'Provider', + 'action' => 'getProvider', + 'dimension' => Piwik_Translate('Provider_ColumnProvider'), + 'documentation' => Piwik_Translate('Provider_ProviderReportDocumentation', '
'), + 'order' => 50 + ); + } + + /** + * @param Piwik_Event_Notification $notification notification object + */ + public function getSegmentsMetadata($notification) + { + $segments =& $notification->getNotificationObject(); + $segments[] = array( + 'type' => 'dimension', + 'category' => 'Visit Location', + 'name' => Piwik_Translate('Provider_ColumnProvider'), + 'segment' => 'provider', + 'acceptedValues' => 'comcast.net, proxad.net, etc.', + 'sqlSegment' => 'log_visit.location_provider' + ); + } + + function install() + { + // add column hostname / hostname ext in the visit table + $query = "ALTER IGNORE TABLE `" . Piwik_Common::prefixTable('log_visit') . "` ADD `location_provider` VARCHAR( 100 ) NULL"; + + // if the column already exist do not throw error. Could be installed twice... + try { + Piwik_Exec($query); + } catch (Exception $e) { + if (!Zend_Registry::get('db')->isErrNo($e, '1060')) { + throw $e; + } + } + + } + + function uninstall() + { + // add column hostname / hostname ext in the visit table + $query = "ALTER TABLE `" . Piwik_Common::prefixTable('log_visit') . "` DROP `location_provider`"; + Piwik_Exec($query); + } + + function addWidget() + { + Piwik_AddWidget('General_Visitors', 'Provider_WidgetProviders', 'Provider', 'getProvider'); + } + + function addMenu() + { + Piwik_RenameMenuEntry('General_Visitors', 'UserCountry_SubmenuLocations', + 'General_Visitors', 'Provider_SubmenuLocationsProvider'); + } + + function postLoad() + { + Piwik_AddAction('template_footerUserCountry', array('Piwik_Provider', 'footerUserCountry')); + } + + /** + * @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 + */ + public function logProviderInfo($notification) + { + $visitorInfo =& $notification->getNotificationObject(); + + // if provider info has already been set, abort + if (!empty($visitorInfo['location_provider'])) { + return; + } + + $ip = Piwik_IP::N2P($visitorInfo['location_ip']); + + // In case the IP was anonymized, we should not continue since the DNS reverse lookup will fail and this will slow down tracking + if (substr($ip, -2, 2) == '.0') { + printDebug("IP Was anonymized so we skip the Provider DNS reverse lookup..."); + return; + } + + $hostname = $this->getHost($ip); + $hostnameExtension = $this->getCleanHostname($hostname); + + // add the provider value in the table log_visit + $visitorInfo['location_provider'] = $hostnameExtension; + $visitorInfo['location_provider'] = substr($visitorInfo['location_provider'], 0, 100); + + // improve the country using the provider extension if valid + $hostnameDomain = substr($hostnameExtension, 1 + strrpos($hostnameExtension, '.')); + if ($hostnameDomain == 'uk') { + $hostnameDomain = 'gb'; + } + if (array_key_exists($hostnameDomain, Piwik_Common::getCountriesList())) { + $visitorInfo['location_country'] = $hostnameDomain; + } + } + + /** + * Returns the hostname extension (site.co.jp in fvae.VARG.ceaga.site.co.jp) + * given the full hostname looked up from the IP + * + * @param string $hostname + * + * @return string + */ + private function getCleanHostname($hostname) + { + $extToExclude = array( + 'com', 'net', 'org', 'co' + ); + + $off = strrpos($hostname, '.'); + $ext = substr($hostname, $off); + + if (empty($off) || is_numeric($ext) || strlen($hostname) < 5) { + return 'Ip'; + } else { + $cleanHostname = null; + Piwik_PostEvent('Provider.getCleanHostname', $cleanHostname, $hostname); + if ($cleanHostname !== null) { + return $cleanHostname; + } + + $e = explode('.', $hostname); + $s = sizeof($e); + + // if extension not correct + if (isset($e[$s - 2]) && in_array($e[$s - 2], $extToExclude)) { + return $e[$s - 3] . "." . $e[$s - 2] . "." . $e[$s - 1]; + } else { + return $e[$s - 2] . "." . $e[$s - 1]; + } + } + } + + /** + * Returns the hostname given the IP address string + * + * @param string $ip IP Address + * @return string hostname (or human-readable IP address) + */ + private function getHost($ip) + { + return trim(strtolower(@Piwik_IP::getHostByAddr($ip))); + } + + /** + * @param Piwik_Event_Notification $notification notification object + */ + static public function footerUserCountry($notification) + { + $out =& $notification->getNotificationObject(); + $out = '
+

' . Piwik_Translate('Provider_WidgetProviders') . '

'; + $out .= Piwik_FrontController::getInstance()->fetchDispatch('Provider', 'getProvider'); + $out .= '
'; + } } diff --git a/plugins/Provider/functions.php b/plugins/Provider/functions.php index ec1afd4518..34f09534c0 100644 --- a/plugins/Provider/functions.php +++ b/plugins/Provider/functions.php @@ -1,10 +1,10 @@ imageData = 'data:image/png;base64,'. Piwik_Common::getRequestVar('imageData', self::TRANSPARENT_PNG_PIXEL, 'string', $_POST); - echo $view->render(); - } - - function exportImage() - { - self::exportImageWindow(); - } - - /** - * Output binary image from base-64 encoded data. - * - * @deprecated 1.5.1 - * - * @param string $imageData Base-64 encoded image data (via $_POST) - */ - static public function outputBinaryImage() - { - Piwik::checkUserHasSomeViewAccess(); - - $rawData = Piwik_Common::getRequestVar('imageData', '', 'string', $_POST); - - // returns false if any illegal characters in input - $data = base64_decode($rawData); - if($data !== false) - { - // check for PNG header - if(Piwik_Common::substr($data, 0, 8) === "\x89\x50\x4e\x47\x0d\x0a\x1a\x0a") - { - header('Content-Type: image/png'); - - // more robust validation (if available) - if(function_exists('imagecreatefromstring')) - { - // validate image data - $imgResource = @imagecreatefromstring($data); - if($imgResource !== false) - { - // output image and clean-up - imagepng($imgResource); - imagedestroy($imgResource); - exit; - } - } - else - { - echo $data; - exit; - } - } - } - - Piwik::setHttpStatus('400 Bad Request'); - exit; - } - - function outputImage() - { - self::outputBinaryImage(); - } - - /** - * Output the merged CSS file. - * This method is called when the asset manager is enabled. - * - * @see core/AssetManager.php - */ - public function getCss() - { - $cssMergedFile = Piwik_AssetManager::getMergedCssFileLocation(); - Piwik::serveStaticFile($cssMergedFile, "text/css"); - } - - /** - * Output the merged JavaScript file. - * This method is called when the asset manager is enabled. - * - * @see core/AssetManager.php - */ - public function getJs() - { - $jsMergedFile = Piwik_AssetManager::getMergedJsFileLocation(); - Piwik::serveStaticFile($jsMergedFile, "application/javascript; charset=UTF-8"); - } - - /** - * Output redirection page instead of linking directly to avoid - * exposing the referrer on the Piwik demo. - * - * @param string $url (via $_GET) - */ - public function redirect() - { - $url = Piwik_Common::getRequestVar('url', '', 'string', $_GET); - - // validate referrer - $referrer = Piwik_Url::getReferer(); - if(empty($referrer) || !Piwik_Url::isLocalUrl($referrer)) - { - die('Invalid Referer detected - This means that your web browser is not sending the "Referer URL" which is +{ + const TRANSPARENT_PNG_PIXEL = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEUAAACnej3aAAAAAXRSTlMAQObYZgAAAApJREFUCNdjYAAAAAIAAeIhvDMAAAAASUVORK5CYII='; + + /** + * Display the "Export Image" window. + * + * @deprecated 1.5.1 + * + * @param string $imageData Base-64 encoded image data (via $_POST) + */ + static public function exportImageWindow() + { + Piwik::checkUserHasSomeViewAccess(); + + $view = Piwik_View::factory('exportImage'); + $view->imageData = 'data:image/png;base64,' . Piwik_Common::getRequestVar('imageData', self::TRANSPARENT_PNG_PIXEL, 'string', $_POST); + echo $view->render(); + } + + function exportImage() + { + self::exportImageWindow(); + } + + /** + * Output binary image from base-64 encoded data. + * + * @deprecated 1.5.1 + * + * @param string $imageData Base-64 encoded image data (via $_POST) + */ + static public function outputBinaryImage() + { + Piwik::checkUserHasSomeViewAccess(); + + $rawData = Piwik_Common::getRequestVar('imageData', '', 'string', $_POST); + + // returns false if any illegal characters in input + $data = base64_decode($rawData); + if ($data !== false) { + // check for PNG header + if (Piwik_Common::substr($data, 0, 8) === "\x89\x50\x4e\x47\x0d\x0a\x1a\x0a") { + header('Content-Type: image/png'); + + // more robust validation (if available) + if (function_exists('imagecreatefromstring')) { + // validate image data + $imgResource = @imagecreatefromstring($data); + if ($imgResource !== false) { + // output image and clean-up + imagepng($imgResource); + imagedestroy($imgResource); + exit; + } + } else { + echo $data; + exit; + } + } + } + + Piwik::setHttpStatus('400 Bad Request'); + exit; + } + + function outputImage() + { + self::outputBinaryImage(); + } + + /** + * Output the merged CSS file. + * This method is called when the asset manager is enabled. + * + * @see core/AssetManager.php + */ + public function getCss() + { + $cssMergedFile = Piwik_AssetManager::getMergedCssFileLocation(); + Piwik::serveStaticFile($cssMergedFile, "text/css"); + } + + /** + * Output the merged JavaScript file. + * This method is called when the asset manager is enabled. + * + * @see core/AssetManager.php + */ + public function getJs() + { + $jsMergedFile = Piwik_AssetManager::getMergedJsFileLocation(); + Piwik::serveStaticFile($jsMergedFile, "application/javascript; charset=UTF-8"); + } + + /** + * Output redirection page instead of linking directly to avoid + * exposing the referrer on the Piwik demo. + * + * @param string $url (via $_GET) + */ + public function redirect() + { + $url = Piwik_Common::getRequestVar('url', '', 'string', $_GET); + + // validate referrer + $referrer = Piwik_Url::getReferer(); + if (empty($referrer) || !Piwik_Url::isLocalUrl($referrer)) { + die('Invalid Referer detected - This means that your web browser is not sending the "Referer URL" which is required to proceed with the redirect. Verify your browser settings and add-ons, to check why your browser is not sending this referer. -

You can access the page at: '. $url); - } - - // mask visits to *.piwik.org - if (!self::isPiwikUrl($url)) - { - Piwik::checkUserHasSomeViewAccess(); - } - if(!Piwik_Common::isLookLikeUrl($url)) - { - die('Please check the &url= parameter: it should to be a valid URL'); - } - @header('Content-Type: text/html; charset=utf-8'); - echo ''; - - exit; - } - - /** - * Validate URL against *.piwik.org domains - * - * @param string $url - * @return bool True if valid; false otherwise - */ - static public function isPiwikUrl($url) - { - // guard for IE6 meta refresh parsing weakness (OSVDB 19029) - if(strpos($url, ';') !== false - || strpos($url, ';') !== false) - { - return false; - } - if(preg_match('~^http://(qa\.|demo\.|dev\.|forum\.)?piwik.org([#?/]|$)~', $url)) - { - return true; - } - - // Allow clockworksms domain - if(strpos($url, 'http://www.clockworksms.com/') === 0) - { - return true; - } - - return false; - } +

You can access the page at: ' . $url); + } + + // mask visits to *.piwik.org + if (!self::isPiwikUrl($url)) { + Piwik::checkUserHasSomeViewAccess(); + } + if (!Piwik_Common::isLookLikeUrl($url)) { + die('Please check the &url= parameter: it should to be a valid URL'); + } + @header('Content-Type: text/html; charset=utf-8'); + echo ''; + + exit; + } + + /** + * Validate URL against *.piwik.org domains + * + * @param string $url + * @return bool True if valid; false otherwise + */ + static public function isPiwikUrl($url) + { + // guard for IE6 meta refresh parsing weakness (OSVDB 19029) + if (strpos($url, ';') !== false + || strpos($url, ';') !== false + ) { + return false; + } + if (preg_match('~^http://(qa\.|demo\.|dev\.|forum\.)?piwik.org([#?/]|$)~', $url)) { + return true; + } + + // Allow clockworksms domain + if (strpos($url, 'http://www.clockworksms.com/') === 0) { + return true; + } + + return false; + } } diff --git a/plugins/Proxy/Proxy.php b/plugins/Proxy/Proxy.php index 619c388644..c669215411 100644 --- a/plugins/Proxy/Proxy.php +++ b/plugins/Proxy/Proxy.php @@ -1,10 +1,10 @@ 'Proxy services', - 'author' => 'Piwik', - 'author_homepage' => 'http://piwik.org/', - 'version' => Piwik_Version::VERSION, - 'translationAvailable' => false, - ); - } + /** + * Return information about this plugin. + * + * @see Piwik_Plugin + * + * @return array + */ + public function getInformation() + { + return array( + 'description' => 'Proxy services', + 'author' => 'Piwik', + 'author_homepage' => 'http://piwik.org/', + 'version' => Piwik_Version::VERSION, + 'translationAvailable' => false, + ); + } } diff --git a/plugins/Proxy/templates/exportImage.tpl b/plugins/Proxy/templates/exportImage.tpl index 037b0b8fd8..451b5dedb6 100644 --- a/plugins/Proxy/templates/exportImage.tpl +++ b/plugins/Proxy/templates/exportImage.tpl @@ -1,12 +1,13 @@ + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd" /> - - - {'General_ExportAsImage_js'|translate} - - -

-

{'General_SaveImageOnYourComputer_js'|translate}

- + + + {'General_ExportAsImage_js'|translate} + + +

+ +

{'General_SaveImageOnYourComputer_js'|translate}

+ diff --git a/plugins/Referers/API.php b/plugins/Referers/API.php index a74f14290c..ade4026c5b 100644 --- a/plugins/Referers/API.php +++ b/plugins/Referers/API.php @@ -11,510 +11,477 @@ /** * The Referrers API lets you access reports about Websites, Search engines, Keywords, Campaigns used to access your website. - * - * For example, "getKeywords" returns all search engine keywords (with general analytics metrics for each keyword), "getWebsites" returns referrer websites (along with the full Referrer URL if the parameter &expanded=1 is set). + * + * For example, "getKeywords" returns all search engine keywords (with general analytics metrics for each keyword), "getWebsites" returns referrer websites (along with the full Referrer URL if the parameter &expanded=1 is set). * "getRefererType" returns the Referrer overview report. "getCampaigns" returns the list of all campaigns (and all campaign keywords if the parameter &expanded=1 is set). - * - * The methods "getKeywordsForPageUrl" and "getKeywordsForPageTitle" are used to output the top keywords used to find a page. + * + * The methods "getKeywordsForPageUrl" and "getKeywordsForPageTitle" are used to output the top keywords used to find a page. * Check out the widget "Top keywords used to find this page" that you can easily re-use on your website. * @package Piwik_Referers */ -class Piwik_Referers_API +class Piwik_Referers_API { - static private $instance = null; - static public function getInstance() - { - if (self::$instance == null) - { - self::$instance = new self; - } - return self::$instance; - } - - /** - * @return Piwik_DataTable - */ - 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->queueFilter('ReplaceColumnNames'); - return $dataTable; - } - - /** - * Returns a report describing visit information for each possible referrer type. The - * result is a datatable whose subtables are the reports for each parent row's referrer type. - * - * The subtable reports are: 'getKeywords' (for search engine referrer type), 'getWebsites', - * and 'getCampaigns'. - * - * @param string $idSite The site ID. - * @param string $period The period to get data for, either 'day', 'week', 'month', 'year', - * or 'range'. - * @param string $date The date of the period. - * @param string $segment The segment to use. - * @param int $typeReferer (deprecated) If you want to get data only for a specific referrer - * type, supply a type for this parameter. - * @param int $idSubtable For this report this value is a referrer type ID and not an actual - * subtable ID. The result when using this parameter will be the - * specific report for the given referrer type. - * @param bool $expanded Whether to get report w/ subtables loaded or not. - * @return Piwik_DataTable - */ - public function getRefererType($idSite, $period, $date, $segment = false, $typeReferer = false, - $idSubtable = false, $expanded = false) - { - // if idSubtable is supplied, interpret idSubtable as referrer type and return correct report - if ($idSubtable !== false) - { - $result = false; - switch ($idSubtable) - { - case Piwik_Common::REFERER_TYPE_SEARCH_ENGINE: - $result = $this->getKeywords($idSite, $period, $date, $segment); - break; - case Piwik_Common::REFERER_TYPE_WEBSITE: - $result = $this->getWebsites($idSite, $period, $date, $segment); - break; - case Piwik_Common::REFERER_TYPE_CAMPAIGN: - $result = $this->getCampaigns($idSite, $period, $date, $segment); - break; - default: // invalid idSubtable, return whole report - break; - } - - if ($result) - { - return $this->removeSubtableIds($result); // this report won't return subtables of individual reports - } - } - - // get visits by referrer type - $dataTable = $this->getDataTable('Referers_type', $idSite, $period, $date, $segment); - - if ($typeReferer !== false) // filter for a specific referrer type - { - $dataTable->filter('Pattern', array('label', $typeReferer)); - } - - // set subtable IDs for each row to the label (which holds the int referrer type) - // NOTE: not yet possible to do this w/ DataTable_Array instances - if (!($dataTable instanceof Piwik_DataTable_Array)) - { - $this->setGetReferrerTypeSubtables($dataTable, $idSite, $period, $date, $segment, $expanded); - } - - // set referrer type column to readable value - $dataTable->queueFilter('ColumnCallbackReplace', array('label', 'Piwik_getRefererTypeLabel')); - - return $dataTable; - } - - /** - * Returns a report that shows - */ - public function getAll( $idSite, $period, $date, $segment = false ) - { - $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)."); - } - - $dataTable = $dataTable->mergeSubtables($labelColumn = 'referrer_type', $useMetadataColumn = true); - - // presentation filters - $dataTable->filter('Sort', array(Piwik_Archive::INDEX_NB_VISITS, 'desc')); - $dataTable->queueFilter('ReplaceColumnNames'); - $dataTable->queueFilter('ReplaceSummaryRowLabel'); - - return $dataTable; - } - - public function getKeywords($idSite, $period, $date, $segment = false, $expanded = false) - { - $dataTable = $this->getDataTable('Referers_searchEngineByKeyword', $idSite, $period, $date, $segment, $expanded); - $dataTable = $this->handleKeywordNotDefined($dataTable); - return $dataTable; - } - - protected function handleKeywordNotDefined($dataTable) - { - $dataTable->queueFilter('ColumnCallbackReplace', array('label', array('Piwik_Referers', 'getCleanKeyword'))); - return $dataTable; - } - - public function getKeywordsForPageUrl($idSite, $period, $date, $url) - { - // Fetch the Top keywords for this page - $segment = 'entryPageUrl=='.$url; - $table = $this->getKeywords($idSite, $period, $date, $segment); - $this->filterOutKeywordNotDefined($table); - return $this->getLabelsFromTable($table); - - } - - public function getKeywordsForPageTitle($idSite, $period, $date, $title) - { - $segment = 'entryPageTitle=='.$title; - $table = $this->getKeywords($idSite, $period, $date, $segment); - $this->filterOutKeywordNotDefined($table); - return $this->getLabelsFromTable($table); - } - - /** - * @param Piwik_Datatable $table - */ - private function filterOutKeywordNotDefined($table) - { - if($table instanceof Piwik_Datatable) - { - $row = $table->getRowIdFromLabel(''); - if($row) - { - $table->deleteRow($row); - } - } - } - - protected function getLabelsFromTable($table) - { - $request = $_GET; - $request['serialize'] = 0; - - // Apply generic filters - $response = new Piwik_API_ResponseBuilder($format = 'original', $request); - $table = $response->getResponse($table); - - // If period=lastX we only keep the first resultset as we want to return a plain list - if($table instanceof Piwik_DataTable_Array) - { - $tables = $table->getArray(); - $table = current($tables); - } - // Keep the response simple, only include keywords - $keywords = $table->getColumn('label'); - return $keywords; - } - - public function getSearchEnginesFromKeywordId($idSite, $period, $date, $idSubtable, $segment = false) - { - $dataTable = $this->getDataTable('Referers_searchEngineByKeyword',$idSite, $period, $date, $segment, $expanded = false, $idSubtable); - $dataTable->queueFilter('ColumnCallbackAddMetadata', array( 'label', 'url', 'Piwik_getSearchEngineUrlFromName') ); - $dataTable->queueFilter('MetadataCallbackAddMetadata', array( 'url', 'logo', 'Piwik_getSearchEngineLogoFromUrl') ); - - // get the keyword and create the URL to the search result page - $keywords = $this->getKeywords($idSite, $period, $date, $segment); - $subTable = $keywords->getRowFromIdSubDataTable($idSubtable); - if($subTable) - { - $keyword = $subTable->getColumn('label'); - $dataTable->queueFilter('MetadataCallbackReplace', array( 'url', 'Piwik_getSearchEngineUrlFromUrlAndKeyword', array($keyword)) ); - } - return $dataTable; - } - - public function getSearchEngines($idSite, $period, $date, $segment = false, $expanded = false) - { - $dataTable = $this->getDataTable('Referers_keywordBySearchEngine',$idSite, $period, $date, $segment, $expanded); - $dataTable->queueFilter('ColumnCallbackAddMetadata', array( 'label', 'url', 'Piwik_getSearchEngineUrlFromName') ); - $dataTable->queueFilter('MetadataCallbackAddMetadata', array( 'url', 'logo', 'Piwik_getSearchEngineLogoFromUrl') ); - return $dataTable; - } - - public function getKeywordsFromSearchEngineId($idSite, $period, $date, $idSubtable, $segment = false) - { - $dataTable = $this->getDataTable('Referers_keywordBySearchEngine',$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); - $searchEngines->applyQueuedFilters(); - $subTable = $searchEngines->getRowFromIdSubDataTable($idSubtable); - if($subTable) - { - $searchEngineUrl = $subTable->getMetadata('url'); - $dataTable->queueFilter('ColumnCallbackAddMetadata', array( 'label', 'url', 'Piwik_getSearchEngineUrlFromKeywordAndUrl', array($searchEngineUrl))); - } - $dataTable = $this->handleKeywordNotDefined($dataTable); - return $dataTable; - } - - public function getCampaigns($idSite, $period, $date, $segment = false, $expanded = false) - { - $dataTable = $this->getDataTable('Referers_keywordByCampaign',$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); - return $dataTable; - } - - public function getWebsites($idSite, $period, $date, $segment = false, $expanded = false) - { - $dataTable = $this->getDataTable('Referers_urlByWebsite',$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); - // 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);')) ); - $dataTable->queueFilter('ColumnCallbackReplace', array('label', 'Piwik_getPathFromUrl')); - return $dataTable; - } - - /** - * Returns report comparing the number of visits (and other info) for social network referrers. - * This is a view of the getWebsites report. - * - * @param string $idSite - * @param string $period - * @param string $date - * @param string|bool $segment - * @param bool $expanded - * @return Piwik_DataTable - */ - public function getSocials($idSite, $period, $date, $segment = false, $expanded = false) - { - require PIWIK_INCLUDE_PATH.'/core/DataFiles/Socials.php'; - - $dataTable = $this->getDataTable('Referers_urlByWebsite', $idSite, $period, $date, $segment, $expanded); - - $dataTable->filter('ColumnCallbackDeleteRow', array('label', 'Piwik_Referrers_isSocialUrl')); - - $dataTable->filter('ColumnCallbackAddMetadata', array('label', 'url', 'Piwik_Referrers_cleanSocialUrl')); - $dataTable->filter('GroupBy', array('label', 'Piwik_Referrers_getSocialNetworkFromDomain')); - - $this->setSocialIdSubtables($dataTable); - $this->removeSubtableMetadata($dataTable); - - $dataTable->queueFilter('MetadataCallbackAddMetadata', array('url', 'logo', 'Piwik_getSocialsLogoFromUrl')); - - return $dataTable; - } - - /** - * Returns report containing individual referrer URLs for a specific social networking - * site. - * - * @param string $idSite - * @param string $period - * @param string $date - * @param string|false $segment - * @param int|false $idSubtable This ID does not reference a real DataTable record. Instead, it - * is the array index of an item in the /core/DataFiles/Socials.php file. - * The urls are filtered by the social network at this index. - * If false, no filtering is done and every social URL is returned. - * @return Piwik_DataTable - */ - public function getUrlsForSocial( $idSite, $period, $date, $segment = false, $idSubtable = false ) - { - require PIWIK_INCLUDE_PATH.'/core/DataFiles/Socials.php'; - - $dataTable = $this->getDataTable( - 'Referers_urlByWebsite', $idSite, $period, $date, $segment, $expanded = true); - - // get the social network domain referred to by $idSubtable - $social = false; - if ($idSubtable !== false) - { - --$idSubtable; - - reset($GLOBALS['Piwik_socialUrl']); - for ($i = 0; $i != (int)$idSubtable; ++$i) - { - next($GLOBALS['Piwik_socialUrl']); - } - - $social = current($GLOBALS['Piwik_socialUrl']); - } - - // filter out everything but social network indicated by $idSubtable - $dataTable->filter('ColumnCallbackDeleteRow', array('label', 'Piwik_Referrers_isSocialUrl', array($social))); - - // merge the datatable's subtables which contain the individual URLs - $dataTable = $dataTable->mergeSubtables(); - - // make url labels clickable - $dataTable->filter('ColumnCallbackAddMetadata', array('label', 'url')); - - // 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->queueFilter('ReplaceColumnNames'); - - return $dataTable; - } - - public function getNumberOfDistinctSearchEngines($idSite, $period, $date, $segment = false) - { - return $this->getNumeric('Referers_distinctSearchEngines', $idSite, $period, $date, $segment); - } - - public function getNumberOfDistinctKeywords($idSite, $period, $date, $segment = false) - { - return $this->getNumeric('Referers_distinctKeywords', $idSite, $period, $date, $segment); - } - - public function getNumberOfDistinctCampaigns($idSite, $period, $date, $segment = false) - { - return $this->getNumeric('Referers_distinctCampaigns', $idSite, $period, $date, $segment); - } - - public function getNumberOfDistinctWebsites($idSite, $period, $date, $segment = false) - { - return $this->getNumeric('Referers_distinctWebsites', $idSite, $period, $date, $segment); - } - - public function getNumberOfDistinctWebsitesUrls($idSite, $period, $date, $segment = false) - { - return $this->getNumeric('Referers_distinctWebsitesUrls', $idSite, $period, $date, $segment); - } - - private function getNumeric($name, $idSite, $period, $date, $segment) - { - Piwik::checkUserHasViewAccess( $idSite ); - $archive = Piwik_Archive::build($idSite, $period, $date, $segment ); - return $archive->getDataTableFromNumeric($name); - } - - /** - * Removes idsubdatatable_in_db metadata from a DataTable. Used by Social tables since - * they use fake subtable IDs. - * - * @param Piwik_DataTable $dataTable - */ - private function removeSubtableMetadata( $dataTable ) - { - if ($dataTable instanceof Piwik_DataTable_Array) - { - foreach ($dataTable->getArray() as $childTable) - { - $this->removeSubtableMetadata($childTable); - } - } - else - { - foreach ($dataTable->getRows() as $row) - { - $row->deleteMetadata('idsubdatatable_in_db'); - } - } - } - - /** - * Sets the subtable IDs for the DataTable returned by getSocial. - * - * The IDs are int indexes into the array in /core/DataFiles/Socials.php. - * - * @param Piwik_DataTable $dataTable - */ - private function setSocialIdSubtables( $dataTable ) - { - if ($dataTable instanceof Piwik_DataTable_Array) - { - foreach ($dataTable->getArray() as $childTable) - { - $this->setSocialIdSubtables($childTable); - } - } - else - { - foreach ($dataTable->getRows() as $row) - { - $socialName = $row->getColumn('label'); - - $i = 1; // start at one because idSubtable=0 is equivalent to idSubtable=false - foreach ($GLOBALS['Piwik_socialUrl'] as $domain => $name) - { - if ($name == $socialName) - { - $row->c[Piwik_DataTable_Row::DATATABLE_ASSOCIATED] = $i; - break; - } - - ++$i; - } - } - } - } - - /** - * Utility function that removes the subtable IDs for the subtables of the - * getRefererType report. This avoids infinite recursion in said report (ie, - * the grandchildren of the report will be the original report, and it will - * recurse when trying to get a flat report). - * - * @param Piwik_DataTable $table - * @return Piwik_DataTable Returns $table for convenience. - */ - private function removeSubtableIds( $table ) - { - if ($table instanceof Piwik_DataTable_Array) - { - foreach ($table->getArray() as $childTable) - { - $this->removeSubtableIds($childTable); - } - } - else - { - foreach ($table->getRows() as $row) - { - $row->removeSubtable(); - } - } - - return $table; - } - - /** - * Utility function that sets the subtables for the getRefererType report. - * - * If we're not getting an expanded datatable, the subtable ID is set to each parent - * row's referrer type (stored in the label for the getRefererType report). - * - * If we are getting an expanded datatable, the datatable for the row's referrer - * type is loaded and attached to the appropriate row in the getRefererType report. - * - * @param Piwik_DataTable $dataTable - * @param string $idSite - * @param string $period - * @param string $date - * @param string $segment - * @param bool $expanded - */ - private function setGetReferrerTypeSubtables( $dataTable, $idSite, $period, $date, $segment, $expanded ) - { - foreach ($dataTable->getRows() as $row) - { - $typeReferrer = $row->getColumn('label'); - if ($typeReferrer != Piwik_Common::REFERER_TYPE_DIRECT_ENTRY) - { - if (!$expanded) // if we don't want the expanded datatable, then don't do any extra queries - { - $row->c[Piwik_DataTable_Row::DATATABLE_ASSOCIATED] = $typeReferrer; - } - else // otherwise, we have to get the othe datatables - { - $subtable = $this->getRefererType($idSite, $period, $date, $segment, $type = false, - $idSubtable = $typeReferrer); - - if ($expanded) - { - $subtable->applyQueuedFilters(); - } - - $row->setSubtable($subtable); - } - } - } - } + static private $instance = null; + + static public function getInstance() + { + if (self::$instance == null) { + self::$instance = new self; + } + return self::$instance; + } + + /** + * @return Piwik_DataTable + */ + 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->queueFilter('ReplaceColumnNames'); + return $dataTable; + } + + /** + * Returns a report describing visit information for each possible referrer type. The + * result is a datatable whose subtables are the reports for each parent row's referrer type. + * + * The subtable reports are: 'getKeywords' (for search engine referrer type), 'getWebsites', + * and 'getCampaigns'. + * + * @param string $idSite The site ID. + * @param string $period The period to get data for, either 'day', 'week', 'month', 'year', + * or 'range'. + * @param string $date The date of the period. + * @param string $segment The segment to use. + * @param int $typeReferer (deprecated) If you want to get data only for a specific referrer + * type, supply a type for this parameter. + * @param int $idSubtable For this report this value is a referrer type ID and not an actual + * subtable ID. The result when using this parameter will be the + * specific report for the given referrer type. + * @param bool $expanded Whether to get report w/ subtables loaded or not. + * @return Piwik_DataTable + */ + public function getRefererType($idSite, $period, $date, $segment = false, $typeReferer = false, + $idSubtable = false, $expanded = false) + { + // if idSubtable is supplied, interpret idSubtable as referrer type and return correct report + if ($idSubtable !== false) { + $result = false; + switch ($idSubtable) { + case Piwik_Common::REFERER_TYPE_SEARCH_ENGINE: + $result = $this->getKeywords($idSite, $period, $date, $segment); + break; + case Piwik_Common::REFERER_TYPE_WEBSITE: + $result = $this->getWebsites($idSite, $period, $date, $segment); + break; + case Piwik_Common::REFERER_TYPE_CAMPAIGN: + $result = $this->getCampaigns($idSite, $period, $date, $segment); + break; + default: // invalid idSubtable, return whole report + break; + } + + if ($result) { + return $this->removeSubtableIds($result); // this report won't return subtables of individual reports + } + } + + // get visits by referrer type + $dataTable = $this->getDataTable('Referers_type', $idSite, $period, $date, $segment); + + if ($typeReferer !== false) // filter for a specific referrer type + { + $dataTable->filter('Pattern', array('label', $typeReferer)); + } + + // set subtable IDs for each row to the label (which holds the int referrer type) + // NOTE: not yet possible to do this w/ DataTable_Array instances + if (!($dataTable instanceof Piwik_DataTable_Array)) { + $this->setGetReferrerTypeSubtables($dataTable, $idSite, $period, $date, $segment, $expanded); + } + + // set referrer type column to readable value + $dataTable->queueFilter('ColumnCallbackReplace', array('label', 'Piwik_getRefererTypeLabel')); + + return $dataTable; + } + + /** + * Returns a report that shows + */ + public function getAll($idSite, $period, $date, $segment = false) + { + $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)."); + } + + $dataTable = $dataTable->mergeSubtables($labelColumn = 'referrer_type', $useMetadataColumn = true); + + // presentation filters + $dataTable->filter('Sort', array(Piwik_Archive::INDEX_NB_VISITS, 'desc')); + $dataTable->queueFilter('ReplaceColumnNames'); + $dataTable->queueFilter('ReplaceSummaryRowLabel'); + + return $dataTable; + } + + public function getKeywords($idSite, $period, $date, $segment = false, $expanded = false) + { + $dataTable = $this->getDataTable('Referers_searchEngineByKeyword', $idSite, $period, $date, $segment, $expanded); + $dataTable = $this->handleKeywordNotDefined($dataTable); + return $dataTable; + } + + protected function handleKeywordNotDefined($dataTable) + { + $dataTable->queueFilter('ColumnCallbackReplace', array('label', array('Piwik_Referers', 'getCleanKeyword'))); + return $dataTable; + } + + public function getKeywordsForPageUrl($idSite, $period, $date, $url) + { + // Fetch the Top keywords for this page + $segment = 'entryPageUrl==' . $url; + $table = $this->getKeywords($idSite, $period, $date, $segment); + $this->filterOutKeywordNotDefined($table); + return $this->getLabelsFromTable($table); + + } + + public function getKeywordsForPageTitle($idSite, $period, $date, $title) + { + $segment = 'entryPageTitle==' . $title; + $table = $this->getKeywords($idSite, $period, $date, $segment); + $this->filterOutKeywordNotDefined($table); + return $this->getLabelsFromTable($table); + } + + /** + * @param Piwik_Datatable $table + */ + private function filterOutKeywordNotDefined($table) + { + if ($table instanceof Piwik_Datatable) { + $row = $table->getRowIdFromLabel(''); + if ($row) { + $table->deleteRow($row); + } + } + } + + protected function getLabelsFromTable($table) + { + $request = $_GET; + $request['serialize'] = 0; + + // Apply generic filters + $response = new Piwik_API_ResponseBuilder($format = 'original', $request); + $table = $response->getResponse($table); + + // If period=lastX we only keep the first resultset as we want to return a plain list + if ($table instanceof Piwik_DataTable_Array) { + $tables = $table->getArray(); + $table = current($tables); + } + // Keep the response simple, only include keywords + $keywords = $table->getColumn('label'); + return $keywords; + } + + public function getSearchEnginesFromKeywordId($idSite, $period, $date, $idSubtable, $segment = false) + { + $dataTable = $this->getDataTable('Referers_searchEngineByKeyword', $idSite, $period, $date, $segment, $expanded = false, $idSubtable); + $dataTable->queueFilter('ColumnCallbackAddMetadata', array('label', 'url', 'Piwik_getSearchEngineUrlFromName')); + $dataTable->queueFilter('MetadataCallbackAddMetadata', array('url', 'logo', 'Piwik_getSearchEngineLogoFromUrl')); + + // get the keyword and create the URL to the search result page + $keywords = $this->getKeywords($idSite, $period, $date, $segment); + $subTable = $keywords->getRowFromIdSubDataTable($idSubtable); + if ($subTable) { + $keyword = $subTable->getColumn('label'); + $dataTable->queueFilter('MetadataCallbackReplace', array('url', 'Piwik_getSearchEngineUrlFromUrlAndKeyword', array($keyword))); + } + return $dataTable; + } + + public function getSearchEngines($idSite, $period, $date, $segment = false, $expanded = false) + { + $dataTable = $this->getDataTable('Referers_keywordBySearchEngine', $idSite, $period, $date, $segment, $expanded); + $dataTable->queueFilter('ColumnCallbackAddMetadata', array('label', 'url', 'Piwik_getSearchEngineUrlFromName')); + $dataTable->queueFilter('MetadataCallbackAddMetadata', array('url', 'logo', 'Piwik_getSearchEngineLogoFromUrl')); + return $dataTable; + } + + public function getKeywordsFromSearchEngineId($idSite, $period, $date, $idSubtable, $segment = false) + { + $dataTable = $this->getDataTable('Referers_keywordBySearchEngine', $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); + $searchEngines->applyQueuedFilters(); + $subTable = $searchEngines->getRowFromIdSubDataTable($idSubtable); + if ($subTable) { + $searchEngineUrl = $subTable->getMetadata('url'); + $dataTable->queueFilter('ColumnCallbackAddMetadata', array('label', 'url', 'Piwik_getSearchEngineUrlFromKeywordAndUrl', array($searchEngineUrl))); + } + $dataTable = $this->handleKeywordNotDefined($dataTable); + return $dataTable; + } + + public function getCampaigns($idSite, $period, $date, $segment = false, $expanded = false) + { + $dataTable = $this->getDataTable('Referers_keywordByCampaign', $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); + return $dataTable; + } + + public function getWebsites($idSite, $period, $date, $segment = false, $expanded = false) + { + $dataTable = $this->getDataTable('Referers_urlByWebsite', $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); + // 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);'))); + $dataTable->queueFilter('ColumnCallbackReplace', array('label', 'Piwik_getPathFromUrl')); + return $dataTable; + } + + /** + * Returns report comparing the number of visits (and other info) for social network referrers. + * This is a view of the getWebsites report. + * + * @param string $idSite + * @param string $period + * @param string $date + * @param string|bool $segment + * @param bool $expanded + * @return Piwik_DataTable + */ + public function getSocials($idSite, $period, $date, $segment = false, $expanded = false) + { + require PIWIK_INCLUDE_PATH . '/core/DataFiles/Socials.php'; + + $dataTable = $this->getDataTable('Referers_urlByWebsite', $idSite, $period, $date, $segment, $expanded); + + $dataTable->filter('ColumnCallbackDeleteRow', array('label', 'Piwik_Referrers_isSocialUrl')); + + $dataTable->filter('ColumnCallbackAddMetadata', array('label', 'url', 'Piwik_Referrers_cleanSocialUrl')); + $dataTable->filter('GroupBy', array('label', 'Piwik_Referrers_getSocialNetworkFromDomain')); + + $this->setSocialIdSubtables($dataTable); + $this->removeSubtableMetadata($dataTable); + + $dataTable->queueFilter('MetadataCallbackAddMetadata', array('url', 'logo', 'Piwik_getSocialsLogoFromUrl')); + + return $dataTable; + } + + /** + * Returns report containing individual referrer URLs for a specific social networking + * site. + * + * @param string $idSite + * @param string $period + * @param string $date + * @param string|false $segment + * @param int|false $idSubtable This ID does not reference a real DataTable record. Instead, it + * is the array index of an item in the /core/DataFiles/Socials.php file. + * The urls are filtered by the social network at this index. + * If false, no filtering is done and every social URL is returned. + * @return Piwik_DataTable + */ + public function getUrlsForSocial($idSite, $period, $date, $segment = false, $idSubtable = false) + { + require PIWIK_INCLUDE_PATH . '/core/DataFiles/Socials.php'; + + $dataTable = $this->getDataTable( + 'Referers_urlByWebsite', $idSite, $period, $date, $segment, $expanded = true); + + // get the social network domain referred to by $idSubtable + $social = false; + if ($idSubtable !== false) { + --$idSubtable; + + reset($GLOBALS['Piwik_socialUrl']); + for ($i = 0; $i != (int)$idSubtable; ++$i) { + next($GLOBALS['Piwik_socialUrl']); + } + + $social = current($GLOBALS['Piwik_socialUrl']); + } + + // filter out everything but social network indicated by $idSubtable + $dataTable->filter('ColumnCallbackDeleteRow', array('label', 'Piwik_Referrers_isSocialUrl', array($social))); + + // merge the datatable's subtables which contain the individual URLs + $dataTable = $dataTable->mergeSubtables(); + + // make url labels clickable + $dataTable->filter('ColumnCallbackAddMetadata', array('label', 'url')); + + // 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->queueFilter('ReplaceColumnNames'); + + return $dataTable; + } + + public function getNumberOfDistinctSearchEngines($idSite, $period, $date, $segment = false) + { + return $this->getNumeric('Referers_distinctSearchEngines', $idSite, $period, $date, $segment); + } + + public function getNumberOfDistinctKeywords($idSite, $period, $date, $segment = false) + { + return $this->getNumeric('Referers_distinctKeywords', $idSite, $period, $date, $segment); + } + + public function getNumberOfDistinctCampaigns($idSite, $period, $date, $segment = false) + { + return $this->getNumeric('Referers_distinctCampaigns', $idSite, $period, $date, $segment); + } + + public function getNumberOfDistinctWebsites($idSite, $period, $date, $segment = false) + { + return $this->getNumeric('Referers_distinctWebsites', $idSite, $period, $date, $segment); + } + + public function getNumberOfDistinctWebsitesUrls($idSite, $period, $date, $segment = false) + { + return $this->getNumeric('Referers_distinctWebsitesUrls', $idSite, $period, $date, $segment); + } + + private function getNumeric($name, $idSite, $period, $date, $segment) + { + Piwik::checkUserHasViewAccess($idSite); + $archive = Piwik_Archive::build($idSite, $period, $date, $segment); + return $archive->getDataTableFromNumeric($name); + } + + /** + * Removes idsubdatatable_in_db metadata from a DataTable. Used by Social tables since + * they use fake subtable IDs. + * + * @param Piwik_DataTable $dataTable + */ + private function removeSubtableMetadata($dataTable) + { + if ($dataTable instanceof Piwik_DataTable_Array) { + foreach ($dataTable->getArray() as $childTable) { + $this->removeSubtableMetadata($childTable); + } + } else { + foreach ($dataTable->getRows() as $row) { + $row->deleteMetadata('idsubdatatable_in_db'); + } + } + } + + /** + * Sets the subtable IDs for the DataTable returned by getSocial. + * + * The IDs are int indexes into the array in /core/DataFiles/Socials.php. + * + * @param Piwik_DataTable $dataTable + */ + private function setSocialIdSubtables($dataTable) + { + if ($dataTable instanceof Piwik_DataTable_Array) { + foreach ($dataTable->getArray() as $childTable) { + $this->setSocialIdSubtables($childTable); + } + } else { + foreach ($dataTable->getRows() as $row) { + $socialName = $row->getColumn('label'); + + $i = 1; // start at one because idSubtable=0 is equivalent to idSubtable=false + foreach ($GLOBALS['Piwik_socialUrl'] as $domain => $name) { + if ($name == $socialName) { + $row->c[Piwik_DataTable_Row::DATATABLE_ASSOCIATED] = $i; + break; + } + + ++$i; + } + } + } + } + + /** + * Utility function that removes the subtable IDs for the subtables of the + * getRefererType report. This avoids infinite recursion in said report (ie, + * the grandchildren of the report will be the original report, and it will + * recurse when trying to get a flat report). + * + * @param Piwik_DataTable $table + * @return Piwik_DataTable Returns $table for convenience. + */ + private function removeSubtableIds($table) + { + if ($table instanceof Piwik_DataTable_Array) { + foreach ($table->getArray() as $childTable) { + $this->removeSubtableIds($childTable); + } + } else { + foreach ($table->getRows() as $row) { + $row->removeSubtable(); + } + } + + return $table; + } + + /** + * Utility function that sets the subtables for the getRefererType report. + * + * If we're not getting an expanded datatable, the subtable ID is set to each parent + * row's referrer type (stored in the label for the getRefererType report). + * + * If we are getting an expanded datatable, the datatable for the row's referrer + * type is loaded and attached to the appropriate row in the getRefererType report. + * + * @param Piwik_DataTable $dataTable + * @param string $idSite + * @param string $period + * @param string $date + * @param string $segment + * @param bool $expanded + */ + private function setGetReferrerTypeSubtables($dataTable, $idSite, $period, $date, $segment, $expanded) + { + foreach ($dataTable->getRows() as $row) { + $typeReferrer = $row->getColumn('label'); + if ($typeReferrer != Piwik_Common::REFERER_TYPE_DIRECT_ENTRY) { + if (!$expanded) // if we don't want the expanded datatable, then don't do any extra queries + { + $row->c[Piwik_DataTable_Row::DATATABLE_ASSOCIATED] = $typeReferrer; + } else // otherwise, we have to get the othe datatables + { + $subtable = $this->getRefererType($idSite, $period, $date, $segment, $type = false, + $idSubtable = $typeReferrer); + + if ($expanded) { + $subtable->applyQueuedFilters(); + } + + $row->setSubtable($subtable); + } + } + } + } } diff --git a/plugins/Referers/Controller.php b/plugins/Referers/Controller.php index fbbf3c14e4..bc3cc00131 100644 --- a/plugins/Referers/Controller.php +++ b/plugins/Referers/Controller.php @@ -15,579 +15,561 @@ */ class Piwik_Referers_Controller extends Piwik_Controller { - function index() - { - $view = Piwik_View::factory('index'); - - $view->graphEvolutionReferers = $this->getEvolutionGraph(true, Piwik_Common::REFERER_TYPE_DIRECT_ENTRY, array('nb_visits')); - $view->nameGraphEvolutionReferers = 'ReferersgetEvolutionGraph'; - - // building the referers summary report - $view->dataTableRefererType = $this->getRefererType(true); - - $nameValues = $this->getReferersVisitorsByType(); - - $totalVisits = array_sum($nameValues); - foreach($nameValues as $name => $value) - { - $view->$name = $value; - - // calculate percent of total, if there were any visits - if ($value != 0 - && $totalVisits != 0) - { - $percentName = $name.'Percent'; - $view->$percentName = round(($value / $totalVisits) * 100, 0); - } - } - - // set distinct metrics - $distinctMetrics = $this->getDistinctReferrersMetrics(); - foreach ($distinctMetrics as $name => $value) - { - $view->$name = $value; - } - - // calculate evolution for visit metrics & distinct metrics - list($lastPeriodDate, $ignore) = Piwik_Period_Range::getLastDate(); - if ($lastPeriodDate !== false) - { - $date = Piwik_Common::getRequestVar('date'); - $period = Piwik_Common::getRequestVar('period'); - - $prettyDate = self::getPrettyDate($date, $period); - $prettyLastPeriodDate = self::getPrettyDate($lastPeriodDate, $period); - - // visit metrics - $previousValues = $this->getReferersVisitorsByType($lastPeriodDate); - $this->addEvolutionPropertiesToView($view, $prettyDate, $nameValues, $prettyLastPeriodDate, $previousValues); - - // distinct metrics - $previousValues = $this->getDistinctReferrersMetrics($lastPeriodDate); - $this->addEvolutionPropertiesToView($view, $prettyDate, $distinctMetrics, $prettyLastPeriodDate, $previousValues); - } - - // sparkline for the historical data of the above values - $view->urlSparklineSearchEngines = $this->getReferrerUrlSparkline(Piwik_Common::REFERER_TYPE_SEARCH_ENGINE); - $view->urlSparklineDirectEntry = $this->getReferrerUrlSparkline(Piwik_Common::REFERER_TYPE_DIRECT_ENTRY); - $view->urlSparklineWebsites = $this->getReferrerUrlSparkline(Piwik_Common::REFERER_TYPE_WEBSITE); - $view->urlSparklineCampaigns = $this->getReferrerUrlSparkline(Piwik_Common::REFERER_TYPE_CAMPAIGN); - - // sparklines for the evolution of the distinct keywords count/websites count/ etc - $view->urlSparklineDistinctSearchEngines = $this->getUrlSparkline('getLastDistinctSearchEnginesGraph'); - $view->urlSparklineDistinctKeywords = $this->getUrlSparkline('getLastDistinctKeywordsGraph'); - $view->urlSparklineDistinctWebsites = $this->getUrlSparkline('getLastDistinctWebsitesGraph'); - $view->urlSparklineDistinctCampaigns = $this->getUrlSparkline('getLastDistinctCampaignsGraph'); - - $view->totalVisits = $totalVisits; - $view->referrersReportsByDimension = $this->getReferrersReportsByDimensionView($totalVisits); - - echo $view->render(); - } - - /** - * Returns HTML for the Referrers Overview page that categorizes Referrer reports - * & allows the user to switch between them. - * - * @param int $visits The number of visits for this period & site. If <= 0, the - * reports are not shown, since they will have no data. - * @return string The report viewer HTML. - */ - private function getReferrersReportsByDimensionView( $visits ) - { - $result = ''; - - // only display the reports by dimension view if there are visits - if ($visits > 0) - { - $referrersReportsByDimension = new Piwik_View_ReportsByDimension(); - - $referrersReportsByDimension->addReport( - 'Referers_ViewAllReferrers', 'Referers_WidgetGetAll', 'Referers.getAll'); - - $byTypeCategory = Piwik_Translate('Referers_ViewReferrersBy', Piwik_Translate('Live_GoalType')); - $referrersReportsByDimension->addReport( - $byTypeCategory, 'Referers_WidgetKeywords', 'Referers.getKeywords'); - $referrersReportsByDimension->addReport($byTypeCategory, 'SitesManager_Sites', 'Referers.getWebsites'); - $referrersReportsByDimension->addReport($byTypeCategory, 'Referers_Campaigns', 'Referers.getCampaigns'); - - $bySourceCategory = Piwik_Translate('Referers_ViewReferrersBy', Piwik_Translate('General_Source')); - $referrersReportsByDimension->addReport($bySourceCategory, 'Referers_Socials', 'Referers.getSocials'); - $referrersReportsByDimension->addReport( - $bySourceCategory, 'Referers_SearchEngines', 'Referers.getSearchEngines'); - - $result = $referrersReportsByDimension->render(); - } - - return $result; - } + function index() + { + $view = Piwik_View::factory('index'); - function getSearchEnginesAndKeywords() - { - $view = Piwik_View::factory('searchEngines_Keywords'); - $view->searchEngines = $this->getSearchEngines(true) ; - $view->keywords = $this->getKeywords(true); - echo $view->render(); - } - - function getRefererType( $fetch = false) - { - $view = Piwik_ViewDataTable::factory('tableAllColumns'); - $view->init( $this->pluginName, - __FUNCTION__, - 'Referers.getRefererType', - 'getRefererType' - ); - $view->disableSearchBox(); - $view->disableOffsetInformationAndPaginationControls(); - $view->disableExcludeLowPopulation(); - $view->disableSubTableWhenShowGoals(); - $view->enableShowGoals(); - $view->setLimit(10); - $view->setColumnsToDisplay( array('label', 'nb_visits') ); - - $idSubtable = Piwik_Common::getRequestVar('idSubtable', false); - $labelColumnTitle = Piwik_Translate('Referers_ColumnRefererType'); - if ($idSubtable !== false) - { - switch ($idSubtable) - { - case Piwik_Common::REFERER_TYPE_SEARCH_ENGINE: - $labelColumnTitle = Piwik_Translate('Referers_ColumnSearchEngine'); - break; - case Piwik_Common::REFERER_TYPE_WEBSITE: - $labelColumnTitle = Piwik_Translate('Referers_ColumnWebsite'); - break; - case Piwik_Common::REFERER_TYPE_CAMPAIGN: - $labelColumnTitle = Piwik_Translate('Referers_ColumnCampaign'); - break; - default: - break; - } - } - $view->setColumnTranslation('label', $labelColumnTitle); - - $this->setMetricsVariablesView($view); - return $this->renderView($view, $fetch); - } - - /** - * Returns or echo's a report that shows all search keyword, website and campaign - * referrer information in one report. - * - * @param bool $fetch True if the report HTML should be returned. If false, the - * report is echo'd and nothing is returned. - * @return string The report HTML or nothing if $fetch is set to false. - */ - public function getAll( $fetch = false ) - { - $view = Piwik_ViewDataTable::factory(); - $view->init($this->pluginName, __FUNCTION__, 'Referers.getAll'); - $view->disableExcludeLowPopulation(); - $view->setColumnTranslation('label', Piwik_Translate('Referers_Referrer')); - $view->setColumnsToDisplay(array('label', 'nb_visits')); - $view->enableShowGoals(); - $view->setLimit(20); - $view->setCustomParameter('disable_row_actions', '1'); - - $setGetAllHtmlPrefix = array($this, 'setGetAllHtmlPrefix'); - $view->queueFilter( - 'MetadataCallbackAddMetadata', array('referrer_type', 'html_label_prefix', $setGetAllHtmlPrefix)); - - $view->setMetricsVariablesView($view); - - return $this->renderView($view, $fetch); - } - - /** - * DataTable filter callback that returns the HTML prefix for a label in the - * 'getAll' report based on the row's referrer type. - * - * @param int $referrerType The referrer type. - * @return string - */ - public function setGetAllHtmlPrefix( $referrerType ) - { - // get singular label for referrer type - $indexTranslation = ''; - switch($referrerType) - { - case Piwik_Common::REFERER_TYPE_DIRECT_ENTRY: - $indexTranslation = 'Referers_DirectEntry'; - break; - case Piwik_Common::REFERER_TYPE_SEARCH_ENGINE: - $indexTranslation = 'Referers_ColumnKeyword'; - break; - case Piwik_Common::REFERER_TYPE_WEBSITE: - $indexTranslation = 'Referers_ColumnWebsite'; - break; - case Piwik_Common::REFERER_TYPE_CAMPAIGN: - $indexTranslation = 'Referers_ColumnCampaign'; - break; - default: - // case of newsletter, partners, before Piwik 0.2.25 - $indexTranslation = 'General_Others'; - break; - } - - $label = strtolower(Piwik_Translate($indexTranslation)); - - // return html that displays it as grey & italic - return '('.$label.')'; - } + $view->graphEvolutionReferers = $this->getEvolutionGraph(true, Piwik_Common::REFERER_TYPE_DIRECT_ENTRY, array('nb_visits')); + $view->nameGraphEvolutionReferers = 'ReferersgetEvolutionGraph'; - function getKeywords( $fetch = false) - { - $view = Piwik_ViewDataTable::factory(); - $view->init( $this->pluginName, __FUNCTION__, - 'Referers.getKeywords', - 'getSearchEnginesFromKeywordId' - ); - $view->disableExcludeLowPopulation(); - $view->setColumnTranslation('label', Piwik_Translate('Referers_ColumnKeyword')); - $view->enableShowGoals(); - $view->setLimit(25); - $view->disableSubTableWhenShowGoals(); - - $this->setMetricsVariablesView($view); - - return $this->renderView($view, $fetch); - } - - function getSearchEnginesFromKeywordId( $fetch = false ) - { - $view = Piwik_ViewDataTable::factory(); - $view->init( $this->pluginName, __FUNCTION__, - 'Referers.getSearchEnginesFromKeywordId' - ); - $view->disableSearchBox(); - $view->disableExcludeLowPopulation(); - $view->setColumnsToDisplay( array('label','nb_visits') ); - $view->setColumnTranslation('label', Piwik_Translate('Referers_ColumnSearchEngine')); - return $this->renderView($view, $fetch); - } - - - function getSearchEngines( $fetch = false) - { - $view = Piwik_ViewDataTable::factory(); - $view->init( $this->pluginName, __FUNCTION__, - 'Referers.getSearchEngines', - 'getKeywordsFromSearchEngineId' - ); - $view->disableSearchBox(); - $view->disableExcludeLowPopulation(); - $view->enableShowGoals(); - $view->setLimit(25); - $view->disableSubTableWhenShowGoals(); - $view->setColumnTranslation('label', Piwik_Translate('Referers_ColumnSearchEngine')); - - $this->setMetricsVariablesView($view); - - return $this->renderView($view, $fetch); - } + // building the referers summary report + $view->dataTableRefererType = $this->getRefererType(true); - function getKeywordsFromSearchEngineId( $fetch = false ) - { - $view = Piwik_ViewDataTable::factory(); - $view->init( $this->pluginName, __FUNCTION__, - 'Referers.getKeywordsFromSearchEngineId' - ); - $view->disableSearchBox(); - $view->disableExcludeLowPopulation(); - $view->setColumnsToDisplay( array('label','nb_visits') ); - $view->setColumnTranslation('label', Piwik_Translate('Referers_ColumnKeyword')); - return $this->renderView($view, $fetch); - } - - function indexWebsites($fetch = false) - { - $view = Piwik_View::factory('Websites_SocialNetworks'); - $view->websites = $this->getWebsites(true) ; - $view->socials = $this->getSocials(true); - if ($fetch) - { - return $view->render(); - } - else - { - echo $view->render(); - } - } - - function getWebsites( $fetch = false) - { - $view = Piwik_ViewDataTable::factory(); - $view->init( $this->pluginName, __FUNCTION__, - 'Referers.getWebsites', - 'getUrlsFromWebsiteId' - ); - $view->disableExcludeLowPopulation(); - $view->enableShowGoals(); - $view->setLimit(25); - $view->disableSubTableWhenShowGoals(); - $view->setColumnTranslation('label', Piwik_Translate('Referers_ColumnWebsite')); - - $this->setMetricsVariablesView($view); - - return $this->renderView($view, $fetch); - } - - function getSocials( $fetch = false) - { - $view = Piwik_ViewDataTable::factory('graphPie'); - $view->init($this->pluginName, __FUNCTION__, 'Referers.getSocials', 'getUrlsForSocial'); - $view->disableExcludeLowPopulation(); - $view->setLimit(10); - $view->enableShowGoals(); - $view->disableSubTableWhenShowGoals(); - $view->setColumnTranslation('label', Piwik_Translate('Referers_ColumnSocial')); - - if(empty($_REQUEST['widget'])) { - $view->setFooterMessage(Piwik_Translate('Referers_SocialFooterMessage')); - } - - $this->setMetricsVariablesView($view); - - return $this->renderView($view, $fetch); - } - - function getUrlsForSocial( $fetch = false ) - { - $view = Piwik_ViewDataTable::factory(); - $view->init($this->pluginName, __FUNCTION__, 'Referers.getUrlsForSocial'); - $view->disableExcludeLowPopulation(); - $view->setLimit(10); - $view->enableShowGoals(); - $view->setColumnTranslation('label', Piwik_Translate('Referers_ColumnWebsitePage')); - - $this->setMetricsVariablesView($view); - - return $this->renderView($view, $fetch); - } - - function indexCampaigns($fetch = false) - { - return Piwik_View::singleReport( - Piwik_Translate('Referers_Campaigns'), - $this->getCampaigns(true), $fetch); - } - - function getCampaigns( $fetch = false) - { - $view = Piwik_ViewDataTable::factory(); - $view->init( $this->pluginName, __FUNCTION__, - 'Referers.getCampaigns', - 'getKeywordsFromCampaignId' - ); - $view->disableExcludeLowPopulation(); - $view->enableShowGoals(); - $view->setLimit(25); - $view->setColumnsToDisplay( array('label','nb_visits') ); - $view->setColumnTranslation('label', Piwik_Translate('Referers_ColumnCampaign')); - - $help = Piwik_Translate('Referers_CampaignFooterHelp', array( '', - ' - ', - '' - )); - $view->setFooterMessage( $help ); - $this->setMetricsVariablesView($view); - return $this->renderView($view, $fetch); - } - - function getKeywordsFromCampaignId( $fetch = false) - { - $view = Piwik_ViewDataTable::factory(); - $view->init( $this->pluginName, __FUNCTION__, - 'Referers.getKeywordsFromCampaignId' - ); - - $view->disableSearchBox(); - $view->disableExcludeLowPopulation(); - $view->setColumnsToDisplay( array('label','nb_visits') ); - $view->setColumnTranslation('label', Piwik_Translate('Referers_ColumnKeyword')); - - return $this->renderView($view, $fetch); - } - - function getUrlsFromWebsiteId( $fetch = false) - { - $view = Piwik_ViewDataTable::factory(); - $view->init( $this->pluginName, __FUNCTION__, - 'Referers.getUrlsFromWebsiteId' - ); - $view->disableSearchBox(); - $view->disableExcludeLowPopulation(); - $view->setColumnsToDisplay( array('label','nb_visits') ); - $view->setColumnTranslation('label', Piwik_Translate('Referers_ColumnWebsitePage')); - $view->setTooltipMetadataName('url'); - return $this->renderView($view, $fetch); - } - - protected function getReferersVisitorsByType( $date = false ) - { - if ($date === false) - { - $date = Piwik_Common::getRequestVar('date', false); - } - - // we disable the queued filters because here we want to get the visits coming from search engines - // if the filters were applied we would have to look up for a label looking like "Search Engines" - // which is not good when we have translations - $dataTableReferersType = Piwik_API_Request::processRequest( - "Referers.getRefererType", array('disable_queued_filters' => '1', 'date' => $date)); - - $nameToColumnId = array( - 'visitorsFromSearchEngines' => Piwik_Common::REFERER_TYPE_SEARCH_ENGINE, - 'visitorsFromDirectEntry' => Piwik_Common::REFERER_TYPE_DIRECT_ENTRY, - 'visitorsFromWebsites' => Piwik_Common::REFERER_TYPE_WEBSITE, - 'visitorsFromCampaigns' => Piwik_Common::REFERER_TYPE_CAMPAIGN, - ); - $return = array(); - foreach($nameToColumnId as $nameVar => $columnId) - { - $value = 0; - $row = $dataTableReferersType->getRowFromLabel($columnId); - if($row !== false) - { - $value = $row->getColumn(Piwik_Archive::INDEX_NB_VISITS); - } - $return[$nameVar] = $value; - } - return $return; - } + $nameValues = $this->getReferersVisitorsByType(); - protected $referrerTypeToLabel = array( - Piwik_Common::REFERER_TYPE_DIRECT_ENTRY => 'Referers_DirectEntry', - Piwik_Common::REFERER_TYPE_SEARCH_ENGINE => 'Referers_SearchEngines', - Piwik_Common::REFERER_TYPE_WEBSITE => 'Referers_Websites', - Piwik_Common::REFERER_TYPE_CAMPAIGN => 'Referers_Campaigns', - ); - - public function getEvolutionGraph( $fetch = false, $typeReferer = false, array $columns = array()) - { - $view = $this->getLastUnitGraph($this->pluginName, __FUNCTION__, 'Referers.getRefererType'); - - $view->addTotalRow(); - - // configure displayed columns - if(empty($columns)) - { - $columns = Piwik_Common::getRequestVar('columns'); - $columns = Piwik::getArrayFromApiParameter($columns); - } - $columns = !is_array($columns) ? array($columns) : $columns; - $view->setColumnsToDisplay($columns); - - // configure selectable columns - if (Piwik_Common::getRequestVar('period', false) == 'day') { - $selectable = array('nb_visits', 'nb_uniq_visitors', 'nb_actions'); - } else { - $selectable = array('nb_visits', 'nb_actions'); - } - $view->setSelectableColumns($selectable); - - // configure displayed rows - $visibleRows = Piwik_Common::getRequestVar('rows', false); - if ($visibleRows !== false) - { - // this happens when the row picker has been used - $visibleRows = Piwik::getArrayFromApiParameter($visibleRows); - - // typeReferer is redundant if rows are defined, so make sure it's not used - $view->setCustomParameter('typeReferer', false); - } - else - { - // use $typeReferer as default - if($typeReferer === false) - { - $typeReferer = Piwik_Common::getRequestVar('typeReferer', false); - } - $label = self::getTranslatedReferrerTypeLabel($typeReferer); - $total = Piwik_Translate('General_Total'); - $visibleRows = array($label, $total); - $view->setParametersToModify(array('rows' => $label.','.$total)); - } - $view->addRowPicker($visibleRows); - - $view->setReportDocumentation(Piwik_Translate('Referers_EvolutionDocumentation').'
' - .Piwik_Translate('General_BrokenDownReportDocumentation').'
' - .Piwik_Translate('Referers_EvolutionDocumentationMoreInfo', '"'.Piwik_Translate('Referers_DetailsByRefererType').'"')); - - return $this->renderView($view, $fetch); - } - - function getLastDistinctSearchEnginesGraph( $fetch = false ) - { - $view = $this->getLastUnitGraph($this->pluginName,__FUNCTION__, "Referers.getNumberOfDistinctSearchEngines"); - $view->setColumnTranslation('Referers_distinctSearchEngines', ucfirst(Piwik_Translate('Referers_DistinctSearchEngines'))); - $view->setColumnsToDisplay(array('Referers_distinctSearchEngines')); - return $this->renderView($view, $fetch); - } - function getLastDistinctKeywordsGraph( $fetch = false ) - { - $view = $this->getLastUnitGraph($this->pluginName,__FUNCTION__, "Referers.getNumberOfDistinctKeywords"); - $view->setColumnTranslation('Referers_distinctKeywords', ucfirst(Piwik_Translate('Referers_DistinctKeywords'))); - $view->setColumnsToDisplay(array('Referers_distinctKeywords')); - return $this->renderView($view, $fetch); - } - function getLastDistinctWebsitesGraph( $fetch = false ) - { - $view = $this->getLastUnitGraph($this->pluginName,__FUNCTION__, "Referers.getNumberOfDistinctWebsites"); - $view->setColumnTranslation('Referers_distinctWebsites', ucfirst(Piwik_Translate('Referers_DistinctWebsites'))); - $view->setColumnsToDisplay(array('Referers_distinctWebsites')); - return $this->renderView($view, $fetch); - } - function getLastDistinctCampaignsGraph( $fetch = false ) - { - $view = $this->getLastUnitGraph($this->pluginName,__FUNCTION__, "Referers.getNumberOfDistinctCampaigns"); - $view->setColumnTranslation('Referers_distinctCampaigns', ucfirst(Piwik_Translate('Referers_DistinctCampaigns'))); - $view->setColumnsToDisplay(array('Referers_distinctCampaigns')); - return $this->renderView($view, $fetch); - } + $totalVisits = array_sum($nameValues); + foreach ($nameValues as $name => $value) { + $view->$name = $value; - function getKeywordsForPage() - { - Piwik::checkUserHasViewAccess($this->idSite); - - $requestUrl = '&date=previous1' - .'&period=week' - .'&idSite='.$this->idSite - ; - - $topPageUrlRequest = $requestUrl - .'&method=Actions.getPageUrls' - .'&filter_limit=50' - .'&format=original'; - $request = new Piwik_API_Request($topPageUrlRequest); - $request = $request->process(); - $tables = $request->getArray(); - - $topPageUrl = false; - $first = key($tables); - if(!empty($first)) - { - $topPageUrls = $tables[$first]; - $topPageUrls = $topPageUrls->getRowsMetadata('url'); - $tmpTopPageUrls = array_values($topPageUrls); - $topPageUrl = current($tmpTopPageUrls); - } - if(empty($topPageUrl)) - { - $topPageUrl = $this->site->getMainUrl(); - } - $url = $topPageUrl; - - // HTML - $api = Piwik_Url::getCurrentUrlWithoutFileName() - .'?module=API&method=Referers.getKeywordsForPageUrl' - .'&format=php' - .'&filter_limit=10' - .'&token_auth='.Piwik::getCurrentUserTokenAuth(); - - $api .= $requestUrl; - $code = ' + // calculate percent of total, if there were any visits + if ($value != 0 + && $totalVisits != 0 + ) { + $percentName = $name . 'Percent'; + $view->$percentName = round(($value / $totalVisits) * 100, 0); + } + } + + // set distinct metrics + $distinctMetrics = $this->getDistinctReferrersMetrics(); + foreach ($distinctMetrics as $name => $value) { + $view->$name = $value; + } + + // calculate evolution for visit metrics & distinct metrics + list($lastPeriodDate, $ignore) = Piwik_Period_Range::getLastDate(); + if ($lastPeriodDate !== false) { + $date = Piwik_Common::getRequestVar('date'); + $period = Piwik_Common::getRequestVar('period'); + + $prettyDate = self::getPrettyDate($date, $period); + $prettyLastPeriodDate = self::getPrettyDate($lastPeriodDate, $period); + + // visit metrics + $previousValues = $this->getReferersVisitorsByType($lastPeriodDate); + $this->addEvolutionPropertiesToView($view, $prettyDate, $nameValues, $prettyLastPeriodDate, $previousValues); + + // distinct metrics + $previousValues = $this->getDistinctReferrersMetrics($lastPeriodDate); + $this->addEvolutionPropertiesToView($view, $prettyDate, $distinctMetrics, $prettyLastPeriodDate, $previousValues); + } + + // sparkline for the historical data of the above values + $view->urlSparklineSearchEngines = $this->getReferrerUrlSparkline(Piwik_Common::REFERER_TYPE_SEARCH_ENGINE); + $view->urlSparklineDirectEntry = $this->getReferrerUrlSparkline(Piwik_Common::REFERER_TYPE_DIRECT_ENTRY); + $view->urlSparklineWebsites = $this->getReferrerUrlSparkline(Piwik_Common::REFERER_TYPE_WEBSITE); + $view->urlSparklineCampaigns = $this->getReferrerUrlSparkline(Piwik_Common::REFERER_TYPE_CAMPAIGN); + + // sparklines for the evolution of the distinct keywords count/websites count/ etc + $view->urlSparklineDistinctSearchEngines = $this->getUrlSparkline('getLastDistinctSearchEnginesGraph'); + $view->urlSparklineDistinctKeywords = $this->getUrlSparkline('getLastDistinctKeywordsGraph'); + $view->urlSparklineDistinctWebsites = $this->getUrlSparkline('getLastDistinctWebsitesGraph'); + $view->urlSparklineDistinctCampaigns = $this->getUrlSparkline('getLastDistinctCampaignsGraph'); + + $view->totalVisits = $totalVisits; + $view->referrersReportsByDimension = $this->getReferrersReportsByDimensionView($totalVisits); + + echo $view->render(); + } + + /** + * Returns HTML for the Referrers Overview page that categorizes Referrer reports + * & allows the user to switch between them. + * + * @param int $visits The number of visits for this period & site. If <= 0, the + * reports are not shown, since they will have no data. + * @return string The report viewer HTML. + */ + private function getReferrersReportsByDimensionView($visits) + { + $result = ''; + + // only display the reports by dimension view if there are visits + if ($visits > 0) { + $referrersReportsByDimension = new Piwik_View_ReportsByDimension(); + + $referrersReportsByDimension->addReport( + 'Referers_ViewAllReferrers', 'Referers_WidgetGetAll', 'Referers.getAll'); + + $byTypeCategory = Piwik_Translate('Referers_ViewReferrersBy', Piwik_Translate('Live_GoalType')); + $referrersReportsByDimension->addReport( + $byTypeCategory, 'Referers_WidgetKeywords', 'Referers.getKeywords'); + $referrersReportsByDimension->addReport($byTypeCategory, 'SitesManager_Sites', 'Referers.getWebsites'); + $referrersReportsByDimension->addReport($byTypeCategory, 'Referers_Campaigns', 'Referers.getCampaigns'); + + $bySourceCategory = Piwik_Translate('Referers_ViewReferrersBy', Piwik_Translate('General_Source')); + $referrersReportsByDimension->addReport($bySourceCategory, 'Referers_Socials', 'Referers.getSocials'); + $referrersReportsByDimension->addReport( + $bySourceCategory, 'Referers_SearchEngines', 'Referers.getSearchEngines'); + + $result = $referrersReportsByDimension->render(); + } + + return $result; + } + + function getSearchEnginesAndKeywords() + { + $view = Piwik_View::factory('searchEngines_Keywords'); + $view->searchEngines = $this->getSearchEngines(true); + $view->keywords = $this->getKeywords(true); + echo $view->render(); + } + + function getRefererType($fetch = false) + { + $view = Piwik_ViewDataTable::factory('tableAllColumns'); + $view->init($this->pluginName, + __FUNCTION__, + 'Referers.getRefererType', + 'getRefererType' + ); + $view->disableSearchBox(); + $view->disableOffsetInformationAndPaginationControls(); + $view->disableExcludeLowPopulation(); + $view->disableSubTableWhenShowGoals(); + $view->enableShowGoals(); + $view->setLimit(10); + $view->setColumnsToDisplay(array('label', 'nb_visits')); + + $idSubtable = Piwik_Common::getRequestVar('idSubtable', false); + $labelColumnTitle = Piwik_Translate('Referers_ColumnRefererType'); + if ($idSubtable !== false) { + switch ($idSubtable) { + case Piwik_Common::REFERER_TYPE_SEARCH_ENGINE: + $labelColumnTitle = Piwik_Translate('Referers_ColumnSearchEngine'); + break; + case Piwik_Common::REFERER_TYPE_WEBSITE: + $labelColumnTitle = Piwik_Translate('Referers_ColumnWebsite'); + break; + case Piwik_Common::REFERER_TYPE_CAMPAIGN: + $labelColumnTitle = Piwik_Translate('Referers_ColumnCampaign'); + break; + default: + break; + } + } + $view->setColumnTranslation('label', $labelColumnTitle); + + $this->setMetricsVariablesView($view); + return $this->renderView($view, $fetch); + } + + /** + * Returns or echo's a report that shows all search keyword, website and campaign + * referrer information in one report. + * + * @param bool $fetch True if the report HTML should be returned. If false, the + * report is echo'd and nothing is returned. + * @return string The report HTML or nothing if $fetch is set to false. + */ + public function getAll($fetch = false) + { + $view = Piwik_ViewDataTable::factory(); + $view->init($this->pluginName, __FUNCTION__, 'Referers.getAll'); + $view->disableExcludeLowPopulation(); + $view->setColumnTranslation('label', Piwik_Translate('Referers_Referrer')); + $view->setColumnsToDisplay(array('label', 'nb_visits')); + $view->enableShowGoals(); + $view->setLimit(20); + $view->setCustomParameter('disable_row_actions', '1'); + + $setGetAllHtmlPrefix = array($this, 'setGetAllHtmlPrefix'); + $view->queueFilter( + 'MetadataCallbackAddMetadata', array('referrer_type', 'html_label_prefix', $setGetAllHtmlPrefix)); + + $view->setMetricsVariablesView($view); + + return $this->renderView($view, $fetch); + } + + /** + * DataTable filter callback that returns the HTML prefix for a label in the + * 'getAll' report based on the row's referrer type. + * + * @param int $referrerType The referrer type. + * @return string + */ + public function setGetAllHtmlPrefix($referrerType) + { + // get singular label for referrer type + $indexTranslation = ''; + switch ($referrerType) { + case Piwik_Common::REFERER_TYPE_DIRECT_ENTRY: + $indexTranslation = 'Referers_DirectEntry'; + break; + case Piwik_Common::REFERER_TYPE_SEARCH_ENGINE: + $indexTranslation = 'Referers_ColumnKeyword'; + break; + case Piwik_Common::REFERER_TYPE_WEBSITE: + $indexTranslation = 'Referers_ColumnWebsite'; + break; + case Piwik_Common::REFERER_TYPE_CAMPAIGN: + $indexTranslation = 'Referers_ColumnCampaign'; + break; + default: + // case of newsletter, partners, before Piwik 0.2.25 + $indexTranslation = 'General_Others'; + break; + } + + $label = strtolower(Piwik_Translate($indexTranslation)); + + // return html that displays it as grey & italic + return '(' . $label . ')'; + } + + function getKeywords($fetch = false) + { + $view = Piwik_ViewDataTable::factory(); + $view->init($this->pluginName, __FUNCTION__, + 'Referers.getKeywords', + 'getSearchEnginesFromKeywordId' + ); + $view->disableExcludeLowPopulation(); + $view->setColumnTranslation('label', Piwik_Translate('Referers_ColumnKeyword')); + $view->enableShowGoals(); + $view->setLimit(25); + $view->disableSubTableWhenShowGoals(); + + $this->setMetricsVariablesView($view); + + return $this->renderView($view, $fetch); + } + + function getSearchEnginesFromKeywordId($fetch = false) + { + $view = Piwik_ViewDataTable::factory(); + $view->init($this->pluginName, __FUNCTION__, + 'Referers.getSearchEnginesFromKeywordId' + ); + $view->disableSearchBox(); + $view->disableExcludeLowPopulation(); + $view->setColumnsToDisplay(array('label', 'nb_visits')); + $view->setColumnTranslation('label', Piwik_Translate('Referers_ColumnSearchEngine')); + return $this->renderView($view, $fetch); + } + + + function getSearchEngines($fetch = false) + { + $view = Piwik_ViewDataTable::factory(); + $view->init($this->pluginName, __FUNCTION__, + 'Referers.getSearchEngines', + 'getKeywordsFromSearchEngineId' + ); + $view->disableSearchBox(); + $view->disableExcludeLowPopulation(); + $view->enableShowGoals(); + $view->setLimit(25); + $view->disableSubTableWhenShowGoals(); + $view->setColumnTranslation('label', Piwik_Translate('Referers_ColumnSearchEngine')); + + $this->setMetricsVariablesView($view); + + return $this->renderView($view, $fetch); + } + + function getKeywordsFromSearchEngineId($fetch = false) + { + $view = Piwik_ViewDataTable::factory(); + $view->init($this->pluginName, __FUNCTION__, + 'Referers.getKeywordsFromSearchEngineId' + ); + $view->disableSearchBox(); + $view->disableExcludeLowPopulation(); + $view->setColumnsToDisplay(array('label', 'nb_visits')); + $view->setColumnTranslation('label', Piwik_Translate('Referers_ColumnKeyword')); + return $this->renderView($view, $fetch); + } + + function indexWebsites($fetch = false) + { + $view = Piwik_View::factory('Websites_SocialNetworks'); + $view->websites = $this->getWebsites(true); + $view->socials = $this->getSocials(true); + if ($fetch) { + return $view->render(); + } else { + echo $view->render(); + } + } + + function getWebsites($fetch = false) + { + $view = Piwik_ViewDataTable::factory(); + $view->init($this->pluginName, __FUNCTION__, + 'Referers.getWebsites', + 'getUrlsFromWebsiteId' + ); + $view->disableExcludeLowPopulation(); + $view->enableShowGoals(); + $view->setLimit(25); + $view->disableSubTableWhenShowGoals(); + $view->setColumnTranslation('label', Piwik_Translate('Referers_ColumnWebsite')); + + $this->setMetricsVariablesView($view); + + return $this->renderView($view, $fetch); + } + + function getSocials($fetch = false) + { + $view = Piwik_ViewDataTable::factory('graphPie'); + $view->init($this->pluginName, __FUNCTION__, 'Referers.getSocials', 'getUrlsForSocial'); + $view->disableExcludeLowPopulation(); + $view->setLimit(10); + $view->enableShowGoals(); + $view->disableSubTableWhenShowGoals(); + $view->setColumnTranslation('label', Piwik_Translate('Referers_ColumnSocial')); + + if (empty($_REQUEST['widget'])) { + $view->setFooterMessage(Piwik_Translate('Referers_SocialFooterMessage')); + } + + $this->setMetricsVariablesView($view); + + return $this->renderView($view, $fetch); + } + + function getUrlsForSocial($fetch = false) + { + $view = Piwik_ViewDataTable::factory(); + $view->init($this->pluginName, __FUNCTION__, 'Referers.getUrlsForSocial'); + $view->disableExcludeLowPopulation(); + $view->setLimit(10); + $view->enableShowGoals(); + $view->setColumnTranslation('label', Piwik_Translate('Referers_ColumnWebsitePage')); + + $this->setMetricsVariablesView($view); + + return $this->renderView($view, $fetch); + } + + function indexCampaigns($fetch = false) + { + return Piwik_View::singleReport( + Piwik_Translate('Referers_Campaigns'), + $this->getCampaigns(true), $fetch); + } + + function getCampaigns($fetch = false) + { + $view = Piwik_ViewDataTable::factory(); + $view->init($this->pluginName, __FUNCTION__, + 'Referers.getCampaigns', + 'getKeywordsFromCampaignId' + ); + $view->disableExcludeLowPopulation(); + $view->enableShowGoals(); + $view->setLimit(25); + $view->setColumnsToDisplay(array('label', 'nb_visits')); + $view->setColumnTranslation('label', Piwik_Translate('Referers_ColumnCampaign')); + + $help = Piwik_Translate('Referers_CampaignFooterHelp', array('', + ' - ', + '' + )); + $view->setFooterMessage($help); + $this->setMetricsVariablesView($view); + return $this->renderView($view, $fetch); + } + + function getKeywordsFromCampaignId($fetch = false) + { + $view = Piwik_ViewDataTable::factory(); + $view->init($this->pluginName, __FUNCTION__, + 'Referers.getKeywordsFromCampaignId' + ); + + $view->disableSearchBox(); + $view->disableExcludeLowPopulation(); + $view->setColumnsToDisplay(array('label', 'nb_visits')); + $view->setColumnTranslation('label', Piwik_Translate('Referers_ColumnKeyword')); + + return $this->renderView($view, $fetch); + } + + function getUrlsFromWebsiteId($fetch = false) + { + $view = Piwik_ViewDataTable::factory(); + $view->init($this->pluginName, __FUNCTION__, + 'Referers.getUrlsFromWebsiteId' + ); + $view->disableSearchBox(); + $view->disableExcludeLowPopulation(); + $view->setColumnsToDisplay(array('label', 'nb_visits')); + $view->setColumnTranslation('label', Piwik_Translate('Referers_ColumnWebsitePage')); + $view->setTooltipMetadataName('url'); + return $this->renderView($view, $fetch); + } + + protected function getReferersVisitorsByType($date = false) + { + if ($date === false) { + $date = Piwik_Common::getRequestVar('date', false); + } + + // we disable the queued filters because here we want to get the visits coming from search engines + // if the filters were applied we would have to look up for a label looking like "Search Engines" + // which is not good when we have translations + $dataTableReferersType = Piwik_API_Request::processRequest( + "Referers.getRefererType", array('disable_queued_filters' => '1', 'date' => $date)); + + $nameToColumnId = array( + 'visitorsFromSearchEngines' => Piwik_Common::REFERER_TYPE_SEARCH_ENGINE, + 'visitorsFromDirectEntry' => Piwik_Common::REFERER_TYPE_DIRECT_ENTRY, + 'visitorsFromWebsites' => Piwik_Common::REFERER_TYPE_WEBSITE, + 'visitorsFromCampaigns' => Piwik_Common::REFERER_TYPE_CAMPAIGN, + ); + $return = array(); + foreach ($nameToColumnId as $nameVar => $columnId) { + $value = 0; + $row = $dataTableReferersType->getRowFromLabel($columnId); + if ($row !== false) { + $value = $row->getColumn(Piwik_Archive::INDEX_NB_VISITS); + } + $return[$nameVar] = $value; + } + return $return; + } + + protected $referrerTypeToLabel = array( + Piwik_Common::REFERER_TYPE_DIRECT_ENTRY => 'Referers_DirectEntry', + Piwik_Common::REFERER_TYPE_SEARCH_ENGINE => 'Referers_SearchEngines', + Piwik_Common::REFERER_TYPE_WEBSITE => 'Referers_Websites', + Piwik_Common::REFERER_TYPE_CAMPAIGN => 'Referers_Campaigns', + ); + + public function getEvolutionGraph($fetch = false, $typeReferer = false, array $columns = array()) + { + $view = $this->getLastUnitGraph($this->pluginName, __FUNCTION__, 'Referers.getRefererType'); + + $view->addTotalRow(); + + // configure displayed columns + if (empty($columns)) { + $columns = Piwik_Common::getRequestVar('columns'); + $columns = Piwik::getArrayFromApiParameter($columns); + } + $columns = !is_array($columns) ? array($columns) : $columns; + $view->setColumnsToDisplay($columns); + + // configure selectable columns + if (Piwik_Common::getRequestVar('period', false) == 'day') { + $selectable = array('nb_visits', 'nb_uniq_visitors', 'nb_actions'); + } else { + $selectable = array('nb_visits', 'nb_actions'); + } + $view->setSelectableColumns($selectable); + + // configure displayed rows + $visibleRows = Piwik_Common::getRequestVar('rows', false); + if ($visibleRows !== false) { + // this happens when the row picker has been used + $visibleRows = Piwik::getArrayFromApiParameter($visibleRows); + + // typeReferer is redundant if rows are defined, so make sure it's not used + $view->setCustomParameter('typeReferer', false); + } else { + // use $typeReferer as default + if ($typeReferer === false) { + $typeReferer = Piwik_Common::getRequestVar('typeReferer', false); + } + $label = self::getTranslatedReferrerTypeLabel($typeReferer); + $total = Piwik_Translate('General_Total'); + $visibleRows = array($label, $total); + $view->setParametersToModify(array('rows' => $label . ',' . $total)); + } + $view->addRowPicker($visibleRows); + + $view->setReportDocumentation(Piwik_Translate('Referers_EvolutionDocumentation') . '
' + . Piwik_Translate('General_BrokenDownReportDocumentation') . '
' + . Piwik_Translate('Referers_EvolutionDocumentationMoreInfo', '"' . Piwik_Translate('Referers_DetailsByRefererType') . '"')); + + return $this->renderView($view, $fetch); + } + + function getLastDistinctSearchEnginesGraph($fetch = false) + { + $view = $this->getLastUnitGraph($this->pluginName, __FUNCTION__, "Referers.getNumberOfDistinctSearchEngines"); + $view->setColumnTranslation('Referers_distinctSearchEngines', ucfirst(Piwik_Translate('Referers_DistinctSearchEngines'))); + $view->setColumnsToDisplay(array('Referers_distinctSearchEngines')); + return $this->renderView($view, $fetch); + } + + function getLastDistinctKeywordsGraph($fetch = false) + { + $view = $this->getLastUnitGraph($this->pluginName, __FUNCTION__, "Referers.getNumberOfDistinctKeywords"); + $view->setColumnTranslation('Referers_distinctKeywords', ucfirst(Piwik_Translate('Referers_DistinctKeywords'))); + $view->setColumnsToDisplay(array('Referers_distinctKeywords')); + return $this->renderView($view, $fetch); + } + + function getLastDistinctWebsitesGraph($fetch = false) + { + $view = $this->getLastUnitGraph($this->pluginName, __FUNCTION__, "Referers.getNumberOfDistinctWebsites"); + $view->setColumnTranslation('Referers_distinctWebsites', ucfirst(Piwik_Translate('Referers_DistinctWebsites'))); + $view->setColumnsToDisplay(array('Referers_distinctWebsites')); + return $this->renderView($view, $fetch); + } + + function getLastDistinctCampaignsGraph($fetch = false) + { + $view = $this->getLastUnitGraph($this->pluginName, __FUNCTION__, "Referers.getNumberOfDistinctCampaigns"); + $view->setColumnTranslation('Referers_distinctCampaigns', ucfirst(Piwik_Translate('Referers_DistinctCampaigns'))); + $view->setColumnsToDisplay(array('Referers_distinctCampaigns')); + return $this->renderView($view, $fetch); + } + + function getKeywordsForPage() + { + Piwik::checkUserHasViewAccess($this->idSite); + + $requestUrl = '&date=previous1' + . '&period=week' + . '&idSite=' . $this->idSite; + + $topPageUrlRequest = $requestUrl + . '&method=Actions.getPageUrls' + . '&filter_limit=50' + . '&format=original'; + $request = new Piwik_API_Request($topPageUrlRequest); + $request = $request->process(); + $tables = $request->getArray(); + + $topPageUrl = false; + $first = key($tables); + if (!empty($first)) { + $topPageUrls = $tables[$first]; + $topPageUrls = $topPageUrls->getRowsMetadata('url'); + $tmpTopPageUrls = array_values($topPageUrls); + $topPageUrl = current($tmpTopPageUrls); + } + if (empty($topPageUrl)) { + $topPageUrl = $this->site->getMainUrl(); + } + $url = $topPageUrl; + + // HTML + $api = Piwik_Url::getCurrentUrlWithoutFileName() + . '?module=API&method=Referers.getKeywordsForPageUrl' + . '&format=php' + . '&filter_limit=10' + . '&token_auth=' . Piwik::getCurrentUserTokenAuth(); + + $api .= $requestUrl; + $code = ' // This function will call the API to get best keyword for current URL. // Then it writes the list of best keywords in a HTML list function DisplayTopKeywords($url = "") @@ -596,7 +578,7 @@ function DisplayTopKeywords($url = "") @ini_set("default_socket_timeout", $timeout = 1); // Get the Keywords data $url = empty($url) ? "http://". $_SERVER["HTTP_HOST"] . $_SERVER["REQUEST_URI"] : $url; - $api = "'.$api.'&url=" . urlencode($url); + $api = "' . $api . '&url=" . urlencode($url); $keywords = @unserialize(file_get_contents($api)); if($keywords === false || isset($keywords["result"])) { // DEBUG ONLY: uncomment for troubleshooting an empty output (the URL output reveals the token_auth) @@ -616,131 +598,132 @@ function DisplayTopKeywords($url = "") } '; - $jsonRequest = str_replace('format=php', 'format=json', $api); - echo "

This widget is designed to work in your website directly. + $jsonRequest = str_replace('format=php', 'format=json', $api); + echo "

This widget is designed to work in your website directly. This widget makes it easy to use Piwik to automatically display the list of Top Keywords, for each of your website Page URLs.

Example API URL - For example if you would like to get the top 10 keywords, used last week, to land on the page $topPageUrl, - in format JSON: you would dynamically fetch the data using this API request URL. Make sure you encode the 'url' parameter in the URL.

+ in format JSON: you would dynamically fetch the data using this API request URL. Make sure you encode the 'url' parameter in the URL.

PHP Function ready to use! - If you use PHP on your website, we have prepared a small code snippet that you can copy paste in your Website PHP files. You can then simply call the function DisplayTopKeywords(); anywhere in your template, at the bottom of the content or in your blog sidebar. If you run this code in your page $topPageUrl, it would output the following:"; - - echo "

"; - function DisplayTopKeywords($url = "", $api) - { - // Do not spend more than 1 second fetching the data - @ini_set("default_socket_timeout", $timeout = 1); - // Get the Keywords data - $url = empty($url) ? "http://". $_SERVER["HTTP_HOST"] . $_SERVER["REQUEST_URI"] : $url; - $api = $api."&url=" . urlencode($url); - $keywords = @unserialize(file_get_contents($api)); - if($keywords === false || isset($keywords["result"])) { - // DEBUG ONLY: uncomment for troubleshooting an empty output (the URL output reveals the token_auth) - //echo "Error while fetching the Top Keywords from Piwik"; - return; - } - - // Display the list in HTML - $url = htmlspecialchars($url, ENT_QUOTES); - $output = "

Top Keywords for $url

    "; - foreach($keywords as $keyword) { - $output .= "
  • ". $keyword[0]. "
  • "; - } - if(empty($keywords)) { $output .= "Nothing yet..."; } - $output .= "
"; - echo $output; - } - DisplayTopKeywords($topPageUrl, $api); - - echo "

+ + echo "
"; + function DisplayTopKeywords($url = "", $api) + { + // Do not spend more than 1 second fetching the data + @ini_set("default_socket_timeout", $timeout = 1); + // Get the Keywords data + $url = empty($url) ? "http://" . $_SERVER["HTTP_HOST"] . $_SERVER["REQUEST_URI"] : $url; + $api = $api . "&url=" . urlencode($url); + $keywords = @unserialize(file_get_contents($api)); + if ($keywords === false || isset($keywords["result"])) { + // DEBUG ONLY: uncomment for troubleshooting an empty output (the URL output reveals the token_auth) + //echo "Error while fetching the Top Keywords from Piwik"; + return; + } + + // Display the list in HTML + $url = htmlspecialchars($url, ENT_QUOTES); + $output = "

Top Keywords for $url

    "; + foreach ($keywords as $keyword) { + $output .= "
  • " . $keyword[0] . "
  • "; + } + if (empty($keywords)) { + $output .= "Nothing yet..."; + } + $output .= "
"; + echo $output; + } + + DisplayTopKeywords($topPageUrl, $api); + + echo "

Here is the PHP function that you can paste in your pages:

"; - - echo " + + echo "

Notes: You can for example edit the code to to make the Top search keywords link to your Website search result pages.
On medium to large traffic websites, we recommend to cache this data, as to minimize the performance impact of calling the Piwik API on each page view.

"; - - } - - /** - * Returns the i18n-ized label for a referrer type. - * - * @param int $typeReferrer The referrer type. Referrer types are defined in Piwik_Common class. - * @return string The i18n-ized label. - */ - public static function getTranslatedReferrerTypeLabel( $typeReferrer ) - { - $label = Piwik_getRefererTypeLabel($typeReferrer); - return Piwik_Translate($label); - } - - /** - * Returns the URL for the sparkline of visits with a specific referrer type. - * - * @param int $typeReferrer The referrer type. Referrer types are defined in Piwik_Common class. - * @return string The URL that can be used to get a sparkline image. - */ - private function getReferrerUrlSparkline( $referrerType ) - { - $totalRow = Piwik_Translate('General_Total'); - return $this->getUrlSparkline( - 'getEvolutionGraph', - array('columns' => array('nb_visits'), - 'rows' => array(self::getTranslatedReferrerTypeLabel($referrerType), $totalRow), - 'typeReferer' => $referrerType) - ); - } - - /** - * Returns an array containing the number of distinct referrers for each - * referrer type. - * - * @param string|false $date The date to use when getting metrics. If false, the - * date query param is used. - * @return array The metrics. - */ - private function getDistinctReferrersMetrics( $date = false ) - { - $propertyToAccessorMapping = array( - 'numberDistinctSearchEngines' => 'getNumberOfDistinctSearchEngines', - 'numberDistinctKeywords' => 'getNumberOfDistinctKeywords', - 'numberDistinctWebsites' => 'getNumberOfDistinctWebsites', - 'numberDistinctWebsitesUrls' => 'getNumberOfDistinctWebsitesUrls', - 'numberDistinctCampaigns' => 'getNumberOfDistinctCampaigns', - ); - - $result = array(); - foreach ($propertyToAccessorMapping as $property => $method) - { - $result[$property] = $this->getNumericValue('Referers.'.$method, $date); - } - return $result; - } - - /** - * Utility method that calculates evolution values for a set of current & past values - * and sets properties on a Piwik_View w/ HTML that displays the evolution percents. - * - * @param Piwik_View $view The view to set properties on. - * @param string $date The date of the current values. - * @param array $currentValues Array mapping view property names w/ present values. - * @param string $lastPeriodDate The date of the period in the past. - * @param array $previousValues Array mapping view property names w/ past values. Keys - * in this array should be the same as keys in $currentValues. - * @param bool $isVisits Whether the values are counting visits or something else. - */ - private function addEvolutionPropertiesToView( $view, $date, $currentValues, $lastPeriodDate, $previousValues) - { - foreach ($previousValues as $name => $pastValue) - { - $currentValue = $currentValues[$name]; - $evolutionName = $name.'Evolution'; - - $view->$evolutionName = $this->getEvolutionHtml($date, $currentValue, $lastPeriodDate, $pastValue); - } - } + + } + + /** + * Returns the i18n-ized label for a referrer type. + * + * @param int $typeReferrer The referrer type. Referrer types are defined in Piwik_Common class. + * @return string The i18n-ized label. + */ + public static function getTranslatedReferrerTypeLabel($typeReferrer) + { + $label = Piwik_getRefererTypeLabel($typeReferrer); + return Piwik_Translate($label); + } + + /** + * Returns the URL for the sparkline of visits with a specific referrer type. + * + * @param int $typeReferrer The referrer type. Referrer types are defined in Piwik_Common class. + * @return string The URL that can be used to get a sparkline image. + */ + private function getReferrerUrlSparkline($referrerType) + { + $totalRow = Piwik_Translate('General_Total'); + return $this->getUrlSparkline( + 'getEvolutionGraph', + array('columns' => array('nb_visits'), + 'rows' => array(self::getTranslatedReferrerTypeLabel($referrerType), $totalRow), + 'typeReferer' => $referrerType) + ); + } + + /** + * Returns an array containing the number of distinct referrers for each + * referrer type. + * + * @param string|false $date The date to use when getting metrics. If false, the + * date query param is used. + * @return array The metrics. + */ + private function getDistinctReferrersMetrics($date = false) + { + $propertyToAccessorMapping = array( + 'numberDistinctSearchEngines' => 'getNumberOfDistinctSearchEngines', + 'numberDistinctKeywords' => 'getNumberOfDistinctKeywords', + 'numberDistinctWebsites' => 'getNumberOfDistinctWebsites', + 'numberDistinctWebsitesUrls' => 'getNumberOfDistinctWebsitesUrls', + 'numberDistinctCampaigns' => 'getNumberOfDistinctCampaigns', + ); + + $result = array(); + foreach ($propertyToAccessorMapping as $property => $method) { + $result[$property] = $this->getNumericValue('Referers.' . $method, $date); + } + return $result; + } + + /** + * Utility method that calculates evolution values for a set of current & past values + * and sets properties on a Piwik_View w/ HTML that displays the evolution percents. + * + * @param Piwik_View $view The view to set properties on. + * @param string $date The date of the current values. + * @param array $currentValues Array mapping view property names w/ present values. + * @param string $lastPeriodDate The date of the period in the past. + * @param array $previousValues Array mapping view property names w/ past values. Keys + * in this array should be the same as keys in $currentValues. + * @param bool $isVisits Whether the values are counting visits or something else. + */ + private function addEvolutionPropertiesToView($view, $date, $currentValues, $lastPeriodDate, $previousValues) + { + foreach ($previousValues as $name => $pastValue) { + $currentValue = $currentValues[$name]; + $evolutionName = $name . 'Evolution'; + + $view->$evolutionName = $this->getEvolutionHtml($date, $currentValue, $lastPeriodDate, $pastValue); + } + } } diff --git a/plugins/Referers/Referers.php b/plugins/Referers/Referers.php index 88497f8a47..3e40f2d9a9 100644 --- a/plugins/Referers/Referers.php +++ b/plugins/Referers/Referers.php @@ -19,591 +19,570 @@ 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( - 'description' => Piwik_Translate('Referers_PluginDescription'), - 'author' => 'Piwik', - 'author_homepage' => 'http://piwik.org/', - 'version' => Piwik_Version::VERSION, - ); - - return $info; - } - - function getListHooksRegistered() - { - $hooks = array( - 'ArchiveProcessing_Day.compute' => 'archiveDay', - 'ArchiveProcessing_Period.compute' => 'archivePeriod', - 'WidgetsList.add' => 'addWidgets', - 'Menu.add' => 'addMenus', - 'Goals.getReportsWithGoalMetrics' => 'getReportsWithGoalMetrics', - 'API.getReportMetadata' => 'getReportMetadata', - 'API.getSegmentsMetadata' => 'getSegmentsMetadata', - ); - return $hooks; - } - - /** - * @param Piwik_Event_Notification $notification notification object - */ - public function getReportMetadata($notification) - { - $reports = &$notification->getNotificationObject(); - $reports = array_merge($reports, array( - array( - 'category' => Piwik_Translate('Referers_Referers'), - 'name' => Piwik_Translate('Referers_Type'), - 'module' => 'Referers', - 'action' => 'getRefererType', - 'dimension' => Piwik_Translate('Referers_ColumnRefererType'), - 'constantRowsCount' => true, - 'documentation' => Piwik_Translate('Referers_TypeReportDocumentation').'
' - .''.Piwik_Translate('Referers_DirectEntry').': '.Piwik_Translate('Referers_DirectEntryDocumentation').'
' - .''.Piwik_Translate('Referers_SearchEngines').': '.Piwik_Translate('Referers_SearchEnginesDocumentation', - array('
', '"'.Piwik_Translate('Referers_SubmenuSearchEngines').'"')).'
' - .''.Piwik_Translate('Referers_Websites').': '.Piwik_Translate('Referers_WebsitesDocumentation', - array('
', '"'.Piwik_Translate('Referers_SubmenuWebsites').'"')).'
' - .''.Piwik_Translate('Referers_Campaigns').': '.Piwik_Translate('Referers_CampaignsDocumentation', - array('
', '"'.Piwik_Translate('Referers_SubmenuCampaigns').'"')), - 'order' => 1, - ), - array( - 'category' => Piwik_Translate('Referers_Referers'), - 'name' => Piwik_Translate('Referers_WidgetGetAll'), - 'module' => 'Referers', - 'action' => 'getAll', - 'dimension' => Piwik_Translate('Referers_Referrer'), - 'documentation' => Piwik_Translate('Referers_AllReferersReportDocumentation', '
'), - 'order' => 2, - ), - array( - 'category' => Piwik_Translate('Referers_Referers'), - 'name' => Piwik_Translate('Referers_Keywords'), - 'module' => 'Referers', - 'action' => 'getKeywords', - 'actionToLoadSubTables' => 'getSearchEnginesFromKeywordId', - 'dimension' => Piwik_Translate('Referers_ColumnKeyword'), - 'documentation' => Piwik_Translate('Referers_KeywordsReportDocumentation', '
'), - 'order' => 3, - ), - array( // subtable report - 'category' => Piwik_Translate('Referers_Referers'), - 'name' => Piwik_Translate('Referers_Keywords'), - 'module' => 'Referers', - 'action' => 'getSearchEnginesFromKeywordId', - 'dimension' => Piwik_Translate('Referers_ColumnSearchEngine'), - 'documentation' => Piwik_Translate('Referers_KeywordsReportDocumentation', '
'), - 'isSubtableReport' => true, - 'order' => 4 - ), - - array( - 'category' => Piwik_Translate('Referers_Referers'), - 'name' => Piwik_Translate('Referers_Websites'), - 'module' => 'Referers', - 'action' => 'getWebsites', - 'dimension' => Piwik_Translate('Referers_ColumnWebsite'), - 'documentation' => Piwik_Translate('Referers_WebsitesReportDocumentation', '
'), - 'actionToLoadSubTables' => 'getUrlsFromWebsiteId', - 'order' => 5 - ), - array( // subtable report - 'category' => Piwik_Translate('Referers_Referers'), - 'name' => Piwik_Translate('Referers_Websites'), - 'module' => 'Referers', - 'action' => 'getUrlsFromWebsiteId', - 'dimension' => Piwik_Translate('Referers_ColumnWebsitePage'), - 'documentation' => Piwik_Translate('Referers_WebsitesReportDocumentation', '
'), - 'isSubtableReport' => true, - 'order' => 6, - ), - - array( - 'category' => Piwik_Translate('Referers_Referers'), - 'name' => Piwik_Translate('Referers_SearchEngines'), - 'module' => 'Referers', - 'action' => 'getSearchEngines', - 'dimension' => Piwik_Translate('Referers_ColumnSearchEngine'), - 'documentation' => Piwik_Translate('Referers_SearchEnginesReportDocumentation', '
'), - 'actionToLoadSubTables' => 'getKeywordsFromSearchEngineId', - 'order' => 7, - ), - array( // subtable report - 'category' => Piwik_Translate('Referers_Referers'), - 'name' => Piwik_Translate('Referers_SearchEngines'), - 'module' => 'Referers', - 'action' => 'getKeywordsFromSearchEngineId', - 'dimension' => Piwik_Translate('Referers_ColumnKeyword'), - 'documentation' => Piwik_Translate('Referers_SearchEnginesReportDocumentation', '
'), - 'isSubtableReport' => true, - 'order' => 8, - ), - - array( - 'category' => Piwik_Translate('Referers_Referers'), - 'name' => Piwik_Translate('Referers_Campaigns'), - 'module' => 'Referers', - 'action' => 'getCampaigns', - 'dimension' => Piwik_Translate('Referers_ColumnCampaign'), - 'documentation' => Piwik_Translate('Referers_CampaignsReportDocumentation', - array('
', '', '')), - 'actionToLoadSubTables' => 'getKeywordsFromCampaignId', - 'order' => 9, - ), - array( // subtable report - 'category' => Piwik_Translate('Referers_Referers'), - 'name' => Piwik_Translate('Referers_Campaigns'), - 'module' => 'Referers', - 'action' => 'getKeywordsFromCampaignId', - 'dimension' => Piwik_Translate('Referers_ColumnKeyword'), - 'documentation' => Piwik_Translate('Referers_CampaignsReportDocumentation', - array('
', '', '')), - 'isSubtableReport' => true, - 'order' => 10, - ), - array( - 'category' => Piwik_Translate('Referers_Referers'), - 'name' => Piwik_Translate('Referers_Socials'), - 'module' => 'Referers', - 'action' => 'getSocials', - 'actionToLoadSubTables' => 'getUrlsForSocial', - 'dimension' => Piwik_Translate('Referers_ColumnSocial'), - 'documentation' => Piwik_Translate('Referers_WebsitesReportDocumentation', '
'), - 'order' => 11, - ), - )); - } - - /** - * @param Piwik_Event_Notification $notification notification object - */ - public function getSegmentsMetadata($notification) - { - $segments =& $notification->getNotificationObject(); - $segments[] = array( - 'type' => 'dimension', - 'category' => 'Referers_Referers', - 'name' => 'Referers_ColumnRefererType', - 'segment' => 'referrerType', - 'acceptedValues' => 'direct, search, website, campaign', - 'sqlSegment' => 'log_visit.referer_type', - 'sqlFilter' => 'Piwik_getRefererTypeFromShortName', - ); - $segments[] = array( - 'type' => 'dimension', - 'category' => 'Referers_Referers', - 'name' => 'Referers_ColumnKeyword', - 'segment' => 'referrerKeyword', - 'acceptedValues' => 'Encoded%20Keyword, keyword', - 'sqlSegment' => 'log_visit.referer_keyword', - ); - $segments[] = array( - 'type' => 'dimension', - 'category' => 'Referers_Referers', - 'name' => 'Referers_RefererName', - 'segment' => 'referrerName', - 'acceptedValues' => 'twitter.com, www.facebook.com, Bing, Google, Yahoo, CampaignName', - 'sqlSegment' => 'log_visit.referer_name', - ); - $segments[] = array( - 'type' => 'dimension', - 'category' => 'Referers_Referers', - 'name' => 'Live_Referrer_URL', - 'acceptedValues' => 'http%3A%2F%2Fwww.example.org%2Freferer-page.htm', - 'segment' => 'referrerUrl', - 'sqlSegment' => 'log_visit.referer_url', - ); - } - - /** - * Adds Referer widgets - */ - function addWidgets() - { - Piwik_AddWidget( 'Referers_Referers', 'Referers_WidgetKeywords', 'Referers', 'getKeywords'); - Piwik_AddWidget( 'Referers_Referers', 'Referers_WidgetExternalWebsites', 'Referers', 'getWebsites'); - Piwik_AddWidget( 'Referers_Referers', 'Referers_WidgetSocials', 'Referers', 'getSocials'); - Piwik_AddWidget( 'Referers_Referers', 'Referers_WidgetSearchEngines', 'Referers', 'getSearchEngines'); - 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()) - { - Piwik_AddWidget( 'SEO', 'Referers_WidgetTopKeywordsForPages', 'Referers', 'getKeywordsForPage'); - } - } - - /** - * Adds Web Analytics menus - */ - function addMenus() - { - Piwik_AddMenu('Referers_Referers', '', array('module' => 'Referers', 'action' => 'index'), true, 20); - Piwik_AddMenu('Referers_Referers', 'Referers_SubmenuOverview', array('module' => 'Referers', 'action' => 'index'), true, 1); - Piwik_AddMenu('Referers_Referers', 'Referers_SubmenuSearchEngines', array('module' => 'Referers', 'action' => 'getSearchEnginesAndKeywords'), true, 2); - Piwik_AddMenu('Referers_Referers', 'Referers_SubmenuWebsites', array('module' => 'Referers', 'action' => 'indexWebsites'), true, 3); - Piwik_AddMenu('Referers_Referers', 'Referers_SubmenuCampaigns', array('module' => 'Referers', 'action' => 'indexCampaigns'), true, 4); - } - - /** - * Adds Goal dimensions, so that the dimensions are displayed in the UI Goal Overview page - * - * @param Piwik_Event_Notification $notification notification object - * @return void - */ - function getReportsWithGoalMetrics( $notification ) - { - $dimensions =& $notification->getNotificationObject(); - $dimensions = array_merge($dimensions, array( - array( 'category' => Piwik_Translate('Referers_Referers'), - 'name' => Piwik_Translate('Referers_Keywords'), - 'module' => 'Referers', - 'action' => 'getKeywords', - ), - array( 'category' => Piwik_Translate('Referers_Referers'), - 'name' => Piwik_Translate('Referers_SearchEngines'), - 'module' => 'Referers', - 'action' => 'getSearchEngines', - ), - array( 'category' => Piwik_Translate('Referers_Referers'), - 'name' => Piwik_Translate('Referers_Websites'), - 'module' => 'Referers', - 'action' => 'getWebsites', - ), - array( 'category' => Piwik_Translate('Referers_Referers'), - 'name' => Piwik_Translate('Referers_Campaigns'), - 'module' => 'Referers', - 'action' => 'getCampaigns', - ), - array( 'category' => Piwik_Translate('Referers_Referers'), - 'name' => Piwik_Translate('Referers_Type'), - 'module' => 'Referers', - 'action' => 'getRefererType', - ), - )); - } - - 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']]); - - 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']]); - } - } - - /** - * 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. - * - * @param Piwik_ArchiveProcessing $archiveProcessing - * @return void - */ - protected function archiveDayRecordInDatabase($archiveProcessing) - { - $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); - } - } + public $archiveProcessing; + protected $columnToSortByBeforeTruncation; + protected $maximumRowsInDataTableLevelZero; + protected $maximumRowsInSubDataTable; + + public function getInformation() + { + $info = array( + 'description' => Piwik_Translate('Referers_PluginDescription'), + 'author' => 'Piwik', + 'author_homepage' => 'http://piwik.org/', + 'version' => Piwik_Version::VERSION, + ); + + return $info; + } + + function getListHooksRegistered() + { + $hooks = array( + 'ArchiveProcessing_Day.compute' => 'archiveDay', + 'ArchiveProcessing_Period.compute' => 'archivePeriod', + 'WidgetsList.add' => 'addWidgets', + 'Menu.add' => 'addMenus', + 'Goals.getReportsWithGoalMetrics' => 'getReportsWithGoalMetrics', + 'API.getReportMetadata' => 'getReportMetadata', + 'API.getSegmentsMetadata' => 'getSegmentsMetadata', + ); + return $hooks; + } + + /** + * @param Piwik_Event_Notification $notification notification object + */ + public function getReportMetadata($notification) + { + $reports = & $notification->getNotificationObject(); + $reports = array_merge($reports, array( + array( + 'category' => Piwik_Translate('Referers_Referers'), + 'name' => Piwik_Translate('Referers_Type'), + 'module' => 'Referers', + 'action' => 'getRefererType', + 'dimension' => Piwik_Translate('Referers_ColumnRefererType'), + 'constantRowsCount' => true, + 'documentation' => Piwik_Translate('Referers_TypeReportDocumentation') . '
' + . '' . Piwik_Translate('Referers_DirectEntry') . ': ' . Piwik_Translate('Referers_DirectEntryDocumentation') . '
' + . '' . Piwik_Translate('Referers_SearchEngines') . ': ' . Piwik_Translate('Referers_SearchEnginesDocumentation', + array('
', '"' . Piwik_Translate('Referers_SubmenuSearchEngines') . '"')) . '
' + . '' . Piwik_Translate('Referers_Websites') . ': ' . Piwik_Translate('Referers_WebsitesDocumentation', + array('
', '"' . Piwik_Translate('Referers_SubmenuWebsites') . '"')) . '
' + . '' . Piwik_Translate('Referers_Campaigns') . ': ' . Piwik_Translate('Referers_CampaignsDocumentation', + array('
', '"' . Piwik_Translate('Referers_SubmenuCampaigns') . '"')), + 'order' => 1, + ), + array( + 'category' => Piwik_Translate('Referers_Referers'), + 'name' => Piwik_Translate('Referers_WidgetGetAll'), + 'module' => 'Referers', + 'action' => 'getAll', + 'dimension' => Piwik_Translate('Referers_Referrer'), + 'documentation' => Piwik_Translate('Referers_AllReferersReportDocumentation', '
'), + 'order' => 2, + ), + array( + 'category' => Piwik_Translate('Referers_Referers'), + 'name' => Piwik_Translate('Referers_Keywords'), + 'module' => 'Referers', + 'action' => 'getKeywords', + 'actionToLoadSubTables' => 'getSearchEnginesFromKeywordId', + 'dimension' => Piwik_Translate('Referers_ColumnKeyword'), + 'documentation' => Piwik_Translate('Referers_KeywordsReportDocumentation', '
'), + 'order' => 3, + ), + array( // subtable report + 'category' => Piwik_Translate('Referers_Referers'), + 'name' => Piwik_Translate('Referers_Keywords'), + 'module' => 'Referers', + 'action' => 'getSearchEnginesFromKeywordId', + 'dimension' => Piwik_Translate('Referers_ColumnSearchEngine'), + 'documentation' => Piwik_Translate('Referers_KeywordsReportDocumentation', '
'), + 'isSubtableReport' => true, + 'order' => 4 + ), + + array( + 'category' => Piwik_Translate('Referers_Referers'), + 'name' => Piwik_Translate('Referers_Websites'), + 'module' => 'Referers', + 'action' => 'getWebsites', + 'dimension' => Piwik_Translate('Referers_ColumnWebsite'), + 'documentation' => Piwik_Translate('Referers_WebsitesReportDocumentation', '
'), + 'actionToLoadSubTables' => 'getUrlsFromWebsiteId', + 'order' => 5 + ), + array( // subtable report + 'category' => Piwik_Translate('Referers_Referers'), + 'name' => Piwik_Translate('Referers_Websites'), + 'module' => 'Referers', + 'action' => 'getUrlsFromWebsiteId', + 'dimension' => Piwik_Translate('Referers_ColumnWebsitePage'), + 'documentation' => Piwik_Translate('Referers_WebsitesReportDocumentation', '
'), + 'isSubtableReport' => true, + 'order' => 6, + ), + + array( + 'category' => Piwik_Translate('Referers_Referers'), + 'name' => Piwik_Translate('Referers_SearchEngines'), + 'module' => 'Referers', + 'action' => 'getSearchEngines', + 'dimension' => Piwik_Translate('Referers_ColumnSearchEngine'), + 'documentation' => Piwik_Translate('Referers_SearchEnginesReportDocumentation', '
'), + 'actionToLoadSubTables' => 'getKeywordsFromSearchEngineId', + 'order' => 7, + ), + array( // subtable report + 'category' => Piwik_Translate('Referers_Referers'), + 'name' => Piwik_Translate('Referers_SearchEngines'), + 'module' => 'Referers', + 'action' => 'getKeywordsFromSearchEngineId', + 'dimension' => Piwik_Translate('Referers_ColumnKeyword'), + 'documentation' => Piwik_Translate('Referers_SearchEnginesReportDocumentation', '
'), + 'isSubtableReport' => true, + 'order' => 8, + ), + + array( + 'category' => Piwik_Translate('Referers_Referers'), + 'name' => Piwik_Translate('Referers_Campaigns'), + 'module' => 'Referers', + 'action' => 'getCampaigns', + 'dimension' => Piwik_Translate('Referers_ColumnCampaign'), + 'documentation' => Piwik_Translate('Referers_CampaignsReportDocumentation', + array('
', '', '')), + 'actionToLoadSubTables' => 'getKeywordsFromCampaignId', + 'order' => 9, + ), + array( // subtable report + 'category' => Piwik_Translate('Referers_Referers'), + 'name' => Piwik_Translate('Referers_Campaigns'), + 'module' => 'Referers', + 'action' => 'getKeywordsFromCampaignId', + 'dimension' => Piwik_Translate('Referers_ColumnKeyword'), + 'documentation' => Piwik_Translate('Referers_CampaignsReportDocumentation', + array('
', '', '')), + 'isSubtableReport' => true, + 'order' => 10, + ), + array( + 'category' => Piwik_Translate('Referers_Referers'), + 'name' => Piwik_Translate('Referers_Socials'), + 'module' => 'Referers', + 'action' => 'getSocials', + 'actionToLoadSubTables' => 'getUrlsForSocial', + 'dimension' => Piwik_Translate('Referers_ColumnSocial'), + 'documentation' => Piwik_Translate('Referers_WebsitesReportDocumentation', '
'), + 'order' => 11, + ), + )); + } + + /** + * @param Piwik_Event_Notification $notification notification object + */ + public function getSegmentsMetadata($notification) + { + $segments =& $notification->getNotificationObject(); + $segments[] = array( + 'type' => 'dimension', + 'category' => 'Referers_Referers', + 'name' => 'Referers_ColumnRefererType', + 'segment' => 'referrerType', + 'acceptedValues' => 'direct, search, website, campaign', + 'sqlSegment' => 'log_visit.referer_type', + 'sqlFilter' => 'Piwik_getRefererTypeFromShortName', + ); + $segments[] = array( + 'type' => 'dimension', + 'category' => 'Referers_Referers', + 'name' => 'Referers_ColumnKeyword', + 'segment' => 'referrerKeyword', + 'acceptedValues' => 'Encoded%20Keyword, keyword', + 'sqlSegment' => 'log_visit.referer_keyword', + ); + $segments[] = array( + 'type' => 'dimension', + 'category' => 'Referers_Referers', + 'name' => 'Referers_RefererName', + 'segment' => 'referrerName', + 'acceptedValues' => 'twitter.com, www.facebook.com, Bing, Google, Yahoo, CampaignName', + 'sqlSegment' => 'log_visit.referer_name', + ); + $segments[] = array( + 'type' => 'dimension', + 'category' => 'Referers_Referers', + 'name' => 'Live_Referrer_URL', + 'acceptedValues' => 'http%3A%2F%2Fwww.example.org%2Freferer-page.htm', + 'segment' => 'referrerUrl', + 'sqlSegment' => 'log_visit.referer_url', + ); + } + + /** + * Adds Referer widgets + */ + function addWidgets() + { + Piwik_AddWidget('Referers_Referers', 'Referers_WidgetKeywords', 'Referers', 'getKeywords'); + Piwik_AddWidget('Referers_Referers', 'Referers_WidgetExternalWebsites', 'Referers', 'getWebsites'); + Piwik_AddWidget('Referers_Referers', 'Referers_WidgetSocials', 'Referers', 'getSocials'); + Piwik_AddWidget('Referers_Referers', 'Referers_WidgetSearchEngines', 'Referers', 'getSearchEngines'); + 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()) { + Piwik_AddWidget('SEO', 'Referers_WidgetTopKeywordsForPages', 'Referers', 'getKeywordsForPage'); + } + } + + /** + * Adds Web Analytics menus + */ + function addMenus() + { + Piwik_AddMenu('Referers_Referers', '', array('module' => 'Referers', 'action' => 'index'), true, 20); + Piwik_AddMenu('Referers_Referers', 'Referers_SubmenuOverview', array('module' => 'Referers', 'action' => 'index'), true, 1); + Piwik_AddMenu('Referers_Referers', 'Referers_SubmenuSearchEngines', array('module' => 'Referers', 'action' => 'getSearchEnginesAndKeywords'), true, 2); + Piwik_AddMenu('Referers_Referers', 'Referers_SubmenuWebsites', array('module' => 'Referers', 'action' => 'indexWebsites'), true, 3); + Piwik_AddMenu('Referers_Referers', 'Referers_SubmenuCampaigns', array('module' => 'Referers', 'action' => 'indexCampaigns'), true, 4); + } + + /** + * Adds Goal dimensions, so that the dimensions are displayed in the UI Goal Overview page + * + * @param Piwik_Event_Notification $notification notification object + * @return void + */ + function getReportsWithGoalMetrics($notification) + { + $dimensions =& $notification->getNotificationObject(); + $dimensions = array_merge($dimensions, array( + array('category' => Piwik_Translate('Referers_Referers'), + 'name' => Piwik_Translate('Referers_Keywords'), + 'module' => 'Referers', + 'action' => 'getKeywords', + ), + array('category' => Piwik_Translate('Referers_Referers'), + 'name' => Piwik_Translate('Referers_SearchEngines'), + 'module' => 'Referers', + 'action' => 'getSearchEngines', + ), + array('category' => Piwik_Translate('Referers_Referers'), + 'name' => Piwik_Translate('Referers_Websites'), + 'module' => 'Referers', + 'action' => 'getWebsites', + ), + array('category' => Piwik_Translate('Referers_Referers'), + 'name' => Piwik_Translate('Referers_Campaigns'), + 'module' => 'Referers', + 'action' => 'getCampaigns', + ), + array('category' => Piwik_Translate('Referers_Referers'), + 'name' => Piwik_Translate('Referers_Type'), + 'module' => 'Referers', + 'action' => 'getRefererType', + ), + )); + } + + 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']]); + + 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']]); + } + } + + /** + * 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. + * + * @param Piwik_ArchiveProcessing $archiveProcessing + * @return void + */ + protected function archiveDayRecordInDatabase($archiveProcessing) + { + $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); + } + } } diff --git a/plugins/Referers/functions.php b/plugins/Referers/functions.php index afa9fe4c3f..28faab9885 100644 --- a/plugins/Referers/functions.php +++ b/plugins/Referers/functions.php @@ -1,10 +1,10 @@ $name) - { - if ($name == $GLOBALS['Piwik_socialUrl'][$domain]) - { - $firstDomain = $domainKey; - break; - } - } - - $pathWithCode = 'plugins/Referers/images/socials/'.$firstDomain.'.png'; - return $pathWithCode; - } - else - { - return 'plugins/Referers/images/socials/xx.png'; - } + if (isset($GLOBALS['Piwik_socialUrl'][$domain])) { + // image names are by first domain in list, so make sure we use the first if $domain isn't it + $firstDomain = $domain; + foreach ($GLOBALS['Piwik_socialUrl'] as $domainKey => $name) { + if ($name == $GLOBALS['Piwik_socialUrl'][$domain]) { + $firstDomain = $domainKey; + break; + } + } + + $pathWithCode = 'plugins/Referers/images/socials/' . $firstDomain . '.png'; + return $pathWithCode; + } else { + return 'plugins/Referers/images/socials/xx.png'; + } } /** @@ -120,16 +111,13 @@ function Piwik_getSocialsLogoFromUrl($domain) */ function Piwik_getSearchEngineUrlFromName($name) { - $searchEngineNames = Piwik_Common::getSearchEngineNames(); - if(isset($searchEngineNames[$name])) - { - $url = 'http://'.$searchEngineNames[$name]; - } - else - { - $url = 'URL unknown!'; - } - return $url; + $searchEngineNames = Piwik_Common::getSearchEngineNames(); + if (isset($searchEngineNames[$name])) { + $url = 'http://' . $searchEngineNames[$name]; + } else { + $url = 'URL unknown!'; + } + return $url; } /** @@ -140,12 +128,11 @@ function Piwik_getSearchEngineUrlFromName($name) */ function Piwik_getSearchEngineHostFromUrl($url) { - $url = substr($url, strpos($url,'//') + 2); - if(($p = strpos($url, '/')) !== false) - { - $url = substr($url, 0, $p); - } - return $url; + $url = substr($url, strpos($url, '//') + 2); + if (($p = strpos($url, '/')) !== false) { + $url = substr($url, 0, $p); + } + return $url; } /** @@ -157,14 +144,13 @@ function Piwik_getSearchEngineHostFromUrl($url) */ function Piwik_getSearchEngineLogoFromUrl($url) { - $pathInPiwik = 'plugins/Referers/images/searchEngines/%s.png'; - $pathWithCode = sprintf($pathInPiwik, Piwik_getSearchEngineHostFromUrl($url)); - $absolutePath = PIWIK_INCLUDE_PATH . '/' . $pathWithCode; - if(file_exists($absolutePath)) - { - return $pathWithCode; - } - return sprintf($pathInPiwik, 'xx'); + $pathInPiwik = 'plugins/Referers/images/searchEngines/%s.png'; + $pathWithCode = sprintf($pathInPiwik, Piwik_getSearchEngineHostFromUrl($url)); + $absolutePath = PIWIK_INCLUDE_PATH . '/' . $pathWithCode; + if (file_exists($absolutePath)) { + return $pathWithCode; + } + return sprintf($pathInPiwik, 'xx'); } /** @@ -175,8 +161,8 @@ function Piwik_getSearchEngineLogoFromUrl($url) */ function Piwik_getSearchEngineHostPathFromUrl($url) { - $url = substr($url, strpos($url,'//') + 2); - return $url; + $url = substr($url, strpos($url, '//') + 2); + return $url; } /** @@ -190,20 +176,18 @@ function Piwik_getSearchEngineHostPathFromUrl($url) */ function Piwik_getSearchEngineUrlFromUrlAndKeyword($url, $keyword) { - if($keyword === Piwik_Referers::LABEL_KEYWORD_NOT_DEFINED) - { - return 'http://piwik.org/faq/general/#faq_144'; - } - $searchEngineUrls = Piwik_Common::getSearchEngineUrls(); - $keyword = urlencode($keyword); - $keyword = str_replace(urlencode('+'), urlencode(' '), $keyword); - $path = @$searchEngineUrls[Piwik_getSearchEngineHostPathFromUrl($url)][2]; - if(empty($path)) - { - return false; - } - $path = str_replace("{k}", $keyword, $path); - return $url . (substr($url, -1) != '/' ? '/' : '') . $path; + if ($keyword === Piwik_Referers::LABEL_KEYWORD_NOT_DEFINED) { + return 'http://piwik.org/faq/general/#faq_144'; + } + $searchEngineUrls = Piwik_Common::getSearchEngineUrls(); + $keyword = urlencode($keyword); + $keyword = str_replace(urlencode('+'), urlencode(' '), $keyword); + $path = @$searchEngineUrls[Piwik_getSearchEngineHostPathFromUrl($url)][2]; + if (empty($path)) { + return false; + } + $path = str_replace("{k}", $keyword, $path); + return $url . (substr($url, -1) != '/' ? '/' : '') . $path; } /** @@ -217,7 +201,7 @@ function Piwik_getSearchEngineUrlFromUrlAndKeyword($url, $keyword) */ function Piwik_getSearchEngineUrlFromKeywordAndUrl($keyword, $url) { - return Piwik_getSearchEngineUrlFromUrlAndKeyword($url, $keyword); + return Piwik_getSearchEngineUrlFromUrlAndKeyword($url, $keyword); } /** @@ -228,27 +212,26 @@ function Piwik_getSearchEngineUrlFromKeywordAndUrl($keyword, $url) */ function Piwik_getRefererTypeLabel($label) { - $indexTranslation = ''; - switch($label) - { - case Piwik_Common::REFERER_TYPE_DIRECT_ENTRY: - $indexTranslation = 'Referers_DirectEntry'; - break; - case Piwik_Common::REFERER_TYPE_SEARCH_ENGINE: - $indexTranslation = 'Referers_SearchEngines'; - break; - case Piwik_Common::REFERER_TYPE_WEBSITE: - $indexTranslation = 'Referers_Websites'; - break; - case Piwik_Common::REFERER_TYPE_CAMPAIGN: - $indexTranslation = 'Referers_Campaigns'; - break; - default: - // case of newsletter, partners, before Piwik 0.2.25 - $indexTranslation = 'General_Others'; - break; - } - return Piwik_Translate($indexTranslation); + $indexTranslation = ''; + switch ($label) { + case Piwik_Common::REFERER_TYPE_DIRECT_ENTRY: + $indexTranslation = 'Referers_DirectEntry'; + break; + case Piwik_Common::REFERER_TYPE_SEARCH_ENGINE: + $indexTranslation = 'Referers_SearchEngines'; + break; + case Piwik_Common::REFERER_TYPE_WEBSITE: + $indexTranslation = 'Referers_Websites'; + break; + case Piwik_Common::REFERER_TYPE_CAMPAIGN: + $indexTranslation = 'Referers_Campaigns'; + break; + default: + // case of newsletter, partners, before Piwik 0.2.25 + $indexTranslation = 'General_Others'; + break; + } + return Piwik_Translate($indexTranslation); } /** @@ -259,34 +242,31 @@ function Piwik_getRefererTypeLabel($label) */ function Piwik_getRefererTypeFromShortName($name) { - $map = array( - Piwik_Common::REFERER_TYPE_SEARCH_ENGINE => 'search', - Piwik_Common::REFERER_TYPE_WEBSITE => 'website', - Piwik_Common::REFERER_TYPE_DIRECT_ENTRY => 'direct', - Piwik_Common::REFERER_TYPE_CAMPAIGN => 'campaign', - ); - if(isset($map[$name])) - { - return $map[$name]; - } - if($found = array_search($name, $map)) - { - return $found; - } - throw new Exception("Referrer type '$name' is not valid."); + $map = array( + Piwik_Common::REFERER_TYPE_SEARCH_ENGINE => 'search', + Piwik_Common::REFERER_TYPE_WEBSITE => 'website', + Piwik_Common::REFERER_TYPE_DIRECT_ENTRY => 'direct', + Piwik_Common::REFERER_TYPE_CAMPAIGN => 'campaign', + ); + if (isset($map[$name])) { + return $map[$name]; + } + if ($found = array_search($name, $map)) { + return $found; + } + throw new Exception("Referrer type '$name' is not valid."); } /** * Returns a URL w/o the protocol type. - * + * * @param string $url * @return string */ -function Piwik_Referrers_removeUrlProtocol( $url ) +function Piwik_Referrers_removeUrlProtocol($url) { - if (preg_match('/^[a-zA-Z_-]+:\/\//', $url, $matches)) - { - return substr($url, strlen($matches[0])); - } - return $url; + if (preg_match('/^[a-zA-Z_-]+:\/\//', $url, $matches)) { + return substr($url, strlen($matches[0])); + } + return $url; } diff --git a/plugins/Referers/templates/Websites_SocialNetworks.tpl b/plugins/Referers/templates/Websites_SocialNetworks.tpl index 6e964fff26..496bdb085c 100755 --- a/plugins/Referers/templates/Websites_SocialNetworks.tpl +++ b/plugins/Referers/templates/Websites_SocialNetworks.tpl @@ -1,9 +1,9 @@
-

{'Referers_Websites'|translate}

- {$websites} +

{'Referers_Websites'|translate}

+ {$websites}
-

{'Referers_Socials'|translate}

- {$socials} +

{'Referers_Socials'|translate}

+ {$socials}
diff --git a/plugins/Referers/templates/index.tpl b/plugins/Referers/templates/index.tpl index 521209b0ea..a3ac028c86 100644 --- a/plugins/Referers/templates/index.tpl +++ b/plugins/Referers/templates/index.tpl @@ -2,73 +2,87 @@

{'Referers_Evolution'|translate}

{$graphEvolutionReferers} -
+
-

{'Referers_Type'|translate}

-
-
{sparkline src=$urlSparklineDirectEntry} - {'Referers_TypeDirectEntries'|translate:"$visitorsFromDirectEntry"}{if !empty($visitorsFromDirectEntryPercent)}, {$visitorsFromDirectEntryPercent}% of visits{/if}{if !empty($visitorsFromDirectEntryEvolution)} {$visitorsFromDirectEntryEvolution}{/if} -
-
{sparkline src=$urlSparklineSearchEngines} - {'Referers_TypeSearchEngines'|translate:"$visitorsFromSearchEngines"}{if !empty($visitorsFromSearchEnginesPercent)}, {$visitorsFromSearchEnginesPercent}% of visits{/if}{if !empty($visitorsFromSearchEnginesEvolution)} {$visitorsFromSearchEnginesEvolution}{/if} -
-
-
-
{sparkline src=$urlSparklineWebsites} - {'Referers_TypeWebsites'|translate:"$visitorsFromWebsites"}{if !empty($visitorsFromWebsitesPercent)}, {$visitorsFromWebsitesPercent}% of visits{/if}{if !empty($visitorsFromWebsitesEvolution)} {$visitorsFromWebsitesEvolution}{/if} -
-
{sparkline src=$urlSparklineCampaigns} - {'Referers_TypeCampaigns'|translate:"$visitorsFromCampaigns"}{if !empty($visitorsFromCampaignsPercent)}, {$visitorsFromCampaignsPercent}% of visits{/if}{if !empty($visitorsFromCampaignsEvolution)} {$visitorsFromCampaignsEvolution}{/if} -
-
- -
- -
-
-

{'General_MoreDetails'|translate} ({'General_Show_js'|translate})

-
+

{'Referers_Type'|translate}

- - -

-

+
+
{sparkline src=$urlSparklineDirectEntry} + {'Referers_TypeDirectEntries'|translate:"$visitorsFromDirectEntry"}{if !empty($visitorsFromDirectEntryPercent)}, + {$visitorsFromDirectEntryPercent}% + of visits{/if}{if !empty($visitorsFromDirectEntryEvolution)} {$visitorsFromDirectEntryEvolution}{/if} +
+
{sparkline src=$urlSparklineSearchEngines} + {'Referers_TypeSearchEngines'|translate:"$visitorsFromSearchEngines"}{if !empty($visitorsFromSearchEnginesPercent)}, + {$visitorsFromSearchEnginesPercent}% + of visits{/if}{if !empty($visitorsFromSearchEnginesEvolution)} {$visitorsFromSearchEnginesEvolution}{/if} +
+
+
+
{sparkline src=$urlSparklineWebsites} + {'Referers_TypeWebsites'|translate:"$visitorsFromWebsites"}{if !empty($visitorsFromWebsitesPercent)}, + {$visitorsFromWebsitesPercent}% + of visits{/if}{if !empty($visitorsFromWebsitesEvolution)} {$visitorsFromWebsitesEvolution}{/if} +
+
{sparkline src=$urlSparklineCampaigns} + {'Referers_TypeCampaigns'|translate:"$visitorsFromCampaigns"}{if !empty($visitorsFromCampaignsPercent)}, + {$visitorsFromCampaignsPercent}% + of visits{/if}{if !empty($visitorsFromCampaignsEvolution)} {$visitorsFromCampaignsEvolution}{/if} +
+
+ +
+ +
+
+ +

{'General_MoreDetails'|translate} ({'General_Show_js'|translate})

+
+ + + +

+ +

-

{'Referers_DetailsByRefererType'|translate}

- {$dataTableRefererType} +

{'Referers_DetailsByRefererType'|translate}

+ {$dataTableRefererType}
{if $totalVisits > 0} -

{'Referers_ReferrersOverview'|translate}

-{$referrersReportsByDimension} +

{'Referers_ReferrersOverview'|translate}

+ {$referrersReportsByDimension} {/if} {include file="CoreHome/templates/sparkline_footer.tpl"} diff --git a/plugins/Referers/templates/searchEngines_Keywords.tpl b/plugins/Referers/templates/searchEngines_Keywords.tpl index ededce4848..4d685f681c 100644 --- a/plugins/Referers/templates/searchEngines_Keywords.tpl +++ b/plugins/Referers/templates/searchEngines_Keywords.tpl @@ -1,9 +1,9 @@
-

{'Referers_Keywords'|translate}

- {$keywords} +

{'Referers_Keywords'|translate}

+ {$keywords}
-

{'Referers_SearchEngines'|translate}

- {$searchEngines} +

{'Referers_SearchEngines'|translate}

+ {$searchEngines}
diff --git a/plugins/SEO/API.php b/plugins/SEO/API.php index 62613f3de5..26ac00c5bd 100644 --- a/plugins/SEO/API.php +++ b/plugins/SEO/API.php @@ -17,92 +17,91 @@ require_once PIWIK_INCLUDE_PATH . '/plugins/Referers/functions.php'; /** * The SEO API lets you access a list of SEO metrics for the specified URL: Google Pagerank, Goolge/Bing indexed pages * Alexa Rank, age of the Domain name and count of DMOZ entries. - * + * * @package Piwik_SEO */ -class Piwik_SEO_API +class Piwik_SEO_API { - static private $instance = null; - /** - * @return Piwik_SEO_API - */ - static public function getInstance() - { - if (self::$instance == null) - { - self::$instance = new self; - } - return self::$instance; - } - - /** - * Returns SEO statistics for a URL. - * - * @param string $url URL to request SEO stats for - * @return Piwik_DataTable - */ - public function getRank( $url ) - { - Piwik::checkUserHasSomeViewAccess(); - $rank = new Piwik_SEO_RankChecker($url); - - $linkToMajestic = Piwik_SEO_MajesticClient::getLinkForUrl($url); - - $data = array( - 'Google PageRank' => array( - 'rank' => $rank->getPageRank(), - 'logo' => Piwik_getSearchEngineLogoFromUrl('http://google.com'), - 'id' => 'pagerank' - ), + static private $instance = null; + + /** + * @return Piwik_SEO_API + */ + static public function getInstance() + { + if (self::$instance == null) { + self::$instance = new self; + } + return self::$instance; + } + + /** + * Returns SEO statistics for a URL. + * + * @param string $url URL to request SEO stats for + * @return Piwik_DataTable + */ + public function getRank($url) + { + Piwik::checkUserHasSomeViewAccess(); + $rank = new Piwik_SEO_RankChecker($url); + + $linkToMajestic = Piwik_SEO_MajesticClient::getLinkForUrl($url); + + $data = array( + 'Google PageRank' => array( + 'rank' => $rank->getPageRank(), + 'logo' => Piwik_getSearchEngineLogoFromUrl('http://google.com'), + 'id' => 'pagerank' + ), Piwik_Translate('SEO_Google_IndexedPages') => array( 'rank' => $rank->getIndexedPagesGoogle(), 'logo' => Piwik_getSearchEngineLogoFromUrl('http://google.com'), - 'id' => 'google-index', + 'id' => 'google-index', ), - Piwik_Translate('SEO_Bing_IndexedPages') => array( + Piwik_Translate('SEO_Bing_IndexedPages') => array( 'rank' => $rank->getIndexedPagesBing(), 'logo' => Piwik_getSearchEngineLogoFromUrl('http://bing.com'), - 'id' => 'bing-index', - ), - Piwik_Translate('SEO_AlexaRank') => array( - 'rank' => $rank->getAlexaRank(), - 'logo' => Piwik_getSearchEngineLogoFromUrl('http://alexa.com'), - 'id' => 'alexa', - ), - Piwik_Translate('SEO_DomainAge') => array( - 'rank' => $rank->getAge(), - 'logo' => 'plugins/SEO/images/whois.png', - 'id' => 'domain-age', - ), - Piwik_Translate('SEO_ExternalBacklinks') => array( - 'rank' => $rank->getExternalBacklinkCount(), - 'logo' => 'plugins/SEO/images/majesticseo.png', - 'logo_link' => $linkToMajestic, - 'logo_tooltip' => Piwik_Translate('SEO_ViewBacklinksOnMajesticSEO'), - 'id' => 'external-backlinks', - ), - Piwik_Translate('SEO_ReferrerDomains') => array( - 'rank' => $rank->getReferrerDomainCount(), - 'logo' => 'plugins/SEO/images/majesticseo.png', - 'logo_link' => $linkToMajestic, - 'logo_tooltip' => Piwik_Translate('SEO_ViewBacklinksOnMajesticSEO'), - 'id' => 'referrer-domains', - ), - ); + 'id' => 'bing-index', + ), + Piwik_Translate('SEO_AlexaRank') => array( + 'rank' => $rank->getAlexaRank(), + 'logo' => Piwik_getSearchEngineLogoFromUrl('http://alexa.com'), + 'id' => 'alexa', + ), + Piwik_Translate('SEO_DomainAge') => array( + 'rank' => $rank->getAge(), + 'logo' => 'plugins/SEO/images/whois.png', + 'id' => 'domain-age', + ), + Piwik_Translate('SEO_ExternalBacklinks') => array( + 'rank' => $rank->getExternalBacklinkCount(), + 'logo' => 'plugins/SEO/images/majesticseo.png', + 'logo_link' => $linkToMajestic, + 'logo_tooltip' => Piwik_Translate('SEO_ViewBacklinksOnMajesticSEO'), + 'id' => 'external-backlinks', + ), + Piwik_Translate('SEO_ReferrerDomains') => array( + 'rank' => $rank->getReferrerDomainCount(), + 'logo' => 'plugins/SEO/images/majesticseo.png', + 'logo_link' => $linkToMajestic, + 'logo_tooltip' => Piwik_Translate('SEO_ViewBacklinksOnMajesticSEO'), + 'id' => 'referrer-domains', + ), + ); - // Add DMOZ only if > 0 entries found - $dmozRank = array( - 'rank' => $rank->getDmoz(), - 'logo' => Piwik_getSearchEngineLogoFromUrl('http://dmoz.org'), - 'id' => 'dmoz', - ); - if($dmozRank['rank'] > 0) - { - $data[Piwik_Translate('SEO_Dmoz')] = $dmozRank; - } + // Add DMOZ only if > 0 entries found + $dmozRank = array( + 'rank' => $rank->getDmoz(), + 'logo' => Piwik_getSearchEngineLogoFromUrl('http://dmoz.org'), + 'id' => 'dmoz', + ); + if ($dmozRank['rank'] > 0) { + $data[Piwik_Translate('SEO_Dmoz')] = $dmozRank; + } - $dataTable = new Piwik_DataTable(); - $dataTable->addRowsFromArrayWithIndexLabel($data); - return $dataTable; - } + $dataTable = new Piwik_DataTable(); + $dataTable->addRowsFromArrayWithIndexLabel($data); + return $dataTable; + } } diff --git a/plugins/SEO/Controller.php b/plugins/SEO/Controller.php index 53a749e2d3..f0e75f8200 100644 --- a/plugins/SEO/Controller.php +++ b/plugins/SEO/Controller.php @@ -1,43 +1,42 @@ getMainUrl(); + } + + $dataTable = Piwik_SEO_API::getInstance()->getRank($url); - $url = urldecode(Piwik_Common::getRequestVar('url', '', 'string')); - - if(!empty($url) && strpos($url, 'http://') !== 0 && strpos($url, 'https://') !== 0) { - $url = 'http://'.$url; - } - - if(empty($url) || !Piwik_Common::isLookLikeUrl($url)) - { - $url = $site->getMainUrl(); - } + $view = Piwik_View::factory('index'); + $view->urlToRank = Piwik_SEO_RankChecker::extractDomainFromUrl($url); - $dataTable = Piwik_SEO_API::getInstance()->getRank($url); - - $view = Piwik_View::factory('index'); - $view->urlToRank = Piwik_SEO_RankChecker::extractDomainFromUrl($url); - - $renderer = Piwik_DataTable_Renderer::factory('php'); - $renderer->setSerialize(false); - $view->ranks = $renderer->render($dataTable); - echo $view->render(); - } + $renderer = Piwik_DataTable_Renderer::factory('php'); + $renderer->setSerialize(false); + $view->ranks = $renderer->render($dataTable); + echo $view->render(); + } } diff --git a/plugins/SEO/MajesticClient.php b/plugins/SEO/MajesticClient.php index 61ab4d2d29..f6678be192 100644 --- a/plugins/SEO/MajesticClient.php +++ b/plugins/SEO/MajesticClient.php @@ -11,85 +11,85 @@ /** * Client for Majestic SEO's HTTP API. - * + * * Hides the HTTP request sending logic. */ class Piwik_SEO_MajesticClient { - const API_BASE = 'http://simpleapi.majesticseo.com/sapi/'; - const API_KEY = 'ETHPYY'; // please only use this key within Piwik - - /** - * Returns a URL that can be used to view all SEO data for a particular website. - * - * @param string $targetSiteUrl The URL of the website for whom SEO stats should be - * accessible for. - * @return string - */ - public static function getLinkForUrl( $targetSiteUrl ) - { - $domain = @parse_url($targetSiteUrl, PHP_URL_HOST); - return "http://www.majesticseo.com/reports/site-explorer/summary/$domain?IndexDataSource=F"; - } - - /** - * Returns backlink statistics including the count of backlinks and count of - * referrer domains (domains with backlinks). - * - * This method issues an HTTP request and waits for it to return. - * - * @param string $siteDomain The domain of the website to get stats for. - * @param int $timeout The number of seconds to wait before aborting - * the HTTP request. - * @return array An array containing the backlink count and referrer - * domain count: - * array( - * 'backlink_count' => X, - * 'referrer_domains_count' => Y - * ) - * If either stat is false, either the API returned an - * error, or the IP was blocked for this request. - */ - public function getBacklinkStats( $siteDomain, $timeout = 300 ) - { - $apiUrl = $this->getApiUrl($method = 'GetBacklinkStats', $args = array( - 'items' => '1', - 'item0' => $siteDomain - )); - $apiResponse = Piwik_Http::sendHttpRequest($apiUrl, $timeout); - - $result = array( - 'backlink_count' => false, - 'referrer_domains_count' => false - ); - - $apiResponse = Piwik_Common::json_decode($apiResponse, $assoc = true); - if (!empty($apiResponse) - && !empty($apiResponse['Data'])) - { - $siteSeoStats = reset($apiResponse['Data']); - - if (isset($siteSeoStats['ExtBackLinks']) - && $siteSeoStats['ExtBackLinks'] !== -1) - { - $result['backlink_count'] = $siteSeoStats['ExtBackLinks']; - } - - if (isset($siteSeoStats['RefDomains']) - && $siteSeoStats['RefDomains'] !== -1) - { - $result['referrer_domains_count'] = $siteSeoStats['RefDomains']; - } - } - - return $result; - } - - private function getApiUrl( $method, $args = array() ) - { - $args['sak'] = self::API_KEY; - - $queryString = http_build_query($args); - return self::API_BASE.$method.'?'.$queryString; - } + const API_BASE = 'http://simpleapi.majesticseo.com/sapi/'; + const API_KEY = 'ETHPYY'; // please only use this key within Piwik + + /** + * Returns a URL that can be used to view all SEO data for a particular website. + * + * @param string $targetSiteUrl The URL of the website for whom SEO stats should be + * accessible for. + * @return string + */ + public static function getLinkForUrl($targetSiteUrl) + { + $domain = @parse_url($targetSiteUrl, PHP_URL_HOST); + return "http://www.majesticseo.com/reports/site-explorer/summary/$domain?IndexDataSource=F"; + } + + /** + * Returns backlink statistics including the count of backlinks and count of + * referrer domains (domains with backlinks). + * + * This method issues an HTTP request and waits for it to return. + * + * @param string $siteDomain The domain of the website to get stats for. + * @param int $timeout The number of seconds to wait before aborting + * the HTTP request. + * @return array An array containing the backlink count and referrer + * domain count: + * array( + * 'backlink_count' => X, + * 'referrer_domains_count' => Y + * ) + * If either stat is false, either the API returned an + * error, or the IP was blocked for this request. + */ + public function getBacklinkStats($siteDomain, $timeout = 300) + { + $apiUrl = $this->getApiUrl($method = 'GetBacklinkStats', $args = array( + 'items' => '1', + 'item0' => $siteDomain + )); + $apiResponse = Piwik_Http::sendHttpRequest($apiUrl, $timeout); + + $result = array( + 'backlink_count' => false, + 'referrer_domains_count' => false + ); + + $apiResponse = Piwik_Common::json_decode($apiResponse, $assoc = true); + if (!empty($apiResponse) + && !empty($apiResponse['Data']) + ) { + $siteSeoStats = reset($apiResponse['Data']); + + if (isset($siteSeoStats['ExtBackLinks']) + && $siteSeoStats['ExtBackLinks'] !== -1 + ) { + $result['backlink_count'] = $siteSeoStats['ExtBackLinks']; + } + + if (isset($siteSeoStats['RefDomains']) + && $siteSeoStats['RefDomains'] !== -1 + ) { + $result['referrer_domains_count'] = $siteSeoStats['RefDomains']; + } + } + + return $result; + } + + private function getApiUrl($method, $args = array()) + { + $args['sak'] = self::API_KEY; + + $queryString = http_build_query($args); + return self::API_BASE . $method . '?' . $queryString; + } } diff --git a/plugins/SEO/RankChecker.php b/plugins/SEO/RankChecker.php index da9fa0cf60..57512c4e8f 100644 --- a/plugins/SEO/RankChecker.php +++ b/plugins/SEO/RankChecker.php @@ -20,46 +20,46 @@ */ class Piwik_SEO_RankChecker { - private $url; - private $majesticInfo = null; - - public function __construct($url) - { - $this->url = self::extractDomainFromUrl($url); - } - - /** - * Extract domain from URL as the web services generally - * expect only a domain name (i.e., no protocol, port, path, query, etc). - * - * @param string $url - * @return string - */ - static public function extractDomainFromUrl($url) - { - return preg_replace( - array( - '~^https?\://~si', // strip protocol - '~[/:#?;%&].*~', // strip port, path, query, anchor, etc - '~\.$~', // trailing period - ), - '', $url); - } - - /** - * Web service proxy that retrieves the content at the specified URL - * - * @param string $url - * @return string - */ - private function getPage($url) - { - try { - return str_replace(' ', ' ', Piwik_Http::sendHttpRequest($url, $timeout = 10, @$_SERVER['HTTP_USER_AGENT'])); - } catch(Exception $e) { - return ''; - } - } + private $url; + private $majesticInfo = null; + + public function __construct($url) + { + $this->url = self::extractDomainFromUrl($url); + } + + /** + * Extract domain from URL as the web services generally + * expect only a domain name (i.e., no protocol, port, path, query, etc). + * + * @param string $url + * @return string + */ + static public function extractDomainFromUrl($url) + { + return preg_replace( + array( + '~^https?\://~si', // strip protocol + '~[/:#?;%&].*~', // strip port, path, query, anchor, etc + '~\.$~', // trailing period + ), + '', $url); + } + + /** + * Web service proxy that retrieves the content at the specified URL + * + * @param string $url + * @return string + */ + private function getPage($url) + { + try { + return str_replace(' ', ' ', Piwik_Http::sendHttpRequest($url, $timeout = 10, @$_SERVER['HTTP_USER_AGENT'])); + } catch (Exception $e) { + return ''; + } + } /** * Returns the google page rank for the current url @@ -67,16 +67,16 @@ class Piwik_SEO_RankChecker * @return int */ public function getPageRank() - { - $chwrite = $this->CheckHash($this->HashURL($this->url)); + { + $chwrite = $this->CheckHash($this->HashURL($this->url)); - $url="http://toolbarqueries.google.com/tbr?client=navclient-auto&ch=".$chwrite."&features=Rank&q=info:".$this->url."&num=100&filter=0"; - $data = $this->getPage($url); - preg_match('#Rank_[0-9]:[0-9]:([0-9]+){1,}#si', $data, $p); - $value = isset($p[1]) ? $p[1] : 0; + $url = "http://toolbarqueries.google.com/tbr?client=navclient-auto&ch=" . $chwrite . "&features=Rank&q=info:" . $this->url . "&num=100&filter=0"; + $data = $this->getPage($url); + preg_match('#Rank_[0-9]:[0-9]:([0-9]+){1,}#si', $data, $p); + $value = isset($p[1]) ? $p[1] : 0; - return $value; - } + return $value; + } /** * Returns the alexa traffic rank for the current url @@ -84,26 +84,26 @@ class Piwik_SEO_RankChecker * @return int */ public function getAlexaRank() - { + { $xml = @simplexml_load_string($this->getPage('http://data.alexa.com/data?cli=10&url=' . urlencode($this->url))); - return $xml ? $xml->SD->POPULARITY['TEXT'] : ''; - } + return $xml ? $xml->SD->POPULARITY['TEXT'] : ''; + } /** * Returns the number of Dmoz.org entries for the current url * * @return int */ - public function getDmoz() - { + public function getDmoz() + { $url = 'http://www.dmoz.org/search?q=' . urlencode($this->url); - $data = $this->getPage($url); + $data = $this->getPage($url); preg_match('#Open Directory Sites[^\(]+\([0-9]-[0-9]+ of ([0-9]+)\)#', $data, $p); if (!empty($p[1])) { - return (int) $p[1]; + return (int)$p[1]; } return 0; - } + } /** * Returns the number of pages google holds in it's index for the current url @@ -111,14 +111,14 @@ class Piwik_SEO_RankChecker * @return int */ public function getIndexedPagesGoogle() - { + { $url = 'http://www.google.com/search?hl=en&q=site%3A' . urlencode($this->url); - $data = $this->getPage($url); + $data = $this->getPage($url); if (preg_match('#about ([0-9\,]+) results#i', $data, $p)) { - return (int) str_replace(',', '', $p[1]); - } + return (int)str_replace(',', '', $p[1]); + } return 0; - } + } /** * Returns the number of pages bing holds in it's index for the current url @@ -126,14 +126,14 @@ class Piwik_SEO_RankChecker * @return int */ public function getIndexedPagesBing() - { + { $url = 'http://www.bing.com/search?mkt=en-US&q=site%3A' . urlencode($this->url); - $data = $this->getPage($url); + $data = $this->getPage($url); if (preg_match('#([0-9\,]+) results#i', $data, $p)) { - return (int) str_replace(',', '', $p[1]); - } + return (int)str_replace(',', '', $p[1]); + } return 0; - } + } /** * Returns the domain age for the current url @@ -148,19 +148,19 @@ class Piwik_SEO_RankChecker $ages = array(); - if($ageArchiveOrg > 0) { + if ($ageArchiveOrg > 0) { $ages[] = $ageArchiveOrg; } - if($ageWhoIs > 0) { + if ($ageWhoIs > 0) { $ages[] = $ageWhoIs; } - if($ageWhoisCom > 0) { + if ($ageWhoisCom > 0) { $ages[] = $ageWhoisCom; } - if(count($ages) > 1) { + if (count($ages) > 1) { $maxAge = min($ages); } else { $maxAge = array_shift($ages); @@ -171,27 +171,27 @@ class Piwik_SEO_RankChecker } return false; } - + /** * Returns the number backlinks that link to the current site. - * + * * @return int */ public function getExternalBacklinkCount() { - $majesticInfo = $this->getMajesticInfo(); - return $majesticInfo['backlink_count']; + $majesticInfo = $this->getMajesticInfo(); + return $majesticInfo['backlink_count']; } - + /** * Returns the number of referrer domains that link to the current site. - * + * * @return int */ public function getReferrerDomainCount() { - $majesticInfo = $this->getMajesticInfo(); - return $majesticInfo['referrer_domains_count']; + $majesticInfo = $this->getMajesticInfo(); + return $majesticInfo['referrer_domains_count']; } /** @@ -201,7 +201,7 @@ class Piwik_SEO_RankChecker */ protected function _getAgeArchiveOrg() { - $url = str_replace('www.', '', $this->url); + $url = str_replace('www.', '', $this->url); $data = @$this->getPage('http://wayback.archive.org/web/*/' . urlencode($url)); preg_match('#]*)' . preg_quote($url) . '/\">([^<]*)<\/a>#', $data, $p); if (!empty($p[2])) { @@ -221,8 +221,8 @@ class Piwik_SEO_RankChecker */ protected function _getAgeWhoIs() { - $url = preg_replace('/^www\./', '', $this->url); - $url = 'http://www.who.is/whois/' . urlencode($url); + $url = preg_replace('/^www\./', '', $this->url); + $url = 'http://www.who.is/whois/' . urlencode($url); $data = $this->getPage($url); preg_match('#(?:Creation Date|Created On|Registered on)\.*:\s*([ \ta-z0-9\/\-:\.]+)#si', $data, $p); if (!empty($p[1])) { @@ -242,8 +242,8 @@ class Piwik_SEO_RankChecker */ protected function _getAgeWhoisCom() { - $url = preg_replace('/^www\./', '', $this->url); - $url = 'http://www.whois.com/whois/' . urlencode($url); + $url = preg_replace('/^www\./', '', $this->url); + $url = 'http://www.whois.com/whois/' . urlencode($url); $data = $this->getPage($url); preg_match('#(?:Creation Date|Created On):\s*([ \ta-z0-9\/\-:\.]+)#si', $data, $p); if (!empty($p[1])) { @@ -256,114 +256,107 @@ class Piwik_SEO_RankChecker return 0; } - /** - * Convert numeric string to int - * - * @see getPageRank() - * - * @param string $Str - * @param int $Check - * @param int $Magic - * @return int - */ - private function StrToNum($Str, $Check, $Magic) - { - $Int32Unit = 4294967296; // 2^32 - - $length = strlen($Str); - for($i = 0; $i < $length; $i++) - { - $Check *= $Magic; - // If the float is beyond the boundaries of integer (usually +/- 2.15e+9 = 2^31), - // the result of converting to integer is undefined - // refer to http://www.php.net/manual/en/language.types.integer.php - if($Check >= $Int32Unit) - { - $Check = ($Check - $Int32Unit * (int) ($Check / $Int32Unit)); - //if the check less than -2^31 - $Check = ($Check < -2147483648) ? ($Check + $Int32Unit) : $Check; - } - $Check += ord($Str{$i}); - } - return $Check; - } - - /** - * Generate a hash for a url - * - * @see getPageRank() - * - * @param string $String - * @return int - */ - private function HashURL($String) - { - $Check1 = $this->StrToNum($String, 0x1505, 0x21); - $Check2 = $this->StrToNum($String, 0, 0x1003F); - - $Check1 >>= 2; - $Check1 = (($Check1 >> 4) & 0x3FFFFC0 ) | ($Check1 & 0x3F); - $Check1 = (($Check1 >> 4) & 0x3FFC00 ) | ($Check1 & 0x3FF); - $Check1 = (($Check1 >> 4) & 0x3C000 ) | ($Check1 & 0x3FFF); - - $T1 = (((($Check1 & 0x3C0) << 4) | ($Check1 & 0x3C)) <<2 ) | ($Check2 & 0xF0F ); - $T2 = (((($Check1 & 0xFFFFC000) << 4) | ($Check1 & 0x3C00)) << 0xA) | ($Check2 & 0xF0F0000 ); - - return ($T1 | $T2); - } - - /** - * Generate a checksum for the hash string - * - * @see getPageRank() - * - * @param int $Hashnum - * @return string - */ - private function CheckHash($Hashnum) - { - $CheckByte = 0; - $Flag = 0; - - $HashStr = sprintf('%u', $Hashnum) ; - $length = strlen($HashStr); - - for($i = $length - 1; $i >= 0; $i --) - { - $Re = $HashStr{$i}; - if(1 === ($Flag % 2)) { - $Re += $Re; - $Re = (int)($Re / 10) + ($Re % 10); - } - $CheckByte += $Re; - $Flag ++; - } - - $CheckByte %= 10; - if(0 !== $CheckByte) - { - $CheckByte = 10 - $CheckByte; - if(1 === ($Flag % 2) ) - { - if(1 === ($CheckByte % 2)) - { - $CheckByte += 9; - } - $CheckByte >>= 1; - } - } - - return '7'.$CheckByte.$HashStr; - } - - private function getMajesticInfo() - { - if ($this->majesticInfo === null) - { - $client = new Piwik_SEO_MajesticClient(); - $this->majesticInfo = $client->getBacklinkStats($this->url); - } - - return $this->majesticInfo; - } + /** + * Convert numeric string to int + * + * @see getPageRank() + * + * @param string $Str + * @param int $Check + * @param int $Magic + * @return int + */ + private function StrToNum($Str, $Check, $Magic) + { + $Int32Unit = 4294967296; // 2^32 + + $length = strlen($Str); + for ($i = 0; $i < $length; $i++) { + $Check *= $Magic; + // If the float is beyond the boundaries of integer (usually +/- 2.15e+9 = 2^31), + // the result of converting to integer is undefined + // refer to http://www.php.net/manual/en/language.types.integer.php + if ($Check >= $Int32Unit) { + $Check = ($Check - $Int32Unit * (int)($Check / $Int32Unit)); + //if the check less than -2^31 + $Check = ($Check < -2147483648) ? ($Check + $Int32Unit) : $Check; + } + $Check += ord($Str{$i}); + } + return $Check; + } + + /** + * Generate a hash for a url + * + * @see getPageRank() + * + * @param string $String + * @return int + */ + private function HashURL($String) + { + $Check1 = $this->StrToNum($String, 0x1505, 0x21); + $Check2 = $this->StrToNum($String, 0, 0x1003F); + + $Check1 >>= 2; + $Check1 = (($Check1 >> 4) & 0x3FFFFC0) | ($Check1 & 0x3F); + $Check1 = (($Check1 >> 4) & 0x3FFC00) | ($Check1 & 0x3FF); + $Check1 = (($Check1 >> 4) & 0x3C000) | ($Check1 & 0x3FFF); + + $T1 = (((($Check1 & 0x3C0) << 4) | ($Check1 & 0x3C)) << 2) | ($Check2 & 0xF0F); + $T2 = (((($Check1 & 0xFFFFC000) << 4) | ($Check1 & 0x3C00)) << 0xA) | ($Check2 & 0xF0F0000); + + return ($T1 | $T2); + } + + /** + * Generate a checksum for the hash string + * + * @see getPageRank() + * + * @param int $Hashnum + * @return string + */ + private function CheckHash($Hashnum) + { + $CheckByte = 0; + $Flag = 0; + + $HashStr = sprintf('%u', $Hashnum); + $length = strlen($HashStr); + + for ($i = $length - 1; $i >= 0; $i--) { + $Re = $HashStr{$i}; + if (1 === ($Flag % 2)) { + $Re += $Re; + $Re = (int)($Re / 10) + ($Re % 10); + } + $CheckByte += $Re; + $Flag++; + } + + $CheckByte %= 10; + if (0 !== $CheckByte) { + $CheckByte = 10 - $CheckByte; + if (1 === ($Flag % 2)) { + if (1 === ($CheckByte % 2)) { + $CheckByte += 9; + } + $CheckByte >>= 1; + } + } + + return '7' . $CheckByte . $HashStr; + } + + private function getMajesticInfo() + { + if ($this->majesticInfo === null) { + $client = new Piwik_SEO_MajesticClient(); + $this->majesticInfo = $client->getBacklinkStats($this->url); + } + + return $this->majesticInfo; + } } diff --git a/plugins/SEO/SEO.php b/plugins/SEO/SEO.php index a7e5474506..addc8ae69e 100644 --- a/plugins/SEO/SEO.php +++ b/plugins/SEO/SEO.php @@ -1,10 +1,10 @@ 'This Plugin extracts and displays SEO metrics: Alexa web ranking, Google Pagerank, number of Indexed pages and backlinks of the currently selected website.', - 'author' => 'Piwik', - 'author_homepage' => 'http://piwik.org/', - 'version' => Piwik_Version::VERSION, - ); - } - - function getListHooksRegistered() - { - $hooks = array( 'WidgetsList.add' => 'addWidgets' ); - return $hooks; - } - - function addWidgets() - { - Piwik_AddWidget('SEO', 'SEO_SeoRankings', 'SEO', 'getRank'); - } + public function getInformation() + { + return array( + 'description' => 'This Plugin extracts and displays SEO metrics: Alexa web ranking, Google Pagerank, number of Indexed pages and backlinks of the currently selected website.', + 'author' => 'Piwik', + 'author_homepage' => 'http://piwik.org/', + 'version' => Piwik_Version::VERSION, + ); + } + + function getListHooksRegistered() + { + $hooks = array('WidgetsList.add' => 'addWidgets'); + return $hooks; + } + + function addWidgets() + { + Piwik_AddWidget('SEO', 'SEO_SeoRankings', 'SEO', 'getRank'); + } } diff --git a/plugins/SEO/templates/index.tpl b/plugins/SEO/templates/index.tpl index fe1fe903d3..721ca46ee5 100644 --- a/plugins/SEO/templates/index.tpl +++ b/plugins/SEO/templates/index.tpl @@ -1,42 +1,46 @@
- - -
-
- {'Installation_SetupWebSiteURL'|translate|ucfirst} - + + + +
+ {'Installation_SetupWebSiteURL'|translate|ucfirst} + - + -
- - {ajaxLoadingDiv id=ajaxLoadingSEO} +
+ + {ajaxLoadingDiv id=ajaxLoadingSEO} + +
+ {if empty($ranks)} + {'General_Error'|translate} + {else} + {capture name=cleanUrl} + {$urlToRank|escape:'html'} + {/capture} + {'SEO_SEORankingsFor'|translate:$smarty.capture.cleanUrl} + + {foreach from=$ranks item=rank} + + + + + {/foreach} -
- {if empty($ranks)} - {'General_Error'|translate} - {else} -{capture name=cleanUrl} -{$urlToRank|escape:'html'} -{/capture} - {'SEO_SEORankingsFor'|translate:$smarty.capture.cleanUrl} -
{if !empty($rank.logo_link)}{/if}{$rank.label}{if !empty($rank.logo_link)}{/if} {$rank.label} + +
+ {if isset($rank.rank)}{$rank.rank}{else}-{/if} + {if $rank.id=='pagerank'} /10 + {elseif $rank.id=='google-index' || $rank.id=='bing-index'} {'SEO_Pages'|translate} + {/if} +
+
- {foreach from=$ranks item=rank} - - - - {/foreach} - -
{if !empty($rank.logo_link)}{/if}{$rank.label}{if !empty($rank.logo_link)}{/if} {$rank.label} - -
- {if isset($rank.rank)}{$rank.rank}{else}-{/if} - {if $rank.id=='pagerank'} /10 - {elseif $rank.id=='google-index' || $rank.id=='bing-index'} {'SEO_Pages'|translate} - {/if} -
-
- {/if} -
-
+ + {/if} +
+
diff --git a/plugins/SEO/templates/rank.js b/plugins/SEO/templates/rank.js index d7a3802fc9..1a9dc07d0c 100644 --- a/plugins/SEO/templates/rank.js +++ b/plugins/SEO/templates/rank.js @@ -12,7 +12,7 @@ $(document).ready(function () { ajaxRequest.addParams({ module: 'SEO', action: 'getRank', - url: encodeURIComponent($('#seoUrl').val()) + url: encodeURIComponent($('#seoUrl').val()) }, 'get'); ajaxRequest.setCallback( function (response) { diff --git a/plugins/SecurityInfo/Controller.php b/plugins/SecurityInfo/Controller.php index 94a4d6d02c..1c8ff217cd 100644 --- a/plugins/SecurityInfo/Controller.php +++ b/plugins/SecurityInfo/Controller.php @@ -1,7 +1,7 @@ loadAndRun(); + // load and run all tests + $psi->loadAndRun(); - // grab the results as a multidimensional array - $results = $psi->getResultsAsArray(); + // grab the results as a multidimensional array + $results = $psi->getResultsAsArray(); - // suppress results - unset($results['test_results']['Core']['memory_limit']); - unset($results['test_results']['Core']['post_max_size']); - unset($results['test_results']['Core']['upload_max_filesize']); + // suppress results + unset($results['test_results']['Core']['memory_limit']); + unset($results['test_results']['Core']['post_max_size']); + unset($results['test_results']['Core']['upload_max_filesize']); - $view = Piwik_View::factory('index'); - $this->setBasicVariablesView($view); - $view->menu = Piwik_GetAdminMenu(); - $view->results = $results; - echo $view->render(); - } + $view = Piwik_View::factory('index'); + $this->setBasicVariablesView($view); + $view->menu = Piwik_GetAdminMenu(); + $view->results = $results; + echo $view->render(); + } } diff --git a/plugins/SecurityInfo/PhpSecInfo/PhpSecInfo.php b/plugins/SecurityInfo/PhpSecInfo/PhpSecInfo.php index 0e9bbc7768..acdba6b959 100644 --- a/plugins/SecurityInfo/PhpSecInfo/PhpSecInfo.php +++ b/plugins/SecurityInfo/PhpSecInfo/PhpSecInfo.php @@ -95,497 +95,505 @@ define('PHPSECINFO_BASE_DIR', dirname(__FILE__)); class PhpSecInfo { - /** - * An array of tests to run - * - * @var array PhpSecInfo_Test - */ - var $tests_to_run = array(); - - - /** - * An array of results. Each result is an associative array: - * - * $result['result'] = PHPSECINFO_TEST_RESULT_NOTICE; - * $result['message'] = "a string describing the test results and what they mean"; - * - * - * @var array - */ - var $test_results = array(); - - - /** - * An array of tests that were not run - * - * - * $result['result'] = PHPSECINFO_TEST_RESULT_NOTRUN; - * $result['message'] = "a string explaining why the test was not run"; - * - * - * @var array - */ - var $tests_not_run = array(); - - - /** - * The language code used. Defaults to PHPSECINFO_LANG_DEFAULT, which - * is 'en' - * - * @var string - * @see PHPSECINFO_LANG_DEFAULT - */ - var $language = PHPSECINFO_LANG_DEFAULT; - - - /** - * An array of integers recording the number of test results in each category. Categories can include - * some or all of the PHPSECINFO_TEST_* constants. Constants are the keys, # of results are the values. - * - * @var array - */ - var $result_counts = array(); - - - /** - * The number of tests that have been run - * - * @var integer - */ - var $num_tests_run = 0; - - - /** - * The base directory for phpsecinfo. Set within the constructor. Paths are resolved from this. - * @var string - */ - var $_base_dir; - - - /** - * The directory PHPSecInfo will look for views. It defaults to the value - * in PHPSECINFO_VIEW_DIR_DEFAULT, but can be changed with the setViewDirectory() - * method. - * - * @var string - */ - var $_view_directory; - - - /** - * The output format, used to load the proper view - * - * @var string - **/ - var $_format; - - /** - * Constructor - * - * @return PhpSecInfo - */ - function PhpSecInfo($opts = null) { - - $this->_base_dir = dirname(__FILE__); - - if ($opts) { - if (isset($opts['view_directory'])) { - $this->setViewDirectory($opts['view_directory']); - } else { - $this->setViewDirectory(dirname(__FILE__).DIRECTORY_SEPARATOR . PHPSECINFO_VIEW_DIR_DEFAULT); - } - - if (isset($opts['format'])) { - $this->setFormat($opts['format']); - } else { - if (!strcasecmp(PHP_SAPI, 'cli')) { - $this->setFormat('Cli'); - } else { - $this->setFormat(PHPSECINFO_FORMAT_DEFAULT); - } - } - - } else { /* Use defaults */ - $this->setViewDirectory(dirname(__FILE__).DIRECTORY_SEPARATOR . PHPSECINFO_VIEW_DIR_DEFAULT); - if (!strcasecmp(PHP_SAPI, 'cli')) { - $this->setFormat('Cli'); - } else { - $this->setFormat(PHPSECINFO_FORMAT_DEFAULT); - } - } - } - - - /** - * recurses through the Test subdir and includes classes in each test group subdir, - * then builds an array of classnames for the tests that will be run - * - */ - function loadTests() { - - $test_root = dir(dirname(__FILE__).DIRECTORY_SEPARATOR.'Test'); - - //echo "
"; echo print_r($test_root, true); echo "
"; - - while (false !== ($entry = $test_root->read())) { - if ( is_dir($test_root->path.DIRECTORY_SEPARATOR.$entry) && !preg_match('~^(\.|_vti)(.*)$~', $entry) ) { - $test_dirs[] = $entry; - } - } - //echo "
"; echo print_r($test_dirs, true); echo "
"; - - // include_once all files in each test dir - foreach ($test_dirs as $test_dir) { - $this_dir = dir($test_root->path.DIRECTORY_SEPARATOR.$test_dir); - - while (false !== ($entry = $this_dir->read())) { - if (!is_dir($this_dir->path.DIRECTORY_SEPARATOR.$entry)) { - include_once $this_dir->path.DIRECTORY_SEPARATOR.$entry; - $classNames[] = "PhpSecInfo_Test_".$test_dir."_".basename($entry, '.php'); - } - } - - } - - // modded this to not throw a PHP5 STRICT notice, although I don't like passing by value here - $this->tests_to_run = $classNames; - } - - - /** - * This runs the tests in the tests_to_run array and - * places returned data in the following arrays/scalars: - * - $this->test_results - * - $this->result_counts - * - $this->num_tests_run - * - $this->tests_not_run; - * - */ - function runTests() { - // initialize a bunch of arrays - $this->test_results = array(); - $this->result_counts = array(); - $this->result_counts[PHPSECINFO_TEST_RESULT_NOTRUN] = 0; - $this->num_tests_run = 0; - - foreach ($this->tests_to_run as $testClass) { - - /** - * @var $test PhpSecInfo_Test - */ - $test = new $testClass(); - - if ($test->isTestable()) { - $test->test(); - $rs = array( 'result' => $test->getResult(), - 'message' => $test->getMessage(), - 'value_current' => $test->getCurrentTestValue(), - 'value_recommended' => $test->getRecommendedTestValue(), - 'moreinfo_url' => $test->getMoreInfoURL(), - ); - $this->test_results[$test->getTestGroup()][$test->getTestName()] = $rs; - - // initialize if not yet set - if (!isset ($this->result_counts[$rs['result']]) ) { - $this->result_counts[$rs['result']] = 0; - } - - $this->result_counts[$rs['result']]++; - $this->num_tests_run++; - } else { - $rs = array( 'result' => $test->getResult(), - 'message' => $test->getMessage(), - 'value_current' => NULL, - 'value_recommended' => NULL, - 'moreinfo_url' => $test->getMoreInfoURL(), - ); - $this->result_counts[PHPSECINFO_TEST_RESULT_NOTRUN]++; - $this->tests_not_run[$test->getTestGroup()."::".$test->getTestName()] = $rs; - } - } - } - - - /** - * This is the main output method. The look and feel mimics phpinfo() - * - */ - function renderOutput($page_title="Security Information About PHP") { - /** - * We need to use PhpSecInfo_Test::getBooleanIniValue() below - * @see PhpSecInfo_Test::getBooleanIniValue() - */ - if (!class_exists('PhpSecInfo_Test')) { - include( dirname(__FILE__).DIRECTORY_SEPARATOR.'Test'.DIRECTORY_SEPARATOR.'Test.php'); - } - $this->loadView($this->_format); - } - - - /** - * This is a helper method that makes it easy to output tables of test results - * for a given test group - * - * @param string $group_name - * @param array $group_results - */ - function _outputRenderTable($group_name, $group_results) { - - // exit out if $group_results was empty or not an array. This sorta seems a little hacky... - if (!is_array($group_results) || sizeof($group_results) < 1) { - return false; - } - - ksort($group_results); - - $this->loadView($this->_format.'/Result', array('group_name'=>$group_name, 'group_results'=>$group_results)); - - return true; - } - - - - /** - * This outputs a table containing a summary of the test results (counts and % in each result type) - * - * @see PHPSecInfo::_outputRenderTable() - * @see PHPSecInfo::_outputGetResultTypeFromCode() - */ - function _outputRenderStatsTable() { - - foreach($this->result_counts as $code=>$val) { - if ($code != PHPSECINFO_TEST_RESULT_NOTRUN) { - $percentage = round($val/$this->num_tests_run * 100,2); - $result_type = $this->_outputGetResultTypeFromCode($code); - $stats[$result_type] = array( 'count' => $val, - 'result' => $code, - 'message' => "$val out of {$this->num_tests_run} ($percentage%)"); - } - } - - $this->_outputRenderTable('Test Results Summary', $stats); - - } - - - - /** - * This outputs a table containing a summary or test that were not executed, and the reasons why they were skipped - * - * @see PHPSecInfo::_outputRenderTable() - */ - function _outputRenderNotRunTable() { - - $this->_outputRenderTable('Tests Not Run', $this->tests_not_run); - - } - - - - - /** - * This is a helper function that returns a CSS class corresponding to - * the result code the test returned. This allows us to color-code - * results - * - * @param integer $code - * @return string - */ - function _outputGetCssClassFromResult($code) { - - switch ($code) { - case PHPSECINFO_TEST_RESULT_OK: - return 'value-ok'; - break; - - case PHPSECINFO_TEST_RESULT_NOTICE: - return 'value-notice'; - break; - - case PHPSECINFO_TEST_RESULT_WARN: - return 'value-warn'; - break; - - case PHPSECINFO_TEST_RESULT_NOTRUN: - return 'value-notrun'; - break; - - case PHPSECINFO_TEST_RESULT_ERROR: - return 'value-error'; - break; - - default: - return 'value-notrun'; - break; - } - - } - - - - /** - * This is a helper function that returns a label string corresponding to - * the result code the test returned. This is mainly used for the Test - * Results Summary table. - * - * @see PHPSecInfo::_outputRenderStatsTable() - * @param integer $code - * @return string - */ - function _outputGetResultTypeFromCode($code) { - - switch ($code) { - case PHPSECINFO_TEST_RESULT_OK: - return 'Pass'; - break; - - case PHPSECINFO_TEST_RESULT_NOTICE: - return 'Notice'; - break; - - case PHPSECINFO_TEST_RESULT_WARN: - return 'Warning'; - break; - - case PHPSECINFO_TEST_RESULT_NOTRUN: - return 'Not Run'; - break; - - case PHPSECINFO_TEST_RESULT_ERROR: - return 'Error'; - break; - - default: - return 'Invalid Result Code'; - break; - } - - } - - - /** - * Loads and runs all the tests - * - * As loading, then running, is a pretty common process, this saves a extra method call - * - * @since 0.1.1 - * - */ - function loadAndRun() { - $this->loadTests(); - $this->runTests(); - } - - - /** - * returns an associative array of test data. Four keys are set: - * - test_results (array) - * - tests_not_run (array) - * - result_counts (array) - * - num_tests_run (integer) - * - * note that this must be called after tests are loaded and run - * - * @since 0.1.1 - * @return array - */ - function getResultsAsArray() { - $results = array(); - - $results['test_results'] = $this->test_results; - $results['tests_not_run'] = $this->tests_not_run; - $results['result_counts'] = $this->result_counts; - $results['num_tests_run'] = $this->num_tests_run; - - return $results; - } - - - - /** - * returns the standard output as a string instead of echoing it to the browser - * - * note that this must be called after tests are loaded and run - * - * @since 0.1.1 - * - * @return string - */ - function getOutput() { - ob_start(); - $this->renderOutput(); - $output = ob_get_clean(); - return $output; - } - - - /** - * A very, very simple "view" system - * - */ - function loadView($view_name, $data=null) { - if ($data != null) { - extract($data); - } - - $view_file = $this->getViewDirectory().$view_name.".php"; - - if ( file_exists($view_file) && is_readable($view_file) ) { - ob_start(); - include $view_file; - echo ob_get_clean(); - } else { - user_error("The view '{$view_file}' either does not exist or is not readable", E_USER_WARNING); - } - - - } - - - /** - * Returns the current view directory - * - * @return string - */ - function getViewDirectory() { - return $this->_view_directory; - } - - - /** - * Sets the directory that PHPSecInfo will look in for views - * - * @param string $newdir - */ - function setViewDirectory($newdir) { - $this->_view_directory = $newdir; - } - - - - - function getFormat() { - return $this->_format; - } - - - function setFormat($format) { - $this->_format = $format; - } + /** + * An array of tests to run + * + * @var array PhpSecInfo_Test + */ + var $tests_to_run = array(); + + + /** + * An array of results. Each result is an associative array: + * + * $result['result'] = PHPSECINFO_TEST_RESULT_NOTICE; + * $result['message'] = "a string describing the test results and what they mean"; + * + * + * @var array + */ + var $test_results = array(); + + + /** + * An array of tests that were not run + * + * + * $result['result'] = PHPSECINFO_TEST_RESULT_NOTRUN; + * $result['message'] = "a string explaining why the test was not run"; + * + * + * @var array + */ + var $tests_not_run = array(); + + + /** + * The language code used. Defaults to PHPSECINFO_LANG_DEFAULT, which + * is 'en' + * + * @var string + * @see PHPSECINFO_LANG_DEFAULT + */ + var $language = PHPSECINFO_LANG_DEFAULT; + + + /** + * An array of integers recording the number of test results in each category. Categories can include + * some or all of the PHPSECINFO_TEST_* constants. Constants are the keys, # of results are the values. + * + * @var array + */ + var $result_counts = array(); + + + /** + * The number of tests that have been run + * + * @var integer + */ + var $num_tests_run = 0; + + + /** + * The base directory for phpsecinfo. Set within the constructor. Paths are resolved from this. + * @var string + */ + var $_base_dir; + + + /** + * The directory PHPSecInfo will look for views. It defaults to the value + * in PHPSECINFO_VIEW_DIR_DEFAULT, but can be changed with the setViewDirectory() + * method. + * + * @var string + */ + var $_view_directory; + + + /** + * The output format, used to load the proper view + * + * @var string + **/ + var $_format; + + /** + * Constructor + * + * @return PhpSecInfo + */ + function PhpSecInfo($opts = null) + { + + $this->_base_dir = dirname(__FILE__); + + if ($opts) { + if (isset($opts['view_directory'])) { + $this->setViewDirectory($opts['view_directory']); + } else { + $this->setViewDirectory(dirname(__FILE__) . DIRECTORY_SEPARATOR . PHPSECINFO_VIEW_DIR_DEFAULT); + } + + if (isset($opts['format'])) { + $this->setFormat($opts['format']); + } else { + if (!strcasecmp(PHP_SAPI, 'cli')) { + $this->setFormat('Cli'); + } else { + $this->setFormat(PHPSECINFO_FORMAT_DEFAULT); + } + } + + } else { /* Use defaults */ + $this->setViewDirectory(dirname(__FILE__) . DIRECTORY_SEPARATOR . PHPSECINFO_VIEW_DIR_DEFAULT); + if (!strcasecmp(PHP_SAPI, 'cli')) { + $this->setFormat('Cli'); + } else { + $this->setFormat(PHPSECINFO_FORMAT_DEFAULT); + } + } + } + + + /** + * recurses through the Test subdir and includes classes in each test group subdir, + * then builds an array of classnames for the tests that will be run + * + */ + function loadTests() + { + + $test_root = dir(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'Test'); + + //echo "
"; echo print_r($test_root, true); echo "
"; + + while (false !== ($entry = $test_root->read())) { + if (is_dir($test_root->path . DIRECTORY_SEPARATOR . $entry) && !preg_match('~^(\.|_vti)(.*)$~', $entry)) { + $test_dirs[] = $entry; + } + } + //echo "
"; echo print_r($test_dirs, true); echo "
"; + + // include_once all files in each test dir + foreach ($test_dirs as $test_dir) { + $this_dir = dir($test_root->path . DIRECTORY_SEPARATOR . $test_dir); + + while (false !== ($entry = $this_dir->read())) { + if (!is_dir($this_dir->path . DIRECTORY_SEPARATOR . $entry)) { + include_once $this_dir->path . DIRECTORY_SEPARATOR . $entry; + $classNames[] = "PhpSecInfo_Test_" . $test_dir . "_" . basename($entry, '.php'); + } + } + + } + + // modded this to not throw a PHP5 STRICT notice, although I don't like passing by value here + $this->tests_to_run = $classNames; + } + + + /** + * This runs the tests in the tests_to_run array and + * places returned data in the following arrays/scalars: + * - $this->test_results + * - $this->result_counts + * - $this->num_tests_run + * - $this->tests_not_run; + * + */ + function runTests() + { + // initialize a bunch of arrays + $this->test_results = array(); + $this->result_counts = array(); + $this->result_counts[PHPSECINFO_TEST_RESULT_NOTRUN] = 0; + $this->num_tests_run = 0; + + foreach ($this->tests_to_run as $testClass) { + + /** + * @var $test PhpSecInfo_Test + */ + $test = new $testClass(); + + if ($test->isTestable()) { + $test->test(); + $rs = array('result' => $test->getResult(), + 'message' => $test->getMessage(), + 'value_current' => $test->getCurrentTestValue(), + 'value_recommended' => $test->getRecommendedTestValue(), + 'moreinfo_url' => $test->getMoreInfoURL(), + ); + $this->test_results[$test->getTestGroup()][$test->getTestName()] = $rs; + + // initialize if not yet set + if (!isset ($this->result_counts[$rs['result']])) { + $this->result_counts[$rs['result']] = 0; + } + + $this->result_counts[$rs['result']]++; + $this->num_tests_run++; + } else { + $rs = array('result' => $test->getResult(), + 'message' => $test->getMessage(), + 'value_current' => NULL, + 'value_recommended' => NULL, + 'moreinfo_url' => $test->getMoreInfoURL(), + ); + $this->result_counts[PHPSECINFO_TEST_RESULT_NOTRUN]++; + $this->tests_not_run[$test->getTestGroup() . "::" . $test->getTestName()] = $rs; + } + } + } + + + /** + * This is the main output method. The look and feel mimics phpinfo() + * + */ + function renderOutput($page_title = "Security Information About PHP") + { + /** + * We need to use PhpSecInfo_Test::getBooleanIniValue() below + * @see PhpSecInfo_Test::getBooleanIniValue() + */ + if (!class_exists('PhpSecInfo_Test')) { + include(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'Test' . DIRECTORY_SEPARATOR . 'Test.php'); + } + $this->loadView($this->_format); + } + + + /** + * This is a helper method that makes it easy to output tables of test results + * for a given test group + * + * @param string $group_name + * @param array $group_results + */ + function _outputRenderTable($group_name, $group_results) + { + + // exit out if $group_results was empty or not an array. This sorta seems a little hacky... + if (!is_array($group_results) || sizeof($group_results) < 1) { + return false; + } + + ksort($group_results); + + $this->loadView($this->_format . '/Result', array('group_name' => $group_name, 'group_results' => $group_results)); + + return true; + } + + + /** + * This outputs a table containing a summary of the test results (counts and % in each result type) + * + * @see PHPSecInfo::_outputRenderTable() + * @see PHPSecInfo::_outputGetResultTypeFromCode() + */ + function _outputRenderStatsTable() + { + + foreach ($this->result_counts as $code => $val) { + if ($code != PHPSECINFO_TEST_RESULT_NOTRUN) { + $percentage = round($val / $this->num_tests_run * 100, 2); + $result_type = $this->_outputGetResultTypeFromCode($code); + $stats[$result_type] = array('count' => $val, + 'result' => $code, + 'message' => "$val out of {$this->num_tests_run} ($percentage%)"); + } + } + + $this->_outputRenderTable('Test Results Summary', $stats); + + } + + + /** + * This outputs a table containing a summary or test that were not executed, and the reasons why they were skipped + * + * @see PHPSecInfo::_outputRenderTable() + */ + function _outputRenderNotRunTable() + { + + $this->_outputRenderTable('Tests Not Run', $this->tests_not_run); + + } + + + /** + * This is a helper function that returns a CSS class corresponding to + * the result code the test returned. This allows us to color-code + * results + * + * @param integer $code + * @return string + */ + function _outputGetCssClassFromResult($code) + { + + switch ($code) { + case PHPSECINFO_TEST_RESULT_OK: + return 'value-ok'; + break; + + case PHPSECINFO_TEST_RESULT_NOTICE: + return 'value-notice'; + break; + + case PHPSECINFO_TEST_RESULT_WARN: + return 'value-warn'; + break; + + case PHPSECINFO_TEST_RESULT_NOTRUN: + return 'value-notrun'; + break; + + case PHPSECINFO_TEST_RESULT_ERROR: + return 'value-error'; + break; + + default: + return 'value-notrun'; + break; + } + + } + + + /** + * This is a helper function that returns a label string corresponding to + * the result code the test returned. This is mainly used for the Test + * Results Summary table. + * + * @see PHPSecInfo::_outputRenderStatsTable() + * @param integer $code + * @return string + */ + function _outputGetResultTypeFromCode($code) + { + + switch ($code) { + case PHPSECINFO_TEST_RESULT_OK: + return 'Pass'; + break; + + case PHPSECINFO_TEST_RESULT_NOTICE: + return 'Notice'; + break; + + case PHPSECINFO_TEST_RESULT_WARN: + return 'Warning'; + break; + + case PHPSECINFO_TEST_RESULT_NOTRUN: + return 'Not Run'; + break; + + case PHPSECINFO_TEST_RESULT_ERROR: + return 'Error'; + break; + + default: + return 'Invalid Result Code'; + break; + } + + } + + + /** + * Loads and runs all the tests + * + * As loading, then running, is a pretty common process, this saves a extra method call + * + * @since 0.1.1 + * + */ + function loadAndRun() + { + $this->loadTests(); + $this->runTests(); + } + + + /** + * returns an associative array of test data. Four keys are set: + * - test_results (array) + * - tests_not_run (array) + * - result_counts (array) + * - num_tests_run (integer) + * + * note that this must be called after tests are loaded and run + * + * @since 0.1.1 + * @return array + */ + function getResultsAsArray() + { + $results = array(); + + $results['test_results'] = $this->test_results; + $results['tests_not_run'] = $this->tests_not_run; + $results['result_counts'] = $this->result_counts; + $results['num_tests_run'] = $this->num_tests_run; + + return $results; + } + + + /** + * returns the standard output as a string instead of echoing it to the browser + * + * note that this must be called after tests are loaded and run + * + * @since 0.1.1 + * + * @return string + */ + function getOutput() + { + ob_start(); + $this->renderOutput(); + $output = ob_get_clean(); + return $output; + } + + + /** + * A very, very simple "view" system + * + */ + function loadView($view_name, $data = null) + { + if ($data != null) { + extract($data); + } + + $view_file = $this->getViewDirectory() . $view_name . ".php"; + + if (file_exists($view_file) && is_readable($view_file)) { + ob_start(); + include $view_file; + echo ob_get_clean(); + } else { + user_error("The view '{$view_file}' either does not exist or is not readable", E_USER_WARNING); + } + + + } + + + /** + * Returns the current view directory + * + * @return string + */ + function getViewDirectory() + { + return $this->_view_directory; + } + + + /** + * Sets the directory that PHPSecInfo will look in for views + * + * @param string $newdir + */ + function setViewDirectory($newdir) + { + $this->_view_directory = $newdir; + } + + + function getFormat() + { + return $this->_format; + } + + + function setFormat($format) + { + $this->_format = $format; + } } - - /** * A globally-available function that runs the tests and creates the result page * */ -function phpsecinfo() { - // modded this to not throw a PHP5 STRICT notice, although I don't like passing by value here - $psi = new PhpSecInfo(); - $psi->loadAndRun(); - $psi->renderOutput(); +function phpsecinfo() +{ + // modded this to not throw a PHP5 STRICT notice, although I don't like passing by value here + $psi = new PhpSecInfo(); + $psi->loadAndRun(); + $psi->renderOutput(); } diff --git a/plugins/SecurityInfo/PhpSecInfo/Test/Application/php.php b/plugins/SecurityInfo/PhpSecInfo/Test/Application/php.php index 919d73a2cb..3199cb3167 100644 --- a/plugins/SecurityInfo/PhpSecInfo/Test/Application/php.php +++ b/plugins/SecurityInfo/PhpSecInfo/Test/Application/php.php @@ -9,7 +9,7 @@ /** * require the PhpSecInfo_Test_Application class */ -require_once(PHPSECINFO_BASE_DIR.'/Test/Test_Application.php'); +require_once(PHPSECINFO_BASE_DIR . '/Test/Test_Application.php'); /** * Test class for PHP application @@ -21,49 +21,52 @@ require_once(PHPSECINFO_BASE_DIR.'/Test/Test_Application.php'); */ class PhpSecInfo_Test_Application_Php extends PhpSecInfo_Test_Application { - var $test_name = "PHP"; + var $test_name = "PHP"; - var $recommended_value = null; + var $recommended_value = null; - function _retrieveCurrentValue() { - $this->current_value = PHP_VERSION; + function _retrieveCurrentValue() + { + $this->current_value = PHP_VERSION; - $url = 'http://php.net/releases/?serialize=1&version=5'; - $timeout = Piwik_UpdateCheck::SOCKET_TIMEOUT; - try { - $latestVersion = Piwik_Http::sendHttpRequest($url, $timeout); - $versionInfo = safe_unserialize($latestVersion); - $this->recommended_value = $versionInfo['version']; - } catch(Exception $e) { - $this->recommended_value = ''; - } - } + $url = 'http://php.net/releases/?serialize=1&version=5'; + $timeout = Piwik_UpdateCheck::SOCKET_TIMEOUT; + try { + $latestVersion = Piwik_Http::sendHttpRequest($url, $timeout); + $versionInfo = safe_unserialize($latestVersion); + $this->recommended_value = $versionInfo['version']; + } catch (Exception $e) { + $this->recommended_value = ''; + } + } - function _execTest() { - if (version_compare($this->current_value, '5.2.1') < 0) { - return PHPSECINFO_TEST_RESULT_WARN; - } + function _execTest() + { + if (version_compare($this->current_value, '5.2.1') < 0) { + return PHPSECINFO_TEST_RESULT_WARN; + } - if (empty($this->recommended_value)) { - return PHPSECINFO_TEST_RESULT_ERROR; - } + if (empty($this->recommended_value)) { + return PHPSECINFO_TEST_RESULT_ERROR; + } - if (version_compare($this->current_value, $this->recommended_value) >= 0 ) { - return PHPSECINFO_TEST_RESULT_OK; - } + if (version_compare($this->current_value, $this->recommended_value) >= 0) { + return PHPSECINFO_TEST_RESULT_OK; + } - return PHPSECINFO_TEST_RESULT_NOTICE; - } + return PHPSECINFO_TEST_RESULT_NOTICE; + } - function _setMessages() { - parent::_setMessages(); + function _setMessages() + { + parent::_setMessages(); - $this->setMessageForResult(PHPSECINFO_TEST_RESULT_OK, 'en', "You are running PHP ".$this->current_value. - ($this->current_value == $this->recommended_value - ? " (the latest version)." - : ". The latest version is ".$this->recommended_value.".")); - $this->setMessageForResult(PHPSECINFO_TEST_RESULT_NOTICE, 'en', "You are running PHP ".$this->current_value.". The latest version of PHP is ".$this->recommended_value."."); - $this->setMessageForResult(PHPSECINFO_TEST_RESULT_WARN, 'en', "You are running PHP ".$this->current_value." which is really old. We recommend running the latest (stable) version of PHP which includes numerous bug fixes and security fixes."); - $this->setMessageForResult(PHPSECINFO_TEST_RESULT_ERROR, 'en', "Unable to determine the latest version of PHP available."); - } + $this->setMessageForResult(PHPSECINFO_TEST_RESULT_OK, 'en', "You are running PHP " . $this->current_value . + ($this->current_value == $this->recommended_value + ? " (the latest version)." + : ". The latest version is " . $this->recommended_value . ".")); + $this->setMessageForResult(PHPSECINFO_TEST_RESULT_NOTICE, 'en', "You are running PHP " . $this->current_value . ". The latest version of PHP is " . $this->recommended_value . "."); + $this->setMessageForResult(PHPSECINFO_TEST_RESULT_WARN, 'en', "You are running PHP " . $this->current_value . " which is really old. We recommend running the latest (stable) version of PHP which includes numerous bug fixes and security fixes."); + $this->setMessageForResult(PHPSECINFO_TEST_RESULT_ERROR, 'en', "Unable to determine the latest version of PHP available."); + } } diff --git a/plugins/SecurityInfo/PhpSecInfo/Test/Application/piwik.php b/plugins/SecurityInfo/PhpSecInfo/Test/Application/piwik.php index 5a495a352c..16e7fbf42f 100644 --- a/plugins/SecurityInfo/PhpSecInfo/Test/Application/piwik.php +++ b/plugins/SecurityInfo/PhpSecInfo/Test/Application/piwik.php @@ -9,7 +9,7 @@ /** * require the PhpSecInfo_Test_Application class */ -require_once(PHPSECINFO_BASE_DIR.'/Test/Test_Application.php'); +require_once(PHPSECINFO_BASE_DIR . '/Test/Test_Application.php'); /** * Test class for Piwik application @@ -21,38 +21,41 @@ require_once(PHPSECINFO_BASE_DIR.'/Test/Test_Application.php'); */ class PhpSecInfo_Test_Application_Piwik extends PhpSecInfo_Test_Application { - var $test_name = "Piwik"; + var $test_name = "Piwik"; - var $recommended_value = null; + var $recommended_value = null; - function _retrieveCurrentValue() { - $this->current_value = Piwik_Version::VERSION; + function _retrieveCurrentValue() + { + $this->current_value = Piwik_Version::VERSION; - $this->recommended_value = Piwik_GetOption(Piwik_UpdateCheck::LATEST_VERSION); - } + $this->recommended_value = Piwik_GetOption(Piwik_UpdateCheck::LATEST_VERSION); + } - function _execTest() { - if (version_compare($this->current_value, '0.5') < 0) { - return PHPSECINFO_TEST_RESULT_WARN; - } + function _execTest() + { + if (version_compare($this->current_value, '0.5') < 0) { + return PHPSECINFO_TEST_RESULT_WARN; + } - if (empty($this->recommended_value)) { - return PHPSECINFO_TEST_RESULT_ERROR; - } + if (empty($this->recommended_value)) { + return PHPSECINFO_TEST_RESULT_ERROR; + } - if (version_compare($this->current_value, $this->recommended_value) >= 0) { - return PHPSECINFO_TEST_RESULT_OK; - } + if (version_compare($this->current_value, $this->recommended_value) >= 0) { + return PHPSECINFO_TEST_RESULT_OK; + } - return PHPSECINFO_TEST_RESULT_NOTICE; - } + return PHPSECINFO_TEST_RESULT_NOTICE; + } - function _setMessages() { - parent::_setMessages(); + function _setMessages() + { + parent::_setMessages(); - $this->setMessageForResult(PHPSECINFO_TEST_RESULT_OK, 'en', "You are running Piwik ".$this->current_value." (the latest version)."); - $this->setMessageForResult(PHPSECINFO_TEST_RESULT_NOTICE, 'en', "You are running Piwik ".$this->current_value.". The latest version of Piwik is ".$this->recommended_value."."); - $this->setMessageForResult(PHPSECINFO_TEST_RESULT_WARN, 'en', "You are running Piwik ".$this->current_value." which is no longer supported by the Piwik developers. We recommend running the latest (stable) version of Piwik which includes numerous enhancements, bug fixes, and security fixes."); - $this->setMessageForResult(PHPSECINFO_TEST_RESULT_ERROR, 'en', "Unable to determine the latest version of Piwik available."); - } + $this->setMessageForResult(PHPSECINFO_TEST_RESULT_OK, 'en', "You are running Piwik " . $this->current_value . " (the latest version)."); + $this->setMessageForResult(PHPSECINFO_TEST_RESULT_NOTICE, 'en', "You are running Piwik " . $this->current_value . ". The latest version of Piwik is " . $this->recommended_value . "."); + $this->setMessageForResult(PHPSECINFO_TEST_RESULT_WARN, 'en', "You are running Piwik " . $this->current_value . " which is no longer supported by the Piwik developers. We recommend running the latest (stable) version of Piwik which includes numerous enhancements, bug fixes, and security fixes."); + $this->setMessageForResult(PHPSECINFO_TEST_RESULT_ERROR, 'en', "Unable to determine the latest version of Piwik available."); + } } diff --git a/plugins/SecurityInfo/PhpSecInfo/Test/CGI/force_redirect.php b/plugins/SecurityInfo/PhpSecInfo/Test/CGI/force_redirect.php index d84d709cc4..cdb0ca2c4f 100644 --- a/plugins/SecurityInfo/PhpSecInfo/Test/CGI/force_redirect.php +++ b/plugins/SecurityInfo/PhpSecInfo/Test/CGI/force_redirect.php @@ -9,7 +9,7 @@ /** * require the PhpSecInfo_Test_Cgi class */ -require_once(PHPSECINFO_BASE_DIR.'/Test/Test_Cgi.php'); +require_once(PHPSECINFO_BASE_DIR . '/Test/Test_Cgi.php'); /** * Test class for cgi force_redirect @@ -20,82 +20,81 @@ require_once(PHPSECINFO_BASE_DIR.'/Test/Test_Cgi.php'); class PhpSecInfo_Test_Cgi_Force_Redirect extends PhpSecInfo_Test_Cgi { - /** - * This should be a unique, human-readable identifier for this test - * - * @var string - */ - var $test_name = "force_redirect"; - - /** - * The recommended setting value - * - * @var mixed - */ - var $recommended_value = TRUE; - - - - function _retrieveCurrentValue() { - $this->current_value = $this->getBooleanIniValue('cgi.force_redirect'); - } - - - - private function skipTest() { - if (strpos(PHP_SAPI, 'cgi') === false) { - return PHP_SAPI . ' SAPI for php'; - } - - // these web servers require cgi.force_redirect = 0 - $webServers = array('Microsoft-IIS', 'OmniHTTPd', 'Xitami'); - if (isset($_SERVER['SERVER_SOFTWARE'])) { - foreach ($webServers as $webServer) { - if (strpos($_SERVER['SERVER_SOFTWARE'], $webServer) === 0) { - return $_SERVER['SERVER_SOFTWARE']; - } - } - } - - return false; - } - - - - /** - * Checks to see if cgi.force_redirect is enabled - * - */ - function _execTest() { - if ($this->current_value == $this->recommended_value) { - return PHPSECINFO_TEST_RESULT_OK; - } - - if ($this->skipTest()) - { - return PHPSECINFO_TEST_RESULT_NOTICE; - } - - return PHPSECINFO_TEST_RESULT_WARN; - } - - - - /** - * Set the messages specific to this test - * - */ - function _setMessages() { - parent::_setMessages(); - - $this->setMessageForResult(PHPSECINFO_TEST_RESULT_OK, 'en', "force_redirect is enabled, which is the recommended setting"); - $ini = ini_get_all(); - if (isset($ini['cgi.force_redirect'])) { - $this->setMessageForResult(PHPSECINFO_TEST_RESULT_NOTICE, 'en', "force_redirect is disabled. In most cases, this is a security vulnerability, but it appears this is not needed because you are running " . $this->skipTest()); - $this->setMessageForResult(PHPSECINFO_TEST_RESULT_WARN, 'en', "force_redirect is disabled. In most cases, this is a serious security vulnerability. Unless you are absolutely sure this is not needed, enable this setting"); - } else { - $this->setMessageForResult(PHPSECINFO_TEST_RESULT_NOTICE, 'en', "force_redirect is disabled because php was not compiled with --enable-force-cgi-redirect. In most cases, this is a security vulnerability, but it appears this is not needed because you are running " . $this->skipTest()); - $this->setMessageForResult(PHPSECINFO_TEST_RESULT_WARN, 'en', "force_redirect is disabled because php was not compiled with --enable-force-cgi-redirect. In most cases, this is a serious security vulnerability. Unless you are absolutely sure this is not needed, recompile php with --enable-force-cgi-redirect and enable cgi.force_redirect"); - } - } + /** + * This should be a unique, human-readable identifier for this test + * + * @var string + */ + var $test_name = "force_redirect"; + + /** + * The recommended setting value + * + * @var mixed + */ + var $recommended_value = TRUE; + + + function _retrieveCurrentValue() + { + $this->current_value = $this->getBooleanIniValue('cgi.force_redirect'); + } + + + private function skipTest() + { + if (strpos(PHP_SAPI, 'cgi') === false) { + return PHP_SAPI . ' SAPI for php'; + } + + // these web servers require cgi.force_redirect = 0 + $webServers = array('Microsoft-IIS', 'OmniHTTPd', 'Xitami'); + if (isset($_SERVER['SERVER_SOFTWARE'])) { + foreach ($webServers as $webServer) { + if (strpos($_SERVER['SERVER_SOFTWARE'], $webServer) === 0) { + return $_SERVER['SERVER_SOFTWARE']; + } + } + } + + return false; + } + + + /** + * Checks to see if cgi.force_redirect is enabled + * + */ + function _execTest() + { + if ($this->current_value == $this->recommended_value) { + return PHPSECINFO_TEST_RESULT_OK; + } + + if ($this->skipTest()) { + return PHPSECINFO_TEST_RESULT_NOTICE; + } + + return PHPSECINFO_TEST_RESULT_WARN; + } + + + /** + * Set the messages specific to this test + * + */ + function _setMessages() + { + parent::_setMessages(); + + $this->setMessageForResult(PHPSECINFO_TEST_RESULT_OK, 'en', "force_redirect is enabled, which is the recommended setting"); + $ini = ini_get_all(); + if (isset($ini['cgi.force_redirect'])) { + $this->setMessageForResult(PHPSECINFO_TEST_RESULT_NOTICE, 'en', "force_redirect is disabled. In most cases, this is a security vulnerability, but it appears this is not needed because you are running " . $this->skipTest()); + $this->setMessageForResult(PHPSECINFO_TEST_RESULT_WARN, 'en', "force_redirect is disabled. In most cases, this is a serious security vulnerability. Unless you are absolutely sure this is not needed, enable this setting"); + } else { + $this->setMessageForResult(PHPSECINFO_TEST_RESULT_NOTICE, 'en', "force_redirect is disabled because php was not compiled with --enable-force-cgi-redirect. In most cases, this is a security vulnerability, but it appears this is not needed because you are running " . $this->skipTest()); + $this->setMessageForResult(PHPSECINFO_TEST_RESULT_WARN, 'en', "force_redirect is disabled because php was not compiled with --enable-force-cgi-redirect. In most cases, this is a serious security vulnerability. Unless you are absolutely sure this is not needed, recompile php with --enable-force-cgi-redirect and enable cgi.force_redirect"); + } + } } diff --git a/plugins/SecurityInfo/PhpSecInfo/Test/Core/allow_url_fopen.php b/plugins/SecurityInfo/PhpSecInfo/Test/Core/allow_url_fopen.php index 799bb88110..b6df400148 100644 --- a/plugins/SecurityInfo/PhpSecInfo/Test/Core/allow_url_fopen.php +++ b/plugins/SecurityInfo/PhpSecInfo/Test/Core/allow_url_fopen.php @@ -10,7 +10,7 @@ /** * require the PhpSecInfo_Test_Core class */ -require_once(PHPSECINFO_BASE_DIR.'/Test/Test_Core.php'); +require_once(PHPSECINFO_BASE_DIR . '/Test/Test_Core.php'); /** * Test Class for allow_url_fopen @@ -21,59 +21,61 @@ require_once(PHPSECINFO_BASE_DIR.'/Test/Test_Core.php'); */ class PhpSecInfo_Test_Core_Allow_Url_Fopen extends PhpSecInfo_Test_Core { - /** - * This should be a unique, human-readable identifier for this test - * - * @var string - */ - var $test_name = "allow_url_fopen"; - - /** - * The recommended setting value - * - * @var mixed - */ - var $recommended_value = FALSE; + /** + * This should be a unique, human-readable identifier for this test + * + * @var string + */ + var $test_name = "allow_url_fopen"; + /** + * The recommended setting value + * + * @var mixed + */ + var $recommended_value = FALSE; - - function _retrieveCurrentValue() { - $this->current_value = $this->getBooleanIniValue('allow_url_fopen'); - } - - - /** - * Checks to see if allow_url_fopen is enabled - * - */ - function _execTest() { - if ( version_compare(PHP_VERSION, '5.2', '<') ) { /* this is much more severe if we're running < 5.2 */ - if ($this->current_value == $this->recommended_value) { - return PHPSECINFO_TEST_RESULT_OK; - } - return PHPSECINFO_TEST_RESULT_WARN; - } else { /* In 5.2, we'll consider allow_url_fopen "safe" */ - $this->recommended_value = TRUE; - return PHPSECINFO_TEST_RESULT_OK; - } - } + function _retrieveCurrentValue() + { + $this->current_value = $this->getBooleanIniValue('allow_url_fopen'); + } - /** - * Set the messages specific to this test - * - */ - function _setMessages() { - parent::_setMessages(); - if ( version_compare(PHP_VERSION, '5.2', '<') ) { /* this is much more severe if we're running < 5.2 */ - $this->setMessageForResult(PHPSECINFO_TEST_RESULT_OK, 'en', 'allow_url_fopen is disabled, which is the recommended setting'); - $this->setMessageForResult(PHPSECINFO_TEST_RESULT_WARN, 'en', 'allow_url_fopen is enabled. This could be a serious security risk. You should disable allow_url_fopen and consider using the PHP cURL functions instead.'); - - } else { - $this->setMessageForResult(PHPSECINFO_TEST_RESULT_OK, 'en', 'You are running PHP 5.2 or greater, which makes allow_url_fopen significantly safer. Make sure allow_url_include is disabled, though'); - } - } + /** + * Checks to see if allow_url_fopen is enabled + * + */ + function _execTest() + { + if (version_compare(PHP_VERSION, '5.2', '<')) { /* this is much more severe if we're running < 5.2 */ + if ($this->current_value == $this->recommended_value) { + return PHPSECINFO_TEST_RESULT_OK; + } + + return PHPSECINFO_TEST_RESULT_WARN; + } else { /* In 5.2, we'll consider allow_url_fopen "safe" */ + $this->recommended_value = TRUE; + return PHPSECINFO_TEST_RESULT_OK; + } + } + + + /** + * Set the messages specific to this test + * + */ + function _setMessages() + { + parent::_setMessages(); + if (version_compare(PHP_VERSION, '5.2', '<')) { /* this is much more severe if we're running < 5.2 */ + $this->setMessageForResult(PHPSECINFO_TEST_RESULT_OK, 'en', 'allow_url_fopen is disabled, which is the recommended setting'); + $this->setMessageForResult(PHPSECINFO_TEST_RESULT_WARN, 'en', 'allow_url_fopen is enabled. This could be a serious security risk. You should disable allow_url_fopen and consider using the PHP cURL functions instead.'); + + } else { + $this->setMessageForResult(PHPSECINFO_TEST_RESULT_OK, 'en', 'You are running PHP 5.2 or greater, which makes allow_url_fopen significantly safer. Make sure allow_url_include is disabled, though'); + } + } } \ No newline at end of file diff --git a/plugins/SecurityInfo/PhpSecInfo/Test/Core/allow_url_include.php b/plugins/SecurityInfo/PhpSecInfo/Test/Core/allow_url_include.php index c9bea4d74d..d80b04753c 100644 --- a/plugins/SecurityInfo/PhpSecInfo/Test/Core/allow_url_include.php +++ b/plugins/SecurityInfo/PhpSecInfo/Test/Core/allow_url_include.php @@ -10,7 +10,7 @@ /** * require the PhpSecInfo_Test_Core class */ -require_once(PHPSECINFO_BASE_DIR.'/Test/Test_Core.php'); +require_once(PHPSECINFO_BASE_DIR . '/Test/Test_Core.php'); /** * Test Class for allow_url_include @@ -21,57 +21,60 @@ require_once(PHPSECINFO_BASE_DIR.'/Test/Test_Core.php'); */ class PhpSecInfo_Test_Core_Allow_Url_Include extends PhpSecInfo_Test_Core { - /** - * This should be a unique, human-readable identifier for this test - * - * @var string - */ - var $test_name = "allow_url_include"; - - - var $recommended_value = FALSE; - - - function _retrieveCurrentValue() { - $this->current_value = $this->getBooleanIniValue('allow_url_include'); - } - - - /** - * Checks to see if allow_url_fopen is enabled - * - */ - function _execTest() { - if ($this->current_value == $this->recommended_value) { - return PHPSECINFO_TEST_RESULT_OK; - } - - return PHPSECINFO_TEST_RESULT_WARN; - } - - - /** - * allow_url_include is only available since PHP 5.2 - * - * @return boolean - */ - function isTestable() { - return version_compare(PHP_VERSION, '5.2', '>='); - } - - - - /** - * Set the messages specific to this test - * - */ - function _setMessages() { - parent::_setMessages(); - - $this->setMessageForResult(PHPSECINFO_TEST_RESULT_NOTRUN, 'en', 'You are running a version of PHP older than 5.2, and allow_url_include is not available'); - $this->setMessageForResult(PHPSECINFO_TEST_RESULT_OK, 'en', 'allow_url_include is disabled, which is the recommended setting'); - $this->setMessageForResult(PHPSECINFO_TEST_RESULT_WARN, 'en', 'allow_url_include is enabled. This could be a serious security risk. You should disable allow_url_include and consider using the PHP cURL functions instead.'); - } + /** + * This should be a unique, human-readable identifier for this test + * + * @var string + */ + var $test_name = "allow_url_include"; + + + var $recommended_value = FALSE; + + + function _retrieveCurrentValue() + { + $this->current_value = $this->getBooleanIniValue('allow_url_include'); + } + + + /** + * Checks to see if allow_url_fopen is enabled + * + */ + function _execTest() + { + if ($this->current_value == $this->recommended_value) { + return PHPSECINFO_TEST_RESULT_OK; + } + + return PHPSECINFO_TEST_RESULT_WARN; + } + + + /** + * allow_url_include is only available since PHP 5.2 + * + * @return boolean + */ + function isTestable() + { + return version_compare(PHP_VERSION, '5.2', '>='); + } + + + /** + * Set the messages specific to this test + * + */ + function _setMessages() + { + parent::_setMessages(); + + $this->setMessageForResult(PHPSECINFO_TEST_RESULT_NOTRUN, 'en', 'You are running a version of PHP older than 5.2, and allow_url_include is not available'); + $this->setMessageForResult(PHPSECINFO_TEST_RESULT_OK, 'en', 'allow_url_include is disabled, which is the recommended setting'); + $this->setMessageForResult(PHPSECINFO_TEST_RESULT_WARN, 'en', 'allow_url_include is enabled. This could be a serious security risk. You should disable allow_url_include and consider using the PHP cURL functions instead.'); + } } \ No newline at end of file diff --git a/plugins/SecurityInfo/PhpSecInfo/Test/Core/display_errors.php b/plugins/SecurityInfo/PhpSecInfo/Test/Core/display_errors.php index e0e93d6748..3908b289a3 100644 --- a/plugins/SecurityInfo/PhpSecInfo/Test/Core/display_errors.php +++ b/plugins/SecurityInfo/PhpSecInfo/Test/Core/display_errors.php @@ -1,7 +1,7 @@ */ @@ -10,53 +10,56 @@ /** * require the PhpSecInfo_Test_Core class */ -require_once(PHPSECINFO_BASE_DIR.'/Test/Test_Core.php'); +require_once(PHPSECINFO_BASE_DIR . '/Test/Test_Core.php'); /** * Test class for display_errors - * + * * @package PhpSecInfo */ class PhpSecInfo_Test_Core_Display_Errors extends PhpSecInfo_Test_Core { - /** - * This should be a unique, human-readable identifier for this test - * - * @var string - */ - var $test_name = "display_errors"; - - var $recommended_value = FALSE; - - function _retrieveCurrentValue() { - $this->current_value = $this->getBooleanIniValue('display_errors'); - } - - - /** - * Checks to see if display_errors is enabled - * - */ - function _execTest() { - if ($this->current_value == $this->recommended_value) { - return PHPSECINFO_TEST_RESULT_OK; - } - - return PHPSECINFO_TEST_RESULT_NOTICE; - } - - - /** - * Set the messages specific to this test - * - */ - function _setMessages() { - parent::_setMessages(); - - $this->setMessageForResult(PHPSECINFO_TEST_RESULT_OK, 'en', 'display_errors is disabled, which is the recommended setting'); - $this->setMessageForResult(PHPSECINFO_TEST_RESULT_NOTICE, 'en', 'display_errors is enabled. This is not recommended on "production" servers, as it could reveal sensitive information. You should consider disabling this feature'); - } - + /** + * This should be a unique, human-readable identifier for this test + * + * @var string + */ + var $test_name = "display_errors"; + + var $recommended_value = FALSE; + + function _retrieveCurrentValue() + { + $this->current_value = $this->getBooleanIniValue('display_errors'); + } + + + /** + * Checks to see if display_errors is enabled + * + */ + function _execTest() + { + if ($this->current_value == $this->recommended_value) { + return PHPSECINFO_TEST_RESULT_OK; + } + + return PHPSECINFO_TEST_RESULT_NOTICE; + } + + + /** + * Set the messages specific to this test + * + */ + function _setMessages() + { + parent::_setMessages(); + + $this->setMessageForResult(PHPSECINFO_TEST_RESULT_OK, 'en', 'display_errors is disabled, which is the recommended setting'); + $this->setMessageForResult(PHPSECINFO_TEST_RESULT_NOTICE, 'en', 'display_errors is enabled. This is not recommended on "production" servers, as it could reveal sensitive information. You should consider disabling this feature'); + } + } \ No newline at end of file diff --git a/plugins/SecurityInfo/PhpSecInfo/Test/Core/expose_php.php b/plugins/SecurityInfo/PhpSecInfo/Test/Core/expose_php.php index 3f26648193..da1666eeff 100644 --- a/plugins/SecurityInfo/PhpSecInfo/Test/Core/expose_php.php +++ b/plugins/SecurityInfo/PhpSecInfo/Test/Core/expose_php.php @@ -1,7 +1,7 @@ */ @@ -10,55 +10,58 @@ /** * require the PhpSecInfo_Test_Core class */ -require_once(PHPSECINFO_BASE_DIR.'/Test/Test_Core.php'); +require_once(PHPSECINFO_BASE_DIR . '/Test/Test_Core.php'); /** * Test class for expose_php - * + * * @package PhpSecInfo */ class PhpSecInfo_Test_Core_Expose_Php extends PhpSecInfo_Test_Core { - /** - * This should be a unique, human-readable identifier for this test - * - * @var string - */ - var $test_name = "expose_php"; - - var $recommended_value = FALSE; - - function _retrieveCurrentValue() { - $this->current_value = $this->returnBytes(ini_get('expose_php')); - } - - /** - * Checks to see if expose_php is enabled - * - */ - function _execTest() { - - if ($this->current_value == $this->recommended_value) { - return PHPSECINFO_TEST_RESULT_OK; - } - - return PHPSECINFO_TEST_RESULT_NOTICE; - } - - - /** - * Set the messages specific to this test - * - */ - function _setMessages() { - parent::_setMessages(); - - $this->setMessageForResult(PHPSECINFO_TEST_RESULT_OK, 'en', 'expose_php is disabled, which is the recommended setting'); - $this->setMessageForResult(PHPSECINFO_TEST_RESULT_NOTICE, 'en', 'expose_php is enabled. This adds + /** + * This should be a unique, human-readable identifier for this test + * + * @var string + */ + var $test_name = "expose_php"; + + var $recommended_value = FALSE; + + function _retrieveCurrentValue() + { + $this->current_value = $this->returnBytes(ini_get('expose_php')); + } + + /** + * Checks to see if expose_php is enabled + * + */ + function _execTest() + { + + if ($this->current_value == $this->recommended_value) { + return PHPSECINFO_TEST_RESULT_OK; + } + + return PHPSECINFO_TEST_RESULT_NOTICE; + } + + + /** + * Set the messages specific to this test + * + */ + function _setMessages() + { + parent::_setMessages(); + + $this->setMessageForResult(PHPSECINFO_TEST_RESULT_OK, 'en', 'expose_php is disabled, which is the recommended setting'); + $this->setMessageForResult(PHPSECINFO_TEST_RESULT_NOTICE, 'en', 'expose_php is enabled. This adds the PHP "signature" to the web server header, including the PHP version number. This could attract attackers looking for vulnerable versions of PHP'); - } - + } + } \ No newline at end of file diff --git a/plugins/SecurityInfo/PhpSecInfo/Test/Core/file_uploads.php b/plugins/SecurityInfo/PhpSecInfo/Test/Core/file_uploads.php index 6a3c822aae..fc4f17e07c 100644 --- a/plugins/SecurityInfo/PhpSecInfo/Test/Core/file_uploads.php +++ b/plugins/SecurityInfo/PhpSecInfo/Test/Core/file_uploads.php @@ -1,7 +1,7 @@ */ @@ -10,54 +10,56 @@ /** * require the PhpSecInfo_Test_Core class */ -require_once(PHPSECINFO_BASE_DIR.'/Test/Test_Core.php'); +require_once(PHPSECINFO_BASE_DIR . '/Test/Test_Core.php'); /** * Test Class for file_uploads - * + * * @package PhpSecInfo */ class PhpSecInfo_Test_Core_File_Uploads extends PhpSecInfo_Test_Core { - /** - * This should be a unique, human-readable identifier for this test - * - * @var string - */ - var $test_name = "file_uploads"; - - var $recommended_value = FALSE; - - function _retrieveCurrentValue() { - $this->current_value = $this->returnBytes(ini_get('file_uploads')); - } - - /** - * Checks to see if expose_php is enabled - * - */ - function _execTest() { - - if ($this->current_value == $this->recommended_value) { - return PHPSECINFO_TEST_RESULT_OK; - } - - return PHPSECINFO_TEST_RESULT_NOTICE; - } - - - - /** - * Set the messages specific to this test - * - */ - function _setMessages() { - parent::_setMessages(); - - $this->setMessageForResult(PHPSECINFO_TEST_RESULT_OK, 'en', 'file_uploads are disabled. Unless you\'re sure you need them, this is the recommended setting'); - $this->setMessageForResult(PHPSECINFO_TEST_RESULT_NOTICE, 'en', 'file_uploads are enabled. If you do not require file upload capability, consider disabling them.'); - } - + /** + * This should be a unique, human-readable identifier for this test + * + * @var string + */ + var $test_name = "file_uploads"; + + var $recommended_value = FALSE; + + function _retrieveCurrentValue() + { + $this->current_value = $this->returnBytes(ini_get('file_uploads')); + } + + /** + * Checks to see if expose_php is enabled + * + */ + function _execTest() + { + + if ($this->current_value == $this->recommended_value) { + return PHPSECINFO_TEST_RESULT_OK; + } + + return PHPSECINFO_TEST_RESULT_NOTICE; + } + + + /** + * Set the messages specific to this test + * + */ + function _setMessages() + { + parent::_setMessages(); + + $this->setMessageForResult(PHPSECINFO_TEST_RESULT_OK, 'en', 'file_uploads are disabled. Unless you\'re sure you need them, this is the recommended setting'); + $this->setMessageForResult(PHPSECINFO_TEST_RESULT_NOTICE, 'en', 'file_uploads are enabled. If you do not require file upload capability, consider disabling them.'); + } + } \ No newline at end of file diff --git a/plugins/SecurityInfo/PhpSecInfo/Test/Core/gid.php b/plugins/SecurityInfo/PhpSecInfo/Test/Core/gid.php index 88a6f3c6c1..89265da2c1 100644 --- a/plugins/SecurityInfo/PhpSecInfo/Test/Core/gid.php +++ b/plugins/SecurityInfo/PhpSecInfo/Test/Core/gid.php @@ -10,7 +10,7 @@ /** * require the PhpSecInfo_Test_Core class */ -require_once(PHPSECINFO_BASE_DIR.'/Test/Test_Core.php'); +require_once(PHPSECINFO_BASE_DIR . '/Test/Test_Core.php'); /** @@ -28,32 +28,34 @@ define ('PHPSECINFO_MIN_SAFE_GID', 100); class PhpSecInfo_Test_Core_Gid extends PhpSecInfo_Test_Core { - /** - * This should be a unique, human-readable identifier for this test - * - * @var string - */ - var $test_name = "group_id"; - - var $recommended_value = PHPSECINFO_MIN_SAFE_GID; - - - /** - * This test only works under Unix OSes - * - * @return boolean - */ - function isTestable() { - if ($this->osIsWindows()) { - return false; - } elseif ($this->getUnixId() === false) { - $this->setMessageForResult(PHPSECINFO_TEST_RESULT_NOTRUN, 'en', 'Functions required to retrieve group ID not available'); - return false; - } - return true; - } - - function _retrieveCurrentValue() { + /** + * This should be a unique, human-readable identifier for this test + * + * @var string + */ + var $test_name = "group_id"; + + var $recommended_value = PHPSECINFO_MIN_SAFE_GID; + + + /** + * This test only works under Unix OSes + * + * @return boolean + */ + function isTestable() + { + if ($this->osIsWindows()) { + return false; + } elseif ($this->getUnixId() === false) { + $this->setMessageForResult(PHPSECINFO_TEST_RESULT_NOTRUN, 'en', 'Functions required to retrieve group ID not available'); + return false; + } + return true; + } + + function _retrieveCurrentValue() + { $id = $this->getUnixId(); if (is_array($id)) { $lowest_gid = key($id['groups']); @@ -61,33 +63,35 @@ class PhpSecInfo_Test_Core_Gid extends PhpSecInfo_Test_Core } else { $this->current_value = false; } - } - - /** - * Checks the GID of the PHP process to make sure it is above PHPSECINFO_MIN_SAFE_GID - * - * @see PHPSECINFO_MIN_SAFE_GID - */ - function _execTest() { - if ($this->current_value >= $this->recommended_value) { - return PHPSECINFO_TEST_RESULT_OK; - } - - return PHPSECINFO_TEST_RESULT_WARN; - } - - - /** - * Set the messages specific to this test - * - */ - function _setMessages() { - parent::_setMessages(); - - $this->setMessageForResult(PHPSECINFO_TEST_RESULT_OK, 'en', 'PHP is executing as what is probably a non-privileged group'); - $this->setMessageForResult(PHPSECINFO_TEST_RESULT_WARN, 'en', 'PHP may be executing as a "privileged" group, which could be a serious security vulnerability.'); - $this->setMessageForResult(PHPSECINFO_TEST_RESULT_NOTRUN, 'en', 'This test will not run on Windows OSes'); - } + } + + /** + * Checks the GID of the PHP process to make sure it is above PHPSECINFO_MIN_SAFE_GID + * + * @see PHPSECINFO_MIN_SAFE_GID + */ + function _execTest() + { + if ($this->current_value >= $this->recommended_value) { + return PHPSECINFO_TEST_RESULT_OK; + } + + return PHPSECINFO_TEST_RESULT_WARN; + } + + + /** + * Set the messages specific to this test + * + */ + function _setMessages() + { + parent::_setMessages(); + + $this->setMessageForResult(PHPSECINFO_TEST_RESULT_OK, 'en', 'PHP is executing as what is probably a non-privileged group'); + $this->setMessageForResult(PHPSECINFO_TEST_RESULT_WARN, 'en', 'PHP may be executing as a "privileged" group, which could be a serious security vulnerability.'); + $this->setMessageForResult(PHPSECINFO_TEST_RESULT_NOTRUN, 'en', 'This test will not run on Windows OSes'); + } } \ No newline at end of file diff --git a/plugins/SecurityInfo/PhpSecInfo/Test/Core/magic_quotes_gpc.php b/plugins/SecurityInfo/PhpSecInfo/Test/Core/magic_quotes_gpc.php index 89a0ff6f0d..2a69954f0e 100644 --- a/plugins/SecurityInfo/PhpSecInfo/Test/Core/magic_quotes_gpc.php +++ b/plugins/SecurityInfo/PhpSecInfo/Test/Core/magic_quotes_gpc.php @@ -7,11 +7,10 @@ */ - /** * require the PhpSecInfo_Test_Core class */ -require_once(PHPSECINFO_BASE_DIR.'/Test/Test_Core.php'); +require_once(PHPSECINFO_BASE_DIR . '/Test/Test_Core.php'); /** * Test Class for magic_quotes_gpc @@ -20,61 +19,65 @@ require_once(PHPSECINFO_BASE_DIR.'/Test/Test_Core.php'); */ class PhpSecInfo_Test_Core_Magic_Quotes_GPC extends PhpSecInfo_Test_Core { - /** - * This should be a unique, human-readable identifier for this test - * - * @var string - */ - var $test_name = "magic_quotes_gpc"; + /** + * This should be a unique, human-readable identifier for this test + * + * @var string + */ + var $test_name = "magic_quotes_gpc"; - var $recommended_value = FALSE; + var $recommended_value = FALSE; - function _retrieveCurrentValue() { - $this->current_value = $this->getBooleanIniValue('magic_quotes_gpc'); - } + function _retrieveCurrentValue() + { + $this->current_value = $this->getBooleanIniValue('magic_quotes_gpc'); + } - /** - * magic_quotes_gpc has been removed since PHP 6.0 - * - * @return boolean - */ - function isTestable() { - return version_compare(PHP_VERSION, '6', '<') ; - } + /** + * magic_quotes_gpc has been removed since PHP 6.0 + * + * @return boolean + */ + function isTestable() + { + return version_compare(PHP_VERSION, '6', '<'); + } - /** - * Checks to see if allow_url_fopen is enabled - * - */ - function _execTest() { - if ($this->current_value == $this->recommended_value) { - return PHPSECINFO_TEST_RESULT_OK; - } + /** + * Checks to see if allow_url_fopen is enabled + * + */ + function _execTest() + { + if ($this->current_value == $this->recommended_value) { + return PHPSECINFO_TEST_RESULT_OK; + } - return PHPSECINFO_TEST_RESULT_NOTICE; - } + return PHPSECINFO_TEST_RESULT_NOTICE; + } - /** - * Set the messages specific to this test - * - */ - function _setMessages() { - parent::_setMessages(); + /** + * Set the messages specific to this test + * + */ + function _setMessages() + { + parent::_setMessages(); - $this->setMessageForResult(PHPSECINFO_TEST_RESULT_NOTRUN, 'en', 'You are running PHP 6 or later and magic_quotes_gpc has been removed'); - $this->setMessageForResult(PHPSECINFO_TEST_RESULT_OK, 'en', 'magic_quotes_gpc is disabled, which is the recommended setting'); - $this->setMessageForResult(PHPSECINFO_TEST_RESULT_NOTICE, 'en', 'magic_quotes_gpc is enabled. This + $this->setMessageForResult(PHPSECINFO_TEST_RESULT_NOTRUN, 'en', 'You are running PHP 6 or later and magic_quotes_gpc has been removed'); + $this->setMessageForResult(PHPSECINFO_TEST_RESULT_OK, 'en', 'magic_quotes_gpc is disabled, which is the recommended setting'); + $this->setMessageForResult(PHPSECINFO_TEST_RESULT_NOTICE, 'en', 'magic_quotes_gpc is enabled. This feature is inconsistent in blocking attacks, and can in some cases cause data loss with uploaded files. You should not rely on magic_quotes_gpc to block attacks. It is recommended that magic_quotes_gpc be disabled, and input filtering be handled by your PHP scripts'); - } + } } \ No newline at end of file diff --git a/plugins/SecurityInfo/PhpSecInfo/Test/Core/memory_limit.php b/plugins/SecurityInfo/PhpSecInfo/Test/Core/memory_limit.php index 82a33d8db9..cd76ecd3a8 100644 --- a/plugins/SecurityInfo/PhpSecInfo/Test/Core/memory_limit.php +++ b/plugins/SecurityInfo/PhpSecInfo/Test/Core/memory_limit.php @@ -12,13 +12,13 @@ /** * require the PhpSecInfo_Test_Core class */ -require_once(PHPSECINFO_BASE_DIR.'/Test/Test_Core.php'); +require_once(PHPSECINFO_BASE_DIR . '/Test/Test_Core.php'); /** * The max recommended size for the memory_limit setting, in bytes * */ -define ('PHPSECINFO_MEMORY_LIMIT', 8*1024*1024); +define ('PHPSECINFO_MEMORY_LIMIT', 8 * 1024 * 1024); /** * Test Class for memory_limit setting @@ -29,60 +29,63 @@ class PhpSecInfo_Test_Core_Memory_Limit extends PhpSecInfo_Test_Core { - /** - * This should be a unique, human-readable identifier for this test - * - * @var string - */ - var $test_name = "memory_limit"; + /** + * This should be a unique, human-readable identifier for this test + * + * @var string + */ + var $test_name = "memory_limit"; - var $recommended_value = PHPSECINFO_MEMORY_LIMIT; + var $recommended_value = PHPSECINFO_MEMORY_LIMIT; - function _retrieveCurrentValue() { - $this->current_value = $this->returnBytes(ini_get('memory_limit')); - } + function _retrieveCurrentValue() + { + $this->current_value = $this->returnBytes(ini_get('memory_limit')); + } - /** - * Check to see if the memory_limit setting is enabled. - * - * Test conditions and results: - * OK: memory_limit enabled and set to a value of 8MB or less. - * NOTICE: memory_limit enabled and set to a value greater than 8MB. - * WARNING: memory_limit disabled (compile time option). - * - * @return integer - */ - function _execTest() { - if (!$this->current_value) { - return PHPSECINFO_TEST_RESULT_WARN; - } else if ($this->returnBytes($this->current_value) <= PHPSECINFO_MEMORY_LIMIT) { - return PHPSECINFO_TEST_RESULT_OK; - } - return PHPSECINFO_TEST_RESULT_NOTICE; - } + /** + * Check to see if the memory_limit setting is enabled. + * + * Test conditions and results: + * OK: memory_limit enabled and set to a value of 8MB or less. + * NOTICE: memory_limit enabled and set to a value greater than 8MB. + * WARNING: memory_limit disabled (compile time option). + * + * @return integer + */ + function _execTest() + { + if (!$this->current_value) { + return PHPSECINFO_TEST_RESULT_WARN; + } else if ($this->returnBytes($this->current_value) <= PHPSECINFO_MEMORY_LIMIT) { + return PHPSECINFO_TEST_RESULT_OK; + } + return PHPSECINFO_TEST_RESULT_NOTICE; + } - /** - * Set the messages specific to this test - * - * @access public - * @return null - */ - function _setMessages() { - parent::_setMessages(); - $this->setMessageForResult(PHPSECINFO_TEST_RESULT_OK, 'en', 'memory_limit is enabled, and appears to be set + /** + * Set the messages specific to this test + * + * @access public + * @return null + */ + function _setMessages() + { + parent::_setMessages(); + $this->setMessageForResult(PHPSECINFO_TEST_RESULT_OK, 'en', 'memory_limit is enabled, and appears to be set to a realistic value.'); - $this->setMessageForResult(PHPSECINFO_TEST_RESULT_NOTICE, 'en', 'memory_limit is set to a very high value. Are + $this->setMessageForResult(PHPSECINFO_TEST_RESULT_NOTICE, 'en', 'memory_limit is set to a very high value. Are you sure your apps require this much memory? If not, lower the limit, as certain attacks or poor programming practices can lead to exhaustion of server resources. It is recommended that you set this to a realistic value (8M for example) from which it can be expanded as required.'); - $this->setMessageForResult(PHPSECINFO_TEST_RESULT_WARN, 'en', 'memory_limit does not appear to be enabled. This + $this->setMessageForResult(PHPSECINFO_TEST_RESULT_WARN, 'en', 'memory_limit does not appear to be enabled. This leaves the server vulnerable to attacks that attempt to exhaust resources and creates an environment where poor programming practices can propagate unchecked. This must be enabled at compile time by including the parameter "--enable-memory-limit" in the configure line. Once enabled "memory_limit" may be set in php.ini to define the maximum amount of memory a script is allowed to allocate.'); - } + } } \ No newline at end of file diff --git a/plugins/SecurityInfo/PhpSecInfo/Test/Core/open_basedir.php b/plugins/SecurityInfo/PhpSecInfo/Test/Core/open_basedir.php index d5b0d282f6..4363dbdda4 100644 --- a/plugins/SecurityInfo/PhpSecInfo/Test/Core/open_basedir.php +++ b/plugins/SecurityInfo/PhpSecInfo/Test/Core/open_basedir.php @@ -1,7 +1,7 @@ */ @@ -10,60 +10,63 @@ /** * require the PhpSecInfo_Test_Core class */ -require_once(PHPSECINFO_BASE_DIR.'/Test/Test_Core.php'); +require_once(PHPSECINFO_BASE_DIR . '/Test/Test_Core.php'); /** * Test Class for open_basedir - * + * * @package PhpSecInfo */ class PhpSecInfo_Test_Core_Open_Basedir extends PhpSecInfo_Test_Core { - /** - * This should be a unique, human-readable identifier for this test - * - * @var string - */ - var $test_name = "open_basedir"; + /** + * This should be a unique, human-readable identifier for this test + * + * @var string + */ + var $test_name = "open_basedir"; - var $recommended_value = TRUE; + var $recommended_value = TRUE; - - function _retrieveCurrentValue() { - $this->current_value = $this->getBooleanIniValue('open_basedir'); - } - - - /** - * Checks to see if allow_url_fopen is enabled - * - */ - function _execTest() { - if ($this->current_value == $this->recommended_value) { - return PHPSECINFO_TEST_RESULT_OK; - } - return PHPSECINFO_TEST_RESULT_NOTICE; - } - - - /** - * Set the messages specific to this test - * - */ - function _setMessages() { - parent::_setMessages(); - - $this->setMessageForResult(PHPSECINFO_TEST_RESULT_OK, 'en', 'open_basedir is enabled, which is the + function _retrieveCurrentValue() + { + $this->current_value = $this->getBooleanIniValue('open_basedir'); + } + + + /** + * Checks to see if allow_url_fopen is enabled + * + */ + function _execTest() + { + if ($this->current_value == $this->recommended_value) { + return PHPSECINFO_TEST_RESULT_OK; + } + + return PHPSECINFO_TEST_RESULT_NOTICE; + } + + + /** + * Set the messages specific to this test + * + */ + function _setMessages() + { + parent::_setMessages(); + + $this->setMessageForResult(PHPSECINFO_TEST_RESULT_OK, 'en', 'open_basedir is enabled, which is the recommended setting. Keep in mind that other web applications not written in PHP will not be restricted by this setting.'); - $this->setMessageForResult(PHPSECINFO_TEST_RESULT_NOTICE, 'en', 'open_basedir is disabled. When + $this->setMessageForResult(PHPSECINFO_TEST_RESULT_NOTICE, 'en', 'open_basedir is disabled. When this is enabled, only files that are in the given directory/directories and their subdirectories can be read by PHP scripts. You should consider turning this on. Keep in mind that other web applications not written in PHP will not be restricted by this setting.'); - } - + } + } \ No newline at end of file diff --git a/plugins/SecurityInfo/PhpSecInfo/Test/Core/post_max_size.php b/plugins/SecurityInfo/PhpSecInfo/Test/Core/post_max_size.php index 09bd2474ec..c2d9b633be 100644 --- a/plugins/SecurityInfo/PhpSecInfo/Test/Core/post_max_size.php +++ b/plugins/SecurityInfo/PhpSecInfo/Test/Core/post_max_size.php @@ -10,13 +10,13 @@ /** * require the PhpSecInfo_Test_Core class */ -require_once(PHPSECINFO_BASE_DIR.'/Test/Test_Core.php'); +require_once(PHPSECINFO_BASE_DIR . '/Test/Test_Core.php'); /** * The max recommended size for the post_max_size setting, in bytes * */ -define ('PHPSECINFO_POST_MAXLIMIT', 1024*256); +define ('PHPSECINFO_POST_MAXLIMIT', 1024 * 256); /** * Test Class for post_max_size @@ -26,46 +26,50 @@ define ('PHPSECINFO_POST_MAXLIMIT', 1024*256); class PhpSecInfo_Test_Core_Post_Max_Size extends PhpSecInfo_Test_Core { - /** - * This should be a unique, human-readable identifier for this test - * - * @var string - */ - var $test_name = "post_max_size"; + /** + * This should be a unique, human-readable identifier for this test + * + * @var string + */ + var $test_name = "post_max_size"; - var $recommended_value = PHPSECINFO_POST_MAXLIMIT; + var $recommended_value = PHPSECINFO_POST_MAXLIMIT; - function _retrieveCurrentValue() { - $this->current_value = $this->returnBytes(ini_get('post_max_size')); - } + function _retrieveCurrentValue() + { + $this->current_value = $this->returnBytes(ini_get('post_max_size')); + } - /** - * Check to see if the post_max_size setting is enabled. - */ - function _execTest() { + /** + * Check to see if the post_max_size setting is enabled. + */ + function _execTest() + { - if ($this->current_value - && $this->current_value <= $this->recommended_value - && $post_max_size != -1) { - return PHPSECINFO_TEST_RESULT_OK; - } + if ($this->current_value + && $this->current_value <= $this->recommended_value + && $post_max_size != -1 + ) { + return PHPSECINFO_TEST_RESULT_OK; + } - return PHPSECINFO_TEST_RESULT_NOTICE; - } + return PHPSECINFO_TEST_RESULT_NOTICE; + } - /** - * Set the messages specific to this test - * - */ - function _setMessages() { - parent::_setMessages(); + /** + * Set the messages specific to this test + * + */ + function _setMessages() + { + parent::_setMessages(); - $this->setMessageForResult(PHPSECINFO_TEST_RESULT_OK, 'en', 'post_max_size is enabled, and appears to + $this->setMessageForResult(PHPSECINFO_TEST_RESULT_OK, 'en', 'post_max_size is enabled, and appears to be a relatively low value'); - $this->setMessageForResult(PHPSECINFO_TEST_RESULT_NOTICE, 'en', 'post_max_size is not enabled, or is set to + $this->setMessageForResult(PHPSECINFO_TEST_RESULT_NOTICE, 'en', 'post_max_size is not enabled, or is set to a high value. Allowing a large value may open up your server to denial-of-service attacks'); - } + } } \ No newline at end of file diff --git a/plugins/SecurityInfo/PhpSecInfo/Test/Core/register_globals.php b/plugins/SecurityInfo/PhpSecInfo/Test/Core/register_globals.php index 1caa7e8e30..522813e94b 100644 --- a/plugins/SecurityInfo/PhpSecInfo/Test/Core/register_globals.php +++ b/plugins/SecurityInfo/PhpSecInfo/Test/Core/register_globals.php @@ -10,7 +10,7 @@ /** * require the PhpSecInfo_Test_Core class */ -require_once(PHPSECINFO_BASE_DIR.'/Test/Test_Core.php'); +require_once(PHPSECINFO_BASE_DIR . '/Test/Test_Core.php'); /** @@ -21,57 +21,60 @@ require_once(PHPSECINFO_BASE_DIR.'/Test/Test_Core.php'); class PhpSecInfo_Test_Core_Register_Globals extends PhpSecInfo_Test_Core { - /** - * This should be a unique, human-readable identifier for this test - * - * @var string - */ - var $test_name = "register_globals"; + /** + * This should be a unique, human-readable identifier for this test + * + * @var string + */ + var $test_name = "register_globals"; - var $recommended_value = FALSE; + var $recommended_value = FALSE; - function _retrieveCurrentValue() { - $this->current_value = $this->getBooleanIniValue('register_globals'); - } + function _retrieveCurrentValue() + { + $this->current_value = $this->getBooleanIniValue('register_globals'); + } - /** - * register_globals has been removed since PHP 6.0 - * - * @return boolean - */ - function isTestable() { - return version_compare(PHP_VERSION, '6', '<') ; - } + /** + * register_globals has been removed since PHP 6.0 + * + * @return boolean + */ + function isTestable() + { + return version_compare(PHP_VERSION, '6', '<'); + } + /** + * Checks to see if allow_url_fopen is enabled + * + */ + function _execTest() + { + if ($this->current_value == $this->recommended_value) { + return PHPSECINFO_TEST_RESULT_OK; + } - /** - * Checks to see if allow_url_fopen is enabled - * - */ - function _execTest() { - if ($this->current_value == $this->recommended_value) { - return PHPSECINFO_TEST_RESULT_OK; - } + return PHPSECINFO_TEST_RESULT_WARN; + } - return PHPSECINFO_TEST_RESULT_WARN; - } + /** + * Set the messages specific to this test + * + */ + function _setMessages() + { + parent::_setMessages(); - /** - * Set the messages specific to this test - * - */ - function _setMessages() { - parent::_setMessages(); - - $this->setMessageForResult(PHPSECINFO_TEST_RESULT_NOTRUN, 'en', 'You are running PHP 6 or later and register_globals has been removed'); - $this->setMessageForResult(PHPSECINFO_TEST_RESULT_OK, 'en', 'register_globals is disabled, which is the recommended setting'); - $this->setMessageForResult(PHPSECINFO_TEST_RESULT_WARN, 'en', 'register_globals is enabled. This could be a serious security risk. You should disable register_globals immediately'); - } + $this->setMessageForResult(PHPSECINFO_TEST_RESULT_NOTRUN, 'en', 'You are running PHP 6 or later and register_globals has been removed'); + $this->setMessageForResult(PHPSECINFO_TEST_RESULT_OK, 'en', 'register_globals is disabled, which is the recommended setting'); + $this->setMessageForResult(PHPSECINFO_TEST_RESULT_WARN, 'en', 'register_globals is enabled. This could be a serious security risk. You should disable register_globals immediately'); + } } \ No newline at end of file diff --git a/plugins/SecurityInfo/PhpSecInfo/Test/Core/uid.php b/plugins/SecurityInfo/PhpSecInfo/Test/Core/uid.php index cbc3ae13ec..a07f268836 100644 --- a/plugins/SecurityInfo/PhpSecInfo/Test/Core/uid.php +++ b/plugins/SecurityInfo/PhpSecInfo/Test/Core/uid.php @@ -10,7 +10,7 @@ /** * require the PhpSecInfo_Test_Core class */ -require_once(PHPSECINFO_BASE_DIR.'/Test/Test_Core.php'); +require_once(PHPSECINFO_BASE_DIR . '/Test/Test_Core.php'); /** @@ -28,66 +28,70 @@ define ('PHPSECINFO_MIN_SAFE_UID', 100); class PhpSecInfo_Test_Core_Uid extends PhpSecInfo_Test_Core { - /** - * This should be a unique, human-readable identifier for this test - * - * @var string - */ - var $test_name = "user_id"; - - var $recommended_value = PHPSECINFO_MIN_SAFE_UID; - - /** - * This test only works under Unix OSes - * - * @return boolean - */ - function isTestable() { - if ($this->osIsWindows()) { - return false; - } elseif ($this->getUnixId() === false) { - $this->setMessageForResult(PHPSECINFO_TEST_RESULT_NOTRUN, 'en', 'Functions required to retrieve user ID not available'); - return false; - } - return true; - } - - - function _retrieveCurrentValue() { - $id = $this->getUnixId(); + /** + * This should be a unique, human-readable identifier for this test + * + * @var string + */ + var $test_name = "user_id"; + + var $recommended_value = PHPSECINFO_MIN_SAFE_UID; + + /** + * This test only works under Unix OSes + * + * @return boolean + */ + function isTestable() + { + if ($this->osIsWindows()) { + return false; + } elseif ($this->getUnixId() === false) { + $this->setMessageForResult(PHPSECINFO_TEST_RESULT_NOTRUN, 'en', 'Functions required to retrieve user ID not available'); + return false; + } + return true; + } + + + function _retrieveCurrentValue() + { + $id = $this->getUnixId(); if (is_array($id)) { $this->current_value = $id['uid']; } else { $this->current_value = false; } - } - - /** - * Checks the GID of the PHP process to make sure it is above PHPSECINFO_MIN_SAFE_UID - * - * @see PHPSECINFO_MIN_SAFE_UID - */ - function _execTest() { - if ($this->current_value >= $this->recommended_value) { - return PHPSECINFO_TEST_RESULT_OK; - } - - return PHPSECINFO_TEST_RESULT_WARN; - } - - - /** - * Set the messages specific to this test - * - */ - function _setMessages() { - parent::_setMessages(); - - $this->setMessageForResult(PHPSECINFO_TEST_RESULT_OK, 'en', 'PHP is executing as what is probably a non-privileged user'); - $this->setMessageForResult(PHPSECINFO_TEST_RESULT_WARN, 'en', 'PHP may be executing as a "privileged" user, which could be a serious security vulnerability.'); - $this->setMessageForResult(PHPSECINFO_TEST_RESULT_NOTRUN, 'en', 'This test will not run on Windows OSes'); - } + } + + /** + * Checks the GID of the PHP process to make sure it is above PHPSECINFO_MIN_SAFE_UID + * + * @see PHPSECINFO_MIN_SAFE_UID + */ + function _execTest() + { + if ($this->current_value >= $this->recommended_value) { + return PHPSECINFO_TEST_RESULT_OK; + } + + return PHPSECINFO_TEST_RESULT_WARN; + } + + + /** + * Set the messages specific to this test + * + */ + function _setMessages() + { + parent::_setMessages(); + + $this->setMessageForResult(PHPSECINFO_TEST_RESULT_OK, 'en', 'PHP is executing as what is probably a non-privileged user'); + $this->setMessageForResult(PHPSECINFO_TEST_RESULT_WARN, 'en', 'PHP may be executing as a "privileged" user, which could be a serious security vulnerability.'); + $this->setMessageForResult(PHPSECINFO_TEST_RESULT_NOTRUN, 'en', 'This test will not run on Windows OSes'); + } } \ No newline at end of file diff --git a/plugins/SecurityInfo/PhpSecInfo/Test/Core/upload_max_filesize.php b/plugins/SecurityInfo/PhpSecInfo/Test/Core/upload_max_filesize.php index bc0b66b42b..07502a4153 100644 --- a/plugins/SecurityInfo/PhpSecInfo/Test/Core/upload_max_filesize.php +++ b/plugins/SecurityInfo/PhpSecInfo/Test/Core/upload_max_filesize.php @@ -9,13 +9,13 @@ /** * require the PhpSecInfo_Test_Core class */ -require_once(PHPSECINFO_BASE_DIR.'/Test/Test_Core.php'); +require_once(PHPSECINFO_BASE_DIR . '/Test/Test_Core.php'); /** * The max recommended size for the upload_max_filesize setting, in bytes * */ -define ('PHPSECINFO_UPLOAD_MAXLIMIT', 1024*256); +define ('PHPSECINFO_UPLOAD_MAXLIMIT', 1024 * 256); /** @@ -27,44 +27,48 @@ class PhpSecInfo_Test_Core_Upload_Max_Filesize extends PhpSecInfo_Test_Core { - /** - * This should be a unique, human-readable identifier for this test - * - * @var string - */ - var $test_name = "upload_max_filesize"; - - var $recommended_value = PHPSECINFO_UPLOAD_MAXLIMIT; - - function _retrieveCurrentValue() { - $this->current_value = $this->returnBytes(ini_get('upload_max_filesize')); - } - - /** - * Check to see if the post_max_size setting is enabled. - */ - function _execTest() { - - if ($this->current_value - && $this->current_value <= $this->recommended_value - && $post_max_size != -1) { - return PHPSECINFO_TEST_RESULT_OK; - } - - return PHPSECINFO_TEST_RESULT_NOTICE; - } - - - /** - * Set the messages specific to this test - * - */ - function _setMessages() { - parent::_setMessages(); - - $this->setMessageForResult(PHPSECINFO_TEST_RESULT_OK, 'en', 'upload_max_filesize is enabled, and appears to be a relatively low value.'); - $this->setMessageForResult(PHPSECINFO_TEST_RESULT_NOTICE, 'en', 'upload_max_filesize is not enabled, or is set to a high value. Are you sure your apps require uploading files of this size? If not, lower the limit, as large file uploads can impact server performance'); - } + /** + * This should be a unique, human-readable identifier for this test + * + * @var string + */ + var $test_name = "upload_max_filesize"; + + var $recommended_value = PHPSECINFO_UPLOAD_MAXLIMIT; + + function _retrieveCurrentValue() + { + $this->current_value = $this->returnBytes(ini_get('upload_max_filesize')); + } + + /** + * Check to see if the post_max_size setting is enabled. + */ + function _execTest() + { + + if ($this->current_value + && $this->current_value <= $this->recommended_value + && $post_max_size != -1 + ) { + return PHPSECINFO_TEST_RESULT_OK; + } + + return PHPSECINFO_TEST_RESULT_NOTICE; + } + + + /** + * Set the messages specific to this test + * + */ + function _setMessages() + { + parent::_setMessages(); + + $this->setMessageForResult(PHPSECINFO_TEST_RESULT_OK, 'en', 'upload_max_filesize is enabled, and appears to be a relatively low value.'); + $this->setMessageForResult(PHPSECINFO_TEST_RESULT_NOTICE, 'en', 'upload_max_filesize is not enabled, or is set to a high value. Are you sure your apps require uploading files of this size? If not, lower the limit, as large file uploads can impact server performance'); + } } \ No newline at end of file diff --git a/plugins/SecurityInfo/PhpSecInfo/Test/Core/upload_tmp_dir.php b/plugins/SecurityInfo/PhpSecInfo/Test/Core/upload_tmp_dir.php index e0b173dbea..4a83e0f7cd 100644 --- a/plugins/SecurityInfo/PhpSecInfo/Test/Core/upload_tmp_dir.php +++ b/plugins/SecurityInfo/PhpSecInfo/Test/Core/upload_tmp_dir.php @@ -9,7 +9,7 @@ /** * require the PhpSecInfo_Test_Core class */ -require_once(PHPSECINFO_BASE_DIR.'/Test/Test_Core.php'); +require_once(PHPSECINFO_BASE_DIR . '/Test/Test_Core.php'); /** * Test Class for upload_tmp_dir @@ -19,82 +19,87 @@ require_once(PHPSECINFO_BASE_DIR.'/Test/Test_Core.php'); class PhpSecInfo_Test_Core_Upload_Tmp_Dir extends PhpSecInfo_Test_Core { - /** - * This should be a unique, human-readable identifier for this test - * - * @var string - */ - var $test_name = "upload_tmp_dir"; + /** + * This should be a unique, human-readable identifier for this test + * + * @var string + */ + var $test_name = "upload_tmp_dir"; - var $recommended_value = "A non-world readable/writable directory"; + var $recommended_value = "A non-world readable/writable directory"; - function _retrieveCurrentValue() { - $this->current_value = ini_get('upload_tmp_dir'); + function _retrieveCurrentValue() + { + $this->current_value = ini_get('upload_tmp_dir'); - if( empty($this->current_value) ) { - if (function_exists("sys_get_temp_dir")) { - $this->current_value = sys_get_temp_dir(); - } else { - $this->current_value = $this->sys_get_temp_dir(); - } - } - } + if (empty($this->current_value)) { + if (function_exists("sys_get_temp_dir")) { + $this->current_value = sys_get_temp_dir(); + } else { + $this->current_value = $this->sys_get_temp_dir(); + } + } + } - /** - * We are disabling this function on Windows OSes right now until - * we can be certain of the proper way to check world-readability - * - * @return unknown - */ - function isTestable() { - if ($this->osIsWindows()) { - return FALSE; - } else { - return TRUE; - } - } + /** + * We are disabling this function on Windows OSes right now until + * we can be certain of the proper way to check world-readability + * + * @return unknown + */ + function isTestable() + { + if ($this->osIsWindows()) { + return FALSE; + } else { + return TRUE; + } + } - /** - * Check if upload_tmp_dir matches PHPSECINFO_TEST_COMMON_TMPDIR, or is word-writable - * - * This is still unix-specific, and it's possible that for now - * this test should be disabled under Windows builds. - * - * @see PHPSECINFO_TEST_COMMON_TMPDIR - */ - function _execTest() { + /** + * Check if upload_tmp_dir matches PHPSECINFO_TEST_COMMON_TMPDIR, or is word-writable + * + * This is still unix-specific, and it's possible that for now + * this test should be disabled under Windows builds. + * + * @see PHPSECINFO_TEST_COMMON_TMPDIR + */ + function _execTest() + { - $perms = @fileperms($this->current_value); - if ($perms === false) { - return PHPSECINFO_TEST_RESULT_WARN; - } else if ($this->current_value - && !preg_match("|".PHPSECINFO_TEST_COMMON_TMPDIR."/?|", $this->current_value) - && ! ($perms & 0x0004) - && ! ($perms & 0x0002) ) { - return PHPSECINFO_TEST_RESULT_OK; - } + $perms = @fileperms($this->current_value); + if ($perms === false) { + return PHPSECINFO_TEST_RESULT_WARN; + } else if ($this->current_value + && !preg_match("|" . PHPSECINFO_TEST_COMMON_TMPDIR . "/?|", $this->current_value) + && !($perms & 0x0004) + && !($perms & 0x0002) + ) { + return PHPSECINFO_TEST_RESULT_OK; + } - // rewrite current_value to display perms - $this->current_value .= " (".substr(sprintf('%o', $perms), -4).")"; + // rewrite current_value to display perms + $this->current_value .= " (" . substr(sprintf('%o', $perms), -4) . ")"; - return PHPSECINFO_TEST_RESULT_NOTICE; - } + return PHPSECINFO_TEST_RESULT_NOTICE; + } - /** - * Set the messages specific to this test - * - */ - function _setMessages() { - parent::_setMessages(); + /** + * Set the messages specific to this test + * + */ + function _setMessages() + { + parent::_setMessages(); - $this->setMessageForResult(PHPSECINFO_TEST_RESULT_NOTRUN, 'en', 'Test not run -- currently disabled on Windows OSes'); - $this->setMessageForResult(PHPSECINFO_TEST_RESULT_OK, 'en', 'upload_tmp_dir is enabled, which is the + $this->setMessageForResult(PHPSECINFO_TEST_RESULT_NOTRUN, 'en', 'Test not run -- currently disabled on Windows OSes'); + $this->setMessageForResult(PHPSECINFO_TEST_RESULT_OK, 'en', 'upload_tmp_dir is enabled, which is the recommended setting. Make sure your upload_tmp_dir path is not world-readable'); - $this->setMessageForResult(PHPSECINFO_TEST_RESULT_WARN, 'en', 'unable to retrieve file permissions on upload_tmp_dir'); - $this->setMessageForResult(PHPSECINFO_TEST_RESULT_NOTICE, 'en', 'upload_tmp_dir is disabled, or is set to a + $this->setMessageForResult(PHPSECINFO_TEST_RESULT_WARN, 'en', 'unable to retrieve file permissions on upload_tmp_dir'); + $this->setMessageForResult(PHPSECINFO_TEST_RESULT_NOTICE, 'en', 'upload_tmp_dir is disabled, or is set to a common world-writable directory. This typically allows other users on this server to access temporary copies of files uploaded via your PHP scripts. You should set upload_tmp_dir to a non-world-readable directory'); - } + } } \ No newline at end of file diff --git a/plugins/SecurityInfo/PhpSecInfo/Test/Curl/file_support.php b/plugins/SecurityInfo/PhpSecInfo/Test/Curl/file_support.php index 816d747259..b02428f62f 100644 --- a/plugins/SecurityInfo/PhpSecInfo/Test/Curl/file_support.php +++ b/plugins/SecurityInfo/PhpSecInfo/Test/Curl/file_support.php @@ -9,7 +9,7 @@ /** * require the PhpSecInfo_Test_Curl class */ -require_once(PHPSECINFO_BASE_DIR.'/Test/Test_Curl.php'); +require_once(PHPSECINFO_BASE_DIR . '/Test/Test_Curl.php'); /** * Test class for CURL file_support @@ -24,52 +24,54 @@ require_once(PHPSECINFO_BASE_DIR.'/Test/Test_Curl.php'); class PhpSecInfo_Test_Curl_File_Support extends PhpSecInfo_Test_Curl { - /** - * This should be a unique, human-readable identifier for this test - * - * @var string - */ - var $test_name = "file_support"; + /** + * This should be a unique, human-readable identifier for this test + * + * @var string + */ + var $test_name = "file_support"; - var $recommended_value = '5.1.6+ or 4.4.4+'; + var $recommended_value = '5.1.6+ or 4.4.4+'; - function _retrieveCurrentValue() { - $this->current_value = PHP_VERSION; - } - + function _retrieveCurrentValue() + { + $this->current_value = PHP_VERSION; + } - /** - * Checks to see if libcurl's "file://" support is enabled by examining the "protocols" array - * in the info returned from curl_version() - * @return integer - * - */ - function _execTest() { - $curlinfo = curl_version(); + /** + * Checks to see if libcurl's "file://" support is enabled by examining the "protocols" array + * in the info returned from curl_version() + * @return integer + * + */ + function _execTest() + { - if ( version_compare($this->current_value, '5.1.6', '>=') || - (version_compare($this->current_value, '4.4.4', '>=')) && ( version_compare($this->current_value, '5', '<') ) - ) { - return PHPSECINFO_TEST_RESULT_OK; - } else { - return PHPSECINFO_TEST_RESULT_WARN; - } + $curlinfo = curl_version(); - } + if (version_compare($this->current_value, '5.1.6', '>=') || + (version_compare($this->current_value, '4.4.4', '>=')) && (version_compare($this->current_value, '5', '<')) + ) { + return PHPSECINFO_TEST_RESULT_OK; + } else { + return PHPSECINFO_TEST_RESULT_WARN; + } + } - /** - * Set the messages specific to this test - * - */ - function _setMessages() { - parent::_setMessages(); + /** + * Set the messages specific to this test + * + */ + function _setMessages() + { + parent::_setMessages(); - $this->setMessageForResult(PHPSECINFO_TEST_RESULT_OK, 'en', "You are running PHP 4.4.4 or higher, or PHP 5.1.6 or higher. These versions fix the security hole present in the cURL functions that allow it to bypass safe_mode and open_basedir restrictions."); - $this->setMessageForResult(PHPSECINFO_TEST_RESULT_WARN, 'en', "A security hole present in your version of PHP allows the cURL functions to bypass safe_mode and open_basedir restrictions. You should upgrade to the latest version of PHP."); + $this->setMessageForResult(PHPSECINFO_TEST_RESULT_OK, 'en', "You are running PHP 4.4.4 or higher, or PHP 5.1.6 or higher. These versions fix the security hole present in the cURL functions that allow it to bypass safe_mode and open_basedir restrictions."); + $this->setMessageForResult(PHPSECINFO_TEST_RESULT_WARN, 'en', "A security hole present in your version of PHP allows the cURL functions to bypass safe_mode and open_basedir restrictions. You should upgrade to the latest version of PHP."); - } + } } \ No newline at end of file diff --git a/plugins/SecurityInfo/PhpSecInfo/Test/Session/save_path.php b/plugins/SecurityInfo/PhpSecInfo/Test/Session/save_path.php index 910e5c7fd2..f04d087802 100644 --- a/plugins/SecurityInfo/PhpSecInfo/Test/Session/save_path.php +++ b/plugins/SecurityInfo/PhpSecInfo/Test/Session/save_path.php @@ -9,7 +9,7 @@ /** * require the PhpSecInfo_Test_Core class */ -require_once(PHPSECINFO_BASE_DIR.'/Test/Test_Session.php'); +require_once(PHPSECINFO_BASE_DIR . '/Test/Test_Session.php'); /** * Test class for session save_path @@ -19,87 +19,92 @@ require_once(PHPSECINFO_BASE_DIR.'/Test/Test_Session.php'); class PhpSecInfo_Test_Session_Save_Path extends PhpSecInfo_Test_Session { - /** - * This should be a unique, human-readable identifier for this test - * - * @var string - */ - var $test_name = "save_path"; - - var $recommended_value = "A non-world readable/writable directory"; - - function _retrieveCurrentValue() { - $this->current_value = ini_get('session.save_path'); - - if( empty($this->current_value) ) { - if (function_exists("sys_get_temp_dir")) { - $this->current_value = sys_get_temp_dir(); - } else { - $this->current_value = $this->sys_get_temp_dir(); - } - } - - if( preg_match('/^[0-9]+;(.+)/', $this->current_value, $matches) ) { - $this->current_value = $matches[1]; - } - } - - - /** - * We are disabling this function on Windows OSes right now until - * we can be certain of the proper way to check world-readability - * - * @return unknown - */ - function isTestable() { - if ($this->osIsWindows()) { - return FALSE; - } else { - return TRUE; - } - } - - - /** - * Check if session.save_path matches PHPSECINFO_TEST_COMMON_TMPDIR, or is word-writable - * - * This is still unix-specific, and it's possible that for now - * this test should be disabled under Windows builds. - * - * @see PHPSECINFO_TEST_COMMON_TMPDIR - */ - function _execTest() { - - $perms = @fileperms($this->current_value); - if ($perms === false) { - return PHPSECINFO_TEST_RESULT_WARN; - } else if ($this->current_value - && !preg_match("|".PHPSECINFO_TEST_COMMON_TMPDIR."/?|", $this->current_value) - && ! ($perms & 0x0004) - && ! ($perms & 0x0002) ) { - return PHPSECINFO_TEST_RESULT_OK; - } - - // rewrite current_value to display perms - $this->current_value .= " (".substr(sprintf('%o', $perms), -4).")"; - - return PHPSECINFO_TEST_RESULT_NOTICE; - } - - /** - * Set the messages specific to this test - * - */ - function _setMessages() { - parent::_setMessages(); - - $this->setMessageForResult(PHPSECINFO_TEST_RESULT_NOTRUN, 'en', 'Test not run -- currently disabled on Windows OSes'); - $this->setMessageForResult(PHPSECINFO_TEST_RESULT_OK, 'en', 'save_path is enabled, which is the + /** + * This should be a unique, human-readable identifier for this test + * + * @var string + */ + var $test_name = "save_path"; + + var $recommended_value = "A non-world readable/writable directory"; + + function _retrieveCurrentValue() + { + $this->current_value = ini_get('session.save_path'); + + if (empty($this->current_value)) { + if (function_exists("sys_get_temp_dir")) { + $this->current_value = sys_get_temp_dir(); + } else { + $this->current_value = $this->sys_get_temp_dir(); + } + } + + if (preg_match('/^[0-9]+;(.+)/', $this->current_value, $matches)) { + $this->current_value = $matches[1]; + } + } + + + /** + * We are disabling this function on Windows OSes right now until + * we can be certain of the proper way to check world-readability + * + * @return unknown + */ + function isTestable() + { + if ($this->osIsWindows()) { + return FALSE; + } else { + return TRUE; + } + } + + + /** + * Check if session.save_path matches PHPSECINFO_TEST_COMMON_TMPDIR, or is word-writable + * + * This is still unix-specific, and it's possible that for now + * this test should be disabled under Windows builds. + * + * @see PHPSECINFO_TEST_COMMON_TMPDIR + */ + function _execTest() + { + + $perms = @fileperms($this->current_value); + if ($perms === false) { + return PHPSECINFO_TEST_RESULT_WARN; + } else if ($this->current_value + && !preg_match("|" . PHPSECINFO_TEST_COMMON_TMPDIR . "/?|", $this->current_value) + && !($perms & 0x0004) + && !($perms & 0x0002) + ) { + return PHPSECINFO_TEST_RESULT_OK; + } + + // rewrite current_value to display perms + $this->current_value .= " (" . substr(sprintf('%o', $perms), -4) . ")"; + + return PHPSECINFO_TEST_RESULT_NOTICE; + } + + /** + * Set the messages specific to this test + * + */ + function _setMessages() + { + parent::_setMessages(); + + $this->setMessageForResult(PHPSECINFO_TEST_RESULT_NOTRUN, 'en', 'Test not run -- currently disabled on Windows OSes'); + $this->setMessageForResult(PHPSECINFO_TEST_RESULT_OK, 'en', 'save_path is enabled, which is the recommended setting. Make sure your save_path path is not world-readable'); - $this->setMessageForResult(PHPSECINFO_TEST_RESULT_WARN, 'en', 'unable to retrieve file permissions on save_path'); - $this->setMessageForResult(PHPSECINFO_TEST_RESULT_NOTICE, 'en', 'save_path is disabled, or is set to a + $this->setMessageForResult(PHPSECINFO_TEST_RESULT_WARN, 'en', 'unable to retrieve file permissions on save_path'); + $this->setMessageForResult(PHPSECINFO_TEST_RESULT_NOTICE, 'en', 'save_path is disabled, or is set to a common world-writable directory. This typically allows other users on this server to access session files. You should set save_path to a non-world-readable directory'); - } + } } \ No newline at end of file diff --git a/plugins/SecurityInfo/PhpSecInfo/Test/Session/use_trans_sid.php b/plugins/SecurityInfo/PhpSecInfo/Test/Session/use_trans_sid.php index f7f3f2e4a9..c3a61e5d59 100644 --- a/plugins/SecurityInfo/PhpSecInfo/Test/Session/use_trans_sid.php +++ b/plugins/SecurityInfo/PhpSecInfo/Test/Session/use_trans_sid.php @@ -10,7 +10,7 @@ /** * require the PhpSecInfo_Test_Session class */ -require_once(PHPSECINFO_BASE_DIR.'/Test/Test_Session.php'); +require_once(PHPSECINFO_BASE_DIR . '/Test/Test_Session.php'); /** * Test class for session use_trans_sid @@ -21,46 +21,49 @@ require_once(PHPSECINFO_BASE_DIR.'/Test/Test_Session.php'); class PhpSecInfo_Test_Session_Use_Trans_Sid extends PhpSecInfo_Test_Session { - /** - * This should be a unique, human-readable identifier for this test - * - * @var string - */ - var $test_name = "use_trans_sid"; + /** + * This should be a unique, human-readable identifier for this test + * + * @var string + */ + var $test_name = "use_trans_sid"; - var $recommended_value = FALSE; + var $recommended_value = FALSE; - function _retrieveCurrentValue() { - $this->current_value = $this->getBooleanIniValue('session.use_trans_sid'); - } + function _retrieveCurrentValue() + { + $this->current_value = $this->getBooleanIniValue('session.use_trans_sid'); + } - /** - * Checks to see if allow_url_fopen is enabled - * - */ - function _execTest() { - if ($this->current_value == $this->recommended_value) { - return PHPSECINFO_TEST_RESULT_OK; - } + /** + * Checks to see if allow_url_fopen is enabled + * + */ + function _execTest() + { + if ($this->current_value == $this->recommended_value) { + return PHPSECINFO_TEST_RESULT_OK; + } - return PHPSECINFO_TEST_RESULT_NOTICE; - } + return PHPSECINFO_TEST_RESULT_NOTICE; + } - /** - * Set the messages specific to this test - * - */ - function _setMessages() { - parent::_setMessages(); + /** + * Set the messages specific to this test + * + */ + function _setMessages() + { + parent::_setMessages(); - $this->setMessageForResult(PHPSECINFO_TEST_RESULT_OK, 'en', 'use_trans_sid is disabled, which is the recommended setting'); - $this->setMessageForResult(PHPSECINFO_TEST_RESULT_NOTICE, 'en', 'use_trans_sid is enabled. This makes session hijacking easier. Consider disabling this feature'); + $this->setMessageForResult(PHPSECINFO_TEST_RESULT_OK, 'en', 'use_trans_sid is disabled, which is the recommended setting'); + $this->setMessageForResult(PHPSECINFO_TEST_RESULT_NOTICE, 'en', 'use_trans_sid is enabled. This makes session hijacking easier. Consider disabling this feature'); - } + } } \ No newline at end of file diff --git a/plugins/SecurityInfo/PhpSecInfo/Test/Suhosin/extension.php b/plugins/SecurityInfo/PhpSecInfo/Test/Suhosin/extension.php index 50e4d05359..f6a0d340d2 100644 --- a/plugins/SecurityInfo/PhpSecInfo/Test/Suhosin/extension.php +++ b/plugins/SecurityInfo/PhpSecInfo/Test/Suhosin/extension.php @@ -9,7 +9,7 @@ /** * require the PhpSecInfo_Test_Suhosin class */ -require_once(PHPSECINFO_BASE_DIR.'/Test/Test_Suhosin.php'); +require_once(PHPSECINFO_BASE_DIR . '/Test/Test_Suhosin.php'); /** * Test class for Suhosin extension @@ -21,26 +21,29 @@ require_once(PHPSECINFO_BASE_DIR.'/Test/Test_Suhosin.php'); */ class PhpSecInfo_Test_Suhosin_Extension extends PhpSecInfo_Test_Suhosin { - var $test_name = "Suhosin extension"; + var $test_name = "Suhosin extension"; - var $recommended_value = true; + var $recommended_value = true; - function _retrieveCurrentValue() { - $this->current_value = extension_loaded('suhosin'); - } + function _retrieveCurrentValue() + { + $this->current_value = extension_loaded('suhosin'); + } - function _execTest() { - if ( $this->current_value === true ) { - return PHPSECINFO_TEST_RESULT_OK; - } else { - return PHPSECINFO_TEST_RESULT_NOTICE; - } - } + function _execTest() + { + if ($this->current_value === true) { + return PHPSECINFO_TEST_RESULT_OK; + } else { + return PHPSECINFO_TEST_RESULT_NOTICE; + } + } - function _setMessages() { - parent::_setMessages(); + function _setMessages() + { + parent::_setMessages(); - $this->setMessageForResult(PHPSECINFO_TEST_RESULT_OK, 'en', "You are running PHP with the Suhosin extension loaded. This extension provides high-level runtime protections, and additional filtering and logging features."); - $this->setMessageForResult(PHPSECINFO_TEST_RESULT_NOTICE, 'en', "You are not running PHP with the Suhosin extension loaded. We recommend both the patch and extension for low- and high-level protections including transparent cookie encryption and remote inclusion vulnerabilities."); - } + $this->setMessageForResult(PHPSECINFO_TEST_RESULT_OK, 'en', "You are running PHP with the Suhosin extension loaded. This extension provides high-level runtime protections, and additional filtering and logging features."); + $this->setMessageForResult(PHPSECINFO_TEST_RESULT_NOTICE, 'en', "You are not running PHP with the Suhosin extension loaded. We recommend both the patch and extension for low- and high-level protections including transparent cookie encryption and remote inclusion vulnerabilities."); + } } diff --git a/plugins/SecurityInfo/PhpSecInfo/Test/Suhosin/patch.php b/plugins/SecurityInfo/PhpSecInfo/Test/Suhosin/patch.php index 6e982edae2..ec475281bd 100644 --- a/plugins/SecurityInfo/PhpSecInfo/Test/Suhosin/patch.php +++ b/plugins/SecurityInfo/PhpSecInfo/Test/Suhosin/patch.php @@ -9,7 +9,7 @@ /** * require the PhpSecInfo_Test_Suhosin class */ -require_once(PHPSECINFO_BASE_DIR.'/Test/Test_Suhosin.php'); +require_once(PHPSECINFO_BASE_DIR . '/Test/Test_Suhosin.php'); /** * Test class for Suhosin @@ -21,35 +21,38 @@ require_once(PHPSECINFO_BASE_DIR.'/Test/Test_Suhosin.php'); */ class PhpSecInfo_Test_Suhosin_Patch extends PhpSecInfo_Test_Suhosin { - var $test_name = "Suhosin patch"; - - var $recommended_value = true; - - function _retrieveCurrentValue() { - if (preg_match('/Suhosin/', $_SERVER['SERVER_SOFTWARE'])) { - $this->current_value = true; - } else { - $this->current_value = false; - - $constants = get_defined_constants(); - if(isset($constants['SUHOSIN_PATCH']) && $constants['SUHOSIN_PATCH'] == 1) { - $this->current_value = true; - } - } - } - - function _execTest() { - if ( $this->current_value === true ) { - return PHPSECINFO_TEST_RESULT_OK; - } else { - return PHPSECINFO_TEST_RESULT_NOTICE; - } - } - - function _setMessages() { - parent::_setMessages(); - - $this->setMessageForResult(PHPSECINFO_TEST_RESULT_OK, 'en', "You are running PHP with the Suhosin patch applied against the PHP core. This patch implements various low-level protections against (for example) buffer overflows and format string vulnerabilities."); - $this->setMessageForResult(PHPSECINFO_TEST_RESULT_NOTICE, 'en', "You are not running PHP with the Suhosin patch applied. We recommend both the patch and extension for low- and high-level protections against (for example) buffer overflows and format string vulnerabilities."); - } + var $test_name = "Suhosin patch"; + + var $recommended_value = true; + + function _retrieveCurrentValue() + { + if (preg_match('/Suhosin/', $_SERVER['SERVER_SOFTWARE'])) { + $this->current_value = true; + } else { + $this->current_value = false; + + $constants = get_defined_constants(); + if (isset($constants['SUHOSIN_PATCH']) && $constants['SUHOSIN_PATCH'] == 1) { + $this->current_value = true; + } + } + } + + function _execTest() + { + if ($this->current_value === true) { + return PHPSECINFO_TEST_RESULT_OK; + } else { + return PHPSECINFO_TEST_RESULT_NOTICE; + } + } + + function _setMessages() + { + parent::_setMessages(); + + $this->setMessageForResult(PHPSECINFO_TEST_RESULT_OK, 'en', "You are running PHP with the Suhosin patch applied against the PHP core. This patch implements various low-level protections against (for example) buffer overflows and format string vulnerabilities."); + $this->setMessageForResult(PHPSECINFO_TEST_RESULT_NOTICE, 'en', "You are not running PHP with the Suhosin patch applied. We recommend both the patch and extension for low- and high-level protections against (for example) buffer overflows and format string vulnerabilities."); + } } diff --git a/plugins/SecurityInfo/PhpSecInfo/Test/Test.php b/plugins/SecurityInfo/PhpSecInfo/Test/Test.php index b4902a718c..978309879d 100644 --- a/plugins/SecurityInfo/PhpSecInfo/Test/Test.php +++ b/plugins/SecurityInfo/PhpSecInfo/Test/Test.php @@ -9,8 +9,7 @@ /** * require the main PhpSecInfo class */ -require_once(PHPSECINFO_BASE_DIR.'/PhpSecInfo.php'); - +require_once(PHPSECINFO_BASE_DIR . '/PhpSecInfo.php'); define ('PHPSECINFO_TEST_RESULT_OK', -1); @@ -36,540 +35,554 @@ define ('PHPSECINFO_TEST_MOREINFO_BASEURL', 'http://phpsec.org/projects/phpsecin class PhpSecInfo_Test { - /** - * This value is used to group test results together. - * - * For example, all tests related to the mysql lib should be grouped under "mysql." - * - * @var string - */ - var $test_group = 'misc'; - - - /** - * This should be a unique, human-readable identifier for this test - * - * @var string - */ - var $test_name = 'misc_test'; - - - /** - * This is the recommended value the test will be looking for - * - * @var mixed - */ - var $recommended_value = "bar"; - - - /** - * The result returned from the test - * - * @var integer - */ - var $_result = PHPSECINFO_TEST_RESULT_NOTRUN; - - - /** - * The message corresponding to the result of the test - * - * @var string - */ - var $_message; - - - /** - * the language code. Should be a pointer to the setting in the PhpSecInfo object - * - * @var string - */ - var $_language = PHPSECINFO_LANG_DEFAULT; - - /** - * Enter description here... - * - * @var mixed - */ - var $current_value; - - /** - * This is a hash of messages that correspond to various test result levels. - * - * There are five messages, each corresponding to one of the result constants - * (PHPSECINFO_TEST_RESULT_OK, PHPSECINFO_TEST_RESULT_NOTICE, PHPSECINFO_TEST_RESULT_WARN, - * PHPSECINFO_TEST_RESULT_ERROR, PHPSECINFO_TEST_RESULT_NOTRUN) - * - * - * @var array array - */ - var $_messages = array(); - - - - - /** - * Constructor for Test skeleton class - * - * @return PhpSecInfo_Test - */ - function PhpSecInfo_Test() { - //$this->_setTestValues(); - - $this->_retrieveCurrentValue(); - //$this->setRecommendedValue(); - - $this->_setMessages(); - } - - - /** - * Determines whether or not it's appropriate to run this test (for example, if - * this test is for a particular library, it shouldn't be run if the lib isn't - * loaded). - * - * This is a terrible name, but I couldn't think of a better one atm. - * - * @return boolean - */ - function isTestable() { - - return true; - } - - - /** - * The "meat" of the test. This is where the real test code goes. You should override this when extending - * - * @return integer - */ - function _execTest() { - - return PHPSECINFO_TEST_RESULT_NOTRUN; - } - - - /** - * This function loads up result messages into the $this->_messages array. - * - * Using this method rather than setting $this->_messages directly allows result - * messages to be inherited. This is broken out into a separate function rather - * than the constructor for ease of extension purposes (php4 is whack, man). - * - */ - function _setMessages() { - $this->setMessageForResult(PHPSECINFO_TEST_RESULT_OK, 'en', 'This setting should be safe'); - $this->setMessageForResult(PHPSECINFO_TEST_RESULT_NOTICE, 'en', 'This could potentially be a security issue'); - $this->setMessageForResult(PHPSECINFO_TEST_RESULT_WARN, 'en', 'This setting may be a serious security problem'); - $this->setMessageForResult(PHPSECINFO_TEST_RESULT_ERROR, 'en', 'There was an error running this test'); - $this->setMessageForResult(PHPSECINFO_TEST_RESULT_NOTRUN, 'en', 'This test cannot be run'); - } - - - /** - * Placeholder - extend for tests - * - */ - function _retrieveCurrentValue() { - $this->current_value = "foo"; - } - - - - /** - * This is the wrapper that executes the test and sets the result code and message - */ - function test() { - $result = $this->_execTest(); - $this->_setResult($result); - - } - - - - /** - * Retrieves the result - * - * @return integer - */ - function getResult() { - return $this->_result; - } - - - - - /** - * Retrieves the message for the current result - * - * @return string - */ - function getMessage() { - if (!isset($this->_message) || empty($this->_message)) { - $this->_setMessage($this->_result, $this->_language); - } - - return $this->_message; - } - - - - /** - * Sets the message for a given result code and language - * - * - * $this->setMessageForResult(PHPSECINFO_TEST_RESULT_NOTRUN, 'en', 'This test cannot be run'); - * - * - * @param integer $result_code - * @param string $language_code - * @param string $message - * - */ - function setMessageForResult($result_code, $language_code, $message) { - - if ( !isset($this->_messages[$result_code]) ) { - $this->_messages[$result_code] = array(); - } - - if ( !is_array($this->_messages[$result_code]) ) { - $this->_messages[$result_code] = array(); - } - - $this->_messages[$result_code][$language_code] = $message; - - } - - - - - /** - * returns the current value. This function should be used to access the - * value for display. All values are cast as strings - * - * @return string - */ - function getCurrentTestValue() { - return $this->getStringValue($this->current_value); - } - - /** - * returns the recommended value. This function should be used to access the - * value for display. All values are cast as strings - * - * @return string - */ - function getRecommendedTestValue() { - return $this->getStringValue($this->recommended_value); - } - - - /** - * Sets the result code - * - * @param integer $result_code - */ - function _setResult($result_code) { - $this->_result = $result_code; - } - - - /** - * Sets the $this->_message variable based on the passed result and language codes - * - * @param integer $result_code - * @param string $language_code - */ - function _setMessage($result_code, $language_code) { - $messages = $this->_messages[$result_code]; - $message = $messages[$language_code]; - $this->_message = $message; - } - - - /** - * Returns a link to a page with detailed information about the test - * - * URL is formatted as PHPSECINFO_TEST_MOREINFO_BASEURL + testName - * - * @see PHPSECINFO_TEST_MOREINFO_BASEURL - * - * @return string|boolean - */ - function getMoreInfoURL() { - if ($tn = $this->getTestName()) { - return PHPSECINFO_TEST_MOREINFO_BASEURL.strtolower("{$tn}.html"); - } else { - return false; - } - } - - - - - /** - * This retrieves the name of this test. - * - * If a name has not been set, this returns a formatted version of the class name. - * - * @return string - */ - function getTestName() { - if (isset($this->test_name) && !empty($this->test_name)) { - return $this->test_name; - } else { - return ucwords( - str_replace('_', ' ', - get_class($this) - ) - ); - } - - } - - - /** - * sets the test name - * - * @param string $test_name - */ - function setTestName($test_name) { - $this->test_name = $test_name; - } - - - /** - * Returns the test group this test belongs to - * - * @return string - */ - function getTestGroup() { - return $this->test_group; - } - - - /** - * sets the test group - * - * @param string $test_group - */ - function setTestGroup($test_group) { - $this->test_group = $test_group; - } - - - /** - * This function takes the shorthand notation used in memory limit settings for PHP - * and returns the byte value. Totally stolen from http://us3.php.net/manual/en/function.ini-get.php - * - * - * echo 'post_max_size in bytes = ' . $this->return_bytes(ini_get('post_max_size')); - * - * - * @link http://php.net/manual/en/function.ini-get.php - * @param string $val - * @return integer - */ - function returnBytes($val) { - $val = trim($val); - - if ( (int)$val === 0 ) { - return 0; - } - - $last = strtolower($val{strlen($val)-1}); - switch($last) { - // The 'G' modifier is available since PHP 5.1.0 - case 'g': - $val *= 1024; - case 'm': - $val *= 1024; - case 'k': - $val *= 1024; - } - - return $val; - } - - - /** - * This just does the usual PHP string casting, except for - * the boolean FALSE value, where the string "0" is returned - * instead of an empty string - * - * @param mixed $val - * @return string - */ - function getStringValue($val) { - if ($val === FALSE) { - return "0"; - } else { - return (string)$val; - } - } - - - /** - * This method converts the several possible return values from - * allegedly "boolean" ini settings to proper booleans - * - * Properly converted input values are: 'off', 'on', 'false', 'true', '', '0', '1' - * (the last two might not be neccessary, but I'd rather be safe) - * - * If the ini_value doesn't match any of those, the value is returned as-is. - * - * @param string $ini_key the ini_key you need the value of - * @return boolean|mixed - */ - function getBooleanIniValue($ini_key) { - - $ini_val = ini_get($ini_key); - - switch ( strtolower($ini_val) ) { - - case 'off': - return false; - break; - case 'on': - return true; - break; - case 'false': - return false; - break; - case 'true': - return true; - break; - case '0': - return false; - break; - case '1': - return true; - break; - case '': - return false; - break; - default: - return $ini_val; - - } - - } - - /** - * sys_get_temp_dir provides some temp dir detection capability - * that is lacking in versions of PHP that do not have the - * sys_get_temp_dir() function - * - * @return string|NULL - */ - function sys_get_temp_dir() { - // Try to get from environment variable - $vars = array('TMP', 'TMPDIR', 'TEMP'); - foreach($vars as $var) { - $tmp = getenv($var); - if ( !empty($tmp) ) { - return realpath( $tmp ); - } - } - return NULL; - } - - - /** - * A quick function to determine whether we're running on Windows. - * Uses the PHP_OS constant. - * - * @return boolean - */ - function osIsWindows() { - if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { - return true; - } else { - return false; - } - } - - - /** - * Returns an array of data returned from the UNIX 'id' command - * - * includes uid, username, gid, groupname, and groups (if "exec" - * is enabled). Groups is an array of all the groups the user - * belongs to. Keys are the group ids, values are the group names. - * - * returns FALSE if no suitable function is available to retrieve - * the data - * - * @return array|boolean - */ - function getUnixId() { - - if ($this->osIsWindows()) { - return false; - } - - $success = false; - - - if (function_exists("exec") && !PhpSecInfo_Test::getBooleanIniValue('safe_mode')) { - $id_raw = exec('id'); - // uid=1000(coj) gid=1000(coj) groups=1000(coj),1001(admin) - preg_match( "|uid=(\d+)\((\S+)\)\s+gid=(\d+)\((\S+)\)\s+groups=(.+)|i", - $id_raw, - $matches); - - if (!$matches) { - /** - * for some reason the output from 'id' wasn't as we expected. - * return false so the test doesn't run. - */ - $success = false; - } else { - $id_data = array( 'uid'=>$matches[1], - 'username'=>$matches[2], - 'gid'=>$matches[3], - 'group'=>$matches[4] ); - - $groups = array(); - if ($matches[5]) { - $gs = $matches[5]; - $gs = explode(',', $gs); - foreach ($gs as $groupstr) { - if (preg_match("/(\d+)\(([^\)]+)\)/", $groupstr, $subs)) { - $groups[$subs[1]] = $subs[2]; - } else { - $groups[$groupstr] = ''; - } - } - ksort($groups); - } - $id_data['groups'] = $groups; - $success = true; - } - - } - - if (!$success && function_exists("posix_getpwuid") && function_exists("posix_geteuid") - && function_exists('posix_getgrgid') && function_exists('posix_getgroups') ) { - $data = posix_getpwuid( posix_getuid() ); - $id_data['uid'] = $data['uid']; - $id_data['username'] = $data['name']; - $id_data['gid'] = $data['gid']; - //$group_data = posix_getgrgid( posix_getegid() ); - //$id_data['group'] = $group_data['name']; - $id_data['groups'] = array(); - $groups = posix_getgroups(); - foreach ( $groups as $gid ) { - //$group_data = posix_getgrgid(posix_getgid()); - $id_data['groups'][$gid] = ''; - } - $success = true; - } - - if ($success) { - return $id_data; - } else { - return false; - } - } + /** + * This value is used to group test results together. + * + * For example, all tests related to the mysql lib should be grouped under "mysql." + * + * @var string + */ + var $test_group = 'misc'; + + + /** + * This should be a unique, human-readable identifier for this test + * + * @var string + */ + var $test_name = 'misc_test'; + + + /** + * This is the recommended value the test will be looking for + * + * @var mixed + */ + var $recommended_value = "bar"; + + + /** + * The result returned from the test + * + * @var integer + */ + var $_result = PHPSECINFO_TEST_RESULT_NOTRUN; + + + /** + * The message corresponding to the result of the test + * + * @var string + */ + var $_message; + + + /** + * the language code. Should be a pointer to the setting in the PhpSecInfo object + * + * @var string + */ + var $_language = PHPSECINFO_LANG_DEFAULT; + + /** + * Enter description here... + * + * @var mixed + */ + var $current_value; + + /** + * This is a hash of messages that correspond to various test result levels. + * + * There are five messages, each corresponding to one of the result constants + * (PHPSECINFO_TEST_RESULT_OK, PHPSECINFO_TEST_RESULT_NOTICE, PHPSECINFO_TEST_RESULT_WARN, + * PHPSECINFO_TEST_RESULT_ERROR, PHPSECINFO_TEST_RESULT_NOTRUN) + * + * + * @var array array + */ + var $_messages = array(); + + + /** + * Constructor for Test skeleton class + * + * @return PhpSecInfo_Test + */ + function PhpSecInfo_Test() + { + //$this->_setTestValues(); + + $this->_retrieveCurrentValue(); + //$this->setRecommendedValue(); + + $this->_setMessages(); + } + + + /** + * Determines whether or not it's appropriate to run this test (for example, if + * this test is for a particular library, it shouldn't be run if the lib isn't + * loaded). + * + * This is a terrible name, but I couldn't think of a better one atm. + * + * @return boolean + */ + function isTestable() + { + + return true; + } + + + /** + * The "meat" of the test. This is where the real test code goes. You should override this when extending + * + * @return integer + */ + function _execTest() + { + + return PHPSECINFO_TEST_RESULT_NOTRUN; + } + + + /** + * This function loads up result messages into the $this->_messages array. + * + * Using this method rather than setting $this->_messages directly allows result + * messages to be inherited. This is broken out into a separate function rather + * than the constructor for ease of extension purposes (php4 is whack, man). + * + */ + function _setMessages() + { + $this->setMessageForResult(PHPSECINFO_TEST_RESULT_OK, 'en', 'This setting should be safe'); + $this->setMessageForResult(PHPSECINFO_TEST_RESULT_NOTICE, 'en', 'This could potentially be a security issue'); + $this->setMessageForResult(PHPSECINFO_TEST_RESULT_WARN, 'en', 'This setting may be a serious security problem'); + $this->setMessageForResult(PHPSECINFO_TEST_RESULT_ERROR, 'en', 'There was an error running this test'); + $this->setMessageForResult(PHPSECINFO_TEST_RESULT_NOTRUN, 'en', 'This test cannot be run'); + } + + + /** + * Placeholder - extend for tests + * + */ + function _retrieveCurrentValue() + { + $this->current_value = "foo"; + } + + + /** + * This is the wrapper that executes the test and sets the result code and message + */ + function test() + { + $result = $this->_execTest(); + $this->_setResult($result); + + } + + + /** + * Retrieves the result + * + * @return integer + */ + function getResult() + { + return $this->_result; + } + + + /** + * Retrieves the message for the current result + * + * @return string + */ + function getMessage() + { + if (!isset($this->_message) || empty($this->_message)) { + $this->_setMessage($this->_result, $this->_language); + } + + return $this->_message; + } + + + /** + * Sets the message for a given result code and language + * + * + * $this->setMessageForResult(PHPSECINFO_TEST_RESULT_NOTRUN, 'en', 'This test cannot be run'); + * + * + * @param integer $result_code + * @param string $language_code + * @param string $message + * + */ + function setMessageForResult($result_code, $language_code, $message) + { + + if (!isset($this->_messages[$result_code])) { + $this->_messages[$result_code] = array(); + } + + if (!is_array($this->_messages[$result_code])) { + $this->_messages[$result_code] = array(); + } + + $this->_messages[$result_code][$language_code] = $message; + + } + + + /** + * returns the current value. This function should be used to access the + * value for display. All values are cast as strings + * + * @return string + */ + function getCurrentTestValue() + { + return $this->getStringValue($this->current_value); + } + + /** + * returns the recommended value. This function should be used to access the + * value for display. All values are cast as strings + * + * @return string + */ + function getRecommendedTestValue() + { + return $this->getStringValue($this->recommended_value); + } + + + /** + * Sets the result code + * + * @param integer $result_code + */ + function _setResult($result_code) + { + $this->_result = $result_code; + } + + + /** + * Sets the $this->_message variable based on the passed result and language codes + * + * @param integer $result_code + * @param string $language_code + */ + function _setMessage($result_code, $language_code) + { + $messages = $this->_messages[$result_code]; + $message = $messages[$language_code]; + $this->_message = $message; + } + + + /** + * Returns a link to a page with detailed information about the test + * + * URL is formatted as PHPSECINFO_TEST_MOREINFO_BASEURL + testName + * + * @see PHPSECINFO_TEST_MOREINFO_BASEURL + * + * @return string|boolean + */ + function getMoreInfoURL() + { + if ($tn = $this->getTestName()) { + return PHPSECINFO_TEST_MOREINFO_BASEURL . strtolower("{$tn}.html"); + } else { + return false; + } + } + + + /** + * This retrieves the name of this test. + * + * If a name has not been set, this returns a formatted version of the class name. + * + * @return string + */ + function getTestName() + { + if (isset($this->test_name) && !empty($this->test_name)) { + return $this->test_name; + } else { + return ucwords( + str_replace('_', ' ', + get_class($this) + ) + ); + } + + } + + + /** + * sets the test name + * + * @param string $test_name + */ + function setTestName($test_name) + { + $this->test_name = $test_name; + } + + + /** + * Returns the test group this test belongs to + * + * @return string + */ + function getTestGroup() + { + return $this->test_group; + } + + + /** + * sets the test group + * + * @param string $test_group + */ + function setTestGroup($test_group) + { + $this->test_group = $test_group; + } + + + /** + * This function takes the shorthand notation used in memory limit settings for PHP + * and returns the byte value. Totally stolen from http://us3.php.net/manual/en/function.ini-get.php + * + * + * echo 'post_max_size in bytes = ' . $this->return_bytes(ini_get('post_max_size')); + * + * + * @link http://php.net/manual/en/function.ini-get.php + * @param string $val + * @return integer + */ + function returnBytes($val) + { + $val = trim($val); + + if ((int)$val === 0) { + return 0; + } + + $last = strtolower($val{strlen($val) - 1}); + switch ($last) { + // The 'G' modifier is available since PHP 5.1.0 + case 'g': + $val *= 1024; + case 'm': + $val *= 1024; + case 'k': + $val *= 1024; + } + + return $val; + } + + + /** + * This just does the usual PHP string casting, except for + * the boolean FALSE value, where the string "0" is returned + * instead of an empty string + * + * @param mixed $val + * @return string + */ + function getStringValue($val) + { + if ($val === FALSE) { + return "0"; + } else { + return (string)$val; + } + } + + + /** + * This method converts the several possible return values from + * allegedly "boolean" ini settings to proper booleans + * + * Properly converted input values are: 'off', 'on', 'false', 'true', '', '0', '1' + * (the last two might not be neccessary, but I'd rather be safe) + * + * If the ini_value doesn't match any of those, the value is returned as-is. + * + * @param string $ini_key the ini_key you need the value of + * @return boolean|mixed + */ + function getBooleanIniValue($ini_key) + { + + $ini_val = ini_get($ini_key); + + switch (strtolower($ini_val)) { + + case 'off': + return false; + break; + case 'on': + return true; + break; + case 'false': + return false; + break; + case 'true': + return true; + break; + case '0': + return false; + break; + case '1': + return true; + break; + case '': + return false; + break; + default: + return $ini_val; + + } + + } + + /** + * sys_get_temp_dir provides some temp dir detection capability + * that is lacking in versions of PHP that do not have the + * sys_get_temp_dir() function + * + * @return string|NULL + */ + function sys_get_temp_dir() + { + // Try to get from environment variable + $vars = array('TMP', 'TMPDIR', 'TEMP'); + foreach ($vars as $var) { + $tmp = getenv($var); + if (!empty($tmp)) { + return realpath($tmp); + } + } + return NULL; + } + + + /** + * A quick function to determine whether we're running on Windows. + * Uses the PHP_OS constant. + * + * @return boolean + */ + function osIsWindows() + { + if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { + return true; + } else { + return false; + } + } + + + /** + * Returns an array of data returned from the UNIX 'id' command + * + * includes uid, username, gid, groupname, and groups (if "exec" + * is enabled). Groups is an array of all the groups the user + * belongs to. Keys are the group ids, values are the group names. + * + * returns FALSE if no suitable function is available to retrieve + * the data + * + * @return array|boolean + */ + function getUnixId() + { + + if ($this->osIsWindows()) { + return false; + } + + $success = false; + + + if (function_exists("exec") && !PhpSecInfo_Test::getBooleanIniValue('safe_mode')) { + $id_raw = exec('id'); + // uid=1000(coj) gid=1000(coj) groups=1000(coj),1001(admin) + preg_match("|uid=(\d+)\((\S+)\)\s+gid=(\d+)\((\S+)\)\s+groups=(.+)|i", + $id_raw, + $matches); + + if (!$matches) { + /** + * for some reason the output from 'id' wasn't as we expected. + * return false so the test doesn't run. + */ + $success = false; + } else { + $id_data = array('uid' => $matches[1], + 'username' => $matches[2], + 'gid' => $matches[3], + 'group' => $matches[4]); + + $groups = array(); + if ($matches[5]) { + $gs = $matches[5]; + $gs = explode(',', $gs); + foreach ($gs as $groupstr) { + if (preg_match("/(\d+)\(([^\)]+)\)/", $groupstr, $subs)) { + $groups[$subs[1]] = $subs[2]; + } else { + $groups[$groupstr] = ''; + } + } + ksort($groups); + } + $id_data['groups'] = $groups; + $success = true; + } + + } + + if (!$success && function_exists("posix_getpwuid") && function_exists("posix_geteuid") + && function_exists('posix_getgrgid') && function_exists('posix_getgroups') + ) { + $data = posix_getpwuid(posix_getuid()); + $id_data['uid'] = $data['uid']; + $id_data['username'] = $data['name']; + $id_data['gid'] = $data['gid']; + //$group_data = posix_getgrgid( posix_getegid() ); + //$id_data['group'] = $group_data['name']; + $id_data['groups'] = array(); + $groups = posix_getgroups(); + foreach ($groups as $gid) { + //$group_data = posix_getgrgid(posix_getgid()); + $id_data['groups'][$gid] = ''; + } + $success = true; + } + + if ($success) { + return $id_data; + } else { + return false; + } + } } diff --git a/plugins/SecurityInfo/PhpSecInfo/Test/Test_Application.php b/plugins/SecurityInfo/PhpSecInfo/Test/Test_Application.php index af7a2f9926..af97310aad 100644 --- a/plugins/SecurityInfo/PhpSecInfo/Test/Test_Application.php +++ b/plugins/SecurityInfo/PhpSecInfo/Test/Test_Application.php @@ -1,7 +1,7 @@ 'http://piwik.org/changelog', - 'PHP' => 'http://php.net/', - ); - - if ($tn = $this->getTestName()) { - return $urls[$tn]; - } else { - return false; - } - } + + /** + * This value is used to group test results together. + * + * For example, all tests related to the mysql lib should be grouped under "mysql." + * + * @var string + */ + var $test_group = 'Application'; + + + /** + * "Application" tests should pretty much be always testable, so the default is just to return true + * + * @return boolean + */ + function isTestable() + { + return Piwik_Http::getTransportMethod() !== null; + } + + function getMoreInfoURL() + { + $urls = array( + 'Piwik' => 'http://piwik.org/changelog', + 'PHP' => 'http://php.net/', + ); + + if ($tn = $this->getTestName()) { + return $urls[$tn]; + } else { + return false; + } + } } diff --git a/plugins/SecurityInfo/PhpSecInfo/Test/Test_Cgi.php b/plugins/SecurityInfo/PhpSecInfo/Test/Test_Cgi.php index 1a4156a3f4..5384bfbf50 100644 --- a/plugins/SecurityInfo/PhpSecInfo/Test/Test_Cgi.php +++ b/plugins/SecurityInfo/PhpSecInfo/Test/Test_Cgi.php @@ -9,8 +9,7 @@ /** * require the main PhpSecInfo class */ -require_once(PHPSECINFO_BASE_DIR.'/Test/Test.php'); - +require_once(PHPSECINFO_BASE_DIR . '/Test/Test.php'); /** @@ -20,42 +19,43 @@ require_once(PHPSECINFO_BASE_DIR.'/Test/Test.php'); class PhpSecInfo_Test_Cgi extends PhpSecInfo_Test { - /** - * This value is used to group test results together. - * - * For example, all tests related to the mysql lib should be grouped under "mysql." - * - * @var string - */ - var $test_group = 'CGI'; - - - - /** - * "CGI" tests should only be run if we're running as a CGI. The best way I could think of - * to test this was to preg against the php_sapi_name() return value. - * - * @return boolean - */ - function isTestable() { - /*if ( preg_match('/^cgi.*$/', PHP_SAPI) ) { - return true; - } else { - return false; - }*/ - return !strncmp(PHP_SAPI, 'cgi', 3); - } - - - /** - * Set the messages for CGI tests - * - */ - function _setMessages() { - parent::_setMessages(); - - $this->setMessageForResult(PHPSECINFO_TEST_RESULT_NOTRUN, 'en', "You don't seem to be using the CGI SAPI"); - - } + /** + * This value is used to group test results together. + * + * For example, all tests related to the mysql lib should be grouped under "mysql." + * + * @var string + */ + var $test_group = 'CGI'; + + + /** + * "CGI" tests should only be run if we're running as a CGI. The best way I could think of + * to test this was to preg against the php_sapi_name() return value. + * + * @return boolean + */ + function isTestable() + { + /*if ( preg_match('/^cgi.*$/', PHP_SAPI) ) { + return true; + } else { + return false; + }*/ + return !strncmp(PHP_SAPI, 'cgi', 3); + } + + + /** + * Set the messages for CGI tests + * + */ + function _setMessages() + { + parent::_setMessages(); + + $this->setMessageForResult(PHPSECINFO_TEST_RESULT_NOTRUN, 'en', "You don't seem to be using the CGI SAPI"); + + } } \ No newline at end of file diff --git a/plugins/SecurityInfo/PhpSecInfo/Test/Test_Core.php b/plugins/SecurityInfo/PhpSecInfo/Test/Test_Core.php index 58230794d2..4778394f6f 100644 --- a/plugins/SecurityInfo/PhpSecInfo/Test/Test_Core.php +++ b/plugins/SecurityInfo/PhpSecInfo/Test/Test_Core.php @@ -1,7 +1,7 @@ */ @@ -9,8 +9,7 @@ /** * require the main PhpSecInfo class */ -require_once(PHPSECINFO_BASE_DIR.'/Test/Test.php'); - +require_once(PHPSECINFO_BASE_DIR . '/Test/Test.php'); /** @@ -19,26 +18,27 @@ require_once(PHPSECINFO_BASE_DIR.'/Test/Test.php'); */ class PhpSecInfo_Test_Core extends PhpSecInfo_Test { - - /** - * This value is used to group test results together. - * - * For example, all tests related to the mysql lib should be grouped under "mysql." - * - * @var string - */ - var $test_group = 'Core'; - - - /** - * "Core" tests should pretty much be always testable, so the default is just to return true - * - * @return boolean - */ - function isTestable() { - - return true; - } - - + + /** + * This value is used to group test results together. + * + * For example, all tests related to the mysql lib should be grouped under "mysql." + * + * @var string + */ + var $test_group = 'Core'; + + + /** + * "Core" tests should pretty much be always testable, so the default is just to return true + * + * @return boolean + */ + function isTestable() + { + + return true; + } + + } \ No newline at end of file diff --git a/plugins/SecurityInfo/PhpSecInfo/Test/Test_Curl.php b/plugins/SecurityInfo/PhpSecInfo/Test/Test_Curl.php index a1e7d57a6f..78deeaea6c 100644 --- a/plugins/SecurityInfo/PhpSecInfo/Test/Test_Curl.php +++ b/plugins/SecurityInfo/PhpSecInfo/Test/Test_Curl.php @@ -9,8 +9,7 @@ /** * require the main PhpSecInfo class */ -require_once(PHPSECINFO_BASE_DIR.'/Test/Test.php'); - +require_once(PHPSECINFO_BASE_DIR . '/Test/Test.php'); /** @@ -20,43 +19,44 @@ require_once(PHPSECINFO_BASE_DIR.'/Test/Test.php'); class PhpSecInfo_Test_Curl extends PhpSecInfo_Test { - /** - * This value is used to group test results together. - * - * For example, all tests related to the mysql lib should be grouped under "mysql." - * - * @var string - */ - var $test_group = 'Curl'; - - - - /** - * "Curl" tests should only be run if the curl extension is installed. We can check - * for this by seeing if the function curl_init() is defined - * - * @return boolean - */ - function isTestable() { -/* if ( function_exists('curl_init') ) { - return true; - } else { - return false; - } -*/ - return extension_loaded('curl'); - } - - - /** - * Set the messages for Curl tests - * - */ - function _setMessages() { - parent::_setMessages(); - - $this->setMessageForResult(PHPSECINFO_TEST_RESULT_NOTRUN, 'en', "CURL support is not enabled in your PHP install"); - - } + /** + * This value is used to group test results together. + * + * For example, all tests related to the mysql lib should be grouped under "mysql." + * + * @var string + */ + var $test_group = 'Curl'; + + + /** + * "Curl" tests should only be run if the curl extension is installed. We can check + * for this by seeing if the function curl_init() is defined + * + * @return boolean + */ + function isTestable() + { + /* if ( function_exists('curl_init') ) { + return true; + } else { + return false; + } + */ + return extension_loaded('curl'); + } + + + /** + * Set the messages for Curl tests + * + */ + function _setMessages() + { + parent::_setMessages(); + + $this->setMessageForResult(PHPSECINFO_TEST_RESULT_NOTRUN, 'en', "CURL support is not enabled in your PHP install"); + + } } \ No newline at end of file diff --git a/plugins/SecurityInfo/PhpSecInfo/Test/Test_Session.php b/plugins/SecurityInfo/PhpSecInfo/Test/Test_Session.php index 5270fa62c4..32185eee89 100644 --- a/plugins/SecurityInfo/PhpSecInfo/Test/Test_Session.php +++ b/plugins/SecurityInfo/PhpSecInfo/Test/Test_Session.php @@ -1,7 +1,7 @@ */ @@ -10,8 +10,7 @@ /** * require the main PhpSecInfo class */ -require_once(PHPSECINFO_BASE_DIR.'/Test/Test.php'); - +require_once(PHPSECINFO_BASE_DIR . '/Test/Test.php'); /** @@ -20,27 +19,28 @@ require_once(PHPSECINFO_BASE_DIR.'/Test/Test.php'); */ class PhpSecInfo_Test_Session extends PhpSecInfo_Test { - - /** - * This value is used to group test results together. - * - * For example, all tests related to the mysql lib should be grouped under "mysql." - * - * @var string - */ - var $test_group = 'Session'; - - - /** - * "Session" tests should pretty much be always testable, so the default is - * just to return true - * - * @return boolean - */ - function isTestable() { - - return true; - } - - + + /** + * This value is used to group test results together. + * + * For example, all tests related to the mysql lib should be grouped under "mysql." + * + * @var string + */ + var $test_group = 'Session'; + + + /** + * "Session" tests should pretty much be always testable, so the default is + * just to return true + * + * @return boolean + */ + function isTestable() + { + + return true; + } + + } \ No newline at end of file diff --git a/plugins/SecurityInfo/PhpSecInfo/Test/Test_Suhosin.php b/plugins/SecurityInfo/PhpSecInfo/Test/Test_Suhosin.php index 1f8883eeba..7228fde5e0 100644 --- a/plugins/SecurityInfo/PhpSecInfo/Test/Test_Suhosin.php +++ b/plugins/SecurityInfo/PhpSecInfo/Test/Test_Suhosin.php @@ -1,7 +1,7 @@ = 0) { - return false; - } - return true; - } - function getMoreInfoURL() { - if ($tn = $this->getTestName()) { - return 'http://www.hardened-php.net/suhosin/index.html'; - } else { - return false; - } - } + /** + * This value is used to group test results together. + * + * For example, all tests related to the mysql lib should be grouped under "mysql." + * + * @var string + */ + var $test_group = 'Suhosin'; + + + /** + * "Suhosin" tests should pretty much be always testable, so the default is just to return true + * + * @return boolean + */ + function isTestable() + { + if (version_compare(PHP_VERSION, '5.3.9') >= 0) { + return false; + } + return true; + } + + function getMoreInfoURL() + { + if ($tn = $this->getTestName()) { + return 'http://www.hardened-php.net/suhosin/index.html'; + } else { + return false; + } + } } diff --git a/plugins/SecurityInfo/SecurityInfo.php b/plugins/SecurityInfo/SecurityInfo.php index c42ad9aeee..487d8a92cd 100644 --- a/plugins/SecurityInfo/SecurityInfo.php +++ b/plugins/SecurityInfo/SecurityInfo.php @@ -1,10 +1,10 @@ Piwik_Translate('SecurityInfo_PluginDescription'), - 'author' => 'Piwik', - 'author_homepage' => 'http://piwik.org/', - 'version' => Piwik_Version::VERSION, - ); - return $info; - } - - function getListHooksRegistered() - { - return array( - 'AdminMenu.add' => 'addMenu', - ); - } - - function addMenu() - { - Piwik_AddAdminSubMenu('CoreAdminHome_MenuDiagnostic', 'SecurityInfo_Security', - array('module' => 'SecurityInfo', 'action' => 'index'), - Piwik::isUserIsSuperUser(), - $order = 10); - } +{ + public function getInformation() + { + $info = array( + 'description' => Piwik_Translate('SecurityInfo_PluginDescription'), + 'author' => 'Piwik', + 'author_homepage' => 'http://piwik.org/', + 'version' => Piwik_Version::VERSION, + ); + return $info; + } + + function getListHooksRegistered() + { + return array( + 'AdminMenu.add' => 'addMenu', + ); + } + + function addMenu() + { + Piwik_AddAdminSubMenu('CoreAdminHome_MenuDiagnostic', 'SecurityInfo_Security', + array('module' => 'SecurityInfo', 'action' => 'index'), + Piwik::isUserIsSuperUser(), + $order = 10); + } } diff --git a/plugins/SecurityInfo/templates/index.tpl b/plugins/SecurityInfo/templates/index.tpl index ac9f8c9254..f40b86f1b7 100644 --- a/plugins/SecurityInfo/templates/index.tpl +++ b/plugins/SecurityInfo/templates/index.tpl @@ -3,27 +3,28 @@

{'SecurityInfo_SecurityInformation'|translate}

{'SecurityInfo_PluginDescription'|translate}

-

Learn more: read our guide Hardening Piwik: How to make Piwik and your web server more secure?

+

Learn more: read our guide Hardening Piwik: How to make Piwik and your web server + more secure?

-{foreach from=$results.test_results key=i item=section} -

{$i}

- - - - - - - - - {foreach from=$section key=j item=test} - - - - - {/foreach} - -
{'SecurityInfo_Test'|translate}{'SecurityInfo_Result'|translate}
{$j}{$test.message}
-{/foreach} + {foreach from=$results.test_results key=i item=section} +

{$i}

+ + + + + + + + + {foreach from=$section key=j item=test} + + + + + {/foreach} + +
{'SecurityInfo_Test'|translate}{'SecurityInfo_Result'|translate}
{$j}{$test.message}
+ {/foreach}
{include file="CoreAdminHome/templates/footer.tpl"} \ No newline at end of file diff --git a/plugins/SitesManager/API.php b/plugins/SitesManager/API.php index 3b5e5e9ec0..a87a35551a 100644 --- a/plugins/SitesManager/API.php +++ b/plugins/SitesManager/API.php @@ -1,1411 +1,1344 @@ Managing Websites in Piwik. * @package Piwik_SitesManager */ -class Piwik_SitesManager_API +class Piwik_SitesManager_API { - static private $instance = null; - const DEFAULT_SEARCH_KEYWORD_PARAMETERS = 'q,query,s,search,searchword,k,keyword'; - - /** - * @return Piwik_SitesManager_API - */ - static public function getInstance() - { - if (self::$instance == null) - { - self::$instance = new self; - } - return self::$instance; - } - - const OPTION_EXCLUDED_IPS_GLOBAL = 'SitesManager_ExcludedIpsGlobal'; - const OPTION_DEFAULT_TIMEZONE = 'SitesManager_DefaultTimezone'; - const OPTION_DEFAULT_CURRENCY = 'SitesManager_DefaultCurrency'; - const OPTION_EXCLUDED_QUERY_PARAMETERS_GLOBAL = 'SitesManager_ExcludedQueryParameters'; - const OPTION_SEARCH_KEYWORD_QUERY_PARAMETERS_GLOBAL = 'SitesManager_SearchKeywordParameters'; - const OPTION_SEARCH_CATEGORY_QUERY_PARAMETERS_GLOBAL = 'SitesManager_SearchCategoryParameters'; - const OPTION_EXCLUDED_USER_AGENTS_GLOBAL = 'SitesManager_ExcludedUserAgentsGlobal'; - const OPTION_SITE_SPECIFIC_USER_AGENT_EXCLUDE_ENABLE = 'SitesManager_EnableSiteSpecificUserAgentExclude'; - const OPTION_KEEP_URL_FRAGMENTS_GLOBAL = 'SitesManager_KeepURLFragmentsGlobal'; - - /** - * Returns the javascript tag for the given idSite. - * This tag must be included on every page to be tracked by Piwik - * - * @param int $idSite - * @param string $customTitle Custom title given to the pageview - * @return string The Javascript tag ready to be included on the HTML pages - */ - public function getJavascriptTag( $idSite, $piwikUrl = '') - { - Piwik::checkUserHasViewAccess($idSite); - - if(empty($piwikUrl)) - { - $piwikUrl = Piwik_Url::getCurrentUrlWithoutFileName(); - } - $piwikUrl = Piwik_Common::sanitizeInputValues($piwikUrl); - - $htmlEncoded = Piwik::getJavascriptCode($idSite, $piwikUrl); - $htmlEncoded = str_replace(array('
','
','
'), '', $htmlEncoded); - return $htmlEncoded; - } - - /** - * Returns all websites belonging to the specified group - * @param string $group Group name - */ - public function getSitesFromGroup($group) - { - Piwik::checkUserIsSuperUser(); - $group = trim($group); - - $sites = Zend_Registry::get('db')->fetchAll("SELECT * - FROM ".Piwik_Common::prefixTable("site")." + static private $instance = null; + const DEFAULT_SEARCH_KEYWORD_PARAMETERS = 'q,query,s,search,searchword,k,keyword'; + + /** + * @return Piwik_SitesManager_API + */ + static public function getInstance() + { + if (self::$instance == null) { + self::$instance = new self; + } + return self::$instance; + } + + const OPTION_EXCLUDED_IPS_GLOBAL = 'SitesManager_ExcludedIpsGlobal'; + const OPTION_DEFAULT_TIMEZONE = 'SitesManager_DefaultTimezone'; + const OPTION_DEFAULT_CURRENCY = 'SitesManager_DefaultCurrency'; + const OPTION_EXCLUDED_QUERY_PARAMETERS_GLOBAL = 'SitesManager_ExcludedQueryParameters'; + const OPTION_SEARCH_KEYWORD_QUERY_PARAMETERS_GLOBAL = 'SitesManager_SearchKeywordParameters'; + const OPTION_SEARCH_CATEGORY_QUERY_PARAMETERS_GLOBAL = 'SitesManager_SearchCategoryParameters'; + const OPTION_EXCLUDED_USER_AGENTS_GLOBAL = 'SitesManager_ExcludedUserAgentsGlobal'; + const OPTION_SITE_SPECIFIC_USER_AGENT_EXCLUDE_ENABLE = 'SitesManager_EnableSiteSpecificUserAgentExclude'; + const OPTION_KEEP_URL_FRAGMENTS_GLOBAL = 'SitesManager_KeepURLFragmentsGlobal'; + + /** + * Returns the javascript tag for the given idSite. + * This tag must be included on every page to be tracked by Piwik + * + * @param int $idSite + * @param string $customTitle Custom title given to the pageview + * @return string The Javascript tag ready to be included on the HTML pages + */ + public function getJavascriptTag($idSite, $piwikUrl = '') + { + Piwik::checkUserHasViewAccess($idSite); + + if (empty($piwikUrl)) { + $piwikUrl = Piwik_Url::getCurrentUrlWithoutFileName(); + } + $piwikUrl = Piwik_Common::sanitizeInputValues($piwikUrl); + + $htmlEncoded = Piwik::getJavascriptCode($idSite, $piwikUrl); + $htmlEncoded = str_replace(array('
', '
', '
'), '', $htmlEncoded); + return $htmlEncoded; + } + + /** + * Returns all websites belonging to the specified group + * @param string $group Group name + */ + public function getSitesFromGroup($group) + { + Piwik::checkUserIsSuperUser(); + $group = trim($group); + + $sites = Zend_Registry::get('db')->fetchAll("SELECT * + FROM " . Piwik_Common::prefixTable("site") . " WHERE `group` = ?", $group); - return $sites; - } - - /** - * Returns the list of website groups, including the empty group - * if no group were specified for some websites - * - * @return array of group names strings - */ - public function getSitesGroups() - { - Piwik::checkUserIsSuperUser(); - $groups = Zend_Registry::get('db')->fetchAll("SELECT DISTINCT `group` FROM ".Piwik_Common::prefixTable("site")); - $cleanedGroups = array(); - foreach($groups as $group) - { - $cleanedGroups[] = $group['group']; - } - $cleanedGroups = array_map('trim', $cleanedGroups); - return $cleanedGroups; - } - - /** - * Returns the website information : name, main_url - * - * @exception if the site ID doesn't exist or the user doesn't have access to it - * @return array - */ - public function getSiteFromId( $idSite ) - { - Piwik::checkUserHasViewAccess( $idSite ); - $site = Zend_Registry::get('db')->fetchRow("SELECT * - FROM ".Piwik_Common::prefixTable("site")." + return $sites; + } + + /** + * Returns the list of website groups, including the empty group + * if no group were specified for some websites + * + * @return array of group names strings + */ + public function getSitesGroups() + { + Piwik::checkUserIsSuperUser(); + $groups = Zend_Registry::get('db')->fetchAll("SELECT DISTINCT `group` FROM " . Piwik_Common::prefixTable("site")); + $cleanedGroups = array(); + foreach ($groups as $group) { + $cleanedGroups[] = $group['group']; + } + $cleanedGroups = array_map('trim', $cleanedGroups); + return $cleanedGroups; + } + + /** + * Returns the website information : name, main_url + * + * @exception if the site ID doesn't exist or the user doesn't have access to it + * @return array + */ + public function getSiteFromId($idSite) + { + Piwik::checkUserHasViewAccess($idSite); + $site = Zend_Registry::get('db')->fetchRow("SELECT * + FROM " . Piwik_Common::prefixTable("site") . " WHERE idsite = ?", $idSite); - return $site; - } - - /** - * Returns the list of alias URLs registered for the given idSite. - * The website ID must be valid when calling this method! - * - * @return array list of alias URLs - */ - private function getAliasSiteUrlsFromId( $idsite ) - { - $db = Zend_Registry::get('db'); - $result = $db->fetchAll("SELECT url - FROM ".Piwik_Common::prefixTable("site_url"). " + return $site; + } + + /** + * Returns the list of alias URLs registered for the given idSite. + * The website ID must be valid when calling this method! + * + * @return array list of alias URLs + */ + private function getAliasSiteUrlsFromId($idsite) + { + $db = Zend_Registry::get('db'); + $result = $db->fetchAll("SELECT url + FROM " . Piwik_Common::prefixTable("site_url") . " WHERE idsite = ?", $idsite); - $urls = array(); - foreach($result as $url) - { - $urls[] = $url['url']; - } - return $urls; - } - - /** - * Returns the list of all URLs registered for the given idSite (main_url + alias URLs). - * - * @exception if the website ID doesn't exist or the user doesn't have access to it - * @return array list of URLs - */ - public function getSiteUrlsFromId( $idSite ) - { - Piwik::checkUserHasViewAccess($idSite); - $site = new Piwik_Site($idSite); - $urls = $this->getAliasSiteUrlsFromId($idSite); - return array_merge(array($site->getMainUrl()), $urls); - } - - /** - * Returns the list of all the website IDs registered. - * Caller must check access. - * - * @return array The list of website IDs - */ - private function getSitesId() - { - $result = Piwik_FetchAll("SELECT idsite FROM ".Piwik_Common::prefixTable('site')); - $idSites = array(); - foreach($result as $idSite) - { - $idSites[] = $idSite['idsite']; - } - return $idSites; - } - - /** - * Returns all websites, requires Super User access - * - * @return array The list of websites, indexed by idsite - */ - public function getAllSites() - { - Piwik::checkUserIsSuperUser(); - $sites = Zend_Registry::get('db')->fetchAll("SELECT * FROM ".Piwik_Common::prefixTable("site")); - $return = array(); - foreach($sites as $site) - { - $return[$site['idsite']] = $site; - } - return $return; - } - - /** - * Returns the list of all the website IDs registered. - * Requires super user access. - * - * @return array The list of website IDs - */ - public function getAllSitesId() - { - Piwik::checkUserIsSuperUser(); - return Piwik_SitesManager_API::getInstance()->getSitesId(); - } - - /** - * Returns the list of the website IDs that received some visits since the specified timestamp. - * Requires super user access. - * - * @return array The list of website IDs - */ - public function getSitesIdWithVisits($timestamp = false) - { - Piwik::checkUserIsSuperUser(); - - if(empty($timestamp)) $timestamp = time(); - + $urls = array(); + foreach ($result as $url) { + $urls[] = $url['url']; + } + return $urls; + } + + /** + * Returns the list of all URLs registered for the given idSite (main_url + alias URLs). + * + * @exception if the website ID doesn't exist or the user doesn't have access to it + * @return array list of URLs + */ + public function getSiteUrlsFromId($idSite) + { + Piwik::checkUserHasViewAccess($idSite); + $site = new Piwik_Site($idSite); + $urls = $this->getAliasSiteUrlsFromId($idSite); + return array_merge(array($site->getMainUrl()), $urls); + } + + /** + * Returns the list of all the website IDs registered. + * Caller must check access. + * + * @return array The list of website IDs + */ + private function getSitesId() + { + $result = Piwik_FetchAll("SELECT idsite FROM " . Piwik_Common::prefixTable('site')); + $idSites = array(); + foreach ($result as $idSite) { + $idSites[] = $idSite['idsite']; + } + return $idSites; + } + + /** + * Returns all websites, requires Super User access + * + * @return array The list of websites, indexed by idsite + */ + public function getAllSites() + { + Piwik::checkUserIsSuperUser(); + $sites = Zend_Registry::get('db')->fetchAll("SELECT * FROM " . Piwik_Common::prefixTable("site")); + $return = array(); + foreach ($sites as $site) { + $return[$site['idsite']] = $site; + } + return $return; + } + + /** + * Returns the list of all the website IDs registered. + * Requires super user access. + * + * @return array The list of website IDs + */ + public function getAllSitesId() + { + Piwik::checkUserIsSuperUser(); + return Piwik_SitesManager_API::getInstance()->getSitesId(); + } + + /** + * Returns the list of the website IDs that received some visits since the specified timestamp. + * Requires super user access. + * + * @return array The list of website IDs + */ + public function getSitesIdWithVisits($timestamp = false) + { + Piwik::checkUserIsSuperUser(); + + if (empty($timestamp)) $timestamp = time(); + $time = Piwik_Date::factory((int)$timestamp)->getDatetime(); - $result = Piwik_FetchAll(" + $result = Piwik_FetchAll(" SELECT idsite FROM - ".Piwik_Common::prefixTable('site')." s + " . Piwik_Common::prefixTable('site') . " s WHERE EXISTS ( SELECT 1 - FROM ".Piwik_Common::prefixTable('log_visit'). " v + FROM " . Piwik_Common::prefixTable('log_visit') . " v WHERE v.idsite = s.idsite AND visit_last_action_time > ? AND visit_last_action_time <= ? LIMIT 1) ", array($time, $now = Piwik_Date::now()->addHour(1)->getDatetime())); - $idSites = array(); - foreach($result as $idSite) - { - $idSites[] = $idSite['idsite']; - } - return $idSites; - } - - - /** - * Returns the list of websites with the 'admin' access for the current user. - * For the superUser it returns all the websites in the database. - * - * @return array for each site, an array of information (idsite, name, main_url, etc.) - */ - public function getSitesWithAdminAccess() - { - $sitesId = $this->getSitesIdWithAdminAccess(); - return $this->getSitesFromIds($sitesId); - } - - /** - * Returns the list of websites with the 'view' access for the current user. - * For the superUser it doesn't return any result because the superUser has admin access on all the websites (use getSitesWithAtLeastViewAccess() instead). - * - * @return array for each site, an array of information (idsite, name, main_url, etc.) - */ - public function getSitesWithViewAccess() - { - $sitesId = $this->getSitesIdWithViewAccess(); - return $this->getSitesFromIds($sitesId); - } - - /** - * Returns the list of websites with the 'view' or 'admin' access for the current user. - * For the superUser it returns all the websites in the database. - * - * @param int $limit Specify max number of sites to return - * @param bool $_restrictSitesToLogin Hack necessary when runnning scheduled tasks, where "Super User" is forced, but sometimes not desired, see #3017 - * @return array array for each site, an array of information (idsite, name, main_url, etc.) - */ - public function getSitesWithAtLeastViewAccess($limit = false, $_restrictSitesToLogin = false) - { - $sitesId = $this->getSitesIdWithAtLeastViewAccess($_restrictSitesToLogin); - return $this->getSitesFromIds($sitesId, $limit); - } - - /** - * Returns the list of websites ID with the 'admin' access for the current user. - * For the superUser it returns all the websites in the database. - * - * @return array list of websites ID - */ - public function getSitesIdWithAdminAccess() - { - $sitesId = Zend_Registry::get('access')->getSitesIdWithAdminAccess(); - return $sitesId; - } - - /** - * Returns the list of websites ID with the 'view' access for the current user. - * For the superUser it doesn't return any result because the superUser has admin access on all the websites (use getSitesIdWithAtLeastViewAccess() instead). - * - * @return array list of websites ID - */ - public function getSitesIdWithViewAccess() - { - return Zend_Registry::get('access')->getSitesIdWithViewAccess(); - } - - /** - * Returns the list of websites ID with the 'view' or 'admin' access for the current user. - * For the superUser it returns all the websites in the database. - * - * @return array list of websites ID - */ - public function getSitesIdWithAtLeastViewAccess($_restrictSitesToLogin = false) - { - if(!empty($_restrictSitesToLogin) - // Very important here to make sure we only proceed when in a scheduled task - // Otherwise anyone could get all websites for a given user - && Piwik_TaskScheduler::isTaskBeingExecuted()) - { - $accessRaw = Piwik_Access::getRawSitesWithSomeViewAccess($_restrictSitesToLogin); - $sitesId = array(); - foreach($accessRaw as $access) - { - $sitesId[] = $access['idsite']; - } - return $sitesId; - } - else - { - return Zend_Registry::get('access')->getSitesIdWithAtLeastViewAccess(); - } - } - - /** - * Returns the list of websites from the ID array in parameters. - * The user access is not checked in this method so the ID have to be accessible by the user! - * - * @param array list of website ID - */ - private function getSitesFromIds( $idSites, $limit = false ) - { - if(count($idSites) === 0) - { - return array(); - } - - if($limit) - { - $limit = "LIMIT " . (int)$limit; - } - - $db = Zend_Registry::get('db'); - $sites = $db->fetchAll("SELECT * - FROM ".Piwik_Common::prefixTable("site")." - WHERE idsite IN (".implode(", ", $idSites).") + $idSites = array(); + foreach ($result as $idSite) { + $idSites[] = $idSite['idsite']; + } + return $idSites; + } + + + /** + * Returns the list of websites with the 'admin' access for the current user. + * For the superUser it returns all the websites in the database. + * + * @return array for each site, an array of information (idsite, name, main_url, etc.) + */ + public function getSitesWithAdminAccess() + { + $sitesId = $this->getSitesIdWithAdminAccess(); + return $this->getSitesFromIds($sitesId); + } + + /** + * Returns the list of websites with the 'view' access for the current user. + * For the superUser it doesn't return any result because the superUser has admin access on all the websites (use getSitesWithAtLeastViewAccess() instead). + * + * @return array for each site, an array of information (idsite, name, main_url, etc.) + */ + public function getSitesWithViewAccess() + { + $sitesId = $this->getSitesIdWithViewAccess(); + return $this->getSitesFromIds($sitesId); + } + + /** + * Returns the list of websites with the 'view' or 'admin' access for the current user. + * For the superUser it returns all the websites in the database. + * + * @param int $limit Specify max number of sites to return + * @param bool $_restrictSitesToLogin Hack necessary when runnning scheduled tasks, where "Super User" is forced, but sometimes not desired, see #3017 + * @return array array for each site, an array of information (idsite, name, main_url, etc.) + */ + public function getSitesWithAtLeastViewAccess($limit = false, $_restrictSitesToLogin = false) + { + $sitesId = $this->getSitesIdWithAtLeastViewAccess($_restrictSitesToLogin); + return $this->getSitesFromIds($sitesId, $limit); + } + + /** + * Returns the list of websites ID with the 'admin' access for the current user. + * For the superUser it returns all the websites in the database. + * + * @return array list of websites ID + */ + public function getSitesIdWithAdminAccess() + { + $sitesId = Zend_Registry::get('access')->getSitesIdWithAdminAccess(); + return $sitesId; + } + + /** + * Returns the list of websites ID with the 'view' access for the current user. + * For the superUser it doesn't return any result because the superUser has admin access on all the websites (use getSitesIdWithAtLeastViewAccess() instead). + * + * @return array list of websites ID + */ + public function getSitesIdWithViewAccess() + { + return Zend_Registry::get('access')->getSitesIdWithViewAccess(); + } + + /** + * Returns the list of websites ID with the 'view' or 'admin' access for the current user. + * For the superUser it returns all the websites in the database. + * + * @return array list of websites ID + */ + public function getSitesIdWithAtLeastViewAccess($_restrictSitesToLogin = false) + { + if (!empty($_restrictSitesToLogin) + // Very important here to make sure we only proceed when in a scheduled task + // Otherwise anyone could get all websites for a given user + && Piwik_TaskScheduler::isTaskBeingExecuted() + ) { + $accessRaw = Piwik_Access::getRawSitesWithSomeViewAccess($_restrictSitesToLogin); + $sitesId = array(); + foreach ($accessRaw as $access) { + $sitesId[] = $access['idsite']; + } + return $sitesId; + } else { + return Zend_Registry::get('access')->getSitesIdWithAtLeastViewAccess(); + } + } + + /** + * Returns the list of websites from the ID array in parameters. + * The user access is not checked in this method so the ID have to be accessible by the user! + * + * @param array list of website ID + */ + private function getSitesFromIds($idSites, $limit = false) + { + if (count($idSites) === 0) { + return array(); + } + + if ($limit) { + $limit = "LIMIT " . (int)$limit; + } + + $db = Zend_Registry::get('db'); + $sites = $db->fetchAll("SELECT * + FROM " . Piwik_Common::prefixTable("site") . " + WHERE idsite IN (" . implode(", ", $idSites) . ") ORDER BY idsite ASC $limit"); - return $sites; - } - - protected function getNormalizedUrls($url) - { - if(strpos($url, 'www.') !== false) - { - $urlBis = str_replace('www.', '', $url); - } - else - { - $urlBis = str_replace('://', '://www.', $url); - } - return array($url, $urlBis); - } - - /** - * Returns the list of websites ID associated with a URL. - * - * @param string $url - * @return array list of websites ID - */ - public function getSitesIdFromSiteUrl( $url ) - { - $url = $this->removeTrailingSlash($url); - list($url, $urlBis) = $this->getNormalizedUrls($url); - if(Piwik::isUserIsSuperUser()) - { - $ids = Zend_Registry::get('db')->fetchAll( - 'SELECT idsite - FROM ' . Piwik_Common::prefixTable('site') . ' + return $sites; + } + + protected function getNormalizedUrls($url) + { + if (strpos($url, 'www.') !== false) { + $urlBis = str_replace('www.', '', $url); + } else { + $urlBis = str_replace('://', '://www.', $url); + } + return array($url, $urlBis); + } + + /** + * Returns the list of websites ID associated with a URL. + * + * @param string $url + * @return array list of websites ID + */ + public function getSitesIdFromSiteUrl($url) + { + $url = $this->removeTrailingSlash($url); + list($url, $urlBis) = $this->getNormalizedUrls($url); + if (Piwik::isUserIsSuperUser()) { + $ids = Zend_Registry::get('db')->fetchAll( + 'SELECT idsite + FROM ' . Piwik_Common::prefixTable('site') . ' WHERE (main_url = ? OR main_url = ?) ' . - 'UNION - SELECT idsite - FROM ' . Piwik_Common::prefixTable('site_url') . ' + 'UNION + SELECT idsite + FROM ' . Piwik_Common::prefixTable('site_url') . ' WHERE (url = ? OR url = ?) ', array($url, $urlBis, $url, $urlBis)); - } - else - { - $login = Piwik::getCurrentUserLogin(); - $ids = Zend_Registry::get('db')->fetchAll( - 'SELECT idsite - FROM ' . Piwik_Common::prefixTable('site') . ' + } else { + $login = Piwik::getCurrentUserLogin(); + $ids = Zend_Registry::get('db')->fetchAll( + 'SELECT idsite + FROM ' . Piwik_Common::prefixTable('site') . ' WHERE (main_url = ? OR main_url = ?)' . - 'AND idsite IN (' . Piwik_Access::getSqlAccessSite('idsite') . ') ' . - 'UNION - SELECT idsite - FROM ' . Piwik_Common::prefixTable('site_url') . ' + 'AND idsite IN (' . Piwik_Access::getSqlAccessSite('idsite') . ') ' . + 'UNION + SELECT idsite + FROM ' . Piwik_Common::prefixTable('site_url') . ' WHERE (url = ? OR url = ?)' . - 'AND idsite IN (' . Piwik_Access::getSqlAccessSite('idsite') . ')', - array($url, $urlBis, $login, $url, $urlBis, $login)); - } - - return $ids; - } - - /** - * Returns all websites with a timezone matching one the specified timezones - * - * @param array $timezones - * @ignore - */ - public function getSitesIdFromTimezones( $timezones ) - { - Piwik::checkUserIsSuperUser(); - $timezones = Piwik::getArrayFromApiParameter($timezones); - $timezones = array_unique($timezones); - $ids = Zend_Registry::get('db')->fetchAll( - 'SELECT idsite - FROM ' . Piwik_Common::prefixTable('site') . ' - WHERE timezone IN ('.Piwik_Common::getSqlStringFieldsArray($timezones).') - ORDER BY idsite ASC', - $timezones); - $return = array(); - foreach($ids as $id) - { - $return[] = $id['idsite']; - } - return $return; - } - - /** - * Add a website. - * Requires Super User access. - * - * The website is defined by a name and an array of URLs. - * @param string Site name - * @param array|string The URLs array must contain at least one URL called the 'main_url' ; - * if several URLs are provided in the array, they will be recorded - * as Alias URLs for this website. - * @param int Is Ecommerce Reporting enabled for this website? - * @param int $sitesearch Whether site search is enabled, 0 or 1 - * @param string $searchKeywordParameters Comma separated list of search keyword parameter names - * @param string $searchCategoryParameters Comma separated list of search category parameter names - * @param string Comma separated list of IPs to exclude from the reports (allows wildcards) - * @param string Timezone string, eg. 'Europe/London' - * @param string Currency, eg. 'EUR' - * @param string Website group identifier - * @param string Date at which the statistics for this website will start. Defaults to today's date in YYYY-MM-DD format - * @param int $keepURLFragments If 1, URL fragments will be kept when tracking. If 2, they - * will be removed. If 0, the default global behavior will be used. - * @see getKeepURLFragmentsGlobal. - * - * @return int the website ID created - */ - public function addSite( $siteName, - $urls, - $ecommerce = null, - $siteSearch = null, - $searchKeywordParameters = null, - $searchCategoryParameters = null, - $excludedIps = null, - $excludedQueryParameters = null, - $timezone = null, - $currency = null, - $group = null, - $startDate = null, - $excludedUserAgents = null, - $keepURLFragments = 0 ) - { - Piwik::checkUserIsSuperUser(); - - $this->checkName($siteName); - $urls = $this->cleanParameterUrls($urls); - $this->checkUrls($urls); - $this->checkAtLeastOneUrl($urls); - $siteSearch = $this->checkSiteSearch($siteSearch); - list($searchKeywordParameters, $searchCategoryParameters ) = $this->checkSiteSearchParameters($searchKeywordParameters, $searchCategoryParameters); - - $keepURLFragments = (int)$keepURLFragments; - self::checkKeepURLFragmentsValue($keepURLFragments); - - $timezone = trim($timezone); - if(empty($timezone)) - { - $timezone = $this->getDefaultTimezone(); - } - $this->checkValidTimezone($timezone); - - if(empty($currency)) - { - $currency = $this->getDefaultCurrency(); - } - $this->checkValidCurrency($currency); - - $db = Zend_Registry::get('db'); - - $url = $urls[0]; - $urls = array_slice($urls, 1); - - $bind = array( 'name' => $siteName, - 'main_url' => $url, - - ); - - $bind['excluded_ips'] = $this->checkAndReturnExcludedIps($excludedIps); - $bind['excluded_parameters'] = $this->checkAndReturnCommaSeparatedStringList($excludedQueryParameters); - $bind['excluded_user_agents'] = $this->checkAndReturnCommaSeparatedStringList($excludedUserAgents); - $bind['keep_url_fragment'] = $keepURLFragments; - $bind['timezone'] = $timezone; - $bind['currency'] = $currency; - $bind['ecommerce'] = (int)$ecommerce; - $bind['sitesearch'] = $siteSearch; - $bind['sitesearch_keyword_parameters'] = $searchKeywordParameters; - $bind['sitesearch_category_parameters'] = $searchCategoryParameters; - $bind['ts_created'] = !is_null($startDate) - ? Piwik_Date::factory($startDate)->getDatetime() - : Piwik_Date::now()->getDatetime(); - - if(!empty($group) - && Piwik::isUserIsSuperUser()) - { - $bind['group'] = trim($group); - } - else - { - $bind['group'] = ""; - } - - $db->insert(Piwik_Common::prefixTable("site"), $bind); - - $idSite = $db->lastInsertId(); - - $this->insertSiteUrls($idSite, $urls); - - // we reload the access list which doesn't yet take in consideration this new website - Zend_Registry::get('access')->reloadAccess(); - $this->postUpdateWebsite($idSite); - - Piwik_PostEvent('SitesManager.addSite', $idSite); - - return (int)$idSite; - } - - private function postUpdateWebsite($idSite) - { - Piwik_Site::clearCache(); - Piwik_Tracker_Cache::regenerateCacheWebsiteAttributes($idSite); - } - - /** - * Delete a website from the database, given its Id. - * - * Requires Super User access. - * - * @param int $idSite - * @throws Exception - */ - public function deleteSite( $idSite ) - { - Piwik::checkUserIsSuperUser(); - - $idSites = Piwik_SitesManager_API::getInstance()->getSitesId(); - if(!in_array($idSite, $idSites)) - { - throw new Exception("website id = $idSite not found"); - } - $nbSites = count($idSites); - if($nbSites == 1) - { - throw new Exception(Piwik_TranslateException("SitesManager_ExceptionDeleteSite")); - } - - $db = Zend_Registry::get('db'); - - $db->query("DELETE FROM ".Piwik_Common::prefixTable("site")." + 'AND idsite IN (' . Piwik_Access::getSqlAccessSite('idsite') . ')', + array($url, $urlBis, $login, $url, $urlBis, $login)); + } + + return $ids; + } + + /** + * Returns all websites with a timezone matching one the specified timezones + * + * @param array $timezones + * @ignore + */ + public function getSitesIdFromTimezones($timezones) + { + Piwik::checkUserIsSuperUser(); + $timezones = Piwik::getArrayFromApiParameter($timezones); + $timezones = array_unique($timezones); + $ids = Zend_Registry::get('db')->fetchAll( + 'SELECT idsite + FROM ' . Piwik_Common::prefixTable('site') . ' + WHERE timezone IN (' . Piwik_Common::getSqlStringFieldsArray($timezones) . ') + ORDER BY idsite ASC', + $timezones); + $return = array(); + foreach ($ids as $id) { + $return[] = $id['idsite']; + } + return $return; + } + + /** + * Add a website. + * Requires Super User access. + * + * The website is defined by a name and an array of URLs. + * @param string Site name + * @param array|string The URLs array must contain at least one URL called the 'main_url' ; + * if several URLs are provided in the array, they will be recorded + * as Alias URLs for this website. + * @param int Is Ecommerce Reporting enabled for this website? + * @param int $sitesearch Whether site search is enabled, 0 or 1 + * @param string $searchKeywordParameters Comma separated list of search keyword parameter names + * @param string $searchCategoryParameters Comma separated list of search category parameter names + * @param string Comma separated list of IPs to exclude from the reports (allows wildcards) + * @param string Timezone string, eg. 'Europe/London' + * @param string Currency, eg. 'EUR' + * @param string Website group identifier + * @param string Date at which the statistics for this website will start. Defaults to today's date in YYYY-MM-DD format + * @param int $keepURLFragments If 1, URL fragments will be kept when tracking. If 2, they + * will be removed. If 0, the default global behavior will be used. + * @see getKeepURLFragmentsGlobal. + * + * @return int the website ID created + */ + public function addSite($siteName, + $urls, + $ecommerce = null, + $siteSearch = null, + $searchKeywordParameters = null, + $searchCategoryParameters = null, + $excludedIps = null, + $excludedQueryParameters = null, + $timezone = null, + $currency = null, + $group = null, + $startDate = null, + $excludedUserAgents = null, + $keepURLFragments = 0) + { + Piwik::checkUserIsSuperUser(); + + $this->checkName($siteName); + $urls = $this->cleanParameterUrls($urls); + $this->checkUrls($urls); + $this->checkAtLeastOneUrl($urls); + $siteSearch = $this->checkSiteSearch($siteSearch); + list($searchKeywordParameters, $searchCategoryParameters) = $this->checkSiteSearchParameters($searchKeywordParameters, $searchCategoryParameters); + + $keepURLFragments = (int)$keepURLFragments; + self::checkKeepURLFragmentsValue($keepURLFragments); + + $timezone = trim($timezone); + if (empty($timezone)) { + $timezone = $this->getDefaultTimezone(); + } + $this->checkValidTimezone($timezone); + + if (empty($currency)) { + $currency = $this->getDefaultCurrency(); + } + $this->checkValidCurrency($currency); + + $db = Zend_Registry::get('db'); + + $url = $urls[0]; + $urls = array_slice($urls, 1); + + $bind = array('name' => $siteName, + 'main_url' => $url, + + ); + + $bind['excluded_ips'] = $this->checkAndReturnExcludedIps($excludedIps); + $bind['excluded_parameters'] = $this->checkAndReturnCommaSeparatedStringList($excludedQueryParameters); + $bind['excluded_user_agents'] = $this->checkAndReturnCommaSeparatedStringList($excludedUserAgents); + $bind['keep_url_fragment'] = $keepURLFragments; + $bind['timezone'] = $timezone; + $bind['currency'] = $currency; + $bind['ecommerce'] = (int)$ecommerce; + $bind['sitesearch'] = $siteSearch; + $bind['sitesearch_keyword_parameters'] = $searchKeywordParameters; + $bind['sitesearch_category_parameters'] = $searchCategoryParameters; + $bind['ts_created'] = !is_null($startDate) + ? Piwik_Date::factory($startDate)->getDatetime() + : Piwik_Date::now()->getDatetime(); + + if (!empty($group) + && Piwik::isUserIsSuperUser() + ) { + $bind['group'] = trim($group); + } else { + $bind['group'] = ""; + } + + $db->insert(Piwik_Common::prefixTable("site"), $bind); + + $idSite = $db->lastInsertId(); + + $this->insertSiteUrls($idSite, $urls); + + // we reload the access list which doesn't yet take in consideration this new website + Zend_Registry::get('access')->reloadAccess(); + $this->postUpdateWebsite($idSite); + + Piwik_PostEvent('SitesManager.addSite', $idSite); + + return (int)$idSite; + } + + private function postUpdateWebsite($idSite) + { + Piwik_Site::clearCache(); + Piwik_Tracker_Cache::regenerateCacheWebsiteAttributes($idSite); + } + + /** + * Delete a website from the database, given its Id. + * + * Requires Super User access. + * + * @param int $idSite + * @throws Exception + */ + public function deleteSite($idSite) + { + Piwik::checkUserIsSuperUser(); + + $idSites = Piwik_SitesManager_API::getInstance()->getSitesId(); + if (!in_array($idSite, $idSites)) { + throw new Exception("website id = $idSite not found"); + } + $nbSites = count($idSites); + if ($nbSites == 1) { + throw new Exception(Piwik_TranslateException("SitesManager_ExceptionDeleteSite")); + } + + $db = Zend_Registry::get('db'); + + $db->query("DELETE FROM " . Piwik_Common::prefixTable("site") . " WHERE idsite = ?", $idSite); - - $db->query("DELETE FROM ".Piwik_Common::prefixTable("site_url")." + + $db->query("DELETE FROM " . Piwik_Common::prefixTable("site_url") . " WHERE idsite = ?", $idSite); - - $db->query("DELETE FROM ".Piwik_Common::prefixTable("access")." + + $db->query("DELETE FROM " . Piwik_Common::prefixTable("access") . " WHERE idsite = ?", $idSite); - // we do not delete logs here on purpose (you can run these queries on the log_ tables to delete all data) - Piwik_Tracker_Cache::deleteCacheWebsiteAttributes($idSite); - - Piwik_PostEvent('SitesManager.deleteSite', $idSite); - } - - - /** - * Checks that the array has at least one element - * - * @param array $urls - * @throws Exception - */ - private function checkAtLeastOneUrl( $urls ) - { - if(!is_array($urls) - || count($urls) == 0) - { - throw new Exception(Piwik_TranslateException("SitesManager_ExceptionNoUrl")); - } - } - - private function checkValidTimezone($timezone) - { - $timezones = $this->getTimezonesList(); - foreach(array_values($timezones) as $cities) - { - foreach($cities as $timezoneId => $city) - { - if($timezoneId == $timezone) - { - return true; - } - } - } - throw new Exception(Piwik_TranslateException('SitesManager_ExceptionInvalidTimezone', array($timezone))); - } - - private function checkValidCurrency($currency) - { - if(!in_array($currency, array_keys($this->getCurrencyList()))) - { - throw new Exception(Piwik_TranslateException('SitesManager_ExceptionInvalidCurrency', array($currency, "USD, EUR, etc."))); - } - } - - /** - * Checks that the submitted IPs (comma separated list) are valid - * Returns the cleaned up IPs - * - * @param string $excludedIps Comma separated list of IP addresses - * @throws Exception - * @return array of IPs - */ - private function checkAndReturnExcludedIps($excludedIps) - { - if(empty($excludedIps)) - { - return ''; - } - $ips = explode(',', $excludedIps); - $ips = array_map('trim', $ips); - $ips = array_filter($ips, 'strlen'); - foreach($ips as $ip) - { - if(!$this->isValidIp($ip)) - { - throw new Exception(Piwik_TranslateException('SitesManager_ExceptionInvalidIPFormat', array($ip, "1.2.3.4, 1.2.3.*, or 1.2.3.4/5"))); - } - } - $ips = implode(',', $ips); - return $ips; - } - /** - * Add a list of alias Urls to the given idSite - * - * If some URLs given in parameter are already recorded as alias URLs for this website, - * they won't be duplicated. The 'main_url' of the website won't be affected by this method. - * - * @return int the number of inserted URLs - */ - public function addSiteAliasUrls( $idSite, $urls) - { - Piwik::checkUserHasAdminAccess( $idSite ); - - $urls = $this->cleanParameterUrls($urls); - $this->checkUrls($urls); - - $urlsInit = $this->getSiteUrlsFromId($idSite); - $toInsert = array_diff($urls, $urlsInit); - $this->insertSiteUrls($idSite, $toInsert); - $this->postUpdateWebsite($idSite); - - return count($toInsert); - } - - /** - * Get the start and end IP addresses for an IP address range - * - * @param string $ipRange IP address range in presentation format - * @return array|false Array( low, high ) IP addresses in presentation format; or false if error - */ - public function getIpsForRange($ipRange) - { - $range = Piwik_IP::getIpsForRange($ipRange); - if($range === false) - { - return false; - } - - return array( Piwik_IP::N2P($range[0]), Piwik_IP::N2P($range[1]) ); - } - - /** - * Sets IPs to be excluded from all websites. IPs can contain wildcards. - * Will also apply to websites created in the future. - * - * @param string Comma separated list of IPs to exclude from being tracked (allows wildcards) - * @return bool - */ - public function setGlobalExcludedIps($excludedIps) - { - Piwik::checkUserIsSuperUser(); - $excludedIps = $this->checkAndReturnExcludedIps($excludedIps); - Piwik_SetOption(self::OPTION_EXCLUDED_IPS_GLOBAL, $excludedIps); - Piwik_Tracker_Cache::deleteTrackerCache(); - return true; - } - - /** - * Sets Site Search keyword/category parameter names, to be used on websites which have not specified these values - * Expects Comma separated list of query params names - * - * @param string - * @param string - * @return bool - */ - public function setGlobalSearchParameters($searchKeywordParameters, $searchCategoryParameters) - { - Piwik::checkUserIsSuperUser(); - Piwik_SetOption(self::OPTION_SEARCH_KEYWORD_QUERY_PARAMETERS_GLOBAL, $searchKeywordParameters); - Piwik_SetOption(self::OPTION_SEARCH_CATEGORY_QUERY_PARAMETERS_GLOBAL, $searchCategoryParameters); - Piwik_Tracker_Cache::deleteTrackerCache(); - return true; - } - - /** - * @return string Comma separated list of URL parameters - */ - public function getSearchKeywordParametersGlobal() - { - Piwik::checkUserHasSomeAdminAccess(); - $names = Piwik_GetOption(self::OPTION_SEARCH_KEYWORD_QUERY_PARAMETERS_GLOBAL); - if($names === false) { - $names = self::DEFAULT_SEARCH_KEYWORD_PARAMETERS; - } - if(empty($names)) { - $names = ''; - } - return $names; - } - - /** - * @return string Comma separated list of URL parameters - */ - public function getSearchCategoryParametersGlobal() - { - Piwik::checkUserHasSomeAdminAccess(); - return Piwik_GetOption(self::OPTION_SEARCH_CATEGORY_QUERY_PARAMETERS_GLOBAL); - } - - /** - * Returns the list of URL query parameters that are excluded from all websites - * - * @return string Comma separated list of URL parameters - */ - public function getExcludedQueryParametersGlobal() - { - Piwik::checkUserHasSomeViewAccess(); - return Piwik_GetOption(self::OPTION_EXCLUDED_QUERY_PARAMETERS_GLOBAL); - } - - /** - * Returns the list of user agent substrings to look for when excluding visits for - * all websites. If a visitor's user agent string contains one of these substrings, - * their visits will not be included. - * - * @return string Comma separated list of strings. - */ - public function getExcludedUserAgentsGlobal() - { - Piwik::checkUserHasSomeAdminAccess(); - return Piwik_GetOption(self::OPTION_EXCLUDED_USER_AGENTS_GLOBAL); - } - - /** - * Sets list of user agent substrings to look for when excluding visits. For more info, - * @see getExcludedUserAgentsGlobal. - * - * @param string $excludedUserAgents Comma separated list of strings. Each element is trimmed, - * and empty strings are removed. - */ - public function setGlobalExcludedUserAgents( $excludedUserAgents ) - { - Piwik::checkUserIsSuperUser(); - - // update option - $excludedUserAgents = $this->checkAndReturnCommaSeparatedStringList($excludedUserAgents); - Piwik_SetOption(self::OPTION_EXCLUDED_USER_AGENTS_GLOBAL, $excludedUserAgents); - - // make sure tracker cache will reflect change - Piwik_Tracker_Cache::deleteTrackerCache(); - } - - /** - * Returns true if site-specific user agent exclusion has been enabled. If it hasn't, - * only the global user agent substrings (see @setGlobalExcludedUserAgents) will be used. - * - * @return bool - */ - public function isSiteSpecificUserAgentExcludeEnabled() - { - Piwik::checkUserHasSomeAdminAccess(); - return (bool)Piwik_GetOption(self::OPTION_SITE_SPECIFIC_USER_AGENT_EXCLUDE_ENABLE); - } - - /** - * Sets whether it should be allowed to exclude different user agents for different - * websites. - * - * @param bool $enabled - */ - public function setSiteSpecificUserAgentExcludeEnabled( $enabled ) - { - Piwik::checkUserIsSuperUser(); - - // update option - Piwik_SetOption(self::OPTION_SITE_SPECIFIC_USER_AGENT_EXCLUDE_ENABLE, $enabled); - - // make sure tracker cache will reflect change - Piwik_Tracker_Cache::deleteTrackerCache(); - } - - /** - * Returns true if the default behavior is to keep URL fragments when tracking, - * false if otherwise. - * - * @return bool - */ - public function getKeepURLFragmentsGlobal() - { - Piwik::checkUserHasSomeViewAccess(); - return (bool)Piwik_GetOption(self::OPTION_KEEP_URL_FRAGMENTS_GLOBAL); - } - - /** - * Sets whether the default behavior should be to keep URL fragments when - * tracking or not. - * - * @param $enabled bool If true, the default behavior will be to keep URL - * fragments when tracking. If false, the default - * behavior will be to remove them. - */ - public function setKeepURLFragmentsGlobal( $enabled ) - { - Piwik::checkUserIsSuperUser(); - - // update option - Piwik_SetOption(self::OPTION_KEEP_URL_FRAGMENTS_GLOBAL, $enabled); - - // make sure tracker cache will reflect change - Piwik_Tracker_Cache::deleteTrackerCache(); - } - - /** - * Sets list of URL query parameters to be excluded on all websites. - * Will also apply to websites created in the future. - * - * @param string Comma separated list of URL query parameters to exclude from URLs - * @return bool - */ - public function setGlobalExcludedQueryParameters($excludedQueryParameters) - { - Piwik::checkUserIsSuperUser(); - $excludedQueryParameters = $this->checkAndReturnCommaSeparatedStringList($excludedQueryParameters); - Piwik_SetOption(self::OPTION_EXCLUDED_QUERY_PARAMETERS_GLOBAL, $excludedQueryParameters); - Piwik_Tracker_Cache::deleteTrackerCache(); - return true; - } - - /** - * Returns the list of IPs that are excluded from all websites - * - * @return string Comma separated list of IPs - */ - public function getExcludedIpsGlobal() - { - Piwik::checkUserHasSomeAdminAccess(); - return Piwik_GetOption(self::OPTION_EXCLUDED_IPS_GLOBAL); - } - - /** - * Returns the default currency that will be set when creating a website through the API. - * - * @return string Currency ID eg. 'USD' - */ - public function getDefaultCurrency() - { - Piwik::checkUserHasSomeAdminAccess(); - $defaultCurrency = Piwik_GetOption(self::OPTION_DEFAULT_CURRENCY); - if($defaultCurrency) - { - return $defaultCurrency; - } - return 'USD'; - } - - /** - * Sets the default currency that will be used when creating websites - * - * @param string $defaultCurrency Currency code, eg. 'USD' - * @return bool - */ - public function setDefaultCurrency($defaultCurrency) - { - Piwik::checkUserIsSuperUser(); - $this->checkValidCurrency($defaultCurrency); - Piwik_SetOption(self::OPTION_DEFAULT_CURRENCY, $defaultCurrency); - return true; - } - - /** - * Returns the default timezone that will be set when creating a website through the API. - * Via the UI, if the default timezone is not UTC, it will be pre-selected in the drop down - * - * @return string Timezone eg. UTC+7 or Europe/Paris - */ - public function getDefaultTimezone() - { - $defaultTimezone = Piwik_GetOption(self::OPTION_DEFAULT_TIMEZONE); - if($defaultTimezone) - { - return $defaultTimezone; - } - return 'UTC'; - } - - /** - * Sets the default timezone that will be used when creating websites - * - * @param string $defaultTimezone Timezone string eg. Europe/Paris or UTC+8 - * @return bool - */ - public function setDefaultTimezone($defaultTimezone) - { - Piwik::checkUserIsSuperUser(); - $this->checkValidTimezone($defaultTimezone); - Piwik_SetOption(self::OPTION_DEFAULT_TIMEZONE, $defaultTimezone); - return true; - } - - /** - * Update an existing website. - * If only one URL is specified then only the main url will be updated. - * If several URLs are specified, both the main URL and the alias URLs will be updated. - * - * @param int $idSite website ID defining the website to edit - * @param string $siteName website name - * @param string|array $urls the website URLs - * @param int $ecommerce Whether Ecommerce is enabled, 0 or 1 - * @param int $sitesearch Whether site search is enabled, 0 or 1 - * @param string $searchKeywordParameters Comma separated list of search keyword parameter names - * @param string $searchCategoryParameters Comma separated list of search category parameter names - * @param string $excludedIps Comma separated list of IPs to exclude from being tracked (allows wildcards) - * @param null $excludedQueryParameters - * @param string $timezone Timezone - * @param string $currency Currency code - * @param string $group Group name where this website belongs - * @param string $startDate Date at which the statistics for this website will start. Defaults to today's date in YYYY-MM-DD format - * @param int|null $keepURLFragments If 1, URL fragments will be kept when tracking. If 2, they - * will be removed. If 0, the default global behavior will be used. - * @see getKeepURLFragmentsGlobal. If null, the existing value will - * not be modified. - * - * @throws Exception - * @return bool true on success - */ - public function updateSite( $idSite, - $siteName, - $urls = null, - $ecommerce = null, - $siteSearch = null, - $searchKeywordParameters = null, - $searchCategoryParameters = null, - $excludedIps = null, - $excludedQueryParameters = null, - $timezone = null, - $currency = null, - $group = null, - $startDate = null, - $excludedUserAgents = null, - $keepURLFragments = null) - { - Piwik::checkUserHasAdminAccess($idSite); - - $idSites = Piwik_SitesManager_API::getInstance()->getSitesId(); - if(!in_array($idSite, $idSites)) - { - throw new Exception("website id = $idSite not found"); - } - - $this->checkName($siteName); - - // Build the SQL UPDATE based on specified updates to perform - $bind = array(); - if(!is_null($urls)) - { - $urls = $this->cleanParameterUrls($urls); - $this->checkUrls($urls); - $this->checkAtLeastOneUrl($urls); - $url = $urls[0]; - $bind['main_url'] = $url; - } - - if(!is_null($currency)) - { - $currency = trim($currency); - $this->checkValidCurrency($currency); - $bind['currency'] = $currency; - } - if(!is_null($timezone)) - { - $timezone = trim($timezone); - $this->checkValidTimezone($timezone); - $bind['timezone'] = $timezone; - } - if(!is_null($group) - && Piwik::isUserIsSuperUser()) - { - $bind['group'] = trim($group); - } - if(!is_null($ecommerce)) - { - $bind['ecommerce'] = (int)(bool)$ecommerce; - } - if(!is_null($startDate)) - { - $bind['ts_created'] = Piwik_Date::factory($startDate)->getDatetime(); - } - $bind['excluded_ips'] = $this->checkAndReturnExcludedIps($excludedIps); - $bind['excluded_parameters'] = $this->checkAndReturnCommaSeparatedStringList($excludedQueryParameters); - $bind['excluded_user_agents'] = $this->checkAndReturnCommaSeparatedStringList($excludedUserAgents); - - if (!is_null($keepURLFragments)) - { - $keepURLFragments = (int)$keepURLFragments; - self::checkKeepURLFragmentsValue($keepURLFragments); - - $bind['keep_url_fragment'] = $keepURLFragments; - } - - $bind['sitesearch'] = $this->checkSiteSearch($siteSearch); - list($searchKeywordParameters, $searchCategoryParameters ) = $this->checkSiteSearchParameters($searchKeywordParameters, $searchCategoryParameters); - $bind['sitesearch_keyword_parameters'] = $searchKeywordParameters; - $bind['sitesearch_category_parameters'] = $searchCategoryParameters; - - $bind['name'] = $siteName; - $db = Zend_Registry::get('db'); - $db->update(Piwik_Common::prefixTable("site"), - $bind, - "idsite = $idSite" - ); - - // we now update the main + alias URLs - $this->deleteSiteAliasUrls($idSite); - if(count($urls) > 1) - { - $this->addSiteAliasUrls($idSite, array_slice($urls,1)); - } - $this->postUpdateWebsite($idSite); - - Piwik_PostEvent('SitesManager.updateSite', $idSite); - } - - private function checkAndReturnCommaSeparatedStringList($parameters) - { - $parameters = trim($parameters); - if(empty($parameters)) - { - return ''; - } - - $parameters = explode(',', $parameters); - $parameters = array_map('trim', $parameters); - $parameters = array_filter($parameters, 'strlen'); - $parameters = array_unique($parameters); - return implode(',', $parameters); - } - - /** - * Returns the list of supported currencies - * @see getCurrencySymbols() - * @return array ( currencyId => currencyName) - */ - public function getCurrencyList() - { - $currencies = Piwik::getCurrencyList(); - return array_map(create_function('$a', 'return $a[1]." (".$a[0].")";'), $currencies); - } - - /** - * Returns the list of currency symbols - * @see getCurrencyList() - * @return array( currencyId => currencySymbol ) - */ - public function getCurrencySymbols() - { - $currencies = Piwik::getCurrencyList(); - return array_map(create_function('$a', 'return $a[0];'), $currencies); - } - - /** - * Returns the list of timezones supported. - * Used for addSite and updateSite - * - * @TODO NOT COMPATIBLE WITH API RESPONSE AUTO BUILDER - * - * @return array of timezone strings - */ - public function getTimezonesList() - { - if(!Piwik::isTimezoneSupportEnabled()) - { - return array('UTC' => $this->getTimezonesListUTCOffsets()); - } - - $continents = array( 'Africa', 'America', 'Antarctica', 'Arctic', 'Asia', 'Atlantic', 'Australia', 'Europe', 'Indian', 'Pacific'); - $timezones = timezone_identifiers_list(); - - $return = array(); - foreach($timezones as $timezone) - { - // filter out timezones not recognized by strtotime() - // @see http://bugs.php.net/46111 - $testDate = '2008-09-18 13:00:00 ' . $timezone; - if(!strtotime($testDate)) - { - continue; - } - - $timezoneExploded = explode('/', $timezone); - $continent = $timezoneExploded[0]; - - // only display timezones that are grouped by continent - if(!in_array($continent, $continents)) - { - continue; - } - $city = $timezoneExploded[1]; - if(!empty($timezoneExploded[2])) - { - $city .= ' - '.$timezoneExploded[2]; - } - $city = str_replace('_', ' ', $city); - $return[$continent][$timezone] = $city; - } - - foreach($continents as $continent) - { - if(!empty($return[$continent])) - { - ksort($return[$continent]); - } - } - - $return['UTC'] = $this->getTimezonesListUTCOffsets(); - return $return; - } - - private function getTimezonesListUTCOffsets() - { - // manually add the UTC offsets - $GmtOffsets = array (-12, -11.5, -11, -10.5, -10, -9.5, -9, -8.5, -8, -7.5, -7, -6.5, -6, -5.5, -5, -4.5, -4, -3.5, -3, -2.5, -2, -1.5, -1, -0.5, - 0, 0.5, 1, 1.5, 2, 2.5, 3, 3.5, 4, 4.5, 5, 5.5, 5.75, 6, 6.5, 7, 7.5, 8, 8.5, 8.75, 9, 9.5, 10, 10.5, 11, 11.5, 12, 12.75, 13, 13.75, 14); - - $return = array(); - foreach($GmtOffsets as $offset) - { - if($offset > 0) - { - $offset = '+'.$offset; - } - elseif($offset == 0) - { - $offset = ''; - } - $offset = 'UTC' . $offset; - $offsetName = str_replace(array('.25','.5','.75'), array(':15',':30',':45'), $offset); - $return[$offset] = $offsetName; - } - return $return; - } - - /** - * Returns the list of unique timezones from all configured sites. - * - * @return array ( string ) - */ - public function getUniqueSiteTimezones() - { - Piwik::checkUserIsSuperUser(); - $results = Piwik_FetchAll("SELECT distinct timezone FROM ".Piwik_Common::prefixTable('site')); - $timezones = array(); - foreach($results as $result) - { - $timezones[] = $result['timezone']; - } - return $timezones; - } - - /** - * Insert the list of alias URLs for the website. - * The URLs must not exist already for this website! - */ - private function insertSiteUrls($idSite, $urls) - { - if(count($urls) != 0) - { - $db = Zend_Registry::get('db'); - foreach($urls as $url) - { - $db->insert(Piwik_Common::prefixTable("site_url"), array( - 'idsite' => $idSite, - 'url' => $url - ) - ); - } - } - } - - /** - * Delete all the alias URLs for the given idSite. - */ - private function deleteSiteAliasUrls($idsite) - { - $db = Zend_Registry::get('db'); - $db->query("DELETE FROM ".Piwik_Common::prefixTable("site_url") ." + // we do not delete logs here on purpose (you can run these queries on the log_ tables to delete all data) + Piwik_Tracker_Cache::deleteCacheWebsiteAttributes($idSite); + + Piwik_PostEvent('SitesManager.deleteSite', $idSite); + } + + + /** + * Checks that the array has at least one element + * + * @param array $urls + * @throws Exception + */ + private function checkAtLeastOneUrl($urls) + { + if (!is_array($urls) + || count($urls) == 0 + ) { + throw new Exception(Piwik_TranslateException("SitesManager_ExceptionNoUrl")); + } + } + + private function checkValidTimezone($timezone) + { + $timezones = $this->getTimezonesList(); + foreach (array_values($timezones) as $cities) { + foreach ($cities as $timezoneId => $city) { + if ($timezoneId == $timezone) { + return true; + } + } + } + throw new Exception(Piwik_TranslateException('SitesManager_ExceptionInvalidTimezone', array($timezone))); + } + + private function checkValidCurrency($currency) + { + if (!in_array($currency, array_keys($this->getCurrencyList()))) { + throw new Exception(Piwik_TranslateException('SitesManager_ExceptionInvalidCurrency', array($currency, "USD, EUR, etc."))); + } + } + + /** + * Checks that the submitted IPs (comma separated list) are valid + * Returns the cleaned up IPs + * + * @param string $excludedIps Comma separated list of IP addresses + * @throws Exception + * @return array of IPs + */ + private function checkAndReturnExcludedIps($excludedIps) + { + if (empty($excludedIps)) { + return ''; + } + $ips = explode(',', $excludedIps); + $ips = array_map('trim', $ips); + $ips = array_filter($ips, 'strlen'); + foreach ($ips as $ip) { + if (!$this->isValidIp($ip)) { + throw new Exception(Piwik_TranslateException('SitesManager_ExceptionInvalidIPFormat', array($ip, "1.2.3.4, 1.2.3.*, or 1.2.3.4/5"))); + } + } + $ips = implode(',', $ips); + return $ips; + } + + /** + * Add a list of alias Urls to the given idSite + * + * If some URLs given in parameter are already recorded as alias URLs for this website, + * they won't be duplicated. The 'main_url' of the website won't be affected by this method. + * + * @return int the number of inserted URLs + */ + public function addSiteAliasUrls($idSite, $urls) + { + Piwik::checkUserHasAdminAccess($idSite); + + $urls = $this->cleanParameterUrls($urls); + $this->checkUrls($urls); + + $urlsInit = $this->getSiteUrlsFromId($idSite); + $toInsert = array_diff($urls, $urlsInit); + $this->insertSiteUrls($idSite, $toInsert); + $this->postUpdateWebsite($idSite); + + return count($toInsert); + } + + /** + * Get the start and end IP addresses for an IP address range + * + * @param string $ipRange IP address range in presentation format + * @return array|false Array( low, high ) IP addresses in presentation format; or false if error + */ + public function getIpsForRange($ipRange) + { + $range = Piwik_IP::getIpsForRange($ipRange); + if ($range === false) { + return false; + } + + return array(Piwik_IP::N2P($range[0]), Piwik_IP::N2P($range[1])); + } + + /** + * Sets IPs to be excluded from all websites. IPs can contain wildcards. + * Will also apply to websites created in the future. + * + * @param string Comma separated list of IPs to exclude from being tracked (allows wildcards) + * @return bool + */ + public function setGlobalExcludedIps($excludedIps) + { + Piwik::checkUserIsSuperUser(); + $excludedIps = $this->checkAndReturnExcludedIps($excludedIps); + Piwik_SetOption(self::OPTION_EXCLUDED_IPS_GLOBAL, $excludedIps); + Piwik_Tracker_Cache::deleteTrackerCache(); + return true; + } + + /** + * Sets Site Search keyword/category parameter names, to be used on websites which have not specified these values + * Expects Comma separated list of query params names + * + * @param string + * @param string + * @return bool + */ + public function setGlobalSearchParameters($searchKeywordParameters, $searchCategoryParameters) + { + Piwik::checkUserIsSuperUser(); + Piwik_SetOption(self::OPTION_SEARCH_KEYWORD_QUERY_PARAMETERS_GLOBAL, $searchKeywordParameters); + Piwik_SetOption(self::OPTION_SEARCH_CATEGORY_QUERY_PARAMETERS_GLOBAL, $searchCategoryParameters); + Piwik_Tracker_Cache::deleteTrackerCache(); + return true; + } + + /** + * @return string Comma separated list of URL parameters + */ + public function getSearchKeywordParametersGlobal() + { + Piwik::checkUserHasSomeAdminAccess(); + $names = Piwik_GetOption(self::OPTION_SEARCH_KEYWORD_QUERY_PARAMETERS_GLOBAL); + if ($names === false) { + $names = self::DEFAULT_SEARCH_KEYWORD_PARAMETERS; + } + if (empty($names)) { + $names = ''; + } + return $names; + } + + /** + * @return string Comma separated list of URL parameters + */ + public function getSearchCategoryParametersGlobal() + { + Piwik::checkUserHasSomeAdminAccess(); + return Piwik_GetOption(self::OPTION_SEARCH_CATEGORY_QUERY_PARAMETERS_GLOBAL); + } + + /** + * Returns the list of URL query parameters that are excluded from all websites + * + * @return string Comma separated list of URL parameters + */ + public function getExcludedQueryParametersGlobal() + { + Piwik::checkUserHasSomeViewAccess(); + return Piwik_GetOption(self::OPTION_EXCLUDED_QUERY_PARAMETERS_GLOBAL); + } + + /** + * Returns the list of user agent substrings to look for when excluding visits for + * all websites. If a visitor's user agent string contains one of these substrings, + * their visits will not be included. + * + * @return string Comma separated list of strings. + */ + public function getExcludedUserAgentsGlobal() + { + Piwik::checkUserHasSomeAdminAccess(); + return Piwik_GetOption(self::OPTION_EXCLUDED_USER_AGENTS_GLOBAL); + } + + /** + * Sets list of user agent substrings to look for when excluding visits. For more info, + * @see getExcludedUserAgentsGlobal. + * + * @param string $excludedUserAgents Comma separated list of strings. Each element is trimmed, + * and empty strings are removed. + */ + public function setGlobalExcludedUserAgents($excludedUserAgents) + { + Piwik::checkUserIsSuperUser(); + + // update option + $excludedUserAgents = $this->checkAndReturnCommaSeparatedStringList($excludedUserAgents); + Piwik_SetOption(self::OPTION_EXCLUDED_USER_AGENTS_GLOBAL, $excludedUserAgents); + + // make sure tracker cache will reflect change + Piwik_Tracker_Cache::deleteTrackerCache(); + } + + /** + * Returns true if site-specific user agent exclusion has been enabled. If it hasn't, + * only the global user agent substrings (see @setGlobalExcludedUserAgents) will be used. + * + * @return bool + */ + public function isSiteSpecificUserAgentExcludeEnabled() + { + Piwik::checkUserHasSomeAdminAccess(); + return (bool)Piwik_GetOption(self::OPTION_SITE_SPECIFIC_USER_AGENT_EXCLUDE_ENABLE); + } + + /** + * Sets whether it should be allowed to exclude different user agents for different + * websites. + * + * @param bool $enabled + */ + public function setSiteSpecificUserAgentExcludeEnabled($enabled) + { + Piwik::checkUserIsSuperUser(); + + // update option + Piwik_SetOption(self::OPTION_SITE_SPECIFIC_USER_AGENT_EXCLUDE_ENABLE, $enabled); + + // make sure tracker cache will reflect change + Piwik_Tracker_Cache::deleteTrackerCache(); + } + + /** + * Returns true if the default behavior is to keep URL fragments when tracking, + * false if otherwise. + * + * @return bool + */ + public function getKeepURLFragmentsGlobal() + { + Piwik::checkUserHasSomeViewAccess(); + return (bool)Piwik_GetOption(self::OPTION_KEEP_URL_FRAGMENTS_GLOBAL); + } + + /** + * Sets whether the default behavior should be to keep URL fragments when + * tracking or not. + * + * @param $enabled bool If true, the default behavior will be to keep URL + * fragments when tracking. If false, the default + * behavior will be to remove them. + */ + public function setKeepURLFragmentsGlobal($enabled) + { + Piwik::checkUserIsSuperUser(); + + // update option + Piwik_SetOption(self::OPTION_KEEP_URL_FRAGMENTS_GLOBAL, $enabled); + + // make sure tracker cache will reflect change + Piwik_Tracker_Cache::deleteTrackerCache(); + } + + /** + * Sets list of URL query parameters to be excluded on all websites. + * Will also apply to websites created in the future. + * + * @param string Comma separated list of URL query parameters to exclude from URLs + * @return bool + */ + public function setGlobalExcludedQueryParameters($excludedQueryParameters) + { + Piwik::checkUserIsSuperUser(); + $excludedQueryParameters = $this->checkAndReturnCommaSeparatedStringList($excludedQueryParameters); + Piwik_SetOption(self::OPTION_EXCLUDED_QUERY_PARAMETERS_GLOBAL, $excludedQueryParameters); + Piwik_Tracker_Cache::deleteTrackerCache(); + return true; + } + + /** + * Returns the list of IPs that are excluded from all websites + * + * @return string Comma separated list of IPs + */ + public function getExcludedIpsGlobal() + { + Piwik::checkUserHasSomeAdminAccess(); + return Piwik_GetOption(self::OPTION_EXCLUDED_IPS_GLOBAL); + } + + /** + * Returns the default currency that will be set when creating a website through the API. + * + * @return string Currency ID eg. 'USD' + */ + public function getDefaultCurrency() + { + Piwik::checkUserHasSomeAdminAccess(); + $defaultCurrency = Piwik_GetOption(self::OPTION_DEFAULT_CURRENCY); + if ($defaultCurrency) { + return $defaultCurrency; + } + return 'USD'; + } + + /** + * Sets the default currency that will be used when creating websites + * + * @param string $defaultCurrency Currency code, eg. 'USD' + * @return bool + */ + public function setDefaultCurrency($defaultCurrency) + { + Piwik::checkUserIsSuperUser(); + $this->checkValidCurrency($defaultCurrency); + Piwik_SetOption(self::OPTION_DEFAULT_CURRENCY, $defaultCurrency); + return true; + } + + /** + * Returns the default timezone that will be set when creating a website through the API. + * Via the UI, if the default timezone is not UTC, it will be pre-selected in the drop down + * + * @return string Timezone eg. UTC+7 or Europe/Paris + */ + public function getDefaultTimezone() + { + $defaultTimezone = Piwik_GetOption(self::OPTION_DEFAULT_TIMEZONE); + if ($defaultTimezone) { + return $defaultTimezone; + } + return 'UTC'; + } + + /** + * Sets the default timezone that will be used when creating websites + * + * @param string $defaultTimezone Timezone string eg. Europe/Paris or UTC+8 + * @return bool + */ + public function setDefaultTimezone($defaultTimezone) + { + Piwik::checkUserIsSuperUser(); + $this->checkValidTimezone($defaultTimezone); + Piwik_SetOption(self::OPTION_DEFAULT_TIMEZONE, $defaultTimezone); + return true; + } + + /** + * Update an existing website. + * If only one URL is specified then only the main url will be updated. + * If several URLs are specified, both the main URL and the alias URLs will be updated. + * + * @param int $idSite website ID defining the website to edit + * @param string $siteName website name + * @param string|array $urls the website URLs + * @param int $ecommerce Whether Ecommerce is enabled, 0 or 1 + * @param int $sitesearch Whether site search is enabled, 0 or 1 + * @param string $searchKeywordParameters Comma separated list of search keyword parameter names + * @param string $searchCategoryParameters Comma separated list of search category parameter names + * @param string $excludedIps Comma separated list of IPs to exclude from being tracked (allows wildcards) + * @param null $excludedQueryParameters + * @param string $timezone Timezone + * @param string $currency Currency code + * @param string $group Group name where this website belongs + * @param string $startDate Date at which the statistics for this website will start. Defaults to today's date in YYYY-MM-DD format + * @param int|null $keepURLFragments If 1, URL fragments will be kept when tracking. If 2, they + * will be removed. If 0, the default global behavior will be used. + * @see getKeepURLFragmentsGlobal. If null, the existing value will + * not be modified. + * + * @throws Exception + * @return bool true on success + */ + public function updateSite($idSite, + $siteName, + $urls = null, + $ecommerce = null, + $siteSearch = null, + $searchKeywordParameters = null, + $searchCategoryParameters = null, + $excludedIps = null, + $excludedQueryParameters = null, + $timezone = null, + $currency = null, + $group = null, + $startDate = null, + $excludedUserAgents = null, + $keepURLFragments = null) + { + Piwik::checkUserHasAdminAccess($idSite); + + $idSites = Piwik_SitesManager_API::getInstance()->getSitesId(); + if (!in_array($idSite, $idSites)) { + throw new Exception("website id = $idSite not found"); + } + + $this->checkName($siteName); + + // Build the SQL UPDATE based on specified updates to perform + $bind = array(); + if (!is_null($urls)) { + $urls = $this->cleanParameterUrls($urls); + $this->checkUrls($urls); + $this->checkAtLeastOneUrl($urls); + $url = $urls[0]; + $bind['main_url'] = $url; + } + + if (!is_null($currency)) { + $currency = trim($currency); + $this->checkValidCurrency($currency); + $bind['currency'] = $currency; + } + if (!is_null($timezone)) { + $timezone = trim($timezone); + $this->checkValidTimezone($timezone); + $bind['timezone'] = $timezone; + } + if (!is_null($group) + && Piwik::isUserIsSuperUser() + ) { + $bind['group'] = trim($group); + } + if (!is_null($ecommerce)) { + $bind['ecommerce'] = (int)(bool)$ecommerce; + } + if (!is_null($startDate)) { + $bind['ts_created'] = Piwik_Date::factory($startDate)->getDatetime(); + } + $bind['excluded_ips'] = $this->checkAndReturnExcludedIps($excludedIps); + $bind['excluded_parameters'] = $this->checkAndReturnCommaSeparatedStringList($excludedQueryParameters); + $bind['excluded_user_agents'] = $this->checkAndReturnCommaSeparatedStringList($excludedUserAgents); + + if (!is_null($keepURLFragments)) { + $keepURLFragments = (int)$keepURLFragments; + self::checkKeepURLFragmentsValue($keepURLFragments); + + $bind['keep_url_fragment'] = $keepURLFragments; + } + + $bind['sitesearch'] = $this->checkSiteSearch($siteSearch); + list($searchKeywordParameters, $searchCategoryParameters) = $this->checkSiteSearchParameters($searchKeywordParameters, $searchCategoryParameters); + $bind['sitesearch_keyword_parameters'] = $searchKeywordParameters; + $bind['sitesearch_category_parameters'] = $searchCategoryParameters; + + $bind['name'] = $siteName; + $db = Zend_Registry::get('db'); + $db->update(Piwik_Common::prefixTable("site"), + $bind, + "idsite = $idSite" + ); + + // we now update the main + alias URLs + $this->deleteSiteAliasUrls($idSite); + if (count($urls) > 1) { + $this->addSiteAliasUrls($idSite, array_slice($urls, 1)); + } + $this->postUpdateWebsite($idSite); + + Piwik_PostEvent('SitesManager.updateSite', $idSite); + } + + private function checkAndReturnCommaSeparatedStringList($parameters) + { + $parameters = trim($parameters); + if (empty($parameters)) { + return ''; + } + + $parameters = explode(',', $parameters); + $parameters = array_map('trim', $parameters); + $parameters = array_filter($parameters, 'strlen'); + $parameters = array_unique($parameters); + return implode(',', $parameters); + } + + /** + * Returns the list of supported currencies + * @see getCurrencySymbols() + * @return array ( currencyId => currencyName) + */ + public function getCurrencyList() + { + $currencies = Piwik::getCurrencyList(); + return array_map(create_function('$a', 'return $a[1]." (".$a[0].")";'), $currencies); + } + + /** + * Returns the list of currency symbols + * @see getCurrencyList() + * @return array( currencyId => currencySymbol ) + */ + public function getCurrencySymbols() + { + $currencies = Piwik::getCurrencyList(); + return array_map(create_function('$a', 'return $a[0];'), $currencies); + } + + /** + * Returns the list of timezones supported. + * Used for addSite and updateSite + * + * @TODO NOT COMPATIBLE WITH API RESPONSE AUTO BUILDER + * + * @return array of timezone strings + */ + public function getTimezonesList() + { + if (!Piwik::isTimezoneSupportEnabled()) { + return array('UTC' => $this->getTimezonesListUTCOffsets()); + } + + $continents = array('Africa', 'America', 'Antarctica', 'Arctic', 'Asia', 'Atlantic', 'Australia', 'Europe', 'Indian', 'Pacific'); + $timezones = timezone_identifiers_list(); + + $return = array(); + foreach ($timezones as $timezone) { + // filter out timezones not recognized by strtotime() + // @see http://bugs.php.net/46111 + $testDate = '2008-09-18 13:00:00 ' . $timezone; + if (!strtotime($testDate)) { + continue; + } + + $timezoneExploded = explode('/', $timezone); + $continent = $timezoneExploded[0]; + + // only display timezones that are grouped by continent + if (!in_array($continent, $continents)) { + continue; + } + $city = $timezoneExploded[1]; + if (!empty($timezoneExploded[2])) { + $city .= ' - ' . $timezoneExploded[2]; + } + $city = str_replace('_', ' ', $city); + $return[$continent][$timezone] = $city; + } + + foreach ($continents as $continent) { + if (!empty($return[$continent])) { + ksort($return[$continent]); + } + } + + $return['UTC'] = $this->getTimezonesListUTCOffsets(); + return $return; + } + + private function getTimezonesListUTCOffsets() + { + // manually add the UTC offsets + $GmtOffsets = array(-12, -11.5, -11, -10.5, -10, -9.5, -9, -8.5, -8, -7.5, -7, -6.5, -6, -5.5, -5, -4.5, -4, -3.5, -3, -2.5, -2, -1.5, -1, -0.5, + 0, 0.5, 1, 1.5, 2, 2.5, 3, 3.5, 4, 4.5, 5, 5.5, 5.75, 6, 6.5, 7, 7.5, 8, 8.5, 8.75, 9, 9.5, 10, 10.5, 11, 11.5, 12, 12.75, 13, 13.75, 14); + + $return = array(); + foreach ($GmtOffsets as $offset) { + if ($offset > 0) { + $offset = '+' . $offset; + } elseif ($offset == 0) { + $offset = ''; + } + $offset = 'UTC' . $offset; + $offsetName = str_replace(array('.25', '.5', '.75'), array(':15', ':30', ':45'), $offset); + $return[$offset] = $offsetName; + } + return $return; + } + + /** + * Returns the list of unique timezones from all configured sites. + * + * @return array ( string ) + */ + public function getUniqueSiteTimezones() + { + Piwik::checkUserIsSuperUser(); + $results = Piwik_FetchAll("SELECT distinct timezone FROM " . Piwik_Common::prefixTable('site')); + $timezones = array(); + foreach ($results as $result) { + $timezones[] = $result['timezone']; + } + return $timezones; + } + + /** + * Insert the list of alias URLs for the website. + * The URLs must not exist already for this website! + */ + private function insertSiteUrls($idSite, $urls) + { + if (count($urls) != 0) { + $db = Zend_Registry::get('db'); + foreach ($urls as $url) { + $db->insert(Piwik_Common::prefixTable("site_url"), array( + 'idsite' => $idSite, + 'url' => $url + ) + ); + } + } + } + + /** + * Delete all the alias URLs for the given idSite. + */ + private function deleteSiteAliasUrls($idsite) + { + $db = Zend_Registry::get('db'); + $db->query("DELETE FROM " . Piwik_Common::prefixTable("site_url") . " WHERE idsite = ?", $idsite); - } - - /** - * Remove the final slash in the URLs if found - * - * @return string the URL without the trailing slash - */ - private function removeTrailingSlash($url) - { - // if there is a final slash, we take the URL without this slash (expected URL format) - if(strlen($url) > 5 - && $url[strlen($url)-1] == '/') - { - $url = substr($url,0,strlen($url)-1); - } - return $url; - } - - /** - * Tests if the URL is a valid URL - * - * @return bool - */ - private function isValidUrl( $url ) - { - return Piwik_Common::isLookLikeUrl($url); - } - - /** - * Tests if the IP is a valid IP, allowing wildcards, except in the first octet. - * Wildcards can only be used from right to left, ie. 1.1.*.* is allowed, but 1.1.*.1 is not. - * - * @param string $ip IP address - * @return bool - */ - private function isValidIp( $ip ) - { - return Piwik_IP::getIpsForRange($ip) !== false; - } - - /** - * Check that the website name has a correct format. - * - * @param $siteName - * @throws Exception - */ - private function checkName($siteName) - { - if(empty($siteName)) - { - throw new Exception(Piwik_TranslateException("SitesManager_ExceptionEmptyName")); - } - } - - - private function checkSiteSearch($siteSearch) - { - if($siteSearch === null) { - return "1"; - } - return $siteSearch == 1 ? "1" : "0"; - } - - private function checkSiteSearchParameters($searchKeywordParameters, $searchCategoryParameters) - { - $searchKeywordParameters = trim($searchKeywordParameters); - $searchCategoryParameters = trim($searchCategoryParameters); - if(empty($searchKeywordParameters)) { - $searchKeywordParameters = ''; - } - if(empty($searchCategoryParameters)) { - $searchCategoryParameters = ''; - } - - return array($searchKeywordParameters, $searchCategoryParameters); - } - - /** - * Check that the array of URLs are valid URLs - * - * @param array $urls - * @throws Exception if any of the urls is not valid - */ - private function checkUrls($urls) - { - foreach($urls as $url) - { - if(!$this->isValidUrl($url)) - { - throw new Exception(sprintf(Piwik_TranslateException("SitesManager_ExceptionInvalidUrl"),$url)); - } - } - } - - /** - * Clean the parameter URLs: - * - if the parameter is a string make it an array - * - remove the trailing slashes if found - * - * @param string|array urls - * @return array the array of cleaned URLs - */ - private function cleanParameterUrls( $urls ) - { - if(!is_array($urls)) - { - $urls = array($urls); - } - $urls = array_filter($urls); - - $urls = array_map('urldecode', $urls); - foreach($urls as &$url) - { - $url = $this->removeTrailingSlash($url); - if(strpos($url, 'http') !== 0) - { - $url = 'http://'.$url; - } - $url = Piwik_Common::sanitizeInputValue($url); - } - $urls = array_unique($urls); - return $urls; - } - - public function getPatternMatchSites($pattern) - { - $ids = $this->getSitesIdWithAtLeastViewAccess(); - if(empty($ids)) - { - return array(); - } - - $ids_str = ''; - foreach($ids as $id_num => $id_val) - { - $ids_str .= $id_val.' , '; - } - $ids_str .= $id_val; - - $db = Zend_Registry::get('db'); - $bind = array('%'.$pattern.'%', 'http%'.$pattern.'%'); - - // Also match the idsite - $where = ''; - if(is_numeric($pattern)) - { - $bind[] = $pattern; - $where = 'OR s.idsite = ?'; - } - $sites = $db->fetchAll("SELECT idsite, name, main_url - FROM ".Piwik_Common::prefixTable('site')." s + } + + /** + * Remove the final slash in the URLs if found + * + * @return string the URL without the trailing slash + */ + private function removeTrailingSlash($url) + { + // if there is a final slash, we take the URL without this slash (expected URL format) + if (strlen($url) > 5 + && $url[strlen($url) - 1] == '/' + ) { + $url = substr($url, 0, strlen($url) - 1); + } + return $url; + } + + /** + * Tests if the URL is a valid URL + * + * @return bool + */ + private function isValidUrl($url) + { + return Piwik_Common::isLookLikeUrl($url); + } + + /** + * Tests if the IP is a valid IP, allowing wildcards, except in the first octet. + * Wildcards can only be used from right to left, ie. 1.1.*.* is allowed, but 1.1.*.1 is not. + * + * @param string $ip IP address + * @return bool + */ + private function isValidIp($ip) + { + return Piwik_IP::getIpsForRange($ip) !== false; + } + + /** + * Check that the website name has a correct format. + * + * @param $siteName + * @throws Exception + */ + private function checkName($siteName) + { + if (empty($siteName)) { + throw new Exception(Piwik_TranslateException("SitesManager_ExceptionEmptyName")); + } + } + + + private function checkSiteSearch($siteSearch) + { + if ($siteSearch === null) { + return "1"; + } + return $siteSearch == 1 ? "1" : "0"; + } + + private function checkSiteSearchParameters($searchKeywordParameters, $searchCategoryParameters) + { + $searchKeywordParameters = trim($searchKeywordParameters); + $searchCategoryParameters = trim($searchCategoryParameters); + if (empty($searchKeywordParameters)) { + $searchKeywordParameters = ''; + } + if (empty($searchCategoryParameters)) { + $searchCategoryParameters = ''; + } + + return array($searchKeywordParameters, $searchCategoryParameters); + } + + /** + * Check that the array of URLs are valid URLs + * + * @param array $urls + * @throws Exception if any of the urls is not valid + */ + private function checkUrls($urls) + { + foreach ($urls as $url) { + if (!$this->isValidUrl($url)) { + throw new Exception(sprintf(Piwik_TranslateException("SitesManager_ExceptionInvalidUrl"), $url)); + } + } + } + + /** + * Clean the parameter URLs: + * - if the parameter is a string make it an array + * - remove the trailing slashes if found + * + * @param string|array urls + * @return array the array of cleaned URLs + */ + private function cleanParameterUrls($urls) + { + if (!is_array($urls)) { + $urls = array($urls); + } + $urls = array_filter($urls); + + $urls = array_map('urldecode', $urls); + foreach ($urls as &$url) { + $url = $this->removeTrailingSlash($url); + if (strpos($url, 'http') !== 0) { + $url = 'http://' . $url; + } + $url = Piwik_Common::sanitizeInputValue($url); + } + $urls = array_unique($urls); + return $urls; + } + + public function getPatternMatchSites($pattern) + { + $ids = $this->getSitesIdWithAtLeastViewAccess(); + if (empty($ids)) { + return array(); + } + + $ids_str = ''; + foreach ($ids as $id_num => $id_val) { + $ids_str .= $id_val . ' , '; + } + $ids_str .= $id_val; + + $db = Zend_Registry::get('db'); + $bind = array('%' . $pattern . '%', 'http%' . $pattern . '%'); + + // Also match the idsite + $where = ''; + if (is_numeric($pattern)) { + $bind[] = $pattern; + $where = 'OR s.idsite = ?'; + } + $sites = $db->fetchAll("SELECT idsite, name, main_url + FROM " . Piwik_Common::prefixTable('site') . " s WHERE ( s.name like ? OR s.main_url like ? $where ) AND idsite in ($ids_str) - LIMIT ".Piwik::getWebsitesCountToDisplay(), - $bind) ; - return $sites; - } - - /** - * Utility function that throws if a value is not valid for the 'keep_url_fragment' - * column of the piwik_site table. - * - * @param int $keepURLFragments - * @throws Exception - */ - private static function checkKeepURLFragmentsValue( $keepURLFragments ) - { - // make sure value is between 0 & 2 - if (!in_array($keepURLFragments, array(0,1,2))) - { - throw new Exception("Error in SitesManager.updateSite: keepURLFragments must be between 0 & 2" + - " (actual value: $keepURLFragments)."); - } - } + LIMIT " . Piwik::getWebsitesCountToDisplay(), + $bind); + return $sites; + } + + /** + * Utility function that throws if a value is not valid for the 'keep_url_fragment' + * column of the piwik_site table. + * + * @param int $keepURLFragments + * @throws Exception + */ + private static function checkKeepURLFragmentsValue($keepURLFragments) + { + // make sure value is between 0 & 2 + if (!in_array($keepURLFragments, array(0, 1, 2))) { + throw new Exception("Error in SitesManager.updateSite: keepURLFragments must be between 0 & 2" + + " (actual value: $keepURLFragments)."); + } + } } diff --git a/plugins/SitesManager/Controller.php b/plugins/SitesManager/Controller.php index 6b230e8193..c99ed03705 100644 --- a/plugins/SitesManager/Controller.php +++ b/plugins/SitesManager/Controller.php @@ -15,189 +15,178 @@ */ class Piwik_SitesManager_Controller extends Piwik_Controller_Admin { - /* - * Main view showing listing of websites and settings - */ - function index() - { - $view = Piwik_View::factory('SitesManager'); - - if (Piwik::isUserIsSuperUser()) - { - $sites = Piwik_SitesManager_API::getInstance()->getAllSites(); - Piwik_Site::setSites($sites); - $sites = array_values($sites); - } - else - { - $sites = Piwik_SitesManager_API::getInstance()->getSitesWithAdminAccess(); - Piwik_Site::setSitesFromArray($sites); - } - - foreach($sites as &$site) - { - $site['alias_urls'] = Piwik_SitesManager_API::getInstance()->getSiteUrlsFromId($site['idsite']); - $site['excluded_ips'] = str_replace(',','
', $site['excluded_ips']); - $site['excluded_parameters'] = str_replace(',','
', $site['excluded_parameters']); - $site['excluded_user_agents'] = str_replace(',', '
', $site['excluded_user_agents']); - } - $view->adminSites = $sites; - $view->adminSitesCount = count($sites); - - $timezones = Piwik_SitesManager_API::getInstance()->getTimezonesList(); - $view->timezoneSupported = Piwik::isTimezoneSupportEnabled(); - $view->timezones = Piwik_Common::json_encode($timezones); - $view->defaultTimezone = Piwik_SitesManager_API::getInstance()->getDefaultTimezone(); - - $view->currencies = Piwik_Common::json_encode(Piwik_SitesManager_API::getInstance()->getCurrencyList()); - $view->defaultCurrency = Piwik_SitesManager_API::getInstance()->getDefaultCurrency(); - - $view->utcTime = Piwik_Date::now()->getDatetime(); - $excludedIpsGlobal = Piwik_SitesManager_API::getInstance()->getExcludedIpsGlobal(); - $view->globalExcludedIps = str_replace(',',"\n", $excludedIpsGlobal); - $excludedQueryParametersGlobal = Piwik_SitesManager_API::getInstance()->getExcludedQueryParametersGlobal(); - $view->globalExcludedQueryParameters = str_replace(',',"\n", $excludedQueryParametersGlobal); - - $globalExcludedUserAgents = Piwik_SitesManager_API::getInstance()->getExcludedUserAgentsGlobal(); - $view->globalExcludedUserAgents = str_replace(',', "\n", $globalExcludedUserAgents); - - $view->globalSearchKeywordParameters = Piwik_SitesManager_API::getInstance()->getSearchKeywordParametersGlobal(); - $view->globalSearchCategoryParameters = Piwik_SitesManager_API::getInstance()->getSearchCategoryParametersGlobal(); - $view->isSearchCategoryTrackingEnabled = Piwik_PluginsManager::getInstance()->isPluginActivated('CustomVariables'); - $view->allowSiteSpecificUserAgentExclude = - Piwik_SitesManager_API::getInstance()->isSiteSpecificUserAgentExcludeEnabled(); - - $view->globalKeepURLFragments = Piwik_SitesManager_API::getInstance()->getKeepURLFragmentsGlobal(); - - $view->currentIpAddress = Piwik_IP::getIpFromHeader(); - - $view->showAddSite = (boolean) Piwik_Common::getRequestVar('showaddsite', false); - - $this->setBasicVariablesView($view); - $view->menu = Piwik_GetAdminMenu(); - echo $view->render(); - } - - /* - * Records Global settings when user submit changes - */ - function setGlobalSettings() - { - $response = new Piwik_API_ResponseBuilder(Piwik_Common::getRequestVar('format')); - - try { - $this->checkTokenInUrl(); - $timezone = Piwik_Common::getRequestVar('timezone', false); - $excludedIps = Piwik_Common::getRequestVar('excludedIps', false); - $excludedQueryParameters = Piwik_Common::getRequestVar('excludedQueryParameters', false); - $excludedUserAgents = Piwik_Common::getRequestVar('excludedUserAgents', false); - $currency = Piwik_Common::getRequestVar('currency', false); - $searchKeywordParameters = Piwik_Common::getRequestVar('searchKeywordParameters', $default = ""); - $searchCategoryParameters = Piwik_Common::getRequestVar('searchCategoryParameters', $default = ""); - $enableSiteUserAgentExclude = Piwik_Common::getRequestVar('enableSiteUserAgentExclude', $default = 0); - $keepURLFragments = Piwik_Common::getRequestVar('keepURLFragments', $default = 0); - - $api = Piwik_SitesManager_API::getInstance(); - $api->setDefaultTimezone($timezone); - $api->setDefaultCurrency($currency); - $api->setGlobalExcludedQueryParameters($excludedQueryParameters); - $api->setGlobalExcludedIps($excludedIps); - $api->setGlobalExcludedUserAgents($excludedUserAgents); - $api->setGlobalSearchParameters($searchKeywordParameters, $searchCategoryParameters); - $api->setSiteSpecificUserAgentExcludeEnabled($enableSiteUserAgentExclude == 1); - $api->setKeepURLFragmentsGlobal($keepURLFragments); - - $toReturn = $response->getResponse(); - } catch(Exception $e ) { - $toReturn = $response->getResponseException( $e ); - } - echo $toReturn; - } - - /** - * Displays the admin UI page showing all tracking tags - * @return unknown_type - */ - function displayJavascriptCode() - { - $idSite = Piwik_Common::getRequestVar('idSite'); - Piwik::checkUserHasViewAccess($idSite); - $jsTag = Piwik::getJavascriptCode($idSite, Piwik_Url::getCurrentUrlWithoutFileName()); - $view = Piwik_View::factory('Tracking'); - $this->setBasicVariablesView($view); - $view->menu = Piwik_GetAdminMenu(); - $view->idSite = $idSite; - $site = new Piwik_Site($idSite); - $view->displaySiteName = $site->getName(); - $view->jsTag = $jsTag; - echo $view->render(); - } - - /* - * User will download a file called PiwikTracker.php that is the content of the actual script - */ - function downloadPiwikTracker() - { - $path = PIWIK_INCLUDE_PATH . '/libs/PiwikTracker/'; - $filename = 'PiwikTracker.php'; - header('Content-type: text/php'); - header('Content-Disposition: attachment; filename="'.$filename.'"'); - echo file_get_contents( $path . $filename); - } - - /** - * Used to generate the doc at http://piwik.org/docs/tracking-api/ - */ - function displayAlternativeTagsHelp() - { - $view = Piwik_View::factory('DisplayAlternativeTags'); - $view->idSite = Piwik_Common::getRequestVar('idSite'); - $url = Piwik_Common::getRequestVar('piwikUrl', '', 'string'); - if(empty($url) - || !Piwik_Common::isLookLikeUrl($url) ) - { - $url = $view->piwikUrl; - } - $view->piwikUrlRequest = $url; - $view->calledExternally = true; - echo $view->render(); - } - - function getSitesForAutocompleter() - { - $pattern = Piwik_Common::getRequestVar('term'); - $sites = Piwik_SitesManager_API::getInstance()->getPatternMatchSites($pattern); - $pattern = str_replace('%', '', $pattern); - if(!count($sites)) - { - $results[] = array('label' => Piwik_Translate('SitesManager_NotFound')." $pattern.", 'id' => '#'); - } - else - { - if(strpos($pattern, '/') !== false - && strpos($pattern, '\\/') === false) - { - $pattern = str_replace('/', '\\/', $pattern); - } - foreach($sites as $s) - { - $hl_name = $s['name']; - if(strlen($pattern) > 0) - { - @preg_match_all("/$pattern+/i", $hl_name, $matches); - if (is_array($matches[0]) && count($matches[0]) >= 1) - { - foreach ($matches[0] as $match) - { - $hl_name = str_replace($match, ''.$match.'', $s['name']); - } - } - } - $results[] = array('label' => $hl_name, 'id' => $s['idsite'], 'name' => $s['name'] ); - } - } - - Piwik_DataTable_Renderer_Json::sendHeaderJSON(); - print Piwik_Common::json_encode($results); - } + /* + * Main view showing listing of websites and settings + */ + function index() + { + $view = Piwik_View::factory('SitesManager'); + + if (Piwik::isUserIsSuperUser()) { + $sites = Piwik_SitesManager_API::getInstance()->getAllSites(); + Piwik_Site::setSites($sites); + $sites = array_values($sites); + } else { + $sites = Piwik_SitesManager_API::getInstance()->getSitesWithAdminAccess(); + Piwik_Site::setSitesFromArray($sites); + } + + foreach ($sites as &$site) { + $site['alias_urls'] = Piwik_SitesManager_API::getInstance()->getSiteUrlsFromId($site['idsite']); + $site['excluded_ips'] = str_replace(',', '
', $site['excluded_ips']); + $site['excluded_parameters'] = str_replace(',', '
', $site['excluded_parameters']); + $site['excluded_user_agents'] = str_replace(',', '
', $site['excluded_user_agents']); + } + $view->adminSites = $sites; + $view->adminSitesCount = count($sites); + + $timezones = Piwik_SitesManager_API::getInstance()->getTimezonesList(); + $view->timezoneSupported = Piwik::isTimezoneSupportEnabled(); + $view->timezones = Piwik_Common::json_encode($timezones); + $view->defaultTimezone = Piwik_SitesManager_API::getInstance()->getDefaultTimezone(); + + $view->currencies = Piwik_Common::json_encode(Piwik_SitesManager_API::getInstance()->getCurrencyList()); + $view->defaultCurrency = Piwik_SitesManager_API::getInstance()->getDefaultCurrency(); + + $view->utcTime = Piwik_Date::now()->getDatetime(); + $excludedIpsGlobal = Piwik_SitesManager_API::getInstance()->getExcludedIpsGlobal(); + $view->globalExcludedIps = str_replace(',', "\n", $excludedIpsGlobal); + $excludedQueryParametersGlobal = Piwik_SitesManager_API::getInstance()->getExcludedQueryParametersGlobal(); + $view->globalExcludedQueryParameters = str_replace(',', "\n", $excludedQueryParametersGlobal); + + $globalExcludedUserAgents = Piwik_SitesManager_API::getInstance()->getExcludedUserAgentsGlobal(); + $view->globalExcludedUserAgents = str_replace(',', "\n", $globalExcludedUserAgents); + + $view->globalSearchKeywordParameters = Piwik_SitesManager_API::getInstance()->getSearchKeywordParametersGlobal(); + $view->globalSearchCategoryParameters = Piwik_SitesManager_API::getInstance()->getSearchCategoryParametersGlobal(); + $view->isSearchCategoryTrackingEnabled = Piwik_PluginsManager::getInstance()->isPluginActivated('CustomVariables'); + $view->allowSiteSpecificUserAgentExclude = + Piwik_SitesManager_API::getInstance()->isSiteSpecificUserAgentExcludeEnabled(); + + $view->globalKeepURLFragments = Piwik_SitesManager_API::getInstance()->getKeepURLFragmentsGlobal(); + + $view->currentIpAddress = Piwik_IP::getIpFromHeader(); + + $view->showAddSite = (boolean)Piwik_Common::getRequestVar('showaddsite', false); + + $this->setBasicVariablesView($view); + $view->menu = Piwik_GetAdminMenu(); + echo $view->render(); + } + + /* + * Records Global settings when user submit changes + */ + function setGlobalSettings() + { + $response = new Piwik_API_ResponseBuilder(Piwik_Common::getRequestVar('format')); + + try { + $this->checkTokenInUrl(); + $timezone = Piwik_Common::getRequestVar('timezone', false); + $excludedIps = Piwik_Common::getRequestVar('excludedIps', false); + $excludedQueryParameters = Piwik_Common::getRequestVar('excludedQueryParameters', false); + $excludedUserAgents = Piwik_Common::getRequestVar('excludedUserAgents', false); + $currency = Piwik_Common::getRequestVar('currency', false); + $searchKeywordParameters = Piwik_Common::getRequestVar('searchKeywordParameters', $default = ""); + $searchCategoryParameters = Piwik_Common::getRequestVar('searchCategoryParameters', $default = ""); + $enableSiteUserAgentExclude = Piwik_Common::getRequestVar('enableSiteUserAgentExclude', $default = 0); + $keepURLFragments = Piwik_Common::getRequestVar('keepURLFragments', $default = 0); + + $api = Piwik_SitesManager_API::getInstance(); + $api->setDefaultTimezone($timezone); + $api->setDefaultCurrency($currency); + $api->setGlobalExcludedQueryParameters($excludedQueryParameters); + $api->setGlobalExcludedIps($excludedIps); + $api->setGlobalExcludedUserAgents($excludedUserAgents); + $api->setGlobalSearchParameters($searchKeywordParameters, $searchCategoryParameters); + $api->setSiteSpecificUserAgentExcludeEnabled($enableSiteUserAgentExclude == 1); + $api->setKeepURLFragmentsGlobal($keepURLFragments); + + $toReturn = $response->getResponse(); + } catch (Exception $e) { + $toReturn = $response->getResponseException($e); + } + echo $toReturn; + } + + /** + * Displays the admin UI page showing all tracking tags + * @return unknown_type + */ + function displayJavascriptCode() + { + $idSite = Piwik_Common::getRequestVar('idSite'); + Piwik::checkUserHasViewAccess($idSite); + $jsTag = Piwik::getJavascriptCode($idSite, Piwik_Url::getCurrentUrlWithoutFileName()); + $view = Piwik_View::factory('Tracking'); + $this->setBasicVariablesView($view); + $view->menu = Piwik_GetAdminMenu(); + $view->idSite = $idSite; + $site = new Piwik_Site($idSite); + $view->displaySiteName = $site->getName(); + $view->jsTag = $jsTag; + echo $view->render(); + } + + /* + * User will download a file called PiwikTracker.php that is the content of the actual script + */ + function downloadPiwikTracker() + { + $path = PIWIK_INCLUDE_PATH . '/libs/PiwikTracker/'; + $filename = 'PiwikTracker.php'; + header('Content-type: text/php'); + header('Content-Disposition: attachment; filename="' . $filename . '"'); + echo file_get_contents($path . $filename); + } + + /** + * Used to generate the doc at http://piwik.org/docs/tracking-api/ + */ + function displayAlternativeTagsHelp() + { + $view = Piwik_View::factory('DisplayAlternativeTags'); + $view->idSite = Piwik_Common::getRequestVar('idSite'); + $url = Piwik_Common::getRequestVar('piwikUrl', '', 'string'); + if (empty($url) + || !Piwik_Common::isLookLikeUrl($url) + ) { + $url = $view->piwikUrl; + } + $view->piwikUrlRequest = $url; + $view->calledExternally = true; + echo $view->render(); + } + + function getSitesForAutocompleter() + { + $pattern = Piwik_Common::getRequestVar('term'); + $sites = Piwik_SitesManager_API::getInstance()->getPatternMatchSites($pattern); + $pattern = str_replace('%', '', $pattern); + if (!count($sites)) { + $results[] = array('label' => Piwik_Translate('SitesManager_NotFound') . " $pattern.", 'id' => '#'); + } else { + if (strpos($pattern, '/') !== false + && strpos($pattern, '\\/') === false + ) { + $pattern = str_replace('/', '\\/', $pattern); + } + foreach ($sites as $s) { + $hl_name = $s['name']; + if (strlen($pattern) > 0) { + @preg_match_all("/$pattern+/i", $hl_name, $matches); + if (is_array($matches[0]) && count($matches[0]) >= 1) { + foreach ($matches[0] as $match) { + $hl_name = str_replace($match, '' . $match . '', $s['name']); + } + } + } + $results[] = array('label' => $hl_name, 'id' => $s['idsite'], 'name' => $s['name']); + } + } + + Piwik_DataTable_Renderer_Json::sendHeaderJSON(); + print Piwik_Common::json_encode($results); + } } diff --git a/plugins/SitesManager/SitesManager.php b/plugins/SitesManager/SitesManager.php index 6f596f9f87..b258a29b8f 100644 --- a/plugins/SitesManager/SitesManager.php +++ b/plugins/SitesManager/SitesManager.php @@ -15,212 +15,204 @@ */ class Piwik_SitesManager extends Piwik_Plugin { - const KEEP_URL_FRAGMENT_USE_DEFAULT = 0; - const KEEP_URL_FRAGMENT_YES = 1; - const KEEP_URL_FRAGMENT_NO = 2; - - public function getInformation() - { - $info = array( - 'description' => Piwik_Translate('SitesManager_PluginDescription'), - 'author' => 'Piwik', - 'author_homepage' => 'http://piwik.org/', - 'version' => Piwik_Version::VERSION, - ); - return $info; - } - - function getListHooksRegistered() - { - return array( - 'AssetManager.getJsFiles' => 'getJsFiles', - 'AssetManager.getCssFiles' => 'getCssFiles', - 'AdminMenu.add' => 'addMenu', - 'Common.fetchWebsiteAttributes' => 'recordWebsiteDataInCache', - ); - } - - function addMenu() - { - Piwik_AddAdminSubMenu('CoreAdminHome_MenuManage', 'SitesManager_MenuSites', - array('module' => 'SitesManager', 'action' => 'index'), - Piwik::isUserHasSomeAdminAccess(), - $order = 1); - } - - /** - * Get CSS files - * - * @param Piwik_Event_Notification $notification notification object - */ - function getCssFiles( $notification ) - { - $cssFiles = &$notification->getNotificationObject(); - - $cssFiles[] = "themes/default/styles.css"; - } - - /** - * Get JavaScript files - * - * @param Piwik_Event_Notification $notification notification object - */ - function getJsFiles( $notification ) - { - $jsFiles = &$notification->getNotificationObject(); - - $jsFiles[] = "plugins/SitesManager/templates/SitesManager.js"; - } - - /** - * Hooks when a website tracker cache is flushed (website updated, cache deleted, or empty cache) - * Will record in the tracker config file all data needed for this website in Tracker. - * - * @param Piwik_Event_Notification $notification notification object - * @return void - */ - function recordWebsiteDataInCache($notification) - { - $idSite = (int)$notification->getNotificationInfo(); - // add the 'hosts' entry in the website array - $array =& $notification->getNotificationObject(); - $array['hosts'] = $this->getTrackerHosts($idSite); - - $website = Piwik_SitesManager_API::getInstance()->getSiteFromId($idSite); - $array['excluded_ips'] = $this->getTrackerExcludedIps($website); - $array['excluded_parameters'] = self::getTrackerExcludedQueryParameters($website); - $array['excluded_user_agents'] = self::getExcludedUserAgents($website); - $array['keep_url_fragment'] = self::shouldKeepURLFragmentsFor($website); - $array['sitesearch'] = $website['sitesearch']; - $array['sitesearch_keyword_parameters'] = $this->getTrackerSearchKeywordParameters($website); - $array['sitesearch_category_parameters'] = $this->getTrackerSearchCategoryParameters($website); - } - - /** - * Returns whether we should keep URL fragments for a specific site. - * - * @param array $site DB data for the site. - * @return bool - */ - private static function shouldKeepURLFragmentsFor( $site ) - { - if ($site['keep_url_fragment'] == self::KEEP_URL_FRAGMENT_YES) - { - return true; - } - else if ($site['keep_url_fragment'] == self::KEEP_URL_FRAGMENT_NO) - { - return false; - } - - return Piwik_SitesManager_API::getInstance()->getKeepURLFragmentsGlobal(); - } - - private function getTrackerSearchKeywordParameters($website) - { - $searchParameters = $website['sitesearch_keyword_parameters']; - if(empty($searchParameters)) { - $searchParameters = Piwik_SitesManager_API::getInstance()->getSearchKeywordParametersGlobal(); - } - return explode(",", $searchParameters); - } - - private function getTrackerSearchCategoryParameters($website) - { - $searchParameters = $website['sitesearch_category_parameters']; - if(empty($searchParameters)) { - $searchParameters = Piwik_SitesManager_API::getInstance()->getSearchCategoryParametersGlobal(); - } - return explode(",", $searchParameters); - } - - /** - * Returns the array of excluded IPs to save in the config file - * - * @return array - */ - private function getTrackerExcludedIps($website) - { - $excludedIps = $website['excluded_ips']; - $globalExcludedIps = Piwik_SitesManager_API::getInstance()->getExcludedIpsGlobal(); - - $excludedIps .= ',' . $globalExcludedIps; - - $ipRanges = array(); - foreach(explode(',', $excludedIps) as $ip) - { - $ipRange = Piwik_SitesManager_API::getInstance()->getIpsForRange($ip); - if($ipRange !== false) - { - $ipRanges[] = $ipRange; - } - } - return $ipRanges; - } - - /** - * Returns the array of excluded user agent substrings for a site. Filters out - * any garbage data & trims each entry. - * - * @param array $website The full set of information for a site. - * @return array - */ - private static function getExcludedUserAgents( $website ) - { - $excludedUserAgents = Piwik_SitesManager_API::getInstance()->getExcludedUserAgentsGlobal(); - if (Piwik_SitesManager_API::getInstance()->isSiteSpecificUserAgentExcludeEnabled()) - { - $excludedUserAgents .= ','.$website['excluded_user_agents']; - } - return self::filterBlankFromCommaSepList($excludedUserAgents); - } - - /** - * Returns the array of URL query parameters to exclude from URLs - * - * @return array - */ - public static function getTrackerExcludedQueryParameters($website) - { - $excludedQueryParameters = $website['excluded_parameters']; - $globalExcludedQueryParameters = Piwik_SitesManager_API::getInstance()->getExcludedQueryParametersGlobal(); - - $excludedQueryParameters .= ',' . $globalExcludedQueryParameters; - return self::filterBlankFromCommaSepList($excludedQueryParameters); - } - - /** - * Trims each element of a comma-separated list of strings, removes empty elements and - * returns the result (as an array). - * - * @param string $parameters The unfiltered list. - * @return array The filtered list of strings as an array. - */ - static private function filterBlankFromCommaSepList( $parameters ) - { - $parameters = explode(',', $parameters); - $parameters = array_filter($parameters, 'strlen'); - $parameters = array_unique($parameters); - return $parameters; - } - - /** - * Returns the hosts alias URLs - * @param int $idSite - * @return array - */ - private function getTrackerHosts($idSite) - { - $urls = Piwik_SitesManager_API::getInstance()->getSiteUrlsFromId($idSite); - $hosts = array(); - foreach($urls as $url) - { - $url = parse_url($url); - if(isset($url['host'])) - { - $hosts[] = $url['host']; - } - } - return $hosts; - } + const KEEP_URL_FRAGMENT_USE_DEFAULT = 0; + const KEEP_URL_FRAGMENT_YES = 1; + const KEEP_URL_FRAGMENT_NO = 2; + + public function getInformation() + { + $info = array( + 'description' => Piwik_Translate('SitesManager_PluginDescription'), + 'author' => 'Piwik', + 'author_homepage' => 'http://piwik.org/', + 'version' => Piwik_Version::VERSION, + ); + return $info; + } + + function getListHooksRegistered() + { + return array( + 'AssetManager.getJsFiles' => 'getJsFiles', + 'AssetManager.getCssFiles' => 'getCssFiles', + 'AdminMenu.add' => 'addMenu', + 'Common.fetchWebsiteAttributes' => 'recordWebsiteDataInCache', + ); + } + + function addMenu() + { + Piwik_AddAdminSubMenu('CoreAdminHome_MenuManage', 'SitesManager_MenuSites', + array('module' => 'SitesManager', 'action' => 'index'), + Piwik::isUserHasSomeAdminAccess(), + $order = 1); + } + + /** + * Get CSS files + * + * @param Piwik_Event_Notification $notification notification object + */ + function getCssFiles($notification) + { + $cssFiles = & $notification->getNotificationObject(); + + $cssFiles[] = "themes/default/styles.css"; + } + + /** + * Get JavaScript files + * + * @param Piwik_Event_Notification $notification notification object + */ + function getJsFiles($notification) + { + $jsFiles = & $notification->getNotificationObject(); + + $jsFiles[] = "plugins/SitesManager/templates/SitesManager.js"; + } + + /** + * Hooks when a website tracker cache is flushed (website updated, cache deleted, or empty cache) + * Will record in the tracker config file all data needed for this website in Tracker. + * + * @param Piwik_Event_Notification $notification notification object + * @return void + */ + function recordWebsiteDataInCache($notification) + { + $idSite = (int)$notification->getNotificationInfo(); + // add the 'hosts' entry in the website array + $array =& $notification->getNotificationObject(); + $array['hosts'] = $this->getTrackerHosts($idSite); + + $website = Piwik_SitesManager_API::getInstance()->getSiteFromId($idSite); + $array['excluded_ips'] = $this->getTrackerExcludedIps($website); + $array['excluded_parameters'] = self::getTrackerExcludedQueryParameters($website); + $array['excluded_user_agents'] = self::getExcludedUserAgents($website); + $array['keep_url_fragment'] = self::shouldKeepURLFragmentsFor($website); + $array['sitesearch'] = $website['sitesearch']; + $array['sitesearch_keyword_parameters'] = $this->getTrackerSearchKeywordParameters($website); + $array['sitesearch_category_parameters'] = $this->getTrackerSearchCategoryParameters($website); + } + + /** + * Returns whether we should keep URL fragments for a specific site. + * + * @param array $site DB data for the site. + * @return bool + */ + private static function shouldKeepURLFragmentsFor($site) + { + if ($site['keep_url_fragment'] == self::KEEP_URL_FRAGMENT_YES) { + return true; + } else if ($site['keep_url_fragment'] == self::KEEP_URL_FRAGMENT_NO) { + return false; + } + + return Piwik_SitesManager_API::getInstance()->getKeepURLFragmentsGlobal(); + } + + private function getTrackerSearchKeywordParameters($website) + { + $searchParameters = $website['sitesearch_keyword_parameters']; + if (empty($searchParameters)) { + $searchParameters = Piwik_SitesManager_API::getInstance()->getSearchKeywordParametersGlobal(); + } + return explode(",", $searchParameters); + } + + private function getTrackerSearchCategoryParameters($website) + { + $searchParameters = $website['sitesearch_category_parameters']; + if (empty($searchParameters)) { + $searchParameters = Piwik_SitesManager_API::getInstance()->getSearchCategoryParametersGlobal(); + } + return explode(",", $searchParameters); + } + + /** + * Returns the array of excluded IPs to save in the config file + * + * @return array + */ + private function getTrackerExcludedIps($website) + { + $excludedIps = $website['excluded_ips']; + $globalExcludedIps = Piwik_SitesManager_API::getInstance()->getExcludedIpsGlobal(); + + $excludedIps .= ',' . $globalExcludedIps; + + $ipRanges = array(); + foreach (explode(',', $excludedIps) as $ip) { + $ipRange = Piwik_SitesManager_API::getInstance()->getIpsForRange($ip); + if ($ipRange !== false) { + $ipRanges[] = $ipRange; + } + } + return $ipRanges; + } + + /** + * Returns the array of excluded user agent substrings for a site. Filters out + * any garbage data & trims each entry. + * + * @param array $website The full set of information for a site. + * @return array + */ + private static function getExcludedUserAgents($website) + { + $excludedUserAgents = Piwik_SitesManager_API::getInstance()->getExcludedUserAgentsGlobal(); + if (Piwik_SitesManager_API::getInstance()->isSiteSpecificUserAgentExcludeEnabled()) { + $excludedUserAgents .= ',' . $website['excluded_user_agents']; + } + return self::filterBlankFromCommaSepList($excludedUserAgents); + } + + /** + * Returns the array of URL query parameters to exclude from URLs + * + * @return array + */ + public static function getTrackerExcludedQueryParameters($website) + { + $excludedQueryParameters = $website['excluded_parameters']; + $globalExcludedQueryParameters = Piwik_SitesManager_API::getInstance()->getExcludedQueryParametersGlobal(); + + $excludedQueryParameters .= ',' . $globalExcludedQueryParameters; + return self::filterBlankFromCommaSepList($excludedQueryParameters); + } + + /** + * Trims each element of a comma-separated list of strings, removes empty elements and + * returns the result (as an array). + * + * @param string $parameters The unfiltered list. + * @return array The filtered list of strings as an array. + */ + static private function filterBlankFromCommaSepList($parameters) + { + $parameters = explode(',', $parameters); + $parameters = array_filter($parameters, 'strlen'); + $parameters = array_unique($parameters); + return $parameters; + } + + /** + * Returns the hosts alias URLs + * @param int $idSite + * @return array + */ + private function getTrackerHosts($idSite) + { + $urls = Piwik_SitesManager_API::getInstance()->getSiteUrlsFromId($idSite); + $hosts = array(); + foreach ($urls as $url) { + $url = parse_url($url); + if (isset($url['host'])) { + $hosts[] = $url['host']; + } + } + return $hosts; + } } diff --git a/plugins/SitesManager/templates/DisplayAlternativeTags.tpl b/plugins/SitesManager/templates/DisplayAlternativeTags.tpl index 25328f351f..3dee0c15c4 100644 --- a/plugins/SitesManager/templates/DisplayAlternativeTags.tpl +++ b/plugins/SitesManager/templates/DisplayAlternativeTags.tpl @@ -1,101 +1,124 @@

Image Tracker code

-The Image Tracker code can be used when Javascript is not allowed. -
+The Image Tracker code can be used when Javascript is not allowed. +
+
-

Some websites like MySpace or eBay will not allow users to add Javascript to their profile but accept HTML. In this case, you can still track visits with Piwik using the Image Tracker. -
-Note: the code doesn't use Javascript so Piwik will not be able to track some user information - such as search keywords, referrers, screen resolutions, browser plugins and page titles. -

- -<!-- Piwik Image Tracker -->
-<img src="{if isset($piwikUrlRequest)}{$piwikUrlRequest}{else}{$piwikUrl}{/if}piwik.php?idsite={$idSite}&amp;rec=1" style="border:0" alt="" />
-<!-- End Piwik -->
-
-
-The following parameters can also be passed to the image URL: -
    -
  • rec - (required) The parameter &rec=1 is required to force the request to be recorded
  • -
  • idsite - (required) Defines the Website ID being tracked
  • -
  • action_name - Defines the custom Page Title for this page view
  • -
  • urlref - The Referrer URL: must be set to the referrer URL used before landing on the page containing the Image tracker. For example, in PHP this value is accessible via
    $_SERVER['HTTP_REFERER']
  • -
  • idgoal - The request will trigger the given Goal
  • -
  • revenue - Used with idgoal, defines the custom revenue for this conversion
  • -
  • and more! - There are many more parameters you can set beyond the main ones above. See the Tracking API documentation page.
  • -
+

Some websites like MySpace or eBay will not allow users to add Javascript to their profile but accept HTML. In this case, you can still track visits with + Piwik using the Image Tracker. +
+ Note: the code doesn't use Javascript so Piwik will not be able to track some user information + such as search keywords, referrers, screen resolutions, browser plugins and page titles. +

+ + <!-- Piwik Image Tracker -->
+ <img src="{if isset($piwikUrlRequest)}{$piwikUrlRequest}{else}{$piwikUrl}{/if}piwik.php?idsite={$idSite}&amp;rec=1" style="border:0" alt="" />
+ <!-- End Piwik -->
+
+
+ The following parameters can also be passed to the image URL: +
    +
  • rec - (required) The parameter &rec=1 is required to force the request to be recorded
  • +
  • idsite - (required) Defines the Website ID being tracked
  • +
  • action_name - Defines the custom Page Title for this page view
  • +
  • urlref - The Referrer URL: must be set to the referrer URL used before landing on the page containing the Image tracker. For example, in PHP + this value is accessible via +
    $_SERVER['HTTP_REFERER']
    +
  • +
  • idgoal - The request will trigger the given Goal
  • +
  • revenue - Used with idgoal, defines the custom revenue for this conversion
  • +
  • and more! - There are many more parameters you can set beyond the main ones above. See the Tracking API documentation page. +
  • +

Piwik Tracking API (Advanced users)

-It is also possible to call the Piwik Tracking API using your favorite programming language. -
+It is also possible to call the Piwik Tracking API using your favorite programming language. +
+
-

-The Piwik Tracking API allows to trigger visits (page views and Goal conversions) from any environment (Desktop App, iPhone or Android app, Mobile website, etc.). -

+

+ The Piwik Tracking API allows to trigger visits (page views and Goal conversions) from any environment (Desktop App, iPhone or Android app, Mobile + website, etc.). +

-

We currently provide a PHP client to call the API from your PHP projects. -If you would like to contribute a version of the client in another programming language (Python, Java, Ruby, Perl, etc.) please create a ticket in our developer area (please attach the client code to the ticket). -

Follow these instructions to get started with the Tracking API: -

    -
  • Click here to download the file PiwikTracker.php -
  • Upload the PiwikTracker.php file in the same path as your project files -
  • Copy the following code, then paste it onto every page you want to track. - -<?php
    -// -- Piwik Tracking API init --
    -require_once "/path/to/PiwikTracker.php";
    -PiwikTracker::$URL = '{if isset($piwikUrlRequest)}{$piwikUrlRequest}{else}{$piwikUrl}{/if}';
    - ?> -
    -
  • Choose a Tracking method, then paste the code onto every page you want to track. +

    We currently provide a PHP client to call the API from your PHP projects. + If you would like to contribute a version of the client in another programming language (Python, Java, Ruby, Perl, etc.) please create + a ticket in our developer area (please attach the client code to the ticket). +

    -
      -
    • Method 1: Advanced Image Tracker -
      -

      The client is used to generate the tracking URL that is wrapped inside a HTML <img src=''> code. -
      Paste this code before the </body> code in your pages. - -<?php
      -// Example 1: Tracks a pageview for Website id = {$idSite}
      -echo '<img src="'. str_replace("&","&amp;", Piwik_getUrlTrackPageView( $idSite = {$idSite}, $customTitle = 'This title will appear in the report Actions > Page titles')) . '" alt="" />';
      -// Example 2: Triggers a Goal conversion for Website id = {$idSite} and Goal id = 2
      -// $customRevenue is optional and is set to the amount generated by the current transaction (in online shops for example)
      -echo '<img src="'. str_replace("&","&amp;", Piwik_getUrlTrackGoal( $idSite = {$idSite}, $idGoal = 2, $customRevenue = 39)) . '" alt="" />';
      - ?> -
      -
      -The Advanced Image Tracker method is similar to using the standard Javascript Tracking code. However, some user settings are not detected (resolution, local time, plugins and cookie support). -

      - -
    • -
    • Method 2: HTTP Request -
      -

      You can also query the Piwik Tracker API remotely via HTTP. -This is useful for environment where you can't execute HTML nor Javascript. -
      Paste this code anywhere in your code where you wish to track a user interaction. - - -<?php
      -$piwikTracker = new PiwikTracker( $idSite = {$idSite} );
      -// You can manually set the visitor details (resolution, time, plugins, etc.)
      -// See all other ->set* functions available in the PiwikTracker.php file
      -$piwikTracker->setResolution(1600, 1400);

      -// Sends Tracker request via http
      -$piwikTracker->doTrackPageView('Document title of current page view');

      -// You can also track Goal conversions
      -$piwikTracker->doTrackGoal($idGoal = 1, $revenue = 42);
      - ?> -
      -

      -
    -
  • -
-

-{if !isset($calledExternally) || !$calledExternally} -

- Read more about the Piwik Tracking API in the documentation -

-{/if} +

Follow these instructions to get started with the Tracking API: +

    +
  • Click here to + download the file PiwikTracker.php +
  • +
  • Upload the PiwikTracker.php file in the same path as your project files +
  • +
  • Copy the following code, then paste it onto every page you want to track. + + <?php
    + // -- Piwik Tracking API init --
    + require_once "/path/to/PiwikTracker.php";
    + PiwikTracker::$URL = '{if isset($piwikUrlRequest)}{$piwikUrlRequest}{else}{$piwikUrl}{/if}';
    + ?> +
    +
  • +
  • Choose a Tracking method, then paste the code onto every page you want to track. + +
      +
    • Method 1: Advanced Image Tracker +
      + +

      The client is used to generate the tracking URL that is wrapped inside a HTML <img src=''> code. +
      Paste this code before the </body> code in your pages. + + <?php
      + // Example 1: Tracks a pageview for Website id = {$idSite}
      + echo '<img src="'. str_replace("&","&amp;", Piwik_getUrlTrackPageView( $idSite = {$idSite}, $customTitle = 'This title + will appear in the report Actions > Page titles')) . '" alt="" />';
      + // Example 2: Triggers a Goal conversion for Website id = {$idSite} and Goal id = 2
      + // $customRevenue is optional and is set to the amount generated by the current transaction (in online shops for example)
      + echo '<img src="'. str_replace("&","&amp;", Piwik_getUrlTrackGoal( $idSite = {$idSite}, $idGoal = 2, $customRevenue = + 39)) . '" alt="" />';
      + ?> +
      +
      + The Advanced Image Tracker method is similar to using the standard Javascript Tracking code. However, some user settings are not + detected (resolution, local time, plugins and cookie support). +

      + +
    • +
    • Method 2: HTTP Request +
      + +

      You can also query the Piwik Tracker API remotely via HTTP. + This is useful for environment where you can't execute HTML nor Javascript. +
      Paste this code anywhere in your code where you wish to track a user interaction. + + + <?php
      + $piwikTracker = new PiwikTracker( $idSite = {$idSite} );
      + // You can manually set the visitor details (resolution, time, plugins, etc.)
      + // See all other ->set* functions available in the PiwikTracker.php file
      + $piwikTracker->setResolution(1600, 1400);

      + // Sends Tracker request via http
      + $piwikTracker->doTrackPageView('Document title of current page view');

      + // You can also track Goal conversions
      + $piwikTracker->doTrackGoal($idGoal = 1, $revenue = 42);
      + ?> +
      +

      +
    • +
    +
  • +
+

+ {if !isset($calledExternally) || !$calledExternally} +

+ Read more about the Piwik Tracking API in the documentation +

+ {/if}
diff --git a/plugins/SitesManager/templates/DisplayJavascriptCode.tpl b/plugins/SitesManager/templates/DisplayJavascriptCode.tpl index 51bf27e2fd..b34e38fec0 100644 --- a/plugins/SitesManager/templates/DisplayJavascriptCode.tpl +++ b/plugins/SitesManager/templates/DisplayJavascriptCode.tpl @@ -1,66 +1,69 @@ - {literal} - + {/literal}

{'SitesManager_TrackingTags'|translate:$displaySiteName}

-{'Installation_JSTracking_Intro'|translate} -

-{'CoreAdminHome_JSTrackingIntro3'|translate:'':''} + {'Installation_JSTracking_Intro'|translate} +

+ {'CoreAdminHome_JSTrackingIntro3'|translate:'':''} -

{'SitesManager_JsTrackingTag'|translate}

-

{'CoreAdminHome_JSTracking_CodeNote'|translate:"</body>"}

+

{'SitesManager_JsTrackingTag'|translate}

-
{$jsTag}
+

{'CoreAdminHome_JSTracking_CodeNote'|translate:"</body>"}

-
-{'CoreAdminHome_JSTrackingIntro5'|translate:'':''} -

-{'Installation_JSTracking_EndNote'|translate:'':''} +
{$jsTag}
+ +
+ {'CoreAdminHome_JSTrackingIntro5'|translate:'':''} +

+ {'Installation_JSTracking_EndNote'|translate:'':''}
{literal} - + {/literal} diff --git a/plugins/SitesManager/templates/SitesManager.js b/plugins/SitesManager/templates/SitesManager.js index 70a42bf7db..24d6afa849 100644 --- a/plugins/SitesManager/templates/SitesManager.js +++ b/plugins/SitesManager/templates/SitesManager.js @@ -6,17 +6,16 @@ */ // NOTE: if you cannot find the definition of a variable here, look in SitesManager.tpl -function SitesManager ( _timezones, _currencies, _defaultTimezone, _defaultCurrency ) { +function SitesManager(_timezones, _currencies, _defaultTimezone, _defaultCurrency) { - var timezones = _timezones; - var currencies = _currencies; - var defaultTimezone = _defaultTimezone; - var defaultCurrency = _defaultCurrency; - var siteBeingEdited = false; - var siteBeingEditedName = ''; + var timezones = _timezones; + var currencies = _currencies; + var defaultTimezone = _defaultTimezone; + var defaultCurrency = _defaultCurrency; + var siteBeingEdited = false; + var siteBeingEditedName = ''; - function sendDeleteSiteAJAX( idSite ) - { + function sendDeleteSiteAJAX(idSite) { var ajaxHandler = new ajaxHelper(); ajaxHandler.addParams({ idSite: idSite, @@ -27,27 +26,26 @@ function SitesManager ( _timezones, _currencies, _defaultTimezone, _defaultCurre ajaxHandler.redirectOnSuccess(); ajaxHandler.setLoadingElement(); ajaxHandler.send(true); - } - - function sendAddSiteAJAX( row ) - { - var siteName = $(row).find('input#name').val(); - var urls = $(row).find('textarea#urls').val(); - urls = urls.trim().split("\n"); - var excludedIps = $(row).find('textarea#excludedIps').val(); - excludedIps = piwikHelper.getApiFormatTextarea(excludedIps); - var timezone = $(row).find('#timezones option:selected').val(); - var currency = $(row).find('#currencies option:selected').val(); - var excludedQueryParameters = $(row).find('textarea#excludedQueryParameters').val(); - excludedQueryParameters = piwikHelper.getApiFormatTextarea(excludedQueryParameters); - var excludedUserAgents = $(row).find('textarea#excludedUserAgents').val(); - excludedUserAgents = piwikHelper.getApiFormatTextarea(excludedUserAgents); - var keepURLFragments = $('#keepURLFragmentSelect', row).val(); - var ecommerce = $(row).find('#ecommerce option:selected').val(); + } + + function sendAddSiteAJAX(row) { + var siteName = $(row).find('input#name').val(); + var urls = $(row).find('textarea#urls').val(); + urls = urls.trim().split("\n"); + var excludedIps = $(row).find('textarea#excludedIps').val(); + excludedIps = piwikHelper.getApiFormatTextarea(excludedIps); + var timezone = $(row).find('#timezones option:selected').val(); + var currency = $(row).find('#currencies option:selected').val(); + var excludedQueryParameters = $(row).find('textarea#excludedQueryParameters').val(); + excludedQueryParameters = piwikHelper.getApiFormatTextarea(excludedQueryParameters); + var excludedUserAgents = $(row).find('textarea#excludedUserAgents').val(); + excludedUserAgents = piwikHelper.getApiFormatTextarea(excludedUserAgents); + var keepURLFragments = $('#keepURLFragmentSelect', row).val(); + var ecommerce = $(row).find('#ecommerce option:selected').val(); var sitesearch = $(row).find('#sitesearch option:selected').val(); var searchKeywordParameters = $('input#searchKeywordParameters').val(); var searchCategoryParameters = $('input#searchCategoryParameters').val(); - + var ajaxHandler = new ajaxHelper(); ajaxHandler.addParams({ module: 'API', @@ -72,22 +70,21 @@ function SitesManager ( _timezones, _currencies, _defaultTimezone, _defaultCurre ajaxHandler.setLoadingElement(); ajaxHandler.send(true); } - - function sendUpdateSiteAJAX( row ) - { - var siteName = $(row).find('input#siteName').val(); - var idSite = $(row).children('#idSite').html(); - var urls = $(row).find('textarea#urls').val(); - urls = urls.trim().split("\n"); - var excludedIps = $(row).find('textarea#excludedIps').val(); - excludedIps = piwikHelper.getApiFormatTextarea(excludedIps); - var excludedQueryParameters = $(row).find('textarea#excludedQueryParameters').val(); - excludedQueryParameters = piwikHelper.getApiFormatTextarea(excludedQueryParameters); - var excludedUserAgents = $(row).find('textarea#excludedUserAgents').val(); - excludedUserAgents = piwikHelper.getApiFormatTextarea(excludedUserAgents); - var keepURLFragments = $('#keepURLFragmentSelect', row).val(); - var timezone = $(row).find('#timezones option:selected').val(); - var currency = $(row).find('#currencies option:selected').val(); + + function sendUpdateSiteAJAX(row) { + var siteName = $(row).find('input#siteName').val(); + var idSite = $(row).children('#idSite').html(); + var urls = $(row).find('textarea#urls').val(); + urls = urls.trim().split("\n"); + var excludedIps = $(row).find('textarea#excludedIps').val(); + excludedIps = piwikHelper.getApiFormatTextarea(excludedIps); + var excludedQueryParameters = $(row).find('textarea#excludedQueryParameters').val(); + excludedQueryParameters = piwikHelper.getApiFormatTextarea(excludedQueryParameters); + var excludedUserAgents = $(row).find('textarea#excludedUserAgents').val(); + excludedUserAgents = piwikHelper.getApiFormatTextarea(excludedUserAgents); + var keepURLFragments = $('#keepURLFragmentSelect', row).val(); + var timezone = $(row).find('#timezones option:selected').val(); + var currency = $(row).find('#currencies option:selected').val(); var ecommerce = $(row).find('#ecommerce option:selected').val(); var sitesearch = $(row).find('#sitesearch option:selected').val(); var searchKeywordParameters = $('input#searchKeywordParameters').val(); @@ -117,19 +114,18 @@ function SitesManager ( _timezones, _currencies, _defaultTimezone, _defaultCurre ajaxHandler.redirectOnSuccess(); ajaxHandler.setLoadingElement(); ajaxHandler.send(true); - } - - function sendGlobalSettingsAJAX() - { - var timezone = $('#defaultTimezone option:selected').val(); - var currency = $('#defaultCurrency option:selected').val(); - var excludedIps = $('textarea#globalExcludedIps').val(); - excludedIps = piwikHelper.getApiFormatTextarea(excludedIps); - var excludedQueryParameters = $('textarea#globalExcludedQueryParameters').val(); - excludedQueryParameters = piwikHelper.getApiFormatTextarea(excludedQueryParameters); - var globalExcludedUserAgents = $('textarea#globalExcludedUserAgents').val(); - globalExcludedUserAgents = piwikHelper.getApiFormatTextarea(globalExcludedUserAgents); - var globalKeepURLFragments = $('#globalKeepURLFragments').is(':checked') ? 1 : 0; + } + + function sendGlobalSettingsAJAX() { + var timezone = $('#defaultTimezone option:selected').val(); + var currency = $('#defaultCurrency option:selected').val(); + var excludedIps = $('textarea#globalExcludedIps').val(); + excludedIps = piwikHelper.getApiFormatTextarea(excludedIps); + var excludedQueryParameters = $('textarea#globalExcludedQueryParameters').val(); + excludedQueryParameters = piwikHelper.getApiFormatTextarea(excludedQueryParameters); + var globalExcludedUserAgents = $('textarea#globalExcludedUserAgents').val(); + globalExcludedUserAgents = piwikHelper.getApiFormatTextarea(globalExcludedUserAgents); + var globalKeepURLFragments = $('#globalKeepURLFragments').is(':checked') ? 1 : 0; var searchKeywordParameters = $('input#globalSearchKeywordParameters').val(); var searchCategoryParameters = $('input#globalSearchCategoryParameters').val(); var enableSiteUserAgentExclude = $('input#enableSiteUserAgentExclude').is(':checked') ? 1 : 0; @@ -155,172 +151,162 @@ function SitesManager ( _timezones, _currencies, _defaultTimezone, _defaultCurre ajaxHandler.setLoadingElement('#ajaxLoadingGlobalSettings'); ajaxHandler.setErrorElement('#ajaxErrorGlobalSettings'); ajaxHandler.send(true); - } - - this.init = function () { - $('.addRowSite').click( function() { - piwikHelper.hideAjaxError(); - $('.addRowSite').toggle(); - - var numberOfRows = $('table#editSites')[0].rows.length; - var newRowId = 'rowNew' + numberOfRows; - var submitButtonHtml = ''; - $(' \ + } + + this.init = function () { + $('.addRowSite').click(function () { + piwikHelper.hideAjaxError(); + $('.addRowSite').toggle(); + + var numberOfRows = $('table#editSites')[0].rows.length; + var newRowId = 'rowNew' + numberOfRows; + var submitButtonHtml = ''; + $(' \  \ -


'+submitButtonHtml+'\ -
'+aliasUrlsHelp+keepURLFragmentSelectHTML+'\ -
'+excludedIpHelp+'\ -
'+excludedQueryParametersHelp+'\ -
'+excludedUserAgentsHelp+'\ - '+getSitesearchSelector(false)+'\ - '+getTimezoneSelector(defaultTimezone)+'
' + timezoneHelp + '\ - '+getCurrencySelector(defaultCurrency)+'
' + currencyHelp + '\ - '+getEcommerceSelector(0) + '
' + ecommerceHelp+ '\ - '+submitButtonHtml+'\ - '+sprintf(_pk_translate('General_OrCancel_js'),"","")+'\ +


' + submitButtonHtml + '\ +
' + aliasUrlsHelp + keepURLFragmentSelectHTML + '\ +
' + excludedIpHelp + '\ +
' + excludedQueryParametersHelp + '\ +
' + excludedUserAgentsHelp + '\ + ' + getSitesearchSelector(false) + '\ + ' + getTimezoneSelector(defaultTimezone) + '
' + timezoneHelp + '\ + ' + getCurrencySelector(defaultCurrency) + '
' + currencyHelp + '\ + ' + getEcommerceSelector(0) + '
' + ecommerceHelp + '\ + ' + submitButtonHtml + '\ + ' + sprintf(_pk_translate('General_OrCancel_js'), "", "") + '\ ') - .appendTo('#editSites') - ; - - piwikHelper.lazyScrollTo('#'+newRowId); - - $('.addsite').click( function(){ - sendAddSiteAJAX($('tr#'+newRowId)); - }); - - $('.cancel').click(function() { - piwikHelper.hideAjaxError(); - $(this).parents('tr').remove(); - $('.addRowSite').toggle(); - }); - return false; - } ); - - // when click on deleteuser, the we ask for confirmation and then delete the user - $('.deleteSite').click( function() { - piwikHelper.hideAjaxError(); - var idRow = $(this).attr('id'); - 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+')')); - piwikHelper.modalConfirm('#confirm', {yes: function(){ - sendDeleteSiteAJAX( idsiteToDelete ); - }}); - } - ); - - var alreadyEdited = new Array; - $('.editSite') - .click( function() { - piwikHelper.hideAjaxError(); - var idRow = $(this).attr('id'); - if(alreadyEdited[idRow]==1) return; - if(siteBeingEdited) - { - $('#alert h2').text(sprintf(_pk_translate('SitesManager_OnlyOneSiteAtTime_js'), '"'+$("
").html(siteBeingEditedName).text()+'"')); - piwikHelper.modalConfirm('#alert', {}); - return; - } - siteBeingEdited = true; - - alreadyEdited[idRow] = 1; - $('tr#'+idRow+' .editableSite').each( - // make the fields editable - // change the EDIT button to VALID button - function (i,n) { - var contentBefore = $(n).html(); - - var idName = $(n).attr('id'); - if(idName == 'siteName') - { - siteBeingEditedName = contentBefore; - var contentAfter = ''; - - var inputSave = $('
') - .click( function(){ submitUpdateSite($(this).parent()); }); - var spanCancel = $('

'+sprintf(_pk_translate('General_OrCancel_js'),"","")+'
') - .click( function(){ piwikHelper.refreshAfter(0); } ); - $(n) - .html(contentAfter) - .keypress( submitSiteOnEnter ) - .append(inputSave) - .append(spanCancel); - } - else if(idName == 'urls') - { - var keepURLFragmentsForSite = $(this).closest('tr').attr('data-keep-url-fragments'); - - var contentAfter = ''; - contentAfter += '
'+aliasUrlsHelp+keepURLFragmentSelectHTML; - $(n).html(contentAfter).find('select').val(keepURLFragmentsForSite); - } - else if(idName == 'excludedIps') - { - var contentAfter = ''; - contentAfter += '
'+excludedIpHelp; - $(n).html(contentAfter); - } - else if(idName == 'excludedQueryParameters') - { - var contentAfter = ''; - contentAfter += '
'+excludedQueryParametersHelp; - $(n).html(contentAfter); - } - else if (idName == 'excludedUserAgents') - { - var contentAfter = '
'+excludedUserAgentsHelp; - $(n).html(contentAfter); - } - else if(idName == 'timezone') - { - var contentAfter = getTimezoneSelector(contentBefore); - contentAfter += '
' + timezoneHelp; - $(n).html(contentAfter); - } - else if(idName == 'currency') - { - var contentAfter = getCurrencySelector(contentBefore); - contentAfter += '
' + currencyHelp; - $(n).html(contentAfter); - } - else if(idName == 'ecommerce') - { - ecommerceActive = contentBefore.indexOf("ecommerceActive") > 0 ? 1 : 0; - contentAfter = getEcommerceSelector(ecommerceActive) + '
' + ecommerceHelp; - $(n).html(contentAfter); - } - else if(idName == 'sitesearch') { - contentAfter = getSitesearchSelector(contentBefore); - $(n).html(contentAfter); - onClickSiteSearchUseDefault(); + .appendTo('#editSites') + ; + + piwikHelper.lazyScrollTo('#' + newRowId); + + $('.addsite').click(function () { + sendAddSiteAJAX($('tr#' + newRowId)); + }); + + $('.cancel').click(function () { + piwikHelper.hideAjaxError(); + $(this).parents('tr').remove(); + $('.addRowSite').toggle(); + }); + return false; + }); + + // when click on deleteuser, the we ask for confirmation and then delete the user + $('.deleteSite').click(function () { + piwikHelper.hideAjaxError(); + var idRow = $(this).attr('id'); + 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 + ')')); + piwikHelper.modalConfirm('#confirm', {yes: function () { + sendDeleteSiteAJAX(idsiteToDelete); + }}); + } + ); + + var alreadyEdited = new Array; + $('.editSite') + .click(function () { + piwikHelper.hideAjaxError(); + var idRow = $(this).attr('id'); + if (alreadyEdited[idRow] == 1) return; + if (siteBeingEdited) { + $('#alert h2').text(sprintf(_pk_translate('SitesManager_OnlyOneSiteAtTime_js'), '"' + $("
").html(siteBeingEditedName).text() + '"')); + piwikHelper.modalConfirm('#alert', {}); + return; + } + siteBeingEdited = true; + + alreadyEdited[idRow] = 1; + $('tr#' + idRow + ' .editableSite').each( + // make the fields editable + // change the EDIT button to VALID button + function (i, n) { + var contentBefore = $(n).html(); + + var idName = $(n).attr('id'); + if (idName == 'siteName') { + siteBeingEditedName = contentBefore; + var contentAfter = ''; + + var inputSave = $('
') + .click(function () { submitUpdateSite($(this).parent()); }); + var spanCancel = $('

' + sprintf(_pk_translate('General_OrCancel_js'), "", "") + '
') + .click(function () { piwikHelper.refreshAfter(0); }); + $(n) + .html(contentAfter) + .keypress(submitSiteOnEnter) + .append(inputSave) + .append(spanCancel); + } + else if (idName == 'urls') { + var keepURLFragmentsForSite = $(this).closest('tr').attr('data-keep-url-fragments'); + + var contentAfter = ''; + contentAfter += '
' + aliasUrlsHelp + keepURLFragmentSelectHTML; + $(n).html(contentAfter).find('select').val(keepURLFragmentsForSite); + } + else if (idName == 'excludedIps') { + var contentAfter = ''; + contentAfter += '
' + excludedIpHelp; + $(n).html(contentAfter); + } + else if (idName == 'excludedQueryParameters') { + var contentAfter = ''; + contentAfter += '
' + excludedQueryParametersHelp; + $(n).html(contentAfter); + } + else if (idName == 'excludedUserAgents') { + var contentAfter = '
' + excludedUserAgentsHelp; + $(n).html(contentAfter); + } + else if (idName == 'timezone') { + var contentAfter = getTimezoneSelector(contentBefore); + contentAfter += '
' + timezoneHelp; + $(n).html(contentAfter); + } + else if (idName == 'currency') { + var contentAfter = getCurrencySelector(contentBefore); + contentAfter += '
' + currencyHelp; + $(n).html(contentAfter); + } + else if (idName == 'ecommerce') { + ecommerceActive = contentBefore.indexOf("ecommerceActive") > 0 ? 1 : 0; + contentAfter = getEcommerceSelector(ecommerceActive) + '
' + ecommerceHelp; + $(n).html(contentAfter); + } + else if (idName == 'sitesearch') { + contentAfter = getSitesearchSelector(contentBefore); + $(n).html(contentAfter); + onClickSiteSearchUseDefault(); + } } - } - ); - $(this) - .toggle() - .parent() - .prepend( $('') - .click( function(){ sendUpdateSiteAJAX( $('tr#'+idRow) ); } ) - ); - }); - - $('#globalSettingsSubmit').click( function() { - sendGlobalSettingsAJAX(); - }); - - $('#defaultTimezone').html( getTimezoneSelector(defaultTimezone)); - $('#defaultCurrency').html( getCurrencySelector(defaultCurrency)); - - $('td.editableSite').click( function(){ $(this).parent().find('.editSite').click(); } ); - } - - function getSitesearchSelector(contentBefore) - { + ); + $(this) + .toggle() + .parent() + .prepend($('') + .click(function () { sendUpdateSiteAJAX($('tr#' + idRow)); }) + ); + }); + + $('#globalSettingsSubmit').click(function () { + sendGlobalSettingsAJAX(); + }); + + $('#defaultTimezone').html(getTimezoneSelector(defaultTimezone)); + $('#defaultCurrency').html(getCurrencySelector(defaultCurrency)); + + $('td.editableSite').click(function () { $(this).parent().find('.editSite').click(); }); + } + + function getSitesearchSelector(contentBefore) { var globalKeywordParameters = $('input#globalSearchKeywordParameters').val().trim(); var globalCategoryParameters = $('input#globalSearchCategoryParameters').val().trim(); - if(contentBefore) { + if (contentBefore) { enabled = contentBefore.indexOf("sitesearchActive") > 0 ? 1 : 0; spanSearch = $(contentBefore).filter('.sskp'); var searchKeywordParameters = spanSearch.attr('sitesearch_keyword_parameters').trim(); @@ -341,103 +327,96 @@ function SitesManager ( _timezones, _currencies, _defaultTimezone, _defaultCurre html += ''; html += '
'; - if(searchGlobalHasValues) - { + if (searchGlobalHasValues) { checkedStr = checked ? ' checked ' : ''; - html += '
'; return html; } - function getEcommerceSelector(enabled) - { - var html = ''; - return html; - } - - function getTimezoneSelector(selectedTimezone) - { - var html = ''; - return html; - } - - - function getCurrencySelector(selectedCurrency) - { - var html = ''; - return html; - } - - function submitSiteOnEnter(e) - { - var key=e.keyCode || e.which; - if (key==13) - { - submitUpdateSite(this); - $(this).find('.addsite').click(); - } - } - function submitUpdateSite(self) - { - $(self).parent().find('.updateSite').click(); - } + function getEcommerceSelector(enabled) { + var html = ''; + return html; + } + + function getTimezoneSelector(selectedTimezone) { + var html = ''; + return html; + } + + + function getCurrencySelector(selectedCurrency) { + var html = ''; + return html; + } + + function submitSiteOnEnter(e) { + var key = e.keyCode || e.which; + if (key == 13) { + submitUpdateSite(this); + $(this).find('.addsite').click(); + } + } + + function submitUpdateSite(self) { + $(self).parent().find('.updateSite').click(); + } } -function onClickSiteSearchUseDefault() -{ +function onClickSiteSearchUseDefault() { // Site Search enabled - if($('select#sitesearch').val() == "1") { + if ($('select#sitesearch').val() == "1") { $('#sitesearchUseDefault').show(); // Use default is checked - if($('#sitesearchUseDefaultCheck').is(':checked')) { + if ($('#sitesearchUseDefaultCheck').is(':checked')) { $('#searchSiteParameters').hide(); $('#sitesearchIntro').show(); $('#searchKeywordParameters,#searchCategoryParameters').val(''); $('.searchDisplayParams').show(); - // Use default is unchecked + // Use default is unchecked } else { $('#sitesearchIntro').hide(); diff --git a/plugins/SitesManager/templates/SitesManager.tpl b/plugins/SitesManager/templates/SitesManager.tpl index 775e82f816..f720fc2f21 100644 --- a/plugins/SitesManager/templates/SitesManager.tpl +++ b/plugins/SitesManager/templates/SitesManager.tpl @@ -2,322 +2,410 @@ {loadJavascriptTranslations plugins='SitesManager'} - + {/literal}

{'SitesManager_WebsitesManagement'|translate}

{'SitesManager_MainDescription'|translate} -{'SitesManager_YouCurrentlyHaveAccessToNWebsites'|translate:"$adminSitesCount"} -{if $isSuperUser} -
{'SitesManager_SuperUserCan'|translate:"":""} -{/if} + {'SitesManager_YouCurrentlyHaveAccessToNWebsites'|translate:"$adminSitesCount"} + {if $isSuperUser} +
+ {'SitesManager_SuperUserCan'|translate:"":""} + {/if}

{ajaxErrorDiv} {ajaxLoadingDiv} {capture assign=createNewWebsite} -
{'SitesManager_AddSite'|translate}
+
{'SitesManager_AddSite'|translate}
{/capture} {if $adminSites|@count == 0} - {'SitesManager_NoWebsites'|translate} + {'SitesManager_NoWebsites'|translate} {else}

- - + + +
+
+ {if $isSuperUser} + {$createNewWebsite} + {/if} + + + + + + + + + + + + + + + + + + + + {foreach from=$adminSites key=i item=site} + + + + + + + + + + + + + + + + {/foreach} + +
{'General_Id'|translate}{'General_Name'|translate}{'SitesManager_Urls'|translate}{'SitesManager_ExcludedIps'|translate}{'SitesManager_ExcludedParameters'|translate|replace:" ":"
"}
{'SitesManager_ExcludedUserAgents'|translate}{'Actions_SubmenuSitesearch'|translate}{'SitesManager_Timezone'|translate}{'SitesManager_Currency'|translate}{'Goals_Ecommerce'|translate} {'SitesManager_JsTrackingTag'|translate}
{$site.idsite}{$site.name}{foreach from=$site.alias_urls item=url}{$url|replace:"http://":""}
{/foreach}
{foreach from=$site.excluded_ips item=ip}{$ip}
{/foreach}
{foreach from=$site.excluded_parameters item=parameter}{$parameter}
{/foreach} +
{foreach from=$site.excluded_user_agents item=ua}{$ua}
{/foreach} +
{if $site.sitesearch}{'General_Yes'|translate}{else}-{/if}{$site.timezone}{$site.currency}{if $site.ecommerce}{'General_Yes'|translate}{else} + - + {/if} {'General_Edit'|translate} {'General_Delete'|translate} + {'SitesManager_ShowTrackingTag'|translate} +
+ {if $isSuperUser} + {$createNewWebsite} + {/if}
- -
- {if $isSuperUser} - {$createNewWebsite} - {/if} - - - - - - - - - - - - - - - - - - - - {foreach from=$adminSites key=i item=site} - - - - - - - - - - - - - - - - {/foreach} - -
{'General_Id'|translate}{'General_Name'|translate}{'SitesManager_Urls'|translate}{'SitesManager_ExcludedIps'|translate}{'SitesManager_ExcludedParameters'|translate|replace:" ":"
"}
{'SitesManager_ExcludedUserAgents'|translate}{'Actions_SubmenuSitesearch'|translate}{'SitesManager_Timezone'|translate}{'SitesManager_Currency'|translate}{'Goals_Ecommerce'|translate} {'SitesManager_JsTrackingTag'|translate}
{$site.idsite}{$site.name}{foreach from=$site.alias_urls item=url}{$url|replace:"http://":""}
{/foreach}
{foreach from=$site.excluded_ips item=ip}{$ip}
{/foreach}
{foreach from=$site.excluded_parameters item=parameter}{$parameter}
{/foreach}
{foreach from=$site.excluded_user_agents item=ua}{$ua}
{/foreach}
{if $site.sitesearch}{'General_Yes'|translate}{else}-{/if}{$site.timezone}{$site.currency}{if $site.ecommerce}{'General_Yes'|translate}{else}-{/if} {'General_Edit'|translate} {'General_Delete'|translate}{'SitesManager_ShowTrackingTag'|translate}
- {if $isSuperUser} - {$createNewWebsite} - {/if} -
{/if} {* Admin users use these values for Site Search column, when editing websites *} {if !$isSuperUser} - - + + {/if} {if $isSuperUser} -
- -

{'SitesManager_GlobalWebsitesSettings'|translate}

-
- - - - - - - - - - {* global excluded user agents *} - - - - - - - {* global keep URL fragments *} - - - {* global site search *} - - - - - - - - - - - -
- {'SitesManager_GlobalListExcludedIps'|translate} -

{'SitesManager_ListOfIpsToBeExcludedOnAllWebsites'|translate}

-
- - - -
- {'SitesManager_GlobalListExcludedQueryParameters'|translate} -

{'SitesManager_ListOfQueryParametersToBeExcludedOnAllWebsites'|translate}

-
- - -
- {'SitesManager_GlobalListExcludedUserAgents'|translate} -

{'SitesManager_GlobalListExcludedUserAgents_Desc'|translate}

-
- - -
- - - {'SitesManager_EnableSiteSpecificUserAgentExclude_Help'|translate:'':''|inlineHelp} -
- {'SitesManager_KeepURLFragments'|translate} -

{'SitesManager_KeepURLFragmentsHelp'|translate:"#":"example.org/index.html#first_section":"example.org/index.html"} -

- - -

{'SitesManager_KeepURLFragmentsHelp2'|translate}

-
- {'SitesManager_TrackingSiteSearch'|translate} -

{$sitesearchIntro}

- {'SitesManager_SearchParametersNote'|translate} {'SitesManager_SearchParametersNote2'|translate} -
- -
- {if !$isSearchCategoryTrackingEnabled} - - Note: you could also track your Internal Search Engine Categories, but the plugin Custom Variables is required. Please enable the plugin CustomVariables (or ask your Piwik admin). - {else} - {'Goals_Optional'|translate} {'SitesManager_SearchCategoryDesc'|translate}
-
- - {/if} -
- {'SitesManager_DefaultTimezoneForNewWebsites'|translate} -

{'SitesManager_SelectDefaultTimezone'|translate}

-
-
-
- {$defaultTimezoneHelp} -
- {'SitesManager_DefaultCurrencyForNewWebsites'|translate} -

{'SitesManager_SelectDefaultCurrency'|translate}

-
-
-
- {$currencyHelpPlain} -
- - +
+ +

{'SitesManager_GlobalWebsitesSettings'|translate}

+
+ + + + + + + + + + + + + + + + + + + + {* global excluded user agents *} + + + + + + + + + + + + + + + {* global keep URL fragments *} + + + + + {* global site search *} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ {'SitesManager_GlobalListExcludedIps'|translate} + +

{'SitesManager_ListOfIpsToBeExcludedOnAllWebsites'|translate}

+
+ + + +
+ {'SitesManager_GlobalListExcludedQueryParameters'|translate} + +

{'SitesManager_ListOfQueryParametersToBeExcludedOnAllWebsites'|translate}

+
+ + +
+ {'SitesManager_GlobalListExcludedUserAgents'|translate} + +

{'SitesManager_GlobalListExcludedUserAgents_Desc'|translate}

+
+ + +
+ + + {'SitesManager_EnableSiteSpecificUserAgentExclude_Help'|translate:'':''|inlineHelp} +
+ {'SitesManager_KeepURLFragments'|translate} + +

{'SitesManager_KeepURLFragmentsHelp'|translate:"#":"example.org/index.html#first_section":"example.org/index.html"} +

+ + + +

{'SitesManager_KeepURLFragmentsHelp2'|translate}

+
+ {'SitesManager_TrackingSiteSearch'|translate} + +

{$sitesearchIntro}

+ {'SitesManager_SearchParametersNote'|translate} {'SitesManager_SearchParametersNote2'|translate} +
+ +
+ {if !$isSearchCategoryTrackingEnabled} + + Note: you could also track your Internal Search Engine Categories, but the plugin Custom Variables is required. Please enable the plugin CustomVariables (or ask your Piwik admin). + {else} + {'Goals_Optional'|translate} {'SitesManager_SearchCategoryDesc'|translate}
+
+ + {/if} +
+ {'SitesManager_DefaultTimezoneForNewWebsites'|translate} + +

{'SitesManager_SelectDefaultTimezone'|translate}

+
+
+
+ {$defaultTimezoneHelp} +
+ {'SitesManager_DefaultCurrencyForNewWebsites'|translate} + +

{'SitesManager_SelectDefaultCurrency'|translate}

+
+
+
+ {$currencyHelpPlain} +
+ + - {ajaxErrorDiv id=ajaxErrorGlobalSettings} - {ajaxLoadingDiv id=ajaxLoadingGlobalSettings} + {ajaxErrorDiv id=ajaxErrorGlobalSettings} + {ajaxLoadingDiv id=ajaxLoadingGlobalSettings} {/if} {if $showAddSite} - + {/if} -



+



{include file="CoreAdminHome/templates/footer.tpl"} diff --git a/plugins/Transitions/API.php b/plugins/Transitions/API.php index ca178a02a2..67e4735dbd 100644 --- a/plugins/Transitions/API.php +++ b/plugins/Transitions/API.php @@ -1,10 +1,10 @@ getTransitionsForAction($pageTitle, 'title', $idSite, $period, $date, $segment, $limitBeforeGrouping); - } - - public function getTransitionsForPageUrl($pageUrl, $idSite, $period, $date, $segment = false, $limitBeforeGrouping = false) - { - return $this->getTransitionsForAction($pageUrl, 'url', $idSite, $period, $date, $segment, $limitBeforeGrouping); - } - - /** - * General method to get transitions for an action - * - * @param $actionName - * @param $actionType "url"|"title" - * @param $idSite - * @param $period - * @param $date - * @param bool $segment - * @param bool $limitBeforeGrouping - * @param string $parts - * @param bool $returnNormalizedUrls - * @return array - * @throws Exception - */ - public function getTransitionsForAction($actionName, $actionType, $idSite, $period, $date, - $segment = false, $limitBeforeGrouping = false, $parts = 'all', $returnNormalizedUrls = false) - { - Piwik::checkUserHasViewAccess($idSite); - - // get idaction of the requested action - $idaction = $this->deriveIdAction($actionName, $actionType); - if ($idaction < 0) - { - throw new Exception('NoDataForAction'); - } - - // 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(); - - // prepare the report - $report = array( - 'date' => Piwik_Period_Day::advancedFactory($period, $date)->getLocalizedShortString() - ); - - // add data to the report - $transitionsArchiving = new Piwik_Transitions; - if ($returnNormalizedUrls) - { - $transitionsArchiving->returnNormalizedUrls(); - } - - $partsArray = explode(',', $parts); - - if ($parts == 'all' || in_array('internalReferrers', $partsArray)) - { - $this->addInternalReferrers($transitionsArchiving, $archiveProcessing, $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); - } - if ($parts == 'all' || in_array('externalReferrers', $partsArray)) - { - $this->addExternalReferrers($transitionsArchiving, $archiveProcessing, $report, $idaction, - $actionType, $limitBeforeGrouping); - } - - // derive the number of exits from the other metrics - if ($parts == 'all') { - $report['pageMetrics']['exits'] = $report['pageMetrics']['pageviews'] - - $transitionsArchiving->getTotalTransitionsToFollowingActions() - - $report['pageMetrics']['loops']; - } - - // replace column names in the data tables - $reportNames = array( - 'previousPages' => true, - 'previousSiteSearches' => false, - 'followingPages' => true, - 'followingSiteSearches' => false, - 'outlinks' => true, - 'downloads' => true - ); - foreach ($reportNames as $reportName => $replaceLabel) - { - if (isset($report[$reportName])) - { - $columnNames = array(Piwik_Archive::INDEX_NB_ACTIONS => 'referrals'); - if ($replaceLabel) - { - $columnNames[Piwik_Archive::INDEX_NB_ACTIONS] = 'referrals'; - } - $report[$reportName]->filter('ReplaceColumnNames', array($columnNames)); - } - } - - return $report; - } - - /** - * Derive the action ID from the request action name and type. - */ - private function deriveIdAction($actionName, $actionType) - { - $actionsPlugin = new Piwik_Actions; - switch ($actionType) - { - case 'url': - $originalActionName = $actionName; - $actionName = Piwik_Common::unsanitizeInputValue($actionName); - $id = $actionsPlugin->getIdActionFromSegment($actionName, 'idaction_url'); - - if ($id < 0) - { - // an example where this is needed is urls containing < or > - $actionName = $originalActionName; - $id = $actionsPlugin->getIdActionFromSegment($actionName, 'idaction_url'); - } - - return $id; - - case 'title': - $id = $actionsPlugin->getIdActionFromSegment($actionName, 'idaction_name'); - - if ($id < 0) - { - $unkown = Piwik_Actions_ArchivingHelper::getUnknownActionName( - Piwik_Tracker_Action::TYPE_ACTION_NAME); - - if (trim($actionName) == trim($unkown)) - { - $id = $actionsPlugin->getIdActionFromSegment('', 'idaction_name'); - } - } - - return $id; - - default: - throw new Exception('Unknown action type'); - } - } - - /** - * Add the internal referrers to the report: - * previous pages and previous site searches - * - * @param Piwik_Transitions $transitionsArchiving - * @param $archiveProcessing - * @param $report - * @param $idaction - * @param string $actionType - * @param $limitBeforeGrouping - * @throws Exception - */ - private function addInternalReferrers($transitionsArchiving, $archiveProcessing, &$report, - $idaction, $actionType, $limitBeforeGrouping) { - - $data = $transitionsArchiving->queryInternalReferrers( - $idaction, $actionType, $archiveProcessing, $limitBeforeGrouping); - - if ($data['pageviews'] == 0) - { - throw new Exception('NoDataForAction'); - } - - $report['previousPages'] = &$data['previousPages']; - $report['previousSiteSearches'] = &$data['previousSiteSearches']; - $report['pageMetrics']['loops'] = $data['loops']; - $report['pageMetrics']['pageviews'] = $data['pageviews']; - } - - /** - * Add the following actions to the report: - * following pages, downloads, outlinks - * - * @param Piwik_Transitions $transitionsArchiving - * @param $archiveProcessing - * @param $report - * @param $idaction - * @param string $actionType - * @param $limitBeforeGrouping - * @param boolean $includeLoops - */ - private function addFollowingActions($transitionsArchiving, $archiveProcessing, &$report, - $idaction, $actionType, $limitBeforeGrouping, $includeLoops=false) { - - $data = $transitionsArchiving->queryFollowingActions( - $idaction, $actionType, $archiveProcessing, $limitBeforeGrouping, $includeLoops); - - foreach ($data as $tableName => $table) - { - $report[$tableName] = $table; - } - } - - /** - * Add the external referrers to the report: - * direct entries, websites, campaigns, search engines - * - * @param Piwik_Transitions $transitionsArchiving - * @param $archiveProcessing - * @param $report - * @param $idaction - * @param string $actionType - * @param $limitBeforeGrouping - */ - private function addExternalReferrers($transitionsArchiving, $archiveProcessing, &$report, - $idaction, $actionType, $limitBeforeGrouping) { - - $data = $transitionsArchiving->queryExternalReferrers( - $idaction, $actionType, $archiveProcessing, $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); - if ($visits) - { - // load details (i.e. subtables) - $details = array(); - if ($idSubTable = $row->getIdSubDataTable()) - { - $subTable = Piwik_DataTable_Manager::getInstance()->getTable($idSubTable); - foreach ($subTable->getRows() as $subRow) - { - $details[] = array( - 'label' => $subRow->getColumn('label'), - 'referrals' => $subRow->getColumn(Piwik_Archive::INDEX_NB_VISITS) - ); - } - } - $report['referrers'][] = array( - 'label' => $this->getReferrerLabel($referrerId), - 'shortName' => Piwik_getRefererTypeFromShortName($referrerId), - 'visits' => $visits, - 'details' => $details - ); - $report['pageMetrics']['entries'] += $visits; - } - } - - // if there's no data for referrers, Piwik_API_ResponseBuilder::handleMultiDimensionalArray - // does not detect the multi dimensional array and the data is rendered differently, which - // causes an exception. - if (count($report['referrers']) == 0) - { - $report['referrers'][] = array( - 'label' => $this->getReferrerLabel(Piwik_Common::REFERER_TYPE_DIRECT_ENTRY), - 'shortName' => Piwik_getRefererTypeLabel(Piwik_Common::REFERER_TYPE_DIRECT_ENTRY), - 'visits' => 0 - ); - } - } - - private function getReferrerLabel($referrerId) { - switch ($referrerId) - { - case Piwik_Common::REFERER_TYPE_DIRECT_ENTRY: - return Piwik_Transitions_Controller::getTranslation('directEntries'); - case Piwik_Common::REFERER_TYPE_SEARCH_ENGINE: - return Piwik_Transitions_Controller::getTranslation('fromSearchEngines'); - case Piwik_Common::REFERER_TYPE_WEBSITE: - return Piwik_Transitions_Controller::getTranslation('fromWebsites'); - case Piwik_Common::REFERER_TYPE_CAMPAIGN: - return Piwik_Transitions_Controller::getTranslation('fromCampaigns'); - default: - return Piwik_Translate('General_Others'); - } - } - - public function getTranslations() { - $controller = new Piwik_Transitions_Controller(); - return $controller->getTranslations(); - } - + + static private $instance = null; + + static public function getInstance() + { + if (self::$instance == null) { + self::$instance = new self; + } + return self::$instance; + } + + public function getTransitionsForPageTitle($pageTitle, $idSite, $period, $date, $segment = false, $limitBeforeGrouping = false) + { + return $this->getTransitionsForAction($pageTitle, 'title', $idSite, $period, $date, $segment, $limitBeforeGrouping); + } + + public function getTransitionsForPageUrl($pageUrl, $idSite, $period, $date, $segment = false, $limitBeforeGrouping = false) + { + return $this->getTransitionsForAction($pageUrl, 'url', $idSite, $period, $date, $segment, $limitBeforeGrouping); + } + + /** + * General method to get transitions for an action + * + * @param $actionName + * @param $actionType "url"|"title" + * @param $idSite + * @param $period + * @param $date + * @param bool $segment + * @param bool $limitBeforeGrouping + * @param string $parts + * @param bool $returnNormalizedUrls + * @return array + * @throws Exception + */ + public function getTransitionsForAction($actionName, $actionType, $idSite, $period, $date, + $segment = false, $limitBeforeGrouping = false, $parts = 'all', $returnNormalizedUrls = false) + { + Piwik::checkUserHasViewAccess($idSite); + + // get idaction of the requested action + $idaction = $this->deriveIdAction($actionName, $actionType); + if ($idaction < 0) { + throw new Exception('NoDataForAction'); + } + + // 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(); + + // prepare the report + $report = array( + 'date' => Piwik_Period_Day::advancedFactory($period, $date)->getLocalizedShortString() + ); + + // add data to the report + $transitionsArchiving = new Piwik_Transitions; + if ($returnNormalizedUrls) { + $transitionsArchiving->returnNormalizedUrls(); + } + + $partsArray = explode(',', $parts); + + if ($parts == 'all' || in_array('internalReferrers', $partsArray)) { + $this->addInternalReferrers($transitionsArchiving, $archiveProcessing, $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); + } + if ($parts == 'all' || in_array('externalReferrers', $partsArray)) { + $this->addExternalReferrers($transitionsArchiving, $archiveProcessing, $report, $idaction, + $actionType, $limitBeforeGrouping); + } + + // derive the number of exits from the other metrics + if ($parts == 'all') { + $report['pageMetrics']['exits'] = $report['pageMetrics']['pageviews'] + - $transitionsArchiving->getTotalTransitionsToFollowingActions() + - $report['pageMetrics']['loops']; + } + + // replace column names in the data tables + $reportNames = array( + 'previousPages' => true, + 'previousSiteSearches' => false, + 'followingPages' => true, + 'followingSiteSearches' => false, + 'outlinks' => true, + 'downloads' => true + ); + foreach ($reportNames as $reportName => $replaceLabel) { + if (isset($report[$reportName])) { + $columnNames = array(Piwik_Archive::INDEX_NB_ACTIONS => 'referrals'); + if ($replaceLabel) { + $columnNames[Piwik_Archive::INDEX_NB_ACTIONS] = 'referrals'; + } + $report[$reportName]->filter('ReplaceColumnNames', array($columnNames)); + } + } + + return $report; + } + + /** + * Derive the action ID from the request action name and type. + */ + private function deriveIdAction($actionName, $actionType) + { + $actionsPlugin = new Piwik_Actions; + switch ($actionType) { + case 'url': + $originalActionName = $actionName; + $actionName = Piwik_Common::unsanitizeInputValue($actionName); + $id = $actionsPlugin->getIdActionFromSegment($actionName, 'idaction_url'); + + if ($id < 0) { + // an example where this is needed is urls containing < or > + $actionName = $originalActionName; + $id = $actionsPlugin->getIdActionFromSegment($actionName, 'idaction_url'); + } + + return $id; + + case 'title': + $id = $actionsPlugin->getIdActionFromSegment($actionName, 'idaction_name'); + + if ($id < 0) { + $unkown = Piwik_Actions_ArchivingHelper::getUnknownActionName( + Piwik_Tracker_Action::TYPE_ACTION_NAME); + + if (trim($actionName) == trim($unkown)) { + $id = $actionsPlugin->getIdActionFromSegment('', 'idaction_name'); + } + } + + return $id; + + default: + throw new Exception('Unknown action type'); + } + } + + /** + * Add the internal referrers to the report: + * previous pages and previous site searches + * + * @param Piwik_Transitions $transitionsArchiving + * @param $archiveProcessing + * @param $report + * @param $idaction + * @param string $actionType + * @param $limitBeforeGrouping + * @throws Exception + */ + private function addInternalReferrers($transitionsArchiving, $archiveProcessing, &$report, + $idaction, $actionType, $limitBeforeGrouping) + { + + $data = $transitionsArchiving->queryInternalReferrers( + $idaction, $actionType, $archiveProcessing, $limitBeforeGrouping); + + if ($data['pageviews'] == 0) { + throw new Exception('NoDataForAction'); + } + + $report['previousPages'] = & $data['previousPages']; + $report['previousSiteSearches'] = & $data['previousSiteSearches']; + $report['pageMetrics']['loops'] = $data['loops']; + $report['pageMetrics']['pageviews'] = $data['pageviews']; + } + + /** + * Add the following actions to the report: + * following pages, downloads, outlinks + * + * @param Piwik_Transitions $transitionsArchiving + * @param $archiveProcessing + * @param $report + * @param $idaction + * @param string $actionType + * @param $limitBeforeGrouping + * @param boolean $includeLoops + */ + private function addFollowingActions($transitionsArchiving, $archiveProcessing, &$report, + $idaction, $actionType, $limitBeforeGrouping, $includeLoops = false) + { + + $data = $transitionsArchiving->queryFollowingActions( + $idaction, $actionType, $archiveProcessing, $limitBeforeGrouping, $includeLoops); + + foreach ($data as $tableName => $table) { + $report[$tableName] = $table; + } + } + + /** + * Add the external referrers to the report: + * direct entries, websites, campaigns, search engines + * + * @param Piwik_Transitions $transitionsArchiving + * @param $archiveProcessing + * @param $report + * @param $idaction + * @param string $actionType + * @param $limitBeforeGrouping + */ + private function addExternalReferrers($transitionsArchiving, $archiveProcessing, &$report, + $idaction, $actionType, $limitBeforeGrouping) + { + + $data = $transitionsArchiving->queryExternalReferrers( + $idaction, $actionType, $archiveProcessing, $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); + if ($visits) { + // load details (i.e. subtables) + $details = array(); + if ($idSubTable = $row->getIdSubDataTable()) { + $subTable = Piwik_DataTable_Manager::getInstance()->getTable($idSubTable); + foreach ($subTable->getRows() as $subRow) { + $details[] = array( + 'label' => $subRow->getColumn('label'), + 'referrals' => $subRow->getColumn(Piwik_Archive::INDEX_NB_VISITS) + ); + } + } + $report['referrers'][] = array( + 'label' => $this->getReferrerLabel($referrerId), + 'shortName' => Piwik_getRefererTypeFromShortName($referrerId), + 'visits' => $visits, + 'details' => $details + ); + $report['pageMetrics']['entries'] += $visits; + } + } + + // if there's no data for referrers, Piwik_API_ResponseBuilder::handleMultiDimensionalArray + // does not detect the multi dimensional array and the data is rendered differently, which + // causes an exception. + if (count($report['referrers']) == 0) { + $report['referrers'][] = array( + 'label' => $this->getReferrerLabel(Piwik_Common::REFERER_TYPE_DIRECT_ENTRY), + 'shortName' => Piwik_getRefererTypeLabel(Piwik_Common::REFERER_TYPE_DIRECT_ENTRY), + 'visits' => 0 + ); + } + } + + private function getReferrerLabel($referrerId) + { + switch ($referrerId) { + case Piwik_Common::REFERER_TYPE_DIRECT_ENTRY: + return Piwik_Transitions_Controller::getTranslation('directEntries'); + case Piwik_Common::REFERER_TYPE_SEARCH_ENGINE: + return Piwik_Transitions_Controller::getTranslation('fromSearchEngines'); + case Piwik_Common::REFERER_TYPE_WEBSITE: + return Piwik_Transitions_Controller::getTranslation('fromWebsites'); + case Piwik_Common::REFERER_TYPE_CAMPAIGN: + return Piwik_Transitions_Controller::getTranslation('fromCampaigns'); + default: + return Piwik_Translate('General_Others'); + } + } + + public function getTranslations() + { + $controller = new Piwik_Transitions_Controller(); + return $controller->getTranslations(); + } + } \ No newline at end of file diff --git a/plugins/Transitions/Controller.php b/plugins/Transitions/Controller.php index 86e093c523..5ef2dc539e 100644 --- a/plugins/Transitions/Controller.php +++ b/plugins/Transitions/Controller.php @@ -1,10 +1,10 @@ 'Transitions_PageviewsInline', - 'loopsInline' => 'Transitions_LoopsInline', - 'fromPreviousPages' => 'Transitions_FromPreviousPages', - 'fromPreviousPagesInline' => 'Transitions_FromPreviousPagesInline', - 'fromPreviousSiteSearches' => 'Transitions_FromPreviousSiteSearches', - 'fromPreviousSiteSearchesInline' => 'Transitions_FromPreviousSiteSearchesInline', - 'fromSearchEngines' => 'Transitions_FromSearchEngines', - 'fromSearchEnginesInline' => 'Transitions_FromSearchEnginesInline', - 'fromWebsites' => 'Transitions_FromWebsites', - 'fromWebsitesInline' => 'Transitions_FromWebsitesInline', - 'fromCampaigns' => 'Transitions_FromCampaigns', - 'fromCampaignsInline' => 'Transitions_FromCampaignsInline', - 'directEntries' => 'Transitions_DirectEntries', - 'directEntriesInline' => 'Referers_TypeDirectEntries', - 'toFollowingPages' => 'Transitions_ToFollowingPages', - 'toFollowingPagesInline' => 'Transitions_ToFollowingPagesInline', - 'toFollowingSiteSearches' => 'Transitions_ToFollowingSiteSearches', - 'toFollowingSiteSearchesInline' => 'Transitions_ToFollowingSiteSearchesInline', - 'downloads' => 'Actions_ColumnDownloads', - 'downloadsInline' => 'VisitsSummary_NbDownloadsDescription', - 'outlinks' => 'Actions_ColumnOutlinks', - 'outlinksInline' => 'VisitsSummary_NbOutlinksDescription', - 'exits' => 'General_ColumnExits', - 'exitsInline' => 'Transitions_ExitsInline', - 'bouncesInline' => 'Transitions_BouncesInline' - ); - /** - * Translations that are added to JS - * (object Piwik_Transitions_Translations) - */ - private static $jsTranslations = array( - 'XOfY' => 'Transitions_XOutOfYVisits', - 'XOfAllPageviews' => 'Transitions_XOfAllPageviews', - 'NoDataForAction' => 'Transitions_NoDataForAction', - 'NoDataForActionDetails' => 'Transitions_NoDataForActionDetails', - 'NoDataForActionBack' => 'Transitions_ErrorBack', - 'ShareOfAllPageviews' => 'Transitions_ShareOfAllPageviews', - 'DateRange' => 'General_DateRange' - ); - - public static function getTranslation($key) - { - return Piwik_Translate(self::$metricTranslations[$key]); - } - - /** - * The main method of the plugin. - * It is triggered from the Transitions data table action. - */ - public function renderPopover() - { - $view = Piwik_View::factory('transitions'); - $view->translations = $this->getTranslations(); - echo $view->render(); - } - - public function getTranslations() - { - $translations = self::$metricTranslations + self::$jsTranslations; - foreach ($translations as &$message) { - $message = Piwik_Translate($message); - } - return $translations; - } - + /** + * Since the metric translations are taken from different plugins, + * it makes the rest of the code easier to read and maintain when we + * use this indirection to map between the metrics and the actual + * translation keys. + */ + private static $metricTranslations = array( + 'pageviewsInline' => 'Transitions_PageviewsInline', + 'loopsInline' => 'Transitions_LoopsInline', + 'fromPreviousPages' => 'Transitions_FromPreviousPages', + 'fromPreviousPagesInline' => 'Transitions_FromPreviousPagesInline', + 'fromPreviousSiteSearches' => 'Transitions_FromPreviousSiteSearches', + 'fromPreviousSiteSearchesInline' => 'Transitions_FromPreviousSiteSearchesInline', + 'fromSearchEngines' => 'Transitions_FromSearchEngines', + 'fromSearchEnginesInline' => 'Transitions_FromSearchEnginesInline', + 'fromWebsites' => 'Transitions_FromWebsites', + 'fromWebsitesInline' => 'Transitions_FromWebsitesInline', + 'fromCampaigns' => 'Transitions_FromCampaigns', + 'fromCampaignsInline' => 'Transitions_FromCampaignsInline', + 'directEntries' => 'Transitions_DirectEntries', + 'directEntriesInline' => 'Referers_TypeDirectEntries', + 'toFollowingPages' => 'Transitions_ToFollowingPages', + 'toFollowingPagesInline' => 'Transitions_ToFollowingPagesInline', + 'toFollowingSiteSearches' => 'Transitions_ToFollowingSiteSearches', + 'toFollowingSiteSearchesInline' => 'Transitions_ToFollowingSiteSearchesInline', + 'downloads' => 'Actions_ColumnDownloads', + 'downloadsInline' => 'VisitsSummary_NbDownloadsDescription', + 'outlinks' => 'Actions_ColumnOutlinks', + 'outlinksInline' => 'VisitsSummary_NbOutlinksDescription', + 'exits' => 'General_ColumnExits', + 'exitsInline' => 'Transitions_ExitsInline', + 'bouncesInline' => 'Transitions_BouncesInline' + ); + + /** + * Translations that are added to JS + * (object Piwik_Transitions_Translations) + */ + private static $jsTranslations = array( + 'XOfY' => 'Transitions_XOutOfYVisits', + 'XOfAllPageviews' => 'Transitions_XOfAllPageviews', + 'NoDataForAction' => 'Transitions_NoDataForAction', + 'NoDataForActionDetails' => 'Transitions_NoDataForActionDetails', + 'NoDataForActionBack' => 'Transitions_ErrorBack', + 'ShareOfAllPageviews' => 'Transitions_ShareOfAllPageviews', + 'DateRange' => 'General_DateRange' + ); + + public static function getTranslation($key) + { + return Piwik_Translate(self::$metricTranslations[$key]); + } + + /** + * The main method of the plugin. + * It is triggered from the Transitions data table action. + */ + public function renderPopover() + { + $view = Piwik_View::factory('transitions'); + $view->translations = $this->getTranslations(); + echo $view->render(); + } + + public function getTranslations() + { + $translations = self::$metricTranslations + self::$jsTranslations; + foreach ($translations as &$message) { + $message = Piwik_Translate($message); + } + return $translations; + } + } diff --git a/plugins/Transitions/Transitions.php b/plugins/Transitions/Transitions.php index 18b45bf61b..4adaf3f997 100644 --- a/plugins/Transitions/Transitions.php +++ b/plugins/Transitions/Transitions.php @@ -8,321 +8,299 @@ * @category Piwik_Plugins * @package Piwik_Transitions */ - + /** * @package Piwik_Transitions */ class Piwik_Transitions extends Piwik_Plugin { - - private $limitBeforeGrouping = 5; - private $totalTransitionsToFollowingActions = 0; - - private $returnNormalizedUrls = false; - - public function getInformation() - { - return array( - 'description' => Piwik_Translate('Transitions_PluginDescription'), - 'author' => 'Piwik', - 'author_homepage' => 'http://piwik.org/', - 'version' => Piwik_Version::VERSION, - ); - } - - function getListHooksRegistered() - { - return array( - 'AssetManager.getCssFiles' => 'getCssFiles', - 'AssetManager.getJsFiles' => 'getJsFiles' - ); - } - - public function getCssFiles($notification) - { - $cssFiles = &$notification->getNotificationObject(); - $cssFiles[] = 'plugins/Transitions/templates/transitions.css'; - } - - public function getJsFiles($notification) - { - $jsFiles = &$notification->getNotificationObject(); - $jsFiles[] = 'plugins/Transitions/templates/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 = ' + + private $limitBeforeGrouping = 5; + private $totalTransitionsToFollowingActions = 0; + + private $returnNormalizedUrls = false; + + public function getInformation() + { + return array( + 'description' => Piwik_Translate('Transitions_PluginDescription'), + 'author' => 'Piwik', + 'author_homepage' => 'http://piwik.org/', + 'version' => Piwik_Version::VERSION, + ); + } + + function getListHooksRegistered() + { + return array( + 'AssetManager.getCssFiles' => 'getCssFiles', + 'AssetManager.getJsFiles' => 'getJsFiles' + ); + } + + public function getCssFiles($notification) + { + $cssFiles = & $notification->getNotificationObject(); + $cssFiles[] = 'plugins/Transitions/templates/transitions.css'; + } + + public function getJsFiles($notification) + { + $jsFiles = & $notification->getNotificationObject(); + $jsFiles[] = 'plugins/Transitions/templates/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) + 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 = ' + + // 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_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 + 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 = ' + + $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 + 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 = ' + $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 + 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, @@ -330,76 +308,73 @@ class Piwik_Transitions extends Piwik_Plugin ' /* 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 + 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'; - } - + } + + // 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/Transitions/templates/transitions.css b/plugins/Transitions/templates/transitions.css index 75ecacb825..b0e3b0278a 100644 --- a/plugins/Transitions/templates/transitions.css +++ b/plugins/Transitions/templates/transitions.css @@ -1,197 +1,198 @@ - #Transitions_Container { - position: relative; - z-index: 1500; - height: 550px; - text-align: left; - margin-left: 7px; + position: relative; + z-index: 1500; + height: 550px; + text-align: left; + margin-left: 7px; } .Transitions_Canvas_Container { - position: absolute; + position: absolute; } #Transitions_Canvas_Background_Left { - z-index: 1501; + z-index: 1501; } #Transitions_Canvas_Background_Right { - z-index: 1502; + z-index: 1502; } #Transitions_Canvas_Left { - z-index: 1503; + z-index: 1503; } #Transitions_Canvas_Right { - z-index: 1504; + z-index: 1504; } #Transitions_Canvas_Loops { - z-index: 1505; + z-index: 1505; } .Transitions_Text { - color: black; - font-size: 11px; - line-height: 14px; - position: absolute; - background: rgba(0,0,0,0); /* without this, IE9 triggers hover only on the text, not the box */ - z-index: 1506; - font-family: Arial, Helvetica, sans-serif; - word-wrap: break-word; - text-align: left; - cursor: default; + color: black; + font-size: 11px; + line-height: 14px; + position: absolute; + background: rgba(0, 0, 0, 0); /* without this, IE9 triggers hover only on the text, not the box */ + z-index: 1506; + font-family: Arial, Helvetica, sans-serif; + word-wrap: break-word; + text-align: left; + cursor: default; } #Transitions_CenterBox { - margin: 27px 0 0 345px; - width: 208px; - height: 373px; - background: #f7f7f7; - border: 1px solid #a9a399; - border-radius:10px; - -moz-border-radius:10px; - -webkit-border-radius:10px; - -webkit-box-shadow: 0px 0px 9px 0px #999; - -moz-box-shadow: 0px 0px 9px 0px #999; - box-shadow: 0px 0px 9px 0px #999; - z-index: 1507; + margin: 27px 0 0 345px; + width: 208px; + height: 373px; + background: #f7f7f7; + border: 1px solid #a9a399; + border-radius: 10px; + -moz-border-radius: 10px; + -webkit-border-radius: 10px; + -webkit-box-shadow: 0px 0px 9px 0px #999; + -moz-box-shadow: 0px 0px 9px 0px #999; + box-shadow: 0px 0px 9px 0px #999; + z-index: 1507; } #Transitions_CenterBox h2 { - font-size: 12px; - line-height: 16px; - padding: 10px; - border-bottom: 1px dotted #a9a399; - font-weight: bold; - overflow: hidden; - color: #255792; + font-size: 12px; + line-height: 16px; + padding: 10px; + border-bottom: 1px dotted #a9a399; + font-weight: bold; + overflow: hidden; + color: #255792; } .Transitions_Pageviews { - text-align: center; + text-align: center; } .Transitions_OutgoingTraffic { - text-align: right; + text-align: right; } .Transitions_CenterBoxMetrics { - padding: 15px 10px 0 10px; - display: none; - font-size: 12px; + padding: 15px 10px 0 10px; + display: none; + font-size: 12px; } .Transitions_CenterBoxMetrics table td { - padding: 0 0 5px 0; + padding: 0 0 5px 0; } .Transitions_CenterBoxMetrics table td.Transitions_Percentage { - padding-right: 6px; - font-weight: bold; + padding-right: 6px; + font-weight: bold; } #Transitions_CenterBox h3 { - font-weight: bold; - font-size: 12px; - margin: 15px 0 7px 0; - padding: 0; - color: #7E7363; + font-weight: bold; + font-size: 12px; + margin: 15px 0 7px 0; + padding: 0; + color: #7E7363; } #Transitions_Loops { - margin: 445px 0 0 346px; - width: 208px; - text-align: center; - line-height: 25px; - font-size: 12px; - display: none; - z-index: 1506; - cursor: default; + margin: 445px 0 0 346px; + width: 208px; + text-align: center; + line-height: 25px; + font-size: 12px; + display: none; + z-index: 1506; + cursor: default; } .Transitions_CenterBoxMetrics p { - margin: 0 0 3px 0; - padding: 0; - cursor: default; - font-size: 12px; - line-height: 15px; + margin: 0 0 3px 0; + padding: 0; + cursor: default; + font-size: 12px; + line-height: 15px; } .Transitions_CenterBoxMetrics p.Transitions_Margin { - margin-bottom: 11px; + margin-bottom: 11px; } .Transitions_CenterBoxMetrics .Transitions_Highlighted { - color: #E87500; + color: #E87500; } span.Transitions_Metric { - font-weight: bold; - cursor: default; + font-weight: bold; + cursor: default; } .Transitions_Value0 { - color: #666; + color: #666; } .Transitions_TitleOfOpenGroup { - font-size: 12px; - color: #E87500; - font-weight: bold; + font-size: 12px; + color: #E87500; + font-weight: bold; } .Transitions_BoxTextLeft, .Transitions_BoxTextRight { - width: 165px; - height: 42px; - overflow: hidden; + width: 165px; + height: 42px; + overflow: hidden; } .Transitions_BoxTextRight { - text-align: right; + text-align: right; } .Transitions_BoxTextLeft.Transitions_HasBackground, .Transitions_BoxTextRight.Transitions_HasBackground { - background-repeat: no-repeat; - height: 18px; + background-repeat: no-repeat; + height: 18px; } .Transitions_BoxTextLeft.Transitions_HasBackground { - background-position: 0 1px; - width: 175px; + background-position: 0 1px; + width: 175px; } + .Transitions_BoxTextLeft.Transitions_HasBackground span { - display: block; - padding-left: 16px; + display: block; + padding-left: 16px; } .Transitions_BoxTextRight.Transitions_HasBackground { - background-position: right 1px; + background-position: right 1px; } + .Transitions_BoxTextRight.Transitions_HasBackground span { - display: block; - padding-right: 17px; + display: block; + padding-right: 17px; } .Transitions_CurveTextLeft, .Transitions_CurveTextRight { - color: #255792; - font-weight: bold; - width: 34px; - text-align: center; - cursor: default; + color: #255792; + font-weight: bold; + width: 34px; + text-align: center; + cursor: default; } body .piwik-tooltip.Transitions_Tooltip_Small { - font-size: 11px; - padding: 3px 5px 3px 6px; - background: white; + font-size: 11px; + padding: 3px 5px 3px 6px; + background: white; } .Transitions_SingleLine { - font-size: 12px; - height: 21px; + font-size: 12px; + height: 21px; } \ No newline at end of file diff --git a/plugins/Transitions/templates/transitions.js b/plugins/Transitions/templates/transitions.js index 3f2d63db99..dc54e80d54 100644 --- a/plugins/Transitions/templates/transitions.js +++ b/plugins/Transitions/templates/transitions.js @@ -11,106 +11,106 @@ // function DataTable_RowActions_Transitions(dataTable) { - this.dataTable = dataTable; - this.transitions = null; + this.dataTable = dataTable; + this.transitions = null; } DataTable_RowActions_Transitions.prototype = new DataTable_RowAction; /** Static helper method to launch transitions from anywhere */ -DataTable_RowActions_Transitions.launchForUrl = function(url) { - broadcast.propagateNewPopoverParameter('RowAction', 'Transitions:url:' + url); +DataTable_RowActions_Transitions.launchForUrl = function (url) { + broadcast.propagateNewPopoverParameter('RowAction', 'Transitions:url:' + url); }; -DataTable_RowActions_Transitions.isPageUrlReport = function(module, action) { - return module == 'Actions' && - (action == 'getPageUrls' || action == 'getEntryPageUrls' || action == 'getExitPageUrls' || action == 'getPageUrlsFollowingSiteSearch'); +DataTable_RowActions_Transitions.isPageUrlReport = function (module, action) { + return module == 'Actions' && + (action == 'getPageUrls' || action == 'getEntryPageUrls' || action == 'getExitPageUrls' || action == 'getPageUrlsFollowingSiteSearch'); }; -DataTable_RowActions_Transitions.isPageTitleReport = function(module, action) { - return module == 'Actions' && (action == 'getPageTitles' || action == 'getPageTitlesFollowingSiteSearch'); +DataTable_RowActions_Transitions.isPageTitleReport = function (module, action) { + return module == 'Actions' && (action == 'getPageTitles' || action == 'getPageTitlesFollowingSiteSearch'); } -DataTable_RowActions_Transitions.prototype.trigger = function(tr, e, subTableLabel) { - var link = tr.find('> td:first > a').attr('href'); - link = $('