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

github.com/matomo-org/matomo.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormattab <matthieu.aubry@gmail.com>2013-03-28 03:42:39 +0400
committermattab <matthieu.aubry@gmail.com>2013-03-28 03:42:40 +0400
commitae4b03163792f0b6e933933e5d37df87dc3fd566 (patch)
treed1d7510a9728f587d3d63ebd03e4ecf3d904838b /plugins
parent158c2150f5f2e13ece459b8d131244c11b763997 (diff)
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/
Diffstat (limited to 'plugins')
-rw-r--r--plugins/API/API.php3125
-rw-r--r--plugins/API/Controller.php168
-rw-r--r--plugins/API/css/styles.css47
-rw-r--r--plugins/API/templates/listAllAPI.tpl26
-rw-r--r--plugins/Actions/API.php991
-rw-r--r--plugins/Actions/Actions.php1099
-rw-r--r--plugins/Actions/Archiving.php950
-rw-r--r--plugins/Actions/ArchivingHelper.php939
-rw-r--r--plugins/Actions/Controller.php1007
-rw-r--r--plugins/Actions/templates/indexSiteSearch.tpl20
-rwxr-xr-xplugins/Annotations/API.php691
-rwxr-xr-xplugins/Annotations/AnnotationList.php824
-rwxr-xr-xplugins/Annotations/Annotations.php96
-rwxr-xr-xplugins/Annotations/Controller.php406
-rwxr-xr-xplugins/Annotations/templates/annotation.tpl83
-rwxr-xr-xplugins/Annotations/templates/annotationManager.tpl36
-rwxr-xr-xplugins/Annotations/templates/annotations.js1194
-rwxr-xr-xplugins/Annotations/templates/annotations.tpl47
-rwxr-xr-xplugins/Annotations/templates/evolutionAnnotations.tpl18
-rwxr-xr-xplugins/Annotations/templates/styles.css216
-rw-r--r--plugins/AnonymizeIP/AnonymizeIP.php106
-rw-r--r--plugins/CoreAdminHome/API.php425
-rw-r--r--plugins/CoreAdminHome/Controller.php442
-rw-r--r--plugins/CoreAdminHome/CoreAdminHome.php214
-rw-r--r--plugins/CoreAdminHome/templates/generalSettings.js231
-rw-r--r--plugins/CoreAdminHome/templates/generalSettings.tpl456
-rw-r--r--plugins/CoreAdminHome/templates/header.tpl125
-rw-r--r--plugins/CoreAdminHome/templates/jsTrackingGenerator.css67
-rw-r--r--plugins/CoreAdminHome/templates/jsTrackingGenerator.js661
-rw-r--r--plugins/CoreAdminHome/templates/jsTrackingGenerator.tpl429
-rw-r--r--plugins/CoreAdminHome/templates/menu.css89
-rw-r--r--plugins/CoreAdminHome/templates/menu.tpl37
-rw-r--r--plugins/CoreAdminHome/templates/optOut.tpl49
-rw-r--r--plugins/CoreAdminHome/templates/styles.css165
-rw-r--r--plugins/CoreHome/Controller.php419
-rw-r--r--plugins/CoreHome/CoreHome.php162
-rw-r--r--plugins/CoreHome/DataTableRowAction/MultiRowEvolution.php134
-rw-r--r--plugins/CoreHome/DataTableRowAction/RowEvolution.php548
-rw-r--r--plugins/CoreHome/templates/autocomplete.js463
-rw-r--r--plugins/CoreHome/templates/broadcast.js449
-rw-r--r--plugins/CoreHome/templates/calendar.js1094
-rw-r--r--plugins/CoreHome/templates/cloud.css51
-rw-r--r--plugins/CoreHome/templates/cloud.tpl45
-rw-r--r--plugins/CoreHome/templates/datatable.css706
-rw-r--r--plugins/CoreHome/templates/datatable.js3739
-rw-r--r--plugins/CoreHome/templates/datatable.tpl111
-rw-r--r--plugins/CoreHome/templates/datatable_actions.tpl101
-rw-r--r--plugins/CoreHome/templates/datatable_actions_recursive.tpl71
-rw-r--r--plugins/CoreHome/templates/datatable_actions_subdatable.tpl30
-rw-r--r--plugins/CoreHome/templates/datatable_cell.tpl23
-rw-r--r--plugins/CoreHome/templates/datatable_footer.tpl198
-rw-r--r--plugins/CoreHome/templates/datatable_js.tpl7
-rw-r--r--plugins/CoreHome/templates/datatable_manager.js356
-rw-r--r--plugins/CoreHome/templates/datatable_rowactions.js540
-rw-r--r--plugins/CoreHome/templates/date.js77
-rwxr-xr-xplugins/CoreHome/templates/donate.css156
-rwxr-xr-xplugins/CoreHome/templates/donate.js282
-rwxr-xr-xplugins/CoreHome/templates/donate.tpl117
-rw-r--r--plugins/CoreHome/templates/footer.tpl2
-rw-r--r--plugins/CoreHome/templates/graph.tpl74
-rw-r--r--plugins/CoreHome/templates/header.tpl40
-rw-r--r--plugins/CoreHome/templates/header_message.tpl54
-rw-r--r--plugins/CoreHome/templates/html_report_body.tpl142
-rw-r--r--plugins/CoreHome/templates/html_report_footer.tpl2
-rw-r--r--plugins/CoreHome/templates/html_report_header.tpl55
-rw-r--r--plugins/CoreHome/templates/iframe_buster_body.tpl6
-rw-r--r--plugins/CoreHome/templates/iframe_buster_header.tpl8
-rw-r--r--plugins/CoreHome/templates/index_before_menu.tpl6
-rw-r--r--plugins/CoreHome/templates/index_content.tpl27
-rw-r--r--plugins/CoreHome/templates/jqplot.css199
-rw-r--r--plugins/CoreHome/templates/jqplot.js2600
-rw-r--r--plugins/CoreHome/templates/jquery.ui.autocomplete.css61
-rw-r--r--plugins/CoreHome/templates/js_css_includes.tpl2
-rw-r--r--plugins/CoreHome/templates/js_disabled_notice.tpl4
-rw-r--r--plugins/CoreHome/templates/js_global_variables.tpl68
-rw-r--r--plugins/CoreHome/templates/logo.tpl15
-rw-r--r--plugins/CoreHome/templates/menu.js64
-rw-r--r--plugins/CoreHome/templates/menu.tpl26
-rw-r--r--plugins/CoreHome/templates/menu_init.js6
-rwxr-xr-xplugins/CoreHome/templates/misc.js319
-rw-r--r--plugins/CoreHome/templates/period_select.tpl56
-rw-r--r--plugins/CoreHome/templates/piwik_tag.tpl59
-rw-r--r--plugins/CoreHome/templates/popover.js399
-rw-r--r--plugins/CoreHome/templates/popover_multirowevolution.tpl66
-rw-r--r--plugins/CoreHome/templates/popover_rowevolution.tpl62
-rwxr-xr-xplugins/CoreHome/templates/promo_video.tpl248
-rw-r--r--plugins/CoreHome/templates/reports_by_dimension.tpl45
-rw-r--r--plugins/CoreHome/templates/sites_selection.tpl47
-rw-r--r--plugins/CoreHome/templates/sparkline.js104
-rw-r--r--plugins/CoreHome/templates/sparkline_footer.tpl10
-rw-r--r--plugins/CoreHome/templates/styles.css213
-rw-r--r--plugins/CoreHome/templates/tooltip.js216
-rw-r--r--plugins/CoreHome/templates/top_bar.tpl12
-rw-r--r--plugins/CoreHome/templates/top_bar_hello_menu.tpl10
-rw-r--r--plugins/CoreHome/templates/top_bar_top_menu.tpl21
-rw-r--r--plugins/CoreHome/templates/top_screen.tpl4
-rw-r--r--plugins/CoreHome/templates/warning_invalid_host.tpl9
-rw-r--r--plugins/CorePluginsAdmin/Controller.php129
-rw-r--r--plugins/CorePluginsAdmin/CorePluginsAdmin.php50
-rw-r--r--plugins/CorePluginsAdmin/templates/manage.tpl105
-rw-r--r--plugins/CoreUpdater/Controller.php694
-rw-r--r--plugins/CoreUpdater/CoreUpdater.php132
-rw-r--r--plugins/CoreUpdater/templates/cli_update_database_done.tpl80
-rw-r--r--plugins/CoreUpdater/templates/cli_update_welcome.tpl44
-rw-r--r--plugins/CoreUpdater/templates/header.tpl68
-rw-r--r--plugins/CoreUpdater/templates/update_database_done.tpl123
-rw-r--r--plugins/CoreUpdater/templates/update_new_version_available.tpl29
-rw-r--r--plugins/CoreUpdater/templates/update_one_click_results.tpl19
-rw-r--r--plugins/CoreUpdater/templates/update_welcome.tpl208
-rw-r--r--plugins/CustomVariables/API.php144
-rw-r--r--plugins/CustomVariables/Controller.php78
-rw-r--r--plugins/CustomVariables/CustomVariables.php631
-rw-r--r--plugins/DBStats/API.php584
-rw-r--r--plugins/DBStats/Controller.php717
-rw-r--r--plugins/DBStats/DBStats.php128
-rwxr-xr-xplugins/DBStats/MySQLMetadataProvider.php706
-rwxr-xr-xplugins/DBStats/templates/index.tpl231
-rw-r--r--plugins/Dashboard/Controller.php137
-rw-r--r--plugins/Dashboard/Dashboard.php285
-rw-r--r--plugins/Dashboard/templates/dashboard.css266
-rw-r--r--plugins/Dashboard/templates/dashboard.js62
-rw-r--r--plugins/Dashboard/templates/dashboardObject.js182
-rwxr-xr-xplugins/Dashboard/templates/dashboardWidget.js82
-rw-r--r--plugins/Dashboard/templates/header.tpl159
-rw-r--r--plugins/Dashboard/templates/index.tpl105
-rw-r--r--plugins/Dashboard/templates/standalone.tpl13
-rw-r--r--plugins/Dashboard/templates/widgetMenu.js320
-rw-r--r--plugins/DoNotTrack/DoNotTrack.php97
-rw-r--r--plugins/ExampleAPI/API.php323
-rw-r--r--plugins/ExampleAPI/ExampleAPI.php38
-rw-r--r--plugins/ExamplePlugin/Controller.php220
-rw-r--r--plugins/ExamplePlugin/ExamplePlugin.php120
-rw-r--r--plugins/ExamplePlugin/config/local.config.sample.php6
-rw-r--r--plugins/ExamplePlugin/lang/en.php20
-rw-r--r--plugins/ExamplePlugin/templates/piwikDownloads.tpl26
-rw-r--r--plugins/ExampleRssWidget/Controller.php7
-rw-r--r--plugins/ExampleRssWidget/ExampleRssWidget.php62
-rw-r--r--plugins/ExampleRssWidget/Rss.php23
-rw-r--r--plugins/ExampleRssWidget/templates/styles.css43
-rw-r--r--plugins/ExampleUI/API.php174
-rw-r--r--plugins/ExampleUI/Controller.php234
-rw-r--r--plugins/ExampleUI/ExampleUI.php87
-rw-r--r--plugins/Feedback/Controller.php110
-rw-r--r--plugins/Feedback/Feedback.php94
-rw-r--r--plugins/Feedback/templates/feedback.js58
-rw-r--r--plugins/Feedback/templates/index.tpl103
-rw-r--r--plugins/Feedback/templates/sent.tpl31
-rw-r--r--plugins/Feedback/templates/styles.css83
-rw-r--r--plugins/Goals/API.php1061
-rw-r--r--plugins/Goals/Controller.php1083
-rw-r--r--plugins/Goals/Goals.php1738
-rw-r--r--plugins/Goals/templates/GoalForm.js230
-rw-r--r--plugins/Goals/templates/add_edit_goal.tpl109
-rw-r--r--plugins/Goals/templates/add_new_goal.tpl15
-rw-r--r--plugins/Goals/templates/form_add_goal.tpl180
-rw-r--r--plugins/Goals/templates/goals.css33
-rw-r--r--plugins/Goals/templates/list_goal_edit.tpl88
-rw-r--r--plugins/Goals/templates/list_top_dimension.tpl11
-rw-r--r--plugins/Goals/templates/overview.tpl73
-rw-r--r--plugins/Goals/templates/single_goal.tpl73
-rw-r--r--plugins/Goals/templates/title_and_evolution_graph.tpl109
-rw-r--r--plugins/ImageGraph/API.php1053
-rw-r--r--plugins/ImageGraph/Controller.php106
-rw-r--r--plugins/ImageGraph/ImageGraph.php273
-rw-r--r--plugins/ImageGraph/StaticGraph.php598
-rw-r--r--plugins/ImageGraph/StaticGraph/3DPie.php18
-rw-r--r--plugins/ImageGraph/StaticGraph/Evolution.php22
-rw-r--r--plugins/ImageGraph/StaticGraph/Exception.php86
-rw-r--r--plugins/ImageGraph/StaticGraph/GridGraph.php851
-rw-r--r--plugins/ImageGraph/StaticGraph/HorizontalBar.php346
-rw-r--r--plugins/ImageGraph/StaticGraph/Pie.php18
-rw-r--r--plugins/ImageGraph/StaticGraph/PieGraph.php217
-rw-r--r--plugins/ImageGraph/StaticGraph/VerticalBar.php32
-rw-r--r--plugins/ImageGraph/templates/debug_graphs_all_sizes.tpl91
-rw-r--r--plugins/ImageGraph/templates/index.tpl4
-rw-r--r--plugins/Installation/Controller.php1913
-rw-r--r--plugins/Installation/FormDatabaseSetup.php528
-rw-r--r--plugins/Installation/FormFirstWebsiteSetup.php105
-rw-r--r--plugins/Installation/FormGeneralSetup.php113
-rw-r--r--plugins/Installation/Installation.php142
-rw-r--r--plugins/Installation/View.php79
-rw-r--r--plugins/Installation/templates/allSteps.tpl18
-rw-r--r--plugins/Installation/templates/databaseCheck.tpl44
-rw-r--r--plugins/Installation/templates/databaseSetup.tpl14
-rw-r--r--plugins/Installation/templates/displayJavascriptCode.tpl28
-rw-r--r--plugins/Installation/templates/finished.tpl2
-rw-r--r--plugins/Installation/templates/firstWebsiteSetup.tpl20
-rw-r--r--plugins/Installation/templates/generalSetup.tpl2
-rw-r--r--plugins/Installation/templates/install.css240
-rw-r--r--plugins/Installation/templates/integrityDetails.tpl75
-rw-r--r--plugins/Installation/templates/structure.tpl126
-rw-r--r--plugins/Installation/templates/systemCheck.tpl14
-rwxr-xr-xplugins/Installation/templates/systemCheckPage.css46
-rwxr-xr-xplugins/Installation/templates/systemCheckPage.tpl24
-rwxr-xr-xplugins/Installation/templates/systemCheckSection.tpl558
-rw-r--r--plugins/Installation/templates/systemCheck_legend.tpl20
-rw-r--r--plugins/Installation/templates/tablesCreation.tpl96
-rw-r--r--plugins/Installation/templates/welcome.tpl62
-rw-r--r--plugins/LanguagesManager/API.php322
-rw-r--r--plugins/LanguagesManager/Controller.php47
-rw-r--r--plugins/LanguagesManager/LanguagesManager.php440
-rw-r--r--plugins/LanguagesManager/templates/languageSelector.js82
-rw-r--r--plugins/LanguagesManager/templates/languages.tpl26
-rw-r--r--plugins/Live/API.php888
-rw-r--r--plugins/Live/Controller.php255
-rw-r--r--plugins/Live/Live.php92
-rw-r--r--plugins/Live/Visitor.php1010
-rw-r--r--plugins/Live/templates/index.tpl56
-rw-r--r--plugins/Live/templates/lastVisits.tpl145
-rw-r--r--plugins/Live/templates/live.css116
-rw-r--r--plugins/Live/templates/scripts/live.js128
-rw-r--r--plugins/Live/templates/simpleLastVisitCount.tpl232
-rw-r--r--plugins/Live/templates/totalVisits.tpl51
-rw-r--r--plugins/Live/templates/visitorLog.tpl581
-rw-r--r--plugins/Login/Auth.php177
-rw-r--r--plugins/Login/Controller.php972
-rw-r--r--plugins/Login/FormLogin.php36
-rw-r--r--plugins/Login/FormResetPassword.php32
-rw-r--r--plugins/Login/Login.php355
-rw-r--r--plugins/Login/templates/header.tpl114
-rw-r--r--plugins/Login/templates/login.css251
-rwxr-xr-xplugins/Login/templates/login.js191
-rw-r--r--plugins/Login/templates/login.tpl141
-rwxr-xr-xplugins/Login/templates/message.tpl14
-rw-r--r--plugins/MobileMessaging/API.php893
-rw-r--r--plugins/MobileMessaging/Controller.php91
-rw-r--r--plugins/MobileMessaging/CountryCallingCodes.php486
-rw-r--r--plugins/MobileMessaging/GSMCharset.php276
-rw-r--r--plugins/MobileMessaging/MobileMessaging.php621
-rw-r--r--plugins/MobileMessaging/ReportRenderer/Exception.php90
-rw-r--r--plugins/MobileMessaging/ReportRenderer/Sms.php198
-rw-r--r--plugins/MobileMessaging/SMSProvider.php283
-rw-r--r--plugins/MobileMessaging/SMSProvider/Clockwork.php163
-rw-r--r--plugins/MobileMessaging/SMSProvider/StubbedProvider.php24
-rw-r--r--plugins/MobileMessaging/scripts/MobileMessagingSettings.js337
-rw-r--r--plugins/MobileMessaging/templates/ReportParameters.tpl105
-rw-r--r--plugins/MobileMessaging/templates/SMSReport.tpl138
-rw-r--r--plugins/MobileMessaging/templates/Settings.tpl347
-rwxr-xr-xplugins/MultiSites/API.php930
-rw-r--r--plugins/MultiSites/Controller.php431
-rw-r--r--plugins/MultiSites/MultiSites.php157
-rw-r--r--plugins/MultiSites/templates/common.js411
-rw-r--r--plugins/MultiSites/templates/index.tpl191
-rw-r--r--plugins/MultiSites/templates/row.tpl37
-rw-r--r--plugins/MultiSites/templates/styles.css76
-rw-r--r--plugins/Overlay/API.php219
-rw-r--r--plugins/Overlay/Controller.php340
-rw-r--r--plugins/Overlay/Overlay.php46
-rw-r--r--plugins/Overlay/client/client.css161
-rw-r--r--plugins/Overlay/client/client.js518
-rw-r--r--plugins/Overlay/client/followingpages.js1018
-rw-r--r--plugins/Overlay/client/translations.js56
-rw-r--r--plugins/Overlay/client/urlnormalizer.js385
-rw-r--r--plugins/Overlay/templates/error_wrong_domain.tpl54
-rw-r--r--plugins/Overlay/templates/helper.js44
-rw-r--r--plugins/Overlay/templates/index.css131
-rw-r--r--plugins/Overlay/templates/index.js408
-rw-r--r--plugins/Overlay/templates/index.tpl60
-rw-r--r--plugins/Overlay/templates/index_noframe.tpl32
-rw-r--r--plugins/Overlay/templates/notify_parent_iframe.tpl16
-rw-r--r--plugins/Overlay/templates/rowaction.js68
-rw-r--r--plugins/Overlay/templates/sidebar.tpl34
-rw-r--r--plugins/PDFReports/API.php1430
-rw-r--r--plugins/PDFReports/Controller.php102
-rw-r--r--plugins/PDFReports/PDFReports.php1208
-rw-r--r--plugins/PDFReports/config/tcpdf_config.php432
-rw-r--r--plugins/PDFReports/templates/add.tpl260
-rw-r--r--plugins/PDFReports/templates/index.tpl62
-rw-r--r--plugins/PDFReports/templates/list.tpl182
-rw-r--r--plugins/PDFReports/templates/pdf.js242
-rw-r--r--plugins/PDFReports/templates/report_parameters.tpl187
-rw-r--r--plugins/PrivacyManager/Controller.php557
-rwxr-xr-xplugins/PrivacyManager/LogDataPurger.php615
-rw-r--r--plugins/PrivacyManager/PrivacyManager.php560
-rwxr-xr-xplugins/PrivacyManager/ReportsPurger.php733
-rwxr-xr-xplugins/PrivacyManager/templates/databaseSize.tpl4
-rw-r--r--plugins/PrivacyManager/templates/privacySettings.js300
-rw-r--r--plugins/PrivacyManager/templates/privacySettings.tpl469
-rw-r--r--plugins/Provider/API.php51
-rw-r--r--plugins/Provider/Controller.php53
-rw-r--r--plugins/Provider/Provider.php481
-rw-r--r--plugins/Provider/functions.php63
-rw-r--r--plugins/Proxy/Controller.php313
-rw-r--r--plugins/Proxy/Proxy.php38
-rw-r--r--plugins/Proxy/templates/exportImage.tpl19
-rw-r--r--plugins/Referers/API.php969
-rw-r--r--plugins/Referers/Controller.php1349
-rw-r--r--plugins/Referers/Referers.php1153
-rw-r--r--plugins/Referers/functions.php262
-rwxr-xr-xplugins/Referers/templates/Websites_SocialNetworks.tpl8
-rw-r--r--plugins/Referers/templates/index.tpl128
-rw-r--r--plugins/Referers/templates/searchEngines_Keywords.tpl8
-rw-r--r--plugins/SEO/API.php153
-rw-r--r--plugins/SEO/Controller.php55
-rw-r--r--plugins/SEO/MajesticClient.php154
-rw-r--r--plugins/SEO/RankChecker.php379
-rw-r--r--plugins/SEO/SEO.php44
-rw-r--r--plugins/SEO/templates/index.tpl78
-rw-r--r--plugins/SEO/templates/rank.js2
-rw-r--r--plugins/SecurityInfo/Controller.php42
-rw-r--r--plugins/SecurityInfo/PhpSecInfo/PhpSecInfo.php976
-rw-r--r--plugins/SecurityInfo/PhpSecInfo/Test/Application/php.php77
-rw-r--r--plugins/SecurityInfo/PhpSecInfo/Test/Application/piwik.php55
-rw-r--r--plugins/SecurityInfo/PhpSecInfo/Test/CGI/force_redirect.php157
-rw-r--r--plugins/SecurityInfo/PhpSecInfo/Test/Core/allow_url_fopen.php100
-rw-r--r--plugins/SecurityInfo/PhpSecInfo/Test/Core/allow_url_include.php107
-rw-r--r--plugins/SecurityInfo/PhpSecInfo/Test/Core/display_errors.php85
-rw-r--r--plugins/SecurityInfo/PhpSecInfo/Test/Core/expose_php.php85
-rw-r--r--plugins/SecurityInfo/PhpSecInfo/Test/Core/file_uploads.php86
-rw-r--r--plugins/SecurityInfo/PhpSecInfo/Test/Core/gid.php112
-rw-r--r--plugins/SecurityInfo/PhpSecInfo/Test/Core/magic_quotes_gpc.php83
-rw-r--r--plugins/SecurityInfo/PhpSecInfo/Test/Core/memory_limit.php87
-rw-r--r--plugins/SecurityInfo/PhpSecInfo/Test/Core/open_basedir.php81
-rw-r--r--plugins/SecurityInfo/PhpSecInfo/Test/Core/post_max_size.php68
-rw-r--r--plugins/SecurityInfo/PhpSecInfo/Test/Core/register_globals.php83
-rw-r--r--plugins/SecurityInfo/PhpSecInfo/Test/Core/uid.php114
-rw-r--r--plugins/SecurityInfo/PhpSecInfo/Test/Core/upload_max_filesize.php84
-rw-r--r--plugins/SecurityInfo/PhpSecInfo/Test/Core/upload_tmp_dir.php133
-rw-r--r--plugins/SecurityInfo/PhpSecInfo/Test/Curl/file_support.php76
-rw-r--r--plugins/SecurityInfo/PhpSecInfo/Test/Session/save_path.php165
-rw-r--r--plugins/SecurityInfo/PhpSecInfo/Test/Session/use_trans_sid.php63
-rw-r--r--plugins/SecurityInfo/PhpSecInfo/Test/Suhosin/extension.php39
-rw-r--r--plugins/SecurityInfo/PhpSecInfo/Test/Suhosin/patch.php67
-rw-r--r--plugins/SecurityInfo/PhpSecInfo/Test/Test.php1087
-rw-r--r--plugins/SecurityInfo/PhpSecInfo/Test/Test_Application.php71
-rw-r--r--plugins/SecurityInfo/PhpSecInfo/Test/Test_Cgi.php78
-rw-r--r--plugins/SecurityInfo/PhpSecInfo/Test/Test_Core.php50
-rw-r--r--plugins/SecurityInfo/PhpSecInfo/Test/Test_Curl.php80
-rw-r--r--plugins/SecurityInfo/PhpSecInfo/Test/Test_Session.php52
-rw-r--r--plugins/SecurityInfo/PhpSecInfo/Test/Test_Suhosin.php65
-rw-r--r--plugins/SecurityInfo/SecurityInfo.php56
-rw-r--r--plugins/SecurityInfo/templates/index.tpl41
-rw-r--r--plugins/SitesManager/API.php2655
-rw-r--r--plugins/SitesManager/Controller.php359
-rw-r--r--plugins/SitesManager/SitesManager.php408
-rw-r--r--plugins/SitesManager/templates/DisplayAlternativeTags.tpl203
-rw-r--r--plugins/SitesManager/templates/DisplayJavascriptCode.tpl109
-rw-r--r--plugins/SitesManager/templates/SitesManager.js551
-rw-r--r--plugins/SitesManager/templates/SitesManager.tpl648
-rw-r--r--plugins/Transitions/API.php582
-rw-r--r--plugins/Transitions/Controller.php150
-rw-r--r--plugins/Transitions/Transitions.php691
-rw-r--r--plugins/Transitions/templates/transitions.css197
-rw-r--r--plugins/Transitions/templates/transitions.js2380
-rw-r--r--plugins/Transitions/templates/transitions.tpl91
-rw-r--r--plugins/UserCountry/API.php381
-rw-r--r--plugins/UserCountry/Controller.php957
-rwxr-xr-xplugins/UserCountry/GeoIPAutoUpdater.php1124
-rwxr-xr-xplugins/UserCountry/LocationProvider.php883
-rwxr-xr-xplugins/UserCountry/LocationProvider/Default.php182
-rwxr-xr-xplugins/UserCountry/LocationProvider/GeoIp.php495
-rwxr-xr-xplugins/UserCountry/LocationProvider/GeoIp/Pecl.php641
-rwxr-xr-xplugins/UserCountry/LocationProvider/GeoIp/Php.php651
-rwxr-xr-xplugins/UserCountry/LocationProvider/GeoIp/ServerBased.php547
-rw-r--r--plugins/UserCountry/UserCountry.php1012
-rw-r--r--plugins/UserCountry/functions.php180
-rwxr-xr-xplugins/UserCountry/templates/admin.js298
-rwxr-xr-xplugins/UserCountry/templates/adminIndex.tpl199
-rw-r--r--plugins/UserCountry/templates/index.tpl29
-rwxr-xr-xplugins/UserCountry/templates/styles.css59
-rwxr-xr-xplugins/UserCountry/templates/updaterSetup.tpl5
-rw-r--r--plugins/UserCountryMap/Controller.php392
-rw-r--r--plugins/UserCountryMap/UserCountryMap.php42
-rw-r--r--plugins/UserCountryMap/css/map.css7
-rw-r--r--plugins/UserCountryMap/css/qtip.css218
-rw-r--r--plugins/UserCountryMap/css/realtime-map.css45
-rw-r--r--plugins/UserCountryMap/css/visitor-map.css31
-rw-r--r--plugins/UserCountryMap/js/realtime-map.js132
-rw-r--r--plugins/UserCountryMap/js/vendor/chroma.min.js680
-rw-r--r--plugins/UserCountryMap/js/vendor/jquery.qtip.min.js364
-rw-r--r--plugins/UserCountryMap/js/vendor/kartograph.js10564
-rw-r--r--plugins/UserCountryMap/js/vendor/kartograph.min.js1674
-rw-r--r--plugins/UserCountryMap/js/vendor/kmeans.js256
-rw-r--r--plugins/UserCountryMap/js/vendor/raphael-min.js2215
-rw-r--r--plugins/UserCountryMap/js/visitor-map.js759
-rw-r--r--plugins/UserCountryMap/templates/realtime-map.tpl60
-rw-r--r--plugins/UserCountryMap/templates/visitor-map.tpl77
-rw-r--r--plugins/UserSettings/API.php372
-rw-r--r--plugins/UserSettings/Controller.php360
-rw-r--r--plugins/UserSettings/UserSettings.php859
-rw-r--r--plugins/UserSettings/functions.php314
-rw-r--r--plugins/UserSettings/templates/index.tpl42
-rw-r--r--plugins/UsersManager/API.php1307
-rw-r--r--plugins/UsersManager/Controller.php675
-rw-r--r--plugins/UsersManager/UsersManager.php245
-rw-r--r--plugins/UsersManager/templates/UsersManager.js315
-rw-r--r--plugins/UsersManager/templates/UsersManager.tpl251
-rw-r--r--plugins/UsersManager/templates/userSettings.js99
-rw-r--r--plugins/UsersManager/templates/userSettings.tpl225
-rw-r--r--plugins/VisitFrequency/API.php258
-rw-r--r--plugins/VisitFrequency/Controller.php170
-rw-r--r--plugins/VisitFrequency/VisitFrequency.php234
-rw-r--r--plugins/VisitFrequency/templates/index.tpl4
-rw-r--r--plugins/VisitFrequency/templates/sparklines.tpl13
-rw-r--r--plugins/VisitTime/API.php285
-rw-r--r--plugins/VisitTime/Controller.php181
-rw-r--r--plugins/VisitTime/VisitTime.php416
-rw-r--r--plugins/VisitTime/templates/index.tpl8
-rw-r--r--plugins/VisitorGenerator/Controller.php278
-rw-r--r--plugins/VisitorGenerator/VisitorGenerator.php50
-rw-r--r--plugins/VisitorGenerator/templates/generate.tpl19
-rw-r--r--plugins/VisitorGenerator/templates/index.tpl58
-rw-r--r--plugins/VisitorInterest/API.php208
-rw-r--r--plugins/VisitorInterest/Controller.php196
-rw-r--r--plugins/VisitorInterest/VisitorInterest.php633
-rw-r--r--plugins/VisitorInterest/templates/index.tpl1
-rw-r--r--plugins/VisitsSummary/API.php274
-rw-r--r--plugins/VisitsSummary/Controller.php301
-rw-r--r--plugins/VisitsSummary/VisitsSummary.php112
-rw-r--r--plugins/VisitsSummary/templates/sparklines.tpl102
-rw-r--r--plugins/Widgetize/Controller.php120
-rw-r--r--plugins/Widgetize/Widgetize.php112
-rw-r--r--plugins/Widgetize/templates/iframe.tpl24
-rw-r--r--plugins/Widgetize/templates/index.tpl165
-rw-r--r--plugins/Widgetize/templates/js.tpl36
-rw-r--r--plugins/Widgetize/templates/test_jsinclude.tpl7
-rw-r--r--plugins/Widgetize/templates/widgetize.js148
417 files changed, 66540 insertions, 63433 deletions
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 &gt; 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 &gt; 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 @@
<?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_API
*/
/**
- *
+ *
* @package Piwik_API
*/
class Piwik_API_Controller extends Piwik_Controller
{
- 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();
- }
+ 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 .= '<tr><td class="segmentCategory" colspan="2"><b>'.$thisCategory.'</b></td></tr>';
- }
-
- $lastCategory[$segment['type']] = $thisCategory;
-
- $exampleValues = isset($segment['acceptedValues'])
- ? 'Example values: <code>'.$segment['acceptedValues'].'</code>'
- : '';
- $restrictedToAdmin = isset($segment['permission']) ? '<br/>Note: This segment can only be used by an Admin user' : '';
- $output .= '<tr>
- <td class="segmentString">'.$segment['segment'].'</td>
- <td class="segmentName">'.$segment['name'] .$restrictedToAdmin.'<br/>'.$exampleValues.' </td>
+ 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 .= '<tr><td class="segmentCategory" colspan="2"><b>' . $thisCategory . '</b></td></tr>';
+ }
+
+ $lastCategory[$segment['type']] = $thisCategory;
+
+ $exampleValues = isset($segment['acceptedValues'])
+ ? 'Example values: <code>' . $segment['acceptedValues'] . '</code>'
+ : '';
+ $restrictedToAdmin = isset($segment['permission']) ? '<br/>Note: This segment can only be used by an Admin user' : '';
+ $output .= '<tr>
+ <td class="segmentString">' . $segment['segment'] . '</td>
+ <td class="segmentName">' . $segment['name'] . $restrictedToAdmin . '<br/>' . $exampleValues . ' </td>
</tr>';
-
- // Show only 2 custom variables and display message for rest
- if($customVariableWillBeDisplayed)
- {
- $customVariables++;
- if($customVariables == count($onlyDisplay))
- {
- $output .= '<tr><td colspan="2"> 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 .= '<tr><td colspan="2"> There are 5 custom variables available, so you can segment across any segment name and value range.
<br/>For example, <code>customVariableName1==Type;customVariableValue1==Customer</code>
<br/>Returns all visitors that have the Custom Variable "Type" set to "Customer".
<br/>Custom Variables of scope "page" can be queried separately. For example, to query the Custom Variable of scope "page",
<br/>stored in index 1, you would use the segment <code>customVariablePageName1==ArticleLanguage;customVariablePageValue1==FR</code>
</td></tr>';
- }
- }
-
-
- if($segment['type'] == 'dimension') {
- $tableDimensions .= $output;
- } else {
- $tableMetrics .= $output;
- }
- }
-
- echo "
+ }
+ }
+
+
+ if ($segment['type'] == 'dimension') {
+ $tableDimensions .= $output;
+ } else {
+ $tableMetrics .= $output;
+ }
+ }
+
+ echo "
<b>Dimensions</b>
<table>
$tableDimensions
@@ -114,5 +110,5 @@ class Piwik_API_Controller extends Piwik_Controller
$tableMetrics
</table>
";
- }
+ }
}
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 @@
<div class="top_controls_inner">
{include file="CoreHome/templates/period_select.tpl"}
</div>
-
+
<h2>{'API_QuickDocumentationTitle'|translate}</h2>
+
<p>{'API_PluginDescription'|translate}</p>
-
+
{if $isSuperUser}
<p>{'API_GenerateVisits'|translate:'VisitorGenerator':'VisitorGenerator'}</p>
{/if}
-
- <p><b>{'API_MoreInformation'|translate:"<a target='_blank' href='?module=Proxy&action=redirect&url=http://piwik.org/docs/analytics-api'>":"</a>":"<a target='_blank' href='?module=Proxy&action=redirect&url=http://piwik.org/docs/analytics-api/reference'>":"</a>"}</b></p>
-
+
+ <p>
+ <b>{'API_MoreInformation'|translate:"<a target='_blank' href='?module=Proxy&action=redirect&url=http://piwik.org/docs/analytics-api'>":"</a>":"<a target='_blank' href='?module=Proxy&action=redirect&url=http://piwik.org/docs/analytics-api/reference'>":"</a>"}</b>
+ </p>
+
<h2>{'API_UserAuthentication'|translate}</h2>
+
<p>
- {'API_UsingTokenAuth'|translate:'<b>':'</b>':""}<br />
- <span id='token_auth'>&amp;token_auth=<b>{$token_auth}</b></span><br />
- {'API_KeepTokenSecret'|translate:'<b>':'</b>'}
- <!-- {'API_LoadedAPIs'|translate:$countLoadedAPI} -->
- {$list_api_methods_with_links}
- <br />
+ {'API_UsingTokenAuth'|translate:'<b>':'</b>':""}<br/>
+ <span id='token_auth'>&amp;token_auth=<b>{$token_auth}</b></span><br/>
+ {'API_KeepTokenSecret'|translate:'<b>':'</b>'}
+ <!-- {'API_LoadedAPIs'|translate:$countLoadedAPI} -->
+ {$list_api_methods_with_links}
+ <br/>
</div>
{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 @@
<?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_Actions
*/
@@ -12,543 +12,518 @@
/**
* The Actions API lets you request reports for all your Visitor Actions: Page URLs, Page titles (Piwik Events),
* File Downloads and Clicks on external websites.
- *
+ *
* For example, "getPageTitles" will return all your page titles along with standard <a href='http://piwik.org/docs/analytics-api/reference/#toc-metric-definitions' target='_blank'>Actions metrics</a> 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', '<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');
- }
+ ),
+ '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("% ", "%&nbsp;", 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("% ", "%&nbsp;", 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} &mdash; {$endDatePretty}{/if}</div>
+ <div class="annotation-list-range">{$startDatePretty}{if $startDate neq $endDate} &mdash; {$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">&nbsp;</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">&nbsp;</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}:&nbsp;</strong>{$invalidHostMessage}
- </div>
- {/if}
- {if count($trustedHosts) eq 1 && (!isset($isValidHost) || $isValidHost)}
- {'CoreAdminHome_PiwikIsInstalledAt'|translate}:&nbsp;&nbsp;<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">&nbsp;</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}:&nbsp;</strong>{$invalidHostMessage}
+ </div>
+ {/if}
+ {if count($trustedHosts) eq 1 && (!isset($isValidHost) || $isValidHost)}
+ {'CoreAdminHome_PiwikIsInstalledAt'|translate}:&nbsp;&nbsp;
+ <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">&nbsp;</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 &rsaquo; {/if}{'CoreAdminHome_Administration'|translate}</title>
-<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
-<meta name="generator" content="Piwik - Open Source Web Analytics" />
-<link rel="shortcut icon" href="plugins/CoreHome/templates/images/favicon.ico" />
+ <title>{if !$isCustomLogo}Piwik &rsaquo; {/if}{'CoreAdminHome_Administration'|translate}</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
+ <meta name="generator" content="Piwik - Open Source Web Analytics"/>
+ <link rel="shortcut icon" href="plugins/CoreHome/templates/images/favicon.ico"/>
-{loadJavascriptTranslations plugins='CoreAdminHome CoreHome'}
+ {loadJavascriptTranslations plugins='CoreAdminHome CoreHome'}
-{include file="CoreHome/templates/js_global_variables.tpl"}
-{include file="CoreHome/templates/js_css_includes.tpl"}
-<!--[if IE]>
-<link rel="stylesheet" type="text/css" href="themes/default/ieonly.css" />
-<![endif]-->
-{include file="CoreHome/templates/iframe_buster_header.tpl"}
+ {include file="CoreHome/templates/js_global_variables.tpl"}
+ {include file="CoreHome/templates/js_css_includes.tpl"}
+ <!--[if IE]>
+ <link rel="stylesheet" type="text/css" href="themes/default/ieonly.css"/>
+ <![endif]-->
+ {include file="CoreHome/templates/iframe_buster_header.tpl"}
</head>
<body>
{include file="CoreHome/templates/iframe_buster_body.tpl"}
<div id="root">
-{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}
-<div id="header">
-{include file="CoreHome/templates/logo.tpl"}
-{if $showPeriodSelection}{include file="CoreHome/templates/period_select.tpl"}{/if}
-{include file="CoreHome/templates/js_disabled_notice.tpl"}
-</div>
+ <div id="header">
+ {include file="CoreHome/templates/logo.tpl"}
+ {if $showPeriodSelection}{include file="CoreHome/templates/period_select.tpl"}{/if}
+ {include file="CoreHome/templates/js_disabled_notice.tpl"}
+ </div>
-{ajaxRequestErrorDiv}
-<div id="container">
-{if !isset($showMenu) || $showMenu}
- {include file="CoreAdminHome/templates/menu.tpl"}
-{/if}
+ {ajaxRequestErrorDiv}
+ <div id="container">
+ {if !isset($showMenu) || $showMenu}
+ {include file="CoreAdminHome/templates/menu.tpl"}
+ {/if}
-<div id="content" class="admin">
+ <div id="content" class="admin">
-{include file="CoreHome/templates/header_message.tpl"}
+ {include file="CoreHome/templates/header_message.tpl"}
-{if !empty($configFileNotWritable)}
- <div class="ajaxSuccess" style="display:inline-block">
- {'General_ConfigFileIsNotWritable'|translate:"(config/config.ini.php)":"<br/>"}
- </div>
-{elseif preg_match('/updated=[1-9]/', $url)}
- <div class="ajaxSuccess" style="display:inline-block">
- {'General_YourChangesHaveBeenSaved'|translate}
- </div>
-{/if}
+ {if !empty($configFileNotWritable)}
+ <div class="ajaxSuccess" style="display:inline-block">
+ {'General_ConfigFileIsNotWritable'|translate:"(config/config.ini.php)":"<br/>"}
+ </div>
+ {elseif preg_match('/updated=[1-9]/', $url)}
+ <div class="ajaxSuccess" style="display:inline-block">
+ {'General_YourChangesHaveBeenSaved'|translate}
+ </div>
+ {/if}
-{if !empty($statisticsNotRecorded)}
- <div class="ajaxSuccess" style="display:inline-block">
- {'General_StatisticsAreNotRecorded'|translate}
- </div>
-{/if}
+ {if !empty($statisticsNotRecorded)}
+ <div class="ajaxSuccess" style="display:inline-block">
+ {'General_StatisticsAreNotRecorded'|translate}
+ </div>
+ {/if}
-<div class="ui-confirm" id="alert">
- <h2></h2>
- <input role="no" type="button" value="{'General_Ok'|translate}" />
-</div>
+ <div class="ui-confirm" id="alert">
+ <h2></h2>
+ <input role="no" type="button" value="{'General_Ok'|translate}"/>
+ </div>
-{include file="CoreHome/templates/warning_invalid_host.tpl"}
+ {include file="CoreHome/templates/warning_invalid_host.tpl"}
-{* missing plugins warning *}
-{if $isSuperUser && !empty($missingPluginsWarning)}
-<div class="ajaxSuccess">
- <strong>{'General_Warning'|translate}:&nbsp;</strong>{$missingPluginsWarning}
-</div>
-{/if}
+ {* missing plugins warning *}
+ {if $isSuperUser && !empty($missingPluginsWarning)}
+ <div class="ajaxSuccess">
+ <strong>{'General_Warning'|translate}:&nbsp;</strong>{$missingPluginsWarning}
+ </div>
+ {/if}
-{* old GeoIP plugin warning *}
-{if $isSuperUser && !empty($usingOldGeoIPPlugin)}
-<div class="ajaxSuccess">
- <strong>{'General_Warning'|translate}:&nbsp;</strong>{'UserCountry_OldGeoIPWarning'|translate:'<a href="index.php?module=CorePluginsAdmin&action=index&idSite=1&period=day&date=yesterday">':'</a>':'<a href="index.php?module=UserCountry&action=adminIndex&idSite=1&period=day&date=yesterday#location-providers">':'</a>':'<a href="http://piwik.org/faq/how-to/#faq_167">':'</a>':'<a href="http://piwik.org/faq/how-to/#faq_59">':'</a>'}
-</div>
-{/if}
+ {* old GeoIP plugin warning *}
+ {if $isSuperUser && !empty($usingOldGeoIPPlugin)}
+ <div class="ajaxSuccess">
+ <strong>{'General_Warning'|translate}
+ :&nbsp;</strong>{'UserCountry_OldGeoIPWarning'|translate:'<a href="index.php?module=CorePluginsAdmin&action=index&idSite=1&period=day&date=yesterday">':'</a>':'<a href="index.php?module=UserCountry&action=adminIndex&idSite=1&period=day&date=yesterday#location-providers">':'</a>':'<a href="http://piwik.org/faq/how-to/#faq_167">':'</a>':'<a href="http://piwik.org/faq/how-to/#faq_59">':'</a>'}
+ </div>
+ {/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 = $('<a></a>')[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($('<option value=""></option>').text(noneText));
-
- var goals = allGoals[idsite] || [];
- for (var key in goals)
- {
- var goal = goals[key];
- selectElement.append($('<option/>').attr('value', goal.idgoal).text(goal.name));
- }
-
- // set currency string
- $('#' + id).parent().find('.currency').text(siteCurrencies[idsite]);
- };
-
- // function that generates JS code
- var generateJsCode = function()
- {
- // get data
- var idSite = $('#js-tracker-website .custom_select_main_link').attr('siteid'),
- groupPageTitlesByDomain = $('#javascript-tracking-group-by-domain').is(':checked'),
- mergeSubdomains = $('#javascript-tracking-all-subdomains').is(':checked'),
- mergeAliasUrls = $('#javascript-tracking-all-aliases').is(':checked'),
- visitorCustomVariables = getCustomVariables('javascript-tracking-visitor-cv'),
- pageCustomVariables = getCustomVariables('javascript-tracking-page-cv'),
- customCampaignNameQueryParam = null,
- customCampaignKeywordParam = null,
- doNotTrack = $('#javascript-tracking-do-not-track').is(':checked');
-
- if ($('#custom-campaign-query-params-check').is(':checked'))
- {
- customCampaignNameQueryParam = $('#custom-campaign-name-query-param').val();
- customCampaignKeywordParam = $('#custom-campaign-keyword-query-param').val();
- }
-
- // generate JS
- var result = '<!-- Piwik -->\n\
+(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 = $('<a></a>')[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($('<option value=""></option>').text(noneText));
+
+ var goals = allGoals[idsite] || [];
+ for (var key in goals) {
+ var goal = goals[key];
+ selectElement.append($('<option/>').attr('value', goal.idgoal).text(goal.name));
+ }
+
+ // set currency string
+ $('#' + id).parent().find('.currency').text(siteCurrencies[idsite]);
+ };
+
+ // function that generates JS code
+ var generateJsCode = function () {
+ // get data
+ var idSite = $('#js-tracker-website .custom_select_main_link').attr('siteid'),
+ groupPageTitlesByDomain = $('#javascript-tracking-group-by-domain').is(':checked'),
+ mergeSubdomains = $('#javascript-tracking-all-subdomains').is(':checked'),
+ mergeAliasUrls = $('#javascript-tracking-all-aliases').is(':checked'),
+ visitorCustomVariables = getCustomVariables('javascript-tracking-visitor-cv'),
+ pageCustomVariables = getCustomVariables('javascript-tracking-page-cv'),
+ customCampaignNameQueryParam = null,
+ customCampaignKeywordParam = null,
+ doNotTrack = $('#javascript-tracking-do-not-track').is(':checked');
+
+ if ($('#custom-campaign-query-params-check').is(':checked')) {
+ customCampaignNameQueryParam = $('#custom-campaign-name-query-param').val();
+ customCampaignKeywordParam = $('#custom-campaign-keyword-query-param').val();
+ }
+
+ // generate JS
+ var result = '<!-- Piwik -->\n\
<script type="text/javascript">\n\
var _paq = _paq || [];\n';
-
- if (groupPageTitlesByDomain)
- {
- result += ' _paq.push(["setDocumentTitle", document.domain + "/" + document.title]);\n';
- }
-
- if (mergeSubdomains)
- {
- var mainHostAllSub = '*.' + getHostNameFromUrl(siteUrls[idSite][0]);
- result += ' _paq.push(["setCookieDomain", ' + JSON.stringify(mainHostAllSub) + ']);\n';
- }
-
- if (mergeAliasUrls)
- {
- var siteHosts = [];
- for (var i = 0; i != siteUrls[idSite].length; ++i)
- {
- siteHosts[i] = '*.' + getHostNameFromUrl(siteUrls[idSite][i]);
- }
- result += ' _paq.push(["setDomains", ' + JSON.stringify(siteHosts) + ']);\n';
- }
-
- if (visitorCustomVariables.length)
- {
- result += ' // you can set up to 5 custom variables for each visitor\n';
- result += getCustomVariableJS(visitorCustomVariables, 'visit');
- }
-
- if (pageCustomVariables.length)
- {
- result += ' // you can set up to 5 custom variables for each action (page view, ' +
- 'download, click, site search)\n';
- result += getCustomVariableJS(pageCustomVariables, 'page');
- }
-
- if (customCampaignNameQueryParam)
- {
- result += ' _paq.push(["setCampaignNameKey", ' + JSON.stringify(customCampaignNameQueryParam) + ']);\n';
- }
-
- if (customCampaignKeywordParam)
- {
- result += ' _paq.push(["setCampaignKeywordKey", ' + JSON.stringify(customCampaignKeywordParam) + ']);\n';
- }
-
- if (doNotTrack)
- {
- result += ' _paq.push(["setDoNotTrack", true]);\n';
- }
-
- result += ' _paq.push(["trackPageView"]);\n\
+
+ if (groupPageTitlesByDomain) {
+ result += ' _paq.push(["setDocumentTitle", document.domain + "/" + document.title]);\n';
+ }
+
+ if (mergeSubdomains) {
+ var mainHostAllSub = '*.' + getHostNameFromUrl(siteUrls[idSite][0]);
+ result += ' _paq.push(["setCookieDomain", ' + JSON.stringify(mainHostAllSub) + ']);\n';
+ }
+
+ if (mergeAliasUrls) {
+ var siteHosts = [];
+ for (var i = 0; i != siteUrls[idSite].length; ++i) {
+ siteHosts[i] = '*.' + getHostNameFromUrl(siteUrls[idSite][i]);
+ }
+ result += ' _paq.push(["setDomains", ' + JSON.stringify(siteHosts) + ']);\n';
+ }
+
+ if (visitorCustomVariables.length) {
+ result += ' // you can set up to 5 custom variables for each visitor\n';
+ result += getCustomVariableJS(visitorCustomVariables, 'visit');
+ }
+
+ if (pageCustomVariables.length) {
+ result += ' // you can set up to 5 custom variables for each action (page view, ' +
+ 'download, click, site search)\n';
+ result += getCustomVariableJS(pageCustomVariables, 'page');
+ }
+
+ if (customCampaignNameQueryParam) {
+ result += ' _paq.push(["setCampaignNameKey", ' + JSON.stringify(customCampaignNameQueryParam) + ']);\n';
+ }
+
+ if (customCampaignKeywordParam) {
+ result += ' _paq.push(["setCampaignKeywordKey", ' + JSON.stringify(customCampaignKeywordParam) + ']);\n';
+ }
+
+ if (doNotTrack) {
+ result += ' _paq.push(["setDoNotTrack", true]);\n';
+ }
+
+ result += ' _paq.push(["trackPageView"]);\n\
_paq.push(["enableLinkTracking"]);\n\n\
(function() {\n\
var u=(("https:" == document.location.protocol) ? "https" : "http") + "://' + piwikHost + piwikPath + '/";\n\
@@ -237,139 +213,132 @@ $(document).ready(function() {
})();\n\
</script>\n\
<!-- End Piwik Code -->';
-
- $('#javascript-text textarea').val(result)
- };
-
- // function that generates image tracker link
- var generateImageTrackerLink = function()
- {
- // get data ( (("https:" == document.location.protocol)?"https://' + piwikHost + '":"http://' + piwikHost + '") )
- var idSite = $('#image-tracker-website .custom_select_main_link').attr('siteid'),
- path = document.location.pathname,
- piwikURL = ("https:" == document.location.protocol ? "https://" + piwikHost : "http://" + piwikHost) + path.substring(0, path.lastIndexOf('/')) + '/piwik.php',
- actionName = $('#image-tracker-action-name').val(),
- idGoal = null,
- revenue = null;
-
- if ($('#image-tracking-goal-check').is(':checked'))
- {
- idGoal = $('#image-tracker-goal').val();
- if (idGoal)
- {
- revenue = $('#image-tracker-advanced-options .revenue').val();
- }
- }
-
- // generate link HTML
- var params = {
- idsite: idSite,
- rec: 1
- };
-
- if (actionName)
- {
- params.action_name = actionName;
- }
-
- if (idGoal)
- {
- params.idGoal = idGoal;
- if (revenue)
- {
- params.revenue = revenue;
- }
- }
-
- var result = '<!-- Piwik Image Tracker -->\n\
+
+ $('#javascript-text textarea').val(result)
+ };
+
+ // function that generates image tracker link
+ var generateImageTrackerLink = function () {
+ // get data ( (("https:" == document.location.protocol)?"https://' + piwikHost + '":"http://' + piwikHost + '") )
+ var idSite = $('#image-tracker-website .custom_select_main_link').attr('siteid'),
+ path = document.location.pathname,
+ piwikURL = ("https:" == document.location.protocol ? "https://" + piwikHost : "http://" + piwikHost) + path.substring(0, path.lastIndexOf('/')) + '/piwik.php',
+ actionName = $('#image-tracker-action-name').val(),
+ idGoal = null,
+ revenue = null;
+
+ if ($('#image-tracking-goal-check').is(':checked')) {
+ idGoal = $('#image-tracker-goal').val();
+ if (idGoal) {
+ revenue = $('#image-tracker-advanced-options .revenue').val();
+ }
+ }
+
+ // generate link HTML
+ var params = {
+ idsite: idSite,
+ rec: 1
+ };
+
+ if (actionName) {
+ params.action_name = actionName;
+ }
+
+ if (idGoal) {
+ params.idGoal = idGoal;
+ if (revenue) {
+ params.revenue = revenue;
+ }
+ }
+
+ var result = '<!-- Piwik Image Tracker -->\n\
<img src="' + piwikURL + '?' + $.param(params) + '" style="border:0" alt="" />\n\
<!-- End Piwik -->';
- result = result.replace("&", "&amp;", "g");
- $('#image-tracking-link textarea').val(result);
- };
-
- // on image link tracker site change, change available goals
- $('#image-tracker-website').bind('piwik:siteSelected', function(e, site) {
- getSiteData(site.id, '#image-tracking-code-options', function() {
- resetGoalSelectItems(site.id, 'image-tracker-goal');
- generateImageTrackerLink();
- });
- });
-
- // on js link tracker site change, change available goals
- $('#js-tracker-website').bind('piwik:siteSelected', function(e, site) {
- $('.current-site-name', '#optional-js-tracking-options').each(function() {
- $(this).text(site.name);
- });
-
- getSiteData(site.id, '#js-code-options', function() {
- var siteHost = getHostNameFromUrl(siteUrls[site.id][0]);
- $('.current-site-host', '#optional-js-tracking-options').each(function() {
- $(this).text(siteHost);
- });
-
- var defaultAliasUrl = 'x.' + siteHost;
- $('.current-site-alias').text(siteUrls[site.id][1] || defaultAliasUrl);
-
- resetGoalSelectItems(site.id, 'js-tracker-goal');
- generateJsCode();
- });
- });
-
- // on click 'add' link in custom variable section, add a new row, but only
- // allow 5 custom variable entry rows
- $('.add-custom-variable').click(function(e) {
- e.preventDefault();
-
- var newRow = '<tr>\
+ result = result.replace("&", "&amp;", "g");
+ $('#image-tracking-link textarea').val(result);
+ };
+
+ // on image link tracker site change, change available goals
+ $('#image-tracker-website').bind('piwik:siteSelected', function (e, site) {
+ getSiteData(site.id, '#image-tracking-code-options', function () {
+ resetGoalSelectItems(site.id, 'image-tracker-goal');
+ generateImageTrackerLink();
+ });
+ });
+
+ // on js link tracker site change, change available goals
+ $('#js-tracker-website').bind('piwik:siteSelected', function (e, site) {
+ $('.current-site-name', '#optional-js-tracking-options').each(function () {
+ $(this).text(site.name);
+ });
+
+ getSiteData(site.id, '#js-code-options', function () {
+ var siteHost = getHostNameFromUrl(siteUrls[site.id][0]);
+ $('.current-site-host', '#optional-js-tracking-options').each(function () {
+ $(this).text(siteHost);
+ });
+
+ var defaultAliasUrl = 'x.' + siteHost;
+ $('.current-site-alias').text(siteUrls[site.id][1] || defaultAliasUrl);
+
+ resetGoalSelectItems(site.id, 'js-tracker-goal');
+ generateJsCode();
+ });
+ });
+
+ // on click 'add' link in custom variable section, add a new row, but only
+ // allow 5 custom variable entry rows
+ $('.add-custom-variable').click(function (e) {
+ e.preventDefault();
+
+ var newRow = '<tr>\
<td>&nbsp;</td>\
<td><input type="textbox" class="custom-variable-name"/></td>\
<td>&nbsp;</td>\
<td><input type="textbox" class="custom-variable-value"/></td>\
</tr>',
- row = $(this).closest('tr');
-
- row.before(newRow);
-
- // hide add button if max # of custom variables has been reached
- // (5 custom variables + 1 row for add new row)
- if ($('tr', row.parent()).length == 6)
- {
- $(this).hide();
- }
-
- return false;
- });
-
- // when any input in the JS tracking options section changes, regenerate JS code
- $('#optional-js-tracking-options').on('change', 'input', function() {
- generateJsCode();
- });
-
- // when any input/select in the image tracking options section changes, regenerate
- // image tracker link
- $('#image-tracking-section').on('change', 'input,select', function() {
- generateImageTrackerLink();
- });
-
- // on click generated code textareas, select the text so it can be easily copied
- $('#javascript-text>textarea,#image-tracking-link>textarea').click(function() {
- $(this).select();
- });
-
- // initial generation
- getSiteData($(
- '#js-tracker-website .custom_select_main_link').attr('siteid'),
- '#js-code-options,#image-tracking-code-options',
- function() {
- var imageTrackerSiteId = $('#image-tracker-website .custom_select_main_link').attr('siteid');
- resetGoalSelectItems(imageTrackerSiteId, 'image-tracker-goal');
-
- generateJsCode();
- generateImageTrackerLink();
- }
- );
-});
+ row = $(this).closest('tr');
+
+ row.before(newRow);
+
+ // hide add button if max # of custom variables has been reached
+ // (5 custom variables + 1 row for add new row)
+ if ($('tr', row.parent()).length == 6) {
+ $(this).hide();
+ }
+
+ return false;
+ });
+
+ // when any input in the JS tracking options section changes, regenerate JS code
+ $('#optional-js-tracking-options').on('change', 'input', function () {
+ generateJsCode();
+ });
+
+ // when any input/select in the image tracking options section changes, regenerate
+ // image tracker link
+ $('#image-tracking-section').on('change', 'input,select', function () {
+ generateImageTrackerLink();
+ });
+
+ // on click generated code textareas, select the text so it can be easily copied
+ $('#javascript-text>textarea,#image-tracking-link>textarea').click(function () {
+ $(this).select();
+ });
+
+ // initial generation
+ getSiteData($(
+ '#js-tracker-website .custom_select_main_link').attr('siteid'),
+ '#js-code-options,#image-tracking-code-options',
+ function () {
+ var imageTrackerSiteId = $('#image-tracker-website .custom_select_main_link').attr('siteid');
+ resetGoalSelectItems(imageTrackerSiteId, 'image-tracker-goal');
+
+ generateJsCode();
+ generateImageTrackerLink();
+ }
+ );
+ });
}(jQuery));
diff --git a/plugins/CoreAdminHome/templates/jsTrackingGenerator.tpl b/plugins/CoreAdminHome/templates/jsTrackingGenerator.tpl
index 529516c3b5..0348d620b9 100644
--- a/plugins/CoreAdminHome/templates/jsTrackingGenerator.tpl
+++ b/plugins/CoreAdminHome/templates/jsTrackingGenerator.tpl
@@ -3,238 +3,247 @@
<script type="text/javascript" src="plugins/CoreAdminHome/templates/jsTrackingGenerator.js"></script>
<div id="js-tracking-generator-data"
- data-currencies = "{$currencySymbols|@json_encode|escape:'html'}"/>
+ data-currencies="{$currencySymbols|@json_encode|escape:'html'}"/>
<h2>{'CoreAdminHome_JavaScriptTracking'|translate}</h2>
<div id="js-code-options" class="adminTable">
-<p>
- {'CoreAdminHome_JSTrackingIntro1'|translate}
- <br/><br/>
- {'CoreAdminHome_JSTrackingIntro2'|translate} {'CoreAdminHome_JSTrackingIntro3'|translate:'<a href="http://piwik.org/integrate/" target="_blank">':'</a>'}
- <br/><br/>
- {'CoreAdminHome_JSTrackingIntro4'|translate:'<a href="#image-tracking-link">':'</a>'}
- <br/><br/>
- {'CoreAdminHome_JSTrackingIntro5'|translate:'<a target="_blank" href="http://piwik.org/docs/javascript-tracking/">':'</a>'}
-</p>
-
-<div>
- {* website *}
- <label class="website-label"><strong>{'General_Website'|translate}</strong></label>
- {include file="CoreHome/templates/sites_selection.tpl"
- siteName=$defaultReportSiteName idSite=$idSite showAllSitesItem=false switchSiteOnSelect=false
- siteSelectorId="js-tracker-website"}
+ <p>
+ {'CoreAdminHome_JSTrackingIntro1'|translate}
+ <br/><br/>
+ {'CoreAdminHome_JSTrackingIntro2'|translate} {'CoreAdminHome_JSTrackingIntro3'|translate:'<a href="http://piwik.org/integrate/" target="_blank">':'</a>'}
+ <br/><br/>
+ {'CoreAdminHome_JSTrackingIntro4'|translate:'<a href="#image-tracking-link">':'</a>'}
+ <br/><br/>
+ {'CoreAdminHome_JSTrackingIntro5'|translate:'<a target="_blank" href="http://piwik.org/docs/javascript-tracking/">':'</a>'}
+ </p>
+
+ <div>
+ {* website *}
+ <label class="website-label"><strong>{'General_Website'|translate}</strong></label>
+ {include file="CoreHome/templates/sites_selection.tpl"
+ siteName=$defaultReportSiteName idSite=$idSite showAllSitesItem=false switchSiteOnSelect=false
+ siteSelectorId="js-tracker-website"}
+
+ <br/><br/><br/>
+ </div>
+
+ <table id="optional-js-tracking-options" class="adminTable" style='width:1024px'>
+ <tr>
+ <th>{'General_Options'|translate}</th>
+ <th>{'Mobile_Advanced'|translate} <a href="#" class="section-toggler-link"
+ data-section-id="javascript-advanced-options">({'General_Show_js'|translate})</a></th>
+ </tr>
+ <tr>
+ <td>
+ {* track across all subdomains *}
+ <div class="tracking-option-section">
+ <input type="checkbox" id="javascript-tracking-all-subdomains"/>
+ <label for="javascript-tracking-all-subdomains">{'CoreAdminHome_JSTracking_MergeSubdomains'|translate} <span
+ class='current-site-name'>{$defaultReportSiteName}</span></label>
+
+ <div class="small-form-description">
+ {'CoreAdminHome_JSTracking_MergeSubdomainsDesc'|translate:"x.<span class='current-site-host'>$defaultReportSiteDomain</span>":"y.<span class='current-site-host'>$defaultReportSiteDomain</span>"}
+ </div>
+ </div>
+
+ {* group page titles by site domain *}
+ <div class="tracking-option-section">
+ <input type="checkbox" id="javascript-tracking-group-by-domain"/>
+ <label for="javascript-tracking-group-by-domain">{'CoreAdminHome_JSTracking_GroupPageTitlesByDomain'|translate}</label>
+
+ <div class="small-form-description">
+ {'CoreAdminHome_JSTracking_GroupPageTitlesByDomainDesc1'|translate:"<span class='current-site-host'>$defaultReportSiteDomain</span>"}
+ </div>
+ </div>
+
+ {* track across all site aliases *}
+ <div class="tracking-option-section">
+ <input type="checkbox" id="javascript-tracking-all-aliases"/>
+ <label for="javascript-tracking-all-aliases">{'CoreAdminHome_JSTracking_MergeAliases'|translate} <span
+ class='current-site-name'>{$defaultReportSiteName}</span></label>
+
+ <div class="small-form-description">
+ {'CoreAdminHome_JSTracking_MergeAliasesDesc'|translate:"<span class='current-site-alias'>$defaultReportSiteAlias</span>"}
+ </div>
+ </div>
+
+ </td>
+ <td>
+ <div id="javascript-advanced-options" style="display:none">
+ {* visitor custom variable *}
+ <div class="custom-variable tracking-option-section" id="javascript-tracking-visitor-cv">
+ <input class="section-toggler-link" type="checkbox" id="javascript-tracking-visitor-cv-check" data-section-id="js-visitor-cv-extra"/>
+ <label for="javascript-tracking-visitor-cv-check">{'CoreAdminHome_JSTracking_VisitorCustomVars'|translate}</label>
+
+ <div class="small-form-description">
+ {'CoreAdminHome_JSTracking_VisitorCustomVarsDesc'|translate}
+ </div>
+
+ <table style="display:none" id="js-visitor-cv-extra">
+ <tr>
+ <td><strong>{'General_Name'|translate}</strong></td>
+ <td><input type="textbox" class="custom-variable-name" placeholder="e.g. Type"/></td>
+ <td><strong>{'General_Value'|translate}</strong></td>
+ <td><input type="textbox" class="custom-variable-value" placeholder="e.g. Customer"/></td>
+ </tr>
+ <tr>
+ <td colspan="4" style="text-align:right">
+ <a href="#" class="add-custom-variable">{'General_Add'|translate}</a>
+ </td>
+ </tr>
+ </table>
+ </div>
+
+ {* page view custom variable *}
+ <div class="custom-variable tracking-option-section" id="javascript-tracking-page-cv">
+ <input class="section-toggler-link" type="checkbox" id="javascript-tracking-page-cv-check" data-section-id="js-page-cv-extra"/>
+ <label for="javascript-tracking-page-cv-check">{'CoreAdminHome_JSTracking_PageCustomVars'|translate}</label>
+
+ <div class="small-form-description">
+ {'CoreAdminHome_JSTracking_PageCustomVarsDesc'|translate}
+ </div>
+
+ <table style="display:none" id="js-page-cv-extra">
+ <tr>
+ <td><strong>{'General_Name'|translate}</strong></td>
+ <td><input type="textbox" class="custom-variable-name" placeholder="e.g. Category"/></td>
+ <td><strong>{'General_Value'|translate}</strong></td>
+ <td><input type="textbox" class="custom-variable-value" placeholder="e.g. White Papers"/></td>
+ </tr>
+ <tr>
+ <td colspan="4" style="text-align:right">
+ <a href="#" class="add-custom-variable">{'General_Add'|translate}</a>
+ </td>
+ </tr>
+ </table>
+ </div>
+
+ {* do not track support *}
+ <div class="tracking-option-section">
+ <input type="checkbox" id="javascript-tracking-do-not-track"/>
+ <label for="javascript-tracking-do-not-track">{'CoreAdminHome_JSTracking_EnableDoNotTrack'|translate}</label>
+
+ <div class="small-form-description">
+ {'CoreAdminHome_JSTracking_EnableDoNotTrackDesc'|translate}
+ {if $serverSideDoNotTrackEnabled}
+ <br/>
+ <br/>
+ {'CoreAdminHome_JSTracking_EnableDoNotTrack_AlreadyEnabled'|translate}
+ {/if}
+ </div>
+ </div>
+
+ {* custom campaign name/keyword query params *}
+ <div class="tracking-option-section">
+ <input class="section-toggler-link" type="checkbox" id="custom-campaign-query-params-check"
+ data-section-id="js-campaign-query-param-extra"/>
+ <label for="custom-campaign-query-params-check">{'CoreAdminHome_JSTracking_CustomCampaignQueryParam'|translate}</label>
+
+ <div class="small-form-description">
+ {'CoreAdminHome_JSTracking_CustomCampaignQueryParamDesc'|translate:'<a href="http://piwik.org/faq/general/#faq_119" target="_blank">':'</a>'}
+ </div>
+
+ <table style="display:none" id="js-campaign-query-param-extra">
+ <tr>
+ <td><strong>{'CoreAdminHome_JSTracking_CampaignNameParam'|translate}</strong></td>
+ <td><input type="text" id="custom-campaign-name-query-param"/></td>
+ </tr>
+ <tr>
+ <td><strong>{'CoreAdminHome_JSTracking_CampaignKwdParam'|translate}</strong></td>
+ <td><input type="text" id="custom-campaign-keyword-query-param"/></td>
+ </tr>
+ </table>
+ </div>
+ </div>
+ </td>
+ </tr>
+ </table>
- <br/><br/><br/>
</div>
-<table id="optional-js-tracking-options" class="adminTable" style='width:1024px'>
-<tr>
- <th>{'General_Options'|translate}</th>
- <th>{'Mobile_Advanced'|translate} <a href="#" class="section-toggler-link" data-section-id="javascript-advanced-options">({'General_Show_js'|translate})</a></th>
-</tr>
-<tr>
-<td>
- {* track across all subdomains *}
- <div class="tracking-option-section">
- <input type="checkbox" id="javascript-tracking-all-subdomains"/>
- <label for="javascript-tracking-all-subdomains">{'CoreAdminHome_JSTracking_MergeSubdomains'|translate} <span class='current-site-name'>{$defaultReportSiteName}</span></label>
-
- <div class="small-form-description">
- {'CoreAdminHome_JSTracking_MergeSubdomainsDesc'|translate:"x.<span class='current-site-host'>$defaultReportSiteDomain</span>":"y.<span class='current-site-host'>$defaultReportSiteDomain</span>"}
- </div>
- </div>
-
- {* group page titles by site domain *}
- <div class="tracking-option-section">
- <input type="checkbox" id="javascript-tracking-group-by-domain"/>
- <label for="javascript-tracking-group-by-domain">{'CoreAdminHome_JSTracking_GroupPageTitlesByDomain'|translate}</label>
-
- <div class="small-form-description">
- {'CoreAdminHome_JSTracking_GroupPageTitlesByDomainDesc1'|translate:"<span class='current-site-host'>$defaultReportSiteDomain</span>"}
- </div>
- </div>
-
- {* track across all site aliases *}
- <div class="tracking-option-section">
- <input type="checkbox" id="javascript-tracking-all-aliases"/>
- <label for="javascript-tracking-all-aliases">{'CoreAdminHome_JSTracking_MergeAliases'|translate} <span class='current-site-name'>{$defaultReportSiteName}</span></label>
-
- <div class="small-form-description">
- {'CoreAdminHome_JSTracking_MergeAliasesDesc'|translate:"<span class='current-site-alias'>$defaultReportSiteAlias</span>"}
- </div>
- </div>
-
-</td>
-<td>
- <div id="javascript-advanced-options" style="display:none">
- {* visitor custom variable *}
- <div class="custom-variable tracking-option-section" id="javascript-tracking-visitor-cv">
- <input class="section-toggler-link" type="checkbox" id="javascript-tracking-visitor-cv-check" data-section-id="js-visitor-cv-extra"/>
- <label for="javascript-tracking-visitor-cv-check">{'CoreAdminHome_JSTracking_VisitorCustomVars'|translate}</label>
-
- <div class="small-form-description">
- {'CoreAdminHome_JSTracking_VisitorCustomVarsDesc'|translate}
- </div>
-
- <table style="display:none" id="js-visitor-cv-extra">
- <tr>
- <td><strong>{'General_Name'|translate}</strong></td>
- <td><input type="textbox" class="custom-variable-name" placeholder="e.g. Type"/></td>
- <td><strong>{'General_Value'|translate}</strong></td>
- <td><input type="textbox" class="custom-variable-value" placeholder="e.g. Customer"/></td>
- </tr>
- <tr>
- <td colspan="4" style="text-align:right">
- <a href="#" class="add-custom-variable">{'General_Add'|translate}</a>
- </td>
- </tr>
- </table>
- </div>
-
- {* page view custom variable *}
- <div class="custom-variable tracking-option-section" id="javascript-tracking-page-cv">
- <input class="section-toggler-link" type="checkbox" id="javascript-tracking-page-cv-check" data-section-id="js-page-cv-extra"/>
- <label for="javascript-tracking-page-cv-check">{'CoreAdminHome_JSTracking_PageCustomVars'|translate}</label>
-
- <div class="small-form-description">
- {'CoreAdminHome_JSTracking_PageCustomVarsDesc'|translate}
- </div>
-
- <table style="display:none" id="js-page-cv-extra">
- <tr>
- <td><strong>{'General_Name'|translate}</strong></td>
- <td><input type="textbox" class="custom-variable-name" placeholder="e.g. Category"/></td>
- <td><strong>{'General_Value'|translate}</strong></td>
- <td><input type="textbox" class="custom-variable-value" placeholder="e.g. White Papers"/></td>
- </tr>
- <tr>
- <td colspan="4" style="text-align:right">
- <a href="#" class="add-custom-variable">{'General_Add'|translate}</a>
- </td>
- </tr>
- </table>
- </div>
-
- {* do not track support *}
- <div class="tracking-option-section">
- <input type="checkbox" id="javascript-tracking-do-not-track"/>
- <label for="javascript-tracking-do-not-track">{'CoreAdminHome_JSTracking_EnableDoNotTrack'|translate}</label>
-
- <div class="small-form-description">
- {'CoreAdminHome_JSTracking_EnableDoNotTrackDesc'|translate}
- {if $serverSideDoNotTrackEnabled}
- <br/><br/>
- {'CoreAdminHome_JSTracking_EnableDoNotTrack_AlreadyEnabled'|translate}
- {/if}
- </div>
- </div>
-
- {* custom campaign name/keyword query params *}
- <div class="tracking-option-section">
- <input class="section-toggler-link" type="checkbox" id="custom-campaign-query-params-check" data-section-id="js-campaign-query-param-extra"/>
- <label for="custom-campaign-query-params-check">{'CoreAdminHome_JSTracking_CustomCampaignQueryParam'|translate}</label>
-
- <div class="small-form-description">
- {'CoreAdminHome_JSTracking_CustomCampaignQueryParamDesc'|translate:'<a href="http://piwik.org/faq/general/#faq_119" target="_blank">':'</a>'}
- </div>
-
- <table style="display:none" id="js-campaign-query-param-extra">
- <tr>
- <td><strong>{'CoreAdminHome_JSTracking_CampaignNameParam'|translate}</strong></td>
- <td><input type="text" id="custom-campaign-name-query-param"/></td>
- </tr>
- <tr>
- <td><strong>{'CoreAdminHome_JSTracking_CampaignKwdParam'|translate}</strong></td>
- <td><input type="text" id="custom-campaign-keyword-query-param"/></td>
- </tr>
- </table>
- </div>
- </div>
-</td>
-</tr>
-</table>
+<div id="javascript-output-section">
+ <h3>{'Installation_JsTag'|translate}</h3>
-</div>
+ <p class="form-description">{'CoreAdminHome_JSTracking_CodeNote'|translate:"&lt;/body&gt;"}</p>
-<div id="javascript-output-section">
- <h3>{'Installation_JsTag'|translate}</h3>
- <p class="form-description">{'CoreAdminHome_JSTracking_CodeNote'|translate:"&lt;/body&gt;"}</p>
- <div id="javascript-text">
- <textarea> </textarea>
- </div>
- <br/>
+ <div id="javascript-text">
+ <textarea> </textarea>
+ </div>
+ <br/>
</div>
<h2 id="image-tracking-link">{'CoreAdminHome_ImageTracking'|translate}</h2>
<div id="image-tracking-code-options" class="adminTable">
-<p>
- {'CoreAdminHome_ImageTrackingIntro1'|translate} {'CoreAdminHome_ImageTrackingIntro2'|translate:"<em>&lt;noscript&gt;&lt;/noscript&gt;</em>"}
- <br/><br/>
- {'CoreAdminHome_ImageTrackingIntro3'|translate:'<a href="http://piwik.org/docs/tracking-api/reference/" target="_blank">':'</a>'}
-</p>
-
-<div>
- {* website *}
- <label class="website-label"><strong>{'General_Website'|translate}</strong></label>
- {include file="CoreHome/templates/sites_selection.tpl"
- siteName=$defaultReportSiteName idSite=$idSite showAllSitesItem=false switchSiteOnSelect=false
- siteSelectorId="image-tracker-website"}
-
- <br/><br/><br/>
-</div>
-
-<table id="image-tracking-section" class="adminTable" style='width:1024px;'>
-<tr>
- <th>{'General_Options'|translate}</th>
- <th>{'Mobile_Advanced'|translate} <a href="#" class="section-toggler-link" data-section-id="image-tracker-advanced-options">({'General_Show_js'|translate})</a></th>
-</tr>
-<tr>
-<td>
- {* action_name *}
- <div class="tracking-option-section">
- <label for="image-tracker-action-name">{'Actions_ColumnPageName'|translate}</label>
- <input type="text" id="image-tracker-action-name"/>
- </div>
-</td>
-<td>
- <div id="image-tracker-advanced-options" style="display:none">
- {* goal *}
- <div class="goal-picker tracking-option-section">
- <input class="section-toggler-link" type="checkbox" id="image-tracking-goal-check" data-section-id="image-goal-picker-extra"/>
- <label for="image-tracking-goal-check">{'CoreAdminHome_TrackAGoal'|translate}</label>
-
- <div style="display:none" id="image-goal-picker-extra">
- <select id="image-tracker-goal">
- <option value="">{'UserCountryMap_None'|translate}</option>
- </select>
- <span>{'CoreAdminHome_WithOptionalRevenue'|translate}</span>
- <span class="currency">{$defaultSiteRevenue|escape:'html'}</span>
- <input type="text" class="revenue" value=""/>
- </div>
- </div>
- </div>
-</td>
-</tr>
-</table>
-
-<div id="image-link-output-section" width="560px">
- <h3>{'CoreAdminHome_ImageTrackingLink'|translate}</h3><br/><br/>
- <div id="image-tracking-link">
- <textarea> </textarea>
- </div>
- <br/>
-</div>
+ <p>
+ {'CoreAdminHome_ImageTrackingIntro1'|translate} {'CoreAdminHome_ImageTrackingIntro2'|translate:"<em>&lt;noscript&gt;&lt;/noscript&gt;</em>"}
+ <br/><br/>
+ {'CoreAdminHome_ImageTrackingIntro3'|translate:'<a href="http://piwik.org/docs/tracking-api/reference/" target="_blank">':'</a>'}
+ </p>
+
+ <div>
+ {* website *}
+ <label class="website-label"><strong>{'General_Website'|translate}</strong></label>
+ {include file="CoreHome/templates/sites_selection.tpl"
+ siteName=$defaultReportSiteName idSite=$idSite showAllSitesItem=false switchSiteOnSelect=false
+ siteSelectorId="image-tracker-website"}
+
+ <br/><br/><br/>
+ </div>
+
+ <table id="image-tracking-section" class="adminTable" style='width:1024px;'>
+ <tr>
+ <th>{'General_Options'|translate}</th>
+ <th>{'Mobile_Advanced'|translate} <a href="#" class="section-toggler-link"
+ data-section-id="image-tracker-advanced-options">({'General_Show_js'|translate})</a></th>
+ </tr>
+ <tr>
+ <td>
+ {* action_name *}
+ <div class="tracking-option-section">
+ <label for="image-tracker-action-name">{'Actions_ColumnPageName'|translate}</label>
+ <input type="text" id="image-tracker-action-name"/>
+ </div>
+ </td>
+ <td>
+ <div id="image-tracker-advanced-options" style="display:none">
+ {* goal *}
+ <div class="goal-picker tracking-option-section">
+ <input class="section-toggler-link" type="checkbox" id="image-tracking-goal-check" data-section-id="image-goal-picker-extra"/>
+ <label for="image-tracking-goal-check">{'CoreAdminHome_TrackAGoal'|translate}</label>
+
+ <div style="display:none" id="image-goal-picker-extra">
+ <select id="image-tracker-goal">
+ <option value="">{'UserCountryMap_None'|translate}</option>
+ </select>
+ <span>{'CoreAdminHome_WithOptionalRevenue'|translate}</span>
+ <span class="currency">{$defaultSiteRevenue|escape:'html'}</span>
+ <input type="text" class="revenue" value=""/>
+ </div>
+ </div>
+ </div>
+ </td>
+ </tr>
+ </table>
+
+ <div id="image-link-output-section" width="560px">
+ <h3>{'CoreAdminHome_ImageTrackingLink'|translate}</h3><br/><br/>
+
+ <div id="image-tracking-link">
+ <textarea> </textarea>
+ </div>
+ <br/>
+ </div>
</div>
<h2>{'CoreAdminHome_ImportingServerLogs'|translate}</h2>
<p>
-{'CoreAdminHome_ImportingServerLogsDesc'|translate:'<a href="http://piwik.org/log-analytics/" target="_blank">':'</a>'}
+ {'CoreAdminHome_ImportingServerLogsDesc'|translate:'<a href="http://piwik.org/log-analytics/" target="_blank">':'</a>'}
</p>
{include file="CoreAdminHome/templates/footer.tpl"}
diff --git a/plugins/CoreAdminHome/templates/menu.css b/plugins/CoreAdminHome/templates/menu.css
index 5f9b2943b4..207263f86a 100644
--- a/plugins/CoreAdminHome/templates/menu.css
+++ b/plugins/CoreAdminHome/templates/menu.css
@@ -1,81 +1,72 @@
-#menu{
- padding:0 0 0 0;
- float: left;
- width: 240px;
- position: absolute;
+#menu {
+ padding: 0 0 0 0;
+ float: left;
+ width: 240px;
+ position: absolute;
}
#tablist {
- background-image: linear-gradient(top, #FECB00 0%, #FE9800 25%, #FE6702 50%, #CA0000 75%, #670002 100%);
- background-image: -o-linear-gradient(top, #FECB00 0%, #FE9800 25%, #FE6702 50%, #CA0000 75%, #670002 100%);
- background-image: -moz-linear-gradient(top, #FECB00 0%, #FE9800 25%, #FE6702 50%, #CA0000 75%, #670002 100%);
- background-image: -webkit-linear-gradient(top, #FECB00 0%, #FE9800 25%, #FE6702 50%, #CA0000 75%, #670002 100%);
- background-image: -ms-linear-gradient(top, #FECB00 0%, #FE9800 25%, #FE6702 50%, #CA0000 75%, #670002 100%);
+ background-image: linear-gradient(top, #FECB00 0%, #FE9800 25%, #FE6702 50%, #CA0000 75%, #670002 100%);
+ background-image: -o-linear-gradient(top, #FECB00 0%, #FE9800 25%, #FE6702 50%, #CA0000 75%, #670002 100%);
+ background-image: -moz-linear-gradient(top, #FECB00 0%, #FE9800 25%, #FE6702 50%, #CA0000 75%, #670002 100%);
+ background-image: -webkit-linear-gradient(top, #FECB00 0%, #FE9800 25%, #FE6702 50%, #CA0000 75%, #670002 100%);
+ background-image: -ms-linear-gradient(top, #FECB00 0%, #FE9800 25%, #FE6702 50%, #CA0000 75%, #670002 100%);
- background-image: -webkit-gradient(
- linear,
- left top,
- left bottom,
- color-stop(0, #FECB00),
- color-stop(0.25, #FE9800),
- color-stop(0.5, #FE6702),
- color-stop(0.75, #CA0000),
- color-stop(1, #670002)
- );
+ background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0, #FECB00), color-stop(0.25, #FE9800), color-stop(0.5, #FE6702), color-stop(0.75, #CA0000), color-stop(1, #670002));
- -moz-background-size:5px 100%;
- background-size:5px 100%;
- background-position:0 0, 100% 0;
- background-repeat:no-repeat;
+ -moz-background-size: 5px 100%;
+ background-size: 5px 100%;
+ background-position: 0 0, 100% 0;
+ background-repeat: no-repeat;
}
#tablist {
- padding-left: 5px;
- margin-bottom: 0;
- margin-top: 0.1em;
- border: 1px solid #ddd;
- border-radius: 5px;
+ padding-left: 5px;
+ margin-bottom: 0;
+ margin-top: 0.1em;
+ border: 1px solid #ddd;
+ border-radius: 5px;
}
#tablist li {
- list-style: none;
- margin: 0;
+ list-style: none;
+ margin: 0;
}
#tablist > li {
- padding-bottom: 5px;
+ padding-bottom: 5px;
}
#tablist > li > span {
- border-bottom: 1px dotted #778;
- display: block;
- padding: 5px 10px;
- font-size: 18px;
- color: #7E7363;
+ border-bottom: 1px dotted #778;
+ display: block;
+ padding: 5px 10px;
+ font-size: 18px;
+ color: #7E7363;
}
#tablist li a {
- text-decoration: none;
- padding: 0.6em 0.9em;
- background: white;
- font: 14px Arial, Helvetica, sans-serif;
- display: block;
+ text-decoration: none;
+ padding: 0.6em 0.9em;
+ background: white;
+ font: 14px Arial, Helvetica, sans-serif;
+ display: block;
}
-#tablist li a:link,#tablist li a:visited {
- color: #000;
+#tablist li a:link, #tablist li a:visited {
+ color: #000;
}
#tablist li a:hover, #tablist li a.active {
- color: #e87500;
- background: #f1f1f1;
- border-color: #000;
+ color: #e87500;
+ background: #f1f1f1;
+ border-color: #000;
}
#tablist li a:hover {
- text-decoration: underline;
+ text-decoration: underline;
}
#tablist li a.current {
- background: #defdbb;
+ background: #defdbb;
}
diff --git a/plugins/CoreAdminHome/templates/menu.tpl b/plugins/CoreAdminHome/templates/menu.tpl
index ade09008b4..fba9efd87e 100644
--- a/plugins/CoreAdminHome/templates/menu.tpl
+++ b/plugins/CoreAdminHome/templates/menu.tpl
@@ -1,20 +1,21 @@
{if count($menu) > 1}
-<div id="menu">
-<ul id="tablist">
-{foreach from=$menu key=name item=submenu name=menu}
-{if $submenu._hasSubmenu}
- <li>
- <span>{$name|translate}</span>
- <ul>
- {foreach from=$submenu key=sname item=url name=submenu}
- {if strpos($sname, '_') !== 0}
- <li><a href='index.php{$url._url|@urlRewriteWithParameters}' {if isset($currentAdminMenuName) && $sname==$currentAdminMenuName}class='active'{/if}>{$sname|translate}</a></li>
- {/if}
- {/foreach}
- </ul>
- </li>
-{/if}
-{/foreach}
-</ul>
-</div>
+ <div id="menu">
+ <ul id="tablist">
+ {foreach from=$menu key=name item=submenu name=menu}
+ {if $submenu._hasSubmenu}
+ <li>
+ <span>{$name|translate}</span>
+ <ul>
+ {foreach from=$submenu key=sname item=url name=submenu}
+ {if strpos($sname, '_') !== 0}
+ <li><a href='index.php{$url._url|@urlRewriteWithParameters}'
+ {if isset($currentAdminMenuName) && $sname==$currentAdminMenuName}class='active'{/if}>{$sname|translate}</a></li>
+ {/if}
+ {/foreach}
+ </ul>
+ </li>
+ {/if}
+ {/foreach}
+ </ul>
+ </div>
{/if}
diff --git a/plugins/CoreAdminHome/templates/optOut.tpl b/plugins/CoreAdminHome/templates/optOut.tpl
index 10c336d8a6..f53204c5d5 100644
--- a/plugins/CoreAdminHome/templates/optOut.tpl
+++ b/plugins/CoreAdminHome/templates/optOut.tpl
@@ -1,27 +1,28 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
- "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
- <head>
- <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
- </head>
- <body>
- {if !$trackVisits}{'CoreAdminHome_OptOutComplete'|translate}
- <br/>
- {'CoreAdminHome_OptOutCompleteBis'|translate}
- {else}
- {'CoreAdminHome_YouMayOptOut'|translate}
- <br/>
- {'CoreAdminHome_YouMayOptOutBis'|translate}
- {/if}
- <br/><br/>
- <form method="post" action="?module=CoreAdminHome&amp;action=optOut{if $language}&amp;language={$language}{/if}">
- <input type="hidden" name="nonce" value="{$nonce}" ></input>
- <input type="hidden" name="fuzz" value="{$smarty.now}"></input>
- <input onclick="this.form.submit()" type="checkbox" id="trackVisits" name="trackVisits" {if $trackVisits}checked="checked"{/if}></input>
- <label for="trackVisits"><strong>
- {if $trackVisits}{'CoreAdminHome_YouAreOptedIn'|translate} {'CoreAdminHome_ClickHereToOptOut'|translate}
- {else}{'CoreAdminHome_YouAreOptedOut'|translate} {'CoreAdminHome_ClickHereToOptIn'|translate}{/if}
- </strong></label>
- </form>
- </body>
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
+</head>
+<body>
+{if !$trackVisits}{'CoreAdminHome_OptOutComplete'|translate}
+ <br/>
+ {'CoreAdminHome_OptOutCompleteBis'|translate}
+{else}
+ {'CoreAdminHome_YouMayOptOut'|translate}
+ <br/>
+ {'CoreAdminHome_YouMayOptOutBis'|translate}
+{/if}
+<br/><br/>
+
+<form method="post" action="?module=CoreAdminHome&amp;action=optOut{if $language}&amp;language={$language}{/if}">
+ <input type="hidden" name="nonce" value="{$nonce}"></input>
+ <input type="hidden" name="fuzz" value="{$smarty.now}"></input>
+ <input onclick="this.form.submit()" type="checkbox" id="trackVisits" name="trackVisits" {if $trackVisits}checked="checked"{/if}></input>
+ <label for="trackVisits"><strong>
+ {if $trackVisits}{'CoreAdminHome_YouAreOptedIn'|translate} {'CoreAdminHome_ClickHereToOptOut'|translate}
+ {else}{'CoreAdminHome_YouAreOptedOut'|translate} {'CoreAdminHome_ClickHereToOptIn'|translate}{/if}
+ </strong></label>
+</form>
+</body>
</html>
diff --git a/plugins/CoreAdminHome/templates/styles.css b/plugins/CoreAdminHome/templates/styles.css
index 73f5b75195..71871038b0 100644
--- a/plugins/CoreAdminHome/templates/styles.css
+++ b/plugins/CoreAdminHome/templates/styles.css
@@ -1,164 +1,179 @@
.admin img {
- vertical-align: middle;;
+ vertical-align: middle;;
}
+
.admin a {
- color: black;
+ color: black;
}
#content.admin {
- margin:0 15px 0 270px;
+ margin: 0 15px 0 270px;
padding: 0 0 40px;
- font:13px Arial, Helvetica, sans-serif;
+ font: 13px Arial, Helvetica, sans-serif;
}
-.admin #header_message{
- margin-top:-10px;
+.admin #header_message {
+ margin-top: -10px;
}
table.admin {
- font-size: 0.9em;
- font-family: Arial, Helvetica, verdana sans-serif;
- background-color: #fff;
- border-collapse: collapse;
+ font-size: 0.9em;
+ font-family: Arial, Helvetica, verdana sans-serif;
+ background-color: #fff;
+ border-collapse: collapse;
}
table.admin thead th {
- border-right: 1px solid #fff;
- color: #fff;
- text-align: center;
- padding: 5px;
- text-transform: uppercase;
- height: 25px;
- background-color: #a3c159;
- font-weight:bold;
+ border-right: 1px solid #fff;
+ color: #fff;
+ text-align: center;
+ padding: 5px;
+ text-transform: uppercase;
+ height: 25px;
+ background-color: #a3c159;
+ font-weight: bold;
}
table.admin tbody tr {
- background-color: #fff;
- border-bottom: 1px solid #f0f0f0;
+ background-color: #fff;
+ border-bottom: 1px solid #f0f0f0;
}
table.admin tbody td {
- color: #414141;
- text-align: left;
- vertical-align:top;
+ color: #414141;
+ text-align: left;
+ vertical-align: top;
}
table.admin tbody th {
- text-align: left;
- padding: 2px;
+ text-align: left;
+ padding: 2px;
}
+
table.admin tbody td, table.admin tbody th {
- color: #536C2A;
- text-decoration: none;
- font-weight: normal;
- padding: 10px;
+ color: #536C2A;
+ text-decoration: none;
+ font-weight: normal;
+ padding: 10px;
}
table.admin tbody td:hover, table.admin tbody th:hover {
- color: #009193;
- text-decoration: none;
+ color: #009193;
+ text-decoration: none;
}
.warning {
- border: 1px dotted gray;
- padding: 15px;
- font-size: .8em;
+ border: 1px dotted gray;
+ padding: 15px;
+ font-size: .8em;
}
.warning ul {
- margin-left: 50px;
+ margin-left: 50px;
}
.access_error {
- font-size: .7em;
- padding: 15px;
+ font-size: .7em;
+ padding: 15px;
}
.admin h2 {
- border-bottom:1px solid #DADADA;
- margin:15px -15px 20px 0;
- padding:0 0 5px 0;
- font-size:24px;
+ border-bottom: 1px solid #DADADA;
+ margin: 15px -15px 20px 0;
+ padding: 0 0 5px 0;
+ font-size: 24px;
}
-.admin p,.admin section {
- margin-top:10px;
- line-height:140%;
- padding-bottom:20px;
+.admin p, .admin section {
+ margin-top: 10px;
+ line-height: 140%;
+ padding-bottom: 20px;
}
+
.adminTable {
- width: 100%;
- clear: both;
- margin: 0;
+ width: 100%;
+ clear: both;
+ margin: 0;
}
+
.adminTable a {
- text-decoration: none;
- color:#2B5C97;
+ text-decoration: none;
+ color: #2B5C97;
}
+
.adminTable abbr {
- white-space: nowrap;
+ white-space: nowrap;
}
+
.adminTable td {
- font-size: 13px;
- vertical-align: top;
- padding: 7px 15px 9px 10px;
- vertical-align: top;
+ font-size: 13px;
+ vertical-align: top;
+ padding: 7px 15px 9px 10px;
+ vertical-align: top;
}
+
.adminTable td.action-links {
- text-align: right;
+ text-align: right;
}
+
.adminTable .check-column {
- text-align: right;
- width: 1.5em;
- padding: 0;
+ text-align: right;
+ width: 1.5em;
+ padding: 0;
}
+
.adminTable .num {
- text-align: center;
+ text-align: center;
}
+
.adminTable .name {
- font-weight: bold;
+ font-weight: bold;
}
+
.adminTable .ui-inline-help {
- margin-top:0;
- width:100%;
+ margin-top: 0;
+ width: 100%;
}
#logoSettings .ui-inline-help {
- width:550px;
+ width: 550px;
}
/* other styles */
.form-description {
- color:#666666;
- font-style:italic;
- margin-left:10px;
+ color: #666666;
+ font-style: italic;
+ margin-left: 10px;
}
#logoSettings, #smtpSettings {
- margin-left:50px;
+ margin-left: 50px;
}
/* to override .admin a */
.admin .sites_autocomplete a {
- color: #255792;
+ color: #255792;
}
/* trusted host styles */
#trustedHostSettings .adminTable {
- width:300px;
+ width: 300px;
}
+
#trustedHostSettings .adminTable td {
- vertical-align: middle;
- padding-bottom: 0;
+ vertical-align: middle;
+ padding-bottom: 0;
}
+
#trustedHostSettings .adminTable tr td:last-child {
- padding: 0 0 0 0;
+ padding: 0 0 0 0;
}
+
#trustedHostSettings input {
- width:238px;
+ width: 238px;
}
+
#trustedHostSettings .add-trusted-host-container {
- padding: 12px 24px;
+ padding: 12px 24px;
}
diff --git a/plugins/CoreHome/Controller.php b/plugins/CoreHome/Controller.php
index fa08fb98ec..8070d536a1 100644
--- a/plugins/CoreHome/Controller.php
+++ b/plugins/CoreHome/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_CoreHome
*/
@@ -15,215 +15,208 @@
*/
class Piwik_CoreHome_Controller extends Piwik_Controller
{
- function getDefaultAction()
- {
- return 'redirectToCoreHomeIndex';
- }
-
- function redirectToCoreHomeIndex()
- {
- $defaultReport = Piwik_UsersManager_API::getInstance()->getUserPreference(Piwik::getCurrentUserLogin(), Piwik_UsersManager_API::PREFERENCE_DEFAULT_REPORT);
- $module = 'CoreHome';
- $action = 'index';
-
- // User preference: default report to load is the All Websites dashboard
- if($defaultReport == 'MultiSites'
- && Piwik_PluginsManager::getInstance()->isPluginActivated('MultiSites'))
- {
- $module = 'MultiSites';
- }
- if($defaultReport == Piwik::getLoginPluginName())
- {
- $module = Piwik::getLoginPluginName();
- }
- $idSite = Piwik_Common::getRequestVar('idSite', false, 'int');
-
- parent::redirectToIndex($module, $action, !empty($idSite) ? $idSite : null );
- }
-
- public function showInContext()
- {
- $controllerName = Piwik_Common::getRequestVar('moduleToLoad');
- $actionName = Piwik_Common::getRequestVar('actionToLoad', 'index');
- if($actionName == 'showInContext') {
- throw new Exception("Preventing infinite recursion...");
- }
- $view = $this->getDefaultIndexView();
- $view->content = Piwik_FrontController::getInstance()->fetchDispatch( $controllerName, $actionName );
- echo $view->render();
- }
-
- protected function getDefaultIndexView()
- {
- $view = Piwik_View::factory('index');
- $this->setGeneralVariablesView($view);
- $view->menu = Piwik_GetMenu();
- $view->content = '';
- return $view;
- }
-
- protected function setDateTodayIfWebsiteCreatedToday()
- {
- $date = Piwik_Common::getRequestVar('date', false);
- if($date == 'today'
- || Piwik_Common::getRequestVar('period', false) == 'range')
- {
- return;
- }
- $websiteId = Piwik_Common::getRequestVar('idSite', false, 'int');
- if ($websiteId)
- {
- $website = new Piwik_Site($websiteId);
- $datetimeCreationDate = $this->site->getCreationDate()->getDatetime();
- $creationDateLocalTimezone = Piwik_Date::factory($datetimeCreationDate, $website->getTimezone())->toString('Y-m-d');
- $todayLocalTimezone = Piwik_Date::factory('now', $website->getTimezone())->toString('Y-m-d');
- if( $creationDateLocalTimezone == $todayLocalTimezone )
- {
- Piwik::redirectToModule( 'CoreHome', 'index',
- array( 'date' => 'today',
- 'idSite' => $websiteId,
- 'period' => Piwik_Common::getRequestVar('period'))
- );
- }
- }
- }
-
- public function index()
- {
- $this->setDateTodayIfWebsiteCreatedToday();
- $view = $this->getDefaultIndexView();
- echo $view->render();
- }
-
- /*
- * This method is called when the asset manager is configured in merged mode.
- * It returns the content of the css merged file.
- *
- * @see core/AssetManager.php
- */
- public function getCss ()
- {
- $cssMergedFile = Piwik_AssetManager::getMergedCssFileLocation();
- Piwik::serveStaticFile($cssMergedFile, "text/css");
- }
-
- /*
- * This method is called when the asset manager is configured in merged mode.
- * It returns the content of the js merged file.
- *
- * @see core/AssetManager.php
- */
- public function getJs ()
- {
- $jsMergedFile = Piwik_AssetManager::getMergedJsFileLocation();
- Piwik::serveStaticFile($jsMergedFile, "application/javascript; charset=UTF-8");
- }
-
-
- // --------------------------------------------------------
- // ROW EVOLUTION
- // The following methods render the popover that shows the
- // evolution of a singe or multiple rows in a data table
- // --------------------------------------------------------
-
- /**
- * This static cache is necessary because the signature cannot be modified
- * if the method renders a ViewDataTable. So we use it to pass information
- * to getRowEvolutionGraph()
- * @var Piwik_CoreHome_DataTableAction_Evolution
- */
- private static $rowEvolutionCache = null;
-
- /** Render the entire row evolution popover for a single row */
- public function getRowEvolutionPopover()
- {
- $rowEvolution = $this->makeRowEvolution($isMulti = false);
- self::$rowEvolutionCache = $rowEvolution;
- $view = Piwik_View::factory('popover_rowevolution');
- echo $rowEvolution->renderPopover($this, $view);
- }
-
- /** Render the entire row evolution popover for multiple rows */
- public function getMultiRowEvolutionPopover()
- {
- $rowEvolution = $this->makeRowEvolution($isMulti = true);
- self::$rowEvolutionCache = $rowEvolution;
- $view = Piwik_View::factory('popover_multirowevolution');
- echo $rowEvolution->renderPopover($this, $view);
- }
-
- /** Generic method to get an evolution graph or a sparkline for the row evolution popover */
- public function getRowEvolutionGraph($fetch = false)
- {
- $rowEvolution = self::$rowEvolutionCache;
- if ($rowEvolution === null)
- {
- $paramName = Piwik_CoreHome_DataTableRowAction_MultiRowEvolution::IS_MULTI_EVOLUTION_PARAM;
- $isMultiRowEvolution = Piwik_Common::getRequestVar($paramName, false, 'int');
-
- $rowEvolution = $this->makeRowEvolution($isMultiRowEvolution, $graphType = 'graphEvolution');
- $rowEvolution->useAvailableMetrics();
- self::$rowEvolutionCache = $rowEvolution;
- }
-
- $view = $rowEvolution->getRowEvolutionGraph();
- return $this->renderView($view, $fetch);
- }
-
- /** Utility function. Creates a RowEvolution instance. */
- private function makeRowEvolution( $isMultiRowEvolution, $graphType = null )
- {
- if ($isMultiRowEvolution)
- {
- return new Piwik_CoreHome_DataTableRowAction_MultiRowEvolution($this->idSite, $this->date, $graphType);
- }
- else
- {
- return new Piwik_CoreHome_DataTableRowAction_RowEvolution($this->idSite, $this->date, $graphType);
- }
- }
-
- /**
- * Forces a check for updates and re-renders the header message.
- *
- * This will check piwik.org at most once per 10s.
- */
- public function checkForUpdates()
- {
- Piwik::checkUserHasSomeAdminAccess();
- $this->checkTokenInUrl();
-
- // perform check (but only once every 10s)
- Piwik_UpdateCheck::check($force = false, Piwik_UpdateCheck::UI_CLICK_CHECK_INTERVAL);
-
- $view = Piwik_View::factory('header_message');
- $this->setGeneralVariablesView($view);
- echo $view->render();
- }
-
- /**
- * Renders and echo's the in-app donate form w/ slider.
- */
- public function getDonateForm()
- {
- $view = Piwik_View::factory('donate');
- if (Piwik_Common::getRequestVar('widget', false)
- && Piwik::isUserIsSuperUser())
- {
- $view->footerMessage = Piwik_Translate('CoreHome_OnlyForAdmin');
- }
- echo $view->render();
- }
-
- /**
- * Renders and echo's HTML that displays the Piwik promo video.
- */
- public function getPromoVideo()
- {
- $view = Piwik_View::factory('promo_video');
- $view->shareText = Piwik_Translate('CoreHome_SharePiwikShort');
- $view->shareTextLong = Piwik_Translate('CoreHome_SharePiwikLong');
- $view->promoVideoUrl = 'http://www.youtube.com/watch?v=OslfF_EH81g';
- echo $view->render();
- }
+ function getDefaultAction()
+ {
+ return 'redirectToCoreHomeIndex';
+ }
+
+ function redirectToCoreHomeIndex()
+ {
+ $defaultReport = Piwik_UsersManager_API::getInstance()->getUserPreference(Piwik::getCurrentUserLogin(), Piwik_UsersManager_API::PREFERENCE_DEFAULT_REPORT);
+ $module = 'CoreHome';
+ $action = 'index';
+
+ // User preference: default report to load is the All Websites dashboard
+ if ($defaultReport == 'MultiSites'
+ && Piwik_PluginsManager::getInstance()->isPluginActivated('MultiSites')
+ ) {
+ $module = 'MultiSites';
+ }
+ if ($defaultReport == Piwik::getLoginPluginName()) {
+ $module = Piwik::getLoginPluginName();
+ }
+ $idSite = Piwik_Common::getRequestVar('idSite', false, 'int');
+
+ parent::redirectToIndex($module, $action, !empty($idSite) ? $idSite : null);
+ }
+
+ public function showInContext()
+ {
+ $controllerName = Piwik_Common::getRequestVar('moduleToLoad');
+ $actionName = Piwik_Common::getRequestVar('actionToLoad', 'index');
+ if ($actionName == 'showInContext') {
+ throw new Exception("Preventing infinite recursion...");
+ }
+ $view = $this->getDefaultIndexView();
+ $view->content = Piwik_FrontController::getInstance()->fetchDispatch($controllerName, $actionName);
+ echo $view->render();
+ }
+
+ protected function getDefaultIndexView()
+ {
+ $view = Piwik_View::factory('index');
+ $this->setGeneralVariablesView($view);
+ $view->menu = Piwik_GetMenu();
+ $view->content = '';
+ return $view;
+ }
+
+ protected function setDateTodayIfWebsiteCreatedToday()
+ {
+ $date = Piwik_Common::getRequestVar('date', false);
+ if ($date == 'today'
+ || Piwik_Common::getRequestVar('period', false) == 'range'
+ ) {
+ return;
+ }
+ $websiteId = Piwik_Common::getRequestVar('idSite', false, 'int');
+ if ($websiteId) {
+ $website = new Piwik_Site($websiteId);
+ $datetimeCreationDate = $this->site->getCreationDate()->getDatetime();
+ $creationDateLocalTimezone = Piwik_Date::factory($datetimeCreationDate, $website->getTimezone())->toString('Y-m-d');
+ $todayLocalTimezone = Piwik_Date::factory('now', $website->getTimezone())->toString('Y-m-d');
+ if ($creationDateLocalTimezone == $todayLocalTimezone) {
+ Piwik::redirectToModule('CoreHome', 'index',
+ array('date' => 'today',
+ 'idSite' => $websiteId,
+ 'period' => Piwik_Common::getRequestVar('period'))
+ );
+ }
+ }
+ }
+
+ public function index()
+ {
+ $this->setDateTodayIfWebsiteCreatedToday();
+ $view = $this->getDefaultIndexView();
+ echo $view->render();
+ }
+
+ /*
+ * This method is called when the asset manager is configured in merged mode.
+ * It returns the content of the css merged file.
+ *
+ * @see core/AssetManager.php
+ */
+ public function getCss()
+ {
+ $cssMergedFile = Piwik_AssetManager::getMergedCssFileLocation();
+ Piwik::serveStaticFile($cssMergedFile, "text/css");
+ }
+
+ /*
+ * This method is called when the asset manager is configured in merged mode.
+ * It returns the content of the js merged file.
+ *
+ * @see core/AssetManager.php
+ */
+ public function getJs()
+ {
+ $jsMergedFile = Piwik_AssetManager::getMergedJsFileLocation();
+ Piwik::serveStaticFile($jsMergedFile, "application/javascript; charset=UTF-8");
+ }
+
+
+ // --------------------------------------------------------
+ // ROW EVOLUTION
+ // The following methods render the popover that shows the
+ // evolution of a singe or multiple rows in a data table
+ // --------------------------------------------------------
+
+ /**
+ * This static cache is necessary because the signature cannot be modified
+ * if the method renders a ViewDataTable. So we use it to pass information
+ * to getRowEvolutionGraph()
+ * @var Piwik_CoreHome_DataTableAction_Evolution
+ */
+ private static $rowEvolutionCache = null;
+
+ /** Render the entire row evolution popover for a single row */
+ public function getRowEvolutionPopover()
+ {
+ $rowEvolution = $this->makeRowEvolution($isMulti = false);
+ self::$rowEvolutionCache = $rowEvolution;
+ $view = Piwik_View::factory('popover_rowevolution');
+ echo $rowEvolution->renderPopover($this, $view);
+ }
+
+ /** Render the entire row evolution popover for multiple rows */
+ public function getMultiRowEvolutionPopover()
+ {
+ $rowEvolution = $this->makeRowEvolution($isMulti = true);
+ self::$rowEvolutionCache = $rowEvolution;
+ $view = Piwik_View::factory('popover_multirowevolution');
+ echo $rowEvolution->renderPopover($this, $view);
+ }
+
+ /** Generic method to get an evolution graph or a sparkline for the row evolution popover */
+ public function getRowEvolutionGraph($fetch = false)
+ {
+ $rowEvolution = self::$rowEvolutionCache;
+ if ($rowEvolution === null) {
+ $paramName = Piwik_CoreHome_DataTableRowAction_MultiRowEvolution::IS_MULTI_EVOLUTION_PARAM;
+ $isMultiRowEvolution = Piwik_Common::getRequestVar($paramName, false, 'int');
+
+ $rowEvolution = $this->makeRowEvolution($isMultiRowEvolution, $graphType = 'graphEvolution');
+ $rowEvolution->useAvailableMetrics();
+ self::$rowEvolutionCache = $rowEvolution;
+ }
+
+ $view = $rowEvolution->getRowEvolutionGraph();
+ return $this->renderView($view, $fetch);
+ }
+
+ /** Utility function. Creates a RowEvolution instance. */
+ private function makeRowEvolution($isMultiRowEvolution, $graphType = null)
+ {
+ if ($isMultiRowEvolution) {
+ return new Piwik_CoreHome_DataTableRowAction_MultiRowEvolution($this->idSite, $this->date, $graphType);
+ } else {
+ return new Piwik_CoreHome_DataTableRowAction_RowEvolution($this->idSite, $this->date, $graphType);
+ }
+ }
+
+ /**
+ * Forces a check for updates and re-renders the header message.
+ *
+ * This will check piwik.org at most once per 10s.
+ */
+ public function checkForUpdates()
+ {
+ Piwik::checkUserHasSomeAdminAccess();
+ $this->checkTokenInUrl();
+
+ // perform check (but only once every 10s)
+ Piwik_UpdateCheck::check($force = false, Piwik_UpdateCheck::UI_CLICK_CHECK_INTERVAL);
+
+ $view = Piwik_View::factory('header_message');
+ $this->setGeneralVariablesView($view);
+ echo $view->render();
+ }
+
+ /**
+ * Renders and echo's the in-app donate form w/ slider.
+ */
+ public function getDonateForm()
+ {
+ $view = Piwik_View::factory('donate');
+ if (Piwik_Common::getRequestVar('widget', false)
+ && Piwik::isUserIsSuperUser()
+ ) {
+ $view->footerMessage = Piwik_Translate('CoreHome_OnlyForAdmin');
+ }
+ echo $view->render();
+ }
+
+ /**
+ * Renders and echo's HTML that displays the Piwik promo video.
+ */
+ public function getPromoVideo()
+ {
+ $view = Piwik_View::factory('promo_video');
+ $view->shareText = Piwik_Translate('CoreHome_SharePiwikShort');
+ $view->shareTextLong = Piwik_Translate('CoreHome_SharePiwikLong');
+ $view->promoVideoUrl = 'http://www.youtube.com/watch?v=OslfF_EH81g';
+ echo $view->render();
+ }
}
diff --git a/plugins/CoreHome/CoreHome.php b/plugins/CoreHome/CoreHome.php
index 1d965a06bf..fda7884a6c 100644
--- a/plugins/CoreHome/CoreHome.php
+++ b/plugins/CoreHome/CoreHome.php
@@ -15,87 +15,87 @@
*/
class Piwik_CoreHome extends Piwik_Plugin
{
- public function getInformation()
- {
- return array(
- 'description' => Piwik_Translate('CoreHome_PluginDescription'),
- 'author' => 'Piwik',
- 'author_homepage' => 'http://piwik.org/',
- 'version' => Piwik_Version::VERSION,
- );
- }
-
- function getListHooksRegistered()
- {
- return array(
- 'AssetManager.getCssFiles' => 'getCssFiles',
- 'AssetManager.getJsFiles' => 'getJsFiles',
- 'WidgetsList.add' => 'addWidgets',
- );
- }
-
- /**
- * Adds the donate form widget.
- *
- * @param Piwik_Event_Notification $notification notification object
- */
- public function addWidgets()
- {
- Piwik_AddWidget('Example Widgets', 'CoreHome_SupportPiwik', 'CoreHome', 'getDonateForm');
- Piwik_AddWidget('Example Widgets', 'Installation_Welcome', 'CoreHome', 'getPromoVideo');
- }
+ public function getInformation()
+ {
+ return array(
+ 'description' => Piwik_Translate('CoreHome_PluginDescription'),
+ 'author' => 'Piwik',
+ 'author_homepage' => 'http://piwik.org/',
+ 'version' => Piwik_Version::VERSION,
+ );
+ }
- /**
- * @param Piwik_Event_Notification $notification notification object
- */
- function getCssFiles( $notification )
- {
- $cssFiles = &$notification->getNotificationObject();
-
- $cssFiles[] = "libs/jquery/themes/base/jquery-ui.css";
- $cssFiles[] = "themes/default/common.css";
- $cssFiles[] = "plugins/CoreHome/templates/styles.css";
- $cssFiles[] = "plugins/CoreHome/templates/menu.css";
- $cssFiles[] = "plugins/CoreHome/templates/datatable.css";
- $cssFiles[] = "plugins/CoreHome/templates/cloud.css";
- $cssFiles[] = "plugins/CoreHome/templates/jquery.ui.autocomplete.css";
- $cssFiles[] = "plugins/CoreHome/templates/jqplot.css";
- $cssFiles[] = "plugins/CoreHome/templates/donate.css";
- }
+ function getListHooksRegistered()
+ {
+ return array(
+ 'AssetManager.getCssFiles' => 'getCssFiles',
+ 'AssetManager.getJsFiles' => 'getJsFiles',
+ 'WidgetsList.add' => 'addWidgets',
+ );
+ }
+
+ /**
+ * Adds the donate form widget.
+ *
+ * @param Piwik_Event_Notification $notification notification object
+ */
+ public function addWidgets()
+ {
+ Piwik_AddWidget('Example Widgets', 'CoreHome_SupportPiwik', 'CoreHome', 'getDonateForm');
+ Piwik_AddWidget('Example Widgets', 'Installation_Welcome', 'CoreHome', 'getPromoVideo');
+ }
+
+ /**
+ * @param Piwik_Event_Notification $notification notification object
+ */
+ function getCssFiles($notification)
+ {
+ $cssFiles = & $notification->getNotificationObject();
+
+ $cssFiles[] = "libs/jquery/themes/base/jquery-ui.css";
+ $cssFiles[] = "themes/default/common.css";
+ $cssFiles[] = "plugins/CoreHome/templates/styles.css";
+ $cssFiles[] = "plugins/CoreHome/templates/menu.css";
+ $cssFiles[] = "plugins/CoreHome/templates/datatable.css";
+ $cssFiles[] = "plugins/CoreHome/templates/cloud.css";
+ $cssFiles[] = "plugins/CoreHome/templates/jquery.ui.autocomplete.css";
+ $cssFiles[] = "plugins/CoreHome/templates/jqplot.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/jquery/jquery.tooltip.js";
+ $jsFiles[] = "libs/jquery/jquery.truncate.js";
+ $jsFiles[] = "libs/jquery/jquery.scrollTo.js";
+ $jsFiles[] = "libs/jquery/jquery.history.js";
+ $jsFiles[] = "libs/javascript/sprintf.js";
+ $jsFiles[] = "themes/default/common.js";
+ $jsFiles[] = "themes/default/ajaxHelper.js";
+ $jsFiles[] = "plugins/CoreHome/templates/tooltip.js";
+ $jsFiles[] = "plugins/CoreHome/templates/datatable.js";
+ $jsFiles[] = "plugins/CoreHome/templates/datatable_rowactions.js";
+ $jsFiles[] = "plugins/CoreHome/templates/popover.js";
+ $jsFiles[] = "plugins/CoreHome/templates/broadcast.js";
+ $jsFiles[] = "plugins/CoreHome/templates/menu.js";
+ $jsFiles[] = "plugins/CoreHome/templates/menu_init.js";
+ $jsFiles[] = "plugins/CoreHome/templates/calendar.js";
+ $jsFiles[] = "plugins/CoreHome/templates/date.js";
+ $jsFiles[] = "plugins/CoreHome/templates/autocomplete.js";
+ $jsFiles[] = "plugins/CoreHome/templates/sparkline.js";
+ $jsFiles[] = "plugins/CoreHome/templates/misc.js";
+ $jsFiles[] = "plugins/CoreHome/templates/datatable_manager.js";
+ $jsFiles[] = "plugins/CoreHome/templates/donate.js";
+
+ $jsFiles[] = "plugins/CoreHome/templates/jqplot.js";
+ $jsFiles[] = "libs/jqplot/jqplot-custom.min.js";
+ }
- /**
- * @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/jquery/jquery.tooltip.js";
- $jsFiles[] = "libs/jquery/jquery.truncate.js";
- $jsFiles[] = "libs/jquery/jquery.scrollTo.js";
- $jsFiles[] = "libs/jquery/jquery.history.js";
- $jsFiles[] = "libs/javascript/sprintf.js";
- $jsFiles[] = "themes/default/common.js";
- $jsFiles[] = "themes/default/ajaxHelper.js";
- $jsFiles[] = "plugins/CoreHome/templates/tooltip.js";
- $jsFiles[] = "plugins/CoreHome/templates/datatable.js";
- $jsFiles[] = "plugins/CoreHome/templates/datatable_rowactions.js";
- $jsFiles[] = "plugins/CoreHome/templates/popover.js";
- $jsFiles[] = "plugins/CoreHome/templates/broadcast.js";
- $jsFiles[] = "plugins/CoreHome/templates/menu.js";
- $jsFiles[] = "plugins/CoreHome/templates/menu_init.js";
- $jsFiles[] = "plugins/CoreHome/templates/calendar.js";
- $jsFiles[] = "plugins/CoreHome/templates/date.js";
- $jsFiles[] = "plugins/CoreHome/templates/autocomplete.js";
- $jsFiles[] = "plugins/CoreHome/templates/sparkline.js";
- $jsFiles[] = "plugins/CoreHome/templates/misc.js";
- $jsFiles[] = "plugins/CoreHome/templates/datatable_manager.js";
- $jsFiles[] = "plugins/CoreHome/templates/donate.js";
-
- $jsFiles[] = "plugins/CoreHome/templates/jqplot.js";
- $jsFiles[] = "libs/jqplot/jqplot-custom.min.js";
- }
-
}
diff --git a/plugins/CoreHome/DataTableRowAction/MultiRowEvolution.php b/plugins/CoreHome/DataTableRowAction/MultiRowEvolution.php
index 659030a67e..4c4a964c2e 100644
--- a/plugins/CoreHome/DataTableRowAction/MultiRowEvolution.php
+++ b/plugins/CoreHome/DataTableRowAction/MultiRowEvolution.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_CoreHome
*/
@@ -15,71 +15,71 @@
* @package Piwik_CoreHome
*/
class Piwik_CoreHome_DataTableRowAction_MultiRowEvolution
- extends Piwik_CoreHome_DataTableRowAction_RowEvolution
+ extends Piwik_CoreHome_DataTableRowAction_RowEvolution
{
- const IS_MULTI_EVOLUTION_PARAM = 'is_multi_evolution';
-
- /** The requested metric */
- protected $metric;
+ const IS_MULTI_EVOLUTION_PARAM = 'is_multi_evolution';
- /** Show all metrics in the evolution graph when the popover opens */
- protected $initiallyShowAllMetrics = true;
-
- /** The metrics available in the metrics select */
- protected $metricsForSelect;
-
- /**
- * The constructor
- * @param int
- * @param Piwik_Date ($this->date from controller)
- */
- public function __construct($idSite, $date)
- {
- $this->metric = Piwik_Common::getRequestVar('column', '', 'string');
- parent::__construct($idSite, $date);
- }
-
- protected function loadEvolutionReport($column = false)
- {
- // set the "column" parameter for the API.getRowEvolution call
- parent::loadEvolutionReport($this->metric);
- }
-
- protected function extractEvolutionReport($report)
- {
- $this->metric = $report['column'];
- $this->dataTable = $report['reportData'];
- $this->availableMetrics = $report['metadata']['metrics'];
- $this->metricsForSelect = $report['metadata']['columns'];
- $this->dimension = $report['metadata']['dimension'];
- }
-
- /**
- * Render the popover
- * @param Piwik_CoreHome_Controller
- * @param Piwik_View (the popover_rowevolution template)
- */
- public function renderPopover($controller, $view)
- {
- // add data for metric select box
- $view->availableMetrics = $this->metricsForSelect;
- $view->selectedMetric = $this->metric;
-
- $view->availableRecordsText = $this->dimension.': '
- .Piwik_Translate('RowEvolution_ComparingRecords', array(count($this->availableMetrics)));
-
- return parent::renderPopover($controller, $view);
- }
-
- /**
- * Generic method to get an evolution graph or a sparkline for the row evolution popover.
- * Do as much as possible from outside the controller.
- * @return Piwik_ViewDataTable
- */
- public function getRowEvolutionGraph()
- {
- $view = parent::getRowEvolutionGraph();
- $view->setCustomParameter(self::IS_MULTI_EVOLUTION_PARAM, true);
- return $view;
- }
+ /** The requested metric */
+ protected $metric;
+
+ /** Show all metrics in the evolution graph when the popover opens */
+ protected $initiallyShowAllMetrics = true;
+
+ /** The metrics available in the metrics select */
+ protected $metricsForSelect;
+
+ /**
+ * The constructor
+ * @param int
+ * @param Piwik_Date ($this->date from controller)
+ */
+ public function __construct($idSite, $date)
+ {
+ $this->metric = Piwik_Common::getRequestVar('column', '', 'string');
+ parent::__construct($idSite, $date);
+ }
+
+ protected function loadEvolutionReport($column = false)
+ {
+ // set the "column" parameter for the API.getRowEvolution call
+ parent::loadEvolutionReport($this->metric);
+ }
+
+ protected function extractEvolutionReport($report)
+ {
+ $this->metric = $report['column'];
+ $this->dataTable = $report['reportData'];
+ $this->availableMetrics = $report['metadata']['metrics'];
+ $this->metricsForSelect = $report['metadata']['columns'];
+ $this->dimension = $report['metadata']['dimension'];
+ }
+
+ /**
+ * Render the popover
+ * @param Piwik_CoreHome_Controller
+ * @param Piwik_View (the popover_rowevolution template)
+ */
+ public function renderPopover($controller, $view)
+ {
+ // add data for metric select box
+ $view->availableMetrics = $this->metricsForSelect;
+ $view->selectedMetric = $this->metric;
+
+ $view->availableRecordsText = $this->dimension . ': '
+ . Piwik_Translate('RowEvolution_ComparingRecords', array(count($this->availableMetrics)));
+
+ return parent::renderPopover($controller, $view);
+ }
+
+ /**
+ * Generic method to get an evolution graph or a sparkline for the row evolution popover.
+ * Do as much as possible from outside the controller.
+ * @return Piwik_ViewDataTable
+ */
+ public function getRowEvolutionGraph()
+ {
+ $view = parent::getRowEvolutionGraph();
+ $view->setCustomParameter(self::IS_MULTI_EVOLUTION_PARAM, true);
+ return $view;
+ }
}
diff --git a/plugins/CoreHome/DataTableRowAction/RowEvolution.php b/plugins/CoreHome/DataTableRowAction/RowEvolution.php
index bb56d3e52e..8561d8734a 100644
--- a/plugins/CoreHome/DataTableRowAction/RowEvolution.php
+++ b/plugins/CoreHome/DataTableRowAction/RowEvolution.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_CoreHome
*/
@@ -16,283 +16,269 @@
*/
class Piwik_CoreHome_DataTableRowAction_RowEvolution
{
-
- /** The current site id */
- protected $idSite;
-
- /** The api method to get the data. Format: Plugin.apiAction */
- protected $apiMethod;
-
- /** The label of the requested row */
- protected $label;
-
- /** The requested period */
- protected $period;
-
- /** The requested date */
- protected $date;
-
- /** The request segment */
- protected $segment;
-
- /** The metrics that are available for the requested report and period */
- protected $availableMetrics;
-
- /** The name of the dimension of the current report */
- protected $dimension;
-
- /**
- * The data
- * @var Piwik_DataTable_Array
- */
- protected $dataTable;
-
- /** The label of the current record */
- protected $rowLabel;
-
- /** The icon of the current record */
- protected $rowIcon;
-
- /** The type of graph that has been requested last */
- protected $graphType;
-
- /** The metrics for the graph that has been requested last */
- protected $graphMetrics;
-
- /** Whether or not to show all metrics in the evolution graph when to popover opens */
- protected $initiallyShowAllMetrics = false;
-
- /**
- * The constructor
- * Initialize some local variables from the request
- * @param int $idSite
- * @param Piwik_Date $date ($this->date from controller)
- * @throws Exception
- */
- public function __construct($idSite, $date, $graphType = null)
- {
- $this->apiMethod = Piwik_Common::getRequestVar('apiMethod', '', 'string');
- if (empty($this->apiMethod)) throw new Exception("Parameter apiMethod not set.");
-
- $this->label = Piwik_Common::getRequestVar('label', '', 'string');
- $this->label = Piwik_Common::unsanitizeInputValue($this->label);
- if ($this->label === '') throw new Exception("Parameter label not set.");
-
- $this->period = Piwik_Common::getRequestVar('period', '', 'string');
- if (empty($this->period)) throw new Exception("Parameter period not set.");
-
- $this->idSite = $idSite;
- $this->graphType = $graphType;
-
- if ($this->period != 'range')
- {
- // handle day, week, month and year: display last X periods
- $end = $date->toString();
- list($this->date, $lastN) =
- Piwik_ViewDataTable_GenerateGraphHTML_ChartEvolution::getDateRangeAndLastN($this->period, $end);
- }
- $this->segment = Piwik_Common::getRequestVar('segment', '', 'string');
-
- $this->loadEvolutionReport();
- }
-
- /**
- * Render the popover
- * @param Piwik_CoreHome_Controller
- * @param Piwik_View (the popover_rowevolution template)
- */
- public function renderPopover($controller, $view)
- {
- // render main evolution graph
- $this->graphType = 'graphEvolution';
- $this->graphMetrics = $this->availableMetrics;
- $view->graph = $controller->getRowEvolutionGraph(true);
-
- // render metrics overview
- $view->metrics = $this->getMetricsToggles($controller);
-
- // available metrics text
- $metricsText = Piwik_Translate('RowEvolution_AvailableMetrics');
- $popoverTitle = '';
- if ($this->rowLabel)
- {
- $icon = $this->rowIcon ? '<img src="'.$this->rowIcon.'" alt="">' : '';
- $rowLabel = str_replace('/', '<wbr>/', str_replace('&', '<wbr>&', $this->rowLabel));
- $metricsText = sprintf(Piwik_Translate('RowEvolution_MetricsFor'), $this->dimension.': '.$icon.' '.$rowLabel);
- $popoverTitle = $icon.' '.$rowLabel;
- }
-
- $view->availableMetricsText = $metricsText;
- $view->popoverTitle = $popoverTitle;
-
- return $view->render();
- }
-
- protected function loadEvolutionReport($column = false)
- {
- list($apiModule, $apiAction) = explode('.', $this->apiMethod);
-
- $parameters = array(
- 'method' => 'API.getRowEvolution',
- 'label' => urlencode($this->label),
- 'apiModule' => $apiModule,
- 'apiAction' => $apiAction,
- 'idSite' => $this->idSite,
- 'period' => $this->period,
- 'date' => $this->date,
- 'format' => 'original',
- 'serialize' => '0'
- );
- if(!empty($this->segment))
- {
- $parameters['segment'] = $this->segment;
- }
-
- if ($column !== false)
- {
- $parameters['column'] = $column;
- }
-
- $url = Piwik_Url::getQueryStringFromParameters($parameters);
-
- $request = new Piwik_API_Request($url);
- $report = $request->process();
-
- $this->extractEvolutionReport($report);
- }
-
- protected function extractEvolutionReport($report)
- {
- $this->dataTable = $report['reportData'];
- $this->rowLabel = Piwik_Common::sanitizeInputValue($report['label']);
- $this->rowIcon = !empty($report['logo']) ? $report['logo'] : false;
- $this->availableMetrics = $report['metadata']['metrics'];
- $this->dimension = $report['metadata']['dimension'];
- }
-
- /**
- * Generic method to get an evolution graph or a sparkline for the row evolution popover.
- * Do as much as possible from outside the controller.
- * @return Piwik_ViewDataTable
- */
- public function getRowEvolutionGraph()
- {
- // set up the view data table
- $view = Piwik_ViewDataTable::factory($this->graphType);
- $view->setDataTable($this->dataTable);
- $view->init('CoreHome', 'getRowEvolutionGraph', $this->apiMethod);
-
- if(!empty($this->graphMetrics)) // In row Evolution popover, this is empty
- {
- $view->setColumnsToDisplay(array_keys($this->graphMetrics));
- }
- $view->hideAllViewsIcons();
-
- foreach ($this->availableMetrics as $metric => $metadata)
- {
- $view->setColumnTranslation($metric, $metadata['name']);
- }
-
- if (method_exists($view, 'addRowEvolutionSeriesToggle'))
- {
- $view->addRowEvolutionSeriesToggle($this->initiallyShowAllMetrics);
- }
-
- return $view;
- }
-
- /**
- * Prepare metrics toggles with spark lines
- * @param $controller
- * @return array
- */
- protected function getMetricsToggles($controller)
- {
- $chart = new Piwik_Visualization_Chart_Evolution;
- $colors = $chart->getSeriesColors();
-
- $i = 0;
- $metrics = array();
- foreach ($this->availableMetrics as $metric => $metricData)
- {
- $max = isset($metricData['max']) ? $metricData['max'] : 0;
- $min = isset($metricData['min']) ? $metricData['min'] : 0;
- $change = isset($metricData['change']) ? $metricData['change'] : false;
-
- $unit = Piwik_API_API::getUnit($metric, $this->idSite);
- $min .= $unit;
- $max .= $unit;
-
- $details = Piwik_Translate('RowEvolution_MetricBetweenText', array($min, $max));
-
- if ($change !== false)
- {
- $lowerIsBetter = Piwik_API_API::isLowerValueBetter($metric);
- if (substr($change, 0, 1) == '+')
- {
- $changeClass = $lowerIsBetter ? 'bad' : 'good';
- $changeImage = $lowerIsBetter ? 'arrow_up_red' : 'arrow_up';
- }
- else if (substr($change, 0, 1) == '-')
- {
- $changeClass = $lowerIsBetter ? 'good' : 'bad';
- $changeImage = $lowerIsBetter ? 'arrow_down_green' : 'arrow_down';
- }
- else
- {
- $changeClass = 'neutral';
- $changeImage = false;
- }
-
- $change = '<span class="'.$changeClass.'">'
- .($changeImage ? '<img src="plugins/MultiSites/images/'.$changeImage.'.png" /> ' : '')
- .$change.'</span>';
-
- $details .= ', '.Piwik_Translate('RowEvolution_MetricChangeText', $change);
- }
-
- $color = $colors[ $i % count($colors) ];
- $newMetric = array(
- 'label' => $metricData['name'],
- 'color' => $color,
- 'details' => $details,
- 'sparkline' => $this->getSparkline($metric, $controller),
- );
- // Multi Rows, each metric can be for a particular row and display an icon
- if(!empty($metricData['logo']))
- {
- $newMetric['logo'] = $metricData['logo'];
- }
- $metrics[] = $newMetric;
- $i++;
- }
-
- return $metrics;
- }
-
- /** Get the img tag for a sparkline showing a single metric */
- protected function getSparkline($metric, $controller)
- {
- $this->graphType = 'sparkline';
- $this->graphMetrics = array($metric => $metric);
-
- // sparkline is always echoed, so we need to buffer the output
- ob_start();
- $controller->getRowEvolutionGraph();
- $spark = ob_get_contents();
- ob_end_clean();
-
- // undo header change by sparkline renderer
- header('Content-type: text/html');
-
- // base64 encode the image and put it in an img tag
- $spark = base64_encode($spark);
- return '<img src="data:image/png;base64,'.$spark.'" />';
- }
-
- /** Use the available metrics for the metrics of the last requested graph. */
- public function useAvailableMetrics()
- {
- $this->graphMetrics = $this->availableMetrics;
- }
+
+ /** The current site id */
+ protected $idSite;
+
+ /** The api method to get the data. Format: Plugin.apiAction */
+ protected $apiMethod;
+
+ /** The label of the requested row */
+ protected $label;
+
+ /** The requested period */
+ protected $period;
+
+ /** The requested date */
+ protected $date;
+
+ /** The request segment */
+ protected $segment;
+
+ /** The metrics that are available for the requested report and period */
+ protected $availableMetrics;
+
+ /** The name of the dimension of the current report */
+ protected $dimension;
+
+ /**
+ * The data
+ * @var Piwik_DataTable_Array
+ */
+ protected $dataTable;
+
+ /** The label of the current record */
+ protected $rowLabel;
+
+ /** The icon of the current record */
+ protected $rowIcon;
+
+ /** The type of graph that has been requested last */
+ protected $graphType;
+
+ /** The metrics for the graph that has been requested last */
+ protected $graphMetrics;
+
+ /** Whether or not to show all metrics in the evolution graph when to popover opens */
+ protected $initiallyShowAllMetrics = false;
+
+ /**
+ * The constructor
+ * Initialize some local variables from the request
+ * @param int $idSite
+ * @param Piwik_Date $date ($this->date from controller)
+ * @throws Exception
+ */
+ public function __construct($idSite, $date, $graphType = null)
+ {
+ $this->apiMethod = Piwik_Common::getRequestVar('apiMethod', '', 'string');
+ if (empty($this->apiMethod)) throw new Exception("Parameter apiMethod not set.");
+
+ $this->label = Piwik_Common::getRequestVar('label', '', 'string');
+ $this->label = Piwik_Common::unsanitizeInputValue($this->label);
+ if ($this->label === '') throw new Exception("Parameter label not set.");
+
+ $this->period = Piwik_Common::getRequestVar('period', '', 'string');
+ if (empty($this->period)) throw new Exception("Parameter period not set.");
+
+ $this->idSite = $idSite;
+ $this->graphType = $graphType;
+
+ if ($this->period != 'range') {
+ // handle day, week, month and year: display last X periods
+ $end = $date->toString();
+ list($this->date, $lastN) =
+ Piwik_ViewDataTable_GenerateGraphHTML_ChartEvolution::getDateRangeAndLastN($this->period, $end);
+ }
+ $this->segment = Piwik_Common::getRequestVar('segment', '', 'string');
+
+ $this->loadEvolutionReport();
+ }
+
+ /**
+ * Render the popover
+ * @param Piwik_CoreHome_Controller
+ * @param Piwik_View (the popover_rowevolution template)
+ */
+ public function renderPopover($controller, $view)
+ {
+ // render main evolution graph
+ $this->graphType = 'graphEvolution';
+ $this->graphMetrics = $this->availableMetrics;
+ $view->graph = $controller->getRowEvolutionGraph(true);
+
+ // render metrics overview
+ $view->metrics = $this->getMetricsToggles($controller);
+
+ // available metrics text
+ $metricsText = Piwik_Translate('RowEvolution_AvailableMetrics');
+ $popoverTitle = '';
+ if ($this->rowLabel) {
+ $icon = $this->rowIcon ? '<img src="' . $this->rowIcon . '" alt="">' : '';
+ $rowLabel = str_replace('/', '<wbr>/', str_replace('&', '<wbr>&', $this->rowLabel));
+ $metricsText = sprintf(Piwik_Translate('RowEvolution_MetricsFor'), $this->dimension . ': ' . $icon . ' ' . $rowLabel);
+ $popoverTitle = $icon . ' ' . $rowLabel;
+ }
+
+ $view->availableMetricsText = $metricsText;
+ $view->popoverTitle = $popoverTitle;
+
+ return $view->render();
+ }
+
+ protected function loadEvolutionReport($column = false)
+ {
+ list($apiModule, $apiAction) = explode('.', $this->apiMethod);
+
+ $parameters = array(
+ 'method' => 'API.getRowEvolution',
+ 'label' => urlencode($this->label),
+ 'apiModule' => $apiModule,
+ 'apiAction' => $apiAction,
+ 'idSite' => $this->idSite,
+ 'period' => $this->period,
+ 'date' => $this->date,
+ 'format' => 'original',
+ 'serialize' => '0'
+ );
+ if (!empty($this->segment)) {
+ $parameters['segment'] = $this->segment;
+ }
+
+ if ($column !== false) {
+ $parameters['column'] = $column;
+ }
+
+ $url = Piwik_Url::getQueryStringFromParameters($parameters);
+
+ $request = new Piwik_API_Request($url);
+ $report = $request->process();
+
+ $this->extractEvolutionReport($report);
+ }
+
+ protected function extractEvolutionReport($report)
+ {
+ $this->dataTable = $report['reportData'];
+ $this->rowLabel = Piwik_Common::sanitizeInputValue($report['label']);
+ $this->rowIcon = !empty($report['logo']) ? $report['logo'] : false;
+ $this->availableMetrics = $report['metadata']['metrics'];
+ $this->dimension = $report['metadata']['dimension'];
+ }
+
+ /**
+ * Generic method to get an evolution graph or a sparkline for the row evolution popover.
+ * Do as much as possible from outside the controller.
+ * @return Piwik_ViewDataTable
+ */
+ public function getRowEvolutionGraph()
+ {
+ // set up the view data table
+ $view = Piwik_ViewDataTable::factory($this->graphType);
+ $view->setDataTable($this->dataTable);
+ $view->init('CoreHome', 'getRowEvolutionGraph', $this->apiMethod);
+
+ if (!empty($this->graphMetrics)) // In row Evolution popover, this is empty
+ {
+ $view->setColumnsToDisplay(array_keys($this->graphMetrics));
+ }
+ $view->hideAllViewsIcons();
+
+ foreach ($this->availableMetrics as $metric => $metadata) {
+ $view->setColumnTranslation($metric, $metadata['name']);
+ }
+
+ if (method_exists($view, 'addRowEvolutionSeriesToggle')) {
+ $view->addRowEvolutionSeriesToggle($this->initiallyShowAllMetrics);
+ }
+
+ return $view;
+ }
+
+ /**
+ * Prepare metrics toggles with spark lines
+ * @param $controller
+ * @return array
+ */
+ protected function getMetricsToggles($controller)
+ {
+ $chart = new Piwik_Visualization_Chart_Evolution;
+ $colors = $chart->getSeriesColors();
+
+ $i = 0;
+ $metrics = array();
+ foreach ($this->availableMetrics as $metric => $metricData) {
+ $max = isset($metricData['max']) ? $metricData['max'] : 0;
+ $min = isset($metricData['min']) ? $metricData['min'] : 0;
+ $change = isset($metricData['change']) ? $metricData['change'] : false;
+
+ $unit = Piwik_API_API::getUnit($metric, $this->idSite);
+ $min .= $unit;
+ $max .= $unit;
+
+ $details = Piwik_Translate('RowEvolution_MetricBetweenText', array($min, $max));
+
+ if ($change !== false) {
+ $lowerIsBetter = Piwik_API_API::isLowerValueBetter($metric);
+ if (substr($change, 0, 1) == '+') {
+ $changeClass = $lowerIsBetter ? 'bad' : 'good';
+ $changeImage = $lowerIsBetter ? 'arrow_up_red' : 'arrow_up';
+ } else if (substr($change, 0, 1) == '-') {
+ $changeClass = $lowerIsBetter ? 'good' : 'bad';
+ $changeImage = $lowerIsBetter ? 'arrow_down_green' : 'arrow_down';
+ } else {
+ $changeClass = 'neutral';
+ $changeImage = false;
+ }
+
+ $change = '<span class="' . $changeClass . '">'
+ . ($changeImage ? '<img src="plugins/MultiSites/images/' . $changeImage . '.png" /> ' : '')
+ . $change . '</span>';
+
+ $details .= ', ' . Piwik_Translate('RowEvolution_MetricChangeText', $change);
+ }
+
+ $color = $colors[$i % count($colors)];
+ $newMetric = array(
+ 'label' => $metricData['name'],
+ 'color' => $color,
+ 'details' => $details,
+ 'sparkline' => $this->getSparkline($metric, $controller),
+ );
+ // Multi Rows, each metric can be for a particular row and display an icon
+ if (!empty($metricData['logo'])) {
+ $newMetric['logo'] = $metricData['logo'];
+ }
+ $metrics[] = $newMetric;
+ $i++;
+ }
+
+ return $metrics;
+ }
+
+ /** Get the img tag for a sparkline showing a single metric */
+ protected function getSparkline($metric, $controller)
+ {
+ $this->graphType = 'sparkline';
+ $this->graphMetrics = array($metric => $metric);
+
+ // sparkline is always echoed, so we need to buffer the output
+ ob_start();
+ $controller->getRowEvolutionGraph();
+ $spark = ob_get_contents();
+ ob_end_clean();
+
+ // undo header change by sparkline renderer
+ header('Content-type: text/html');
+
+ // base64 encode the image and put it in an img tag
+ $spark = base64_encode($spark);
+ return '<img src="data:image/png;base64,' . $spark . '" />';
+ }
+
+ /** Use the available metrics for the metrics of the last requested graph. */
+ public function useAvailableMetrics()
+ {
+ $this->graphMetrics = $this->availableMetrics;
+ }
}
diff --git a/plugins/CoreHome/templates/autocomplete.js b/plugins/CoreHome/templates/autocomplete.js
index b9ca092420..37af24e72c 100644
--- a/plugins/CoreHome/templates/autocomplete.js
+++ b/plugins/CoreHome/templates/autocomplete.js
@@ -5,248 +5,229 @@
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
-function switchSite(id, name, showAjaxLoading, idCanBeAll)
-{
- if (id == 'all'
- && !idCanBeAll)
- {
- broadcast.propagateNewPage('module=MultiSites&action=index');
- }
- else
- {
- $('.sites_autocomplete input').val(id);
- $('.custom_select_main_link').text(name);
- $('.custom_select_main_link').addClass('custom_select_loading');
- broadcast.propagateNewPage('idSite='+id, showAjaxLoading);
- }
+function switchSite(id, name, showAjaxLoading, idCanBeAll) {
+ if (id == 'all'
+ && !idCanBeAll) {
+ broadcast.propagateNewPage('module=MultiSites&action=index');
+ }
+ else {
+ $('.sites_autocomplete input').val(id);
+ $('.custom_select_main_link').text(name);
+ $('.custom_select_main_link').addClass('custom_select_loading');
+ broadcast.propagateNewPage('idSite=' + id, showAjaxLoading);
+ }
return false;
}
-$(function() {
-
- var reset = function(selector)
- {
- $('.websiteSearch', selector).val('');
- $('.custom_select_ul_list', selector).show();
- $(".siteSelect.ui-autocomplete,.reset", selector).hide();
- };
-
- // sets up every un-inited site selector widget
- piwik.initSiteSelectors = function()
- {
- $('.sites_autocomplete').each(function() {
- var selector = $(this);
-
- if (selector.attr('data-inited') == 1)
- {
- return;
- }
-
- selector.attr('data-inited', 1);
-
- var websiteSearch = $('.websiteSearch', selector);
-
- // when the search input is clicked, clear the input
- websiteSearch.click(function() {
- $(this).val('');
- });
-
- // when a key is released over the search input when empty, reset the selector
- //
- websiteSearch.keyup(function(e) {
- if (e.keyCode == 27)
- {
- $('.custom_select_block', selector).removeClass('custom_select_block_show');
- return false;
- }
-
- if (!$(this).val())
- {
- reset(selector);
- }
- });
-
- // setup the autocompleter
- websiteSearch.autocomplete({
- minLength: 1,
- source: '?module=SitesManager&action=getSitesForAutocompleter',
- appendTo: $('.custom_select_container', selector),
- select: function(event, ui) {
- if (ui.item.id > 0)
- {
- // set attributes of selected site display (what shows in the box)
- $('.custom_select_main_link', selector)
- .attr('siteid', ui.item.id)
- .text(ui.item.name);
-
- // hide the dropdown
- $('.custom_select_block', selector).removeClass('custom_select_block_show');
-
- // fire the site selected event
- selector.trigger('piwik:siteSelected', ui.item);
- }
- else
- {
- reset(selector);
- }
-
- return false;
- },
- focus: function(event, ui) {
- $('.websiteSearch', selector).val(ui.item.name);
- return false;
- },
- search: function(event, ui) {
- $('.reset', selector).show();
- $('.custom_select_main_link', selector).addClass('custom_select_loading');
- },
- open: function(event, ui) {
- var widthSitesSelection = +$('.custom_select_ul_list', selector).width();
-
- $('.custom_select_main_link', selector).removeClass('custom_select_loading');
-
- var maxSitenameWidth = $('.max_sitename_width', selector);
- if (widthSitesSelection > maxSitenameWidth.val())
- {
- maxSitenameWidth.val(widthSitesSelection);
- }
- else
- {
- maxSitenameWidth = +maxSitenameWidth.val(); // convert to int
- }
-
- $('.custom_select_ul_list', selector).hide();
-
- // customize jquery-ui's autocomplete positioning
- var cssToRemove = {float: 'none', position: 'static'};
- $('.siteSelect.ui-autocomplete', selector)
- .show().width(widthSitesSelection).css(cssToRemove)
- .find('li,a').each(function() {
- $(this).css(cssToRemove);
- });
-
- $('.custom_select_block_show', selector).width(widthSitesSelection);
- }
- }).data("autocomplete")._renderItem = function (ul, item) {
- $(ul).addClass('siteSelect');
-
- var idSiteParam = 'idSite=' + item.id,
- hash = broadcast.isHashExists() ? broadcast.getHashFromUrl().replace(/idSite=[0-9]+/, idSiteParam) : "",
- linkUrl = piwikHelper.getCurrentQueryStringWithParametersModified(idSiteParam) + hash,
- link = $("<a></a>").html(item.label).attr('href', linkUrl),
- listItem = $('<li></li>');
-
- listItem.data("item.autocomplete", item)
- .append(link)
- .appendTo(ul);
-
- link.click(function(e) {
- // in ie8, the event would bubble up and cause an error
- e.stopPropagation();
- return true;
- });
-
- return listItem;
- };
-
- // when the reset button is clicked, reset the site selector
- $('.reset', selector).click(reset);
-
- // when mouse button is released on body, check if it is not over the site selector, and if not
- // close it
- $('body').on('mouseup', function(e) {
- var closestSelector = $(e.target).closest('.sites_autocomplete');
- if (closestSelector != selector)
- {
- reset(selector);
- $('.custom_select_block', selector).removeClass('custom_select_block_show');
- }
- });
-
- // set event handling code for non-jquery-autocomplete parts of widget
- if ($('li', selector).length > 1)
- {
- // event handler for when site selector is clicked. shows dropdown w/ first X sites
- $(".custom_select_main_link", selector).click(function() {
- $(".custom_select_block", selector).addClass("custom_select_block_show");
- $('.custom_select_ul_list', selector).show();
- $('.websiteSearch', selector).val('').focus();
- return false;
- });
-
- $('.custom_select_block', selector).on('mouseenter', function() {
- $('.custom_select_ul_list li a', selector).each(function(){
- var hash = broadcast.getHashFromUrl();
- hash = hash ? hash.replace(/idSite=[0-9]+/, 'idSite='+$(this).attr('siteid')) : "";
-
- var queryString = piwikHelper.getCurrentQueryStringWithParametersModified(
- 'idSite=' + $(this).attr('siteid'));
- $(this).attr('href', queryString + hash);
- });
- });
-
- // change selection. fire's site selector's on select event and modifies the attributes
- // of the selected link
- $('.custom_select_ul_list li a', selector).each(function() {
- $(this).click(function (e) {
- var idsite = $(this).attr('siteid'),
- name = $(this).text();
-
- $(".custom_select_main_link", selector)
- .attr('siteid', idsite)
- .text(name);
-
- selector.trigger('piwik:siteSelected', {id: idsite, name: name});
-
- // close the dropdown
- $(".custom_select_block", selector).removeClass("custom_select_block_show");
-
- e.preventDefault();
- });
- });
-
- var inlinePaddingWidth = 22, staticPaddingWidth = 34;
- if ($(".custom_select_block ul", selector)[0])
- {
- var widthSitesSelection = Math.max(
- $(".custom_select_block ul", selector).width()+inlinePaddingWidth,
- $(".custom_select_main_link", selector).width()+staticPaddingWidth
- );
- $(".custom_select_block", selector).css('width', widthSitesSelection);
- }
- }
- else
- {
- $('.custom_select_main_link', selector).addClass('noselect');
- }
-
- // handle multi-sites link click (triggers site selected event w/ id=all)
- $('.custom_select_all', selector).click(function () {
- $(".custom_select_block", selector).toggleClass("custom_select_block_show");
-
- selector.trigger('piwik:siteSelected', {id: 'all', name: $('.custom_select_all>a', selector).text()});
- });
-
- // handle submit button click
- $('.but', selector).on('click', function(e) {
- if (websiteSearch.val() != '')
- {
- websiteSearch.autocomplete('search', websiteSearch.val() + '%%%');
- }
- return false;
- });
-
- // if the data-switch-site-on-select attribute is set to 1 on the selector, set
- // a default handler for piwik:siteSelected that switches the current site
- if (selector.attr('data-switch-site-on-select') == 1)
- {
- selector.bind('piwik:siteSelected', function (e, site) {
- if (piwik.idSite !== site.id)
- {
- switchSite(site.id, site.name);
- }
- });
- }
- });
- };
+$(function () {
+
+ var reset = function (selector) {
+ $('.websiteSearch', selector).val('');
+ $('.custom_select_ul_list', selector).show();
+ $(".siteSelect.ui-autocomplete,.reset", selector).hide();
+ };
+
+ // sets up every un-inited site selector widget
+ piwik.initSiteSelectors = function () {
+ $('.sites_autocomplete').each(function () {
+ var selector = $(this);
+
+ if (selector.attr('data-inited') == 1) {
+ return;
+ }
+
+ selector.attr('data-inited', 1);
+
+ var websiteSearch = $('.websiteSearch', selector);
+
+ // when the search input is clicked, clear the input
+ websiteSearch.click(function () {
+ $(this).val('');
+ });
+
+ // when a key is released over the search input when empty, reset the selector
+ //
+ websiteSearch.keyup(function (e) {
+ if (e.keyCode == 27) {
+ $('.custom_select_block', selector).removeClass('custom_select_block_show');
+ return false;
+ }
+
+ if (!$(this).val()) {
+ reset(selector);
+ }
+ });
+
+ // setup the autocompleter
+ websiteSearch.autocomplete({
+ minLength: 1,
+ source: '?module=SitesManager&action=getSitesForAutocompleter',
+ appendTo: $('.custom_select_container', selector),
+ select: function (event, ui) {
+ if (ui.item.id > 0) {
+ // set attributes of selected site display (what shows in the box)
+ $('.custom_select_main_link', selector)
+ .attr('siteid', ui.item.id)
+ .text(ui.item.name);
+
+ // hide the dropdown
+ $('.custom_select_block', selector).removeClass('custom_select_block_show');
+
+ // fire the site selected event
+ selector.trigger('piwik:siteSelected', ui.item);
+ }
+ else {
+ reset(selector);
+ }
+
+ return false;
+ },
+ focus: function (event, ui) {
+ $('.websiteSearch', selector).val(ui.item.name);
+ return false;
+ },
+ search: function (event, ui) {
+ $('.reset', selector).show();
+ $('.custom_select_main_link', selector).addClass('custom_select_loading');
+ },
+ open: function (event, ui) {
+ var widthSitesSelection = +$('.custom_select_ul_list', selector).width();
+
+ $('.custom_select_main_link', selector).removeClass('custom_select_loading');
+
+ var maxSitenameWidth = $('.max_sitename_width', selector);
+ if (widthSitesSelection > maxSitenameWidth.val()) {
+ maxSitenameWidth.val(widthSitesSelection);
+ }
+ else {
+ maxSitenameWidth = +maxSitenameWidth.val(); // convert to int
+ }
+
+ $('.custom_select_ul_list', selector).hide();
+
+ // customize jquery-ui's autocomplete positioning
+ var cssToRemove = {float: 'none', position: 'static'};
+ $('.siteSelect.ui-autocomplete', selector)
+ .show().width(widthSitesSelection).css(cssToRemove)
+ .find('li,a').each(function () {
+ $(this).css(cssToRemove);
+ });
+
+ $('.custom_select_block_show', selector).width(widthSitesSelection);
+ }
+ }).data("autocomplete")._renderItem = function (ul, item) {
+ $(ul).addClass('siteSelect');
+
+ var idSiteParam = 'idSite=' + item.id,
+ hash = broadcast.isHashExists() ? broadcast.getHashFromUrl().replace(/idSite=[0-9]+/, idSiteParam) : "",
+ linkUrl = piwikHelper.getCurrentQueryStringWithParametersModified(idSiteParam) + hash,
+ link = $("<a></a>").html(item.label).attr('href', linkUrl),
+ listItem = $('<li></li>');
+
+ listItem.data("item.autocomplete", item)
+ .append(link)
+ .appendTo(ul);
+
+ link.click(function (e) {
+ // in ie8, the event would bubble up and cause an error
+ e.stopPropagation();
+ return true;
+ });
+
+ return listItem;
+ };
+
+ // when the reset button is clicked, reset the site selector
+ $('.reset', selector).click(reset);
+
+ // when mouse button is released on body, check if it is not over the site selector, and if not
+ // close it
+ $('body').on('mouseup', function (e) {
+ var closestSelector = $(e.target).closest('.sites_autocomplete');
+ if (closestSelector != selector) {
+ reset(selector);
+ $('.custom_select_block', selector).removeClass('custom_select_block_show');
+ }
+ });
+
+ // set event handling code for non-jquery-autocomplete parts of widget
+ if ($('li', selector).length > 1) {
+ // event handler for when site selector is clicked. shows dropdown w/ first X sites
+ $(".custom_select_main_link", selector).click(function () {
+ $(".custom_select_block", selector).addClass("custom_select_block_show");
+ $('.custom_select_ul_list', selector).show();
+ $('.websiteSearch', selector).val('').focus();
+ return false;
+ });
+
+ $('.custom_select_block', selector).on('mouseenter', function () {
+ $('.custom_select_ul_list li a', selector).each(function () {
+ var hash = broadcast.getHashFromUrl();
+ hash = hash ? hash.replace(/idSite=[0-9]+/, 'idSite=' + $(this).attr('siteid')) : "";
+
+ var queryString = piwikHelper.getCurrentQueryStringWithParametersModified(
+ 'idSite=' + $(this).attr('siteid'));
+ $(this).attr('href', queryString + hash);
+ });
+ });
+
+ // change selection. fire's site selector's on select event and modifies the attributes
+ // of the selected link
+ $('.custom_select_ul_list li a', selector).each(function () {
+ $(this).click(function (e) {
+ var idsite = $(this).attr('siteid'),
+ name = $(this).text();
+
+ $(".custom_select_main_link", selector)
+ .attr('siteid', idsite)
+ .text(name);
+
+ selector.trigger('piwik:siteSelected', {id: idsite, name: name});
+
+ // close the dropdown
+ $(".custom_select_block", selector).removeClass("custom_select_block_show");
+
+ e.preventDefault();
+ });
+ });
+
+ var inlinePaddingWidth = 22, staticPaddingWidth = 34;
+ if ($(".custom_select_block ul", selector)[0]) {
+ var widthSitesSelection = Math.max(
+ $(".custom_select_block ul", selector).width() + inlinePaddingWidth,
+ $(".custom_select_main_link", selector).width() + staticPaddingWidth
+ );
+ $(".custom_select_block", selector).css('width', widthSitesSelection);
+ }
+ }
+ else {
+ $('.custom_select_main_link', selector).addClass('noselect');
+ }
+
+ // handle multi-sites link click (triggers site selected event w/ id=all)
+ $('.custom_select_all', selector).click(function () {
+ $(".custom_select_block", selector).toggleClass("custom_select_block_show");
+
+ selector.trigger('piwik:siteSelected', {id: 'all', name: $('.custom_select_all>a', selector).text()});
+ });
+
+ // handle submit button click
+ $('.but', selector).on('click', function (e) {
+ if (websiteSearch.val() != '') {
+ websiteSearch.autocomplete('search', websiteSearch.val() + '%%%');
+ }
+ return false;
+ });
+
+ // if the data-switch-site-on-select attribute is set to 1 on the selector, set
+ // a default handler for piwik:siteSelected that switches the current site
+ if (selector.attr('data-switch-site-on-select') == 1) {
+ selector.bind('piwik:siteSelected', function (e, site) {
+ if (piwik.idSite !== site.id) {
+ switchSite(site.id, site.name);
+ }
+ });
+ }
+ });
+ };
});
diff --git a/plugins/CoreHome/templates/broadcast.js b/plugins/CoreHome/templates/broadcast.js
index d57bca82d9..9b5a9ffdf8 100644
--- a/plugins/CoreHome/templates/broadcast.js
+++ b/plugins/CoreHome/templates/broadcast.js
@@ -27,25 +27,25 @@ var broadcast = {
*/
_isInit: false,
- /**
- * Last known hash url without popover parameter
- */
- currentHashUrl: false,
-
- /**
- * Last known popover parameter
- */
- currentPopoverParameter: false,
-
- /**
- * Callbacks for popover parameter change
- */
- popoverHandlers: [],
-
- /**
- * Force reload once
- */
- forceReload: false,
+ /**
+ * Last known hash url without popover parameter
+ */
+ currentHashUrl: false,
+
+ /**
+ * Last known popover parameter
+ */
+ currentPopoverParameter: false,
+
+ /**
+ * Callbacks for popover parameter change
+ */
+ popoverHandlers: [],
+
+ /**
+ * Force reload once
+ */
+ forceReload: false,
/**
* Suppress content update on hash changing
@@ -56,8 +56,8 @@ var broadcast = {
* Initializes broadcast object
* @return {void}
*/
- init: function() {
- if(broadcast._isInit) {
+ init: function () {
+ if (broadcast._isInit) {
return;
}
broadcast._isInit = true;
@@ -75,15 +75,14 @@ var broadcast = {
* 1. after calling $.history.init();
* 2. after calling $.history.load(); //look at broadcast.changeParameter();
* 3. after pushing "Go Back" button of a browser
- *
- * * Note: the method is manipulated in Overlay/templates/index.js - keep this in mind when making changes.
+ *
+ * * Note: the method is manipulated in Overlay/templates/index.js - keep this in mind when making changes.
*
* @param {string} hash to load page with
* @return {void}
*/
- pageload: function( hash )
- {
- broadcast.init();
+ pageload: function (hash) {
+ broadcast.init();
// Unbind any previously attached resize handlers
$(window).off('resize');
@@ -93,66 +92,66 @@ var broadcast = {
broadcast.updateHashOnly = false;
return;
}
-
- // hash doesn't contain the first # character.
- if( hash ) {
-
- var hashParts = hash.split('&popover=');
- var hashUrl = hashParts[0];
- var popoverParam = '';
- if (hashParts.length > 1) {
- popoverParam = hashParts[1];
- // in case the $ was encoded (e.g. when using copy&paste on urls in some browsers)
- popoverParam = decodeURIComponent(popoverParam);
- // revert special encoding from broadcast.propagateNewPopoverParameter()
- popoverParam = popoverParam.replace(/\$/g, '%');
- popoverParam = decodeURIComponent(popoverParam);
- }
-
- var pageUrlUpdated = (popoverParam == '' ||
- (broadcast.currentHashUrl !== false && broadcast.currentHashUrl != hashUrl));
-
- var popoverParamUpdated = (popoverParam != '' && hashUrl == broadcast.currentHashUrl);
-
- if (broadcast.currentHashUrl === false) {
- // new page load
- pageUrlUpdated = true;
- popoverParamUpdated = (popoverParam != '');
- }
-
- if (pageUrlUpdated || broadcast.forceReload) {
- Piwik_Popover.close();
-
- if (hashUrl != broadcast.currentHashUrl || broadcast.forceReload) {
- // restore ajax loaded state
- broadcast.loadAjaxContent(hashUrl);
-
- // make sure the "Widgets & Dashboard" is deleted on reload
- $('#dashboardSettings').remove();
- $('#dashboardWidgetsArea').dashboard('destroy');
- }
- }
-
- broadcast.forceReload = false;
- broadcast.currentHashUrl = hashUrl;
- broadcast.currentPopoverParameter = popoverParam;
-
- if (popoverParamUpdated && popoverParam == '') {
- Piwik_Popover.close();
- } else if (popoverParamUpdated) {
- var popoverParamParts = popoverParam.split(':');
- var handlerName = popoverParamParts[0];
- popoverParamParts.shift();
- var param = popoverParamParts.join(':');
- if (typeof broadcast.popoverHandlers[handlerName] != 'undefined') {
- broadcast.popoverHandlers[handlerName](param);
- }
- }
-
- } else {
- // start page
- $('#content').empty();
- }
+
+ // hash doesn't contain the first # character.
+ if (hash) {
+
+ var hashParts = hash.split('&popover=');
+ var hashUrl = hashParts[0];
+ var popoverParam = '';
+ if (hashParts.length > 1) {
+ popoverParam = hashParts[1];
+ // in case the $ was encoded (e.g. when using copy&paste on urls in some browsers)
+ popoverParam = decodeURIComponent(popoverParam);
+ // revert special encoding from broadcast.propagateNewPopoverParameter()
+ popoverParam = popoverParam.replace(/\$/g, '%');
+ popoverParam = decodeURIComponent(popoverParam);
+ }
+
+ var pageUrlUpdated = (popoverParam == '' ||
+ (broadcast.currentHashUrl !== false && broadcast.currentHashUrl != hashUrl));
+
+ var popoverParamUpdated = (popoverParam != '' && hashUrl == broadcast.currentHashUrl);
+
+ if (broadcast.currentHashUrl === false) {
+ // new page load
+ pageUrlUpdated = true;
+ popoverParamUpdated = (popoverParam != '');
+ }
+
+ if (pageUrlUpdated || broadcast.forceReload) {
+ Piwik_Popover.close();
+
+ if (hashUrl != broadcast.currentHashUrl || broadcast.forceReload) {
+ // restore ajax loaded state
+ broadcast.loadAjaxContent(hashUrl);
+
+ // make sure the "Widgets & Dashboard" is deleted on reload
+ $('#dashboardSettings').remove();
+ $('#dashboardWidgetsArea').dashboard('destroy');
+ }
+ }
+
+ broadcast.forceReload = false;
+ broadcast.currentHashUrl = hashUrl;
+ broadcast.currentPopoverParameter = popoverParam;
+
+ if (popoverParamUpdated && popoverParam == '') {
+ Piwik_Popover.close();
+ } else if (popoverParamUpdated) {
+ var popoverParamParts = popoverParam.split(':');
+ var handlerName = popoverParamParts[0];
+ popoverParamParts.shift();
+ var param = popoverParamParts.join(':');
+ if (typeof broadcast.popoverHandlers[handlerName] != 'undefined') {
+ broadcast.popoverHandlers[handlerName](param);
+ }
+ }
+
+ } else {
+ // start page
+ $('#content').empty();
+ }
},
/**
@@ -166,11 +165,10 @@ var broadcast = {
* NOTE: this method will only make ajax call and replacing main content.
*
* @param {string} ajaxUrl querystring with parameters to be updated
- * @param {boolean} disableHistory the hash change won't be available in the browser history
+ * @param {boolean} disableHistory the hash change won't be available in the browser history
* @return {void}
*/
- propagateAjax: function (ajaxUrl, disableHistory)
- {
+ propagateAjax: function (ajaxUrl, disableHistory) {
broadcast.init();
// abort all existing ajax requests
@@ -179,40 +177,35 @@ var broadcast = {
// available in global scope
var currentHashStr = broadcast.getHash();
- ajaxUrl = ajaxUrl.replace(/^\?|&#/,'');
-
+ ajaxUrl = ajaxUrl.replace(/^\?|&#/, '');
+
var params_vals = ajaxUrl.split("&");
- for( var i=0; i<params_vals.length; i++ )
- {
- currentHashStr = broadcast.updateParamValue(params_vals[i],currentHashStr);
+ for (var i = 0; i < params_vals.length; i++) {
+ currentHashStr = broadcast.updateParamValue(params_vals[i], currentHashStr);
}
// if the module is not 'Goals', we specifically unset the 'idGoal' parameter
// this is to ensure that the URLs are clean (and that clicks on graphs work as expected - they are broken with the extra parameter)
var action = broadcast.getParamValue('action', currentHashStr);
- if( action != 'goalReport' && action != 'ecommerceReport')
- {
+ if (action != 'goalReport' && action != 'ecommerceReport') {
currentHashStr = broadcast.updateParamValue('idGoal=', currentHashStr);
}
// unset idDashboard if use doesn't display a dashboard
var module = broadcast.getParamValue('module', currentHashStr);
- if( module != 'Dashboard')
- {
+ if (module != 'Dashboard') {
currentHashStr = broadcast.updateParamValue('idDashboard=', currentHashStr);
}
-
- if (disableHistory)
- {
- var newLocation = window.location.href.split('#')[0] + '#' + currentHashStr;
- // window.location.replace changes the current url without pushing it on the browser's history stack
- window.location.replace(newLocation);
- }
- else
- {
- // Let history know about this new Hash and load it.
- broadcast.forceReload = true;
- $.history.load(currentHashStr);
- }
+
+ if (disableHistory) {
+ var newLocation = window.location.href.split('#')[0] + '#' + currentHashStr;
+ // window.location.replace changes the current url without pushing it on the browser's history stack
+ window.location.replace(newLocation);
+ }
+ else {
+ // Let history know about this new Hash and load it.
+ broadcast.forceReload = true;
+ $.history.load(currentHashStr);
+ }
},
/**
@@ -238,14 +231,12 @@ var broadcast = {
* @param {bool} showAjaxLoading whether to show the ajax loading gif or not.
* @return {void}
*/
- propagateNewPage: function (str, showAjaxLoading)
- {
+ propagateNewPage: function (str, showAjaxLoading) {
// abort all existing ajax requests
globalAjaxQueue.abort();
-
- if (typeof showAjaxLoading === 'undefined' || showAjaxLoading)
- {
- piwikHelper.showAjaxLoading();
+
+ if (typeof showAjaxLoading === 'undefined' || showAjaxLoading) {
+ piwikHelper.showAjaxLoading();
}
var params_vals = str.split("&");
@@ -255,22 +246,22 @@ var broadcast = {
var currentHashStr = broadcast.getHashFromUrl();
var oldUrl = currentSearchStr + currentHashStr;
- for( var i=0; i<params_vals.length; i++ ) {
+ for (var i = 0; i < params_vals.length; i++) {
// update both the current search query and hash string
- currentSearchStr = broadcast.updateParamValue(params_vals[i],currentSearchStr);
+ currentSearchStr = broadcast.updateParamValue(params_vals[i], currentSearchStr);
- if(currentHashStr.length != 0 ) {
- currentHashStr = broadcast.updateParamValue(params_vals[i],currentHashStr);
+ if (currentHashStr.length != 0) {
+ currentHashStr = broadcast.updateParamValue(params_vals[i], currentHashStr);
}
}
// Now load the new page.
var newUrl = currentSearchStr + currentHashStr;
- if(oldUrl == newUrl) {
+ if (oldUrl == newUrl) {
window.location.reload();
} else {
- this.forceReload = true;
+ this.forceReload = true;
window.location.href = newUrl;
}
return false;
@@ -296,79 +287,75 @@ var broadcast = {
* @param {string} urlStr url to be updated
* @return {string} urlStr with updated param
*/
- updateParamValue: function(newParamValue, urlStr)
- {
+ updateParamValue: function (newParamValue, urlStr) {
var p_v = newParamValue.split("=");
var paramName = p_v[0];
- var valFromUrl = broadcast.getParamValue(paramName,urlStr);
+ var valFromUrl = broadcast.getParamValue(paramName, urlStr);
// if set 'idGoal=' then we remove the parameter from the URL automatically (rather than passing an empty value)
var paramValue = p_v[1];
- if(paramValue == '')
- {
+ if (paramValue == '') {
newParamValue = '';
}
- if( valFromUrl != '') {
+ if (valFromUrl != '') {
// replacing current param=value to newParamValue;
- valFromUrl = valFromUrl.replace(/\$/g, '\\$');
- valFromUrl = valFromUrl.replace(/\./g, '\\.');
+ valFromUrl = valFromUrl.replace(/\$/g, '\\$');
+ valFromUrl = valFromUrl.replace(/\./g, '\\.');
var regToBeReplace = new RegExp(paramName + '=' + valFromUrl, 'ig');
- if(newParamValue == '') {
+ if (newParamValue == '') {
// if new value is empty remove leading &, aswell
regToBeReplace = new RegExp('[\&]?' + paramName + '=' + valFromUrl, 'ig');
}
- urlStr = urlStr.replace( regToBeReplace, newParamValue );
- } else if(newParamValue != '') {
+ urlStr = urlStr.replace(regToBeReplace, newParamValue);
+ } else if (newParamValue != '') {
urlStr += (urlStr == '') ? newParamValue : '&' + newParamValue;
}
return urlStr;
},
- /**
- * Update the part after the second hash
- */
- propagateNewPopoverParameter: function(handlerName, value)
- {
- var hash = broadcast.getHashFromUrl(window.location.href);
- var hashParts = hash.split('&popover=');
-
- var newHash = hashParts[0];
- if (handlerName) {
- var popover = handlerName + ':' + value;
-
- // between jquery.history and different browser bugs, it's impossible to ensure
- // that the parameter is en- and decoded the same number of times. in order to
- // make sure it doesn't change, we have to manipulate the url encoding a bit.
- popover = encodeURIComponent(popover);
- popover = popover.replace(/%/g, '\$');
- newHash = hashParts[0] + '&popover=' + popover;
- }
-
- window.location.href = 'index.php' + window.location.search + newHash;
- },
-
- /**
- * Add a handler for the popover parameter
- */
- addPopoverHandler: function(handlerName, callback) {
- this.popoverHandlers[handlerName] = callback;
- },
+ /**
+ * Update the part after the second hash
+ */
+ propagateNewPopoverParameter: function (handlerName, value) {
+ var hash = broadcast.getHashFromUrl(window.location.href);
+ var hashParts = hash.split('&popover=');
+
+ var newHash = hashParts[0];
+ if (handlerName) {
+ var popover = handlerName + ':' + value;
+
+ // between jquery.history and different browser bugs, it's impossible to ensure
+ // that the parameter is en- and decoded the same number of times. in order to
+ // make sure it doesn't change, we have to manipulate the url encoding a bit.
+ popover = encodeURIComponent(popover);
+ popover = popover.replace(/%/g, '\$');
+ newHash = hashParts[0] + '&popover=' + popover;
+ }
+
+ window.location.href = 'index.php' + window.location.search + newHash;
+ },
+
+ /**
+ * Add a handler for the popover parameter
+ */
+ addPopoverHandler: function (handlerName, callback) {
+ this.popoverHandlers[handlerName] = callback;
+ },
/**
* Loads the given url with ajax and replaces the content
- *
- * Note: the method is replaced in Overlay/templates/index.js - keep this in mind when making changes.
+ *
+ * Note: the method is replaced in Overlay/templates/index.js - keep this in mind when making changes.
*
* @param {string} urlAjax url to load
* @return {Boolean}
*/
- loadAjaxContent: function(urlAjax)
- {
+ loadAjaxContent: function (urlAjax) {
piwikMenu.activateMenu(
- broadcast.getParamValue('module', urlAjax),
- broadcast.getParamValue('action', urlAjax),
- broadcast.getParamValue('idGoal', urlAjax) || broadcast.getParamValue('idDashboard', urlAjax)
+ broadcast.getParamValue('module', urlAjax),
+ broadcast.getParamValue('action', urlAjax),
+ broadcast.getParamValue('idGoal', urlAjax) || broadcast.getParamValue('idDashboard', urlAjax)
);
piwikHelper.hideAjaxError('loadingError');
@@ -378,21 +365,20 @@ var broadcast = {
urlAjax = urlAjax.match(/^\?/) ? urlAjax : "?" + urlAjax;
broadcast.lastUrlRequested = urlAjax;
- function sectionLoaded(content)
- {
- // if content is whole HTML document, do not show it, otherwise recursive page load could occur
- var htmlDocType = '<!DOCTYPE';
- if (content.substring(0, htmlDocType.length) == htmlDocType)
- {
- return;
- }
-
- if(urlAjax == broadcast.lastUrlRequested) {
- $('#content').html( content ).show();
+ function sectionLoaded(content) {
+ // if content is whole HTML document, do not show it, otherwise recursive page load could occur
+ var htmlDocType = '<!DOCTYPE';
+ if (content.substring(0, htmlDocType.length) == htmlDocType) {
+ return;
+ }
+
+ if (urlAjax == broadcast.lastUrlRequested) {
+ $('#content').html(content).show();
piwikHelper.hideAjaxLoading();
broadcast.lastUrlRequested = null;
}
}
+
var ajaxRequest = {
type: 'POST',
url: urlAjax,
@@ -402,7 +388,7 @@ var broadcast = {
success: sectionLoaded, // Callback when the request succeeds
data: new Object
};
- globalAjaxQueue.push( $.ajax(ajaxRequest) );
+ globalAjaxQueue.push($.ajax(ajaxRequest));
return false;
},
@@ -412,8 +398,7 @@ var broadcast = {
* @param {string} status
* @return {void}
*/
- customAjaxHandleError: function (deferred, status)
- {
+ customAjaxHandleError: function (deferred, status) {
broadcast.lastUrlRequested = null;
piwikHelper.ajaxHandleError(deferred, status);
},
@@ -424,11 +409,10 @@ var broadcast = {
*
* @return {string|false}
*/
- isHashExists: function()
- {
+ isHashExists: function () {
var hashStr = broadcast.getHashFromUrl();
- if ( hashStr != "" ) {
+ if (hashStr != "") {
return hashStr;
} else {
return false;
@@ -442,12 +426,11 @@ var broadcast = {
* @param {string} url
* @return {string} the hash part of the given url
*/
- getHashFromUrl: function(url)
- {
+ getHashFromUrl: function (url) {
var hashStr = "";
// If url provided, give back the hash from url, else get hash from current address.
- if( url && url.match('#') ) {
- hashStr = url.substring(url.indexOf("#"),url.length);
+ if (url && url.match('#')) {
+ hashStr = url.substring(url.indexOf("#"), url.length);
}
else {
hashStr = decodeURIComponent(location.hash);
@@ -463,37 +446,34 @@ var broadcast = {
* @param {string} url
* @return {string} the query part of the given url
*/
- getSearchFromUrl: function(url)
- {
+ getSearchFromUrl: function (url) {
var searchStr = "";
// If url provided, give back the query string from url, else get query string from current address.
- if( url && url.match(/\?/) ) {
- searchStr = url.substring(url.indexOf("?"),url.length);
+ if (url && url.match(/\?/)) {
+ searchStr = url.substring(url.indexOf("?"), url.length);
} else {
searchStr = location.search;
}
return searchStr;
},
-
+
/**
* Returns all key-value pairs in query string of url.
- *
+ *
* @param {string} url url to check. if undefined, null or empty, current url is used.
* @return {object} key value pair describing query string parameters
*/
- getValuesFromUrl: function(url)
- {
- var searchString = this._removeHashFromUrl(url).split('?')[1] || '',
- pairs = searchString.split('&');
-
- var result = {};
- for (var i = 0; i != pairs.length; ++i)
- {
- var pair = pairs[i].split(/=(.+)?/); // split only on first '='
- result[pair[0]] = pair[1];
- }
- return result;
+ getValuesFromUrl: function (url) {
+ var searchString = this._removeHashFromUrl(url).split('?')[1] || '',
+ pairs = searchString.split('&');
+
+ var result = {};
+ for (var i = 0; i != pairs.length; ++i) {
+ var pair = pairs[i].split(/=(.+)?/); // split only on first '='
+ result[pair[0]] = pair[1];
+ }
+ return result;
},
/**
@@ -506,10 +486,9 @@ var broadcast = {
* @param {string} url url to check
* @return {string} value of the given param within the given url
*/
- getValueFromUrl: function (param, url)
- {
+ getValueFromUrl: function (param, url) {
var searchString = this._removeHashFromUrl(url);
- return broadcast.getParamValue(param,searchString);
+ return broadcast.getParamValue(param, searchString);
},
/**
@@ -519,15 +498,14 @@ var broadcast = {
* @param {string} url url to check
* @return {string} value of the given param within the hash part of the given url
*/
- getValueFromHash: function(param, url)
- {
+ getValueFromHash: function (param, url) {
var hashStr = broadcast.getHashFromUrl(url);
- if (hashStr.substr(0, 1) == '#') {
- hashStr = hashStr.substr(1);
- }
- hashStr = hashStr.split('#')[0];
+ if (hashStr.substr(0, 1) == '#') {
+ hashStr = hashStr.substr(1);
+ }
+ hashStr = hashStr.split('#')[0];
- return broadcast.getParamValue(param,hashStr);
+ return broadcast.getParamValue(param, hashStr);
},
@@ -541,17 +519,16 @@ var broadcast = {
* @param {string} url url to check
* @return {string} value of the given param within the given url
*/
- getParamValue: function (param, url)
- {
+ getParamValue: function (param, url) {
var lookFor = param + '=';
var startStr = url.indexOf(lookFor);
- if( startStr >= 0 ) {
+ if (startStr >= 0) {
var endStr = url.indexOf("&", startStr);
- if( endStr == -1 ) {
+ if (endStr == -1) {
endStr = url.length;
}
- var value = url.substring(startStr + param.length +1,endStr);
+ var value = url.substring(startStr + param.length + 1, endStr);
// sanitize values
value = value.replace(/[^_%\+\-\<\>!@\$\.=,;0-9a-zA-Z]/gi, '');
@@ -565,26 +542,24 @@ var broadcast = {
* Returns the hash without the starting #
* @return {string} hash part of the current url
*/
- getHash: function ()
- {
+ getHash: function () {
return broadcast.getHashFromUrl().replace(/^#/, '').split('#')[0];
},
-
- /**
- * Removes the hash portion of a URL and returns the rest.
- *
- * @param {string} url
- * @return {string} url w/o hash
- */
- _removeHashFromUrl: function(url)
- {
+
+ /**
+ * Removes the hash portion of a URL and returns the rest.
+ *
+ * @param {string} url
+ * @return {string} url w/o hash
+ */
+ _removeHashFromUrl: function (url) {
var searchString = '';
- if( url ) {
+ if (url) {
var urlParts = url.split('#');
searchString = urlParts[0];
} else {
searchString = location.search;
}
return searchString;
- }
+ }
};
diff --git a/plugins/CoreHome/templates/calendar.js b/plugins/CoreHome/templates/calendar.js
index f1805ea4be..b6e7060bdc 100644
--- a/plugins/CoreHome/templates/calendar.js
+++ b/plugins/CoreHome/templates/calendar.js
@@ -4,575 +4,537 @@
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
-(function($) {
-
-Date.prototype.getWeek = function() {
- var onejan = new Date(this.getFullYear(),0,1), // needed for getDay()
-
- // use UTC times since getTime() can differ based on user's timezone
- onejan_utc = Date.UTC(this.getFullYear(),0,1),
- this_utc = Date.UTC(this.getFullYear(),this.getMonth(),this.getDate()),
-
- daysSinceYearStart = (this_utc - onejan_utc) / 86400000; // constant is millisecs in one day
-
- return Math.ceil((daysSinceYearStart + onejan.getDay()) / 7);
-}
-
-var currentYear, currentMonth, currentDay, currentDate, currentWeek;
-function setCurrentDate( dateStr )
-{
- var splitDate = dateStr.split("-");
- currentYear = splitDate[0];
- currentMonth = splitDate[1] - 1;
- currentDay = splitDate[2];
- currentDate = new Date(currentYear, currentMonth, currentDay);
- currentWeek = currentDate.getWeek();
-}
-
-setCurrentDate(piwik.currentDateString);
-
-var todayDate = new Date;
-var todayMonth = todayDate.getMonth();
-var todayYear = todayDate.getFullYear();
-var todayDay = todayDate.getDate();
+(function ($) {
+
+ Date.prototype.getWeek = function () {
+ var onejan = new Date(this.getFullYear(), 0, 1), // needed for getDay()
+
+ // use UTC times since getTime() can differ based on user's timezone
+ onejan_utc = Date.UTC(this.getFullYear(), 0, 1),
+ this_utc = Date.UTC(this.getFullYear(), this.getMonth(), this.getDate()),
+
+ daysSinceYearStart = (this_utc - onejan_utc) / 86400000; // constant is millisecs in one day
+
+ return Math.ceil((daysSinceYearStart + onejan.getDay()) / 7);
+ }
+
+ var currentYear, currentMonth, currentDay, currentDate, currentWeek;
+
+ function setCurrentDate(dateStr) {
+ var splitDate = dateStr.split("-");
+ currentYear = splitDate[0];
+ currentMonth = splitDate[1] - 1;
+ currentDay = splitDate[2];
+ currentDate = new Date(currentYear, currentMonth, currentDay);
+ currentWeek = currentDate.getWeek();
+ }
+
+ setCurrentDate(piwik.currentDateString);
+
+ var todayDate = new Date;
+ var todayMonth = todayDate.getMonth();
+ var todayYear = todayDate.getFullYear();
+ var todayDay = todayDate.getDate();
// min/max date for picker
-var piwikMinDate = new Date(piwik.minDateYear, piwik.minDateMonth - 1, piwik.minDateDay),
- piwikMaxDate = new Date(piwik.maxDateYear, piwik.maxDateMonth - 1, piwik.maxDateDay);
+ var piwikMinDate = new Date(piwik.minDateYear, piwik.minDateMonth - 1, piwik.minDateDay),
+ piwikMaxDate = new Date(piwik.maxDateYear, piwik.maxDateMonth - 1, piwik.maxDateDay);
// we start w/ the current period
-var selectedPeriod = piwik.period;
-
-function isDateInCurrentPeriod( date )
-{
- // if the selected period isn't the current period, don't highlight any dates
- if (selectedPeriod != piwik.period)
- {
- return [true, ''];
- }
-
- var valid = false;
-
- var dateMonth = date.getMonth();
- var dateYear = date.getFullYear();
- var dateDay = date.getDate();
- var style = '';
-
- // we don't color dates in the future
- if( dateMonth == todayMonth
- && dateYear == todayYear
- && dateDay > todayDay
- )
- {
- return [true, ''];
- }
-
- // we don't color dates before the minimum date
- if( dateYear < piwik.minDateYear
- || ( dateYear == piwik.minDateYear
- &&
- (
- (dateMonth == piwik.minDateMonth - 1
- && dateDay < piwik.minDateDay)
- || (dateMonth < piwik.minDateMonth - 1)
- )
- )
- )
- {
- return [true, ''];
- }
-
- // we color all day of the month for the same year for the month period
- if(piwik.period == "month"
- && dateMonth == currentMonth
- && dateYear == currentYear
- )
- {
- valid = true;
- }
- // we color all day of the year for the year period
- else if(piwik.period == "year"
- && dateYear == currentYear
- )
- {
- valid = true;
- }
- else if(piwik.period == "week"
- && date.getWeek() == currentWeek
- && dateYear == currentYear
- )
- {
- valid = true;
- }
- else if( piwik.period == "day"
- && dateDay == currentDay
- && dateMonth == currentMonth
- && dateYear == currentYear
- )
- {
- valid = true;
- }
-
- if(valid)
- {
- return [true, 'ui-datepicker-current-period'];
- }
-
- return [true, ''];
-}
-
-piwik.getBaseDatePickerOptions = function(defaultDate)
-{
- return {
- showOtherMonths: false,
- dateFormat: 'yy-mm-dd',
- firstDay: 1,
- minDate: piwikMinDate,
- maxDate: piwikMaxDate,
- prevText: "",
- nextText: "",
- currentText: "",
- defaultDate: defaultDate,
- changeMonth: true,
- changeYear: true,
- stepMonths: 1,
- // jquery-ui-i18n 1.7.2 lacks some translations, so we use our own
- dayNamesMin: [
- _pk_translate('General_DaySu_js'),
- _pk_translate('General_DayMo_js'),
- _pk_translate('General_DayTu_js'),
- _pk_translate('General_DayWe_js'),
- _pk_translate('General_DayTh_js'),
- _pk_translate('General_DayFr_js'),
- _pk_translate('General_DaySa_js')],
- dayNamesShort: [
- _pk_translate('General_ShortDay_1_js'),
- _pk_translate('General_ShortDay_2_js'),
- _pk_translate('General_ShortDay_3_js'),
- _pk_translate('General_ShortDay_4_js'),
- _pk_translate('General_ShortDay_5_js'),
- _pk_translate('General_ShortDay_6_js'),
- _pk_translate('General_ShortDay_7_js')],
- dayNames: [
- _pk_translate('General_LongDay_1_js'),
- _pk_translate('General_LongDay_2_js'),
- _pk_translate('General_LongDay_3_js'),
- _pk_translate('General_LongDay_4_js'),
- _pk_translate('General_LongDay_5_js'),
- _pk_translate('General_LongDay_6_js'),
- _pk_translate('General_LongDay_7_js')],
- monthNamesShort: [
- _pk_translate('General_ShortMonth_1_js'),
- _pk_translate('General_ShortMonth_2_js'),
- _pk_translate('General_ShortMonth_3_js'),
- _pk_translate('General_ShortMonth_4_js'),
- _pk_translate('General_ShortMonth_5_js'),
- _pk_translate('General_ShortMonth_6_js'),
- _pk_translate('General_ShortMonth_7_js'),
- _pk_translate('General_ShortMonth_8_js'),
- _pk_translate('General_ShortMonth_9_js'),
- _pk_translate('General_ShortMonth_10_js'),
- _pk_translate('General_ShortMonth_11_js'),
- _pk_translate('General_ShortMonth_12_js')],
- monthNames: [
- _pk_translate('General_MonthJanuary_js'),
- _pk_translate('General_MonthFebruary_js'),
- _pk_translate('General_MonthMarch_js'),
- _pk_translate('General_MonthApril_js'),
- _pk_translate('General_MonthMay_js'),
- _pk_translate('General_MonthJune_js'),
- _pk_translate('General_MonthJuly_js'),
- _pk_translate('General_MonthAugust_js'),
- _pk_translate('General_MonthSeptember_js'),
- _pk_translate('General_MonthOctober_js'),
- _pk_translate('General_MonthNovember_js'),
- _pk_translate('General_MonthDecember_js')]
- };
-};
-
-var updateDate;
-function getDatePickerOptions()
-{
- var result = piwik.getBaseDatePickerOptions(currentDate);
- result.beforeShowDay = isDateInCurrentPeriod;
- result.stepMonths = selectedPeriod == 'year' ? 12 : 1;
- result.onSelect = function () { updateDate.apply(this, arguments); };
- return result;
-};
-
-$(document).ready(function() {
-
- var datepickerElem = $('#datepicker').datepicker(getDatePickerOptions()),
- periodLabels = $('#periodString .period-type label'),
- periodTooltip = $('#periodString .period-click-tooltip').html();
-
- var toggleWhitespaceHighlighting = function (klass, toggleTop, toggleBottom)
- {
- var viewedYear = $('.ui-datepicker-year', datepickerElem).val(),
- viewedMonth = +$('.ui-datepicker-month', datepickerElem).val(), // convert to int w/ '+'
- firstOfViewedMonth = new Date(viewedYear, viewedMonth, 1),
- lastOfViewedMonth = new Date(viewedYear, viewedMonth + 1, 0);
-
- // only highlight dates between piwik.minDate... & piwik.maxDate...
- // we select the cells to highlight by checking whether the first & last of the
- // currently viewed month are within the min/max dates.
- if (firstOfViewedMonth >= piwikMinDate)
- {
- $('tbody>tr:first-child td.ui-datepicker-other-month', datepickerElem).toggleClass(klass, toggleTop);
- }
- if (lastOfViewedMonth < piwikMaxDate)
- {
- $('tbody>tr:last-child td.ui-datepicker-other-month', datepickerElem).toggleClass(klass, toggleBottom);
- }
- };
-
- // 'this' is the table cell
- var highlightCurrentPeriod = function ()
- {
- switch (selectedPeriod)
- {
- case 'day':
- // highlight this link
- $('a', $(this)).addClass('ui-state-hover');
- break;
- case 'week':
- var row = $(this).parent();
-
- // highlight parent row (the week)
- $('a', row).addClass('ui-state-hover');
-
- // toggle whitespace if week goes into previous or next month. we check if week is on
- // top or bottom row.
- var toggleTop = row.is(':first-child'),
- toggleBottom = row.is(':last-child');
- toggleWhitespaceHighlighting('ui-state-hover', toggleTop, toggleBottom);
- break;
- case 'month':
- // highlight all parent rows (the month)
- $('a', $(this).parent().parent()).addClass('ui-state-hover');
- break;
- case 'year':
- // highlight table (month + whitespace)
- $('a', $(this).parent().parent()).addClass('ui-state-hover');
- toggleWhitespaceHighlighting('ui-state-hover', true, true);
- break;
- }
- };
-
- var unhighlightAllDates = function ()
- {
- // make sure nothing is highlighted
- $('.ui-state-active,.ui-state-hover', datepickerElem).removeClass('ui-state-active ui-state-hover');
-
- // color whitespace
- if (piwik.period == 'year')
- {
- var viewedYear = $('.ui-datepicker-year', datepickerElem).val(),
- toggle = selectedPeriod == 'year' && currentYear == viewedYear;
- toggleWhitespaceHighlighting('ui-datepicker-current-period', toggle, toggle);
- }
- else if (piwik.period == 'week')
- {
- var toggleTop = $('tr:first-child a', datepickerElem).parent().hasClass('ui-datepicker-current-period'),
- toggleBottom = $('tr:last-child a', datepickerElem).parent().hasClass('ui-datepicker-current-period');
- toggleWhitespaceHighlighting('ui-datepicker-current-period', toggleTop, toggleBottom);
- }
- };
-
- updateDate = function (dateText)
- {
- piwikHelper.showAjaxLoading('ajaxLoadingCalendar');
- var date = dateText;
-
- // select new dates in calendar
- setCurrentDate(dateText);
- piwik.period = selectedPeriod;
-
- // make sure it's called after jquery-ui is done, otherwise everything we do will
- // be undone.
- setTimeout(unhighlightAllDates, 1);
-
- datepickerElem.datepicker('refresh');
-
- // Let broadcast do its job:
- // It will replace date value to both search query and hash and load the new page.
- broadcast.propagateNewPage('date=' + date + '&period=' + selectedPeriod);
- };
-
- var toggleMonthDropdown = function (disable)
- {
- if (typeof disable === 'undefined')
- {
- disable = selectedPeriod == 'year';
- }
-
- // enable/disable month dropdown based on period == year
- $('.ui-datepicker-month', datepickerElem).attr('disabled', disable);
- };
-
- var togglePeriodPickers = function (showSingle)
- {
- $('#periodString .period-date').toggle(showSingle);
- $('#periodString .period-range').toggle(!showSingle);
- $('#calendarRangeApply').toggle(!showSingle);
- };
-
- //
- // setup datepicker
- //
-
- unhighlightAllDates();
-
- //
- // hook up event slots
- //
-
- // highlight current period when mouse enters date
- datepickerElem.on('mouseenter', 'tbody td', function() {
- if ($(this).hasClass('ui-state-hover')) // if already highlighted, do nothing
- {
- return;
- }
-
- // unhighlight if cell is disabled/blank, unless the period is year
- if ($(this).hasClass('ui-state-disabled') && selectedPeriod != 'year')
- {
- unhighlightAllDates();
-
- // if period is week, then highlight the current week
- if (selectedPeriod == 'week')
- {
- highlightCurrentPeriod.call(this);
- }
- }
- else
- {
- highlightCurrentPeriod.call(this);
- }
- });
-
- // make sure cell stays highlighted when mouse leaves cell (overrides jquery-ui behavior)
- datepickerElem.on('mouseleave', 'tbody td', function () {
- $('a', this).addClass('ui-state-hover');
- });
-
- // unhighlight everything when mouse leaves table body (can't do event on tbody, for some reason
- // that fails, so we do two events, one on the table & one on thead)
- datepickerElem.on('mouseleave', 'table', unhighlightAllDates)
- .on('mouseenter', 'thead', unhighlightAllDates);
-
- // make sure whitespace is clickable when the period makes it appropriate
- datepickerElem.on('click', 'tbody td.ui-datepicker-other-month', function () {
- if ($(this).hasClass('ui-state-hover'))
- {
- var row = $(this).parent(), tbody = row.parent();
-
- if (row.is(':first-child'))
- {
- // click on first of the month
- $('a', tbody).first().click();
- }
- else
- {
- // click on last of month
- $('a', tbody).last().click();
- }
- }
- });
-
- // Hack to get around firefox bug. When double clicking a label in firefox, the 'click'
- // event of its associated input will not be fired twice. We want to change the period
- // if clicking the select period's label OR input, so we catch the click event on the
- // label & the input.
- var reloading = false;
- var changePeriodOnClick = function(periodInput)
- {
- if (reloading) // if a click event resulted in reloading, don't reload again
- {
- return;
- }
-
- var url = periodInput.val(),
- period = broadcast.getValueFromUrl('period', url);
-
- // if clicking on the selected period, change the period but not the date
- if (selectedPeriod == period && selectedPeriod != 'range')
- {
- // only reload if current period is different from selected
- if (piwik.period != selectedPeriod && !reloading)
- {
- reloading = true;
- selectedPeriod = period;
- updateDate(piwik.currentDateString);
- }
- return true;
- }
-
- return false;
- };
-
- $("#otherPeriods label").on('click', function(e) {
- var id = $(e.target).attr('for');
- changePeriodOnClick($('#' + id));
- });
-
- // when non-range period is clicked, change the period & refresh the date picker
- $("#otherPeriods input").on('click', function(e) {
- var request_URL = $(e.target).val(),
- period = broadcast.getValueFromUrl('period', request_URL),
- lastPeriod = selectedPeriod;
-
- if (changePeriodOnClick($(e.target)))
- {
- return true;
- }
-
- // switch the selected period
- selectedPeriod = period;
-
- // remove tooltips from the period inputs
- periodLabels.each(function() { $(this).attr('title', '').removeClass('selected-period-label'); });
-
- // range periods are handled in an event handler below
- if (period == 'range')
- {
- return true;
- }
-
- // set the tooltip of the current period
- if (period != piwik.period) // don't add tooltip for current period
- {
- $(this).parent().find('label[for=period_id_'+period+']')
- .attr('title', periodTooltip).addClass('selected-period-label');
- }
-
- // toggle the right selector controls (show period selector datepicker & hide 'apply range' button)
- togglePeriodPickers(true);
-
- // set months step to 12 for year period (or set back to 1 if leaving year period)
- if (selectedPeriod == 'year' || lastPeriod == 'year')
- {
- // setting stepMonths will change the month in view back to the selected date. to avoid
- // we set the selected date to the month in view.
- var currentMonth = $('.ui-datepicker-month', datepickerElem).val(),
- currentYear = $('.ui-datepicker-year', datepickerElem).val();
-
- datepickerElem
- .datepicker('option', 'stepMonths', selectedPeriod == 'year' ? 12 : 1)
- .datepicker('setDate', new Date(currentYear, currentMonth));
- }
-
- datepickerElem.datepicker('refresh'); // must be last datepicker call, otherwise cells get highlighted
-
- unhighlightAllDates();
- toggleMonthDropdown();
-
- return true;
- });
-
- // clicking left/right re-enables the month dropdown, so we disable it again
- $(datepickerElem).on('click', '.ui-datepicker-next,.ui-datepicker-prev', function() {
- unhighlightAllDates(); // make sure today's date isn't highlighted & toggle extra year highlighting
- toggleMonthDropdown(selectedPeriod == 'year');
- });
-
- // reset date/period when opening calendar
- var firstClick = true;
- $('#periodString #date').click(function() {
- if (!firstClick)
- {
- datepickerElem.datepicker('setDate', currentDate);
- $('#period_id_' + piwik.period).click();
- }
-
- firstClick = false;
- });
-
- function onDateRangeSelect(dateText, inst)
- {
- var toOrFrom = inst.id == 'calendarFrom' ? 'From' : 'To';
- $('#inputCalendar'+toOrFrom).val(dateText);
- }
-
- // this will trigger to change only the period value on search query and hash string.
- $("#period_id_range").on('click', function(e) {
- togglePeriodPickers(false);
-
- var options = getDatePickerOptions();
-
- // Custom Date range callback
- options.onSelect = onDateRangeSelect;
- // Do not highlight the period
- options.beforeShowDay = '';
- // Create both calendars
- options.defaultDate = piwik.startDateString;
- $('#calendarFrom').datepicker(options).datepicker("setDate", $.datepicker.parseDate('yy-mm-dd', piwik.startDateString));
-
- // Technically we should trigger the onSelect event on the calendar, but I couldn't find how to do that
- // So calling the onSelect bind function manually...
- //$('#calendarFrom').trigger('dateSelected'); // or onSelect
- onDateRangeSelect(piwik.startDateString, { "id": "calendarFrom" } );
-
- // Same code for the other calendar
- options.defaultDate = piwik.endDateString;
- $('#calendarTo').datepicker(options).datepicker("setDate", $.datepicker.parseDate('yy-mm-dd', piwik.endDateString));
- onDateRangeSelect(piwik.endDateString, { "id": "calendarTo" });
-
-
- // If not called, the first date appears light brown instead of dark brown
- $('.ui-state-hover').removeClass('ui-state-hover');
-
- // Apply date range button will reload the page with the selected range
- $('#calendarRangeApply')
- .on('click', function() {
- var request_URL = $(e.target).val();
- var dateFrom = $('#inputCalendarFrom').val(),
- dateTo = $('#inputCalendarTo').val(),
- oDateFrom = $.datepicker.parseDate('yy-mm-dd', dateFrom),
- oDateTo = $.datepicker.parseDate('yy-mm-dd', dateTo);
-
- if( !isValidDate(oDateFrom )
- || !isValidDate(oDateTo )
- || oDateFrom > oDateTo )
- {
- $('#alert h2').text(_pk_translate('General_InvalidDateRange_js'));
- piwikHelper.modalConfirm('#alert', {});
- return false;
- }
- piwikHelper.showAjaxLoading('ajaxLoadingCalendar');
- broadcast.propagateNewPage('period=range&date='+dateFrom+','+dateTo);
- })
- .show();
-
-
- // Bind the input fields to update the calendar's date when date is manually changed
- $('#inputCalendarFrom, #inputCalendarTo')
- .keyup( function (e) {
- var fromOrTo = this.id == 'inputCalendarFrom' ? 'From' : 'To';
- var dateInput = $(this).val();
- try {
- var newDate = $.datepicker.parseDate('yy-mm-dd', dateInput);
- } catch (e) {
- return;
- }
- $("#calendar"+fromOrTo).datepicker("setDate", newDate);
- if(e.keyCode == 13) {
- $('#calendarRangeApply').click();
- }
- });
- return true;
- });
- function isValidDate(d) {
- if ( Object.prototype.toString.call(d) !== "[object Date]" )
- return false;
- return !isNaN(d.getTime());
- }
-
- var period = broadcast.getValueFromUrl('period');
- if (period == 'range')
- {
- $("#period_id_range").click();
- }
-});
+ var selectedPeriod = piwik.period;
+
+ function isDateInCurrentPeriod(date) {
+ // if the selected period isn't the current period, don't highlight any dates
+ if (selectedPeriod != piwik.period) {
+ return [true, ''];
+ }
+
+ var valid = false;
+
+ var dateMonth = date.getMonth();
+ var dateYear = date.getFullYear();
+ var dateDay = date.getDate();
+ var style = '';
+
+ // we don't color dates in the future
+ if (dateMonth == todayMonth
+ && dateYear == todayYear
+ && dateDay > todayDay
+ ) {
+ return [true, ''];
+ }
+
+ // we don't color dates before the minimum date
+ if (dateYear < piwik.minDateYear
+ || ( dateYear == piwik.minDateYear
+ &&
+ (
+ (dateMonth == piwik.minDateMonth - 1
+ && dateDay < piwik.minDateDay)
+ || (dateMonth < piwik.minDateMonth - 1)
+ )
+ )
+ ) {
+ return [true, ''];
+ }
+
+ // we color all day of the month for the same year for the month period
+ if (piwik.period == "month"
+ && dateMonth == currentMonth
+ && dateYear == currentYear
+ ) {
+ valid = true;
+ }
+ // we color all day of the year for the year period
+ else if (piwik.period == "year"
+ && dateYear == currentYear
+ ) {
+ valid = true;
+ }
+ else if (piwik.period == "week"
+ && date.getWeek() == currentWeek
+ && dateYear == currentYear
+ ) {
+ valid = true;
+ }
+ else if (piwik.period == "day"
+ && dateDay == currentDay
+ && dateMonth == currentMonth
+ && dateYear == currentYear
+ ) {
+ valid = true;
+ }
+
+ if (valid) {
+ return [true, 'ui-datepicker-current-period'];
+ }
+
+ return [true, ''];
+ }
+
+ piwik.getBaseDatePickerOptions = function (defaultDate) {
+ return {
+ showOtherMonths: false,
+ dateFormat: 'yy-mm-dd',
+ firstDay: 1,
+ minDate: piwikMinDate,
+ maxDate: piwikMaxDate,
+ prevText: "",
+ nextText: "",
+ currentText: "",
+ defaultDate: defaultDate,
+ changeMonth: true,
+ changeYear: true,
+ stepMonths: 1,
+ // jquery-ui-i18n 1.7.2 lacks some translations, so we use our own
+ dayNamesMin: [
+ _pk_translate('General_DaySu_js'),
+ _pk_translate('General_DayMo_js'),
+ _pk_translate('General_DayTu_js'),
+ _pk_translate('General_DayWe_js'),
+ _pk_translate('General_DayTh_js'),
+ _pk_translate('General_DayFr_js'),
+ _pk_translate('General_DaySa_js')],
+ dayNamesShort: [
+ _pk_translate('General_ShortDay_1_js'),
+ _pk_translate('General_ShortDay_2_js'),
+ _pk_translate('General_ShortDay_3_js'),
+ _pk_translate('General_ShortDay_4_js'),
+ _pk_translate('General_ShortDay_5_js'),
+ _pk_translate('General_ShortDay_6_js'),
+ _pk_translate('General_ShortDay_7_js')],
+ dayNames: [
+ _pk_translate('General_LongDay_1_js'),
+ _pk_translate('General_LongDay_2_js'),
+ _pk_translate('General_LongDay_3_js'),
+ _pk_translate('General_LongDay_4_js'),
+ _pk_translate('General_LongDay_5_js'),
+ _pk_translate('General_LongDay_6_js'),
+ _pk_translate('General_LongDay_7_js')],
+ monthNamesShort: [
+ _pk_translate('General_ShortMonth_1_js'),
+ _pk_translate('General_ShortMonth_2_js'),
+ _pk_translate('General_ShortMonth_3_js'),
+ _pk_translate('General_ShortMonth_4_js'),
+ _pk_translate('General_ShortMonth_5_js'),
+ _pk_translate('General_ShortMonth_6_js'),
+ _pk_translate('General_ShortMonth_7_js'),
+ _pk_translate('General_ShortMonth_8_js'),
+ _pk_translate('General_ShortMonth_9_js'),
+ _pk_translate('General_ShortMonth_10_js'),
+ _pk_translate('General_ShortMonth_11_js'),
+ _pk_translate('General_ShortMonth_12_js')],
+ monthNames: [
+ _pk_translate('General_MonthJanuary_js'),
+ _pk_translate('General_MonthFebruary_js'),
+ _pk_translate('General_MonthMarch_js'),
+ _pk_translate('General_MonthApril_js'),
+ _pk_translate('General_MonthMay_js'),
+ _pk_translate('General_MonthJune_js'),
+ _pk_translate('General_MonthJuly_js'),
+ _pk_translate('General_MonthAugust_js'),
+ _pk_translate('General_MonthSeptember_js'),
+ _pk_translate('General_MonthOctober_js'),
+ _pk_translate('General_MonthNovember_js'),
+ _pk_translate('General_MonthDecember_js')]
+ };
+ };
+
+ var updateDate;
+
+ function getDatePickerOptions() {
+ var result = piwik.getBaseDatePickerOptions(currentDate);
+ result.beforeShowDay = isDateInCurrentPeriod;
+ result.stepMonths = selectedPeriod == 'year' ? 12 : 1;
+ result.onSelect = function () { updateDate.apply(this, arguments); };
+ return result;
+ };
+
+ $(document).ready(function () {
+
+ var datepickerElem = $('#datepicker').datepicker(getDatePickerOptions()),
+ periodLabels = $('#periodString .period-type label'),
+ periodTooltip = $('#periodString .period-click-tooltip').html();
+
+ var toggleWhitespaceHighlighting = function (klass, toggleTop, toggleBottom) {
+ var viewedYear = $('.ui-datepicker-year', datepickerElem).val(),
+ viewedMonth = +$('.ui-datepicker-month', datepickerElem).val(), // convert to int w/ '+'
+ firstOfViewedMonth = new Date(viewedYear, viewedMonth, 1),
+ lastOfViewedMonth = new Date(viewedYear, viewedMonth + 1, 0);
+
+ // only highlight dates between piwik.minDate... & piwik.maxDate...
+ // we select the cells to highlight by checking whether the first & last of the
+ // currently viewed month are within the min/max dates.
+ if (firstOfViewedMonth >= piwikMinDate) {
+ $('tbody>tr:first-child td.ui-datepicker-other-month', datepickerElem).toggleClass(klass, toggleTop);
+ }
+ if (lastOfViewedMonth < piwikMaxDate) {
+ $('tbody>tr:last-child td.ui-datepicker-other-month', datepickerElem).toggleClass(klass, toggleBottom);
+ }
+ };
+
+ // 'this' is the table cell
+ var highlightCurrentPeriod = function () {
+ switch (selectedPeriod) {
+ case 'day':
+ // highlight this link
+ $('a', $(this)).addClass('ui-state-hover');
+ break;
+ case 'week':
+ var row = $(this).parent();
+
+ // highlight parent row (the week)
+ $('a', row).addClass('ui-state-hover');
+
+ // toggle whitespace if week goes into previous or next month. we check if week is on
+ // top or bottom row.
+ var toggleTop = row.is(':first-child'),
+ toggleBottom = row.is(':last-child');
+ toggleWhitespaceHighlighting('ui-state-hover', toggleTop, toggleBottom);
+ break;
+ case 'month':
+ // highlight all parent rows (the month)
+ $('a', $(this).parent().parent()).addClass('ui-state-hover');
+ break;
+ case 'year':
+ // highlight table (month + whitespace)
+ $('a', $(this).parent().parent()).addClass('ui-state-hover');
+ toggleWhitespaceHighlighting('ui-state-hover', true, true);
+ break;
+ }
+ };
+
+ var unhighlightAllDates = function () {
+ // make sure nothing is highlighted
+ $('.ui-state-active,.ui-state-hover', datepickerElem).removeClass('ui-state-active ui-state-hover');
+
+ // color whitespace
+ if (piwik.period == 'year') {
+ var viewedYear = $('.ui-datepicker-year', datepickerElem).val(),
+ toggle = selectedPeriod == 'year' && currentYear == viewedYear;
+ toggleWhitespaceHighlighting('ui-datepicker-current-period', toggle, toggle);
+ }
+ else if (piwik.period == 'week') {
+ var toggleTop = $('tr:first-child a', datepickerElem).parent().hasClass('ui-datepicker-current-period'),
+ toggleBottom = $('tr:last-child a', datepickerElem).parent().hasClass('ui-datepicker-current-period');
+ toggleWhitespaceHighlighting('ui-datepicker-current-period', toggleTop, toggleBottom);
+ }
+ };
+
+ updateDate = function (dateText) {
+ piwikHelper.showAjaxLoading('ajaxLoadingCalendar');
+ var date = dateText;
+
+ // select new dates in calendar
+ setCurrentDate(dateText);
+ piwik.period = selectedPeriod;
+
+ // make sure it's called after jquery-ui is done, otherwise everything we do will
+ // be undone.
+ setTimeout(unhighlightAllDates, 1);
+
+ datepickerElem.datepicker('refresh');
+
+ // Let broadcast do its job:
+ // It will replace date value to both search query and hash and load the new page.
+ broadcast.propagateNewPage('date=' + date + '&period=' + selectedPeriod);
+ };
+
+ var toggleMonthDropdown = function (disable) {
+ if (typeof disable === 'undefined') {
+ disable = selectedPeriod == 'year';
+ }
+
+ // enable/disable month dropdown based on period == year
+ $('.ui-datepicker-month', datepickerElem).attr('disabled', disable);
+ };
+
+ var togglePeriodPickers = function (showSingle) {
+ $('#periodString .period-date').toggle(showSingle);
+ $('#periodString .period-range').toggle(!showSingle);
+ $('#calendarRangeApply').toggle(!showSingle);
+ };
+
+ //
+ // setup datepicker
+ //
+
+ unhighlightAllDates();
+
+ //
+ // hook up event slots
+ //
+
+ // highlight current period when mouse enters date
+ datepickerElem.on('mouseenter', 'tbody td', function () {
+ if ($(this).hasClass('ui-state-hover')) // if already highlighted, do nothing
+ {
+ return;
+ }
+
+ // unhighlight if cell is disabled/blank, unless the period is year
+ if ($(this).hasClass('ui-state-disabled') && selectedPeriod != 'year') {
+ unhighlightAllDates();
+
+ // if period is week, then highlight the current week
+ if (selectedPeriod == 'week') {
+ highlightCurrentPeriod.call(this);
+ }
+ }
+ else {
+ highlightCurrentPeriod.call(this);
+ }
+ });
+
+ // make sure cell stays highlighted when mouse leaves cell (overrides jquery-ui behavior)
+ datepickerElem.on('mouseleave', 'tbody td', function () {
+ $('a', this).addClass('ui-state-hover');
+ });
+
+ // unhighlight everything when mouse leaves table body (can't do event on tbody, for some reason
+ // that fails, so we do two events, one on the table & one on thead)
+ datepickerElem.on('mouseleave', 'table', unhighlightAllDates)
+ .on('mouseenter', 'thead', unhighlightAllDates);
+
+ // make sure whitespace is clickable when the period makes it appropriate
+ datepickerElem.on('click', 'tbody td.ui-datepicker-other-month', function () {
+ if ($(this).hasClass('ui-state-hover')) {
+ var row = $(this).parent(), tbody = row.parent();
+
+ if (row.is(':first-child')) {
+ // click on first of the month
+ $('a', tbody).first().click();
+ }
+ else {
+ // click on last of month
+ $('a', tbody).last().click();
+ }
+ }
+ });
+
+ // Hack to get around firefox bug. When double clicking a label in firefox, the 'click'
+ // event of its associated input will not be fired twice. We want to change the period
+ // if clicking the select period's label OR input, so we catch the click event on the
+ // label & the input.
+ var reloading = false;
+ var changePeriodOnClick = function (periodInput) {
+ if (reloading) // if a click event resulted in reloading, don't reload again
+ {
+ return;
+ }
+
+ var url = periodInput.val(),
+ period = broadcast.getValueFromUrl('period', url);
+
+ // if clicking on the selected period, change the period but not the date
+ if (selectedPeriod == period && selectedPeriod != 'range') {
+ // only reload if current period is different from selected
+ if (piwik.period != selectedPeriod && !reloading) {
+ reloading = true;
+ selectedPeriod = period;
+ updateDate(piwik.currentDateString);
+ }
+ return true;
+ }
+
+ return false;
+ };
+
+ $("#otherPeriods label").on('click', function (e) {
+ var id = $(e.target).attr('for');
+ changePeriodOnClick($('#' + id));
+ });
+
+ // when non-range period is clicked, change the period & refresh the date picker
+ $("#otherPeriods input").on('click', function (e) {
+ var request_URL = $(e.target).val(),
+ period = broadcast.getValueFromUrl('period', request_URL),
+ lastPeriod = selectedPeriod;
+
+ if (changePeriodOnClick($(e.target))) {
+ return true;
+ }
+
+ // switch the selected period
+ selectedPeriod = period;
+
+ // remove tooltips from the period inputs
+ periodLabels.each(function () { $(this).attr('title', '').removeClass('selected-period-label'); });
+
+ // range periods are handled in an event handler below
+ if (period == 'range') {
+ return true;
+ }
+
+ // set the tooltip of the current period
+ if (period != piwik.period) // don't add tooltip for current period
+ {
+ $(this).parent().find('label[for=period_id_' + period + ']')
+ .attr('title', periodTooltip).addClass('selected-period-label');
+ }
+
+ // toggle the right selector controls (show period selector datepicker & hide 'apply range' button)
+ togglePeriodPickers(true);
+
+ // set months step to 12 for year period (or set back to 1 if leaving year period)
+ if (selectedPeriod == 'year' || lastPeriod == 'year') {
+ // setting stepMonths will change the month in view back to the selected date. to avoid
+ // we set the selected date to the month in view.
+ var currentMonth = $('.ui-datepicker-month', datepickerElem).val(),
+ currentYear = $('.ui-datepicker-year', datepickerElem).val();
+
+ datepickerElem
+ .datepicker('option', 'stepMonths', selectedPeriod == 'year' ? 12 : 1)
+ .datepicker('setDate', new Date(currentYear, currentMonth));
+ }
+
+ datepickerElem.datepicker('refresh'); // must be last datepicker call, otherwise cells get highlighted
+
+ unhighlightAllDates();
+ toggleMonthDropdown();
+
+ return true;
+ });
+
+ // clicking left/right re-enables the month dropdown, so we disable it again
+ $(datepickerElem).on('click', '.ui-datepicker-next,.ui-datepicker-prev', function () {
+ unhighlightAllDates(); // make sure today's date isn't highlighted & toggle extra year highlighting
+ toggleMonthDropdown(selectedPeriod == 'year');
+ });
+
+ // reset date/period when opening calendar
+ var firstClick = true;
+ $('#periodString #date').click(function () {
+ if (!firstClick) {
+ datepickerElem.datepicker('setDate', currentDate);
+ $('#period_id_' + piwik.period).click();
+ }
+
+ firstClick = false;
+ });
+
+ function onDateRangeSelect(dateText, inst) {
+ var toOrFrom = inst.id == 'calendarFrom' ? 'From' : 'To';
+ $('#inputCalendar' + toOrFrom).val(dateText);
+ }
+
+ // this will trigger to change only the period value on search query and hash string.
+ $("#period_id_range").on('click', function (e) {
+ togglePeriodPickers(false);
+
+ var options = getDatePickerOptions();
+
+ // Custom Date range callback
+ options.onSelect = onDateRangeSelect;
+ // Do not highlight the period
+ options.beforeShowDay = '';
+ // Create both calendars
+ options.defaultDate = piwik.startDateString;
+ $('#calendarFrom').datepicker(options).datepicker("setDate", $.datepicker.parseDate('yy-mm-dd', piwik.startDateString));
+
+ // Technically we should trigger the onSelect event on the calendar, but I couldn't find how to do that
+ // So calling the onSelect bind function manually...
+ //$('#calendarFrom').trigger('dateSelected'); // or onSelect
+ onDateRangeSelect(piwik.startDateString, { "id": "calendarFrom" });
+
+ // Same code for the other calendar
+ options.defaultDate = piwik.endDateString;
+ $('#calendarTo').datepicker(options).datepicker("setDate", $.datepicker.parseDate('yy-mm-dd', piwik.endDateString));
+ onDateRangeSelect(piwik.endDateString, { "id": "calendarTo" });
+
+
+ // If not called, the first date appears light brown instead of dark brown
+ $('.ui-state-hover').removeClass('ui-state-hover');
+
+ // Apply date range button will reload the page with the selected range
+ $('#calendarRangeApply')
+ .on('click', function () {
+ var request_URL = $(e.target).val();
+ var dateFrom = $('#inputCalendarFrom').val(),
+ dateTo = $('#inputCalendarTo').val(),
+ oDateFrom = $.datepicker.parseDate('yy-mm-dd', dateFrom),
+ oDateTo = $.datepicker.parseDate('yy-mm-dd', dateTo);
+
+ if (!isValidDate(oDateFrom)
+ || !isValidDate(oDateTo)
+ || oDateFrom > oDateTo) {
+ $('#alert h2').text(_pk_translate('General_InvalidDateRange_js'));
+ piwikHelper.modalConfirm('#alert', {});
+ return false;
+ }
+ piwikHelper.showAjaxLoading('ajaxLoadingCalendar');
+ broadcast.propagateNewPage('period=range&date=' + dateFrom + ',' + dateTo);
+ })
+ .show();
+
+
+ // Bind the input fields to update the calendar's date when date is manually changed
+ $('#inputCalendarFrom, #inputCalendarTo')
+ .keyup(function (e) {
+ var fromOrTo = this.id == 'inputCalendarFrom' ? 'From' : 'To';
+ var dateInput = $(this).val();
+ try {
+ var newDate = $.datepicker.parseDate('yy-mm-dd', dateInput);
+ } catch (e) {
+ return;
+ }
+ $("#calendar" + fromOrTo).datepicker("setDate", newDate);
+ if (e.keyCode == 13) {
+ $('#calendarRangeApply').click();
+ }
+ });
+ return true;
+ });
+ function isValidDate(d) {
+ if (Object.prototype.toString.call(d) !== "[object Date]")
+ return false;
+ return !isNaN(d.getTime());
+ }
+
+ var period = broadcast.getValueFromUrl('period');
+ if (period == 'range') {
+ $("#period_id_range").click();
+ }
+ });
}(jQuery));
diff --git a/plugins/CoreHome/templates/cloud.css b/plugins/CoreHome/templates/cloud.css
index a1dba84562..b17741e1cf 100644
--- a/plugins/CoreHome/templates/cloud.css
+++ b/plugins/CoreHome/templates/cloud.css
@@ -1,45 +1,56 @@
.tagCloud {
- padding:10px;
+ padding: 10px;
}
+
.tagCloud img {
- border:0;
+ border: 0;
}
+
.tagCloud .word a {
- text-decoration:none;
+ text-decoration: none;
}
+
.tagCloud .word {
- padding: 4px 8px 4px 0;
- white-space: nowrap;
+ padding: 4px 8px 4px 0;
+ white-space: nowrap;
}
+
.tagCloud .valueIsZero {
- text-decoration: line-through;
+ text-decoration: line-through;
}
+
.tagCloud span.size0, .tagCloud span.size0 a {
- color: #255792;
- font-size: 28px;
+ color: #255792;
+ font-size: 28px;
}
+
.tagCloud span.size1, .tagCloud span.size1 a {
- color: #255792;
- font-size: 24px;
+ color: #255792;
+ font-size: 24px;
}
+
.tagCloud span.size2, .tagCloud span.size2 a {
- color: #255792;
- font-size:20px;
+ color: #255792;
+ font-size: 20px;
}
+
.tagCloud span.size3, .tagCloud span.size3 a {
- color: #255792;
- font-size: 16px;
+ color: #255792;
+ font-size: 16px;
}
+
.tagCloud span.size4, .tagCloud span.size4 a {
- color: #255792;
- font-size: 15px;
+ color: #255792;
+ font-size: 15px;
}
+
.tagCloud span.size5, .tagCloud span.size5 a {
- color: #255792;
- font-size: 14px;
+ color: #255792;
+ font-size: 14px;
}
+
.tagCloud span.size6, .tagCloud span.size6 a {
- color: #255792;
- font-size: 11px;
+ color: #255792;
+ font-size: 11px;
}
diff --git a/plugins/CoreHome/templates/cloud.tpl b/plugins/CoreHome/templates/cloud.tpl
index efcfbeac5f..9aabeedcc2 100644
--- a/plugins/CoreHome/templates/cloud.tpl
+++ b/plugins/CoreHome/templates/cloud.tpl
@@ -1,25 +1,26 @@
<div class="dataTable" data-report="{$properties.uniqueId}" data-params="{$javascriptVariablesToSet|@json_encode|escape:'html'}">
- {if !empty($reportDocumentation) && $javascriptVariablesToSet.viewDataTable != 'tableGoals'}
- <div class="reportDocumentation"><p>{$reportDocumentation}</p></div>
- {/if}
- <div class="tagCloud">
- {if count($cloudValues) == 0}
- {if $showReportDataWasPurgedMessage}
- <div class="pk-emptyDataTable">{'General_DataForThisTagCloudHasBeenPurged'|translate:$deleteReportsOlderThan}</div>
- {else}
- <div class="pk-emptyDataTable">{'General_NoDataForTagCloud'|translate}</div>
- {/if}
- {else}
- {foreach from=$cloudValues key=word item=value}
- <span title="{$value.word} ({$value.value} {$columnTranslation})" class="word size{$value.size} {* we strike tags with 0 hits *} {if $value.value == 0}valueIsZero{/if}">
+ {if !empty($reportDocumentation) && $javascriptVariablesToSet.viewDataTable != 'tableGoals'}
+ <div class="reportDocumentation"><p>{$reportDocumentation}</p></div>
+ {/if}
+ <div class="tagCloud">
+ {if count($cloudValues) == 0}
+ {if $showReportDataWasPurgedMessage}
+ <div class="pk-emptyDataTable">{'General_DataForThisTagCloudHasBeenPurged'|translate:$deleteReportsOlderThan}</div>
+ {else}
+ <div class="pk-emptyDataTable">{'General_NoDataForTagCloud'|translate}</div>
+ {/if}
+ {else}
+ {foreach from=$cloudValues key=word item=value}
+ <span title="{$value.word} ({$value.value} {$columnTranslation})"
+ class="word size{$value.size} {* we strike tags with 0 hits *} {if $value.value == 0}valueIsZero{/if}">
{if false !== $labelMetadata[$value.word].url}<a href="{$labelMetadata[$value.word].url}" target="_blank">{/if}
- {if false !== $labelMetadata[$value.word].logo}<img src="{$labelMetadata[$value.word].logo}" width="{$value.logoWidth}" />{else}
- {$value.wordTruncated}{/if}{if false !== $labelMetadata[$value.word].url}</a>{/if}</span>
- {/foreach}
- {/if}
- </div>
- {if $properties.show_footer}
- {include file="CoreHome/templates/datatable_footer.tpl"}
- {/if}
- {include file="CoreHome/templates/datatable_js.tpl"}
+ {if false !== $labelMetadata[$value.word].logo}<img src="{$labelMetadata[$value.word].logo}" width="{$value.logoWidth}" />{else}
+ {$value.wordTruncated}{/if}{if false !== $labelMetadata[$value.word].url}</a>{/if}</span>
+ {/foreach}
+ {/if}
+ </div>
+ {if $properties.show_footer}
+ {include file="CoreHome/templates/datatable_footer.tpl"}
+ {/if}
+ {include file="CoreHome/templates/datatable_js.tpl"}
</div>
diff --git a/plugins/CoreHome/templates/datatable.css b/plugins/CoreHome/templates/datatable.css
index 7975c819f4..c71c2f6a31 100644
--- a/plugins/CoreHome/templates/datatable.css
+++ b/plugins/CoreHome/templates/datatable.css
@@ -1,99 +1,102 @@
-
/*Overriding some dataTable css for better dashboard display*/
-.widget .dataTableWrapper,
+.widget .dataTableWrapper,
.widget .dataTableAllColumnsWrapper,
.widget .dataTableGraphWrapper,
.widget .dataTableActionsWrapper,
.widget .dataTableGraphEvolutionWrapper {
- width: 100%;
+ width: 100%;
}
.widget {
- z-index:1;
+ z-index: 1;
}
.widget p {
- margin-left:10px;
+ margin-left: 10px;
}
+
/* container of each table */
.dataTableWrapper {
- width: 450px;
- /* not more than 450px to make sure 2 tables can fit horizontally on a 1024 screen */
+ width: 450px;
+ /* not more than 450px to make sure 2 tables can fit horizontally on a 1024 screen */
}
+
.dataTableAllColumnsWrapper {
- width: 535px;
+ width: 535px;
}
-.subdataTableWrapper{
- width: 95%;
+.subdataTableWrapper {
+ width: 95%;
}
+
.subdataTableAllColumnsWrapper {
- width: 95%;
+ width: 95%;
}
.dataTableActionsWrapper {
- width: 500px;
- min-height:1px;
+ width: 500px;
+ min-height: 1px;
}
.dataTableGraphWrapper {
- width: 500px;
- min-height:1px;
+ width: 500px;
+ min-height: 1px;
}
.dataTableGraphEvolutionWrapper {
- width: 100%;
+ width: 100%;
}
/* main data table */
table.dataTable {
- border:0;
- width: 100%;
- padding: 0;
- border-spacing: 0;
- margin: 0;
+ border: 0;
+ width: 100%;
+ padding: 0;
+ border-spacing: 0;
+ margin: 0;
}
table.dataTable td.label,
table.subDataTable td.label,
table.dataTableActions td.label {
- width: 100%;
- white-space:nowrap;
+ width: 100%;
+ white-space: nowrap;
}
table.dataTable img,
table.subDataTable img,
table.dataTableActions img {
- vertical-align: middle;
+ vertical-align: middle;
}
+
#UserCountrygetCountry table.dataTable img {
- vertical-align: baseline;
+ vertical-align: baseline;
}
+
table.dataTable img {
- border: 0;
- margin-right: 1em;
- margin-left: 0.5em;
+ border: 0;
+ margin-right: 1em;
+ margin-left: 0.5em;
}
table.dataTable tr.subDataTable {
- cursor: pointer;
+ cursor: pointer;
}
-
-
table.dataTable th {
- margin: 0;
- color: #255792;
- text-align: left;
- padding: 6px 6px 6px 12px;
- background: #e4e2d7;
- font-size:12px;
- font-weight: normal;
- border-left: 1px solid #d4d0c4;
- vertical-align: top;
-}
-table.dataTable th.sortable{
- cursor:pointer;
+ margin: 0;
+ color: #255792;
+ text-align: left;
+ padding: 6px 6px 6px 12px;
+ background: #e4e2d7;
+ font-size: 12px;
+ font-weight: normal;
+ border-left: 1px solid #d4d0c4;
+ vertical-align: top;
+}
+
+table.dataTable th.sortable {
+ cursor: pointer;
}
table.dataTable th.first {
@@ -101,72 +104,73 @@ table.dataTable th.first {
table.dataTable th.last {
}
+
table.dataTable th.columnSorted {
- font-weight: bold;
- padding-right: 20px;
- background: #d5d3c8;
+ font-weight: bold;
+ padding-right: 20px;
+ background: #d5d3c8;
}
table.dataTable td {
- padding: 5px 5px 5px 12px;
- background: #fff;
- border-left:1px solid #e7e7e7;
- border-bottom:1px solid #e7e7e7;
+ padding: 5px 5px 5px 12px;
+ background: #fff;
+ border-left: 1px solid #e7e7e7;
+ border-bottom: 1px solid #e7e7e7;
}
-table.dataTable td,table.dataTable td a {
- margin: 0;
- text-decoration: none;
- color: #444;
+table.dataTable td, table.dataTable td a {
+ margin: 0;
+ text-decoration: none;
+ color: #444;
}
-
-
table.dataTable tr:hover>td,
-table.dataTable tr:hover>td .dataTableRowActions{
- background-color:#FFFFF7;
+table.dataTable tr:hover>td .dataTableRowActions {
+ background-color: #FFFFF7;
}
+
table.dataTable tr.subDataTable:hover>td,
-table.dataTable tr.subDataTable:hover>td .dataTableRowActions{
- background-color:#ffffcb;
+table.dataTable tr.subDataTable:hover>td .dataTableRowActions {
+ background-color: #ffffcb;
}
+
table.dataTable tr:hover>td.cellSubDataTable
-table.dataTable tr:hover>td.cellSubDataTable .dataTableRowActions{
- background-color:#fff;
+table.dataTable tr:hover>td.cellSubDataTable .dataTableRowActions {
+ background-color: #fff;
}
-td.clean{
- background-color:#fff!important;
+td.clean {
+ background-color: #fff !important;
}
-
table.dataTable td.columneven {
- background: #efeeec;
+ background: #efeeec;
}
+
table.dataTable td.columnodd {
- background: #f6f5f3;
+ background: #f6f5f3;
}
table.dataTable td.labeleven {
- background: #F9FAFA url(images/bullet2.gif) no-repeat;
+ background: #F9FAFA url(images/bullet2.gif) no-repeat;
}
table.dataTable td.labelodd {
- background: #fff url(images/bullet1.gif) no-repeat;
+ background: #fff url(images/bullet1.gif) no-repeat;
}
.dataTable tr.highlight td {
- background-color: #ECF9DD;
- font-weight: bold;
+ background-color: #ECF9DD;
+ font-weight: bold;
}
-table.dataTable td.label,table.subActionsDataTable td.label,table.actionsDataTable td.label {
- border-top: 0;
- border-left: 0;
+table.dataTable td.label, table.subActionsDataTable td.label, table.actionsDataTable td.label {
+ border-top: 0;
+ border-left: 0;
}
table.dataTable th.label {
- border-left: 0;
+ border-left: 0;
}
table.dataTableActions th.label {
@@ -175,465 +179,474 @@ table.dataTableActions th.label {
}
table.dataTable span.label.highlighted {
- font-style: italic;
+ font-style: italic;
}
/* the cell containing the subdatatable */
table.dataTable .cellSubDataTable {
- margin: 0;
- border-left:0;
+ margin: 0;
+ border-left: 0;
}
/* A link in a column in the DataTable */
table.dataTable td #urlLink {
- display: none;
+ display: none;
}
-/* SUBDATATABLE */ /* a datatable inside another datatable */
+/* SUBDATATABLE */
+/* a datatable inside another datatable */
table.subDataTable {
- background: #FFFFFF;
- margin: 5px 10px;
+ background: #FFFFFF;
+ margin: 5px 10px;
}
table.subDataTable td {
- border: 0;
+ border: 0;
}
table.subDataTable thead th {
- font-weight: normal;
- font-size: 12px;
- text-align: left;
- padding: .3em 1em;
- background: #f1f0eb;
- border: 0;
- border-top: 1px solid #e7e7e7;
- border-bottom: 1px solid #e7e7e7;
+ font-weight: normal;
+ font-size: 12px;
+ text-align: left;
+ padding: .3em 1em;
+ background: #f1f0eb;
+ border: 0;
+ border-top: 1px solid #e7e7e7;
+ border-bottom: 1px solid #e7e7e7;
}
-
table.subDataTable th.columnSorted {
- background: #e3e1d9;
+ background: #e3e1d9;
}
-table.subDataTable td.labeleven,table.subDataTable td.labelodd {
- background-image: none;
+table.subDataTable td.labeleven, table.subDataTable td.labelodd {
+ background-image: none;
}
table.subDataTable td {
- border-bottom: 1px solid #e7e7e7;
- border-left: 0;
+ border-bottom: 1px solid #e7e7e7;
+ border-left: 0;
}
-table.subDataTable td,table.subDataTable td a {
- color: #615B53;
+table.subDataTable td, table.subDataTable td a {
+ color: #615B53;
}
-table.subDataTable td.labeleven,table.subDataTable td.columneven {
- color: #2D2A27;
+table.subDataTable td.labeleven, table.subDataTable td.columneven {
+ color: #2D2A27;
}
table.subDataTable td.label {
- width: 80%;
+ width: 80%;
}
-table.subDataTable td.labelodd,table.subDataTable td.labelodd a {
- background: #fff;
+table.subDataTable td.labelodd, table.subDataTable td.labelodd a {
+ background: #fff;
}
+
table.subDataTable td.label {
- padding: 5px;
+ padding: 5px;
}
+
table.dataTable img {
- margin-left:0;
+ margin-left: 0;
}
+
/* misc SPAN and DIV */
table thead div {
-
+
}
#sortIconContainer {
- float: right;
- position: relative;
+ float: right;
+ position: relative;
}
#sortIcon {
- margin: 0;
- position: absolute;
+ margin: 0;
+ position: absolute;
}
.datatableFooterMessage {
- color: #888;
- text-align:left;
- margin: 10px;
+ color: #888;
+ text-align: left;
+ margin: 10px;
}
.dataTablePages {
- color: #BFBFBF;
- font-weight: bold;
- margin: 10px;
- font-size: 12px;
+ color: #BFBFBF;
+ font-weight: bold;
+ margin: 10px;
+ font-size: 12px;
}
.dataTableSearchPattern {
- margin:8px 0 0 0;
- height:30px;
- display: block!important;
- white-space: nowrap;
- background: url(../../../themes/default/images/search_bg.png) no-repeat center 0;
- text-align:center;
+ margin: 8px 0 0 0;
+ height: 30px;
+ display: block !important;
+ white-space: nowrap;
+ background: url(../../../themes/default/images/search_bg.png) no-repeat center 0;
+ text-align: center;
}
.dataTableSearchPattern input {
- vertical-align:top;
- font-size:10px!important;
- color:#454545!important;
- border:0!important;
- background:transparent!important;
- width:21px;
- height:17px;
- overflow:hidden;
- opacity:0;
- filter:Alpha(opacity:0);
- cursor:pointer;
+ vertical-align: top;
+ font-size: 10px !important;
+ color: #454545 !important;
+ border: 0 !important;
+ background: transparent !important;
+ width: 21px;
+ height: 17px;
+ overflow: hidden;
+ opacity: 0;
+ filter: Alpha(opacity:0);
+ cursor: pointer;
}
.dataTableSearchPattern input:hover {
-
+
}
.dataTableSearchPattern #keyword {
- width:114px;
- height:auto;
- overflow:visible;
- padding:2px 6px;
- opacity:1;
- cursor:text;
- filter:Alpha(opacity:100);
+ width: 114px;
+ height: auto;
+ overflow: visible;
+ padding: 2px 6px;
+ opacity: 1;
+ cursor: text;
+ filter: Alpha(opacity:100);
}
-.dataTableNext,.dataTablePrevious {
- font-size: 12px;
- color: #184A83;
- cursor: pointer;
+.dataTableNext, .dataTablePrevious {
+ font-size: 12px;
+ color: #184A83;
+ cursor: pointer;
}
.datatableRelatedReports {
- color: #888;
- font-size: 11px;
+ color: #888;
+ font-size: 11px;
padding-bottom: 5px;
}
.datatableRelatedReports span {
- cursor: pointer;
- font-weight:bold;
+ cursor: pointer;
+ font-weight: bold;
}
/* are the following two supposed to be together? */
.subDataTable.dataTableFeatures {
- padding-top: 0;
- padding-bottom: 5px;
+ padding-top: 0;
+ padding-bottom: 5px;
}
+
.dataTableFeatures {
- padding-top: 10px;
- text-align: center;
+ padding-top: 10px;
+ text-align: center;
}
-.dataTableNext,.dataTablePrevious,.dataTableSearchPattern
-{
- display: none;
+.dataTableNext, .dataTablePrevious, .dataTableSearchPattern {
+ display: none;
}
+
.dataTableFeatures .loadingPiwik {
- font-size:0.9em;
+ font-size: 0.9em;
}
+
.subDataTable .dataTableFooterIcons {
- height: 0;
+ height: 0;
}
.dataTable .loadingPiwikBelow {
- padding-bottom:5px;
- display:block;
- text-align:center;
+ padding-bottom: 5px;
+ display: block;
+ text-align: center;
}
.dataTableFooterIcons {
- display:block;
- height: 20px;
- white-space:nowrap;
- font-size:10px;
- padding:6px 5px;
- border-top:1px solid #B6B0A6;
+ display: block;
+ height: 20px;
+ white-space: nowrap;
+ font-size: 10px;
+ padding: 6px 5px;
+ border-top: 1px solid #B6B0A6;
}
-.dataTableFooterWrap{
- position:relative;
+.dataTableFooterWrap {
+ position: relative;
float: left;
}
.dataTableFooterWrap select {
- float: left;
- margin: 1px 0 1px 10px;
+ float: left;
+ margin: 1px 0 1px 10px;
}
-.tableIcon{
- background:#f2f1ed;
- display:inline-block;
- float:left;
- margin:0 1px 0 0;
- padding:2px;
- border-radius:2px;
- -moz-border-radius:2px;
- -webkit-border-radius:2px;
+.tableIcon {
+ background: #f2f1ed;
+ display: inline-block;
+ float: left;
+ margin: 0 1px 0 0;
+ padding: 2px;
+ border-radius: 2px;
+ -moz-border-radius: 2px;
+ -webkit-border-radius: 2px;
}
-.tableIcon:hover{
- background:#e9e8e1;
+
+.tableIcon:hover {
+ background: #e9e8e1;
}
-.activeIcon{
- background:#e9e8e1;
+.activeIcon {
+ background: #e9e8e1;
}
-.dataTableFooterActiveItem{
- position:absolute;
- top:-6px;
- left:0;
+.dataTableFooterActiveItem {
+ position: absolute;
+ top: -6px;
+ left: 0;
}
-.exportToFormatItems{
- background:#dcdacf;
- float:left;
- margin:0 1px 0 0;
- padding:4px 6px 3px 6px;
- color: #968d7f;
- border-radius:2px;
- -moz-border-radius:2px;
- -webkit-border-radius:2px;
+.exportToFormatItems {
+ background: #dcdacf;
+ float: left;
+ margin: 0 1px 0 0;
+ padding: 4px 6px 3px 6px;
+ color: #968d7f;
+ border-radius: 2px;
+ -moz-border-radius: 2px;
+ -webkit-border-radius: 2px;
}
-.exportToFormatItems img{
- vertical-align:middle;
- margin:-4px -3px -2px 2px;
+.exportToFormatItems img {
+ vertical-align: middle;
+ margin: -4px -3px -2px 2px;
}
-.tableIconsGroup{
- float:left;
- padding-right:4px;
+.tableIconsGroup {
+ float: left;
+ padding-right: 4px;
}
+
.tableIconsGroup .tableIcon span {
- margin-right:5px;
- margin-left:5px;
+ margin-right: 5px;
+ margin-left: 5px;
}
-.tableIconsGroup img{
- vertical-align:bottom;
+
+.tableIconsGroup img {
+ vertical-align: bottom;
}
-.tableIconsGroupActive{
- display:block;
- float:left;
- background:#dcdacf;
- border-radius:2px;
- -moz-border-radius:2px;
- -webkit-border-radius:2px;
+.tableIconsGroupActive {
+ display: block;
+ float: left;
+ background: #dcdacf;
+ border-radius: 2px;
+ -moz-border-radius: 2px;
+ -webkit-border-radius: 2px;
}
-.tableIconsGroupActive .tableIcon{
- background:none;
+
+.tableIconsGroupActive .tableIcon {
+ background: none;
}
-.tableIconsGroupActive .tableIcon:hover{
- background:#e9e8e1;
+
+.tableIconsGroupActive .tableIcon:hover {
+ background: #e9e8e1;
}
.exportToFormatIcons {
- float: left;
+ float: left;
}
.dataTableFooterIconsShow {
- float: left;
+ float: left;
}
-.dataTableFooterIcons,.dataTableFooterIcons a {
- text-decoration: none;
- color: #255792;
+.dataTableFooterIcons, .dataTableFooterIcons a {
+ text-decoration: none;
+ color: #255792;
}
.dataTableSpacer {
- clear: both;
+ clear: both;
}
/* Actions table */
table.dataTableActions tr td.labelodd {
- background-image: none;
+ background-image: none;
}
-
/* levels higher than 4 have a default padding left */
-tr.subActionsDataTable td.label,tr.actionsDataTable td.label {
- padding-left: 7em;
+tr.subActionsDataTable td.label, tr.actionsDataTable td.label {
+ padding-left: 7em;
}
-tr.subActionsDataTable{
- cursor:pointer;
+tr.subActionsDataTable {
+ cursor: pointer;
}
tr.level0 td.label {
- padding-left: 1.5em;
+ padding-left: 1.5em;
}
tr.level1 td.label {
- padding-left: 2.5em;
+ padding-left: 2.5em;
}
tr.level2 td.label {
- padding-left: 3.5em;
+ padding-left: 3.5em;
}
tr.level3 td.label {
- padding-left: 4.5em;
+ padding-left: 4.5em;
}
tr.level4 td.label {
- padding-left: 5em;
+ padding-left: 5em;
}
/* less right margins for the link image in the Pa*/
table.dataTableActions img.link {
- margin-right: 0.3em;
- margin-left:-0.5em;
+ margin-right: 0.3em;
+ margin-left: -0.5em;
}
+
tr td.label img.plusMinus {
- margin-left:-1em;
- margin-right: 3px;
+ margin-left: -1em;
+ margin-right: 3px;
}
.pk-emptyDataTable {
- padding-top: 20px;
- padding-bottom: 10px;
- text-align: center;
- font-style: italic;
+ padding-top: 20px;
+ padding-bottom: 10px;
+ text-align: center;
+ font-style: italic;
}
-
/* Documentation */
table.dataTable th .columnDocumentation {
- display: none;
- width: 165px;
- text-align: left;
- background: #f7f7f7;
- color: #444;
- font-size: 11px;
- font-weight: normal;
- border: 1px solid #e4e5e4;
- padding: 5px 10px 6px 10px;
- border-radius: 4px;
- -moz-border-radius: 4px;
- -webkit-border-radius: 4px;
- z-index: 125;
- position: absolute;
- -moz-box-shadow: 0 0 4px #e4e5e4;
- -webkit-box-shadow: 0 0 4px #e4e5e4;
- box-shadow: 0 0 4px #e4e5e4;
- cursor: default;
+ display: none;
+ width: 165px;
+ text-align: left;
+ background: #f7f7f7;
+ color: #444;
+ font-size: 11px;
+ font-weight: normal;
+ border: 1px solid #e4e5e4;
+ padding: 5px 10px 6px 10px;
+ border-radius: 4px;
+ -moz-border-radius: 4px;
+ -webkit-border-radius: 4px;
+ z-index: 125;
+ position: absolute;
+ -moz-box-shadow: 0 0 4px #e4e5e4;
+ -webkit-box-shadow: 0 0 4px #e4e5e4;
+ box-shadow: 0 0 4px #e4e5e4;
+ cursor: default;
}
table.dataTable th .columnDocumentationTitle {
- background: url(../../../themes/default/images/help.png) no-repeat;
- line-height: 14px;
- padding: 2px 0 3px 21px;
- font-weight: bold;
+ background: url(../../../themes/default/images/help.png) no-repeat;
+ line-height: 14px;
+ padding: 2px 0 3px 21px;
+ font-weight: bold;
}
.reportDocumentation {
- display: none;
- background: #f7f7f7;
- font-size: 12px;
- font-weight: normal;
- border: 1px solid #e4e5e4;
- margin: 0 0 10px 0;
- padding: 0;
- border-radius: 4px;
- -moz-border-radius: 4px;
- -webkit-border-radius: 4px;
- max-width: 500px;
+ display: none;
+ background: #f7f7f7;
+ font-size: 12px;
+ font-weight: normal;
+ border: 1px solid #e4e5e4;
+ margin: 0 0 10px 0;
+ padding: 0;
+ border-radius: 4px;
+ -moz-border-radius: 4px;
+ -webkit-border-radius: 4px;
+ max-width: 500px;
}
.reportDocumentation p {
- padding: 5px 10px 6px 10px;
- margin: 0;
- color: #444;
- font-size: 12px;
- }
-
+ padding: 5px 10px 6px 10px;
+ margin: 0;
+ color: #444;
+ font-size: 12px;
+}
+
.reportDocumentationIcon {
- display: block;
- width: 16px;
- height: 16px;
- margin: 10px 0;
- background: url(../../../themes/default/images/help.png) no-repeat;
+ display: block;
+ width: 16px;
+ height: 16px;
+ margin: 10px 0;
+ background: url(../../../themes/default/images/help.png) no-repeat;
}
h2 .reportDocumentationIcon {
- position: absolute;
- margin: 4px 0 0 0;
- display: none;
- background: url(../../../themes/default/images/help_grey.png) no-repeat;
+ position: absolute;
+ margin: 4px 0 0 0;
+ display: none;
+ background: url(../../../themes/default/images/help_grey.png) no-repeat;
}
h2 .reportDocumentationIcon.hidden {
- background: none;
+ background: none;
}
.helpDate {
- color: #777777;
+ color: #777777;
font-size: 11px;
- font-style:italic;
+ font-style: italic;
padding: 4px;
text-align: right;
- display:block;
+ display: block;
}
table.dataTable .dataTableRowActions {
- position: absolute;
- display: none;
- overflow: hidden;
- margin-top: -5px;
+ position: absolute;
+ display: none;
+ overflow: hidden;
+ margin-top: -5px;
}
+
*+html table.dataTable .dataTableRowActions {
- margin-top: -7px;
+ margin-top: -7px;
}
table.dataTable .dataTableRowActions a {
- display: block;
- float: left;
- padding: 6px 4px 6px 0;
- margin: 0;
+ display: block;
+ float: left;
+ padding: 6px 4px 6px 0;
+ margin: 0;
}
table.dataTable .dataTableRowActions a.leftmost {
- padding-left: 4px;
+ padding-left: 4px;
}
table.dataTable .dataTableRowActions a.rightmost {
- padding-right: 8px;
+ padding-right: 8px;
}
table.dataTable .dataTableRowActions a img {
- margin: 0;
- padding: 0;
- border: 0;
- width: 20px;
- height: 17px;
+ margin: 0;
+ padding: 0;
+ border: 0;
+ width: 20px;
+ height: 17px;
}
body .piwik-tooltip.rowActionTooltip {
- font-size: 11px;
- padding: 3px 5px 3px 6px;
+ font-size: 11px;
+ padding: 3px 5px 3px 6px;
}
-
.limitSelection {
float: right;
position: relative;
margin-left: 5px;
min-height: 20px;
- z-index:1;
+ z-index: 1;
}
.limitSelection.hidden {
@@ -656,7 +669,7 @@ body .piwik-tooltip.rowActionTooltip {
.limitSelection.disabled div {
opacity: 0.5;
cursor: not-allowed;
- filter:Alpha(opacity:50);
+ filter: Alpha(opacity:50);
}
.limitSelection.visible div {
@@ -703,46 +716,45 @@ body .piwik-tooltip.rowActionTooltip {
display: inline-block;
}
-
.tableConfiguration {
float: right;
position: relative;
margin-left: 5px;
min-height: 20px;
- min-width: 25px;
+ min-width: 25px;
}
a.tableConfigurationIcon {
- display: block;
- width: 30px;
- height: 22px;
- background: url(../../../themes/default/images/configure.png) no-repeat center 2px;
- position: absolute;
- z-index: 9;
- right: 0;
+ display: block;
+ width: 30px;
+ height: 22px;
+ background: url(../../../themes/default/images/configure.png) no-repeat center 2px;
+ position: absolute;
+ z-index: 9;
+ right: 0;
}
a.tableConfigurationIcon.highlighted {
- display: block;
- width: 30px;
- height: 22px;
- background-image: url(../../../themes/default/images/configure-highlight.png);
- position: absolute;
- z-index: 9;
- right: 0;
+ display: block;
+ width: 30px;
+ height: 22px;
+ background-image: url(../../../themes/default/images/configure-highlight.png);
+ position: absolute;
+ z-index: 9;
+ right: 0;
}
.tableConfiguration ul {
overflow: visible;
background-color: #fff;
- display: none;
- position: relative;
- z-index: 8;
- text-align: left;
+ display: none;
+ position: relative;
+ z-index: 8;
+ text-align: left;
}
.tableConfiguration ul.open {
- display: block;
+ display: block;
}
.tableConfiguration ul li {
@@ -761,13 +773,13 @@ a.tableConfigurationIcon.highlighted {
border-radius: 0 0 4px 4px;
-moz-border-radius: 0 0 4px 4px;
-webkit-border-radius: 0 0 4px 4px;
- height: 25px;
- cursor: default;
- margin-top: -4px;
+ height: 25px;
+ cursor: default;
+ margin-top: -4px;
}
.tableConfiguration ul li.first {
- margin-top: -65px;
+ margin-top: -65px;
}
.tableConfiguration ul li.last {
@@ -780,8 +792,8 @@ a.tableConfigurationIcon.highlighted {
.tableConfiguration div.configItem {
cursor: pointer;
padding: 5px 10px;
- line-height: 15px;
- color: #444;
+ line-height: 15px;
+ color: #444;
}
.tableConfiguration div.configItem:hover {
@@ -789,5 +801,5 @@ a.tableConfigurationIcon.highlighted {
}
.tableConfiguration div.configItem span.action {
- color: #255792;
+ color: #255792;
}
diff --git a/plugins/CoreHome/templates/datatable.js b/plugins/CoreHome/templates/datatable.js
index 755ddcbbf1..1476a2e711 100644
--- a/plugins/CoreHome/templates/datatable.js
+++ b/plugins/CoreHome/templates/datatable.js
@@ -10,150 +10,129 @@
//-----------------------------------------------------------------------------
//DataTable constructor
-function dataTable()
-{
- this.param = {};
+function dataTable() {
+ this.param = {};
}
//Prototype of the DataTable object
dataTable.prototype =
{
- //initialisation function
- init: function(workingDivId, domElem)
- {
- if(typeof domElem == "undefined")
- {
- domElem = $('#'+workingDivId);
- }
-
- this.workingDivId = workingDivId;
- this.loadedSubDataTable = {};
- this.isEmpty = $('.pk-emptyDataTable', domElem).length > 0;
- this.bindEventsAndApplyStyle(domElem);
- this.initialized = true;
-
- domElem.data('piwikDataTable', this);
- },
-
- //function triggered when user click on column sort
- onClickSort: function(domElem)
- {
- var self = this;
- var newColumnToSort = $(domElem).attr('id');
- // we lookup if the column to sort was already this one, if it is the case then we switch from desc <-> asc
- if(self.param.filter_sort_column == newColumnToSort)
- {
- // toggle the sorted order
- if(this.param.filter_sort_order == 'asc')
- {
- self.param.filter_sort_order = 'desc';
- }
- else
- {
- self.param.filter_sort_order = 'asc';
- }
- }
- self.param.filter_offset = 0;
- self.param.filter_sort_column = newColumnToSort;
- self.reloadAjaxDataTable();
- },
-
- setGraphedColumn: function( columnName )
- {
- this.param.columns = columnName;
- },
-
- //Reset DataTable filters (used before a reload or view change)
- resetAllFilters: function()
- {
- var self = this;
- var FiltersToRestore = new Array();
- filters = [
- 'filter_column',
- 'filter_pattern',
- 'filter_column_recursive',
- 'filter_pattern_recursive',
- 'enable_filter_excludelowpop',
- 'filter_offset',
- 'filter_sort_column',
- 'filter_sort_order',
- 'disable_generic_filters',
- 'columns',
- 'flat',
- 'include_aggregate_rows'
- ];
-
- for(var key in filters)
- {
- var value = filters[key];
- FiltersToRestore[value] = self.param[value];
- delete self.param[value];
- }
-
- return FiltersToRestore;
- },
-
- //Restores the filters to the values given in the array in parameters
- restoreAllFilters: function(FiltersToRestore)
- {
- var self = this;
- for(key in FiltersToRestore)
- {
- self.param[key] = FiltersToRestore[key];
- }
- },
-
- //Translate string parameters to javascript builtins
- //'true' -> true, 'false' -> false
- //it simplifies condition tests in the code
- cleanParams: function()
- {
- var self = this;
- for(var key in self.param)
- {
- if(self.param[key] == 'true') self.param[key]=true;
- if(self.param[key] == 'false') self.param[key]=false;
- }
- },
-
- // Function called to trigger the AJAX request
- // The ajax request contains the function callback to trigger if the request is successful or failed
- // displayLoading = false When we don't want to display the Loading... DIV .loadingPiwik
- // for example when the script add a Loading... it self and doesn't want to display the generic Loading
- reloadAjaxDataTable: function(displayLoading, callbackSuccess)
- {
- var self = this;
-
- if (typeof displayLoading == "undefined")
- {
- displayLoading = true;
- }
- if (typeof callbackSuccess == "undefined")
- {
- callbackSuccess = function (response)
- {
- self.dataTableLoaded(response, self.workingDivId);
- };
- }
-
- if(displayLoading)
- {
- $('#'+self.workingDivId+' .loadingPiwik').last().css('display','block');
- }
-
- // when switching to display graphs, reset limit
- if (self.param.viewDataTable && self.param.viewDataTable.indexOf('graph') === 0)
- {
- delete self.param.filter_offset;
- delete self.param.filter_limit;
- }
-
- var container = $('#'+self.workingDivId+' .piwik-graph');
+ //initialisation function
+ init: function (workingDivId, domElem) {
+ if (typeof domElem == "undefined") {
+ domElem = $('#' + workingDivId);
+ }
+
+ this.workingDivId = workingDivId;
+ this.loadedSubDataTable = {};
+ this.isEmpty = $('.pk-emptyDataTable', domElem).length > 0;
+ this.bindEventsAndApplyStyle(domElem);
+ this.initialized = true;
+
+ domElem.data('piwikDataTable', this);
+ },
+
+ //function triggered when user click on column sort
+ onClickSort: function (domElem) {
+ var self = this;
+ var newColumnToSort = $(domElem).attr('id');
+ // we lookup if the column to sort was already this one, if it is the case then we switch from desc <-> asc
+ if (self.param.filter_sort_column == newColumnToSort) {
+ // toggle the sorted order
+ if (this.param.filter_sort_order == 'asc') {
+ self.param.filter_sort_order = 'desc';
+ }
+ else {
+ self.param.filter_sort_order = 'asc';
+ }
+ }
+ self.param.filter_offset = 0;
+ self.param.filter_sort_column = newColumnToSort;
+ self.reloadAjaxDataTable();
+ },
+
+ setGraphedColumn: function (columnName) {
+ this.param.columns = columnName;
+ },
+
+ //Reset DataTable filters (used before a reload or view change)
+ resetAllFilters: function () {
+ var self = this;
+ var FiltersToRestore = new Array();
+ filters = [
+ 'filter_column',
+ 'filter_pattern',
+ 'filter_column_recursive',
+ 'filter_pattern_recursive',
+ 'enable_filter_excludelowpop',
+ 'filter_offset',
+ 'filter_sort_column',
+ 'filter_sort_order',
+ 'disable_generic_filters',
+ 'columns',
+ 'flat',
+ 'include_aggregate_rows'
+ ];
+
+ for (var key in filters) {
+ var value = filters[key];
+ FiltersToRestore[value] = self.param[value];
+ delete self.param[value];
+ }
+
+ return FiltersToRestore;
+ },
+
+ //Restores the filters to the values given in the array in parameters
+ restoreAllFilters: function (FiltersToRestore) {
+ var self = this;
+ for (key in FiltersToRestore) {
+ self.param[key] = FiltersToRestore[key];
+ }
+ },
+
+ //Translate string parameters to javascript builtins
+ //'true' -> true, 'false' -> false
+ //it simplifies condition tests in the code
+ cleanParams: function () {
+ var self = this;
+ for (var key in self.param) {
+ if (self.param[key] == 'true') self.param[key] = true;
+ if (self.param[key] == 'false') self.param[key] = false;
+ }
+ },
+
+ // Function called to trigger the AJAX request
+ // The ajax request contains the function callback to trigger if the request is successful or failed
+ // displayLoading = false When we don't want to display the Loading... DIV .loadingPiwik
+ // for example when the script add a Loading... it self and doesn't want to display the generic Loading
+ reloadAjaxDataTable: function (displayLoading, callbackSuccess) {
+ var self = this;
+
+ if (typeof displayLoading == "undefined") {
+ displayLoading = true;
+ }
+ if (typeof callbackSuccess == "undefined") {
+ callbackSuccess = function (response) {
+ self.dataTableLoaded(response, self.workingDivId);
+ };
+ }
+
+ if (displayLoading) {
+ $('#' + self.workingDivId + ' .loadingPiwik').last().css('display', 'block');
+ }
+
+ // when switching to display graphs, reset limit
+ if (self.param.viewDataTable && self.param.viewDataTable.indexOf('graph') === 0) {
+ delete self.param.filter_offset;
+ delete self.param.filter_limit;
+ }
+
+ var container = $('#' + self.workingDivId + ' .piwik-graph');
var params = {};
- for(var key in self.param)
- {
- if(typeof self.param[key] != "undefined" && self.param[key] != '')
+ for (var key in self.param) {
+ if (typeof self.param[key] != "undefined" && self.param[key] != '')
params[key] = self.param[key];
}
@@ -168,1511 +147,1373 @@ dataTable.prototype =
);
ajaxRequest.setFormat('html');
ajaxRequest.send(false);
- },
-
- // Function called when the AJAX request is successful
- // it looks for the ID of the response and replace the very same ID
- // in the current page with the AJAX response
- dataTableLoaded: function(response, workingDivId)
- {
- var content = $(response);
-
- var idToReplace = workingDivId || $(content).attr('id');
- var dataTableSel = $('#'+idToReplace);
-
- // keep the original list of related reports
- var oldReportsElem = $('.datatableRelatedReports', dataTableSel);
- $('.datatableRelatedReports', content).replaceWith(oldReportsElem);
-
- // if the current dataTable is located inside another datatable
- table = $(content).parents('table.dataTable');
- if(dataTableSel.parents('.dataTable').is('table'))
- {
- // we add class to the table so that we can give a different style to the subtable
- $(content).find('table.dataTable').addClass('subDataTable');
- $(content).find('.dataTableFeatures').addClass('subDataTable');
-
- //we force the initialisation of subdatatables
- dataTableSel.replaceWith(content);
- }
- else
- {
- dataTableSel.find('object').remove();
- dataTableSel.replaceWith(content);
- }
-
- piwikHelper.lazyScrollTo(content[0], 400);
-
- return content;
- },
-
- /* This method is triggered when a new DIV is loaded, which happens
- - at the first loading of the page
- - after any AJAX loading of a DataTable
-
- This method basically add features to the DataTable,
- - such as column sorting, searching in the rows, displaying Next / Previous links, etc.
- - add styles to the cells and rows (odd / even styles)
- - modify some rows to add images if a span img is found, or add a link if a span urlLink is found
- or truncate the labels when they are too big
- - bind new events onclick / hover / etc. to trigger AJAX requests,
- nice hovertip boxes for truncated cells
- */
- bindEventsAndApplyStyle: function(domElem)
- {
- var self = this;
- self.cleanParams();
- self.handleSort(domElem);
- self.handleLimit(domElem);
- self.handleSearchBox(domElem);
- self.handleOffsetInformation(domElem);
- self.handleAnnotationsButton(domElem);
- self.handleEvolutionAnnotations(domElem);
- self.handleExportBox(domElem);
- self.applyCosmetics(domElem);
- self.handleSubDataTable(domElem);
- self.handleConfigurationBox(domElem);
- self.handleColumnDocumentation(domElem);
- self.handleReportDocumentation(domElem);
- self.handleRowActions(domElem);
- self.handleRelatedReports(domElem);
- self.handleTriggeredEvents(domElem);
- },
-
- handleLimit: function(domElem)
- {
- var tableRowLimits = [5, 10, 25, 50, 100, 250, 500],
- evolutionLimits =
- {
- day: [30, 60, 90, 180, 365, 500],
- week: [4, 12, 26, 52, 104, 500],
- month: [3, 6, 12, 24, 36, 120],
- year: [3, 5, 10]
- };
-
- var self = this;
- if( typeof self.parentId != "undefined" && self.parentId != '')
- {
- // no limit selector for subtables
- $('.limitSelection', domElem).remove();
- return;
- }
-
- // configure limit control
- var setLimitValue, numbers, limitParamName;
- if (self.param.viewDataTable == 'graphEvolution')
- {
- limitParamName = 'evolution_' + self.param.period + '_last_n';
- numbers = evolutionLimits[self.param.period] || tableRowLimits;
-
- setLimitValue = function (params, limit)
- {
- params[limitParamName] = limit;
- };
- }
- else
- {
- numbers = tableRowLimits;
- limitParamName = 'filter_limit';
-
- setLimitValue = function (params, value)
- {
- params.filter_limit = value;
- params.filter_offset = 0;
- };
- }
-
- // setup limit control
- $('.limitSelection', domElem).append('<div><span>'+self.param[limitParamName]+'</span></div><ul></ul>');
-
- if (self.param.viewDataTable == 'table'
- || self.param.viewDataTable == 'tableAllColumns'
- || self.param.viewDataTable == 'tableGoals'
- || self.param.viewDataTable == 'ecommerceOrder'
- || self.param.viewDataTable == 'ecommerceAbandonedCart'
- || self.param.viewDataTable == 'graphEvolution')
- {
- $('.limitSelection ul', domElem).hide();
- for(var i=0; i<numbers.length; i++)
- {
- $('.limitSelection ul', domElem).append('<li value="'+numbers[i]+'"><span>'+numbers[i]+'</span></li>');
- }
- $('.limitSelection ul li:last', domElem).addClass('last');
-
- if (!self.isEmpty)
- {
- var show = function() {
- $('.limitSelection ul', domElem).show();
- $('.limitSelection', domElem).addClass('visible');
- $(document).on('mouseup.limitSelection',function(e) {
- if ((!$(e.target).parents('.limitSelection').length
- || $(e.target).parents('.limitSelection') != $('.limitSelection', domElem))
- && !$(e.target).is('.limitSelection'))
- {
- hide();
- }
- });
- }
- var hide = function() {
- $('.limitSelection ul', domElem).hide();
- $('.limitSelection', domElem).removeClass('visible');
- $(document).off('mouseup.limitSelection');
- }
- $('.limitSelection div', domElem).on('click', function(){
- $('.limitSelection', domElem).is('.visible') ? hide() : show();
- });
- $('.limitSelection ul li', domElem).on('click', function(event){
- var limit = parseInt($(event.target).text());
-
- hide();
- if (limit != self.param[limitParamName])
- {
- setLimitValue(self.param, limit);
- $('.limitSelection>div>span', domElem).text(limit);
- self.reloadAjaxDataTable();
-
- var data = {};
- data[limitParamName] = self.param[limitParamName];
- self.notifyWidgetParametersChange(domElem, data);
- }
- });
- }
- else
- {
- $('.limitSelection', domElem).toggleClass('disabled');
- }
- }
- else
- {
- $('.limitSelection', domElem).hide();
- }
- },
-
- // if sorting the columns is enabled, when clicking on a column,
- // - if this column was already the one used for sorting, we revert the order desc<->asc
- // - we send the ajax request with the new sorting information
- handleSort: function(domElem)
- {
- var self = this;
-
- function getSortImageSrc() {
- var imageSortSrc = false;
- if (currentIsSubDataTable) {
- if (self.param.filter_sort_order == 'asc') {
- imageSortSrc = 'themes/default/images/sort_subtable_asc.png';
- } else {
- imageSortSrc = 'themes/default/images/sort_subtable_desc.png';
+ },
+
+ // Function called when the AJAX request is successful
+ // it looks for the ID of the response and replace the very same ID
+ // in the current page with the AJAX response
+ dataTableLoaded: function (response, workingDivId) {
+ var content = $(response);
+
+ var idToReplace = workingDivId || $(content).attr('id');
+ var dataTableSel = $('#' + idToReplace);
+
+ // keep the original list of related reports
+ var oldReportsElem = $('.datatableRelatedReports', dataTableSel);
+ $('.datatableRelatedReports', content).replaceWith(oldReportsElem);
+
+ // if the current dataTable is located inside another datatable
+ table = $(content).parents('table.dataTable');
+ if (dataTableSel.parents('.dataTable').is('table')) {
+ // we add class to the table so that we can give a different style to the subtable
+ $(content).find('table.dataTable').addClass('subDataTable');
+ $(content).find('.dataTableFeatures').addClass('subDataTable');
+
+ //we force the initialisation of subdatatables
+ dataTableSel.replaceWith(content);
}
- } else {
- if (self.param.filter_sort_order == 'asc') {
- imageSortSrc = 'themes/default/images/sortasc.png';
- } else {
- imageSortSrc = 'themes/default/images/sortdesc.png';
+ else {
+ dataTableSel.find('object').remove();
+ dataTableSel.replaceWith(content);
}
- }
- return imageSortSrc;
- }
- if( self.param.enable_sort )
- {
- $('.sortable', domElem).off('click.dataTableSort').on('click.dataTableSort',
- function()
- {
- $(this).off('click.dataTableSort');
- self.onClickSort(this);
- }
- );
-
- if (self.param.filter_sort_column != '')
- {
- // are we in a subdatatable?
- var currentIsSubDataTable = $(domElem).parent().hasClass('cellSubDataTable');
- var imageSortSrc = getSortImageSrc();
- var imageSortWidth = 16;
- var imageSortHeight = 16;
- // we change the style of the column currently used as sort column
- // adding an image and the class columnSorted to the TD
- $(".sortable#"+self.param.filter_sort_column+' #thDIV', domElem).parent()
- .addClass('columnSorted')
- .prepend('<div id="sortIconContainer"><img id="sortIcon" width="'+imageSortWidth+'" height="'+imageSortHeight+'" src="'+ imageSortSrc +'" /></div>');
- }
- }
- },
-
- //behaviour for the DataTable 'search box'
- handleSearchBox: function(domElem, callbackSuccess)
- {
- var self = this;
-
- var currentPattern = self.param.filter_pattern;
- if(typeof self.param.filter_pattern != "undefined"
- && self.param.filter_pattern.length > 0)
- {
- currentPattern = self.param.filter_pattern;
- }
- else if(typeof self.param.filter_pattern_recursive != "undefined"
- && self.param.filter_pattern_recursive.length > 0)
- {
- currentPattern = self.param.filter_pattern_recursive;
- }
- else
- {
- currentPattern = '';
- }
- currentPattern = piwikHelper.htmlDecode(currentPattern);
-
- $('.dataTableSearchPattern', domElem)
- .show()
- .each(function(){
- // when enter is pressed in the input field we submit the form
- $('#keyword', this)
- .on("keyup",
- function(e)
- {
- if(isEnterKey(e))
- {
- $(this).siblings(':submit').submit();
- }
- }
- )
- .val(currentPattern)
- ;
-
- $(':submit', this).submit(
- function()
- {
- var keyword = $(this).siblings('#keyword').val();
- self.param.filter_offset = 0;
-
- if(self.param.search_recursive)
- {
- self.param.filter_column_recursive = 'label';
- self.param.filter_pattern_recursive = keyword;
- }
- else
- {
- self.param.filter_column = 'label';
- self.param.filter_pattern = keyword;
- }
- self.reloadAjaxDataTable(true, callbackSuccess);
- }
- );
-
- $(':submit', this)
- .click( function(){ $(this).submit(); })
- ;
-
- // in the case there is a searched keyword we display the RESET image
- if(currentPattern)
- {
- var target = this;
- var clearImg = $('<span style="position: relative;">\
+ piwikHelper.lazyScrollTo(content[0], 400);
+
+ return content;
+ },
+
+ /* This method is triggered when a new DIV is loaded, which happens
+ - at the first loading of the page
+ - after any AJAX loading of a DataTable
+
+ This method basically add features to the DataTable,
+ - such as column sorting, searching in the rows, displaying Next / Previous links, etc.
+ - add styles to the cells and rows (odd / even styles)
+ - modify some rows to add images if a span img is found, or add a link if a span urlLink is found
+ or truncate the labels when they are too big
+ - bind new events onclick / hover / etc. to trigger AJAX requests,
+ nice hovertip boxes for truncated cells
+ */
+ bindEventsAndApplyStyle: function (domElem) {
+ var self = this;
+ self.cleanParams();
+ self.handleSort(domElem);
+ self.handleLimit(domElem);
+ self.handleSearchBox(domElem);
+ self.handleOffsetInformation(domElem);
+ self.handleAnnotationsButton(domElem);
+ self.handleEvolutionAnnotations(domElem);
+ self.handleExportBox(domElem);
+ self.applyCosmetics(domElem);
+ self.handleSubDataTable(domElem);
+ self.handleConfigurationBox(domElem);
+ self.handleColumnDocumentation(domElem);
+ self.handleReportDocumentation(domElem);
+ self.handleRowActions(domElem);
+ self.handleRelatedReports(domElem);
+ self.handleTriggeredEvents(domElem);
+ },
+
+ handleLimit: function (domElem) {
+ var tableRowLimits = [5, 10, 25, 50, 100, 250, 500],
+ evolutionLimits =
+ {
+ day: [30, 60, 90, 180, 365, 500],
+ week: [4, 12, 26, 52, 104, 500],
+ month: [3, 6, 12, 24, 36, 120],
+ year: [3, 5, 10]
+ };
+
+ var self = this;
+ if (typeof self.parentId != "undefined" && self.parentId != '') {
+ // no limit selector for subtables
+ $('.limitSelection', domElem).remove();
+ return;
+ }
+
+ // configure limit control
+ var setLimitValue, numbers, limitParamName;
+ if (self.param.viewDataTable == 'graphEvolution') {
+ limitParamName = 'evolution_' + self.param.period + '_last_n';
+ numbers = evolutionLimits[self.param.period] || tableRowLimits;
+
+ setLimitValue = function (params, limit) {
+ params[limitParamName] = limit;
+ };
+ }
+ else {
+ numbers = tableRowLimits;
+ limitParamName = 'filter_limit';
+
+ setLimitValue = function (params, value) {
+ params.filter_limit = value;
+ params.filter_offset = 0;
+ };
+ }
+
+ // setup limit control
+ $('.limitSelection', domElem).append('<div><span>' + self.param[limitParamName] + '</span></div><ul></ul>');
+
+ if (self.param.viewDataTable == 'table'
+ || self.param.viewDataTable == 'tableAllColumns'
+ || self.param.viewDataTable == 'tableGoals'
+ || self.param.viewDataTable == 'ecommerceOrder'
+ || self.param.viewDataTable == 'ecommerceAbandonedCart'
+ || self.param.viewDataTable == 'graphEvolution') {
+ $('.limitSelection ul', domElem).hide();
+ for (var i = 0; i < numbers.length; i++) {
+ $('.limitSelection ul', domElem).append('<li value="' + numbers[i] + '"><span>' + numbers[i] + '</span></li>');
+ }
+ $('.limitSelection ul li:last', domElem).addClass('last');
+
+ if (!self.isEmpty) {
+ var show = function () {
+ $('.limitSelection ul', domElem).show();
+ $('.limitSelection', domElem).addClass('visible');
+ $(document).on('mouseup.limitSelection', function (e) {
+ if ((!$(e.target).parents('.limitSelection').length
+ || $(e.target).parents('.limitSelection') != $('.limitSelection', domElem))
+ && !$(e.target).is('.limitSelection')) {
+ hide();
+ }
+ });
+ }
+ var hide = function () {
+ $('.limitSelection ul', domElem).hide();
+ $('.limitSelection', domElem).removeClass('visible');
+ $(document).off('mouseup.limitSelection');
+ }
+ $('.limitSelection div', domElem).on('click', function () {
+ $('.limitSelection', domElem).is('.visible') ? hide() : show();
+ });
+ $('.limitSelection ul li', domElem).on('click', function (event) {
+ var limit = parseInt($(event.target).text());
+
+ hide();
+ if (limit != self.param[limitParamName]) {
+ setLimitValue(self.param, limit);
+ $('.limitSelection>div>span', domElem).text(limit);
+ self.reloadAjaxDataTable();
+
+ var data = {};
+ data[limitParamName] = self.param[limitParamName];
+ self.notifyWidgetParametersChange(domElem, data);
+ }
+ });
+ }
+ else {
+ $('.limitSelection', domElem).toggleClass('disabled');
+ }
+ }
+ else {
+ $('.limitSelection', domElem).hide();
+ }
+ },
+
+ // if sorting the columns is enabled, when clicking on a column,
+ // - if this column was already the one used for sorting, we revert the order desc<->asc
+ // - we send the ajax request with the new sorting information
+ handleSort: function (domElem) {
+ var self = this;
+
+ function getSortImageSrc() {
+ var imageSortSrc = false;
+ if (currentIsSubDataTable) {
+ if (self.param.filter_sort_order == 'asc') {
+ imageSortSrc = 'themes/default/images/sort_subtable_asc.png';
+ } else {
+ imageSortSrc = 'themes/default/images/sort_subtable_desc.png';
+ }
+ } else {
+ if (self.param.filter_sort_order == 'asc') {
+ imageSortSrc = 'themes/default/images/sortasc.png';
+ } else {
+ imageSortSrc = 'themes/default/images/sortdesc.png';
+ }
+ }
+ return imageSortSrc;
+ }
+
+ if (self.param.enable_sort) {
+ $('.sortable', domElem).off('click.dataTableSort').on('click.dataTableSort',
+ function () {
+ $(this).off('click.dataTableSort');
+ self.onClickSort(this);
+ }
+ );
+
+ if (self.param.filter_sort_column != '') {
+ // are we in a subdatatable?
+ var currentIsSubDataTable = $(domElem).parent().hasClass('cellSubDataTable');
+ var imageSortSrc = getSortImageSrc();
+ var imageSortWidth = 16;
+ var imageSortHeight = 16;
+ // we change the style of the column currently used as sort column
+ // adding an image and the class columnSorted to the TD
+ $(".sortable#" + self.param.filter_sort_column + ' #thDIV', domElem).parent()
+ .addClass('columnSorted')
+ .prepend('<div id="sortIconContainer"><img id="sortIcon" width="' + imageSortWidth + '" height="' + imageSortHeight + '" src="' + imageSortSrc + '" /></div>');
+ }
+ }
+ },
+
+ //behaviour for the DataTable 'search box'
+ handleSearchBox: function (domElem, callbackSuccess) {
+ var self = this;
+
+ var currentPattern = self.param.filter_pattern;
+ if (typeof self.param.filter_pattern != "undefined"
+ && self.param.filter_pattern.length > 0) {
+ currentPattern = self.param.filter_pattern;
+ }
+ else if (typeof self.param.filter_pattern_recursive != "undefined"
+ && self.param.filter_pattern_recursive.length > 0) {
+ currentPattern = self.param.filter_pattern_recursive;
+ }
+ else {
+ currentPattern = '';
+ }
+ currentPattern = piwikHelper.htmlDecode(currentPattern);
+
+ $('.dataTableSearchPattern', domElem)
+ .show()
+ .each(function () {
+ // when enter is pressed in the input field we submit the form
+ $('#keyword', this)
+ .on("keyup",
+ function (e) {
+ if (isEnterKey(e)) {
+ $(this).siblings(':submit').submit();
+ }
+ }
+ )
+ .val(currentPattern)
+ ;
+
+ $(':submit', this).submit(
+ function () {
+ var keyword = $(this).siblings('#keyword').val();
+ self.param.filter_offset = 0;
+
+ if (self.param.search_recursive) {
+ self.param.filter_column_recursive = 'label';
+ self.param.filter_pattern_recursive = keyword;
+ }
+ else {
+ self.param.filter_column = 'label';
+ self.param.filter_pattern = keyword;
+ }
+ self.reloadAjaxDataTable(true, callbackSuccess);
+ }
+ );
+
+ $(':submit', this)
+ .click(function () { $(this).submit(); })
+ ;
+
+ // in the case there is a searched keyword we display the RESET image
+ if (currentPattern) {
+ var target = this;
+ var clearImg = $('<span style="position: relative;">\
<img src="plugins/CoreHome/templates/images/reset_search.png" style="position: absolute; top: 4px; left: -15px; cursor: pointer; display: inline;" title="Clear" />\
</span>')
- .click( function() {
- $('#keyword', target).val('');
- $(':submit', target).submit();
- });
- $('#keyword',this).after(clearImg);
-
- }
- }
- );
- },
-
- //behaviour for '< prev' 'next >' links and page count
- handleOffsetInformation: function(domElem)
- {
- var self = this;
-
- $('.dataTablePages', domElem).each(
- function(){
- var offset = 1+Number(self.param.filter_offset);
- var offsetEnd = Number(self.param.filter_offset) + Number(self.param.filter_limit);
- var totalRows = Number(self.param.totalRows);
- offsetEndDisp = offsetEnd;
-
- if (self.param.keep_summary_row == 1) --totalRows;
-
- if(offsetEnd > totalRows) offsetEndDisp = totalRows;
-
- // only show this string if there is some rows in the datatable
- if(totalRows != 0)
- {
- var str = sprintf(_pk_translate('CoreHome_PageOf_js'),offset + '-' + offsetEndDisp,totalRows);
- $(this).text(str);
- }
- }
- );
-
- // Display the next link if the total Rows is greater than the current end row
- $('.dataTableNext', domElem)
- .each(function(){
- var offsetEnd = Number(self.param.filter_offset)
- + Number(self.param.filter_limit);
- var totalRows = Number(self.param.totalRows);
- if (self.param.keep_summary_row == 1) --totalRows;
- if(offsetEnd < totalRows)
- {
- $(this).css('display','inline');
- }
- })
- // bind the click event to trigger the ajax request with the new offset
- .click(function(){
- $(this).off('click');
- self.param.filter_offset = Number(self.param.filter_offset) + Number(self.param.filter_limit);
- self.reloadAjaxDataTable();
- })
- ;
-
- // Display the previous link if the current offset is not zero
- $('.dataTablePrevious', domElem)
- .each(function(){
- var offset = 1+Number(self.param.filter_offset);
- if(offset != 1)
- {
- $(this).css('display','inline');
- }
- }
- )
- // bind the click event to trigger the ajax request with the new offset
- // take care of the negative offset, we setup 0
- .click(
- function(){
- $(this).off('click');
- var offset = Number(self.param.filter_offset) - Number(self.param.filter_limit);
- if(offset < 0) { offset = 0; }
- self.param.filter_offset = offset;
- self.param.previous = 1;
- self.reloadAjaxDataTable();
- }
- );
- },
-
- handleEvolutionAnnotations: function(domElem)
- {
- var self = this;
- if (self.param.viewDataTable == 'graphEvolution'
- && $('.annotationView', domElem).length > 0)
- {
- // get dates w/ annotations across evolution period (have to do it through AJAX since we
- // determine placement using the elements created by jqplot)
- piwik.annotations.api.getEvolutionIcons(
- self.param.idSite,
- self.param.date,
- self.param.period,
- self.param['evolution_' + self.param.period + '_last_n'],
- function (response)
- {
- var annotations = $(response),
- datatableFeatures = $('.dataTableFeatures', domElem),
- noteSize = 16,
- annotationAxisHeight = 30 // css height + padding + margin
- ;
-
- // set position of evolution annotation icons
- annotations.css({
- top: -datatableFeatures.height() - annotationAxisHeight + noteSize / 2,
- left: 6 // padding-left of .jqplot-evolution element (in graph.tpl)
- });
-
- piwik.annotations.placeEvolutionIcons(annotations, domElem);
-
- // add new section under axis
- datatableFeatures.append(annotations);
-
- // reposition annotation icons every time the graph is resized
- $('.piwik-graph', domElem).on('resizeGraph', function() {
- piwik.annotations.placeEvolutionIcons(annotations, domElem);
- });
-
- // on hover of x-axis, show note icon over correct part of x-axis
- $('span', annotations).hover(
- function() { $(this).css('opacity', 1); },
- function() {
- if ($(this).attr('data-count') == 0) // only hide if there are no annotations for this note
- {
- $(this).css('opacity', 0);
- }
- }
- );
-
- // when clicking an annotation, show the annotation viewer for that period
- $('span', annotations).click(function() {
- var spanSelf = $(this),
- date = spanSelf.attr('data-date'),
- oldDate = $('.annotation-manager', domElem).attr('data-date');
- if (date)
- {
- var period = self.param.period;
- if (period == 'range')
- {
- period = 'day';
- }
-
- piwik.annotations.showAnnotationViewer(
- domElem,
- self.param.idSite,
- date,
- period,
- undefined, // lastN
- function (manager) {
- manager.attr('data-is-range', 0);
- $('.annotationView img', domElem)
- .attr('title', _pk_translate('Annotations_IconDesc_js'));
-
- var viewAndAdd = _pk_translate('Annotations_ViewAndAddAnnotations_js'),
- hideNotes = _pk_translate('Annotations_HideAnnotationsFor_js');
-
- // change the tooltip of the previously clicked evolution icon (if any)
- if (oldDate)
- {
- $('span', annotations).each(function() {
- if ($(this).attr('data-date') == oldDate)
- {
- $(this).attr('title', viewAndAdd.replace("%s", oldDate));
- return false;
- }
- });
- }
-
- // change the tooltip of the clicked evolution icon
- if (manager.is(':hidden'))
- {
- spanSelf.attr('title', viewAndAdd.replace("%s", date));
- }
- else
- {
- spanSelf.attr('title', hideNotes.replace("%s", date));
- }
- }
- );
- }
- });
-
- // when hover over annotation in annotation manager, highlight the annotation
- // icon
- var runningAnimation = null;
- domElem.on('mouseenter', '.annotation', function(e) {
- var date = $(this).attr('data-date');
-
- // find the icon for this annotation
- var icon = $();
- $('span', annotations).each(function() {
- if ($(this).attr('data-date') == date)
- {
- icon = $('img', this);
- return false;
- }
- });
-
- if (icon[0] == runningAnimation) // if the animation is already running, do nothing
- {
- return;
- }
-
- // stop ongoing animations
- $('span', annotations).each(function() {
- $('img', this).removeAttr('style');
- });
-
- // start a bounce animation
- icon.effect("bounce", {times: 1, distance: 10}, 1000);
- runningAnimation = icon[0];
- });
-
- // reset running animation item when leaving annotations list
- domElem.on('mouseleave', '.annotations', function(e) {
- runningAnimation = null;
- });
- }
- );
- }
- },
-
- handleAnnotationsButton: function(domElem)
- {
- var self = this;
- if (self.param.idSubtable) // no annotations for subtables, just whole reports
- {
- return;
- }
-
- // show the annotations view on click
- $('.annotationView', domElem).click(function() {
- var annotationManager = $('.annotation-manager', domElem);
-
- if (annotationManager.length > 0
- && annotationManager.attr('data-is-range') == 1)
- {
- if (annotationManager.is(':hidden'))
- {
- annotationManager.slideDown('slow'); // showing
- $('img', this).attr('title', _pk_translate('Annotations_IconDescHideNotes_js'));
- }
- else
- {
- annotationManager.slideUp('slow'); // hiding
- $('img', this).attr('title', _pk_translate('Annotations_IconDesc_js'));
- }
- }
- else
- {
- // show the annotation viewer for the whole date range
- var lastN = self.param['evolution_' + self.param.period + '_last_n'];
- piwik.annotations.showAnnotationViewer(
- domElem,
- self.param.idSite,
- self.param.date,
- self.param.period,
- lastN,
- function(manager) {
- manager.attr('data-is-range', 1);
- }
- );
-
- // change the tooltip of the view annotation icon
- $('img', this).attr('title', _pk_translate('Annotations_IconDescHideNotes_js'));
- }
- });
- },
-
- // DataTable view box (simple table, all columns table, Goals table, pie graph, tag cloud, graph, ...)
- handleExportBox: function(domElem)
- {
- var self = this;
- if( self.param.idSubtable )
- {
- // no view box for subtables
- return;
- }
-
- // When the (+) image is hovered, the export buttons are displayed
- $('.dataTableFooterIconsShow', domElem)
- .show()
- .hover( function() {
- $(this).fadeOut('slow');
- $('.exportToFormatIcons', $(this).parent()).show('slow');
- }, function(){}
- );
-
- //footer arrow position element name
- self.jsViewDataTable=$('.dataTableFooterWrap', domElem).attr('var');
-
- $('.tableAllColumnsSwitch a', domElem)
- .show()
- .click(
- function(){
- // we only reset the limit filter, in case switch to table view from cloud view where limit is custom set to 30
- // this value is stored in config file General->datatable_default_limit but this is more an edge case so ok to set it to 10
-
- self.setActiveIcon(this, domElem);
-
- var viewDataTable = $(this).attr('format');
- self.param.viewDataTable = viewDataTable;
-
- //self.resetAllFilters();
-
- // when switching to display simple table, do not exclude low pop by default
- delete self.param.enable_filter_excludelowpop;
- delete self.param.filter_sort_column;
- delete self.param.filter_sort_order;
- delete columns;
- self.reloadAjaxDataTable();
- self.notifyWidgetParametersChange($(this), {viewDataTable: self.param.viewDataTable});
- }
- )
-
- //handle Graph View icons
- $('.tableGraphViews a', domElem)
- .click(function(){
- var viewDataTable = $(this).attr('format');
- self.setActiveIcon(this, domElem);
-
- var filters = self.resetAllFilters();
- self.param.flat = filters.flat;
- self.param.columns = filters.columns;
-
- self.param.viewDataTable = viewDataTable;
- self.reloadAjaxDataTable();
- self.notifyWidgetParametersChange($(this), {viewDataTable: self.param.viewDataTable});
- });
-
- //Graph icon Collapsed functionality
- self.currentGraphViewIcon=0;
- self.graphViewEnabled=0;
- self.graphViewStartingThreads=0;
- self.graphViewStartingKeep=false; //show keep flag
-
- //define collapsed icons
- $('.tableGraphCollapsed a', domElem)
- .each(function(i){
- if(self.jsViewDataTable==$(this).attr('var')){
- self.currentGraphViewIcon=i;
- self.graphViewEnabled=true;
- }
- })
- .each(function(i){
- if(self.currentGraphViewIcon!=i) $(this).hide();
- });
-
- $('.tableGraphCollapsed', domElem).hover(
- function(){
- //Graph icon onmouseover
- if(self.graphViewStartingThreads>0) return self.graphViewStartingKeep=true; //exit if animation is not finished
- $(this).addClass('tableIconsGroupActive');
- $('a', this).each(function(i){
- if(self.currentGraphViewIcon!=i || self.graphViewEnabled){
- self.graphViewStartingThreads++;
- }
- if(self.currentGraphViewIcon!=i){
- //show other icons
- $(this).show('fast', function(){self.graphViewStartingThreads--});
- }
- else if (self.graphViewEnabled){
- //set footer arrow position
- $('.dataTableFooterActiveItem', domElem).animate({left:$(this).parent().position().left+i*(this.offsetWidth+1)}, "fast", function(){self.graphViewStartingThreads--});
- }
- });
- self.exportToFormatHide(domElem);
- },
- function(){
- //Graph icon onmouseout
- if(self.graphViewStartingKeep) return self.graphViewStartingKeep=false; //exit while icons animate
- $('a', this).each(function(i){
- if(self.currentGraphViewIcon!=i){
- //hide other icons
- $(this).hide('fast');
- }
- else if (self.graphViewEnabled){
- //set footer arrow position
- $('.dataTableFooterActiveItem', domElem).animate({left:$(this).parent().position().left}, "fast");
- }
- });
- $(this).removeClass('tableIconsGroupActive');
- }
- );
-
- //handle exportToFormat icons
- self.exportToFormat=null;
- $('.exportToFormatIcons a', domElem).click(function(){
- self.exportToFormat={};
- self.exportToFormat.lastActiveIcon=self.setActiveIcon(this, domElem);
- self.exportToFormat.target=$(this).parent().siblings('.exportToFormatItems').show('fast');
- self.exportToFormat.obj=$(this).hide();
- });
-
- //close exportToFormat onClickOutside
- $('body').on('mouseup',function(e){
- if(self.exportToFormat){
- self.exportToFormatHide(domElem);
- }
- });
-
-
- $('.exportToFormatItems a', domElem)
- // prevent click jacking attacks by dynamically adding the token auth when the link is clicked
- .click( function() {
- $(this).attr('href', function() {
- return $(this).attr('href') +'&token_auth='+piwik.token_auth;
- })
- })
- .attr( 'href', function(){
- var format = $(this).attr('format');
- var method = $(this).attr('methodToCall');
- var filter_limit = $(this).attr('filter_limit');
- var segment = self.param.segment;
- var label = self.param.label;
- var idGoal = self.param.idGoal;
- var param_date = self.param.date;
- var date = $(this).attr('date');
- if(typeof date != 'undefined') {
- param_date = date;
- }
- if( typeof self.param.dateUsedInGraph != 'undefined') {
+ .click(function () {
+ $('#keyword', target).val('');
+ $(':submit', target).submit();
+ });
+ $('#keyword', this).after(clearImg);
+
+ }
+ }
+ );
+ },
+
+ //behaviour for '< prev' 'next >' links and page count
+ handleOffsetInformation: function (domElem) {
+ var self = this;
+
+ $('.dataTablePages', domElem).each(
+ function () {
+ var offset = 1 + Number(self.param.filter_offset);
+ var offsetEnd = Number(self.param.filter_offset) + Number(self.param.filter_limit);
+ var totalRows = Number(self.param.totalRows);
+ offsetEndDisp = offsetEnd;
+
+ if (self.param.keep_summary_row == 1) --totalRows;
+
+ if (offsetEnd > totalRows) offsetEndDisp = totalRows;
+
+ // only show this string if there is some rows in the datatable
+ if (totalRows != 0) {
+ var str = sprintf(_pk_translate('CoreHome_PageOf_js'), offset + '-' + offsetEndDisp, totalRows);
+ $(this).text(str);
+ }
+ }
+ );
+
+ // Display the next link if the total Rows is greater than the current end row
+ $('.dataTableNext', domElem)
+ .each(function () {
+ var offsetEnd = Number(self.param.filter_offset)
+ + Number(self.param.filter_limit);
+ var totalRows = Number(self.param.totalRows);
+ if (self.param.keep_summary_row == 1) --totalRows;
+ if (offsetEnd < totalRows) {
+ $(this).css('display', 'inline');
+ }
+ })
+ // bind the click event to trigger the ajax request with the new offset
+ .click(function () {
+ $(this).off('click');
+ self.param.filter_offset = Number(self.param.filter_offset) + Number(self.param.filter_limit);
+ self.reloadAjaxDataTable();
+ })
+ ;
+
+ // Display the previous link if the current offset is not zero
+ $('.dataTablePrevious', domElem)
+ .each(function () {
+ var offset = 1 + Number(self.param.filter_offset);
+ if (offset != 1) {
+ $(this).css('display', 'inline');
+ }
+ }
+ )
+ // bind the click event to trigger the ajax request with the new offset
+ // take care of the negative offset, we setup 0
+ .click(
+ function () {
+ $(this).off('click');
+ var offset = Number(self.param.filter_offset) - Number(self.param.filter_limit);
+ if (offset < 0) { offset = 0; }
+ self.param.filter_offset = offset;
+ self.param.previous = 1;
+ self.reloadAjaxDataTable();
+ }
+ );
+ },
+
+ handleEvolutionAnnotations: function (domElem) {
+ var self = this;
+ if (self.param.viewDataTable == 'graphEvolution'
+ && $('.annotationView', domElem).length > 0) {
+ // get dates w/ annotations across evolution period (have to do it through AJAX since we
+ // determine placement using the elements created by jqplot)
+ piwik.annotations.api.getEvolutionIcons(
+ self.param.idSite,
+ self.param.date,
+ self.param.period,
+ self.param['evolution_' + self.param.period + '_last_n'],
+ function (response) {
+ var annotations = $(response),
+ datatableFeatures = $('.dataTableFeatures', domElem),
+ noteSize = 16,
+ annotationAxisHeight = 30 // css height + padding + margin
+ ;
+
+ // set position of evolution annotation icons
+ annotations.css({
+ top: -datatableFeatures.height() - annotationAxisHeight + noteSize / 2,
+ left: 6 // padding-left of .jqplot-evolution element (in graph.tpl)
+ });
+
+ piwik.annotations.placeEvolutionIcons(annotations, domElem);
+
+ // add new section under axis
+ datatableFeatures.append(annotations);
+
+ // reposition annotation icons every time the graph is resized
+ $('.piwik-graph', domElem).on('resizeGraph', function () {
+ piwik.annotations.placeEvolutionIcons(annotations, domElem);
+ });
+
+ // on hover of x-axis, show note icon over correct part of x-axis
+ $('span', annotations).hover(
+ function () { $(this).css('opacity', 1); },
+ function () {
+ if ($(this).attr('data-count') == 0) // only hide if there are no annotations for this note
+ {
+ $(this).css('opacity', 0);
+ }
+ }
+ );
+
+ // when clicking an annotation, show the annotation viewer for that period
+ $('span', annotations).click(function () {
+ var spanSelf = $(this),
+ date = spanSelf.attr('data-date'),
+ oldDate = $('.annotation-manager', domElem).attr('data-date');
+ if (date) {
+ var period = self.param.period;
+ if (period == 'range') {
+ period = 'day';
+ }
+
+ piwik.annotations.showAnnotationViewer(
+ domElem,
+ self.param.idSite,
+ date,
+ period,
+ undefined, // lastN
+ function (manager) {
+ manager.attr('data-is-range', 0);
+ $('.annotationView img', domElem)
+ .attr('title', _pk_translate('Annotations_IconDesc_js'));
+
+ var viewAndAdd = _pk_translate('Annotations_ViewAndAddAnnotations_js'),
+ hideNotes = _pk_translate('Annotations_HideAnnotationsFor_js');
+
+ // change the tooltip of the previously clicked evolution icon (if any)
+ if (oldDate) {
+ $('span', annotations).each(function () {
+ if ($(this).attr('data-date') == oldDate) {
+ $(this).attr('title', viewAndAdd.replace("%s", oldDate));
+ return false;
+ }
+ });
+ }
+
+ // change the tooltip of the clicked evolution icon
+ if (manager.is(':hidden')) {
+ spanSelf.attr('title', viewAndAdd.replace("%s", date));
+ }
+ else {
+ spanSelf.attr('title', hideNotes.replace("%s", date));
+ }
+ }
+ );
+ }
+ });
+
+ // when hover over annotation in annotation manager, highlight the annotation
+ // icon
+ var runningAnimation = null;
+ domElem.on('mouseenter', '.annotation', function (e) {
+ var date = $(this).attr('data-date');
+
+ // find the icon for this annotation
+ var icon = $();
+ $('span', annotations).each(function () {
+ if ($(this).attr('data-date') == date) {
+ icon = $('img', this);
+ return false;
+ }
+ });
+
+ if (icon[0] == runningAnimation) // if the animation is already running, do nothing
+ {
+ return;
+ }
+
+ // stop ongoing animations
+ $('span', annotations).each(function () {
+ $('img', this).removeAttr('style');
+ });
+
+ // start a bounce animation
+ icon.effect("bounce", {times: 1, distance: 10}, 1000);
+ runningAnimation = icon[0];
+ });
+
+ // reset running animation item when leaving annotations list
+ domElem.on('mouseleave', '.annotations', function (e) {
+ runningAnimation = null;
+ });
+ }
+ );
+ }
+ },
+
+ handleAnnotationsButton: function (domElem) {
+ var self = this;
+ if (self.param.idSubtable) // no annotations for subtables, just whole reports
+ {
+ return;
+ }
+
+ // show the annotations view on click
+ $('.annotationView', domElem).click(function () {
+ var annotationManager = $('.annotation-manager', domElem);
+
+ if (annotationManager.length > 0
+ && annotationManager.attr('data-is-range') == 1) {
+ if (annotationManager.is(':hidden')) {
+ annotationManager.slideDown('slow'); // showing
+ $('img', this).attr('title', _pk_translate('Annotations_IconDescHideNotes_js'));
+ }
+ else {
+ annotationManager.slideUp('slow'); // hiding
+ $('img', this).attr('title', _pk_translate('Annotations_IconDesc_js'));
+ }
+ }
+ else {
+ // show the annotation viewer for the whole date range
+ var lastN = self.param['evolution_' + self.param.period + '_last_n'];
+ piwik.annotations.showAnnotationViewer(
+ domElem,
+ self.param.idSite,
+ self.param.date,
+ self.param.period,
+ lastN,
+ function (manager) {
+ manager.attr('data-is-range', 1);
+ }
+ );
+
+ // change the tooltip of the view annotation icon
+ $('img', this).attr('title', _pk_translate('Annotations_IconDescHideNotes_js'));
+ }
+ });
+ },
+
+ // DataTable view box (simple table, all columns table, Goals table, pie graph, tag cloud, graph, ...)
+ handleExportBox: function (domElem) {
+ var self = this;
+ if (self.param.idSubtable) {
+ // no view box for subtables
+ return;
+ }
+
+ // When the (+) image is hovered, the export buttons are displayed
+ $('.dataTableFooterIconsShow', domElem)
+ .show()
+ .hover(function () {
+ $(this).fadeOut('slow');
+ $('.exportToFormatIcons', $(this).parent()).show('slow');
+ }, function () {}
+ );
+
+ //footer arrow position element name
+ self.jsViewDataTable = $('.dataTableFooterWrap', domElem).attr('var');
+
+ $('.tableAllColumnsSwitch a', domElem)
+ .show()
+ .click(
+ function () {
+ // we only reset the limit filter, in case switch to table view from cloud view where limit is custom set to 30
+ // this value is stored in config file General->datatable_default_limit but this is more an edge case so ok to set it to 10
+
+ self.setActiveIcon(this, domElem);
+
+ var viewDataTable = $(this).attr('format');
+ self.param.viewDataTable = viewDataTable;
+
+ //self.resetAllFilters();
+
+ // when switching to display simple table, do not exclude low pop by default
+ delete self.param.enable_filter_excludelowpop;
+ delete self.param.filter_sort_column;
+ delete self.param.filter_sort_order;
+ delete columns;
+ self.reloadAjaxDataTable();
+ self.notifyWidgetParametersChange($(this), {viewDataTable: self.param.viewDataTable});
+ }
+ )
+
+ //handle Graph View icons
+ $('.tableGraphViews a', domElem)
+ .click(function () {
+ var viewDataTable = $(this).attr('format');
+ self.setActiveIcon(this, domElem);
+
+ var filters = self.resetAllFilters();
+ self.param.flat = filters.flat;
+ self.param.columns = filters.columns;
+
+ self.param.viewDataTable = viewDataTable;
+ self.reloadAjaxDataTable();
+ self.notifyWidgetParametersChange($(this), {viewDataTable: self.param.viewDataTable});
+ });
+
+ //Graph icon Collapsed functionality
+ self.currentGraphViewIcon = 0;
+ self.graphViewEnabled = 0;
+ self.graphViewStartingThreads = 0;
+ self.graphViewStartingKeep = false; //show keep flag
+
+ //define collapsed icons
+ $('.tableGraphCollapsed a', domElem)
+ .each(function (i) {
+ if (self.jsViewDataTable == $(this).attr('var')) {
+ self.currentGraphViewIcon = i;
+ self.graphViewEnabled = true;
+ }
+ })
+ .each(function (i) {
+ if (self.currentGraphViewIcon != i) $(this).hide();
+ });
+
+ $('.tableGraphCollapsed', domElem).hover(
+ function () {
+ //Graph icon onmouseover
+ if (self.graphViewStartingThreads > 0) return self.graphViewStartingKeep = true; //exit if animation is not finished
+ $(this).addClass('tableIconsGroupActive');
+ $('a', this).each(function (i) {
+ if (self.currentGraphViewIcon != i || self.graphViewEnabled) {
+ self.graphViewStartingThreads++;
+ }
+ if (self.currentGraphViewIcon != i) {
+ //show other icons
+ $(this).show('fast', function () {self.graphViewStartingThreads--});
+ }
+ else if (self.graphViewEnabled) {
+ //set footer arrow position
+ $('.dataTableFooterActiveItem', domElem).animate({left: $(this).parent().position().left + i * (this.offsetWidth + 1)}, "fast", function () {self.graphViewStartingThreads--});
+ }
+ });
+ self.exportToFormatHide(domElem);
+ },
+ function () {
+ //Graph icon onmouseout
+ if (self.graphViewStartingKeep) return self.graphViewStartingKeep = false; //exit while icons animate
+ $('a', this).each(function (i) {
+ if (self.currentGraphViewIcon != i) {
+ //hide other icons
+ $(this).hide('fast');
+ }
+ else if (self.graphViewEnabled) {
+ //set footer arrow position
+ $('.dataTableFooterActiveItem', domElem).animate({left: $(this).parent().position().left}, "fast");
+ }
+ });
+ $(this).removeClass('tableIconsGroupActive');
+ }
+ );
+
+ //handle exportToFormat icons
+ self.exportToFormat = null;
+ $('.exportToFormatIcons a', domElem).click(function () {
+ self.exportToFormat = {};
+ self.exportToFormat.lastActiveIcon = self.setActiveIcon(this, domElem);
+ self.exportToFormat.target = $(this).parent().siblings('.exportToFormatItems').show('fast');
+ self.exportToFormat.obj = $(this).hide();
+ });
+
+ //close exportToFormat onClickOutside
+ $('body').on('mouseup', function (e) {
+ if (self.exportToFormat) {
+ self.exportToFormatHide(domElem);
+ }
+ });
+
+
+ $('.exportToFormatItems a', domElem)
+ // prevent click jacking attacks by dynamically adding the token auth when the link is clicked
+ .click(function () {
+ $(this).attr('href', function () {
+ return $(this).attr('href') + '&token_auth=' + piwik.token_auth;
+ })
+ })
+ .attr('href', function () {
+ var format = $(this).attr('format');
+ var method = $(this).attr('methodToCall');
+ var filter_limit = $(this).attr('filter_limit');
+ var segment = self.param.segment;
+ var label = self.param.label;
+ var idGoal = self.param.idGoal;
+ var param_date = self.param.date;
+ var date = $(this).attr('date');
+ if (typeof date != 'undefined') {
+ param_date = date;
+ }
+ if (typeof self.param.dateUsedInGraph != 'undefined') {
param_date = self.param.dateUsedInGraph;
}
- var period = self.param.period;
-
- // RSS does not work for period=range
- if(format == 'RSS'
- && self.param.period == 'range') {
- period = 'day';
- }
- var str = 'index.php?module=API'
- +'&method='+method
- +'&format='+format
- +'&idSite='+self.param.idSite
- +'&period='+period
- +'&date='+ param_date
- + ( typeof self.param.filter_pattern != "undefined" ? '&filter_pattern=' + self.param.filter_pattern : '')
- + ( typeof self.param.filter_pattern_recursive != "undefined" ? '&filter_pattern_recursive=' + self.param.filter_pattern_recursive : '');
-
- if (typeof self.param.flat != "undefined") {
- str += '&flat=' + (self.param.flat == 0 ? '0' : '1');
- if (typeof self.param.include_aggregate_rows != "undefined" && self.param.include_aggregate_rows) {
- str += '&include_aggregate_rows=1';
- }
- } else {
- str += '&expanded=1';
- }
- if (format == 'CSV' || format == 'TSV' || format == 'RSS') {
- str += '&translateColumnNames=1&language='+piwik.language;
- }
- if(typeof segment != 'undefined') {
- str += '&segment='+segment;
- }
- // Export Goals specific reports
- if(typeof idGoal != 'undefined'
- && idGoal != '-1') {
- str += '&idGoal='+idGoal;
- }
- if(filter_limit)
- {
- str += '&filter_limit='+filter_limit;
- }
- if(label)
- {
- str += '&label='+encodeURIComponent(label);
- }
- return str;
- }
- );
-
- // Initialize arrow footer to correct icon
- $('.dataTableFooterWrap a.tableIcon', domElem).each(function(){
- if(self.jsViewDataTable==$(this).attr('var')) self.setActiveIcon(this, domElem);
- });
-
- },
-
- exportToFormatHide: function(domElem, noAnimation)
- {
- var self=this;
- if(self.exportToFormat){
- self.setActiveIcon(self.exportToFormat.lastActiveIcon, domElem);
- var animationSpeed = noAnimation ? 0 : 'fast';
- self.exportToFormat.target.hide(animationSpeed);
- self.exportToFormat.obj.show(animationSpeed);
- self.exportToFormat=null;
- }
- },
-
- handleConfigurationBox: function(domElem, callbackSuccess)
- {
- var self = this;
-
- if (typeof self.parentId != "undefined" && self.parentId != '')
- {
- // no manipulation when loading subtables
- return;
- }
-
- if ((typeof self.numberOfSubtables == 'undefined' || self.numberOfSubtables == 0)
- && (typeof self.param.flat == 'undefined' || self.param.flat != 1))
- {
- // if there are no subtables, remove the flatten action
- $('.dataTableFlatten', domElem).parent().remove();
- }
-
- var ul = $('div.tableConfiguration ul', domElem);
-
- function hideConfigurationIcon()
- {
- // hide the icon when there are no actions available or we're not in a table view
- $('div.tableConfiguration', domElem).remove();
- }
-
- if (ul.find('li').size() == 0)
- {
- hideConfigurationIcon();
- return;
- }
-
- var icon = $('a.tableConfigurationIcon', domElem);
- icon.click(function() { return false; });
- var iconHighlighted = false;
-
- ul.find('li:first').addClass('first');
- ul.find('li:last').addClass('last');
- ul.prepend('<li class="firstDummy"></li>');
-
- // open and close the box
- var open = function() {
- self.exportToFormatHide(domElem, true);
- ul.addClass('open');
- icon.css('opacity', 1);
- };
- var close = function() {
- ul.removeClass('open');
- icon.css('opacity', icon.hasClass('highlighted') ? .85 : .6);
- };
- $('div.tableConfiguration', domElem).hover(open, close);
-
- var generateClickCallback = function(paramName, callbackAfterToggle)
- {
- return function()
- {
- close();
- self.param[paramName] = 1 - self.param[paramName];
- self.param.filter_offset = 0;
- if (callbackAfterToggle) callbackAfterToggle();
- self.reloadAjaxDataTable(true, callbackSuccess);
+ var period = self.param.period;
+
+ // RSS does not work for period=range
+ if (format == 'RSS'
+ && self.param.period == 'range') {
+ period = 'day';
+ }
+ var str = 'index.php?module=API'
+ + '&method=' + method
+ + '&format=' + format
+ + '&idSite=' + self.param.idSite
+ + '&period=' + period
+ + '&date=' + param_date
+ + ( typeof self.param.filter_pattern != "undefined" ? '&filter_pattern=' + self.param.filter_pattern : '')
+ + ( typeof self.param.filter_pattern_recursive != "undefined" ? '&filter_pattern_recursive=' + self.param.filter_pattern_recursive : '');
+
+ if (typeof self.param.flat != "undefined") {
+ str += '&flat=' + (self.param.flat == 0 ? '0' : '1');
+ if (typeof self.param.include_aggregate_rows != "undefined" && self.param.include_aggregate_rows) {
+ str += '&include_aggregate_rows=1';
+ }
+ } else {
+ str += '&expanded=1';
+ }
+ if (format == 'CSV' || format == 'TSV' || format == 'RSS') {
+ str += '&translateColumnNames=1&language=' + piwik.language;
+ }
+ if (typeof segment != 'undefined') {
+ str += '&segment=' + segment;
+ }
+ // Export Goals specific reports
+ if (typeof idGoal != 'undefined'
+ && idGoal != '-1') {
+ str += '&idGoal=' + idGoal;
+ }
+ if (filter_limit) {
+ str += '&filter_limit=' + filter_limit;
+ }
+ if (label) {
+ str += '&label=' + encodeURIComponent(label);
+ }
+ return str;
+ }
+ );
+
+ // Initialize arrow footer to correct icon
+ $('.dataTableFooterWrap a.tableIcon', domElem).each(function () {
+ if (self.jsViewDataTable == $(this).attr('var')) self.setActiveIcon(this, domElem);
+ });
+
+ },
+
+ exportToFormatHide: function (domElem, noAnimation) {
+ var self = this;
+ if (self.exportToFormat) {
+ self.setActiveIcon(self.exportToFormat.lastActiveIcon, domElem);
+ var animationSpeed = noAnimation ? 0 : 'fast';
+ self.exportToFormat.target.hide(animationSpeed);
+ self.exportToFormat.obj.show(animationSpeed);
+ self.exportToFormat = null;
+ }
+ },
+
+ handleConfigurationBox: function (domElem, callbackSuccess) {
+ var self = this;
+
+ if (typeof self.parentId != "undefined" && self.parentId != '') {
+ // no manipulation when loading subtables
+ return;
+ }
+
+ if ((typeof self.numberOfSubtables == 'undefined' || self.numberOfSubtables == 0)
+ && (typeof self.param.flat == 'undefined' || self.param.flat != 1)) {
+ // if there are no subtables, remove the flatten action
+ $('.dataTableFlatten', domElem).parent().remove();
+ }
+
+ var ul = $('div.tableConfiguration ul', domElem);
+
+ function hideConfigurationIcon() {
+ // hide the icon when there are no actions available or we're not in a table view
+ $('div.tableConfiguration', domElem).remove();
+ }
+
+ if (ul.find('li').size() == 0) {
+ hideConfigurationIcon();
+ return;
+ }
+
+ var icon = $('a.tableConfigurationIcon', domElem);
+ icon.click(function () { return false; });
+ var iconHighlighted = false;
+
+ ul.find('li:first').addClass('first');
+ ul.find('li:last').addClass('last');
+ ul.prepend('<li class="firstDummy"></li>');
+
+ // open and close the box
+ var open = function () {
+ self.exportToFormatHide(domElem, true);
+ ul.addClass('open');
+ icon.css('opacity', 1);
+ };
+ var close = function () {
+ ul.removeClass('open');
+ icon.css('opacity', icon.hasClass('highlighted') ? .85 : .6);
+ };
+ $('div.tableConfiguration', domElem).hover(open, close);
+
+ var generateClickCallback = function (paramName, callbackAfterToggle) {
+ return function () {
+ close();
+ self.param[paramName] = 1 - self.param[paramName];
+ self.param.filter_offset = 0;
+ if (callbackAfterToggle) callbackAfterToggle();
+ self.reloadAjaxDataTable(true, callbackSuccess);
var data = {};
- data[paramName] = self.param[paramName];
+ data[paramName] = self.param[paramName];
self.notifyWidgetParametersChange(domElem, data);
- };
- };
-
- var getText = function(text, addDefault)
- {
- text = _pk_translate(text);
- if (text.indexOf('%s') > 0)
- {
- text = text.replace('%s', '<br /><span class="action">&raquo; ');
- if (addDefault) text += ' (' + _pk_translate('CoreHome_Default_js') + ')';
- text += '</span>';
- }
- return text;
- };
-
- var setText = function(el, paramName, textA, textB)
- {
- if (typeof self.param[paramName] != 'undefined' && self.param[paramName] == 1)
- {
- $(el).html(getText(textA, true));
- iconHighlighted = true;
- }
- else
- {
- self.param[paramName] = 0;
- $(el).html(getText(textB));
- }
- };
-
- // handle low population
- $('.dataTableExcludeLowPopulation', domElem)
- .each(function()
- {
- // Set the text, either "Exclude low pop" or "Include all"
- if(typeof self.param.enable_filter_excludelowpop == 'undefined')
- {
- self.param.enable_filter_excludelowpop = 0;
- }
- if(Number(self.param.enable_filter_excludelowpop) != 0)
- {
- string = getText('CoreHome_IncludeRowsWithLowPopulation_js', true);
- self.param.enable_filter_excludelowpop = 1;
- iconHighlighted = true;
- }
- else
- {
- string = getText('CoreHome_ExcludeRowsWithLowPopulation_js');
- self.param.enable_filter_excludelowpop = 0;
- }
- $(this).html(string);
- })
- .click( generateClickCallback('enable_filter_excludelowpop') );
-
- // handle flatten
- $('.dataTableFlatten', domElem)
- .each( function() {
- setText(this, 'flat', 'CoreHome_UnFlattenDataTable_js', 'CoreHome_FlattenDataTable_js');
- })
- .click( generateClickCallback('flat') );
-
- $('.dataTableIncludeAggregateRows', domElem)
- .each( function() {
- setText(this, 'include_aggregate_rows', 'CoreHome_DataTableExcludeAggregateRows_js',
- 'CoreHome_DataTableIncludeAggregateRows_js');
- })
- .click( generateClickCallback('include_aggregate_rows', function() {
- if (self.param.include_aggregate_rows == 1)
- {
- // when including aggregate rows is enabled, we remove the sorting
- // this way, the aggregate rows appear directly before their children
- self.param.filter_sort_column = '';
+ };
+ };
+
+ var getText = function (text, addDefault) {
+ text = _pk_translate(text);
+ if (text.indexOf('%s') > 0) {
+ text = text.replace('%s', '<br /><span class="action">&raquo; ');
+ if (addDefault) text += ' (' + _pk_translate('CoreHome_Default_js') + ')';
+ text += '</span>';
+ }
+ return text;
+ };
+
+ var setText = function (el, paramName, textA, textB) {
+ if (typeof self.param[paramName] != 'undefined' && self.param[paramName] == 1) {
+ $(el).html(getText(textA, true));
+ iconHighlighted = true;
+ }
+ else {
+ self.param[paramName] = 0;
+ $(el).html(getText(textB));
+ }
+ };
+
+ // handle low population
+ $('.dataTableExcludeLowPopulation', domElem)
+ .each(function () {
+ // Set the text, either "Exclude low pop" or "Include all"
+ if (typeof self.param.enable_filter_excludelowpop == 'undefined') {
+ self.param.enable_filter_excludelowpop = 0;
+ }
+ if (Number(self.param.enable_filter_excludelowpop) != 0) {
+ string = getText('CoreHome_IncludeRowsWithLowPopulation_js', true);
+ self.param.enable_filter_excludelowpop = 1;
+ iconHighlighted = true;
+ }
+ else {
+ string = getText('CoreHome_ExcludeRowsWithLowPopulation_js');
+ self.param.enable_filter_excludelowpop = 0;
+ }
+ $(this).html(string);
+ })
+ .click(generateClickCallback('enable_filter_excludelowpop'));
+
+ // handle flatten
+ $('.dataTableFlatten', domElem)
+ .each(function () {
+ setText(this, 'flat', 'CoreHome_UnFlattenDataTable_js', 'CoreHome_FlattenDataTable_js');
+ })
+ .click(generateClickCallback('flat'));
+
+ $('.dataTableIncludeAggregateRows', domElem)
+ .each(function () {
+ setText(this, 'include_aggregate_rows', 'CoreHome_DataTableExcludeAggregateRows_js',
+ 'CoreHome_DataTableIncludeAggregateRows_js');
+ })
+ .click(generateClickCallback('include_aggregate_rows', function () {
+ if (self.param.include_aggregate_rows == 1) {
+ // when including aggregate rows is enabled, we remove the sorting
+ // this way, the aggregate rows appear directly before their children
+ self.param.filter_sort_column = '';
self.notifyWidgetParametersChange(domElem, {filter_sort_column: ''});
- }
- }));
-
- // handle highlighted icon
- if (iconHighlighted)
- {
- icon.addClass('highlighted');
- }
- close();
-
- if( !iconHighlighted
- && !(self.param.viewDataTable == 'table'
- || self.param.viewDataTable == 'tableAllColumns'
- || self.param.viewDataTable == 'tableGoals'))
- {
- hideConfigurationIcon();
- return;
- }
-
- // fix a css bug of ie7
- if (document.all && !window.opera && window.XMLHttpRequest)
- {
- window.setTimeout(function() {
- open();
- var width = 0;
- ul.find('li').each(function() {
- width = Math.max(width, $(this).width());
- }).width(width);
- close();
- }, 400);
- }
- },
-
- //footer arrow position handler
- setActiveIcon: function(obj, domElem)
- {
- if(!obj) return false;
-
- var lastActiveIcon=this.lastActiveIcon;
-
- if(lastActiveIcon){
- $(lastActiveIcon).removeClass("activeIcon");
- }
-
- $(obj).addClass("activeIcon");
- this.lastActiveIcon=obj;
-
- var target=$('.dataTableFooterActiveItem', domElem);
-
- //set arrow position with delay (for ajax widget loading)
- setTimeout(function(){
- target.css({left:$(obj).position().left});
- },100);
-
- return lastActiveIcon;
-
- },
-
- // Tell parent widget that the parameters of this table was updated,
- notifyWidgetParametersChange: function(domWidget, parameters)
- {
+ }
+ }));
+
+ // handle highlighted icon
+ if (iconHighlighted) {
+ icon.addClass('highlighted');
+ }
+ close();
+
+ if (!iconHighlighted
+ && !(self.param.viewDataTable == 'table'
+ || self.param.viewDataTable == 'tableAllColumns'
+ || self.param.viewDataTable == 'tableGoals')) {
+ hideConfigurationIcon();
+ return;
+ }
+
+ // fix a css bug of ie7
+ if (document.all && !window.opera && window.XMLHttpRequest) {
+ window.setTimeout(function () {
+ open();
+ var width = 0;
+ ul.find('li').each(function () {
+ width = Math.max(width, $(this).width());
+ }).width(width);
+ close();
+ }, 400);
+ }
+ },
+
+ //footer arrow position handler
+ setActiveIcon: function (obj, domElem) {
+ if (!obj) return false;
+
+ var lastActiveIcon = this.lastActiveIcon;
+
+ if (lastActiveIcon) {
+ $(lastActiveIcon).removeClass("activeIcon");
+ }
+
+ $(obj).addClass("activeIcon");
+ this.lastActiveIcon = obj;
+
+ var target = $('.dataTableFooterActiveItem', domElem);
+
+ //set arrow position with delay (for ajax widget loading)
+ setTimeout(function () {
+ target.css({left: $(obj).position().left});
+ }, 100);
+
+ return lastActiveIcon;
+
+ },
+
+ // Tell parent widget that the parameters of this table was updated,
+ notifyWidgetParametersChange: function (domWidget, parameters) {
var widget = $(domWidget).parents('[widgetId]');
// trigger setParameters event on base element
widget.trigger('setParameters', parameters);
- },
-
- truncate: function(domElemToTruncate, truncationOffset)
- {
- var self = this;
-
- domElemToTruncate = $(domElemToTruncate);
-
- if (typeof domElemToTruncate.data('originalText') != 'undefined')
- {
- // truncate only once. otherwise, the tooltip will show the truncated text as well.
- return;
- }
-
- // make the original text (before truncation) available for others.
- // the .truncate plugins adds a title to the dom element but the .tooltip
- // plugin removes that again.
- domElemToTruncate.data('originalText', domElemToTruncate.text());
-
- if (typeof truncationOffset == 'undefined')
- {
- truncationOffset = 0;
- }
- var truncationLimit = 50;
-
- if (typeof self.param.idSubtable == 'undefined'
- && self.param.viewDataTable == 'tableAllColumns')
- {
- // when showing all columns in a subtable, space is restricted
- truncationLimit = 25;
- }
-
- truncationLimit += truncationOffset;
- domElemToTruncate.truncate(truncationLimit);
-
- var tooltipElem = $('.truncated', domElemToTruncate),
- customToolTipText = domElemToTruncate.attr('title');
-
- // if there's a title on the dom element, use this as the tooltip instead of
- // the one set by the truncate plugin
- if (customToolTipText)
- {
- // make sure browser doesn't add its own tooltip for the truncated element
- if (tooltipElem[0])
- {
- tooltipElem.removeAttr('title');
- }
-
- tooltipElem = domElemToTruncate;
- tooltipElem.attr('title', customToolTipText);
- }
-
- // use tooltip (tooltip text determined by the 'title' attribute)
- tooltipElem.tooltip();
- },
-
- //Apply some miscelleaneous style to the DataTable
- applyCosmetics: function(domElem)
- {
- var self = this;
-
- // Add some styles on the cells even/odd
- // label (first column of a data row) or not
- $("th:first-child", domElem).addClass('label');
- $("td:first-child:odd", domElem).addClass('label labeleven');
- $("td:first-child:even", domElem).addClass('label labelodd');
- $("tr:odd td", domElem).slice(1).addClass('columnodd');
- $("tr:even td", domElem).slice(1).addClass('columneven');
-
- $('td span.label', domElem).each(function(){ self.truncate($(this)); } );
-
- },
-
- //behaviour for 'nested DataTable' (DataTable loaded on a click on a row)
- handleSubDataTable: function(domElem)
- {
- var self = this;
- // When the TR has a subDataTable class it means that this row has a link to a subDataTable
- this.numberOfSubtables = $('tr.subDataTable', domElem)
- .click(
- function()
- {
- // get the idSubTable
- var idSubTable = $(this).attr('id');
- var divIdToReplaceWithSubTable = 'subDataTable_'+idSubTable;
-
- // if the subDataTable is not already loaded
- if (typeof self.loadedSubDataTable[divIdToReplaceWithSubTable] == "undefined")
- {
- var numberOfColumns = $(this).children().length;
-
- // at the end of the query it will replace the ID matching the new HTML table #ID
- // we need to create this ID first
- $(this).after(
- '<tr>'+
- '<td colspan="'+numberOfColumns+'" class="cellSubDataTable">'+
- '<div id="'+divIdToReplaceWithSubTable+'">'+
- '<span class="loadingPiwik" style="display:inline"><img src="themes/default/images/loading-blue.gif" />'+ _pk_translate('General_Loading_js') +'</span>'+
- '</div>'+
- '</td>'+
- '</tr>'
- );
-
- var savedActionVariable = self.param.action;
-
- // reset all the filters from the Parent table
- var filtersToRestore = self.resetAllFilters();
- // do not ignore the exclude low population click
- self.param.enable_filter_excludelowpop = filtersToRestore.enable_filter_excludelowpop;
-
- self.param.idSubtable = idSubTable;
- self.param.action = self.param.controllerActionCalledWhenRequestSubTable;
- self.reloadAjaxDataTable(false);
-
- self.param.action = savedActionVariable;
- delete self.param.idSubtable;
- self.restoreAllFilters(filtersToRestore);
-
- self.loadedSubDataTable[divIdToReplaceWithSubTable] = true;
-
- $(this).next().toggle();
-
- // when "loading..." is displayed, hide actions
- // repositioning after loading is not easily possible
- $(this).find('div.dataTableRowActions').hide();
- }
-
- $(this).next().toggle();
- self.repositionRowActions($(this));
- }
- ).size();
- },
-
- // tooltip for column documentation
- handleColumnDocumentation: function(domElem)
- {
- if ($('#dashboard').size() > 0) {
- // don't display column documentation in dashboard
- // it causes trouble in full screen view
- return;
- }
-
- var self = this;
-
- $('th:has(.columnDocumentation)', domElem).each(function()
- {
- var th = $(this);
- var tooltip = th.find('.columnDocumentation');
-
- tooltip.next().hover(function()
- {
- var left = (-1 * tooltip.outerWidth() / 2) + th.width() / 2;
- var top = -1 * (tooltip.outerHeight() + 10);
-
- if (th.next().size() == 0)
- {
- left = (-1 * tooltip.outerWidth()) + th.width() +
- parseInt(th.css('padding-right'), 10);
- }
-
- tooltip.css({
- marginLeft: left,
- marginTop: top
- });
-
- tooltip.stop(true, true).fadeIn(250);
- },
- function()
- {
- $(this).prev().stop(true, true).fadeOut(400);
- });
- });
- },
-
- // documentation for report
- handleReportDocumentation: function(domElem)
- {
- // don't display report documentation in dashboard
- if ($('#dashboard').size() > 0
- // or in Widgetize screen
- || $('.widgetContent').size() > 0
- // or in Widget export
- || $('.widget').size() > 0
- ) {
- return;
- }
- domElem = $(domElem);
- var doc = domElem.find('.reportDocumentation');
-
- var h2 = this._findReportHeader(domElem);
- if (doc.size() == 0 || doc.children().size() == 0) // if we can't find the element, or the element is empty
- {
- if (h2 && h2.size() > 0)
- {
- h2.find('a.reportDocumentationIcon').addClass('hidden');
- }
- return;
- }
-
- var icon = $('<a href="#"></a>');
- var docShown = false;
-
- icon.click(function()
- {
- if (docShown)
- {
- doc.stop(true, true).fadeOut(250);
- }
- else
- {
- var widthOrientation = domElem.find('table, canvas, object').eq(0);
- if (widthOrientation.size() > 0)
- {
- var width = Math.min(widthOrientation.width(), doc.parent().innerWidth());
- doc.css('width', (width - 2) + 'px');
- }
- doc.stop(true, true).fadeIn(250);
- }
- docShown = !docShown;
- return false;
- });
-
- icon.addClass('reportDocumentationIcon');
- if (h2 && h2.size() > 0)
- {
- // handle previously added icon
- var existingIcon = h2.find('a.reportDocumentationIcon');
- if (existingIcon.size() > 0)
- {
- existingIcon.replaceWith(icon);
- }
- else
- {
- // add icon
- h2.append('&nbsp;&nbsp;&nbsp;');
- h2.append(icon);
-
- h2.hover(function()
- {
- $(this).find('a.reportDocumentationIcon').show();
- },
- function()
- {
- $(this).find('a.reportDocumentationIcon').hide();
- })
- .click(
- function()
- {
- $(this).find('a.reportDocumentationIcon').click();
- })
- .css('cursor', 'pointer');
- }
- }
- else
- {
- //domElem.prepend(icon);
- }
- },
-
- handleRowActions: function(domElem)
- {
- this.doHandleRowActions(domElem.find('table > tbody > tr'));
- },
-
- handleRelatedReports: function(domElem)
- {
- var self = this,
- hideShowRelatedReports = function(thisReport)
- {
- $('span', $(thisReport).parent().parent()).each(function () {
- if (thisReport == this)
- $(this).hide();
- else
- $(this).show();
- });
- },
- // 'this' report must be hidden in datatable output
- thisReport = $('.datatableRelatedReports span:hidden', domElem)[0];
-
- hideShowRelatedReports(thisReport);
- $('.datatableRelatedReports span', domElem).each(function() {
- var clicked = this;
- $(this).unbind('click').click(function(e) {
- var url = $(this).attr('href');
-
- // if this url is also the url of a menu item, better to click that menu item instead of
- // doing AJAX request
- var menuItem = null;
- $("#root>ul.nav a").each(function () {
- if ($(this).attr('name') == url)
- {
- menuItem = this;
- return false
- }
- });
-
- if (menuItem)
- {
- $(menuItem).click();
- return;
- }
-
- // modify parameters
- self.resetAllFilters();
- var newParams = broadcast.getValuesFromUrl(url);
- for (var key in newParams)
- {
- self.param[key] = decodeURIComponent(newParams[key]);
- }
-
- // do ajax request
- self.reloadAjaxDataTable(true, function(newReport) {
- var newDomElem = self.dataTableLoaded(newReport, self.workingDivId);
- hideShowRelatedReports(clicked);
-
- // update header, if we can find it
- var h2 = self._findReportHeader(newDomElem);
- if (h2)
- h2.text($(clicked).text());
- });
- });
- });
- },
-
- /**
- * Handle events that other code triggers on this table.
- *
- * You can trigger one of these events to get the datatable to do things,
- * such as reload its data.
- *
- * Events handled:
- * - reload: Triggering 'reload' on a datatable DOM element will
- * reload the datatable's data. You can pass in an object mapping
- * parameters to set before reloading data.
- *
- * $(datatableDomElem).trigger('reload', {columns: 'nb_visits,nb_actions', idSite: 2});
- */
- handleTriggeredEvents: function(domElem)
- {
- var self = this;
-
- // reload datatable w/ new params if desired (NOTE: must use 'bind', not 'on')
- $(domElem).bind('reload', function(e, paramOverride) {
- paramOverride = paramOverride || {};
- for (var name in paramOverride)
- {
- self.param[name] = paramOverride[name];
- };
-
- self.reloadAjaxDataTable(true);
- });
- },
-
- // also used in action data table
- doHandleRowActions: function(trs)
- {
- var self = this;
-
- var availableActionsForReport = DataTable_RowActions_Registry
- .getAvailableActionsForReport(self.param);
-
- if (availableActionsForReport.length == 0)
- {
- return;
- }
-
- var actionInstances = {};
- for (var i = 0; i < availableActionsForReport.length; i++)
- {
- var action = availableActionsForReport[i];
- actionInstances[action.name] = action.createInstance(self);
- }
-
- trs.each(function()
- {
- var tr = $(this);
- var td = tr.find('td:first');
-
- // call initTr on all actions that are available for the report
- for (var i = 0; i < availableActionsForReport.length; i++)
- {
- var action = availableActionsForReport[i];
- actionInstances[action.name].initTr(tr);
- }
-
- // if there are row actions, make sure the first column is not too narrow
- td.css('minWidth', '145px');
-
- // show actions that are available for the row on hover
- var actionsDom = null;
- tr.hover(function()
- {
- if (actionsDom === null)
- {
- // create dom nodes on the fly
- actionsDom = self.createRowActions(availableActionsForReport, tr, actionInstances);
- td.prepend(actionsDom);
- }
- // reposition and show the actions
- self.repositionRowActions(tr);
- actionsDom.show();
- },
- function()
- {
- if (actionsDom !== null)
- {
- actionsDom.hide();
- }
- });
- });
- },
-
- createRowActions: function(availableActionsForReport, tr, actionInstances)
- {
- var container = $(document.createElement('div')).addClass('dataTableRowActions');
-
- for (var i = availableActionsForReport.length - 1; i >= 0; i--)
- {
- var action = availableActionsForReport[i];
-
- if (!action.isAvailableOnRow(this.param, tr)) {
- continue;
- }
-
- var actionEl = $(document.createElement('a')).attr({href: '#'}).addClass('action' + action.name);
- actionEl.append($(document.createElement('img')).attr({src: action.dataTableIcon}));
- container.append(actionEl);
-
- if (i == availableActionsForReport.length - 1) {
- actionEl.addClass('leftmost');
- }
- if (i == 0) {
- actionEl.addClass('rightmost');
- }
-
- actionEl.click((function(action, el)
- {
- return function(e)
- {
- $(this).blur();
- container.hide();
- Piwik_Tooltip.hide();
- if (typeof actionInstances[action.name].onClick == 'function') {
- return actionInstances[action.name].onClick(el, tr, e);
- }
- actionInstances[action.name].trigger(tr, e);
- return false;
- }
- })(action, actionEl));
-
- if (typeof action.dataTableIconHover != 'undefined')
- {
- actionEl.append($(document.createElement('img')).attr({src: action.dataTableIconHover}).hide());
-
- actionEl.hover(function()
- {
- var img = $(this).find('img');
- img.eq(0).hide();
- img.eq(1).show();
- },
- function()
- {
- var img = $(this).find('img');
- img.eq(1).hide();
- img.eq(0).show();
- });
- }
-
- if (typeof action.dataTableIconTooltip != 'undefined')
- {
- actionEl.hover((function(action)
- {
- return function() {
- Piwik_Tooltip.showWithTitle(
- action.dataTableIconTooltip[0],
- action.dataTableIconTooltip[1],
- 'rowActionTooltip');
- };
- })(action), function()
- {
- Piwik_Tooltip.hide();
- });
- }
- }
-
- return container;
- },
-
- repositionRowActions: function(tr) {
- var td = tr.find('td:first');
- var actions = tr.find('div.dataTableRowActions');
- actions.height(tr.innerHeight() - 2);
- actions.css('marginLeft', (td.width() + 5 - actions.outerWidth()) + 'px');
- },
-
- _findReportHeader: function(domElem) {
- var h2 = false;
- if (domElem.prev().is('h2'))
- {
- h2 = domElem.prev();
- }
- else if (this.param.viewDataTable == 'tableGoals')
- {
- h2 = $('#titleGoalsByDimension');
- }
- else if( $('h2', domElem))
- {
- h2 = $('h2', domElem);
- }
- return h2;
- }
-};
+ },
+
+ truncate: function (domElemToTruncate, truncationOffset) {
+ var self = this;
+
+ domElemToTruncate = $(domElemToTruncate);
+
+ if (typeof domElemToTruncate.data('originalText') != 'undefined') {
+ // truncate only once. otherwise, the tooltip will show the truncated text as well.
+ return;
+ }
+
+ // make the original text (before truncation) available for others.
+ // the .truncate plugins adds a title to the dom element but the .tooltip
+ // plugin removes that again.
+ domElemToTruncate.data('originalText', domElemToTruncate.text());
+
+ if (typeof truncationOffset == 'undefined') {
+ truncationOffset = 0;
+ }
+ var truncationLimit = 50;
+
+ if (typeof self.param.idSubtable == 'undefined'
+ && self.param.viewDataTable == 'tableAllColumns') {
+ // when showing all columns in a subtable, space is restricted
+ truncationLimit = 25;
+ }
+
+ truncationLimit += truncationOffset;
+ domElemToTruncate.truncate(truncationLimit);
+
+ var tooltipElem = $('.truncated', domElemToTruncate),
+ customToolTipText = domElemToTruncate.attr('title');
+
+ // if there's a title on the dom element, use this as the tooltip instead of
+ // the one set by the truncate plugin
+ if (customToolTipText) {
+ // make sure browser doesn't add its own tooltip for the truncated element
+ if (tooltipElem[0]) {
+ tooltipElem.removeAttr('title');
+ }
+
+ tooltipElem = domElemToTruncate;
+ tooltipElem.attr('title', customToolTipText);
+ }
+
+ // use tooltip (tooltip text determined by the 'title' attribute)
+ tooltipElem.tooltip();
+ },
+
+ //Apply some miscelleaneous style to the DataTable
+ applyCosmetics: function (domElem) {
+ var self = this;
+
+ // Add some styles on the cells even/odd
+ // label (first column of a data row) or not
+ $("th:first-child", domElem).addClass('label');
+ $("td:first-child:odd", domElem).addClass('label labeleven');
+ $("td:first-child:even", domElem).addClass('label labelodd');
+ $("tr:odd td", domElem).slice(1).addClass('columnodd');
+ $("tr:even td", domElem).slice(1).addClass('columneven');
+
+ $('td span.label', domElem).each(function () { self.truncate($(this)); });
+
+ },
+
+ //behaviour for 'nested DataTable' (DataTable loaded on a click on a row)
+ handleSubDataTable: function (domElem) {
+ var self = this;
+ // When the TR has a subDataTable class it means that this row has a link to a subDataTable
+ this.numberOfSubtables = $('tr.subDataTable', domElem)
+ .click(
+ function () {
+ // get the idSubTable
+ var idSubTable = $(this).attr('id');
+ var divIdToReplaceWithSubTable = 'subDataTable_' + idSubTable;
+
+ // if the subDataTable is not already loaded
+ if (typeof self.loadedSubDataTable[divIdToReplaceWithSubTable] == "undefined") {
+ var numberOfColumns = $(this).children().length;
+
+ // at the end of the query it will replace the ID matching the new HTML table #ID
+ // we need to create this ID first
+ $(this).after(
+ '<tr>' +
+ '<td colspan="' + numberOfColumns + '" class="cellSubDataTable">' +
+ '<div id="' + divIdToReplaceWithSubTable + '">' +
+ '<span class="loadingPiwik" style="display:inline"><img src="themes/default/images/loading-blue.gif" />' + _pk_translate('General_Loading_js') + '</span>' +
+ '</div>' +
+ '</td>' +
+ '</tr>'
+ );
+
+ var savedActionVariable = self.param.action;
+
+ // reset all the filters from the Parent table
+ var filtersToRestore = self.resetAllFilters();
+ // do not ignore the exclude low population click
+ self.param.enable_filter_excludelowpop = filtersToRestore.enable_filter_excludelowpop;
+
+ self.param.idSubtable = idSubTable;
+ self.param.action = self.param.controllerActionCalledWhenRequestSubTable;
+ self.reloadAjaxDataTable(false);
+
+ self.param.action = savedActionVariable;
+ delete self.param.idSubtable;
+ self.restoreAllFilters(filtersToRestore);
+
+ self.loadedSubDataTable[divIdToReplaceWithSubTable] = true;
+
+ $(this).next().toggle();
+
+ // when "loading..." is displayed, hide actions
+ // repositioning after loading is not easily possible
+ $(this).find('div.dataTableRowActions').hide();
+ }
+
+ $(this).next().toggle();
+ self.repositionRowActions($(this));
+ }
+ ).size();
+ },
+
+ // tooltip for column documentation
+ handleColumnDocumentation: function (domElem) {
+ if ($('#dashboard').size() > 0) {
+ // don't display column documentation in dashboard
+ // it causes trouble in full screen view
+ return;
+ }
+
+ var self = this;
+
+ $('th:has(.columnDocumentation)', domElem).each(function () {
+ var th = $(this);
+ var tooltip = th.find('.columnDocumentation');
+
+ tooltip.next().hover(function () {
+ var left = (-1 * tooltip.outerWidth() / 2) + th.width() / 2;
+ var top = -1 * (tooltip.outerHeight() + 10);
+
+ if (th.next().size() == 0) {
+ left = (-1 * tooltip.outerWidth()) + th.width() +
+ parseInt(th.css('padding-right'), 10);
+ }
+
+ tooltip.css({
+ marginLeft: left,
+ marginTop: top
+ });
+
+ tooltip.stop(true, true).fadeIn(250);
+ },
+ function () {
+ $(this).prev().stop(true, true).fadeOut(400);
+ });
+ });
+ },
+
+ // documentation for report
+ handleReportDocumentation: function (domElem) {
+ // don't display report documentation in dashboard
+ if ($('#dashboard').size() > 0
+ // or in Widgetize screen
+ || $('.widgetContent').size() > 0
+ // or in Widget export
+ || $('.widget').size() > 0
+ ) {
+ return;
+ }
+ domElem = $(domElem);
+ var doc = domElem.find('.reportDocumentation');
+
+ var h2 = this._findReportHeader(domElem);
+ if (doc.size() == 0 || doc.children().size() == 0) // if we can't find the element, or the element is empty
+ {
+ if (h2 && h2.size() > 0) {
+ h2.find('a.reportDocumentationIcon').addClass('hidden');
+ }
+ return;
+ }
+
+ var icon = $('<a href="#"></a>');
+ var docShown = false;
+
+ icon.click(function () {
+ if (docShown) {
+ doc.stop(true, true).fadeOut(250);
+ }
+ else {
+ var widthOrientation = domElem.find('table, canvas, object').eq(0);
+ if (widthOrientation.size() > 0) {
+ var width = Math.min(widthOrientation.width(), doc.parent().innerWidth());
+ doc.css('width', (width - 2) + 'px');
+ }
+ doc.stop(true, true).fadeIn(250);
+ }
+ docShown = !docShown;
+ return false;
+ });
+
+ icon.addClass('reportDocumentationIcon');
+ if (h2 && h2.size() > 0) {
+ // handle previously added icon
+ var existingIcon = h2.find('a.reportDocumentationIcon');
+ if (existingIcon.size() > 0) {
+ existingIcon.replaceWith(icon);
+ }
+ else {
+ // add icon
+ h2.append('&nbsp;&nbsp;&nbsp;');
+ h2.append(icon);
+
+ h2.hover(function () {
+ $(this).find('a.reportDocumentationIcon').show();
+ },
+ function () {
+ $(this).find('a.reportDocumentationIcon').hide();
+ })
+ .click(
+ function () {
+ $(this).find('a.reportDocumentationIcon').click();
+ })
+ .css('cursor', 'pointer');
+ }
+ }
+ else {
+ //domElem.prepend(icon);
+ }
+ },
+
+ handleRowActions: function (domElem) {
+ this.doHandleRowActions(domElem.find('table > tbody > tr'));
+ },
+
+ handleRelatedReports: function (domElem) {
+ var self = this,
+ hideShowRelatedReports = function (thisReport) {
+ $('span', $(thisReport).parent().parent()).each(function () {
+ if (thisReport == this)
+ $(this).hide();
+ else
+ $(this).show();
+ });
+ },
+ // 'this' report must be hidden in datatable output
+ thisReport = $('.datatableRelatedReports span:hidden', domElem)[0];
+
+ hideShowRelatedReports(thisReport);
+ $('.datatableRelatedReports span', domElem).each(function () {
+ var clicked = this;
+ $(this).unbind('click').click(function (e) {
+ var url = $(this).attr('href');
+
+ // if this url is also the url of a menu item, better to click that menu item instead of
+ // doing AJAX request
+ var menuItem = null;
+ $("#root>ul.nav a").each(function () {
+ if ($(this).attr('name') == url) {
+ menuItem = this;
+ return false
+ }
+ });
+
+ if (menuItem) {
+ $(menuItem).click();
+ return;
+ }
+
+ // modify parameters
+ self.resetAllFilters();
+ var newParams = broadcast.getValuesFromUrl(url);
+ for (var key in newParams) {
+ self.param[key] = decodeURIComponent(newParams[key]);
+ }
+
+ // do ajax request
+ self.reloadAjaxDataTable(true, function (newReport) {
+ var newDomElem = self.dataTableLoaded(newReport, self.workingDivId);
+ hideShowRelatedReports(clicked);
+
+ // update header, if we can find it
+ var h2 = self._findReportHeader(newDomElem);
+ if (h2)
+ h2.text($(clicked).text());
+ });
+ });
+ });
+ },
+
+ /**
+ * Handle events that other code triggers on this table.
+ *
+ * You can trigger one of these events to get the datatable to do things,
+ * such as reload its data.
+ *
+ * Events handled:
+ * - reload: Triggering 'reload' on a datatable DOM element will
+ * reload the datatable's data. You can pass in an object mapping
+ * parameters to set before reloading data.
+ *
+ * $(datatableDomElem).trigger('reload', {columns: 'nb_visits,nb_actions', idSite: 2});
+ */
+ handleTriggeredEvents: function (domElem) {
+ var self = this;
+
+ // reload datatable w/ new params if desired (NOTE: must use 'bind', not 'on')
+ $(domElem).bind('reload', function (e, paramOverride) {
+ paramOverride = paramOverride || {};
+ for (var name in paramOverride) {
+ self.param[name] = paramOverride[name];
+ }
+ ;
+
+ self.reloadAjaxDataTable(true);
+ });
+ },
+
+ // also used in action data table
+ doHandleRowActions: function (trs) {
+ var self = this;
+
+ var availableActionsForReport = DataTable_RowActions_Registry
+ .getAvailableActionsForReport(self.param);
+
+ if (availableActionsForReport.length == 0) {
+ return;
+ }
+
+ var actionInstances = {};
+ for (var i = 0; i < availableActionsForReport.length; i++) {
+ var action = availableActionsForReport[i];
+ actionInstances[action.name] = action.createInstance(self);
+ }
+
+ trs.each(function () {
+ var tr = $(this);
+ var td = tr.find('td:first');
+ // call initTr on all actions that are available for the report
+ for (var i = 0; i < availableActionsForReport.length; i++) {
+ var action = availableActionsForReport[i];
+ actionInstances[action.name].initTr(tr);
+ }
+
+ // if there are row actions, make sure the first column is not too narrow
+ td.css('minWidth', '145px');
+
+ // show actions that are available for the row on hover
+ var actionsDom = null;
+ tr.hover(function () {
+ if (actionsDom === null) {
+ // create dom nodes on the fly
+ actionsDom = self.createRowActions(availableActionsForReport, tr, actionInstances);
+ td.prepend(actionsDom);
+ }
+ // reposition and show the actions
+ self.repositionRowActions(tr);
+ actionsDom.show();
+ },
+ function () {
+ if (actionsDom !== null) {
+ actionsDom.hide();
+ }
+ });
+ });
+ },
+
+ createRowActions: function (availableActionsForReport, tr, actionInstances) {
+ var container = $(document.createElement('div')).addClass('dataTableRowActions');
+
+ for (var i = availableActionsForReport.length - 1; i >= 0; i--) {
+ var action = availableActionsForReport[i];
+
+ if (!action.isAvailableOnRow(this.param, tr)) {
+ continue;
+ }
+
+ var actionEl = $(document.createElement('a')).attr({href: '#'}).addClass('action' + action.name);
+ actionEl.append($(document.createElement('img')).attr({src: action.dataTableIcon}));
+ container.append(actionEl);
+
+ if (i == availableActionsForReport.length - 1) {
+ actionEl.addClass('leftmost');
+ }
+ if (i == 0) {
+ actionEl.addClass('rightmost');
+ }
+
+ actionEl.click((function (action, el) {
+ return function (e) {
+ $(this).blur();
+ container.hide();
+ Piwik_Tooltip.hide();
+ if (typeof actionInstances[action.name].onClick == 'function') {
+ return actionInstances[action.name].onClick(el, tr, e);
+ }
+ actionInstances[action.name].trigger(tr, e);
+ return false;
+ }
+ })(action, actionEl));
+
+ if (typeof action.dataTableIconHover != 'undefined') {
+ actionEl.append($(document.createElement('img')).attr({src: action.dataTableIconHover}).hide());
+
+ actionEl.hover(function () {
+ var img = $(this).find('img');
+ img.eq(0).hide();
+ img.eq(1).show();
+ },
+ function () {
+ var img = $(this).find('img');
+ img.eq(1).hide();
+ img.eq(0).show();
+ });
+ }
+
+ if (typeof action.dataTableIconTooltip != 'undefined') {
+ actionEl.hover((function (action) {
+ return function () {
+ Piwik_Tooltip.showWithTitle(
+ action.dataTableIconTooltip[0],
+ action.dataTableIconTooltip[1],
+ 'rowActionTooltip');
+ };
+ })(action), function () {
+ Piwik_Tooltip.hide();
+ });
+ }
+ }
+ return container;
+ },
+ repositionRowActions: function (tr) {
+ var td = tr.find('td:first');
+ var actions = tr.find('div.dataTableRowActions');
+ actions.height(tr.innerHeight() - 2);
+ actions.css('marginLeft', (td.width() + 5 - actions.outerWidth()) + 'px');
+ },
+ _findReportHeader: function (domElem) {
+ var h2 = false;
+ if (domElem.prev().is('h2')) {
+ h2 = domElem.prev();
+ }
+ else if (this.param.viewDataTable == 'tableGoals') {
+ h2 = $('#titleGoalsByDimension');
+ }
+ else if ($('h2', domElem)) {
+ h2 = $('h2', domElem);
+ }
+ return h2;
+ }
+};
//-----------------------------------------------------------------------------
@@ -1685,364 +1526,334 @@ actionDataTable.prototype = new dataTable;
actionDataTable.prototype.constructor = actionDataTable;
//actionDataTable constructor
-function actionDataTable()
-{
- dataTable.call(this);
- this.parentAttributeParent = '';
- this.parentId = '';
- this.disabledRowDom = {}; //to handle double click on '+' row
+function actionDataTable() {
+ dataTable.call(this);
+ this.parentAttributeParent = '';
+ this.parentId = '';
+ this.disabledRowDom = {}; //to handle double click on '+' row
}
//Prototype of the actionDataTable object
actionDataTable.prototype =
-{
- //method inheritance
- cleanParams: dataTable.prototype.cleanParams,
- reloadAjaxDataTable: dataTable.prototype.reloadAjaxDataTable,
- handleConfigurationBox: dataTable.prototype.handleConfigurationBox,
- handleSearchBox: dataTable.prototype.handleSearchBox,
- handleAnnotationsButton: dataTable.prototype.handleAnnotationsButton,
- handleExportBox: dataTable.prototype.handleExportBox,
- handleSort: dataTable.prototype.handleSort,
- handleColumnDocumentation: dataTable.prototype.handleColumnDocumentation,
- handleReportDocumentation: dataTable.prototype.handleReportDocumentation,
- doHandleRowActions: dataTable.prototype.doHandleRowActions,
- createRowActions: dataTable.prototype.createRowActions,
- repositionRowActions: dataTable.prototype.repositionRowActions,
- onClickSort: dataTable.prototype.onClickSort,
- truncate: dataTable.prototype.truncate,
- handleOffsetInformation: dataTable.prototype.handleOffsetInformation,
- setActiveIcon: dataTable.prototype.setActiveIcon,
- resetAllFilters: dataTable.prototype.resetAllFilters,
- restoreAllFilters: dataTable.prototype.restoreAllFilters,
- exportToFormatHide: dataTable.prototype.exportToFormatHide,
- handleLimit: dataTable.prototype.handleLimit,
- notifyWidgetParametersChange: dataTable.prototype.notifyWidgetParametersChange,
- handleRelatedReports: dataTable.prototype.handleRelatedReports,
- handleTriggeredEvents: dataTable.prototype.handleTriggeredEvents,
- _findReportHeader: dataTable.prototype._findReportHeader,
-
- //initialisation of the actionDataTable
- init: function(workingDivId, domElem)
- {
- if(typeof domElem == "undefined"
- || domElem.length == 0 ) // needed for actions subtables where truncating was not working otherwise
- {
- domElem = $('#'+workingDivId);
- }
- this.workingDivId = workingDivId;
- this.bindEventsAndApplyStyle(domElem);
- this.initialized = true;
-
- domElem.data('piwikDataTable', this);
- },
-
- //see dataTable::bindEventsAndApplyStyle
- bindEventsAndApplyStyle: function(domElem)
- {
- var self = this;
-
- self.cleanParams();
-
- // we dont display the link on the row with subDataTable when we are already
- // printing all the subTables (case of recursive search when the content is
- // including recursively all the subtables
- if(!self.param.filter_pattern_recursive)
- {
- self.numberOfSubtables = $('tr.subActionsDataTable.rowToProcess').click( function() {
- self.onClickActionSubDataTable(this)
- }).size();
- }
-
- self.applyCosmetics(domElem);
- self.handleRowActions(domElem);
- self.handleLimit(domElem);
- self.handleAnnotationsButton(domElem);
- self.handleExportBox(domElem);
- self.handleSort(domElem);
- self.handleOffsetInformation(domElem);
- if( self.workingDivId != undefined)
- {
- var dataTableLoadedProxy = function (response) {
- self.dataTableLoaded(response, self.workingDivId);
- };
-
- self.handleSearchBox(domElem, dataTableLoadedProxy);
- self.handleConfigurationBox(domElem, dataTableLoadedProxy);
- }
-
- self.handleColumnDocumentation(domElem);
- self.handleReportDocumentation(domElem);
- self.handleRelatedReports(domElem);
- self.handleTriggeredEvents(domElem);
- },
-
- //see dataTable::applyCosmetics
- applyCosmetics: function(domElem)
- {
- var self = this;
-
- $('tr.subActionsDataTable.rowToProcess')
- .css('font-weight','bold');
-
- $("th:first-child", domElem).addClass('label');
- $('td span.label', domElem).each(function(){ self.truncate($(this)); } );
- var imagePlusMinusWidth = 12;
- var imagePlusMinusHeight = 12;
- $('tr.subActionsDataTable.rowToProcess td:first-child')
- .each( function(){
- $(this).prepend('<img width="'+imagePlusMinusWidth+'" height="'+imagePlusMinusHeight+'" class="plusMinus" src="" />');
- if(self.param.filter_pattern_recursive)
- {
- setImageMinus(this);
- }
- else
- {
- setImagePlus(this);
- }
- });
-
- $('tr.rowToProcess')
- .each( function() {
- // we add the CSS style depending on the level of the current loading category
- // we look at the style of the parent row
- var style = $(this).prev().attr('class');
- var currentStyle = $(this).attr('class');
-
- if( (typeof currentStyle != 'undefined')
- && currentStyle.indexOf('level') >= 0 )
- {
- }
- else
- {
- var level = getNextLevelFromClass( style );
- $(this).addClass('level'+ level);
- }
-
- // we add an attribute parent that contains the ID of all the parent categories
- // this ID is used when collapsing a parent row, it searches for all children rows
- // which 'parent' attribute's value contains the collapsed row ID
- $(this).prop('parent', function(){
- return self.parentAttributeParent + ' ' + self.parentId;
- }
- );
-
- // Add some styles on the cells even/odd
- // label (first column of a data row) or not
- $("td:first-child:odd", this).addClass('label labeleven');
- $("td:first-child:even", this).addClass('label labelodd');
- });
- },
-
- handleRowActions: function(domElem)
- {
- var rowsToProcess = $('tr.rowToProcess').removeClass('rowToProcess');
- this.doHandleRowActions(rowsToProcess);
- },
-
- // Called when the user click on an actionDataTable row
- onClickActionSubDataTable: function(domElem)
- {
- var self = this;
-
- // get the idSubTable
- var idSubTable = $(domElem).attr('id');
-
- var divIdToReplaceWithSubTable = 'subDataTable_'+idSubTable;
-
- var NextStyle = $(domElem).next().attr('class');
- var CurrentStyle = $(domElem).attr('class');
-
- var currentRowLevel = getLevelFromClass(CurrentStyle);
- var nextRowLevel = getLevelFromClass(NextStyle);
-
- // if the row has not been clicked
- // which is the same as saying that the next row level is equal or less than the current row
- // because when we click a row the level of the next rows is higher (level2 row gives level3 rows)
- if(currentRowLevel >= nextRowLevel)
- {
- //unbind click to avoid double click problem
- $(domElem).off('click');
- self.disabledRowDom = $(domElem);
-
- var numberOfColumns = $(domElem).children().length;
- $(domElem).after( '\
- <tr id="'+divIdToReplaceWithSubTable+'" class="cellSubDataTable">\
- <td colspan="'+numberOfColumns+'">\
+{
+ //method inheritance
+ cleanParams: dataTable.prototype.cleanParams,
+ reloadAjaxDataTable: dataTable.prototype.reloadAjaxDataTable,
+ handleConfigurationBox: dataTable.prototype.handleConfigurationBox,
+ handleSearchBox: dataTable.prototype.handleSearchBox,
+ handleAnnotationsButton: dataTable.prototype.handleAnnotationsButton,
+ handleExportBox: dataTable.prototype.handleExportBox,
+ handleSort: dataTable.prototype.handleSort,
+ handleColumnDocumentation: dataTable.prototype.handleColumnDocumentation,
+ handleReportDocumentation: dataTable.prototype.handleReportDocumentation,
+ doHandleRowActions: dataTable.prototype.doHandleRowActions,
+ createRowActions: dataTable.prototype.createRowActions,
+ repositionRowActions: dataTable.prototype.repositionRowActions,
+ onClickSort: dataTable.prototype.onClickSort,
+ truncate: dataTable.prototype.truncate,
+ handleOffsetInformation: dataTable.prototype.handleOffsetInformation,
+ setActiveIcon: dataTable.prototype.setActiveIcon,
+ resetAllFilters: dataTable.prototype.resetAllFilters,
+ restoreAllFilters: dataTable.prototype.restoreAllFilters,
+ exportToFormatHide: dataTable.prototype.exportToFormatHide,
+ handleLimit: dataTable.prototype.handleLimit,
+ notifyWidgetParametersChange: dataTable.prototype.notifyWidgetParametersChange,
+ handleRelatedReports: dataTable.prototype.handleRelatedReports,
+ handleTriggeredEvents: dataTable.prototype.handleTriggeredEvents,
+ _findReportHeader: dataTable.prototype._findReportHeader,
+
+ //initialisation of the actionDataTable
+ init: function (workingDivId, domElem) {
+ if (typeof domElem == "undefined"
+ || domElem.length == 0) // needed for actions subtables where truncating was not working otherwise
+ {
+ domElem = $('#' + workingDivId);
+ }
+ this.workingDivId = workingDivId;
+ this.bindEventsAndApplyStyle(domElem);
+ this.initialized = true;
+
+ domElem.data('piwikDataTable', this);
+ },
+
+ //see dataTable::bindEventsAndApplyStyle
+ bindEventsAndApplyStyle: function (domElem) {
+ var self = this;
+
+ self.cleanParams();
+
+ // we dont display the link on the row with subDataTable when we are already
+ // printing all the subTables (case of recursive search when the content is
+ // including recursively all the subtables
+ if (!self.param.filter_pattern_recursive) {
+ self.numberOfSubtables = $('tr.subActionsDataTable.rowToProcess').click(function () {
+ self.onClickActionSubDataTable(this)
+ }).size();
+ }
+
+ self.applyCosmetics(domElem);
+ self.handleRowActions(domElem);
+ self.handleLimit(domElem);
+ self.handleAnnotationsButton(domElem);
+ self.handleExportBox(domElem);
+ self.handleSort(domElem);
+ self.handleOffsetInformation(domElem);
+ if (self.workingDivId != undefined) {
+ var dataTableLoadedProxy = function (response) {
+ self.dataTableLoaded(response, self.workingDivId);
+ };
+
+ self.handleSearchBox(domElem, dataTableLoadedProxy);
+ self.handleConfigurationBox(domElem, dataTableLoadedProxy);
+ }
+
+ self.handleColumnDocumentation(domElem);
+ self.handleReportDocumentation(domElem);
+ self.handleRelatedReports(domElem);
+ self.handleTriggeredEvents(domElem);
+ },
+
+ //see dataTable::applyCosmetics
+ applyCosmetics: function (domElem) {
+ var self = this;
+
+ $('tr.subActionsDataTable.rowToProcess')
+ .css('font-weight', 'bold');
+
+ $("th:first-child", domElem).addClass('label');
+ $('td span.label', domElem).each(function () { self.truncate($(this)); });
+ var imagePlusMinusWidth = 12;
+ var imagePlusMinusHeight = 12;
+ $('tr.subActionsDataTable.rowToProcess td:first-child')
+ .each(function () {
+ $(this).prepend('<img width="' + imagePlusMinusWidth + '" height="' + imagePlusMinusHeight + '" class="plusMinus" src="" />');
+ if (self.param.filter_pattern_recursive) {
+ setImageMinus(this);
+ }
+ else {
+ setImagePlus(this);
+ }
+ });
+
+ $('tr.rowToProcess')
+ .each(function () {
+ // we add the CSS style depending on the level of the current loading category
+ // we look at the style of the parent row
+ var style = $(this).prev().attr('class');
+ var currentStyle = $(this).attr('class');
+
+ if ((typeof currentStyle != 'undefined')
+ && currentStyle.indexOf('level') >= 0) {
+ }
+ else {
+ var level = getNextLevelFromClass(style);
+ $(this).addClass('level' + level);
+ }
+
+ // we add an attribute parent that contains the ID of all the parent categories
+ // this ID is used when collapsing a parent row, it searches for all children rows
+ // which 'parent' attribute's value contains the collapsed row ID
+ $(this).prop('parent', function () {
+ return self.parentAttributeParent + ' ' + self.parentId;
+ }
+ );
+
+ // Add some styles on the cells even/odd
+ // label (first column of a data row) or not
+ $("td:first-child:odd", this).addClass('label labeleven');
+ $("td:first-child:even", this).addClass('label labelodd');
+ });
+ },
+
+ handleRowActions: function (domElem) {
+ var rowsToProcess = $('tr.rowToProcess').removeClass('rowToProcess');
+ this.doHandleRowActions(rowsToProcess);
+ },
+
+ // Called when the user click on an actionDataTable row
+ onClickActionSubDataTable: function (domElem) {
+ var self = this;
+
+ // get the idSubTable
+ var idSubTable = $(domElem).attr('id');
+
+ var divIdToReplaceWithSubTable = 'subDataTable_' + idSubTable;
+
+ var NextStyle = $(domElem).next().attr('class');
+ var CurrentStyle = $(domElem).attr('class');
+
+ var currentRowLevel = getLevelFromClass(CurrentStyle);
+ var nextRowLevel = getLevelFromClass(NextStyle);
+
+ // if the row has not been clicked
+ // which is the same as saying that the next row level is equal or less than the current row
+ // because when we click a row the level of the next rows is higher (level2 row gives level3 rows)
+ if (currentRowLevel >= nextRowLevel) {
+ //unbind click to avoid double click problem
+ $(domElem).off('click');
+ self.disabledRowDom = $(domElem);
+
+ var numberOfColumns = $(domElem).children().length;
+ $(domElem).after('\
+ <tr id="' + divIdToReplaceWithSubTable + '" class="cellSubDataTable">\
+ <td colspan="' + numberOfColumns + '">\
<span class="loadingPiwik" style="display:inline"><img src="themes/default/images/loading-blue.gif" /> Loading...</span>\
</td>\
</tr>\
');
- var savedActionVariable = self.param.action;
-
- // reset all the filters from the Parent table
- var filtersToRestore = self.resetAllFilters();
-
- // Do not reset the sorting filters that must be applied to sub tables
- this.param['filter_sort_column'] = filtersToRestore['filter_sort_column'];
- this.param['filter_sort_order'] = filtersToRestore['filter_sort_order'];
- this.param['enable_filter_excludelowpop'] = filtersToRestore['enable_filter_excludelowpop'];
-
- self.param.idSubtable = idSubTable;
- self.param.action = self.param.controllerActionCalledWhenRequestSubTable;
-
- self.reloadAjaxDataTable(false, function(resp){
- self.actionsSubDataTableLoaded(resp);
- self.repositionRowActions($(domElem));
- });
- self.param.action = savedActionVariable;
-
- self.restoreAllFilters(filtersToRestore);
-
- delete self.param.idSubtable;
- }
- // else we toggle all these rows
- else
- {
- var plusDetected = $('td img.plusMinus', domElem).attr('src').indexOf('plus') >= 0;
-
- $(domElem).siblings().each( function(){
- var parents = $(this).prop('parent').split(' ');
- if(parents)
- {
- if(parents.indexOf(idSubTable) >= 0
- || parents.indexOf('subDataTable_'+idSubTable) >= 0)
- {
- if(plusDetected)
- {
- $(this).css('display','');
-
- //unroll everything and display '-' sign
- //if the row is already opened
- var NextStyle = $(this).next().attr('class');
- var CurrentStyle = $(this).attr('class');
-
- var currentRowLevel = getLevelFromClass(CurrentStyle);
- var nextRowLevel = getLevelFromClass(NextStyle);
-
- if(currentRowLevel < nextRowLevel)
- setImageMinus(this);
- }
- else
- {
- $(this).css('display','none');
- }
- self.repositionRowActions($(domElem));
- }
- }
- });
- }
-
- // toggle the +/- image
- var plusDetected = $('td img.plusMinus', domElem).attr('src').indexOf('plus') >= 0;
- if(plusDetected)
- {
- setImageMinus(domElem);
- }
- else
- {
- setImagePlus(domElem);
- }
- },
-
- //called when the full table actions is loaded
- dataTableLoaded: function(response, workingDivId)
- {
- var content = $(response);
- var idToReplace = workingDivId || $(content).attr('id');
-
- //reset parents id
- self.parentAttributeParent = '';
- self.parentId = '';
-
- var dataTableSel = $('#'+idToReplace);
-
- // keep the original list of related reports
- var oldReportsElem = $('.datatableRelatedReports', dataTableSel);
- $('.datatableRelatedReports', content).replaceWith(oldReportsElem);
-
- dataTableSel.replaceWith(content);
- piwikHelper.lazyScrollTo(content[0], 400);
-
- return content;
- },
-
- // Called when a set of rows for a category of actions is loaded
- actionsSubDataTableLoaded: function(response)
- {
- var self = this;
- var idToReplace = $(response).attr('id');
-
- // remove the first row of results which is only used to get the Id
- var response = $(response).filter('tr').slice(1).addClass('rowToProcess');
- self.parentAttributeParent = $('tr#'+idToReplace).prev().prop('parent');
- self.parentId = idToReplace;
-
- $('tr#'+idToReplace).after( response ).remove();
-
- var missingColumns = (response.prev().find('td').size() - response.find('td').size());
- for (var i = 0; i < missingColumns; i++) {
- // if the subtable has fewer columns than the parent table, add some columns.
- // this happens for example, when the parent table has performance metrics and the subtable doesn't.
- response.append('<td>-</td>');
- }
-
- var re = /subDataTable_(\d+)/;
- ok = re.exec(self.parentId);
- if(ok)
- {
- self.parentId = ok[1];
- }
-
- // we execute the bindDataTableEvent function for the new DIV
- self.init(self.workingDivId, $('#'+idToReplace));
-
- //bind back the click event (disabled to avoid double-click problem)
- self.disabledRowDom.click(
- function()
- {
- self.onClickActionSubDataTable(this)
- });
- }
+ var savedActionVariable = self.param.action;
+
+ // reset all the filters from the Parent table
+ var filtersToRestore = self.resetAllFilters();
+
+ // Do not reset the sorting filters that must be applied to sub tables
+ this.param['filter_sort_column'] = filtersToRestore['filter_sort_column'];
+ this.param['filter_sort_order'] = filtersToRestore['filter_sort_order'];
+ this.param['enable_filter_excludelowpop'] = filtersToRestore['enable_filter_excludelowpop'];
+
+ self.param.idSubtable = idSubTable;
+ self.param.action = self.param.controllerActionCalledWhenRequestSubTable;
+
+ self.reloadAjaxDataTable(false, function (resp) {
+ self.actionsSubDataTableLoaded(resp);
+ self.repositionRowActions($(domElem));
+ });
+ self.param.action = savedActionVariable;
+
+ self.restoreAllFilters(filtersToRestore);
+
+ delete self.param.idSubtable;
+ }
+ // else we toggle all these rows
+ else {
+ var plusDetected = $('td img.plusMinus', domElem).attr('src').indexOf('plus') >= 0;
+
+ $(domElem).siblings().each(function () {
+ var parents = $(this).prop('parent').split(' ');
+ if (parents) {
+ if (parents.indexOf(idSubTable) >= 0
+ || parents.indexOf('subDataTable_' + idSubTable) >= 0) {
+ if (plusDetected) {
+ $(this).css('display', '');
+
+ //unroll everything and display '-' sign
+ //if the row is already opened
+ var NextStyle = $(this).next().attr('class');
+ var CurrentStyle = $(this).attr('class');
+
+ var currentRowLevel = getLevelFromClass(CurrentStyle);
+ var nextRowLevel = getLevelFromClass(NextStyle);
+
+ if (currentRowLevel < nextRowLevel)
+ setImageMinus(this);
+ }
+ else {
+ $(this).css('display', 'none');
+ }
+ self.repositionRowActions($(domElem));
+ }
+ }
+ });
+ }
+
+ // toggle the +/- image
+ var plusDetected = $('td img.plusMinus', domElem).attr('src').indexOf('plus') >= 0;
+ if (plusDetected) {
+ setImageMinus(domElem);
+ }
+ else {
+ setImagePlus(domElem);
+ }
+ },
+
+ //called when the full table actions is loaded
+ dataTableLoaded: function (response, workingDivId) {
+ var content = $(response);
+ var idToReplace = workingDivId || $(content).attr('id');
+
+ //reset parents id
+ self.parentAttributeParent = '';
+ self.parentId = '';
+
+ var dataTableSel = $('#' + idToReplace);
+
+ // keep the original list of related reports
+ var oldReportsElem = $('.datatableRelatedReports', dataTableSel);
+ $('.datatableRelatedReports', content).replaceWith(oldReportsElem);
+
+ dataTableSel.replaceWith(content);
+ piwikHelper.lazyScrollTo(content[0], 400);
+
+ return content;
+ },
+
+ // Called when a set of rows for a category of actions is loaded
+ actionsSubDataTableLoaded: function (response) {
+ var self = this;
+ var idToReplace = $(response).attr('id');
+
+ // remove the first row of results which is only used to get the Id
+ var response = $(response).filter('tr').slice(1).addClass('rowToProcess');
+ self.parentAttributeParent = $('tr#' + idToReplace).prev().prop('parent');
+ self.parentId = idToReplace;
+
+ $('tr#' + idToReplace).after(response).remove();
+
+ var missingColumns = (response.prev().find('td').size() - response.find('td').size());
+ for (var i = 0; i < missingColumns; i++) {
+ // if the subtable has fewer columns than the parent table, add some columns.
+ // this happens for example, when the parent table has performance metrics and the subtable doesn't.
+ response.append('<td>-</td>');
+ }
+
+ var re = /subDataTable_(\d+)/;
+ ok = re.exec(self.parentId);
+ if (ok) {
+ self.parentId = ok[1];
+ }
+
+ // we execute the bindDataTableEvent function for the new DIV
+ self.init(self.workingDivId, $('#' + idToReplace));
+
+ //bind back the click event (disabled to avoid double-click problem)
+ self.disabledRowDom.click(
+ function () {
+ self.onClickActionSubDataTable(this)
+ });
+ }
};
//helper function for actionDataTable
-function getLevelFromClass( style)
-{
- if (!style || typeof style == "undefined") return 0;
-
- var currentLevelIndex = style.indexOf('level');
- var currentLevel = 0;
- if( currentLevelIndex >= 0)
- {
- currentLevel = Number(style.substr(currentLevelIndex+5,1));
- }
- return currentLevel;
+function getLevelFromClass(style) {
+ if (!style || typeof style == "undefined") return 0;
+
+ var currentLevelIndex = style.indexOf('level');
+ var currentLevel = 0;
+ if (currentLevelIndex >= 0) {
+ currentLevel = Number(style.substr(currentLevelIndex + 5, 1));
+ }
+ return currentLevel;
}
//helper function for actionDataTable
-function getNextLevelFromClass( style )
-{
- if (!style || typeof style == "undefined") return 0;
- currentLevel = getLevelFromClass(style);
- newLevel = currentLevel;
- // if this is not a row to process so
- if( style.indexOf('rowToProcess') < 0 )
- {
- newLevel = currentLevel + 1;
- }
- return newLevel;
+function getNextLevelFromClass(style) {
+ if (!style || typeof style == "undefined") return 0;
+ currentLevel = getLevelFromClass(style);
+ newLevel = currentLevel;
+ // if this is not a row to process so
+ if (style.indexOf('rowToProcess') < 0) {
+ newLevel = currentLevel + 1;
+ }
+ return newLevel;
}
//helper function for actionDataTable
-function setImageMinus( domElem )
-{
- $('img.plusMinus',domElem).attr('src', 'themes/default/images/minus.png');
+function setImageMinus(domElem) {
+ $('img.plusMinus', domElem).attr('src', 'themes/default/images/minus.png');
}
//helper function for actionDataTable
-function setImagePlus( domElem )
-{
- $('img.plusMinus',domElem).attr('src', 'themes/default/images/plus.png');
+function setImagePlus(domElem) {
+ $('img.plusMinus', domElem).attr('src', 'themes/default/images/plus.png');
}
diff --git a/plugins/CoreHome/templates/datatable.tpl b/plugins/CoreHome/templates/datatable.tpl
index e596519e45..9b05930cf7 100644
--- a/plugins/CoreHome/templates/datatable.tpl
+++ b/plugins/CoreHome/templates/datatable.tpl
@@ -1,57 +1,58 @@
<div class="dataTable" data-report="{$properties.uniqueId}" data-params="{$javascriptVariablesToSet|@json_encode|escape:'html'}">
- <div class="reportDocumentation">
- {if !empty($reportDocumentation)}<p>{$reportDocumentation}</p>{/if}
- {if isset($properties.metadata.archived_date)}<span class='helpDate'>{$properties.metadata.archived_date}</span>{/if}
- </div>
- <div class="{if isset($javascriptVariablesToSet.idSubtable)&& $javascriptVariablesToSet.idSubtable!=0}sub{/if}{if $javascriptVariablesToSet.viewDataTable=='tableAllColumns'}dataTableAllColumnsWrapper{elseif $javascriptVariablesToSet.viewDataTable=='tableGoals'}dataTableAllColumnsWrapper{else}dataTableWrapper{/if}">
- {if isset($arrayDataTable.result) and $arrayDataTable.result == 'error'}
- {$arrayDataTable.message}
- {else}
- {if count($arrayDataTable) == 0}
- {if isset($showReportDataWasPurgedMessage) && $showReportDataWasPurgedMessage}
- <div class="pk-emptyDataTable">{'CoreHome_DataForThisReportHasBeenPurged'|translate:$deleteReportsOlderThan}</div>
- {else}
- <div class="pk-emptyDataTable">{'CoreHome_ThereIsNoDataForThisReport'|translate}</div>
- {/if}
- {else}
- <a name="{$properties.uniqueId}"></a>
- <table cellspacing="0" class="dataTable">
- <thead>
- <tr>
- {foreach from=$dataTableColumns item=column name=head}
- <th class="sortable {if $smarty.foreach.head.first}first{elseif $smarty.foreach.head.last}last{/if}" id="{$column}">
- {if !empty($columnDocumentation[$column])}
- <div class="columnDocumentation">
- <div class="columnDocumentationTitle">
- {$columnTranslations[$column]|escape:'html'|replace:"&amp;nbsp;":"&nbsp;"}
- </div>
- {$columnDocumentation[$column]|escape:'html'}
- </div>
- {/if}
- <div id="thDIV">{$columnTranslations[$column]|escape:'html'|replace:"&amp;nbsp;":"&nbsp;"}</div>
- </th>
- {/foreach}
- </tr>
- </thead>
-
- <tbody>
- {foreach from=$arrayDataTable item=row}
- <tr {if $row.idsubdatatable && $javascriptVariablesToSet.controllerActionCalledWhenRequestSubTable != null}class="subDataTable" id="{$row.idsubdatatable}"{/if}{if isset($row.issummaryrow) && $row.issummaryrow && $properties.highlight_summary_row} class="highlight"{/if}>
- {foreach from=$dataTableColumns item=column}
- <td>
- {include file="CoreHome/templates/datatable_cell.tpl"}
- </td>
- {/foreach}
- </tr>
- {/foreach}
- </tbody>
- </table>
- {/if}
-
- {if $properties.show_footer}
- {include file="CoreHome/templates/datatable_footer.tpl"}
- {/if}
- {include file="CoreHome/templates/datatable_js.tpl"}
- {/if}
- </div>
+ <div class="reportDocumentation">
+ {if !empty($reportDocumentation)}<p>{$reportDocumentation}</p>{/if}
+ {if isset($properties.metadata.archived_date)}<span class='helpDate'>{$properties.metadata.archived_date}</span>{/if}
+ </div>
+ <div class="{if isset($javascriptVariablesToSet.idSubtable)&& $javascriptVariablesToSet.idSubtable!=0}sub{/if}{if $javascriptVariablesToSet.viewDataTable=='tableAllColumns'}dataTableAllColumnsWrapper{elseif $javascriptVariablesToSet.viewDataTable=='tableGoals'}dataTableAllColumnsWrapper{else}dataTableWrapper{/if}">
+ {if isset($arrayDataTable.result) and $arrayDataTable.result == 'error'}
+ {$arrayDataTable.message}
+ {else}
+ {if count($arrayDataTable) == 0}
+ {if isset($showReportDataWasPurgedMessage) && $showReportDataWasPurgedMessage}
+ <div class="pk-emptyDataTable">{'CoreHome_DataForThisReportHasBeenPurged'|translate:$deleteReportsOlderThan}</div>
+ {else}
+ <div class="pk-emptyDataTable">{'CoreHome_ThereIsNoDataForThisReport'|translate}</div>
+ {/if}
+ {else}
+ <a name="{$properties.uniqueId}"></a>
+ <table cellspacing="0" class="dataTable">
+ <thead>
+ <tr>
+ {foreach from=$dataTableColumns item=column name=head}
+ <th class="sortable {if $smarty.foreach.head.first}first{elseif $smarty.foreach.head.last}last{/if}" id="{$column}">
+ {if !empty($columnDocumentation[$column])}
+ <div class="columnDocumentation">
+ <div class="columnDocumentationTitle">
+ {$columnTranslations[$column]|escape:'html'|replace:"&amp;nbsp;":"&nbsp;"}
+ </div>
+ {$columnDocumentation[$column]|escape:'html'}
+ </div>
+ {/if}
+ <div id="thDIV">{$columnTranslations[$column]|escape:'html'|replace:"&amp;nbsp;":"&nbsp;"}</div>
+ </th>
+ {/foreach}
+ </tr>
+ </thead>
+
+ <tbody>
+ {foreach from=$arrayDataTable item=row}
+ <tr {if $row.idsubdatatable && $javascriptVariablesToSet.controllerActionCalledWhenRequestSubTable != null}class="subDataTable"
+ id="{$row.idsubdatatable}"{/if}{if isset($row.issummaryrow) && $row.issummaryrow && $properties.highlight_summary_row} class="highlight"{/if}>
+ {foreach from=$dataTableColumns item=column}
+ <td>
+ {include file="CoreHome/templates/datatable_cell.tpl"}
+ </td>
+ {/foreach}
+ </tr>
+ {/foreach}
+ </tbody>
+ </table>
+ {/if}
+
+ {if $properties.show_footer}
+ {include file="CoreHome/templates/datatable_footer.tpl"}
+ {/if}
+ {include file="CoreHome/templates/datatable_js.tpl"}
+ {/if}
+ </div>
</div>
diff --git a/plugins/CoreHome/templates/datatable_actions.tpl b/plugins/CoreHome/templates/datatable_actions.tpl
index f9a48cf17e..976555262d 100644
--- a/plugins/CoreHome/templates/datatable_actions.tpl
+++ b/plugins/CoreHome/templates/datatable_actions.tpl
@@ -1,52 +1,53 @@
<div class="dataTable" data-report="{$properties.uniqueId}" data-params="{$javascriptVariablesToSet|@json_encode|escape:'html'}">
- <div class="reportDocumentation">
- {if !empty($reportDocumentation)}<p>{$reportDocumentation}</p>{/if}
- {if isset($properties.metadata.archived_date)}<span class='helpDate'>{$properties.metadata.archived_date}</span>{/if}
- </div>
- <div class="dataTableActionsWrapper">
- {if isset($arrayDataTable.result) and $arrayDataTable.result == 'error'}
- {$arrayDataTable.message}
- {else}
- {if count($arrayDataTable) == 0}
- <div class="pk-emptyDataTable">{'CoreHome_ThereIsNoDataForThisReport'|translate}</div>
- {else}
- <table cellspacing="0" class="dataTable dataTableActions">
- <thead>
- <tr>
- {foreach from=$dataTableColumns item=column name=head}
- <th class="sortable {if $smarty.foreach.head.first}first{elseif $smarty.foreach.head.last}last{/if}" id="{$column}">
- {if !empty($columnDocumentation[$column])}
- <div class="columnDocumentation">
- <div class="columnDocumentationTitle">
- {$columnTranslations[$column]|escape:'html'|replace:"&amp;nbsp;":"&nbsp;"}
- </div>
- {$columnDocumentation[$column]|escape:'html'}
- </div>
- {/if}
- <div id="thDIV">{$columnTranslations[$column]|escape:'html'}</div>
- </th>
- {/foreach}
- </tr>
- </thead>
-
- <tbody>
- {foreach from=$arrayDataTable item=row}
- <tr {if $row.idsubdatatable}class="rowToProcess subActionsDataTable" id="{$row.idsubdatatable}"{else}class="actionsDataTable rowToProcess"{/if}>
- {foreach from=$dataTableColumns item=column}
- <td>
- {include file="CoreHome/templates/datatable_cell.tpl"}
- </td>
- {/foreach}
- </tr>
- {/foreach}
- </tbody>
- </table>
- {/if}
-
- {if $properties.show_footer}
- {include file="CoreHome/templates/datatable_footer.tpl"}
- {/if}
- {include file="CoreHome/templates/datatable_actions_js.tpl"}
- {/if}
- </div>
+ <div class="reportDocumentation">
+ {if !empty($reportDocumentation)}<p>{$reportDocumentation}</p>{/if}
+ {if isset($properties.metadata.archived_date)}<span class='helpDate'>{$properties.metadata.archived_date}</span>{/if}
+ </div>
+ <div class="dataTableActionsWrapper">
+ {if isset($arrayDataTable.result) and $arrayDataTable.result == 'error'}
+ {$arrayDataTable.message}
+ {else}
+ {if count($arrayDataTable) == 0}
+ <div class="pk-emptyDataTable">{'CoreHome_ThereIsNoDataForThisReport'|translate}</div>
+ {else}
+ <table cellspacing="0" class="dataTable dataTableActions">
+ <thead>
+ <tr>
+ {foreach from=$dataTableColumns item=column name=head}
+ <th class="sortable {if $smarty.foreach.head.first}first{elseif $smarty.foreach.head.last}last{/if}" id="{$column}">
+ {if !empty($columnDocumentation[$column])}
+ <div class="columnDocumentation">
+ <div class="columnDocumentationTitle">
+ {$columnTranslations[$column]|escape:'html'|replace:"&amp;nbsp;":"&nbsp;"}
+ </div>
+ {$columnDocumentation[$column]|escape:'html'}
+ </div>
+ {/if}
+ <div id="thDIV">{$columnTranslations[$column]|escape:'html'}</div>
+ </th>
+ {/foreach}
+ </tr>
+ </thead>
+
+ <tbody>
+ {foreach from=$arrayDataTable item=row}
+ <tr {if $row.idsubdatatable}class="rowToProcess subActionsDataTable" id="{$row.idsubdatatable}"
+ {else}class="actionsDataTable rowToProcess"{/if}>
+ {foreach from=$dataTableColumns item=column}
+ <td>
+ {include file="CoreHome/templates/datatable_cell.tpl"}
+ </td>
+ {/foreach}
+ </tr>
+ {/foreach}
+ </tbody>
+ </table>
+ {/if}
+
+ {if $properties.show_footer}
+ {include file="CoreHome/templates/datatable_footer.tpl"}
+ {/if}
+ {include file="CoreHome/templates/datatable_actions_js.tpl"}
+ {/if}
+ </div>
</div>
diff --git a/plugins/CoreHome/templates/datatable_actions_recursive.tpl b/plugins/CoreHome/templates/datatable_actions_recursive.tpl
index 00d8b11cd3..eb3d6f4736 100644
--- a/plugins/CoreHome/templates/datatable_actions_recursive.tpl
+++ b/plugins/CoreHome/templates/datatable_actions_recursive.tpl
@@ -1,38 +1,39 @@
<div id="{$properties.uniqueId}">
- <div class="dataTableActionsWrapper">
- {if isset($arrayDataTable.result) and $arrayDataTable.result == 'error'}
- {$arrayDataTable.message}
- {else}
- {if count($arrayDataTable) == 0}
- <div class="pk-emptyDataTable">{'CoreHome_ThereIsNoDataForThisReport'|translate}</div>
- {else}
- <table cellspacing="0" class="dataTable dataTableActions">
- <thead>
- <tr>
- {foreach from=$dataTableColumns item=column}
- <th class="sortable" id="{$column}">{$columnTranslations[$column]|escape:'html'}</th>
- {/foreach}
- </tr>
- </thead>
-
- <tbody>
- {foreach from=$arrayDataTable item=row}
- <tr {if $row.idsubdatatable}class="level{$row.level} rowToProcess subActionsDataTable" id="{$row.idsubdatatable}"{else}class="actionsDataTable rowToProcess level{$row.level}"{/if}>
- {foreach from=$dataTableColumns item=column}
- <td>
- {include file="CoreHome/templates/datatable_cell.tpl"}
- </td>
- {/foreach}
- </tr>
- {/foreach}
- </tbody>
- </table>
- {/if}
-
- {if $properties.show_footer}
- {include file="CoreHome/templates/datatable_footer.tpl"}
+ <div class="dataTableActionsWrapper">
+ {if isset($arrayDataTable.result) and $arrayDataTable.result == 'error'}
+ {$arrayDataTable.message}
+ {else}
+ {if count($arrayDataTable) == 0}
+ <div class="pk-emptyDataTable">{'CoreHome_ThereIsNoDataForThisReport'|translate}</div>
+ {else}
+ <table cellspacing="0" class="dataTable dataTableActions">
+ <thead>
+ <tr>
+ {foreach from=$dataTableColumns item=column}
+ <th class="sortable" id="{$column}">{$columnTranslations[$column]|escape:'html'}</th>
+ {/foreach}
+ </tr>
+ </thead>
+
+ <tbody>
+ {foreach from=$arrayDataTable item=row}
+ <tr {if $row.idsubdatatable}class="level{$row.level} rowToProcess subActionsDataTable" id="{$row.idsubdatatable}"
+ {else}class="actionsDataTable rowToProcess level{$row.level}"{/if}>
+ {foreach from=$dataTableColumns item=column}
+ <td>
+ {include file="CoreHome/templates/datatable_cell.tpl"}
+ </td>
+ {/foreach}
+ </tr>
+ {/foreach}
+ </tbody>
+ </table>
+ {/if}
+
+ {if $properties.show_footer}
+ {include file="CoreHome/templates/datatable_footer.tpl"}
+ {/if}
+ {include file="CoreHome/templates/datatable_actions_js.tpl"}
{/if}
- {include file="CoreHome/templates/datatable_actions_js.tpl"}
- {/if}
- </div>
+ </div>
</div>
diff --git a/plugins/CoreHome/templates/datatable_actions_subdatable.tpl b/plugins/CoreHome/templates/datatable_actions_subdatable.tpl
index 10c0921b69..c7d71c7105 100644
--- a/plugins/CoreHome/templates/datatable_actions_subdatable.tpl
+++ b/plugins/CoreHome/templates/datatable_actions_subdatable.tpl
@@ -1,18 +1,20 @@
<tr id="{$properties.uniqueId}"></tr>
{if isset($arrayDataTable.result) and $arrayDataTable.result == 'error'}
- {$arrayDataTable.message}
+ {$arrayDataTable.message}
{else}
- {if count($arrayDataTable) == 0}
- <tr><td colspan="{$nbColumns}">{'CoreHome_CategoryNoData'|translate}</td></tr>
- {else}
- {foreach from=$arrayDataTable item=row}
- <tr {if $row.idsubdatatable}class="subActionsDataTable" id="{$row.idsubdatatable}"{else}class="actionsDataTable"{/if}>
- {foreach from=$dataTableColumns item=column}
- <td>
- {include file="CoreHome/templates/datatable_cell.tpl"}
- </td>
- {/foreach}
- </tr>
- {/foreach}
- {/if}
+ {if count($arrayDataTable) == 0}
+ <tr>
+ <td colspan="{$nbColumns}">{'CoreHome_CategoryNoData'|translate}</td>
+ </tr>
+ {else}
+ {foreach from=$arrayDataTable item=row}
+ <tr {if $row.idsubdatatable}class="subActionsDataTable" id="{$row.idsubdatatable}" {else}class="actionsDataTable"{/if}>
+ {foreach from=$dataTableColumns item=column}
+ <td>
+ {include file="CoreHome/templates/datatable_cell.tpl"}
+ </td>
+ {/foreach}
+ </tr>
+ {/foreach}
+ {/if}
{/if}
diff --git a/plugins/CoreHome/templates/datatable_cell.tpl b/plugins/CoreHome/templates/datatable_cell.tpl
index d19dbc0b9e..3b1ec5cd98 100644
--- a/plugins/CoreHome/templates/datatable_cell.tpl
+++ b/plugins/CoreHome/templates/datatable_cell.tpl
@@ -1,17 +1,18 @@
{if !$row.idsubdatatable && $column=='label' && !empty($row.metadata.url)}
<a target="_blank" href='{if !in_array(substr($row.metadata.url,0,4), array('http','ftp:'))}http://{/if}{$row.metadata.url|escape:'html'}'>
- {if empty($row.metadata.logo)}
- <img class="link" width="10" height="9" src="themes/default/images/link.gif" />
- {/if}
-{/if}
-{if $column=='label'}
- {logoHtml metadata=$row.metadata alt=$row.columns.label}
- {if !empty($row.metadata.html_label_prefix)}<span class='label-prefix'>{$row.metadata.html_label_prefix}</span>{/if}
- <span class='label{if !empty($row.metadata.is_aggregate) && $row.metadata.is_aggregate } highlighted{/if}' {if !empty($properties.tooltip_metadata_name)}title="{$row.metadata[$properties.tooltip_metadata_name]}"{/if}>{* make sure there are no whitespaces inside the span
+ {if empty($row.metadata.logo)}
+ <img class="link" width="10" height="9" src="themes/default/images/link.gif"/>
+ {/if}
+ {/if}
+ {if $column=='label'}
+ {logoHtml metadata=$row.metadata alt=$row.columns.label}
+ {if !empty($row.metadata.html_label_prefix)}<span class='label-prefix'>{$row.metadata.html_label_prefix}</span>{/if}
+ <span class='label{if !empty($row.metadata.is_aggregate) && $row.metadata.is_aggregate } highlighted{/if}'
+ {if !empty($properties.tooltip_metadata_name)}title="{$row.metadata[$properties.tooltip_metadata_name]}"{/if}>{* make sure there are no whitespaces inside the span
*}{if !empty($row.metadata.html_label_suffix)}<span class='label-suffix'>{$row.metadata.html_label_suffix}</span>{/if}
-{/if}{*
+ {/if}{*
*}{if isset($row.columns[$column])}{$row.columns[$column]}{else}{$defaultWhenColumnValueNotDefined}{/if}{*
*}{if $column=='label'}</span>{/if}
-{if !$row.idsubdatatable && $column=='label' && !empty($row.metadata.url)}
- </a>
+ {if !$row.idsubdatatable && $column=='label' && !empty($row.metadata.url)}
+</a>
{/if}
diff --git a/plugins/CoreHome/templates/datatable_footer.tpl b/plugins/CoreHome/templates/datatable_footer.tpl
index a37031a08f..8aef75b99a 100644
--- a/plugins/CoreHome/templates/datatable_footer.tpl
+++ b/plugins/CoreHome/templates/datatable_footer.tpl
@@ -1,126 +1,152 @@
<div class="dataTableFeatures">
-{if $properties.show_offset_information}
-<span>
+ {if $properties.show_offset_information}
+ <span>
<span class="dataTablePages"></span>
</span>
-{/if}
+ {/if}
-{if $properties.show_pagination_control}
-<span>
+ {if $properties.show_pagination_control}
+ <span>
<span class="dataTablePrevious">&lsaquo; {if isset($javascriptVariablesToSet.dataTablePreviousIsFirst)}{'General_First'|translate}{else}{'General_Previous'|translate}{/if} </span>
<span class="dataTableNext">{'General_Next'|translate} &rsaquo;</span>
</span>
-{/if}
+ {/if}
-{if $properties.show_search}
-<span class="dataTableSearchPattern">
- <input id="keyword" type="text" length="15" />
- <input type="submit" value="{'General_Search'|translate}" />
+ {if $properties.show_search}
+ <span class="dataTableSearchPattern">
+ <input id="keyword" type="text" length="15"/>
+ <input type="submit" value="{'General_Search'|translate}"/>
</span>
-{/if}
+ {/if}
-<span class="loadingPiwik" style='display:none'><img src="themes/default/images/loading-blue.gif" /> {'General_LoadingData'|translate}</span>
-{if $properties.show_footer_icons}
- <div class="dataTableFooterIcons">
- <div class="dataTableFooterWrap" var="{$javascriptVariablesToSet.viewDataTable}">
- {if !$properties.hide_all_views_icons}
- <img src="themes/default/images/data_table_footer_active_item.png" class="dataTableFooterActiveItem" />
- {/if}
- <div class="tableIconsGroup">
+ <span class="loadingPiwik" style='display:none'><img src="themes/default/images/loading-blue.gif"/> {'General_LoadingData'|translate}</span>
+ {if $properties.show_footer_icons}
+ <div class="dataTableFooterIcons">
+ <div class="dataTableFooterWrap" var="{$javascriptVariablesToSet.viewDataTable}">
+ {if !$properties.hide_all_views_icons}
+ <img src="themes/default/images/data_table_footer_active_item.png" class="dataTableFooterActiveItem"/>
+ {/if}
+ <div class="tableIconsGroup">
<span class="tableAllColumnsSwitch">
{if $properties.show_table}
- <a class="tableIcon" format="table" var="table"><img title="{'General_DisplaySimpleTable'|translate}" src="themes/default/images/table.png" /></a>
+ <a class="tableIcon" format="table" var="table"><img title="{'General_DisplaySimpleTable'|translate}"
+ src="themes/default/images/table.png"/></a>
{/if}
{if $properties.show_table_all_columns}
- <a class="tableIcon" format="tableAllColumns" var="tableAllColumns"><img title="{'General_DisplayTableWithMoreMetrics'|translate}" src="themes/default/images/table_more.png" /></a>
+ <a class="tableIcon" format="tableAllColumns" var="tableAllColumns"><img title="{'General_DisplayTableWithMoreMetrics'|translate}"
+ src="themes/default/images/table_more.png"/></a>
{/if}
{if $properties.show_goals}
- <a class="tableIcon" format="tableGoals" var="tableGoals"><img title="{'General_DisplayTableWithGoalMetrics'|translate}" src="themes/default/images/{if isset($javascriptVariablesToSet.idGoal) && $javascriptVariablesToSet.idGoal=='ecommerceOrder'}ecommerceOrder.gif{else}goal.png{/if}" /></a>
+ <a class="tableIcon" format="tableGoals" var="tableGoals"><img title="{'General_DisplayTableWithGoalMetrics'|translate}"
+ src="themes/default/images/{if isset($javascriptVariablesToSet.idGoal) && $javascriptVariablesToSet.idGoal=='ecommerceOrder'}ecommerceOrder.gif{else}goal.png{/if}"/></a>
{/if}
{if $properties.show_ecommerce}
- <a class="tableIcon" format="ecommerceOrder" var="ecommerceOrder"><img title="{'General_EcommerceOrders'|translate}" src="themes/default/images/ecommerceOrder.gif" /> <span>{'General_EcommerceOrders'|translate}</span></a>
- <a class="tableIcon" format="ecommerceAbandonedCart" var="ecommerceAbandonedCart"><img title="{'General_AbandonedCarts'|translate}" src="themes/default/images/ecommerceAbandonedCart.gif" /> <span>{'General_AbandonedCarts'|translate}</span></a>
+ <a class="tableIcon" format="ecommerceOrder" var="ecommerceOrder"><img title="{'General_EcommerceOrders'|translate}"
+ src="themes/default/images/ecommerceOrder.gif"/>
+ <span>{'General_EcommerceOrders'|translate}</span></a>
+ <a class="tableIcon" format="ecommerceAbandonedCart" var="ecommerceAbandonedCart"><img title="{'General_AbandonedCarts'|translate}"
+ src="themes/default/images/ecommerceAbandonedCart.gif"/>
+ <span>{'General_AbandonedCarts'|translate}</span></a>
{/if}
</span>
- </div>
- {if $properties.show_all_views_icons}
- <div class="tableIconsGroup">
+ </div>
+ {if $properties.show_all_views_icons}
+ <div class="tableIconsGroup">
<span class="tableGraphViews tableGraphCollapsed">
- {if $properties.show_bar_chart}<a class="tableIcon" format="graphVerticalBar" var="graphVerticalBar"><img width="16" height="16" src="themes/default/images/chart_bar.png" title="{'General_VBarGraph'|translate}" /></a>{/if}
- {if $properties.show_pie_chart}<a class="tableIcon" format="graphPie" var="graphPie"><img width="16" height="16" src="themes/default/images/chart_pie.png" title="{'General_Piechart'|translate}" /></a>{/if}
- {if $properties.show_tag_cloud}<a class="tableIcon" format="cloud" var="cloud"><img width="16" height="16" src="themes/default/images/tagcloud.png" title="{'General_TagCloud'|translate}" /></a>{/if}
+ {if $properties.show_bar_chart}<a class="tableIcon" format="graphVerticalBar" var="graphVerticalBar"><img width="16" height="16"
+ src="themes/default/images/chart_bar.png"
+ title="{'General_VBarGraph'|translate}"/>
+ </a>{/if}
+ {if $properties.show_pie_chart}<a class="tableIcon" format="graphPie" var="graphPie"><img width="16" height="16"
+ src="themes/default/images/chart_pie.png"
+ title="{'General_Piechart'|translate}"/></a>{/if}
+ {if $properties.show_tag_cloud}<a class="tableIcon" format="cloud" var="cloud"><img width="16" height="16"
+ src="themes/default/images/tagcloud.png"
+ title="{'General_TagCloud'|translate}"/></a>{/if}
</span>
- </div>
- {elseif !$properties.hide_all_views_icons && $javascriptVariablesToSet.viewDataTable == "generateDataChartEvolution"}
- <div class="tableIconsGroup">
+ </div>
+ {elseif !$properties.hide_all_views_icons && $javascriptVariablesToSet.viewDataTable == "generateDataChartEvolution"}
+ <div class="tableIconsGroup">
<span class="tableGraphViews">
- <a class="tableIcon" format="graphEvolution" var="graphEvolution"><img width="16" height="16" src="themes/default/images/chart_bar.png" title="{'General_VBarGraph'|translate}" /></a>
+ <a class="tableIcon" format="graphEvolution" var="graphEvolution"><img width="16" height="16" src="themes/default/images/chart_bar.png"
+ title="{'General_VBarGraph'|translate}"/></a>
</span>
- </div>
-
- {/if}
- <div class="tableIconsGroup">
- <span class="exportToFormatIcons"><a class="tableIcon" var="export"><img width="16" height="16" src="themes/default/images/export.png" title="{'General_ExportThisReport'|translate}" /></a></span>
+ </div>
+ {/if}
+ <div class="tableIconsGroup">
+ <span class="exportToFormatIcons"><a class="tableIcon" var="export"><img width="16" height="16" src="themes/default/images/export.png"
+ title="{'General_ExportThisReport'|translate}"/></a></span>
<span class="exportToFormatItems" style="display:none">
{'General_Export'|translate}:
<a target="_blank" methodToCall="{$properties.apiMethodToRequestDataTable}" format="CSV" filter_limit="{$properties.exportLimit}">CSV</a> |
- <a target="_blank" methodToCall="{$properties.apiMethodToRequestDataTable}" format="TSV" filter_limit="{$properties.exportLimit}">TSV (Excel)</a> |
+ <a target="_blank" methodToCall="{$properties.apiMethodToRequestDataTable}" format="TSV" filter_limit="{$properties.exportLimit}">TSV
+ (Excel)</a> |
<a target="_blank" methodToCall="{$properties.apiMethodToRequestDataTable}" format="XML" filter_limit="{$properties.exportLimit}">XML</a> |
<a target="_blank" methodToCall="{$properties.apiMethodToRequestDataTable}" format="JSON" filter_limit="{$properties.exportLimit}">Json</a> |
<a target="_blank" methodToCall="{$properties.apiMethodToRequestDataTable}" format="PHP" filter_limit="{$properties.exportLimit}">Php</a>
- {if $properties.show_export_as_rss_feed}
- | <a target="_blank" methodToCall="{$properties.apiMethodToRequestDataTable}" format="RSS" filter_limit="{$properties.exportLimit}" date="last10"><img border="0" src="themes/default/images/feed.png" /></a>
- {/if}
+ {if $properties.show_export_as_rss_feed}
+ |
+ <a target="_blank" methodToCall="{$properties.apiMethodToRequestDataTable}" format="RSS" filter_limit="{$properties.exportLimit}"
+ date="last10"><img border="0" src="themes/default/images/feed.png"/></a>
+ {/if}
</span>
- {if $properties.show_export_as_image_icon}
- <span id="dataTableFooterExportAsImageIcon">
- <a class="tableIcon" href="#" onclick="$('#{$chartDivId}').trigger('piwikExportAsImage'); return false;"><img title="{'General_ExportAsImage_js'|translate}" src="themes/default/images/image.png" /></a>
+ {if $properties.show_export_as_image_icon}
+ <span id="dataTableFooterExportAsImageIcon">
+ <a class="tableIcon" href="#" onclick="$('#{$chartDivId}').trigger('piwikExportAsImage'); return false;"><img
+ title="{'General_ExportAsImage_js'|translate}" src="themes/default/images/image.png"/></a>
</span>
- {/if}
- </div>
-
- </div>
- <div class="limitSelection {if !$properties.show_pagination_control && !$properties.show_limit_control} hidden{/if}" title="{'General_RowsToDisplay'|translate:escape:'html'}"></div>
- <div class="tableConfiguration">
- <a class="tableConfigurationIcon" href="#"></a>
- <ul>
- {if isset($javascriptVariablesToSet.flat) && $javascriptVariablesToSet.flat == 1}
- <li><div class="configItem dataTableIncludeAggregateRows"></div></li>
- {/if}
- <li><div class="configItem dataTableFlatten"></div></li>
- {if $properties.show_exclude_low_population}
- <li><div class="configItem dataTableExcludeLowPopulation"></div></li>
- {/if}
- </ul>
- </div>
- {if !$properties.hide_annotations_view}
- <div class="annotationView" title="{'Annotations_IconDesc_js'|translate}">
- <a class="tableIcon"><img width="16" height="16" src="themes/default/images/grey_marker.png"/></a>
- <span>{'Annotations_Annotations'|translate}</span>
- </div>
- {/if}
- </div>
-{/if}
+ {/if}
+ </div>
-<div class="datatableRelatedReports">
- {if !empty($properties.relatedReports) && (!empty($arrayDataTable) || !empty($cloudValues) || (isset($isDataAvailable) && $isDataAvailable)) && $properties.show_related_reports}
- {if count($properties.relatedReports) == 1}{'General_RelatedReport'|translate}{else}{'General_RelatedReports'|translate}{/if}:
- <ul style="list-style:none;{if count($properties.relatedReports) == 1}display:inline-block;{/if}">
- <li><span href="{$properties.self_url}" style="display:none;">{$properties.title}</span></li>
- {foreach from=$properties.relatedReports key=reportUrl item=reportTitle}
- <li><span href="{$reportUrl}">{$reportTitle}</span></li>
- {/foreach}
- </ul>
- {/if}
-</div>
+ </div>
+ <div class="limitSelection {if !$properties.show_pagination_control && !$properties.show_limit_control} hidden{/if}"
+ title="{'General_RowsToDisplay'|translate:escape:'html'}"></div>
+ <div class="tableConfiguration">
+ <a class="tableConfigurationIcon" href="#"></a>
+ <ul>
+ {if isset($javascriptVariablesToSet.flat) && $javascriptVariablesToSet.flat == 1}
+ <li>
+ <div class="configItem dataTableIncludeAggregateRows"></div>
+ </li>
+ {/if}
+ <li>
+ <div class="configItem dataTableFlatten"></div>
+ </li>
+ {if $properties.show_exclude_low_population}
+ <li>
+ <div class="configItem dataTableExcludeLowPopulation"></div>
+ </li>
+ {/if}
+ </ul>
+ </div>
+ {if !$properties.hide_annotations_view}
+ <div class="annotationView" title="{'Annotations_IconDesc_js'|translate}">
+ <a class="tableIcon"><img width="16" height="16" src="themes/default/images/grey_marker.png"/></a>
+ <span>{'Annotations_Annotations'|translate}</span>
+ </div>
+ {/if}
+ </div>
+ {/if}
+
+ <div class="datatableRelatedReports">
+ {if !empty($properties.relatedReports) && (!empty($arrayDataTable) || !empty($cloudValues) || (isset($isDataAvailable) && $isDataAvailable)) && $properties.show_related_reports}
+ {if count($properties.relatedReports) == 1}{'General_RelatedReport'|translate}{else}{'General_RelatedReports'|translate}{/if}:
+ <ul style="list-style:none;{if count($properties.relatedReports) == 1}display:inline-block;{/if}">
+ <li><span href="{$properties.self_url}" style="display:none;">{$properties.title}</span></li>
+ {foreach from=$properties.relatedReports key=reportUrl item=reportTitle}
+ <li><span href="{$reportUrl}">{$reportTitle}</span></li>
+ {/foreach}
+ </ul>
+ {/if}
+ </div>
-{if !empty($properties.show_footer_message)}
- <div class='datatableFooterMessage'>{$properties.show_footer_message}</div>
-{/if}
+ {if !empty($properties.show_footer_message)}
+ <div class='datatableFooterMessage'>{$properties.show_footer_message}</div>
+ {/if}
</div>
-<span class="loadingPiwikBelow" style='display:none'><img src="themes/default/images/loading-blue.gif" /> {'General_LoadingData'|translate}</span>
+<span class="loadingPiwikBelow" style='display:none'><img src="themes/default/images/loading-blue.gif"/> {'General_LoadingData'|translate}</span>
<div class="dataTableSpacer"></div>
diff --git a/plugins/CoreHome/templates/datatable_js.tpl b/plugins/CoreHome/templates/datatable_js.tpl
index 39fccf447d..ec434ec8cc 100644
--- a/plugins/CoreHome/templates/datatable_js.tpl
+++ b/plugins/CoreHome/templates/datatable_js.tpl
@@ -1,6 +1,7 @@
{if !isset($dataTableClassName)}{assign var=dataTableClassName value=dataTable}{/if}
<script type="text/javascript" defer="defer">
-$(document).ready(function(){literal}{{/literal}
- piwik.DataTableManager.initNewDataTables({$dataTableClassName});
-{literal}}{/literal});
+ $(document).ready(function () {literal}{{/literal}
+ piwik.DataTableManager.initNewDataTables({$dataTableClassName});
+ {literal}
+ }{/literal});
</script>
diff --git a/plugins/CoreHome/templates/datatable_manager.js b/plugins/CoreHome/templates/datatable_manager.js
index d5b48a7041..d7b5cc12e1 100644
--- a/plugins/CoreHome/templates/datatable_manager.js
+++ b/plugins/CoreHome/templates/datatable_manager.js
@@ -5,189 +5,175 @@
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
-(function($) {
-
- /**
- * The DataTableManager class manages the initialization of JS dataTable
- * instances. It's main purpose is to give each dataTable div a unique ID
- * when it's possible that any report will be loaded via AJAX, some more
- * than once.
- *
- * The singleton instance can be accessed via piwik.DataTableManager.
- */
- var DataTableManager = function()
- {
- this.nextId = 0;
- };
-
- DataTableManager.prototype = {
-
- /**
- * Gets the next available dataTable ID.
- *
- * @return {string}
- */
- getNextId: function()
- {
- this.nextId += 1;
- return 'dataTable_' + this.nextId;
- },
-
- /**
- * Gets the ID used for the last table or 0 if a DataTable hasn't been
- * initialized yet.
- */
- getLastId: function()
- {
- return 'dataTable_' + this.nextId;
- },
-
- /**
- * Initializes all uninitialized datatable elements. Uninitialized
- * datatable elements do not have an ID set.
- *
- * @param {Function} The DataTable's JS class.
- */
- initNewDataTables: function(klass)
- {
- var self = this;
-
- // find each datatable that hasn't been initialized (has no id attribute),
- // and initialize it
- $('div.dataTable').each(function() {
- if (!$(this).attr('id'))
- {
- var params = JSON.parse($(this).attr('data-params') || '{}');
-
- // convert values in params that are arrays to comma separated string lists
- for (var key in params)
- {
- if (params[key] instanceof Array)
- {
- params[key] = params[key].join(',');
- }
- }
-
- self.initSingleDataTable(this, klass, params);
- }
- });
- },
-
- /**
- * Initializes a single datatable element.
- *
- * @param {Element} domElem The DataTable div element.
- * @param {Function} klass The DataTable's JS class.
- * @param {Object} params The request params used.
- */
- initSingleDataTable: function(domElem, klass, params)
- {
- var newId = this.getNextId();
-
- $(domElem).attr('id', newId);
-
- var table = new klass();
- $(domElem).data('dataTableInstance', table);
-
- table.param = params;
- table.init(newId);
-
- // if the datatable has a graph, init the graph
- var graphElement = $('.piwik-graph', domElem);
- if (graphElement[0])
- {
- this.initJQPlotGraph(graphElement, newId);
- }
- },
-
- /**
- * Initializes and renders a JQPlot graph contained in a
- * dataTable.
- *
- * @param {Element} graphElement The empty graph div element. Will
- * usually have the .piwik-graph class.
- * @param {String} dataTableId The ID of the containing datatable.
- */
- initJQPlotGraph: function(graphElement, dataTableId)
- {
- graphElement = $(graphElement);
-
- // set a unique ID for the graph element
- var graphId = dataTableId + 'Chart';
- graphElement.attr('id', graphId);
-
- var graphData;
- try {
- graphData = JSON.parse(graphElement.attr('data-data'));
- } catch(e) {
- console.error('JSON.parse Error: "' + e + "\" in:\n" + graphElement.attr('data-data'));
- return;
- }
-
- var plot = new JQPlot(graphData, dataTableId);
-
- // add external series toggle if it should be added
- var externalSeriesToggle = graphElement.attr('data-external-series-toggle');
- if (externalSeriesToggle)
- {
- plot.addExternalSeriesToggle(
- window[externalSeriesToggle], // get the function w/ string name
- graphId,
- graphElement.attr('data-external-series-show-all') == 1
- );
- }
-
- // render the graph (setTimeout is required, otherwise the graph will not
- // render initially)
- setTimeout(function () {
- plot.render(graphElement.attr('data-graph-type'), graphId, {
- noData: _pk_translate('General_NoDataForGraph_js'),
- exportTitle: _pk_translate('General_ExportAsImage_js'),
- exportText: _pk_translate('General_SaveImageOnYourComputer_js'),
- metricsToPlot: _pk_translate('General_MetricsToPlot_js'),
- metricToPlot: _pk_translate('General_MetricToPlot_js'),
- recordsToPlot: _pk_translate('General_RecordsToPlot_js'),
- });
- }, 1);
- },
-
- /**
- * Returns the first datatable div displaying a specific report.
- *
- * @param {string} The report, eg, UserSettings.getWideScreen
- * @return {Element} The datatable div displaying the report, or undefined if
- * it cannot be found.
- */
- getDataTableByReport: function(report)
- {
- var reportWithoutDot = report.replace('.', '');
-
- var result = undefined;
- $('.dataTable').each(function() {
- if ($(this).attr('data-report') == reportWithoutDot)
- {
- result = this;
- return false;
- }
- });
- return result;
- },
-
- /**
- * Returns the datatable instance of the first datatable div displaying
- * a specific report.
- *
- * @param {string} The report, eg, UserSettings.getWideScrren
- * @return {DataTable} The DataTable instance created for the element, if
- * the element can be found. undefined, if it can't be found.
- */
- getDataTableInstanceByReport: function(report)
- {
- var dataTableElement = this.getDataTableByReport(report);
- return dataTableElement ? $(dataTableElement).data('dataTableInstance') : undefined;
- },
- };
-
- piwik.DataTableManager = new DataTableManager();
-
+(function ($) {
+
+ /**
+ * The DataTableManager class manages the initialization of JS dataTable
+ * instances. It's main purpose is to give each dataTable div a unique ID
+ * when it's possible that any report will be loaded via AJAX, some more
+ * than once.
+ *
+ * The singleton instance can be accessed via piwik.DataTableManager.
+ */
+ var DataTableManager = function () {
+ this.nextId = 0;
+ };
+
+ DataTableManager.prototype = {
+
+ /**
+ * Gets the next available dataTable ID.
+ *
+ * @return {string}
+ */
+ getNextId: function () {
+ this.nextId += 1;
+ return 'dataTable_' + this.nextId;
+ },
+
+ /**
+ * Gets the ID used for the last table or 0 if a DataTable hasn't been
+ * initialized yet.
+ */
+ getLastId: function () {
+ return 'dataTable_' + this.nextId;
+ },
+
+ /**
+ * Initializes all uninitialized datatable elements. Uninitialized
+ * datatable elements do not have an ID set.
+ *
+ * @param {Function} The DataTable's JS class.
+ */
+ initNewDataTables: function (klass) {
+ var self = this;
+
+ // find each datatable that hasn't been initialized (has no id attribute),
+ // and initialize it
+ $('div.dataTable').each(function () {
+ if (!$(this).attr('id')) {
+ var params = JSON.parse($(this).attr('data-params') || '{}');
+
+ // convert values in params that are arrays to comma separated string lists
+ for (var key in params) {
+ if (params[key] instanceof Array) {
+ params[key] = params[key].join(',');
+ }
+ }
+
+ self.initSingleDataTable(this, klass, params);
+ }
+ });
+ },
+
+ /**
+ * Initializes a single datatable element.
+ *
+ * @param {Element} domElem The DataTable div element.
+ * @param {Function} klass The DataTable's JS class.
+ * @param {Object} params The request params used.
+ */
+ initSingleDataTable: function (domElem, klass, params) {
+ var newId = this.getNextId();
+
+ $(domElem).attr('id', newId);
+
+ var table = new klass();
+ $(domElem).data('dataTableInstance', table);
+
+ table.param = params;
+ table.init(newId);
+
+ // if the datatable has a graph, init the graph
+ var graphElement = $('.piwik-graph', domElem);
+ if (graphElement[0]) {
+ this.initJQPlotGraph(graphElement, newId);
+ }
+ },
+
+ /**
+ * Initializes and renders a JQPlot graph contained in a
+ * dataTable.
+ *
+ * @param {Element} graphElement The empty graph div element. Will
+ * usually have the .piwik-graph class.
+ * @param {String} dataTableId The ID of the containing datatable.
+ */
+ initJQPlotGraph: function (graphElement, dataTableId) {
+ graphElement = $(graphElement);
+
+ // set a unique ID for the graph element
+ var graphId = dataTableId + 'Chart';
+ graphElement.attr('id', graphId);
+
+ var graphData;
+ try {
+ graphData = JSON.parse(graphElement.attr('data-data'));
+ } catch (e) {
+ console.error('JSON.parse Error: "' + e + "\" in:\n" + graphElement.attr('data-data'));
+ return;
+ }
+
+ var plot = new JQPlot(graphData, dataTableId);
+
+ // add external series toggle if it should be added
+ var externalSeriesToggle = graphElement.attr('data-external-series-toggle');
+ if (externalSeriesToggle) {
+ plot.addExternalSeriesToggle(
+ window[externalSeriesToggle], // get the function w/ string name
+ graphId,
+ graphElement.attr('data-external-series-show-all') == 1
+ );
+ }
+
+ // render the graph (setTimeout is required, otherwise the graph will not
+ // render initially)
+ setTimeout(function () {
+ plot.render(graphElement.attr('data-graph-type'), graphId, {
+ noData: _pk_translate('General_NoDataForGraph_js'),
+ exportTitle: _pk_translate('General_ExportAsImage_js'),
+ exportText: _pk_translate('General_SaveImageOnYourComputer_js'),
+ metricsToPlot: _pk_translate('General_MetricsToPlot_js'),
+ metricToPlot: _pk_translate('General_MetricToPlot_js'),
+ recordsToPlot: _pk_translate('General_RecordsToPlot_js'),
+ });
+ }, 1);
+ },
+
+ /**
+ * Returns the first datatable div displaying a specific report.
+ *
+ * @param {string} The report, eg, UserSettings.getWideScreen
+ * @return {Element} The datatable div displaying the report, or undefined if
+ * it cannot be found.
+ */
+ getDataTableByReport: function (report) {
+ var reportWithoutDot = report.replace('.', '');
+
+ var result = undefined;
+ $('.dataTable').each(function () {
+ if ($(this).attr('data-report') == reportWithoutDot) {
+ result = this;
+ return false;
+ }
+ });
+ return result;
+ },
+
+ /**
+ * Returns the datatable instance of the first datatable div displaying
+ * a specific report.
+ *
+ * @param {string} The report, eg, UserSettings.getWideScrren
+ * @return {DataTable} The DataTable instance created for the element, if
+ * the element can be found. undefined, if it can't be found.
+ */
+ getDataTableInstanceByReport: function (report) {
+ var dataTableElement = this.getDataTableByReport(report);
+ return dataTableElement ? $(dataTableElement).data('dataTableInstance') : undefined;
+ },
+ };
+
+ piwik.DataTableManager = new DataTableManager();
+
}(jQuery));
diff --git a/plugins/CoreHome/templates/datatable_rowactions.js b/plugins/CoreHome/templates/datatable_rowactions.js
index e28a676f55..afe31cc61b 100644
--- a/plugins/CoreHome/templates/datatable_rowactions.js
+++ b/plugins/CoreHome/templates/datatable_rowactions.js
@@ -13,100 +13,100 @@
*/
var DataTable_RowActions_Registry = {
- registry: [],
-
- register: function(action) {
- var createInstance = action.createInstance;
- action.createInstance = function(dataTable, param) {
- var instance = createInstance(dataTable, param);
- instance.actionName = action.name;
- return instance;
- };
-
- this.registry.push(action);
- },
-
- getAvailableActionsForReport: function(dataTableParams, tr) {
- if (dataTableParams.disable_row_actions == '1') {
- return [];
- }
-
- var available = [];
- for (var i = 0; i < this.registry.length; i++) {
- if (this.registry[i].isAvailableOnReport(dataTableParams, tr)) {
- available.push(this.registry[i]);
- }
- }
- available.sort(function(a, b) {
- return b.order - a.order;
- });
- return available;
- },
-
- getActionByName: function(name) {
- for (var i = 0; i < this.registry.length; i++) {
- if (this.registry[i].name == name) {
- return this.registry[i];
- }
- }
- return false;
- }
+ registry: [],
+
+ register: function (action) {
+ var createInstance = action.createInstance;
+ action.createInstance = function (dataTable, param) {
+ var instance = createInstance(dataTable, param);
+ instance.actionName = action.name;
+ return instance;
+ };
+
+ this.registry.push(action);
+ },
+
+ getAvailableActionsForReport: function (dataTableParams, tr) {
+ if (dataTableParams.disable_row_actions == '1') {
+ return [];
+ }
+
+ var available = [];
+ for (var i = 0; i < this.registry.length; i++) {
+ if (this.registry[i].isAvailableOnReport(dataTableParams, tr)) {
+ available.push(this.registry[i]);
+ }
+ }
+ available.sort(function (a, b) {
+ return b.order - a.order;
+ });
+ return available;
+ },
+
+ getActionByName: function (name) {
+ for (var i = 0; i < this.registry.length; i++) {
+ if (this.registry[i].name == name) {
+ return this.registry[i];
+ }
+ }
+ return false;
+ }
};
// Register Row Evolution (also servers as example)
DataTable_RowActions_Registry.register({
- name: 'RowEvolution',
-
- dataTableIcon: 'themes/default/images/row_evolution.png',
- dataTableIconHover: 'themes/default/images/row_evolution_hover.png',
-
- order: 50,
-
- dataTableIconTooltip: [
- _pk_translate('General_RowEvolutionRowActionTooltipTitle_js'),
- _pk_translate('General_RowEvolutionRowActionTooltip_js')
- ],
-
- createInstance: function(dataTable, param) {
- if (dataTable !== null && typeof dataTable.rowEvolutionActionInstance != 'undefined') {
- return dataTable.rowEvolutionActionInstance;
- }
-
- if (dataTable === null && param) {
- // when row evolution is triggered from the url (not a click on the data table)
- // we look for the data table instance in the dom
- var report = param.split(':')[0];
- var div = $(piwik.DataTableManager.getDataTableByReport(report));
- if (div.size() > 0 && div.data('piwikDataTable')) {
- dataTable = div.data('piwikDataTable');
- if (typeof dataTable.rowEvolutionActionInstance != 'undefined') {
- return dataTable.rowEvolutionActionInstance;
- }
- }
- }
-
- var instance = new DataTable_RowActions_RowEvolution(dataTable);
- if (dataTable !== null) {
- dataTable.rowEvolutionActionInstance = instance;
- }
- return instance;
- },
-
- isAvailableOnReport: function(dataTableParams) {
- return (
- typeof dataTableParams.disable_row_evolution == 'undefined'
- || dataTableParams.disable_row_evolution == "0"
- ) && (
- typeof dataTableParams.flat == 'undefined'
- || dataTableParams.flat == "0"
- );
- },
-
- isAvailableOnRow: function(dataTableParams, tr) {
- return true;
- }
+ name: 'RowEvolution',
+
+ dataTableIcon: 'themes/default/images/row_evolution.png',
+ dataTableIconHover: 'themes/default/images/row_evolution_hover.png',
+
+ order: 50,
+
+ dataTableIconTooltip: [
+ _pk_translate('General_RowEvolutionRowActionTooltipTitle_js'),
+ _pk_translate('General_RowEvolutionRowActionTooltip_js')
+ ],
+
+ createInstance: function (dataTable, param) {
+ if (dataTable !== null && typeof dataTable.rowEvolutionActionInstance != 'undefined') {
+ return dataTable.rowEvolutionActionInstance;
+ }
+
+ if (dataTable === null && param) {
+ // when row evolution is triggered from the url (not a click on the data table)
+ // we look for the data table instance in the dom
+ var report = param.split(':')[0];
+ var div = $(piwik.DataTableManager.getDataTableByReport(report));
+ if (div.size() > 0 && div.data('piwikDataTable')) {
+ dataTable = div.data('piwikDataTable');
+ if (typeof dataTable.rowEvolutionActionInstance != 'undefined') {
+ return dataTable.rowEvolutionActionInstance;
+ }
+ }
+ }
+
+ var instance = new DataTable_RowActions_RowEvolution(dataTable);
+ if (dataTable !== null) {
+ dataTable.rowEvolutionActionInstance = instance;
+ }
+ return instance;
+ },
+
+ isAvailableOnReport: function (dataTableParams) {
+ return (
+ typeof dataTableParams.disable_row_evolution == 'undefined'
+ || dataTableParams.disable_row_evolution == "0"
+ ) && (
+ typeof dataTableParams.flat == 'undefined'
+ || dataTableParams.flat == "0"
+ );
+ },
+
+ isAvailableOnRow: function (dataTableParams, tr) {
+ return true;
+ }
});
@@ -131,116 +131,116 @@ DataTable_RowActions_Registry.register({
//
function DataTable_RowAction(dataTable) {
- this.dataTable = dataTable;
+ this.dataTable = dataTable;
- // has to be overridden in subclasses
- this.trEventName = 'piwikTriggerRowAction';
+ // has to be overridden in subclasses
+ this.trEventName = 'piwikTriggerRowAction';
- // set in registry
- this.actionName = 'RowAction';
+ // set in registry
+ this.actionName = 'RowAction';
}
/** Initialize a row when the table is loaded */
-DataTable_RowAction.prototype.initTr = function(tr) {
- var self = this;
-
- // For subtables, we need to make sure that the actions are always triggered on the
- // action instance connected to the root table. Otherwise sharing data (e.g. for
- // for multi-row evolution) wouldn't be possible. Also, sub-tables might have different
- // API actions. For the label filter to work, we need to use the parent action.
- // We use jQuery events to let subtables access their parents.
- tr.bind(self.trEventName, function(e, params) {
- self.trigger($(this), params.originalEvent, params.label);
- });
+DataTable_RowAction.prototype.initTr = function (tr) {
+ var self = this;
+
+ // For subtables, we need to make sure that the actions are always triggered on the
+ // action instance connected to the root table. Otherwise sharing data (e.g. for
+ // for multi-row evolution) wouldn't be possible. Also, sub-tables might have different
+ // API actions. For the label filter to work, we need to use the parent action.
+ // We use jQuery events to let subtables access their parents.
+ tr.bind(self.trEventName, function (e, params) {
+ self.trigger($(this), params.originalEvent, params.label);
+ });
};
/**
* This method is called from the click event and the tr event (see this.trEventName).
* It derives the label and calls performAction.
*/
-DataTable_RowAction.prototype.trigger = function(tr, e, subTableLabel) {
- var label = this.getLabelFromTr(tr);
-
- label = label.trim();
- // if we have received the event from the sub table, add the label
- if (subTableLabel) {
- var separator = ' > '; // Piwik_API_DataTableManipulator_LabelFilter::SEPARATOR_RECURSIVE_LABEL
- label += separator + subTableLabel.trim();
- }
-
- // handle sub tables in nested reports: forward to parent
- var subtable = tr.closest('table');
- if (subtable.is('.subDataTable')) {
- subtable.closest('tr').prev().trigger(this.trEventName, {
- label: label,
- originalEvent: e
- });
- return;
- }
-
- // ascend in action reports
- if (tr.hasClass('actionsDataTable') || tr.hasClass('subActionsDataTable')) {
- var allClasses = tr.attr('class');
- var matches = allClasses.match(/level[0-9]+/);
- var level = parseInt(matches[0].substring(5, matches[0].length), 10);
- if (level > 0) {
- // .prev(.levelX) does not work for some reason => do it "by hand"
- var findLevel = 'level' + (level - 1);
- var ptr = tr;
- while ((ptr = ptr.prev()).size() > 0) {
- if (!ptr.hasClass(findLevel)) {
- continue;
- }
- ptr.trigger(this.trEventName, {
- label: label,
- originalEvent: e
- });
- return;
- }
- }
- }
-
- this.performAction(label, tr, e);
+DataTable_RowAction.prototype.trigger = function (tr, e, subTableLabel) {
+ var label = this.getLabelFromTr(tr);
+
+ label = label.trim();
+ // if we have received the event from the sub table, add the label
+ if (subTableLabel) {
+ var separator = ' > '; // Piwik_API_DataTableManipulator_LabelFilter::SEPARATOR_RECURSIVE_LABEL
+ label += separator + subTableLabel.trim();
+ }
+
+ // handle sub tables in nested reports: forward to parent
+ var subtable = tr.closest('table');
+ if (subtable.is('.subDataTable')) {
+ subtable.closest('tr').prev().trigger(this.trEventName, {
+ label: label,
+ originalEvent: e
+ });
+ return;
+ }
+
+ // ascend in action reports
+ if (tr.hasClass('actionsDataTable') || tr.hasClass('subActionsDataTable')) {
+ var allClasses = tr.attr('class');
+ var matches = allClasses.match(/level[0-9]+/);
+ var level = parseInt(matches[0].substring(5, matches[0].length), 10);
+ if (level > 0) {
+ // .prev(.levelX) does not work for some reason => do it "by hand"
+ var findLevel = 'level' + (level - 1);
+ var ptr = tr;
+ while ((ptr = ptr.prev()).size() > 0) {
+ if (!ptr.hasClass(findLevel)) {
+ continue;
+ }
+ ptr.trigger(this.trEventName, {
+ label: label,
+ originalEvent: e
+ });
+ return;
+ }
+ }
+ }
+
+ this.performAction(label, tr, e);
};
/** Get the label string from a tr dom element */
-DataTable_RowAction.prototype.getLabelFromTr = function(tr) {
- var label = tr.find('span.label');
+DataTable_RowAction.prototype.getLabelFromTr = function (tr) {
+ var label = tr.find('span.label');
- // handle truncation
- var value = label.data('originalText');
+ // handle truncation
+ var value = label.data('originalText');
- if (!value) {
- value = label.text();
- }
+ if (!value) {
+ value = label.text();
+ }
- return encodeURIComponent(value);
+ return encodeURIComponent(value);
};
/**
* Base method for opening popovers.
* This method will remember the parameter in the url and call doOpenPopover().
*/
-DataTable_RowAction.prototype.openPopover = function(parameter) {
- broadcast.propagateNewPopoverParameter('RowAction', this.actionName + ':' + parameter);
+DataTable_RowAction.prototype.openPopover = function (parameter) {
+ broadcast.propagateNewPopoverParameter('RowAction', this.actionName + ':' + parameter);
};
-broadcast.addPopoverHandler('RowAction', function(param) {
- var paramParts = param.split(':');
- var rowActionName = paramParts[0];
- paramParts.shift();
- param = paramParts.join(':');
+broadcast.addPopoverHandler('RowAction', function (param) {
+ var paramParts = param.split(':');
+ var rowActionName = paramParts[0];
+ paramParts.shift();
+ param = paramParts.join(':');
- var rowAction = DataTable_RowActions_Registry.getActionByName(rowActionName);
- if (rowAction) {
- rowAction.createInstance(null, param).doOpenPopover(param);
- }
+ var rowAction = DataTable_RowActions_Registry.getActionByName(rowActionName);
+ if (rowAction) {
+ rowAction.createInstance(null, param).doOpenPopover(param);
+ }
});
/** To be overridden */
-DataTable_RowAction.prototype.performAction = function(label, tr, e) {
+DataTable_RowAction.prototype.performAction = function (label, tr, e) {
};
-DataTable_RowAction.prototype.doOpenPopover = function(parameter) {
+DataTable_RowAction.prototype.doOpenPopover = function (parameter) {
};
@@ -249,129 +249,129 @@ DataTable_RowAction.prototype.doOpenPopover = function(parameter) {
//
function DataTable_RowActions_RowEvolution(dataTable) {
- this.dataTable = dataTable;
- this.trEventName = 'piwikTriggerRowEvolution';
+ this.dataTable = dataTable;
+ this.trEventName = 'piwikTriggerRowEvolution';
- /** The rows to be compared in multi row evolution */
- this.multiEvolutionRows = [];
+ /** The rows to be compared in multi row evolution */
+ this.multiEvolutionRows = [];
}
/** Static helper method to launch row evolution from anywhere */
-DataTable_RowActions_RowEvolution.launch = function(apiMethod, label) {
- var param = 'RowEvolution:' + apiMethod + ':0:' + label;
- broadcast.propagateNewPopoverParameter('RowAction', param);
+DataTable_RowActions_RowEvolution.launch = function (apiMethod, label) {
+ var param = 'RowEvolution:' + apiMethod + ':0:' + label;
+ broadcast.propagateNewPopoverParameter('RowAction', param);
};
DataTable_RowActions_RowEvolution.prototype = new DataTable_RowAction;
-DataTable_RowActions_RowEvolution.prototype.performAction = function(label, tr, e) {
- if (e.shiftKey) {
- // only mark for multi row evolution if shift key is pressed
- this.addMultiEvolutionRow(label);
- return;
- }
-
- // check whether we have rows marked for multi row evolution
- var isMultiRowEvolution = '0';
- this.addMultiEvolutionRow(label);
- if (this.multiEvolutionRows.length > 1) {
- isMultiRowEvolution = '1';
- label = this.multiEvolutionRows.join(',');
- }
-
- var apiMethod = this.dataTable.param.module + '.' + this.dataTable.param.action;
- this.openPopover(apiMethod, isMultiRowEvolution, label);
+DataTable_RowActions_RowEvolution.prototype.performAction = function (label, tr, e) {
+ if (e.shiftKey) {
+ // only mark for multi row evolution if shift key is pressed
+ this.addMultiEvolutionRow(label);
+ return;
+ }
+
+ // check whether we have rows marked for multi row evolution
+ var isMultiRowEvolution = '0';
+ this.addMultiEvolutionRow(label);
+ if (this.multiEvolutionRows.length > 1) {
+ isMultiRowEvolution = '1';
+ label = this.multiEvolutionRows.join(',');
+ }
+
+ var apiMethod = this.dataTable.param.module + '.' + this.dataTable.param.action;
+ this.openPopover(apiMethod, isMultiRowEvolution, label);
};
-DataTable_RowActions_RowEvolution.prototype.addMultiEvolutionRow = function(label) {
- if ($.inArray(label, this.multiEvolutionRows) == -1) {
- this.multiEvolutionRows.push(label);
- }
+DataTable_RowActions_RowEvolution.prototype.addMultiEvolutionRow = function (label) {
+ if ($.inArray(label, this.multiEvolutionRows) == -1) {
+ this.multiEvolutionRows.push(label);
+ }
};
-DataTable_RowActions_RowEvolution.prototype.openPopover = function(apiMethod, multiRowEvolutionParam, label) {
- var urlParam = apiMethod + ':' + multiRowEvolutionParam + ':' + label;
- DataTable_RowAction.prototype.openPopover.apply(this, [urlParam]);
+DataTable_RowActions_RowEvolution.prototype.openPopover = function (apiMethod, multiRowEvolutionParam, label) {
+ var urlParam = apiMethod + ':' + multiRowEvolutionParam + ':' + label;
+ DataTable_RowAction.prototype.openPopover.apply(this, [urlParam]);
};
-DataTable_RowActions_RowEvolution.prototype.doOpenPopover = function(urlParam) {
- var urlParamParts = urlParam.split(':');
+DataTable_RowActions_RowEvolution.prototype.doOpenPopover = function (urlParam) {
+ var urlParamParts = urlParam.split(':');
- var apiMethod = urlParamParts[0];
- urlParamParts.shift();
+ var apiMethod = urlParamParts[0];
+ urlParamParts.shift();
- var multiRowEvolutionParam = urlParamParts[0];
- urlParamParts.shift();
+ var multiRowEvolutionParam = urlParamParts[0];
+ urlParamParts.shift();
- var label = urlParamParts.join(':');
+ var label = urlParamParts.join(':');
- this.showRowEvolution(apiMethod, label, multiRowEvolutionParam);
+ this.showRowEvolution(apiMethod, label, multiRowEvolutionParam);
};
/** Open the row evolution popover */
-DataTable_RowActions_RowEvolution.prototype.showRowEvolution = function(apiMethod, label, multiRowEvolutionParam) {
-
- var self = this;
-
- // open the popover
- var box = Piwik_Popover.showLoading('Row Evolution');
- box.addClass('rowEvolutionPopover');
-
- // prepare loading the popover contents
- var requestParams = {
- apiMethod: apiMethod,
- label: label,
- disableLink: 1
- };
-
- // derive api action and requested column from multiRowEvolutionParam
- var action;
- if (multiRowEvolutionParam == '0') {
- action = 'getRowEvolutionPopover';
- } else if (multiRowEvolutionParam == '1') {
- action = 'getMultiRowEvolutionPopover';
- } else {
- action = 'getMultiRowEvolutionPopover';
- requestParams.column = multiRowEvolutionParam;
- }
-
- var callback = function(html) {
- Piwik_Popover.setContent(html);
-
- // use the popover title returned from the server
- var title = box.find('div.popover-title');
- if (title.size() > 0) {
- Piwik_Popover.setTitle(title.html());
- title.remove();
- }
-
- Piwik_Popover.onClose(function() {
- // reset rows marked for multi row evolution on close
- self.multiEvolutionRows = [];
- });
-
- if (self.dataTable !== null) {
- // remember label for multi row evolution
- box.find('a.rowevolution-startmulti').click(function() {
- Piwik_Popover.onClose(false); // unbind listener that resets multiEvolutionRows
- Piwik_Popover.close();
- return false;
- });
- } else {
- // when the popover is launched by copy&pasting a url, we don't have the data table.
- // in this case, we can't remember the row marked for multi row evolution so
- // we disable the picker.
- box.find('.compare-container, .rowevolution-startmulti').remove();
- }
-
- // switch metric in multi row evolution
- box.find('select.multirowevoltion-metric').change(function() {
- var metric = $(this).val();
- Piwik_Popover.onClose(false); // unbind listener that resets multiEvolutionRows
- self.openPopover(apiMethod, metric, label);
- return true;
- });
- };
+DataTable_RowActions_RowEvolution.prototype.showRowEvolution = function (apiMethod, label, multiRowEvolutionParam) {
+
+ var self = this;
+
+ // open the popover
+ var box = Piwik_Popover.showLoading('Row Evolution');
+ box.addClass('rowEvolutionPopover');
+
+ // prepare loading the popover contents
+ var requestParams = {
+ apiMethod: apiMethod,
+ label: label,
+ disableLink: 1
+ };
+
+ // derive api action and requested column from multiRowEvolutionParam
+ var action;
+ if (multiRowEvolutionParam == '0') {
+ action = 'getRowEvolutionPopover';
+ } else if (multiRowEvolutionParam == '1') {
+ action = 'getMultiRowEvolutionPopover';
+ } else {
+ action = 'getMultiRowEvolutionPopover';
+ requestParams.column = multiRowEvolutionParam;
+ }
+
+ var callback = function (html) {
+ Piwik_Popover.setContent(html);
+
+ // use the popover title returned from the server
+ var title = box.find('div.popover-title');
+ if (title.size() > 0) {
+ Piwik_Popover.setTitle(title.html());
+ title.remove();
+ }
+
+ Piwik_Popover.onClose(function () {
+ // reset rows marked for multi row evolution on close
+ self.multiEvolutionRows = [];
+ });
+
+ if (self.dataTable !== null) {
+ // remember label for multi row evolution
+ box.find('a.rowevolution-startmulti').click(function () {
+ Piwik_Popover.onClose(false); // unbind listener that resets multiEvolutionRows
+ Piwik_Popover.close();
+ return false;
+ });
+ } else {
+ // when the popover is launched by copy&pasting a url, we don't have the data table.
+ // in this case, we can't remember the row marked for multi row evolution so
+ // we disable the picker.
+ box.find('.compare-container, .rowevolution-startmulti').remove();
+ }
+
+ // switch metric in multi row evolution
+ box.find('select.multirowevoltion-metric').change(function () {
+ var metric = $(this).val();
+ Piwik_Popover.onClose(false); // unbind listener that resets multiEvolutionRows
+ self.openPopover(apiMethod, metric, label);
+ return true;
+ });
+ };
requestParams.module = 'CoreHome';
requestParams.action = action;
diff --git a/plugins/CoreHome/templates/date.js b/plugins/CoreHome/templates/date.js
index 2715113740..1c6c5971bd 100644
--- a/plugins/CoreHome/templates/date.js
+++ b/plugins/CoreHome/templates/date.js
@@ -5,43 +5,42 @@
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
-$(document).ready(function(){
-
- //period widget handler
- var periodWidget={
- show:function(){
- this.isOpen=1;
- $("#periodMore").show();
- },
- hide:function(){
- this.isOpen=0;
- $("#periodMore").hide();
- },
- toggle:function(e){
- if(!this.isOpen) this.show();
- else this.hide();
- }
- };
+$(document).ready(function () {
- $("#periodString #date")
- .hover( function(){
- $(this).css({ cursor: "pointer"});
- }, function(){
-
- })
- .click(function(){
- periodWidget.toggle();
- if($("#periodMore").is(":visible"))
- {
- $("#periodMore .ui-state-highlight").removeClass('ui-state-highlight');
- }
- });
-
- //close periodString onClickOutside
- $('body').on('mouseup',function(e){
- if(!$(e.target).parents('#periodString').length && !$(e.target).is('#periodString') && !$(e.target).is('option') && periodWidget.isOpen) {
- periodWidget.hide();
- }
- });
-
-} );
+ //period widget handler
+ var periodWidget = {
+ show: function () {
+ this.isOpen = 1;
+ $("#periodMore").show();
+ },
+ hide: function () {
+ this.isOpen = 0;
+ $("#periodMore").hide();
+ },
+ toggle: function (e) {
+ if (!this.isOpen) this.show();
+ else this.hide();
+ }
+ };
+
+ $("#periodString #date")
+ .hover(function () {
+ $(this).css({ cursor: "pointer"});
+ }, function () {
+
+ })
+ .click(function () {
+ periodWidget.toggle();
+ if ($("#periodMore").is(":visible")) {
+ $("#periodMore .ui-state-highlight").removeClass('ui-state-highlight');
+ }
+ });
+
+ //close periodString onClickOutside
+ $('body').on('mouseup', function (e) {
+ if (!$(e.target).parents('#periodString').length && !$(e.target).is('#periodString') && !$(e.target).is('option') && periodWidget.isOpen) {
+ periodWidget.hide();
+ }
+ });
+
+});
diff --git a/plugins/CoreHome/templates/donate.css b/plugins/CoreHome/templates/donate.css
index a805ac5538..cff3068992 100755
--- a/plugins/CoreHome/templates/donate.css
+++ b/plugins/CoreHome/templates/donate.css
@@ -1,126 +1,126 @@
.piwik-donate-call {
- padding: 1em 1em 1em 1em;
- border: 1px solid #CCC;
-
- border-radius:4px;
- -moz-border-radius:4px;
- -webkit-border-radius:4px;
-
- max-width: 432px;
- position:relative;
+ padding: 1em 1em 1em 1em;
+ border: 1px solid #CCC;
+
+ border-radius: 4px;
+ -moz-border-radius: 4px;
+ -webkit-border-radius: 4px;
+
+ max-width: 432px;
+ position: relative;
}
#piwik-worth {
- font-size:1.2em;
- font-weight:bold;
- font-style:italic;
- display:block;
- margin: 0 1em 0 1em;
+ font-size: 1.2em;
+ font-weight: bold;
+ font-style: italic;
+ display: block;
+ margin: 0 1em 0 1em;
}
.piwik-donate-slider {
- height:56px;
- margin: 1em 0 1em 1em;
+ height: 56px;
+ margin: 1em 0 1em 1em;
}
.piwik-donate-slider > .slider-range {
- vertical-align:top;
- position:relative;
- display: inline-block;
- border: 1px solid #999;
-
- background-color: #f7f7f7;
-
- border-radius:6px;
- -moz-border-radius:6px;
- -webkit-border-radius:6px;
-
- height: 14px;
- width: 270px;
- margin:22px 8px 0 0;
- cursor:pointer;
+ vertical-align: top;
+ position: relative;
+ display: inline-block;
+ border: 1px solid #999;
+
+ background-color: #f7f7f7;
+
+ border-radius: 6px;
+ -moz-border-radius: 6px;
+ -webkit-border-radius: 6px;
+
+ height: 14px;
+ width: 270px;
+ margin: 22px 8px 0 0;
+ cursor: pointer;
}
.piwik-donate-slider .slider-position {
- border: 1px solid #999;
- background-color:#CCC;
+ border: 1px solid #999;
+ background-color: #CCC;
+
+ border-radius: 3px;
+ -moz-border-radius: 3px;
+ -webkit-border-radius: 3px;
- border-radius:3px;
- -moz-border-radius:3px;
- -webkit-border-radius:3px;
-
- height:18px;
- width:10px;
-
- position:absolute;
- top:-3px;
- left:-1px;
+ height: 18px;
+ width: 10px;
+
+ position: absolute;
+ top: -3px;
+ left: -1px;
}
.piwik-donate-slider .slider-donate-amount {
- display:inline-block;
-
- padding: .3em .5em .3em .5em;
- margin:16px 8px 0 0;
- vertical-align:top;
- width:48px;
- text-align:center;
- background-color: #CCC;
- color:#333;
- cursor:pointer;
+ display: inline-block;
+
+ padding: .3em .5em .3em .5em;
+ margin: 16px 8px 0 0;
+ vertical-align: top;
+ width: 48px;
+ text-align: center;
+ background-color: #CCC;
+ color: #333;
+ cursor: pointer;
}
.piwik-donate-slider .slider-smiley-face {
- margin:8px 0 8px 0;
- display:inline-block;
- cursor:pointer;
+ margin: 8px 0 8px 0;
+ display: inline-block;
+ cursor: pointer;
}
.piwik-donate-call .donate-submit {
- height:55px;
- position:relative;
+ height: 55px;
+ position: relative;
}
.piwik-donate-call .donate-submit input {
- margin-left:13px;
- border-style:none;
- background-image:none;
- padding:0;
+ margin-left: 13px;
+ border-style: none;
+ background-image: none;
+ padding: 0;
}
.piwik-donate-call .donate-submit a {
- display:inline-block;
- position:absolute;
- bottom:.5em;
- right:1.2em;
- margin-left:1.2em;
- font-size:1em;
- font-style:italic;
+ display: inline-block;
+ position: absolute;
+ bottom: .5em;
+ right: 1.2em;
+ margin-left: 1.2em;
+ font-size: 1em;
+ font-style: italic;
}
.piwik-donate-call > .piwik-donate-message {
- margin-bottom:.5em;
+ margin-bottom: .5em;
}
.piwik-donate-call > .piwik-donate-message p {
- margin-left: 1em;
+ margin-left: 1em;
}
.piwik-donate-call > .form-description {
- margin-top:1.25em;
+ margin-top: 1.25em;
}
.donate-form-instructions {
- font-size:.8em;
- margin: 0 1.25em 0 1.25em;
- color:#666;
- font-style:italic;
+ font-size: .8em;
+ margin: 0 1.25em 0 1.25em;
+ color: #666;
+ font-style: italic;
}
.widget .piwik-donate-call {
- border-style:none;
+ border-style: none;
}
.widget .piwik-donate-slider > .slider-range {
- width: 240px;
+ width: 240px;
}
diff --git a/plugins/CoreHome/templates/donate.js b/plugins/CoreHome/templates/donate.js
index 2e667ccf8a..f6348db41c 100755
--- a/plugins/CoreHome/templates/donate.js
+++ b/plugins/CoreHome/templates/donate.js
@@ -4,153 +4,139 @@
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
-(function($) {
-
-$(document).ready(function() {
-
- var donateAmounts = [0, 30, 60, 90, 120];
-
- // returns the space between each donation amount in the donation slider
- var getTickWidth = function(slider)
- {
- var effectiveSliderWidth = $('.slider-range', slider).width() - $('.slider-position', slider).width();
- return effectiveSliderWidth / (donateAmounts.length - 1);
- };
-
- // returns the position index on a slider based on a x coordinate value
- var getPositionFromPageCoord = function(slider, pageX)
- {
- return Math.round((pageX - $('.slider-range', slider).offset().left) / getTickWidth(slider));
- };
-
- // set's the correct amount text & smiley face image based on the position of the slider
- var setSmileyFaceAndAmount = function(slider, pos)
- {
- // set text yearly amount
- $('.slider-donate-amount', slider).text('$' + donateAmounts[pos] + '/' + _pk_translate('General_YearShort_js'));
-
- // set the right smiley face
- $('.slider-smiley-face').attr('src', 'themes/default/images/smileyprog_' + pos + '.png');
-
- // set the hidden option input for paypal
- var option = Math.max(1, pos);
- $('.piwik-donate-call input[name=os0]').val("Option " + option);
- };
-
- // move's a slider's position to a specific spot
- var moveSliderPosition = function(slider, to)
- {
- // make sure 'to' is valid
- if (to < 0)
- {
- to = 0;
- }
- else if (to >= donateAmounts.length)
- {
- to = donateAmounts.length - 1;
- }
-
- // set the slider position
- var left = to * getTickWidth(slider);
- if (left == 0)
- {
- left = -1; // at position 0 we move one pixel left to cover up some of slider bar
- }
-
- $('.slider-position', slider).css({
- left: left + 'px'
- });
-
- // reset the smiley face & amount based on the new position
- setSmileyFaceAndAmount(slider, to);
- };
-
- // when a slider is clicked, set the amount & smiley face appropriately
- $('body').on('click', '.piwik-donate-slider>.slider-range', function(e) {
- var slider = $(this).parent(),
- currentPageX = $('.slider-position', this).offset().left,
- currentPos = getPositionFromPageCoord(slider, currentPageX),
- pos = getPositionFromPageCoord(slider, e.pageX);
-
- // if the closest position is the current one, use the other position since
- // the user obviously wants to move the slider.
- if (currentPos == pos)
- {
- // if click is to right, go forward one, else backwards one
- if (e.pageX > currentPageX)
- {
- ++pos;
- }
- else
- {
- --pos;
- }
- }
-
- moveSliderPosition(slider, pos);
- });
-
- // when the smiley icon is clicked, move the position up one to demonstrate how to use the slider
- $('body').on('click', '.piwik-donate-slider .slider-smiley-face,.piwik-donate-slider .slider-donate-amount',
- function(e) {
- var slider = $(this).closest('.piwik-donate-slider'),
- currentPageX = $('.slider-position', slider).offset().left,
- currentPos = getPositionFromPageCoord(slider, currentPageX);
-
- moveSliderPosition(slider, currentPos + 1);
- }
- );
-
- // stores the current slider being dragged
- var draggingSlider = false;
-
- // start dragging on mousedown for a slider's position bar
- $('body').on('mousedown', '.piwik-donate-slider .slider-position', function () {
- draggingSlider = $(this).parent().parent();
- });
-
- // move the slider position if currently dragging when the mouse moves anywhere over the entire page
- $('body').on('mousemove', function(e) {
- if (draggingSlider)
- {
- var slider = draggingSlider.find('.slider-range'),
- sliderPos = slider.find('.slider-position'),
- left = e.pageX - slider.offset().left;
-
- // only move slider if the mouse x-coord is still on the slider (w/ some padding for borders)
- if (left <= slider.width() - sliderPos.width() + 2
- && left >= -2)
- {
- sliderPos.css({left: left + 'px'});
-
- var closestPos = Math.round(left / getTickWidth(draggingSlider));
- setSmileyFaceAndAmount(draggingSlider, closestPos);
- }
- }
- });
-
- // stop dragging and normalize a slider's position on mouseup over the entire page
- $('body').on('mouseup', function() {
- if (draggingSlider)
- {
- var sliderPos = $('.slider-position', draggingSlider),
- slider = sliderPos.parent();
-
- if (sliderPos.length)
- {
- // move the slider to the nearest donation amount position
- var pos = getPositionFromPageCoord(draggingSlider, sliderPos.offset().left);
- moveSliderPosition(draggingSlider, pos);
- }
-
- draggingSlider = false; // stop dragging
- }
- });
-
- // event for programatically changing the position
- $('body').on('piwik:changePosition', '.piwik-donate-slider', function(e, data) {
- moveSliderPosition(this, data.position);
- });
-});
+(function ($) {
+
+ $(document).ready(function () {
+
+ var donateAmounts = [0, 30, 60, 90, 120];
+
+ // returns the space between each donation amount in the donation slider
+ var getTickWidth = function (slider) {
+ var effectiveSliderWidth = $('.slider-range', slider).width() - $('.slider-position', slider).width();
+ return effectiveSliderWidth / (donateAmounts.length - 1);
+ };
+
+ // returns the position index on a slider based on a x coordinate value
+ var getPositionFromPageCoord = function (slider, pageX) {
+ return Math.round((pageX - $('.slider-range', slider).offset().left) / getTickWidth(slider));
+ };
+
+ // set's the correct amount text & smiley face image based on the position of the slider
+ var setSmileyFaceAndAmount = function (slider, pos) {
+ // set text yearly amount
+ $('.slider-donate-amount', slider).text('$' + donateAmounts[pos] + '/' + _pk_translate('General_YearShort_js'));
+
+ // set the right smiley face
+ $('.slider-smiley-face').attr('src', 'themes/default/images/smileyprog_' + pos + '.png');
+
+ // set the hidden option input for paypal
+ var option = Math.max(1, pos);
+ $('.piwik-donate-call input[name=os0]').val("Option " + option);
+ };
+
+ // move's a slider's position to a specific spot
+ var moveSliderPosition = function (slider, to) {
+ // make sure 'to' is valid
+ if (to < 0) {
+ to = 0;
+ }
+ else if (to >= donateAmounts.length) {
+ to = donateAmounts.length - 1;
+ }
+
+ // set the slider position
+ var left = to * getTickWidth(slider);
+ if (left == 0) {
+ left = -1; // at position 0 we move one pixel left to cover up some of slider bar
+ }
+
+ $('.slider-position', slider).css({
+ left: left + 'px'
+ });
+
+ // reset the smiley face & amount based on the new position
+ setSmileyFaceAndAmount(slider, to);
+ };
+
+ // when a slider is clicked, set the amount & smiley face appropriately
+ $('body').on('click', '.piwik-donate-slider>.slider-range', function (e) {
+ var slider = $(this).parent(),
+ currentPageX = $('.slider-position', this).offset().left,
+ currentPos = getPositionFromPageCoord(slider, currentPageX),
+ pos = getPositionFromPageCoord(slider, e.pageX);
+
+ // if the closest position is the current one, use the other position since
+ // the user obviously wants to move the slider.
+ if (currentPos == pos) {
+ // if click is to right, go forward one, else backwards one
+ if (e.pageX > currentPageX) {
+ ++pos;
+ }
+ else {
+ --pos;
+ }
+ }
+
+ moveSliderPosition(slider, pos);
+ });
+
+ // when the smiley icon is clicked, move the position up one to demonstrate how to use the slider
+ $('body').on('click', '.piwik-donate-slider .slider-smiley-face,.piwik-donate-slider .slider-donate-amount',
+ function (e) {
+ var slider = $(this).closest('.piwik-donate-slider'),
+ currentPageX = $('.slider-position', slider).offset().left,
+ currentPos = getPositionFromPageCoord(slider, currentPageX);
+
+ moveSliderPosition(slider, currentPos + 1);
+ }
+ );
+
+ // stores the current slider being dragged
+ var draggingSlider = false;
+
+ // start dragging on mousedown for a slider's position bar
+ $('body').on('mousedown', '.piwik-donate-slider .slider-position', function () {
+ draggingSlider = $(this).parent().parent();
+ });
+
+ // move the slider position if currently dragging when the mouse moves anywhere over the entire page
+ $('body').on('mousemove', function (e) {
+ if (draggingSlider) {
+ var slider = draggingSlider.find('.slider-range'),
+ sliderPos = slider.find('.slider-position'),
+ left = e.pageX - slider.offset().left;
+
+ // only move slider if the mouse x-coord is still on the slider (w/ some padding for borders)
+ if (left <= slider.width() - sliderPos.width() + 2
+ && left >= -2) {
+ sliderPos.css({left: left + 'px'});
+
+ var closestPos = Math.round(left / getTickWidth(draggingSlider));
+ setSmileyFaceAndAmount(draggingSlider, closestPos);
+ }
+ }
+ });
+
+ // stop dragging and normalize a slider's position on mouseup over the entire page
+ $('body').on('mouseup', function () {
+ if (draggingSlider) {
+ var sliderPos = $('.slider-position', draggingSlider),
+ slider = sliderPos.parent();
+
+ if (sliderPos.length) {
+ // move the slider to the nearest donation amount position
+ var pos = getPositionFromPageCoord(draggingSlider, sliderPos.offset().left);
+ moveSliderPosition(draggingSlider, pos);
+ }
+
+ draggingSlider = false; // stop dragging
+ }
+ });
+
+ // event for programatically changing the position
+ $('body').on('piwik:changePosition', '.piwik-donate-slider', function (e, data) {
+ moveSliderPosition(this, data.position);
+ });
+ });
}(jQuery));
diff --git a/plugins/CoreHome/templates/donate.tpl b/plugins/CoreHome/templates/donate.tpl
index 6f33cc0470..e94a1d1221 100755
--- a/plugins/CoreHome/templates/donate.tpl
+++ b/plugins/CoreHome/templates/donate.tpl
@@ -1,61 +1,64 @@
<div class="piwik-donate-call">
- <div class="piwik-donate-message">
- {if isset($msg)}
- {$msg}
- {else}
- <p>{'CoreHome_DonateCall1'|translate}</p>
- <p><strong><em>{'CoreHome_DonateCall2'|translate}</em></strong></p>
- <p>{'CoreHome_DonateCall3'|translate:'<em><strong>':'</strong></em>'}</p>
- {/if}
- </div>
-
- <span id="piwik-worth">{'CoreHome_HowMuchIsPiwikWorth'|translate}</span>
- <div class="donate-form-instructions">({'CoreHome_DonateFormInstructions'|translate})</div>
-
- <form action="https://www.paypal.com/cgi-bin/webscr" method="post" target="_blank">
- <input type="hidden" name="cmd" value="_s-xclick"/>
- <input type="hidden" name="hosted_button_id" value="DVKLY73RS7JTE"/>
- <input type="hidden" name="currency_code" value="USD"/>
- <input type="hidden" name="on0" value="Piwik Supporter"/>
-
- <div class="piwik-donate-slider">
- <div class="slider-range">
- <div class="slider-position"></div>
- </div>
- <div style="display:inline-block">
- <div class="slider-donate-amount">$30/{'General_YearShort_js'|translate}</div>
-
- <img class="slider-smiley-face" width="40" height="40" src="themes/default/images/smileyprog_1.png"/>
- </div>
-
- <input type="hidden" name="os0" value="Option 1"/>
- </div>
-
- <div class="donate-submit">
- <a href="https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=RPL23NJURMTFA&bb2_screener_=1357583494+83.233.186.82" target="_blank">{'CoreHome_MakeOneTimeDonation'|translate}</a>
- <input type="image" src="themes/default/images/paypal_subscribe.gif" border="0" name="submit" title="{'CoreHome_SubscribeAndBecomePiwikSupporter'|translate}"/>
- </div>
-
- <!-- to cache images -->
- <img style="display:none" src="themes/default/images/smileyprog_0.png"/>
- <img style="display:none" src="themes/default/images/smileyprog_1.png"/>
- <img style="display:none" src="themes/default/images/smileyprog_2.png"/>
- <img style="display:none" src="themes/default/images/smileyprog_3.png"/>
- <img style="display:none" src="themes/default/images/smileyprog_4.png"/>
- </form>
- {if isset($footerMessage)}
- <div class="form-description">
- {$footerMessage}
- </div>
- {/if}
+ <div class="piwik-donate-message">
+ {if isset($msg)}
+ {$msg}
+ {else}
+ <p>{'CoreHome_DonateCall1'|translate}</p>
+ <p><strong><em>{'CoreHome_DonateCall2'|translate}</em></strong></p>
+ <p>{'CoreHome_DonateCall3'|translate:'<em><strong>':'</strong></em>'}</p>
+ {/if}
+ </div>
+
+ <span id="piwik-worth">{'CoreHome_HowMuchIsPiwikWorth'|translate}</span>
+
+ <div class="donate-form-instructions">({'CoreHome_DonateFormInstructions'|translate})</div>
+
+ <form action="https://www.paypal.com/cgi-bin/webscr" method="post" target="_blank">
+ <input type="hidden" name="cmd" value="_s-xclick"/>
+ <input type="hidden" name="hosted_button_id" value="DVKLY73RS7JTE"/>
+ <input type="hidden" name="currency_code" value="USD"/>
+ <input type="hidden" name="on0" value="Piwik Supporter"/>
+
+ <div class="piwik-donate-slider">
+ <div class="slider-range">
+ <div class="slider-position"></div>
+ </div>
+ <div style="display:inline-block">
+ <div class="slider-donate-amount">$30/{'General_YearShort_js'|translate}</div>
+
+ <img class="slider-smiley-face" width="40" height="40" src="themes/default/images/smileyprog_1.png"/>
+ </div>
+
+ <input type="hidden" name="os0" value="Option 1"/>
+ </div>
+
+ <div class="donate-submit">
+ <a href="https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=RPL23NJURMTFA&bb2_screener_=1357583494+83.233.186.82"
+ target="_blank">{'CoreHome_MakeOneTimeDonation'|translate}</a>
+ <input type="image" src="themes/default/images/paypal_subscribe.gif" border="0" name="submit"
+ title="{'CoreHome_SubscribeAndBecomePiwikSupporter'|translate}"/>
+ </div>
+
+ <!-- to cache images -->
+ <img style="display:none" src="themes/default/images/smileyprog_0.png"/>
+ <img style="display:none" src="themes/default/images/smileyprog_1.png"/>
+ <img style="display:none" src="themes/default/images/smileyprog_2.png"/>
+ <img style="display:none" src="themes/default/images/smileyprog_3.png"/>
+ <img style="display:none" src="themes/default/images/smileyprog_4.png"/>
+ </form>
+ {if isset($footerMessage)}
+ <div class="form-description">
+ {$footerMessage}
+ </div>
+ {/if}
</div>
{literal}
-<script type="text/javascript">
-$(document).ready(function() {
- // Note: this will cause problems if more than one donate form is on the page
- $('.piwik-donate-slider').each(function() {
- $(this).trigger('piwik:changePosition', {position: 1});
- });
-});
-</script>
+ <script type="text/javascript">
+ $(document).ready(function () {
+ // Note: this will cause problems if more than one donate form is on the page
+ $('.piwik-donate-slider').each(function () {
+ $(this).trigger('piwik:changePosition', {position: 1});
+ });
+ });
+ </script>
{/literal}
diff --git a/plugins/CoreHome/templates/footer.tpl b/plugins/CoreHome/templates/footer.tpl
index d4e06c6e0c..e7a1377624 100644
--- a/plugins/CoreHome/templates/footer.tpl
+++ b/plugins/CoreHome/templates/footer.tpl
@@ -1,6 +1,6 @@
</div> {* <div id="root"> *}
- {include file="CoreHome/templates/piwik_tag.tpl"}
+{include file="CoreHome/templates/piwik_tag.tpl"}
</body>
</html>
diff --git a/plugins/CoreHome/templates/graph.tpl b/plugins/CoreHome/templates/graph.tpl
index e3483ac9b2..5f46b2f32f 100644
--- a/plugins/CoreHome/templates/graph.tpl
+++ b/plugins/CoreHome/templates/graph.tpl
@@ -1,42 +1,40 @@
<div class="dataTable" data-report="{$properties.uniqueId}" data-params="{$javascriptVariablesToSet|@json_encode|escape:'html'}">
-
- <div class="reportDocumentation">
- {if !empty($reportDocumentation)}<p>{$reportDocumentation}</p>{/if}
- {if isset($properties.metadata.archived_date)}<p>{$properties.metadata.archived_date}</p>{/if}
- </div>
-
- <div class="{if $graphType=='evolution'}dataTableGraphEvolutionWrapper{else}dataTableGraphWrapper{/if}">
- {if $isDataAvailable}
-
- <div class="jqplot-{$graphType}" style="padding-left: 6px;">
- <div class="piwik-graph"
- style="position: relative; width: {$width}{if substr($width, -1) != '%'}px{/if}; height: {$height}{if substr($height, -1) != '%'}px{/if};"
- data-data="{$data|escape:'html'}"
- data-graph-type="{$graphType|escape:'html'}"
- {if isset($properties.externalSeriesToggle) && $properties.externalSeriesToggle}
- data-external-series-toggle="{$properties.externalSeriesToggle|escape:'html'}"
- data-external-series-show-all="{if $properties.externalSeriesToggleShowAll}1{else}0{/if}"
- {/if}>
- </div>
- </div>
-
- {else}
-
- <div><div class="pk-emptyGraph">
- {if $showReportDataWasPurgedMessage}
- {'General_DataForThisGraphHasBeenPurged'|translate:$deleteReportsOlderThan}
- {else}
- {'General_NoDataForGraph_js'|translate}
- {/if}
- </div></div>
-
- {/if}
+ <div class="reportDocumentation">
+ {if !empty($reportDocumentation)}<p>{$reportDocumentation}</p>{/if}
+ {if isset($properties.metadata.archived_date)}<p>{$properties.metadata.archived_date}</p>{/if}
+ </div>
- {if $properties.show_footer}
- {include file="CoreHome/templates/datatable_footer.tpl"}
- {include file="CoreHome/templates/datatable_js.tpl"}
- {/if}
-
- </div>
+ <div class="{if $graphType=='evolution'}dataTableGraphEvolutionWrapper{else}dataTableGraphWrapper{/if}">
+
+ {if $isDataAvailable}
+ <div class="jqplot-{$graphType}" style="padding-left: 6px;">
+ <div class="piwik-graph"
+ style="position: relative; width: {$width}{if substr($width, -1) != '%'}px{/if}; height: {$height}{if substr($height, -1) != '%'}px{/if};"
+ data-data="{$data|escape:'html'}"
+ data-graph-type="{$graphType|escape:'html'}"
+ {if isset($properties.externalSeriesToggle) && $properties.externalSeriesToggle}
+ data-external-series-toggle="{$properties.externalSeriesToggle|escape:'html'}"
+ data-external-series-show-all="{if $properties.externalSeriesToggleShowAll}1{else}0{/if}"
+ {/if}>
+ </div>
+ </div>
+ {else}
+ <div>
+ <div class="pk-emptyGraph">
+ {if $showReportDataWasPurgedMessage}
+ {'General_DataForThisGraphHasBeenPurged'|translate:$deleteReportsOlderThan}
+ {else}
+ {'General_NoDataForGraph_js'|translate}
+ {/if}
+ </div>
+ </div>
+ {/if}
+
+ {if $properties.show_footer}
+ {include file="CoreHome/templates/datatable_footer.tpl"}
+ {include file="CoreHome/templates/datatable_js.tpl"}
+ {/if}
+
+ </div>
</div>
diff --git a/plugins/CoreHome/templates/header.tpl b/plugins/CoreHome/templates/header.tpl
index d98bd78880..461de49918 100644
--- a/plugins/CoreHome/templates/header.tpl
+++ b/plugins/CoreHome/templates/header.tpl
@@ -1,27 +1,29 @@
<!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>{$siteName} - {if !$isCustomLogo}Piwik &rsaquo; {/if} {'CoreHome_WebAnalyticsReports'|translate}</title>
-<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
-<meta name="generator" content="Piwik - Open Source Web Analytics" />
-<meta name="description" content="Web Analytics report for '{$siteName}' - Piwik" />
-<link rel="shortcut icon" href="plugins/CoreHome/templates/images/favicon.ico" />
-{loadJavascriptTranslations plugins='CoreHome Annotations'}
-{include file="CoreHome/templates/js_global_variables.tpl"}
-<!--[if lt IE 9]>
-<script language="javascript" type="text/javascript" src="libs/jqplot/excanvas.min.js"></script>
-<![endif]-->
-{include file="CoreHome/templates/js_css_includes.tpl"}
-<!--[if IE]>
-<link rel="stylesheet" type="text/css" href="themes/default/ieonly.css" />
-<![endif]-->
-{include file="CoreHome/templates/iframe_buster_header.tpl"}
-{if isset($addToHead)}{$addToHead}{/if}
+ <title>{$siteName} - {if !$isCustomLogo}Piwik &rsaquo; {/if} {'CoreHome_WebAnalyticsReports'|translate}</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
+ <meta name="generator" content="Piwik - Open Source Web Analytics"/>
+ <meta name="description" content="Web Analytics report for '{$siteName}' - Piwik"/>
+ <link rel="shortcut icon" href="plugins/CoreHome/templates/images/favicon.ico"/>
+ {loadJavascriptTranslations plugins='CoreHome Annotations'}
+ {include file="CoreHome/templates/js_global_variables.tpl"}
+ <!--[if lt IE 9]>
+ <script language="javascript" type="text/javascript" src="libs/jqplot/excanvas.min.js"></script>
+ <![endif]-->
+ {include file="CoreHome/templates/js_css_includes.tpl"}
+ <!--[if IE]>
+ <link rel="stylesheet" type="text/css" href="themes/default/ieonly.css"/>
+ <![endif]-->
+ {include file="CoreHome/templates/iframe_buster_header.tpl"}
+ {if isset($addToHead)}{$addToHead}{/if}
</head>
<body>
{include file="CoreHome/templates/iframe_buster_body.tpl"}
<div id="root">
-{include file="CoreHome/templates/index_before_menu.tpl"}
+ {include file="CoreHome/templates/index_before_menu.tpl"}
diff --git a/plugins/CoreHome/templates/header_message.tpl b/plugins/CoreHome/templates/header_message.tpl
index 3943ccbe8a..88860a7f60 100644
--- a/plugins/CoreHome/templates/header_message.tpl
+++ b/plugins/CoreHome/templates/header_message.tpl
@@ -5,35 +5,37 @@
<span id="header_message" class="{if $piwikUrl == 'http://demo.piwik.org/' || !$latest_version_available}header_info{else}header_alert{/if}">
<span class="header_short">
{if $piwikUrl == 'http://demo.piwik.org/'}
- {'General_YouAreViewingDemoShortMessage'|translate}
- {elseif $latest_version_available}
- {'General_NewUpdatePiwikX'|translate:$latest_version_available}
- {else}
- {'General_AboutPiwikX'|translate:$piwik_version}
- {/if}
+ {'General_YouAreViewingDemoShortMessage'|translate}
+ {elseif $latest_version_available}
+ {'General_NewUpdatePiwikX'|translate:$latest_version_available}
+ {else}
+ {'General_AboutPiwikX'|translate:$piwik_version}
+ {/if}
</span>
<span class="header_full">
{if $piwikUrl == 'http://demo.piwik.org/'}
- {'General_YouAreViewingDemoShortMessage'|translate}<br/>
- {'General_DownloadFullVersion'|translate:"<a href='http://piwik.org/'>":"</a>":"<a href='http://piwik.org'>piwik.org</a>"}
- {elseif $latest_version_available}
- {if $isSuperUser}
- {'General_PiwikXIsAvailablePleaseUpdateNow'|translate:$latest_version_available:"<br /><a href='index.php?module=CoreUpdater&amp;action=newVersionAvailable'>":"</a>":"<a href='?module=Proxy&amp;action=redirect&amp;url=http://piwik.org/changelog/' target='_blank'>":"</a>"}
- <br/>{'General_YouAreCurrentlyUsing'|translate:$piwik_version}
- {else}
- {'General_PiwikXIsAvailablePleaseNotifyPiwikAdmin'|translate:"<a href='?module=Proxy&action=redirect&url=http://piwik.org/' target='_blank'>Piwik</a> <a href='?module=Proxy&action=redirect&url=http://piwik.org/changelog/' target='_blank'>$latest_version_available</a>"}
- {/if}
- {else}
- {'General_PiwikIsACollaborativeProjectYouCanContributeAndDonate'|translate:"<a href='?module=Proxy&action=redirect&url=http://piwik.org' target='_blank'>":"$piwik_version</a>":"<br />":"<a target='_blank' href='?module=Proxy&action=redirect&url=http://piwik.org/contribute/'>":"</a>":'<br/>':"<a href='http://piwik.org/donate/' target='_blank'><strong><em>":"</em></strong></a>"}
- {/if}
- {if !empty($hasSomeAdminAccess)}
- <br/>
- <div id="updateCheckLinkContainer">
- <span class='loadingPiwik' style="display:none"><img src='./themes/default/images/loading-blue.gif'/></span>
- <img src="themes/default/images/reload.png"/>
- <a href="#" id="checkForUpdates"><em>{'CoreHome_CheckForUpdates'|translate}</em></a>
- </div>
- {/if}
+ {'General_YouAreViewingDemoShortMessage'|translate}
+ <br/>
+ {'General_DownloadFullVersion'|translate:"<a href='http://piwik.org/'>":"</a>":"<a href='http://piwik.org'>piwik.org</a>"}
+ {elseif $latest_version_available}
+ {if $isSuperUser}
+ {'General_PiwikXIsAvailablePleaseUpdateNow'|translate:$latest_version_available:"<br /><a href='index.php?module=CoreUpdater&amp;action=newVersionAvailable'>":"</a>":"<a href='?module=Proxy&amp;action=redirect&amp;url=http://piwik.org/changelog/' target='_blank'>":"</a>"}
+ <br/>
+ {'General_YouAreCurrentlyUsing'|translate:$piwik_version}
+ {else}
+ {'General_PiwikXIsAvailablePleaseNotifyPiwikAdmin'|translate:"<a href='?module=Proxy&action=redirect&url=http://piwik.org/' target='_blank'>Piwik</a> <a href='?module=Proxy&action=redirect&url=http://piwik.org/changelog/' target='_blank'>$latest_version_available</a>"}
+ {/if}
+ {else}
+ {'General_PiwikIsACollaborativeProjectYouCanContributeAndDonate'|translate:"<a href='?module=Proxy&action=redirect&url=http://piwik.org' target='_blank'>":"$piwik_version</a>":"<br />":"<a target='_blank' href='?module=Proxy&action=redirect&url=http://piwik.org/contribute/'>":"</a>":'<br/>':"<a href='http://piwik.org/donate/' target='_blank'><strong><em>":"</em></strong></a>"}
+ {/if}
+ {if !empty($hasSomeAdminAccess)}
+ <br/>
+ <div id="updateCheckLinkContainer">
+ <span class='loadingPiwik' style="display:none"><img src='./themes/default/images/loading-blue.gif'/></span>
+ <img src="themes/default/images/reload.png"/>
+ <a href="#" id="checkForUpdates"><em>{'CoreHome_CheckForUpdates'|translate}</em></a>
+ </div>
+ {/if}
</span>
</span>
diff --git a/plugins/CoreHome/templates/html_report_body.tpl b/plugins/CoreHome/templates/html_report_body.tpl
index 4d1bf67353..5a6b7a33b2 100644
--- a/plugins/CoreHome/templates/html_report_body.tpl
+++ b/plugins/CoreHome/templates/html_report_body.tpl
@@ -1,81 +1,81 @@
-<a name ="{$reportId}"/>
+<a name="{$reportId}"/>
<h2 style="color: rgb({$reportTitleTextColor}); font-size: {$reportTitleTextSize}pt;">
- {$reportName|escape:"html"}
+ {$reportName|escape:"html"}
</h2>
{if empty($reportRows)}
- {'CoreHome_ThereIsNoDataForThisReport'|translate}
+ {'CoreHome_ThereIsNoDataForThisReport'|translate}
{else}
- {if $displayGraph}
- <img
- alt=""
- {if $renderImageInline}
- src="data:image/png;base64,{$generatedImageGraph}"
- {else}
- src="cid:{$reportId}"
- {/if}
- height="{$graphHeight}"
- width="{$graphWidth}" />
- {/if}
+ {if $displayGraph}
+ <img
+ alt=""
+ {if $renderImageInline}
+ src="data:image/png;base64,{$generatedImageGraph}"
+ {else}
+ src="cid:{$reportId}"
+ {/if}
+ height="{$graphHeight}"
+ width="{$graphWidth}"/>
+ {/if}
- {if $displayGraph && $displayTable}
- <br/>
- <br/>
- {/if}
+ {if $displayGraph && $displayTable}
+ <br/>
+ <br/>
+ {/if}
- {if $displayTable}
- <table style="border-collapse:collapse; margin-left: 5px">
- <thead style="background-color: rgb({$tableHeaderBgColor}); color: rgb({$tableHeaderTextColor}); font-size: {$reportTableHeaderTextSize}pt;">
- {foreach from=$reportColumns item=columnName}
- <th style="padding: 6px 0;">
- &nbsp;{$columnName}&nbsp;&nbsp;
- </th>
- {/foreach}
- </thead>
- <tbody>
- {cycle name='tr-background-color' delimiter=';' values=";background-color: rgb(`$tableBgColor`)" print=false reset=true advance=false}
- {foreach from=$reportRows item=row key=rowId}
+ {if $displayTable}
+ <table style="border-collapse:collapse; margin-left: 5px">
+ <thead style="background-color: rgb({$tableHeaderBgColor}); color: rgb({$tableHeaderTextColor}); font-size: {$reportTableHeaderTextSize}pt;">
+ {foreach from=$reportColumns item=columnName}
+ <th style="padding: 6px 0;">
+ &nbsp;{$columnName}&nbsp;&nbsp;
+ </th>
+ {/foreach}
+ </thead>
+ <tbody>
+ {cycle name='tr-background-color' delimiter=';' values=";background-color: rgb(`$tableBgColor`)" print=false reset=true advance=false}
+ {foreach from=$reportRows item=row key=rowId}
- {assign var=rowMetrics value=$row->getColumns()}
+ {assign var=rowMetrics value=$row->getColumns()}
- {if isset($reportRowsMetadata[$rowId])}
- {assign var=rowMetadata value=$reportRowsMetadata[$rowId]->getColumns()}
- {else}
- {assign var=rowMetadata value=null}
- {/if}
-
- <tr style="{cycle name='tr-background-color'}">
- {foreach from=$reportColumns key=columnId item=columnName}
- <td style="font-size: {$reportTableRowTextSize}pt; border-bottom: 1px solid rgb({$tableCellBorderColor}); padding: 5px 0 5px 5px;">
- {if $columnId eq 'label'}
- {if isset($rowMetrics[$columnId])}
- {if isset($rowMetadata.logo)}
- <img src='{$currentPath}{$rowMetadata.logo}'>&nbsp;
- {/if}
- {if isset($rowMetadata.url)}
- <a style="color: rgb({$reportTextColor});" href='{if !in_array(substr($rowMetadata.url,0,4), array('http','ftp:'))}http://{/if}{$rowMetadata.url|escape:'html'}'>
- {/if}
- {$rowMetrics[$columnId]}
- {if isset($rowMetadata.url)}
- </a>
- {/if}
- {/if}
- {else}
- {if empty($rowMetrics[$columnId])}
- 0
- {else}
- {$rowMetrics[$columnId]}
- {/if}
- {/if}
- </td>
- {/foreach}
- </tr>
- {/foreach}
- </tbody>
- </table>
- {/if}
- <br/>
- <a style="text-decoration:none; color: rgb({$reportTitleTextColor}); font-size: {$reportBackToTopTextSize}pt" href="#reportTop">
- {'PDFReports_TopOfReport'|translate}
- </a>
+ {if isset($reportRowsMetadata[$rowId])}
+ {assign var=rowMetadata value=$reportRowsMetadata[$rowId]->getColumns()}
+ {else}
+ {assign var=rowMetadata value=null}
+ {/if}
+ <tr style="{cycle name='tr-background-color'}">
+ {foreach from=$reportColumns key=columnId item=columnName}
+ <td style="font-size: {$reportTableRowTextSize}pt; border-bottom: 1px solid rgb({$tableCellBorderColor}); padding: 5px 0 5px 5px;">
+ {if $columnId eq 'label'}
+ {if isset($rowMetrics[$columnId])}
+ {if isset($rowMetadata.logo)}
+ <img src='{$currentPath}{$rowMetadata.logo}'>
+ &nbsp;
+ {/if}
+ {if isset($rowMetadata.url)}
+ <a style="color: rgb({$reportTextColor});" href='{if !in_array(substr($rowMetadata.url,0,4), array('http','ftp:'))}http://{/if}{$rowMetadata.url|escape:'html'}'>
+ {/if}
+ {$rowMetrics[$columnId]}
+ {if isset($rowMetadata.url)}
+ </a>
+ {/if}
+ {/if}
+ {else}
+ {if empty($rowMetrics[$columnId])}
+ 0
+ {else}
+ {$rowMetrics[$columnId]}
+ {/if}
+ {/if}
+ </td>
+ {/foreach}
+ </tr>
+ {/foreach}
+ </tbody>
+ </table>
+ {/if}
+ <br/>
+ <a style="text-decoration:none; color: rgb({$reportTitleTextColor}); font-size: {$reportBackToTopTextSize}pt" href="#reportTop">
+ {'PDFReports_TopOfReport'|translate}
+ </a>
{/if} \ No newline at end of file
diff --git a/plugins/CoreHome/templates/html_report_footer.tpl b/plugins/CoreHome/templates/html_report_footer.tpl
index 4d4a7e5285..691287b6e3 100644
--- a/plugins/CoreHome/templates/html_report_footer.tpl
+++ b/plugins/CoreHome/templates/html_report_footer.tpl
@@ -1,2 +1,2 @@
- </body>
+</body>
</html> \ No newline at end of file
diff --git a/plugins/CoreHome/templates/html_report_header.tpl b/plugins/CoreHome/templates/html_report_header.tpl
index c40bcd0e80..a8c289a060 100644
--- a/plugins/CoreHome/templates/html_report_header.tpl
+++ b/plugins/CoreHome/templates/html_report_header.tpl
@@ -1,34 +1,31 @@
<html>
- <head>
- <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
- </head>
- <body style="color: rgb({$reportTextColor});">
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
+</head>
+<body style="color: rgb({$reportTextColor});">
- <a name="reportTop"/>
- <a target="_blank" href="{$currentPath}"><img title="{'General_GoTo'|translate:"Piwik"}" border="0" alt="Piwik" src='{$logoHeader}' /></a>
+<a name="reportTop"/>
+<a target="_blank" href="{$currentPath}"><img title="{'General_GoTo'|translate:"Piwik"}" border="0" alt="Piwik" src='{$logoHeader}'/></a>
- <h1 style="color: rgb({$reportTitleTextColor}); font-size: {$reportTitleTextSize}pt;">
- {$websiteName}
- </h1>
+<h1 style="color: rgb({$reportTitleTextColor}); font-size: {$reportTitleTextSize}pt;">
+ {$websiteName}
+</h1>
- <p>
- {$description} - {'General_DateRange'|translate} {$prettyDate}
- </p>
+<p>
+ {$description} - {'General_DateRange'|translate} {$prettyDate}
+</p>
- {if sizeof($reportMetadata) > 1}
-
- <h2 style="color: rgb({$reportTitleTextColor}); font-size: {$reportTitleTextSize}pt;">
- {'PDFReports_TableOfContent'|translate}
- </h2>
-
- <ul>
- {foreach from=$reportMetadata item=metadata}
- <li>
- <a href="#{$metadata.uniqueId}" style="text-decoration:none; color: rgb({$reportTextColor});">
- {$metadata.name|escape:"html"}
- </a>
- </li>
- {/foreach}
- </ul>
-
- {/if}
+{if sizeof($reportMetadata) > 1}
+ <h2 style="color: rgb({$reportTitleTextColor}); font-size: {$reportTitleTextSize}pt;">
+ {'PDFReports_TableOfContent'|translate}
+ </h2>
+ <ul>
+ {foreach from=$reportMetadata item=metadata}
+ <li>
+ <a href="#{$metadata.uniqueId}" style="text-decoration:none; color: rgb({$reportTextColor});">
+ {$metadata.name|escape:"html"}
+ </a>
+ </li>
+ {/foreach}
+ </ul>
+{/if}
diff --git a/plugins/CoreHome/templates/iframe_buster_body.tpl b/plugins/CoreHome/templates/iframe_buster_body.tpl
index 8fadfc3af7..3b00a2dae5 100644
--- a/plugins/CoreHome/templates/iframe_buster_body.tpl
+++ b/plugins/CoreHome/templates/iframe_buster_body.tpl
@@ -1,3 +1,7 @@
{if isset($enableFrames) && !$enableFrames}
-{literal}<script type="text/javascript">if(self == top) { var theBody = document.getElementsByTagName('body')[0]; theBody.style.display = 'block'; } else { top.location = self.location; }</script>
+{literal}
+ <script type="text/javascript">if (self == top) {
+ var theBody = document.getElementsByTagName('body')[0];
+ theBody.style.display = 'block';
+ } else { top.location = self.location; }</script>
{/literal}{/if} \ No newline at end of file
diff --git a/plugins/CoreHome/templates/iframe_buster_header.tpl b/plugins/CoreHome/templates/iframe_buster_header.tpl
index 319aa7db66..afa65b46f0 100644
--- a/plugins/CoreHome/templates/iframe_buster_header.tpl
+++ b/plugins/CoreHome/templates/iframe_buster_header.tpl
@@ -1,5 +1,7 @@
{if isset($enableFrames) && !$enableFrames}
-{literal}
- <style type="text/css">body { display : none; }</style>
-{/literal}
+ {literal}
+ <style type="text/css">body {
+ display: none;
+ }</style>
+ {/literal}
{/if} \ No newline at end of file
diff --git a/plugins/CoreHome/templates/index_before_menu.tpl b/plugins/CoreHome/templates/index_before_menu.tpl
index c6e0ac5f2c..7d76db2e52 100644
--- a/plugins/CoreHome/templates/index_before_menu.tpl
+++ b/plugins/CoreHome/templates/index_before_menu.tpl
@@ -1,12 +1,12 @@
{include file="CoreHome/templates/warning_invalid_host.tpl"}
{if !isset($showTopMenu) || $showTopMenu}
-{include file="CoreHome/templates/top_bar.tpl"}
+ {include file="CoreHome/templates/top_bar.tpl"}
{/if}
{include file="CoreHome/templates/top_screen.tpl"}
<div class="ui-confirm" id="alert">
- <h2></h2>
- <input role="yes" type="button" value="{'General_Ok'|translate}" />
+ <h2></h2>
+ <input role="yes" type="button" value="{'General_Ok'|translate}"/>
</div>
diff --git a/plugins/CoreHome/templates/index_content.tpl b/plugins/CoreHome/templates/index_content.tpl
index 04211fc48c..1d9727eed6 100644
--- a/plugins/CoreHome/templates/index_content.tpl
+++ b/plugins/CoreHome/templates/index_content.tpl
@@ -1,20 +1,19 @@
-
<div class="page">
- <div class="pageWrap">
- <div class="nav_sep"></div>
- <div class="top_controls">
- {include file="CoreHome/templates/period_select.tpl"}
- {include file="CoreHome/templates/header_message.tpl"}
- {ajaxRequestErrorDiv}
- </div>
+ <div class="pageWrap">
+ <div class="nav_sep"></div>
+ <div class="top_controls">
+ {include file="CoreHome/templates/period_select.tpl"}
+ {include file="CoreHome/templates/header_message.tpl"}
+ {ajaxRequestErrorDiv}
+ </div>
- {ajaxLoadingDiv}
+ {ajaxLoadingDiv}
- <div id="content" class="home">
- {if $content}{$content}{/if}
- </div>
- <div class="clear"></div>
- </div>
+ <div id="content" class="home">
+ {if $content}{$content}{/if}
+ </div>
+ <div class="clear"></div>
+ </div>
</div>
<br/><br/> \ No newline at end of file
diff --git a/plugins/CoreHome/templates/jqplot.css b/plugins/CoreHome/templates/jqplot.css
index 70f7742ba5..33014a7178 100644
--- a/plugins/CoreHome/templates/jqplot.css
+++ b/plugins/CoreHome/templates/jqplot.css
@@ -1,8 +1,7 @@
-
.jqplot-loading {
- background: url(../../../themes/default/images/loading-blue.gif) no-repeat center center white;
- position: absolute;
- z-index: 10;
+ background: url(../../../themes/default/images/loading-blue.gif) no-repeat center center white;
+ position: absolute;
+ z-index: 10;
}
.jqplot-target {
@@ -25,7 +24,7 @@
.jqplot-y2axis,
.jqplot-y3axis {
- margin: 0 3px 0 7px;
+ margin: 0 3px 0 7px;
}
.jqplot-axis-tick, .jqplot-xaxis-tick, .jqplot-yaxis-tick {
@@ -45,11 +44,11 @@
}
.jqplot-yaxis-tick.jqplot-breakTick {
- right: -20px;
- margin-right: 0;
- padding:1px 5px 1px 5px;
- z-index: 2;
- font-size: 1.5em;
+ right: -20px;
+ margin-right: 0;
+ padding: 1px 5px 1px 5px;
+ z-index: 2;
+ font-size: 1.5em;
}
.jqplot-xaxis-label {
@@ -71,190 +70,192 @@
font-size: 1.2em;
}
-
/**
* ROW EVOLUTION POPUP
*/
.rowevolution {
- position: relative;
- overflow: hidden;
- text-align: left
+ position: relative;
+ overflow: hidden;
+ text-align: left
}
.rowevolution h2 {
- font-size: 16px;
- margin: 0;
- padding: 0;
+ font-size: 16px;
+ margin: 0;
+ padding: 0;
}
.rowevolution .metrics-container {
- padding: 11px 0 5px 0;
+ padding: 11px 0 5px 0;
}
.rowevolution table.metrics {
- border-spacing: 0;
+ border-spacing: 0;
}
+
.multirowevolution table.metrics {
- margin-bottom: 12px;
+ margin-bottom: 12px;
}
+
.rowevolution table.metrics,
.multirowevolution table.metrics {
- /* prevent select for shift-click on metric toggles */
- user-select: none; /* CSS3 */
+ /* prevent select for shift-click on metric toggles */
+ user-select: none; /* CSS3 */
-moz-user-select: none; /* Gecko (Firefox) */
- -khtml-user-select:none; /* Webkit (Safari, Chrome) */
+ -khtml-user-select: none; /* Webkit (Safari, Chrome) */
}
.rowevolution table.metrics tr {
- margin: 0;
- padding: 0;
- cursor: pointer;
+ margin: 0;
+ padding: 0;
+ cursor: pointer;
}
.rowevolution table.metrics td {
- vertical-align: middle;
- text-align: left;
- margin: 0;
- padding: 4px 0;
- cursor: pointer;
+ vertical-align: middle;
+ text-align: left;
+ margin: 0;
+ padding: 4px 0;
+ cursor: pointer;
}
.rowevolution table.metrics td.sparkline {
- width: 120px;
+ width: 120px;
}
+
.multirowevolution table.metrics td.sparkline {
- padding-top: 15px;
+ padding-top: 15px;
}
/** IE7 does not support inline image data, which is needed for spark lines */
*+html .multirowevolution table.metrics td.sparkline,
*+html .rowevolution table.metrics td.sparkline {
- display: none;
+ display: none;
}
.rowevolution table.metrics td.text {
- font-size: 13px;
- line-height: 18px;
- color: #7E7363;
- font-weight: bold;
+ font-size: 13px;
+ line-height: 18px;
+ color: #7E7363;
+ font-weight: bold;
}
+
.multirowevolution table.metrics td.text {
- padding-top: 8px;
+ padding-top: 8px;
}
.rowevolution table.metrics td.text span.details {
- font-weight: normal;
- color: #444;
+ font-weight: normal;
+ color: #444;
}
.rowevolution table.metrics td.text span.change {
- display: block;
- float: left;
- padding-left: 15px;
+ display: block;
+ float: left;
+ padding-left: 15px;
}
.rowevolution table.metrics td.text span.good {
- color: #008000;
+ color: #008000;
}
.rowevolution table.metrics td.text span.bad {
- color: #f00;
+ color: #f00;
}
.rowevolution-documentation {
- font-size: 12px;
- margin: 2px 0 5px 0;
- padding: 5px 0 5px 23px;
- color: #888;
- background: url(../../../themes/default/images/help.png) no-repeat left center;
+ font-size: 12px;
+ margin: 2px 0 5px 0;
+ padding: 5px 0 5px 23px;
+ color: #888;
+ background: url(../../../themes/default/images/help.png) no-repeat left center;
}
.rowevolution .metric-selectbox,
.rowevolution .compare-container {
- padding: 15px 0 5px 0;
+ padding: 15px 0 5px 0;
}
.rowevolution .metric-selectbox select {
- font-size: 13px;
- color: #444;
- margin: 8px 0 0 0;
- padding: 0;
+ font-size: 13px;
+ color: #444;
+ margin: 8px 0 0 0;
+ padding: 0;
}
a.rowevolution-startmulti {
- font-size: 12px;
- color: #7E7363;
- font-weight: bold;
- text-decoration: none;
+ font-size: 12px;
+ color: #7E7363;
+ font-weight: bold;
+ text-decoration: none;
}
a.rowevolution-startmulti:hover {
- color: #444;
+ color: #444;
}
-
/**
* SERIES PICKER FOR CHARTS
*/
-
+
.jqplot-seriespicker {
- display: block;
- position: absolute;
- z-index: 9;
- width: 24px;
- height: 16px;
- margin-top: 3px;
- background: url(../../../themes/default/images/chart_line_edit.png) no-repeat center center;
- overflow: hidden;
- text-indent: -999px;
+ display: block;
+ position: absolute;
+ z-index: 9;
+ width: 24px;
+ height: 16px;
+ margin-top: 3px;
+ background: url(../../../themes/default/images/chart_line_edit.png) no-repeat center center;
+ overflow: hidden;
+ text-indent: -999px;
}
.jqplock-seriespicker-popover {
- display: block;
- position: absolute;
- z-index: 1010; /* must be above ui dialog */
- margin-top: -2px;
- background: url(../../../themes/default/images/chart_line_edit.png) no-repeat 7px 4px #f7f7f7;
- font-size: 11px;
- font-weight: normal;
- border: 1px solid #e4e5e4;
- padding: 6px 9px;
- border-radius: 4px;
- -moz-border-radius: 4px;
- -webkit-border-radius: 4px;
- -moz-box-shadow: 1px 1px 2px #666;
- -webkit-box-shadow: 1px 1px 2px #666;
- box-shadow: 1px 1px 2px #666;
+ display: block;
+ position: absolute;
+ z-index: 1010; /* must be above ui dialog */
+ margin-top: -2px;
+ background: url(../../../themes/default/images/chart_line_edit.png) no-repeat 7px 4px #f7f7f7;
+ font-size: 11px;
+ font-weight: normal;
+ border: 1px solid #e4e5e4;
+ padding: 6px 9px;
+ border-radius: 4px;
+ -moz-border-radius: 4px;
+ -webkit-border-radius: 4px;
+ -moz-box-shadow: 1px 1px 2px #666;
+ -webkit-box-shadow: 1px 1px 2px #666;
+ box-shadow: 1px 1px 2px #666;
}
.jqplock-seriespicker-popover p {
- margin: 0;
- padding: 0 4px 0 0;
- line-height: 15px;
- vertical-align:middle;
+ margin: 0;
+ padding: 0 4px 0 0;
+ line-height: 15px;
+ vertical-align: middle;
}
.jqplock-seriespicker-popover p.headline {
- font-weight: bold;
- font-size: 12px;
- padding: 0 0 6px 22px;
- color: #7E7363;
+ font-weight: bold;
+ font-size: 12px;
+ padding: 0 0 6px 22px;
+ color: #7E7363;
}
.jqplock-seriespicker-popover p.headline.recordsToPlot {
- padding: 8px 0 3px 0;
+ padding: 8px 0 3px 0;
}
.jqplock-seriespicker-popover.alignright p.headline {
- padding: 0 22px 6px 0;
+ padding: 0 22px 6px 0;
}
.jqplock-seriespicker-popover input.select {
- margin-right: 8px;
+ margin-right: 8px;
}
.jqplock-seriespicker-popover p.pickColumn,
.jqplock-seriespicker-popover p.pickRow {
- cursor: pointer;
+ cursor: pointer;
} \ No newline at end of file
diff --git a/plugins/CoreHome/templates/jqplot.js b/plugins/CoreHome/templates/jqplot.js
index 375b5b38ed..36d37a569e 100644
--- a/plugins/CoreHome/templates/jqplot.js
+++ b/plugins/CoreHome/templates/jqplot.js
@@ -14,574 +14,575 @@
* @param the data that would be passed to open flash chart
*/
function JQPlot(data, dataTableId) {
- this.init(data, dataTableId);
+ this.init(data, dataTableId);
}
JQPlot.prototype = {
- /** Generic init function */
- init: function(data, dataTableId) {
- this.dataTableId = dataTableId;
- this.originalData = data;
- this.data = data.data;
-
- defaultParams = {};
- defaultParams.grid = {
- drawGridLines: false,
- background: '#fff',
- borderColor: '#f00',
- borderWidth: 0,
- shadow: false
- };
+ /** Generic init function */
+ init: function (data, dataTableId) {
+ this.dataTableId = dataTableId;
+ this.originalData = data;
+ this.data = data.data;
- defaultParams.title = {
- show: false
- };
+ defaultParams = {};
+ defaultParams.grid = {
+ drawGridLines: false,
+ background: '#fff',
+ borderColor: '#f00',
+ borderWidth: 0,
+ shadow: false
+ };
- defaultParams.axesDefaults = {
- pad: 1.0,
- tickRenderer: $.jqplot.CanvasAxisTickRenderer,
- tickOptions: {
- showMark: false,
- fontSize: '11px',
- fontFamily: 'Arial'
- }
- };
+ defaultParams.title = {
+ show: false
+ };
- this.params = $.extend(true, {}, defaultParams, data.params);
+ defaultParams.axesDefaults = {
+ pad: 1.0,
+ tickRenderer: $.jqplot.CanvasAxisTickRenderer,
+ tickOptions: {
+ showMark: false,
+ fontSize: '11px',
+ fontFamily: 'Arial'
+ }
+ };
- this.tooltip = data.tooltip;
- this.seriesPicker = data.seriesPicker;
+ this.params = $.extend(true, {}, defaultParams, data.params);
- if (typeof this.params.axes.yaxis == 'undefined') {
- this.params.axes.yaxis = {};
- }
- if (typeof this.params.axes.yaxis.tickOptions == 'undefined') {
- this.params.yaxis.tickOptions = {
- formatString: '%d'
- };
- }
- },
-
- /** Generic render function */
- render: function(type, targetDivId, lang) {
- // preapare the appropriate chart type
- switch (type) {
- case 'evolution':
- this.prepareEvolutionChart(targetDivId, lang);
- break;
- case 'bar':
- this.prepareBarChart(targetDivId, lang);
- break;
- case 'pie':
- this.preparePieChart(targetDivId, lang);
- break;
- default:
- return;
- }
-
- // handle replot
- // this has be bound before the check for an empty graph.
- // otherwise clicking on sparklines won't work anymore after an empty
- // report has been displayed.
- var self = this;
- var target = $('#' + targetDivId)
- .on('replot', function(e, data) {
- target.trigger('piwikDestroyPlot');
- if (target.data('oldHeight') > 0) {
- // handle replot after empty report
- target.height(target.data('oldHeight'));
- target.data('oldHeight', 0);
- this.innerHTML = '';
- }
-
- (new JQPlot(data, self.dataTableId)).render(type, targetDivId, lang);
- });
-
- // show loading
- target.bind('showLoading', function() {
- var loading = $(document.createElement('div')).addClass('jqplot-loading');
- loading.css({
- width: target.innerWidth()+'px',
- height: target.innerHeight()+'px',
- opacity: 0
- });
- target.prepend(loading);
- loading.css({opacity: .7});
- });
-
- // change series
- target.bind('changeColumns', function(e, columns) {
- target.trigger('changeSeries', [columns, []]);
- });
- target.bind('changeSeries', function(e, columns, rows) {
- target.trigger('showLoading');
- if (typeof columns == 'string') {
- columns = columns.split(',');
- }
- if (typeof rows == 'undefined') {
- rows = [];
- }
- else if (typeof rows == 'string') {
- rows = rows.split(',');
- }
- var dataTable = $('#' + self.dataTableId).data('dataTableInstance');
- dataTable.param.columns = columns.join(',');
- dataTable.param.rows = rows.join(',');
- delete dataTable.param.filter_limit;
- delete dataTable.param.totalRows;
- if( dataTable.param.filter_sort_column != 'label' ) {
- dataTable.param.filter_sort_column = columns[0];
- }
- dataTable.param.disable_generic_filters = '0';
- dataTable.reloadAjaxDataTable(false);
- });
-
- // this case happens when there is no data for a line chart
- if (this.data.length == 0) {
- target.addClass('pk-emptyGraph');
- target.data('oldHeight', target.height());
- target.css('height', 'auto').html(lang.noData);
- return;
- }
-
- // create jqplot chart
- try {
- var plot = $.jqplot(targetDivId, this.data, this.params);
- } catch(e) {
- // this is thrown when refreshing piwik in the browser
- if (e != "No plot target specified") {
- throw e;
- }
- }
-
- // bind tooltip
- var self = this;
- target.on('jqplotDataHighlight', function(e, s, i, d) {
- if (type == 'bar') {
- self.showBarChartTooltip(s, i);
- } else if (type == 'pie') {
- self.showPieChartTooltip(i);
- }
- })
- .on('jqplotDataUnhighlight', function(e, s, i, d){
- if (type != 'evolution') {
- self.hideTooltip();
- }
- });
-
- // handle window resize
- var plotWidth = target.innerWidth();
- var timeout = false;
- target.on('resizeGraph', function() {
- var width = target.innerWidth();
- if (width > 0 && Math.abs(plotWidth - width) >= 5) {
- plotWidth = width;
- target.trigger('piwikDestroyPlot');
- (new JQPlot(self.originalData, self.dataTableId))
- .render(type, targetDivId, lang);
- }
- });
- var resizeListener = function() {
- if (timeout) {
- window.clearTimeout(timeout);
- }
- timeout = window.setTimeout(function() {
- target.trigger('resizeGraph');
- }, 300);
- };
- $(window).on('resize', resizeListener);
-
- // export as image
- target.on('piwikExportAsImage', function(e) {
- self.exportAsImage(target, lang);
- });
-
- // manage resources
- target.on('piwikDestroyPlot', function() {
- $(window).off('resize', resizeListener);
- plot.destroy();
- for (var i = 0; i < $.jqplot.visiblePlots.length; i++) {
- if ($.jqplot.visiblePlots[i] == plot) {
- $.jqplot.visiblePlots[i] = null;
- }
- }
- $(this).off();
- });
-
- if (typeof $.jqplot.visiblePlots == 'undefined') {
- $.jqplot.visiblePlots = [];
- $('ul.nav').on('piwikSwitchPage', function() {
- for (var i = 0; i < $.jqplot.visiblePlots.length; i++) {
- if ($.jqplot.visiblePlots[i] == null) {
- continue;
- }
- $.jqplot.visiblePlots[i].destroy();
- }
- $.jqplot.visiblePlots = [];
- });
- }
-
- if (typeof plot != 'undefined') {
- $.jqplot.visiblePlots.push(plot);
- }
- },
-
- /** Export the chart as an image */
- exportAsImage: function(container, lang) {
- var exportCanvas = document.createElement('canvas');
- exportCanvas.width = container.width();
- exportCanvas.height = container.height();
-
- if(!exportCanvas.getContext) { alert("Sorry, not supported in your browser. Please upgrade your browser :)"); return; }
- var exportCtx = exportCanvas.getContext('2d');
-
- var canvases = container.find('canvas');
-
- for (var i=0; i < canvases.length; i++) {
- var canvas = canvases.eq(i);
- var position = canvas.position();
- var parent = canvas.parent();
- if (parent.hasClass('jqplot-axis')) {
- var addPosition = parent.position();
- position.left += addPosition.left;
- position.top += addPosition.top + parseInt(parent.css('marginTop'), 10);
- }
- exportCtx.drawImage(canvas[0], Math.round(position.left), Math.round(position.top));
- }
-
- var exported = exportCanvas.toDataURL("image/png");
-
- var img = document.createElement('img');
- img.src = exported;
-
- img = $(img).css({
- width: exportCanvas.width + 'px',
- height: exportCanvas.height + 'px'
- });
-
- $(document.createElement('div'))
- .append('<div style="font-size: 13px; margin-bottom: 10px;">'
- + lang.exportText + '</div>').append($(img))
- .dialog({
- title: lang.exportTitle,
- modal: true,
- width: 'auto',
- position: ['center', 'center'],
- resizable: false,
- autoOpen: true,
- close: function(event, ui) {
- $(this).dialog("destroy").remove();
- }
- });
- },
-
-
- // ------------------------------------------------------------
- // EVOLUTION CHART
- // ------------------------------------------------------------
-
- prepareEvolutionChart: function(targetDivId, lang) {
- this.setYTicks();
- this.addSeriesPicker(targetDivId, lang);
-
- defaultParams.axes = {
- xaxis: {
- pad: 1.0,
- renderer: $.jqplot.CategoryAxisRenderer,
- tickOptions: {
- showGridline: false
+ this.tooltip = data.tooltip;
+ this.seriesPicker = data.seriesPicker;
+
+ if (typeof this.params.axes.yaxis == 'undefined') {
+ this.params.axes.yaxis = {};
+ }
+ if (typeof this.params.axes.yaxis.tickOptions == 'undefined') {
+ this.params.yaxis.tickOptions = {
+ formatString: '%d'
+ };
+ }
+ },
+
+ /** Generic render function */
+ render: function (type, targetDivId, lang) {
+ // preapare the appropriate chart type
+ switch (type) {
+ case 'evolution':
+ this.prepareEvolutionChart(targetDivId, lang);
+ break;
+ case 'bar':
+ this.prepareBarChart(targetDivId, lang);
+ break;
+ case 'pie':
+ this.preparePieChart(targetDivId, lang);
+ break;
+ default:
+ return;
}
- }
- };
- defaultParams.seriesDefaults = {
- lineWidth: 1,
- markerOptions: {
- style: "filledCircle",
- size: 6,
- shadow: false
- }
- };
+ // handle replot
+ // this has be bound before the check for an empty graph.
+ // otherwise clicking on sparklines won't work anymore after an empty
+ // report has been displayed.
+ var self = this;
+ var target = $('#' + targetDivId)
+ .on('replot', function (e, data) {
+ target.trigger('piwikDestroyPlot');
+ if (target.data('oldHeight') > 0) {
+ // handle replot after empty report
+ target.height(target.data('oldHeight'));
+ target.data('oldHeight', 0);
+ this.innerHTML = '';
+ }
- defaultParams.piwikTicks = {
- showTicks: true,
- showGrid: true,
- showHighlight: true
- };
+ (new JQPlot(data, self.dataTableId)).render(type, targetDivId, lang);
+ });
- this.params = $.extend(true, {}, defaultParams, this.params);
+ // show loading
+ target.bind('showLoading', function () {
+ var loading = $(document.createElement('div')).addClass('jqplot-loading');
+ loading.css({
+ width: target.innerWidth() + 'px',
+ height: target.innerHeight() + 'px',
+ opacity: 0
+ });
+ target.prepend(loading);
+ loading.css({opacity: .7});
+ });
- var self = this;
- var lastTick = false;
-
- $('#' + targetDivId)
- .on('jqplotMouseLeave', function(e, s, i, d){
- self.hideTooltip();
- $(this).css('cursor', 'default');
- })
- .on('jqplotClick', function(e, s, i, d){
- if (lastTick !== false && typeof self.params.axes.xaxis.onclick != 'undefined'
- && typeof self.params.axes.xaxis.onclick[lastTick] == 'string') {
- var url = self.params.axes.xaxis.onclick[lastTick];
- piwikHelper.redirectToUrl(url);
- }
- })
- .on('jqplotPiwikTickOver', function(e, tick){
- lastTick = tick;
- self.showEvolutionChartTooltip(tick);
- if (typeof self.params.axes.xaxis.onclick != 'undefined'
- && typeof self.params.axes.xaxis.onclick[lastTick] == 'string') {
- $(this).css('cursor', 'pointer');
- }
- });
-
- this.params.legend = {
- show: false
- };
- this.params.canvasLegend = {
- show: true
- };
- },
-
- showEvolutionChartTooltip: function(i) {
- var label;
- if (typeof this.params.axes.xaxis.labels != 'undefined') {
- label = this.params.axes.xaxis.labels[i];
- } else {
- label = this.params.axes.xaxis.ticks[i];
- }
-
- var text = [];
- for (var d = 0; d < this.data.length; d++) {
- var value = this.formatY(this.data[d][i], d);
- var series = this.params.series[d].label;
- text.push('<b>' + value + '</b> ' + series);
- }
-
- this.showTooltip(label, text.join('<br />'));
- },
-
-
- // ------------------------------------------------------------
- // PIE CHART
- // ------------------------------------------------------------
-
- preparePieChart: function(targetDivId, lang) {
- this.addSeriesPicker(targetDivId, lang);
-
- this.params.seriesDefaults = {
- renderer: $.jqplot.PieRenderer,
- rendererOptions: {
- shadow: false,
- showDataLabels: false,
- sliceMargin: 1,
- startAngle: 35
- }
- };
-
- this.params.piwikTicks = {
- showTicks: false,
- showGrid: false,
- showHighlight: false
- };
-
- this.params.legend = {
- show: false
- };
- this.params.pieLegend = {
- show: true
- };
- this.params.canvasLegend = {
- show: true,
- singleMetric: true
- };
-
- // pie charts have a different data format
- if (!(this.data[0][0] instanceof Array)) { // check if already in different format
- for (var i = 0; i < this.data[0].length; i++) {
- this.data[0][i] = [this.params.axes.xaxis.ticks[i], this.data[0][i]];
- }
- }
- },
-
- showPieChartTooltip: function(i) {
- var value = this.formatY(this.data[0][i][1], 1); // series index 1 because 0 is the label
- var series = this.params.series[0].label;
- var percentage = this.tooltip.percentages[0][i];
-
- var label = this.data[0][i][0];
-
- var text = '<b>' + percentage + '%</b> (' + value + ' ' + series + ')';
- this.showTooltip(label, text);
- },
-
-
- // ------------------------------------------------------------
- // BAR CHART
- // ------------------------------------------------------------
-
- prepareBarChart: function(targetDivId, lang) {
- this.setYTicks();
- this.addSeriesPicker(targetDivId, lang);
-
- this.params.seriesDefaults = {
- renderer: $.jqplot.BarRenderer,
- rendererOptions: {
- shadowOffset: 1,
- shadowDepth: 2,
- shadowAlpha: .2,
- fillToZero: true,
- barMargin: this.data[0].length > 10 ? 2 : 10
- }
- };
-
- this.params.piwikTicks = {
- showTicks: true,
- showGrid: false,
- showHighlight: false
- };
-
- this.params.axes.xaxis.renderer = $.jqplot.CategoryAxisRenderer;
- this.params.axes.xaxis.tickOptions = {
- showGridline: false
- };
-
- this.params.canvasLegend = {
- show: true
- };
- },
-
- showBarChartTooltip: function(s, i) {
- var value = this.formatY(this.data[s][i], s);
- var series = this.params.series[s].label;
-
- var percentage = '';
- if (typeof this.tooltip.percentages != 'undefined') {
- var percentage = this.tooltip.percentages[s][i];
- percentage = ' (' + percentage + '%)';
- }
-
- var label = this.params.axes.xaxis.labels[i];
- var text = '<b>' + value + '</b> ' + series + percentage;
- this.showTooltip(label, text);
- },
-
-
- // ------------------------------------------------------------
- // HELPER METHODS
- // ------------------------------------------------------------
-
- /** Generate ticks in y direction */
- setYTicks: function() {
- // default axis
- this.setYTicksForAxis('yaxis', this.params.axes.yaxis);
- // other axes: y2axis, y3axis...
- for (var i = 2; typeof this.params.axes['y'+i+'axis'] != 'undefined'; i++) {
- this.setYTicksForAxis('y'+i+'axis', this.params.axes['y'+i+'axis']);
- }
- },
-
- setYTicksForAxis: function(axisName, axis) {
- // calculate maximum x value of all data sets
- var maxCrossDataSets = 0;
- for (var i = 0; i < this.data.length; i++) {
- if (this.params.series[i].yaxis == axisName) {
- maxValue = Math.max.apply(Math, this.data[i]);
- if (maxValue > maxCrossDataSets) {
- maxCrossDataSets = maxValue;
- }
- maxCrossDataSets = parseFloat(maxCrossDataSets);
- }
- }
-
- // add little padding on top
- maxCrossDataSets += Math.max(1, Math.round(maxCrossDataSets * .03));
-
- // round to the nearest multiple of ten
- if (maxCrossDataSets > 15) {
- maxCrossDataSets = maxCrossDataSets + 10 - maxCrossDataSets % 10;
- }
-
- if (maxCrossDataSets == 0) {
- maxCrossDataSets = 1;
- }
-
- // make sure percent axes don't go above 100%
- if (axis.tickOptions.formatString.substring(2, 3) == '%' && maxCrossDataSets > 100) {
- maxCrossDataSets = 100;
- }
-
- // calculate y-values for ticks
- ticks = [];
- numberOfTicks = 2;
- tickDistance = Math.ceil(maxCrossDataSets / numberOfTicks);
- for (var i = 0; i <= numberOfTicks; i++) {
- ticks.push(i * tickDistance);
- }
- axis.ticks = ticks;
- },
-
- /** Get a formatted y values (with unit) */
- formatY: function(value, seriesIndex) {
- var floatVal = parseFloat(value);
- var intVal = parseInt(value, 10);
- if (Math.abs(floatVal - intVal) >= 0.005) {
- value = Math.round(floatVal * 100) / 100;
- } else if (parseFloat(intVal) == floatVal) {
- value = intVal;
- } else {
- value = floatVal;
- }
- if (typeof this.tooltip.yUnits[seriesIndex] != 'undefined') {
- value += this.tooltip.yUnits[seriesIndex];
- }
-
- return value;
- },
-
- /** Show the tppltip. The DOM element is created on the fly. */
- showTooltip: function(head, text) {
- Piwik_Tooltip.showWithTitle(head, text);
- },
-
- /** Hide the tooltip */
- hideTooltip: function() {
- Piwik_Tooltip.hide();
- },
-
- addSeriesPicker: function(targetDivId, lang) {
- this.params.seriesPicker = {
- show: typeof this.seriesPicker.selectableColumns == 'object'
- || typeof this.seriesPicker.selectableRows == 'object',
- selectableColumns: this.seriesPicker.selectableColumns,
- selectableRows: this.seriesPicker.selectableRows,
- multiSelect: this.seriesPicker.multiSelect,
- targetDivId: targetDivId,
- dataTableId: this.dataTableId,
- lang: lang
- };
- },
-
- /**
- * Add an external series toggle.
- * As opposed to addSeriesPicker, the external series toggle can only show/hide
- * series that are already loaded.
- * @param seriesPickerClass a subclass of JQPlotExternalSeriesToggle
- */
- addExternalSeriesToggle: function(seriesPickerClass, targetDivId, initiallyShowAll) {
- new seriesPickerClass(targetDivId, this.originalData, initiallyShowAll);
-
- if (!initiallyShowAll) {
- // initially, show only the first series
- this.data = [this.data[0]];
- this.params.series = [this.params.series[0]];
- }
- }
-
-};
+ // change series
+ target.bind('changeColumns', function (e, columns) {
+ target.trigger('changeSeries', [columns, []]);
+ });
+ target.bind('changeSeries', function (e, columns, rows) {
+ target.trigger('showLoading');
+ if (typeof columns == 'string') {
+ columns = columns.split(',');
+ }
+ if (typeof rows == 'undefined') {
+ rows = [];
+ }
+ else if (typeof rows == 'string') {
+ rows = rows.split(',');
+ }
+ var dataTable = $('#' + self.dataTableId).data('dataTableInstance');
+ dataTable.param.columns = columns.join(',');
+ dataTable.param.rows = rows.join(',');
+ delete dataTable.param.filter_limit;
+ delete dataTable.param.totalRows;
+ if (dataTable.param.filter_sort_column != 'label') {
+ dataTable.param.filter_sort_column = columns[0];
+ }
+ dataTable.param.disable_generic_filters = '0';
+ dataTable.reloadAjaxDataTable(false);
+ });
+
+ // this case happens when there is no data for a line chart
+ if (this.data.length == 0) {
+ target.addClass('pk-emptyGraph');
+ target.data('oldHeight', target.height());
+ target.css('height', 'auto').html(lang.noData);
+ return;
+ }
+
+ // create jqplot chart
+ try {
+ var plot = $.jqplot(targetDivId, this.data, this.params);
+ } catch (e) {
+ // this is thrown when refreshing piwik in the browser
+ if (e != "No plot target specified") {
+ throw e;
+ }
+ }
+
+ // bind tooltip
+ var self = this;
+ target.on('jqplotDataHighlight', function (e, s, i, d) {
+ if (type == 'bar') {
+ self.showBarChartTooltip(s, i);
+ } else if (type == 'pie') {
+ self.showPieChartTooltip(i);
+ }
+ })
+ .on('jqplotDataUnhighlight', function (e, s, i, d) {
+ if (type != 'evolution') {
+ self.hideTooltip();
+ }
+ });
+
+ // handle window resize
+ var plotWidth = target.innerWidth();
+ var timeout = false;
+ target.on('resizeGraph', function () {
+ var width = target.innerWidth();
+ if (width > 0 && Math.abs(plotWidth - width) >= 5) {
+ plotWidth = width;
+ target.trigger('piwikDestroyPlot');
+ (new JQPlot(self.originalData, self.dataTableId))
+ .render(type, targetDivId, lang);
+ }
+ });
+ var resizeListener = function () {
+ if (timeout) {
+ window.clearTimeout(timeout);
+ }
+ timeout = window.setTimeout(function () {
+ target.trigger('resizeGraph');
+ }, 300);
+ };
+ $(window).on('resize', resizeListener);
+
+ // export as image
+ target.on('piwikExportAsImage', function (e) {
+ self.exportAsImage(target, lang);
+ });
+
+ // manage resources
+ target.on('piwikDestroyPlot', function () {
+ $(window).off('resize', resizeListener);
+ plot.destroy();
+ for (var i = 0; i < $.jqplot.visiblePlots.length; i++) {
+ if ($.jqplot.visiblePlots[i] == plot) {
+ $.jqplot.visiblePlots[i] = null;
+ }
+ }
+ $(this).off();
+ });
+
+ if (typeof $.jqplot.visiblePlots == 'undefined') {
+ $.jqplot.visiblePlots = [];
+ $('ul.nav').on('piwikSwitchPage', function () {
+ for (var i = 0; i < $.jqplot.visiblePlots.length; i++) {
+ if ($.jqplot.visiblePlots[i] == null) {
+ continue;
+ }
+ $.jqplot.visiblePlots[i].destroy();
+ }
+ $.jqplot.visiblePlots = [];
+ });
+ }
+
+ if (typeof plot != 'undefined') {
+ $.jqplot.visiblePlots.push(plot);
+ }
+ },
+
+ /** Export the chart as an image */
+ exportAsImage: function (container, lang) {
+ var exportCanvas = document.createElement('canvas');
+ exportCanvas.width = container.width();
+ exportCanvas.height = container.height();
+
+ if (!exportCanvas.getContext) {
+ alert("Sorry, not supported in your browser. Please upgrade your browser :)");
+ return;
+ }
+ var exportCtx = exportCanvas.getContext('2d');
+
+ var canvases = container.find('canvas');
+
+ for (var i = 0; i < canvases.length; i++) {
+ var canvas = canvases.eq(i);
+ var position = canvas.position();
+ var parent = canvas.parent();
+ if (parent.hasClass('jqplot-axis')) {
+ var addPosition = parent.position();
+ position.left += addPosition.left;
+ position.top += addPosition.top + parseInt(parent.css('marginTop'), 10);
+ }
+ exportCtx.drawImage(canvas[0], Math.round(position.left), Math.round(position.top));
+ }
+
+ var exported = exportCanvas.toDataURL("image/png");
+
+ var img = document.createElement('img');
+ img.src = exported;
+
+ img = $(img).css({
+ width: exportCanvas.width + 'px',
+ height: exportCanvas.height + 'px'
+ });
+
+ $(document.createElement('div'))
+ .append('<div style="font-size: 13px; margin-bottom: 10px;">'
+ + lang.exportText + '</div>').append($(img))
+ .dialog({
+ title: lang.exportTitle,
+ modal: true,
+ width: 'auto',
+ position: ['center', 'center'],
+ resizable: false,
+ autoOpen: true,
+ close: function (event, ui) {
+ $(this).dialog("destroy").remove();
+ }
+ });
+ },
+
+
+ // ------------------------------------------------------------
+ // EVOLUTION CHART
+ // ------------------------------------------------------------
+ prepareEvolutionChart: function (targetDivId, lang) {
+ this.setYTicks();
+ this.addSeriesPicker(targetDivId, lang);
+ defaultParams.axes = {
+ xaxis: {
+ pad: 1.0,
+ renderer: $.jqplot.CategoryAxisRenderer,
+ tickOptions: {
+ showGridline: false
+ }
+ }
+ };
+
+ defaultParams.seriesDefaults = {
+ lineWidth: 1,
+ markerOptions: {
+ style: "filledCircle",
+ size: 6,
+ shadow: false
+ }
+ };
+
+ defaultParams.piwikTicks = {
+ showTicks: true,
+ showGrid: true,
+ showHighlight: true
+ };
+
+ this.params = $.extend(true, {}, defaultParams, this.params);
+
+ var self = this;
+ var lastTick = false;
+
+ $('#' + targetDivId)
+ .on('jqplotMouseLeave', function (e, s, i, d) {
+ self.hideTooltip();
+ $(this).css('cursor', 'default');
+ })
+ .on('jqplotClick', function (e, s, i, d) {
+ if (lastTick !== false && typeof self.params.axes.xaxis.onclick != 'undefined'
+ && typeof self.params.axes.xaxis.onclick[lastTick] == 'string') {
+ var url = self.params.axes.xaxis.onclick[lastTick];
+ piwikHelper.redirectToUrl(url);
+ }
+ })
+ .on('jqplotPiwikTickOver', function (e, tick) {
+ lastTick = tick;
+ self.showEvolutionChartTooltip(tick);
+ if (typeof self.params.axes.xaxis.onclick != 'undefined'
+ && typeof self.params.axes.xaxis.onclick[lastTick] == 'string') {
+ $(this).css('cursor', 'pointer');
+ }
+ });
+
+ this.params.legend = {
+ show: false
+ };
+ this.params.canvasLegend = {
+ show: true
+ };
+ },
+
+ showEvolutionChartTooltip: function (i) {
+ var label;
+ if (typeof this.params.axes.xaxis.labels != 'undefined') {
+ label = this.params.axes.xaxis.labels[i];
+ } else {
+ label = this.params.axes.xaxis.ticks[i];
+ }
+
+ var text = [];
+ for (var d = 0; d < this.data.length; d++) {
+ var value = this.formatY(this.data[d][i], d);
+ var series = this.params.series[d].label;
+ text.push('<b>' + value + '</b> ' + series);
+ }
+
+ this.showTooltip(label, text.join('<br />'));
+ },
+
+
+ // ------------------------------------------------------------
+ // PIE CHART
+ // ------------------------------------------------------------
+
+ preparePieChart: function (targetDivId, lang) {
+ this.addSeriesPicker(targetDivId, lang);
+
+ this.params.seriesDefaults = {
+ renderer: $.jqplot.PieRenderer,
+ rendererOptions: {
+ shadow: false,
+ showDataLabels: false,
+ sliceMargin: 1,
+ startAngle: 35
+ }
+ };
+
+ this.params.piwikTicks = {
+ showTicks: false,
+ showGrid: false,
+ showHighlight: false
+ };
+
+ this.params.legend = {
+ show: false
+ };
+ this.params.pieLegend = {
+ show: true
+ };
+ this.params.canvasLegend = {
+ show: true,
+ singleMetric: true
+ };
+
+ // pie charts have a different data format
+ if (!(this.data[0][0] instanceof Array)) { // check if already in different format
+ for (var i = 0; i < this.data[0].length; i++) {
+ this.data[0][i] = [this.params.axes.xaxis.ticks[i], this.data[0][i]];
+ }
+ }
+ },
+
+ showPieChartTooltip: function (i) {
+ var value = this.formatY(this.data[0][i][1], 1); // series index 1 because 0 is the label
+ var series = this.params.series[0].label;
+ var percentage = this.tooltip.percentages[0][i];
+
+ var label = this.data[0][i][0];
+
+ var text = '<b>' + percentage + '%</b> (' + value + ' ' + series + ')';
+ this.showTooltip(label, text);
+ },
+
+
+ // ------------------------------------------------------------
+ // BAR CHART
+ // ------------------------------------------------------------
+
+ prepareBarChart: function (targetDivId, lang) {
+ this.setYTicks();
+ this.addSeriesPicker(targetDivId, lang);
+
+ this.params.seriesDefaults = {
+ renderer: $.jqplot.BarRenderer,
+ rendererOptions: {
+ shadowOffset: 1,
+ shadowDepth: 2,
+ shadowAlpha: .2,
+ fillToZero: true,
+ barMargin: this.data[0].length > 10 ? 2 : 10
+ }
+ };
+
+ this.params.piwikTicks = {
+ showTicks: true,
+ showGrid: false,
+ showHighlight: false
+ };
+
+ this.params.axes.xaxis.renderer = $.jqplot.CategoryAxisRenderer;
+ this.params.axes.xaxis.tickOptions = {
+ showGridline: false
+ };
+
+ this.params.canvasLegend = {
+ show: true
+ };
+ },
+
+ showBarChartTooltip: function (s, i) {
+ var value = this.formatY(this.data[s][i], s);
+ var series = this.params.series[s].label;
+
+ var percentage = '';
+ if (typeof this.tooltip.percentages != 'undefined') {
+ var percentage = this.tooltip.percentages[s][i];
+ percentage = ' (' + percentage + '%)';
+ }
+
+ var label = this.params.axes.xaxis.labels[i];
+ var text = '<b>' + value + '</b> ' + series + percentage;
+ this.showTooltip(label, text);
+ },
+
+
+ // ------------------------------------------------------------
+ // HELPER METHODS
+ // ------------------------------------------------------------
+
+ /** Generate ticks in y direction */
+ setYTicks: function () {
+ // default axis
+ this.setYTicksForAxis('yaxis', this.params.axes.yaxis);
+ // other axes: y2axis, y3axis...
+ for (var i = 2; typeof this.params.axes['y' + i + 'axis'] != 'undefined'; i++) {
+ this.setYTicksForAxis('y' + i + 'axis', this.params.axes['y' + i + 'axis']);
+ }
+ },
+
+ setYTicksForAxis: function (axisName, axis) {
+ // calculate maximum x value of all data sets
+ var maxCrossDataSets = 0;
+ for (var i = 0; i < this.data.length; i++) {
+ if (this.params.series[i].yaxis == axisName) {
+ maxValue = Math.max.apply(Math, this.data[i]);
+ if (maxValue > maxCrossDataSets) {
+ maxCrossDataSets = maxValue;
+ }
+ maxCrossDataSets = parseFloat(maxCrossDataSets);
+ }
+ }
+
+ // add little padding on top
+ maxCrossDataSets += Math.max(1, Math.round(maxCrossDataSets * .03));
+
+ // round to the nearest multiple of ten
+ if (maxCrossDataSets > 15) {
+ maxCrossDataSets = maxCrossDataSets + 10 - maxCrossDataSets % 10;
+ }
+
+ if (maxCrossDataSets == 0) {
+ maxCrossDataSets = 1;
+ }
+
+ // make sure percent axes don't go above 100%
+ if (axis.tickOptions.formatString.substring(2, 3) == '%' && maxCrossDataSets > 100) {
+ maxCrossDataSets = 100;
+ }
+
+ // calculate y-values for ticks
+ ticks = [];
+ numberOfTicks = 2;
+ tickDistance = Math.ceil(maxCrossDataSets / numberOfTicks);
+ for (var i = 0; i <= numberOfTicks; i++) {
+ ticks.push(i * tickDistance);
+ }
+ axis.ticks = ticks;
+ },
+
+ /** Get a formatted y values (with unit) */
+ formatY: function (value, seriesIndex) {
+ var floatVal = parseFloat(value);
+ var intVal = parseInt(value, 10);
+ if (Math.abs(floatVal - intVal) >= 0.005) {
+ value = Math.round(floatVal * 100) / 100;
+ } else if (parseFloat(intVal) == floatVal) {
+ value = intVal;
+ } else {
+ value = floatVal;
+ }
+ if (typeof this.tooltip.yUnits[seriesIndex] != 'undefined') {
+ value += this.tooltip.yUnits[seriesIndex];
+ }
+
+ return value;
+ },
+
+ /** Show the tppltip. The DOM element is created on the fly. */
+ showTooltip: function (head, text) {
+ Piwik_Tooltip.showWithTitle(head, text);
+ },
+
+ /** Hide the tooltip */
+ hideTooltip: function () {
+ Piwik_Tooltip.hide();
+ },
+
+ addSeriesPicker: function (targetDivId, lang) {
+ this.params.seriesPicker = {
+ show: typeof this.seriesPicker.selectableColumns == 'object'
+ || typeof this.seriesPicker.selectableRows == 'object',
+ selectableColumns: this.seriesPicker.selectableColumns,
+ selectableRows: this.seriesPicker.selectableRows,
+ multiSelect: this.seriesPicker.multiSelect,
+ targetDivId: targetDivId,
+ dataTableId: this.dataTableId,
+ lang: lang
+ };
+ },
+
+ /**
+ * Add an external series toggle.
+ * As opposed to addSeriesPicker, the external series toggle can only show/hide
+ * series that are already loaded.
+ * @param seriesPickerClass a subclass of JQPlotExternalSeriesToggle
+ */
+ addExternalSeriesToggle: function (seriesPickerClass, targetDivId, initiallyShowAll) {
+ new seriesPickerClass(targetDivId, this.originalData, initiallyShowAll);
+
+ if (!initiallyShowAll) {
+ // initially, show only the first series
+ this.data = [this.data[0]];
+ this.params.series = [this.params.series[0]];
+ }
+ }
+
+};
// ----------------------------------------------------------------
@@ -590,101 +591,101 @@ JQPlot.prototype = {
// ----------------------------------------------------------------
function JQPlotExternalSeriesToggle(targetDivId, originalConfig, initiallyShowAll) {
- this.init(targetDivId, originalConfig, initiallyShowAll);
+ this.init(targetDivId, originalConfig, initiallyShowAll);
}
JQPlotExternalSeriesToggle.prototype = {
-
- init: function(targetDivId, originalConfig, initiallyShowAll) {
- this.targetDivId = targetDivId;
- this.originalConfig = originalConfig;
- this.originalData = originalConfig.data;
- this.originalSeries = originalConfig.params.series;
- this.originalAxes = originalConfig.params.axes;
- this.originalTooltipUnits = originalConfig.tooltip.yUnits;
- this.originalSeriesColors = originalConfig.params.seriesColors;
- this.initiallyShowAll = initiallyShowAll;
-
- this.activated = [];
- this.target = $('#'+targetDivId);
-
- this.attachEvents();
- },
-
- // can be overridden
- attachEvents: function() {},
-
- // show a single series
- showSeries: function(i) {
- for (var j = 0; j < this.activated.length; j++) {
- this.activated[j] = (i == j);
- }
- this.replot();
- },
-
- // toggle a series (make plotting multiple series possible)
- toggleSeries: function(i) {
- var activatedCount = 0;
- for (var k = 0; k < this.activated.length; k++) {
- if (this.activated[k]) {
- activatedCount++;
- }
- }
- if (activatedCount == 1 && this.activated[i]) {
- // prevent removing the only visible metric
- return;
- }
-
- this.activated[i] = !this.activated[i];
- this.replot();
- },
-
- replot: function() {
- this.beforeReplot();
-
- // build new config and replot
- var usedAxes = [];
- var config = this.originalConfig;
- config.data = [];
- config.params.series = [];
- config.params.axes = {xaxis: this.originalAxes.xaxis};
- config.tooltip.yUnits = [];
- config.params.seriesColors = [];
- for (var j = 0; j < this.activated.length; j++) {
- if (!this.activated[j]) {
- continue;
- }
- config.data.push(this.originalData[j]);
- config.tooltip.yUnits.push(this.originalTooltipUnits[j]);
- config.params.seriesColors.push(this.originalSeriesColors[j]);
- config.params.series.push($.extend(true, {}, this.originalSeries[j]));
- // build array of used axes
- var axis = this.originalSeries[j].yaxis;
- if ($.inArray(axis, usedAxes) == -1) {
- usedAxes.push(axis);
- }
- }
-
- // build new axes config
- var replaceAxes = {};
- for (j = 0; j < usedAxes.length; j++) {
- var originalAxisName = usedAxes[j];
- var newAxisName = (j == 0 ? 'yaxis' : 'y' + (j+1) + 'axis');
- replaceAxes[originalAxisName] = newAxisName;
- config.params.axes[newAxisName] = this.originalAxes[originalAxisName];
- }
-
- // replace axis names in series config
- for (j = 0; j < config.params.series.length; j++) {
- var series = config.params.series[j];
- series.yaxis = replaceAxes[series.yaxis];
- }
-
- this.target.trigger('replot', config);
- },
-
- // can be overridden
- beforeReplot: function() {}
+
+ init: function (targetDivId, originalConfig, initiallyShowAll) {
+ this.targetDivId = targetDivId;
+ this.originalConfig = originalConfig;
+ this.originalData = originalConfig.data;
+ this.originalSeries = originalConfig.params.series;
+ this.originalAxes = originalConfig.params.axes;
+ this.originalTooltipUnits = originalConfig.tooltip.yUnits;
+ this.originalSeriesColors = originalConfig.params.seriesColors;
+ this.initiallyShowAll = initiallyShowAll;
+
+ this.activated = [];
+ this.target = $('#' + targetDivId);
+
+ this.attachEvents();
+ },
+
+ // can be overridden
+ attachEvents: function () {},
+
+ // show a single series
+ showSeries: function (i) {
+ for (var j = 0; j < this.activated.length; j++) {
+ this.activated[j] = (i == j);
+ }
+ this.replot();
+ },
+
+ // toggle a series (make plotting multiple series possible)
+ toggleSeries: function (i) {
+ var activatedCount = 0;
+ for (var k = 0; k < this.activated.length; k++) {
+ if (this.activated[k]) {
+ activatedCount++;
+ }
+ }
+ if (activatedCount == 1 && this.activated[i]) {
+ // prevent removing the only visible metric
+ return;
+ }
+
+ this.activated[i] = !this.activated[i];
+ this.replot();
+ },
+
+ replot: function () {
+ this.beforeReplot();
+
+ // build new config and replot
+ var usedAxes = [];
+ var config = this.originalConfig;
+ config.data = [];
+ config.params.series = [];
+ config.params.axes = {xaxis: this.originalAxes.xaxis};
+ config.tooltip.yUnits = [];
+ config.params.seriesColors = [];
+ for (var j = 0; j < this.activated.length; j++) {
+ if (!this.activated[j]) {
+ continue;
+ }
+ config.data.push(this.originalData[j]);
+ config.tooltip.yUnits.push(this.originalTooltipUnits[j]);
+ config.params.seriesColors.push(this.originalSeriesColors[j]);
+ config.params.series.push($.extend(true, {}, this.originalSeries[j]));
+ // build array of used axes
+ var axis = this.originalSeries[j].yaxis;
+ if ($.inArray(axis, usedAxes) == -1) {
+ usedAxes.push(axis);
+ }
+ }
+
+ // build new axes config
+ var replaceAxes = {};
+ for (j = 0; j < usedAxes.length; j++) {
+ var originalAxisName = usedAxes[j];
+ var newAxisName = (j == 0 ? 'yaxis' : 'y' + (j + 1) + 'axis');
+ replaceAxes[originalAxisName] = newAxisName;
+ config.params.axes[newAxisName] = this.originalAxes[originalAxisName];
+ }
+
+ // replace axis names in series config
+ for (j = 0; j < config.params.series.length; j++) {
+ var series = config.params.series[j];
+ series.yaxis = replaceAxes[series.yaxis];
+ }
+
+ this.target.trigger('replot', config);
+ },
+
+ // can be overridden
+ beforeReplot: function () {}
};
@@ -692,59 +693,56 @@ JQPlotExternalSeriesToggle.prototype = {
// ROW EVOLUTION SERIES TOGGLE
function RowEvolutionSeriesToggle(targetDivId, originalConfig, initiallyShowAll) {
- this.init(targetDivId, originalConfig, initiallyShowAll);
+ this.init(targetDivId, originalConfig, initiallyShowAll);
}
RowEvolutionSeriesToggle.prototype = JQPlotExternalSeriesToggle.prototype;
-RowEvolutionSeriesToggle.prototype.attachEvents = function() {
- var self = this;
- this.seriesPickers = this.target.closest('.rowevolution').find('table.metrics tr');
-
- this.seriesPickers.each(function(i) {
- var el = $(this);
- el.click(function(e) {
- if (e.shiftKey) {
- self.toggleSeries(i);
- } else {
- self.showSeries(i);
- }
- return false;
- });
-
- if (i == 0 || self.initiallyShowAll) {
- // show the active series
- // if initiallyShowAll, all are active; otherwise only the first one
- self.activated.push(true);
- } else {
- // fade out the others
- el.find('td').css('opacity', .5);
- self.activated.push(false);
- }
-
- // prevent selecting in ie & opera (they don't support doing this via css)
- if ($.browser.msie) {
- this.ondrag = function() { return false; };
- this.onselectstart = function() { return false; };
- } else if ($.browser.opera) {
- $(this).attr('unselectable', 'on');
- }
- });
-};
+RowEvolutionSeriesToggle.prototype.attachEvents = function () {
+ var self = this;
+ this.seriesPickers = this.target.closest('.rowevolution').find('table.metrics tr');
-RowEvolutionSeriesToggle.prototype.beforeReplot = function() {
- // fade out if not activated
- for (var i = 0; i < this.activated.length; i++) {
- if (this.activated[i]) {
- this.seriesPickers.eq(i).find('td').css('opacity', 1);
- } else {
- this.seriesPickers.eq(i).find('td').css('opacity', .5);
- }
- }
-};
+ this.seriesPickers.each(function (i) {
+ var el = $(this);
+ el.click(function (e) {
+ if (e.shiftKey) {
+ self.toggleSeries(i);
+ } else {
+ self.showSeries(i);
+ }
+ return false;
+ });
+ if (i == 0 || self.initiallyShowAll) {
+ // show the active series
+ // if initiallyShowAll, all are active; otherwise only the first one
+ self.activated.push(true);
+ } else {
+ // fade out the others
+ el.find('td').css('opacity', .5);
+ self.activated.push(false);
+ }
+ // prevent selecting in ie & opera (they don't support doing this via css)
+ if ($.browser.msie) {
+ this.ondrag = function () { return false; };
+ this.onselectstart = function () { return false; };
+ } else if ($.browser.opera) {
+ $(this).attr('unselectable', 'on');
+ }
+ });
+};
+RowEvolutionSeriesToggle.prototype.beforeReplot = function () {
+ // fade out if not activated
+ for (var i = 0; i < this.activated.length; i++) {
+ if (this.activated[i]) {
+ this.seriesPickers.eq(i).find('td').css('opacity', 1);
+ } else {
+ this.seriesPickers.eq(i).find('td').css('opacity', .5);
+ }
+ }
+};
// ------------------------------------------------------------
@@ -752,160 +750,159 @@ RowEvolutionSeriesToggle.prototype.beforeReplot = function() {
// Handle ticks the piwik way...
// ------------------------------------------------------------
-(function($) {
-
- $.jqplot.PiwikTicks = function(options) {
- // canvas for the grid
- this.piwikTicksCanvas = null;
- // canvas for the highlight
- this.piwikHighlightCanvas = null;
- // renderer used to draw the marker of the highlighted point
- this.markerRenderer = new $.jqplot.MarkerRenderer({
- shadow: false
- });
- // the x tick the mouse is over
- this.currentXTick = false;
- // show the highlight around markers
- this.showHighlight = false;
- // show the grid
- this.showGrid = false;
- // show the ticks
- this.showTicks = false;
-
- $.extend(true, this, options);
- };
-
- $.jqplot.PiwikTicks.init = function(target, data, opts) {
- // add plugin as an attribute to the plot
- var options = opts || {};
- this.plugins.piwikTicks = new $.jqplot.PiwikTicks(options.piwikTicks);
-
- if (typeof $.jqplot.PiwikTicks.init.eventsBound == 'undefined') {
- $.jqplot.PiwikTicks.init.eventsBound = true;
- $.jqplot.eventListenerHooks.push(['jqplotMouseMove', handleMouseMove]);
- $.jqplot.eventListenerHooks.push(['jqplotMouseLeave', handleMouseLeave]);
- }
- };
-
- // draw the grid
- // called with context of plot
- $.jqplot.PiwikTicks.postDraw = function() {
- var c = this.plugins.piwikTicks;
-
- // highligh canvas
- if (c.showHighlight) {
- c.piwikHighlightCanvas = new $.jqplot.GenericCanvas();
-
- this.eventCanvas._elem.before(c.piwikHighlightCanvas.createElement(
- this._gridPadding, 'jqplot-piwik-highlight-canvas', this._plotDimensions, this));
- c.piwikHighlightCanvas.setContext();
- }
-
- // grid canvas
- if (c.showTicks) {
- var dimensions = this._plotDimensions;
- dimensions.height += 6;
- c.piwikTicksCanvas = new $.jqplot.GenericCanvas();
- this.series[0].shadowCanvas._elem.before(c.piwikTicksCanvas.createElement(
- this._gridPadding, 'jqplot-piwik-ticks-canvas', dimensions, this));
- c.piwikTicksCanvas.setContext();
-
- var ctx = c.piwikTicksCanvas._ctx;
-
- var ticks = this.data[0];
- var totalWidth = ctx.canvas.width;
- var tickWidth = totalWidth / ticks.length;
-
- var xaxisLabels = this.axes.xaxis.ticks;
-
- for (var i = 0; i < ticks.length; i++) {
- var pos = Math.round(i * tickWidth + tickWidth / 2);
- var full = xaxisLabels[i] && xaxisLabels[i] != ' ';
- drawLine(ctx, pos, full, c.showGrid);
- }
- }
- };
-
- $.jqplot.preInitHooks.push($.jqplot.PiwikTicks.init);
- $.jqplot.postDrawHooks.push($.jqplot.PiwikTicks.postDraw);
-
- // draw a 1px line
- function drawLine(ctx, x, full, showGrid) {
- ctx.save();
- ctx.strokeStyle = '#cccccc';
-
- ctx.beginPath();
- ctx.lineWidth = 2;
- var top = 0;
- if ((full && !showGrid) || !full) {
- top = ctx.canvas.height - 5;
- }
- ctx.moveTo(x, top);
- ctx.lineTo(x, full ? ctx.canvas.height : ctx.canvas.height - 2);
- ctx.stroke();
-
- // canvas renders line slightly too large
- ctx.clearRect(x, 0, x + 1, ctx.canvas.height);
-
- ctx.restore();
- }
-
- // tigger the event jqplotPiwikTickOver when the mosue enters
- // and new tick. this is used for tooltips.
- function handleMouseMove(ev, gridpos, datapos, neighbor, plot) {
- var c = plot.plugins.piwikTicks;
-
- var tick = Math.floor(datapos.xaxis + 0.5) - 1;
- if (tick !== c.currentXTick) {
- c.currentXTick = tick;
- plot.target.trigger('jqplotPiwikTickOver', [tick]);
- highlight(plot, tick);
- }
- }
-
- function handleMouseLeave(ev, gridpos, datapos, neighbor, plot) {
- unHighlight(plot);
- plot.plugins.piwikTicks.currentXTick = false;
- }
-
- // highlight a marker
- function highlight(plot, tick) {
- var c = plot.plugins.piwikTicks;
-
- if (!c.showHighlight) {
- return;
- }
-
- unHighlight(plot);
-
- for (var i = 0; i < plot.series.length; i++) {
- var series = plot.series[i];
- var seriesMarkerRenderer = series.markerRenderer;
-
- c.markerRenderer.style = seriesMarkerRenderer.style;
- c.markerRenderer.size = seriesMarkerRenderer.size + 5;
-
- var rgba = $.jqplot.getColorComponents(seriesMarkerRenderer.color);
- var newrgb = [rgba[0], rgba[1], rgba[2]];
- var alpha = rgba[3] * .4;
- c.markerRenderer.color = 'rgba(' + newrgb[0] + ',' + newrgb[1] + ',' + newrgb[2] + ',' + alpha + ')';
- c.markerRenderer.init();
-
- var position = series.gridData[tick];
- c.markerRenderer.draw(position[0], position[1], c.piwikHighlightCanvas._ctx);
- }
- }
-
- function unHighlight(plot) {
- var canvas = plot.plugins.piwikTicks.piwikHighlightCanvas;
- if (canvas !== null) {
- var ctx = canvas._ctx;
- ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
- }
- }
-
-})(jQuery);
+(function ($) {
+ $.jqplot.PiwikTicks = function (options) {
+ // canvas for the grid
+ this.piwikTicksCanvas = null;
+ // canvas for the highlight
+ this.piwikHighlightCanvas = null;
+ // renderer used to draw the marker of the highlighted point
+ this.markerRenderer = new $.jqplot.MarkerRenderer({
+ shadow: false
+ });
+ // the x tick the mouse is over
+ this.currentXTick = false;
+ // show the highlight around markers
+ this.showHighlight = false;
+ // show the grid
+ this.showGrid = false;
+ // show the ticks
+ this.showTicks = false;
+
+ $.extend(true, this, options);
+ };
+
+ $.jqplot.PiwikTicks.init = function (target, data, opts) {
+ // add plugin as an attribute to the plot
+ var options = opts || {};
+ this.plugins.piwikTicks = new $.jqplot.PiwikTicks(options.piwikTicks);
+
+ if (typeof $.jqplot.PiwikTicks.init.eventsBound == 'undefined') {
+ $.jqplot.PiwikTicks.init.eventsBound = true;
+ $.jqplot.eventListenerHooks.push(['jqplotMouseMove', handleMouseMove]);
+ $.jqplot.eventListenerHooks.push(['jqplotMouseLeave', handleMouseLeave]);
+ }
+ };
+
+ // draw the grid
+ // called with context of plot
+ $.jqplot.PiwikTicks.postDraw = function () {
+ var c = this.plugins.piwikTicks;
+
+ // highligh canvas
+ if (c.showHighlight) {
+ c.piwikHighlightCanvas = new $.jqplot.GenericCanvas();
+
+ this.eventCanvas._elem.before(c.piwikHighlightCanvas.createElement(
+ this._gridPadding, 'jqplot-piwik-highlight-canvas', this._plotDimensions, this));
+ c.piwikHighlightCanvas.setContext();
+ }
+
+ // grid canvas
+ if (c.showTicks) {
+ var dimensions = this._plotDimensions;
+ dimensions.height += 6;
+ c.piwikTicksCanvas = new $.jqplot.GenericCanvas();
+ this.series[0].shadowCanvas._elem.before(c.piwikTicksCanvas.createElement(
+ this._gridPadding, 'jqplot-piwik-ticks-canvas', dimensions, this));
+ c.piwikTicksCanvas.setContext();
+
+ var ctx = c.piwikTicksCanvas._ctx;
+
+ var ticks = this.data[0];
+ var totalWidth = ctx.canvas.width;
+ var tickWidth = totalWidth / ticks.length;
+
+ var xaxisLabels = this.axes.xaxis.ticks;
+
+ for (var i = 0; i < ticks.length; i++) {
+ var pos = Math.round(i * tickWidth + tickWidth / 2);
+ var full = xaxisLabels[i] && xaxisLabels[i] != ' ';
+ drawLine(ctx, pos, full, c.showGrid);
+ }
+ }
+ };
+
+ $.jqplot.preInitHooks.push($.jqplot.PiwikTicks.init);
+ $.jqplot.postDrawHooks.push($.jqplot.PiwikTicks.postDraw);
+
+ // draw a 1px line
+ function drawLine(ctx, x, full, showGrid) {
+ ctx.save();
+ ctx.strokeStyle = '#cccccc';
+
+ ctx.beginPath();
+ ctx.lineWidth = 2;
+ var top = 0;
+ if ((full && !showGrid) || !full) {
+ top = ctx.canvas.height - 5;
+ }
+ ctx.moveTo(x, top);
+ ctx.lineTo(x, full ? ctx.canvas.height : ctx.canvas.height - 2);
+ ctx.stroke();
+
+ // canvas renders line slightly too large
+ ctx.clearRect(x, 0, x + 1, ctx.canvas.height);
+
+ ctx.restore();
+ }
+
+ // tigger the event jqplotPiwikTickOver when the mosue enters
+ // and new tick. this is used for tooltips.
+ function handleMouseMove(ev, gridpos, datapos, neighbor, plot) {
+ var c = plot.plugins.piwikTicks;
+
+ var tick = Math.floor(datapos.xaxis + 0.5) - 1;
+ if (tick !== c.currentXTick) {
+ c.currentXTick = tick;
+ plot.target.trigger('jqplotPiwikTickOver', [tick]);
+ highlight(plot, tick);
+ }
+ }
+
+ function handleMouseLeave(ev, gridpos, datapos, neighbor, plot) {
+ unHighlight(plot);
+ plot.plugins.piwikTicks.currentXTick = false;
+ }
+
+ // highlight a marker
+ function highlight(plot, tick) {
+ var c = plot.plugins.piwikTicks;
+
+ if (!c.showHighlight) {
+ return;
+ }
+
+ unHighlight(plot);
+
+ for (var i = 0; i < plot.series.length; i++) {
+ var series = plot.series[i];
+ var seriesMarkerRenderer = series.markerRenderer;
+
+ c.markerRenderer.style = seriesMarkerRenderer.style;
+ c.markerRenderer.size = seriesMarkerRenderer.size + 5;
+
+ var rgba = $.jqplot.getColorComponents(seriesMarkerRenderer.color);
+ var newrgb = [rgba[0], rgba[1], rgba[2]];
+ var alpha = rgba[3] * .4;
+ c.markerRenderer.color = 'rgba(' + newrgb[0] + ',' + newrgb[1] + ',' + newrgb[2] + ',' + alpha + ')';
+ c.markerRenderer.init();
+
+ var position = series.gridData[tick];
+ c.markerRenderer.draw(position[0], position[1], c.piwikHighlightCanvas._ctx);
+ }
+ }
+
+ function unHighlight(plot) {
+ var canvas = plot.plugins.piwikTicks.piwikHighlightCanvas;
+ if (canvas !== null) {
+ var ctx = canvas._ctx;
+ ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
+ }
+ }
+
+})(jQuery);
// ------------------------------------------------------------
@@ -913,100 +910,98 @@ RowEvolutionSeriesToggle.prototype.beforeReplot = function() {
// Render legend on canvas
// ------------------------------------------------------------
-(function($) {
-
- $.jqplot.CanvasLegendRenderer = function(options) {
- // canvas for the legend
- this.legendCanvas = null;
- // is it a legend for a single metric only (pie chart)?
- this.singleMetric = false;
- // render the legend?
- this.show = false;
-
- $.extend(true, this, options);
- };
-
- $.jqplot.CanvasLegendRenderer.init = function(target, data, opts) {
- // add plugin as an attribute to the plot
- var options = opts || {};
- this.plugins.canvasLegend = new $.jqplot.CanvasLegendRenderer(options.canvasLegend);
-
- // add padding above the grid
- // legend will be put there
- if (this.plugins.canvasLegend.show) {
- options.gridPadding = {
- top: 21
- };
- }
-
- };
-
- // render the legend
- $.jqplot.CanvasLegendRenderer.postDraw = function() {
- var plot = this;
- var legend = plot.plugins.canvasLegend;
-
- if (!legend.show) {
- return;
- }
-
- // initialize legend canvas
- var padding = {top: 0, right: this._gridPadding.right, bottom: 0, left: this._gridPadding.left};
- var dimensions = {width: this._plotDimensions.width, height: this._gridPadding.top};
- var width = this._plotDimensions.width - this._gridPadding.left - this._gridPadding.right;
-
- legend.legendCanvas = new $.jqplot.GenericCanvas();
- this.eventCanvas._elem.before(legend.legendCanvas.createElement(
- padding, 'jqplot-legend-canvas', dimensions, plot));
- legend.legendCanvas.setContext();
-
- var ctx = legend.legendCanvas._ctx;
- ctx.save();
- ctx.font = '11px Arial';
-
- // render series names
- var x = 0;
- var series = plot.legend._series;
- for (i = 0; i < series.length; i++) {
- var s = series[i];
- var label;
- if (legend.labels && legend.labels[i]) {
- label = legend.labels[i];
- } else {
- label = s.label.toString();
- }
-
- ctx.fillStyle = s.color;
- if (legend.singleMetric)
- {
- ctx.fillStyle = '#666666';
- }
-
- ctx.fillRect(x, 10, 10, 2);
- x += 15;
-
- var nextX = x + ctx.measureText(label).width + 20;
-
- if (nextX + 70 > width) {
- ctx.fillText("[...]", x, 15);
- x += ctx.measureText("[...]").width + 20;
- break;
- }
-
- ctx.fillText(label, x, 15);
- x = nextX;
- }
-
- legend.width = x;
-
- ctx.restore();
- };
-
- $.jqplot.preInitHooks.push($.jqplot.CanvasLegendRenderer.init);
- $.jqplot.postDrawHooks.push($.jqplot.CanvasLegendRenderer.postDraw);
-
-})(jQuery);
+(function ($) {
+
+ $.jqplot.CanvasLegendRenderer = function (options) {
+ // canvas for the legend
+ this.legendCanvas = null;
+ // is it a legend for a single metric only (pie chart)?
+ this.singleMetric = false;
+ // render the legend?
+ this.show = false;
+
+ $.extend(true, this, options);
+ };
+ $.jqplot.CanvasLegendRenderer.init = function (target, data, opts) {
+ // add plugin as an attribute to the plot
+ var options = opts || {};
+ this.plugins.canvasLegend = new $.jqplot.CanvasLegendRenderer(options.canvasLegend);
+
+ // add padding above the grid
+ // legend will be put there
+ if (this.plugins.canvasLegend.show) {
+ options.gridPadding = {
+ top: 21
+ };
+ }
+
+ };
+
+ // render the legend
+ $.jqplot.CanvasLegendRenderer.postDraw = function () {
+ var plot = this;
+ var legend = plot.plugins.canvasLegend;
+
+ if (!legend.show) {
+ return;
+ }
+
+ // initialize legend canvas
+ var padding = {top: 0, right: this._gridPadding.right, bottom: 0, left: this._gridPadding.left};
+ var dimensions = {width: this._plotDimensions.width, height: this._gridPadding.top};
+ var width = this._plotDimensions.width - this._gridPadding.left - this._gridPadding.right;
+
+ legend.legendCanvas = new $.jqplot.GenericCanvas();
+ this.eventCanvas._elem.before(legend.legendCanvas.createElement(
+ padding, 'jqplot-legend-canvas', dimensions, plot));
+ legend.legendCanvas.setContext();
+
+ var ctx = legend.legendCanvas._ctx;
+ ctx.save();
+ ctx.font = '11px Arial';
+
+ // render series names
+ var x = 0;
+ var series = plot.legend._series;
+ for (i = 0; i < series.length; i++) {
+ var s = series[i];
+ var label;
+ if (legend.labels && legend.labels[i]) {
+ label = legend.labels[i];
+ } else {
+ label = s.label.toString();
+ }
+
+ ctx.fillStyle = s.color;
+ if (legend.singleMetric) {
+ ctx.fillStyle = '#666666';
+ }
+
+ ctx.fillRect(x, 10, 10, 2);
+ x += 15;
+
+ var nextX = x + ctx.measureText(label).width + 20;
+
+ if (nextX + 70 > width) {
+ ctx.fillText("[...]", x, 15);
+ x += ctx.measureText("[...]").width + 20;
+ break;
+ }
+
+ ctx.fillText(label, x, 15);
+ x = nextX;
+ }
+
+ legend.width = x;
+
+ ctx.restore();
+ };
+
+ $.jqplot.preInitHooks.push($.jqplot.CanvasLegendRenderer.init);
+ $.jqplot.postDrawHooks.push($.jqplot.CanvasLegendRenderer.postDraw);
+
+})(jQuery);
// ------------------------------------------------------------
@@ -1014,242 +1009,243 @@ RowEvolutionSeriesToggle.prototype.beforeReplot = function() {
// For line charts
// ------------------------------------------------------------
-(function($) {
-
- $.jqplot.SeriesPicker = function(options) {
- // dom element
- this.domElem = null;
- // render the picker?
- this.show = false;
- // the columns that can be selected
- this.selectableColumns = null;
- // the rows that can be selected
- this.selectableRows = null;
- // can multiple rows we selected?
- this.multiSelect = true;
- // css id of the target div dom element
- this.targetDivId = "";
- // the id of the current data table
- this.dataTableId = "";
- // language strings
- this.lang = {};
-
- $.extend(true, this, options);
- };
-
- $.jqplot.SeriesPicker.init = function(target, data, opts) {
- // add plugin as an attribute to the plot
- var options = opts || {};
- this.plugins.seriesPicker = new $.jqplot.SeriesPicker(options.seriesPicker);
- };
-
- // render the link to add series
- $.jqplot.SeriesPicker.postDraw = function() {
- var plot = this;
- var picker = plot.plugins.seriesPicker;
-
- if (!picker.show) {
- return;
- }
-
- // initialize dom element
- picker.domElem = $(document.createElement('a'))
- .addClass('jqplot-seriespicker')
- .attr('href', '#').html('+')
- .css('marginLeft', (plot._gridPadding.left + plot.plugins.canvasLegend.width - 1) + 'px');
-
- picker.domElem.on('hide', function() {
- $(this).css('opacity', .55);
- }).trigger('hide');
-
- plot.baseCanvas._elem.before(picker.domElem);
-
- // show picker on hover
- picker.domElem.hover(function() {
- picker.domElem.css('opacity', 1);
- if (!picker.domElem.hasClass('open')) {
- picker.domElem.addClass('open');
- showPicker(picker, plot._width);
- }
- }, function() {
- // do nothing on mouseout because using this event doesn't work properly.
- // instead, the timeout check beneath is used (checkPickerLeave()).
- }).click(function() {
- return false;
- });
- };
-
- // show the series picker
- function showPicker(picker, plotWidth) {
- var pickerLink = picker.domElem;
- var pickerPopover = $(document.createElement('div'))
- .addClass('jqplock-seriespicker-popover');
-
- var pickerState = {manipulated: false};
-
- // headline
- var title = picker.multiSelect ? picker.lang.metricsToPlot : picker.lang.metricToPlot;
- pickerPopover.append($(document.createElement('p'))
- .addClass('headline').html(title));
-
- if (picker.selectableColumns !== null) {
- // render the selectable columns
- for (var i = 0; i < picker.selectableColumns.length; i++) {
- var column = picker.selectableColumns[i];
- pickerPopover.append(createPickerPopupItem(picker, column, 'column', pickerState, pickerPopover, pickerLink));
- }
- }
-
- if (picker.selectableRows !== null) {
- // "records to plot" subheadline
- pickerPopover.append($(document.createElement('p'))
- .addClass('headline').addClass('recordsToPlot')
- .html(picker.lang.recordsToPlot));
-
- // render the selectable rows
- for (var i = 0; i < picker.selectableRows.length; i++) {
- var row = picker.selectableRows[i];
- pickerPopover.append(createPickerPopupItem(picker, row, 'row', pickerState, pickerPopover, pickerLink));
- }
- }
-
- $('body').prepend(pickerPopover.hide());
- var neededSpace = pickerPopover.outerWidth() + 10;
-
- // try to display popover to the right
- var linkOffset = pickerLink.offset();
- if (navigator.appVersion.indexOf("MSIE 7.") != -1) {
- linkOffset.left -= 10;
- }
- var margin = (parseInt(pickerLink.css('marginLeft'), 10) - 4);
- if (margin + neededSpace < plotWidth
- // make sure it's not too far to the left
- || margin - neededSpace + 60 < 0) {
- pickerPopover.css('marginLeft', (linkOffset.left - 4) + 'px').show();
- } else {
- // display to the left
- pickerPopover.addClass('alignright')
- .css('marginLeft', (linkOffset.left - neededSpace + 38) + 'px')
- .css('backgroundPosition', (pickerPopover.outerWidth() - 25) + 'px 4px')
- .show();
- }
- pickerPopover.css('marginTop', (linkOffset.top - 5) + 'px').show();
-
- // hide and replot on mouse leave
- checkPickerLeave(pickerPopover, function() {
- var replot = pickerState.manipulated;
- hidePicker(picker, pickerPopover, pickerLink, replot);
- });
- }
-
- function createPickerPopupItem(picker, config, type, pickerState, pickerPopover, pickerLink) {
- var checkbox = $(document.createElement('input')).addClass('select')
- .attr('type', picker.multiSelect ? 'checkbox' : 'radio');
-
- if (config.displayed && !(!picker.multiSelect && pickerState.oneChecked)) {
- checkbox.prop('checked', true);
- pickerState.oneChecked = true;
- }
-
- // if we are rendering a column, remember the column name
- // if it's a row, remember the string that can be used to match the row
- checkbox.data('name', type == 'column' ? config.column : config.matcher);
-
- var el = $(document.createElement('p'))
- .append(checkbox)
- .append(type == 'column' ? config.translation : config.label)
- .addClass(type == 'column' ? 'pickColumn' : 'pickRow');
-
- var replot = function() {
- unbindPickerLeaveCheck();
- hidePicker(picker, pickerPopover, pickerLink, true);
- };
-
- var checkBox = function(box) {
- if (!picker.multiSelect) {
- pickerPopover.find('input.select:not(.current)').prop('checked', false);
- }
- box.prop('checked', true);
- replot();
- };
-
- el.click(function(e) {
- pickerState.manipulated = true;
- var box = $(this).find('input.select');
- if (!$(e.target).is('input.select')) {
- if (box.is(':checked')) {
- box.prop('checked', false);
- } else {
- checkBox(box);
- }
- } else {
- if (box.is(':checked')) {
- checkBox(box);
- }
- }
- });
-
- return el;
- }
-
- // check whether the mouse has left the picker
- var onMouseMove;
- function checkPickerLeave(pickerPopover, onLeaveCallback) {
- var offset = pickerPopover.offset();
- var minX = offset.left;
- var minY = offset.top;
- var maxX = minX + pickerPopover.outerWidth();
- var maxY = minY + pickerPopover.outerHeight();
- var currentX, currentY;
- onMouseMove = function(e) {
- currentX = e.pageX;
- currentY = e.pageY;
- if (currentX < minX || currentX > maxX
- || currentY < minY || currentY > maxY) {
- unbindPickerLeaveCheck();
- onLeaveCallback();
- }
- };
- $(document).mousemove(onMouseMove);
- }
- function unbindPickerLeaveCheck() {
- $(document).unbind('mousemove', onMouseMove);
- }
-
- function hidePicker(picker, pickerPopover, pickerLink, replot) {
- // hide picker
- pickerPopover.hide();
- pickerLink.trigger('hide').removeClass('open');
-
- // replot
- if (replot) {
- var columns = [];
- var rows = [];
- pickerPopover.find('input:checked').each(function() {
- if ($(this).closest('p').hasClass('pickRow')) {
- rows.push($(this).data('name'));
- } else {
- columns.push($(this).data('name'));
- }
- });
- var noRowSelected = pickerPopover.find('.pickRow').size() > 0
- && pickerPopover.find('.pickRow input:checked').size() == 0;
- if (columns.length > 0 && !noRowSelected) {
-
- $('#'+picker.targetDivId).trigger('changeSeries', [columns, rows]);
- // inform dashboard widget about changed parameters (to be restored on reload)
- $('#'+picker.targetDivId).parents('[widgetId]').trigger('setParameters', {columns: columns, rows: rows});
- }
- }
-
- pickerPopover.remove();
- }
-
- $.jqplot.preInitHooks.push($.jqplot.SeriesPicker.init);
- $.jqplot.postDrawHooks.push($.jqplot.SeriesPicker.postDraw);
-
-})(jQuery);
+(function ($) {
+
+ $.jqplot.SeriesPicker = function (options) {
+ // dom element
+ this.domElem = null;
+ // render the picker?
+ this.show = false;
+ // the columns that can be selected
+ this.selectableColumns = null;
+ // the rows that can be selected
+ this.selectableRows = null;
+ // can multiple rows we selected?
+ this.multiSelect = true;
+ // css id of the target div dom element
+ this.targetDivId = "";
+ // the id of the current data table
+ this.dataTableId = "";
+ // language strings
+ this.lang = {};
+
+ $.extend(true, this, options);
+ };
+
+ $.jqplot.SeriesPicker.init = function (target, data, opts) {
+ // add plugin as an attribute to the plot
+ var options = opts || {};
+ this.plugins.seriesPicker = new $.jqplot.SeriesPicker(options.seriesPicker);
+ };
+
+ // render the link to add series
+ $.jqplot.SeriesPicker.postDraw = function () {
+ var plot = this;
+ var picker = plot.plugins.seriesPicker;
+
+ if (!picker.show) {
+ return;
+ }
+
+ // initialize dom element
+ picker.domElem = $(document.createElement('a'))
+ .addClass('jqplot-seriespicker')
+ .attr('href', '#').html('+')
+ .css('marginLeft', (plot._gridPadding.left + plot.plugins.canvasLegend.width - 1) + 'px');
+
+ picker.domElem.on('hide',function () {
+ $(this).css('opacity', .55);
+ }).trigger('hide');
+
+ plot.baseCanvas._elem.before(picker.domElem);
+
+ // show picker on hover
+ picker.domElem.hover(function () {
+ picker.domElem.css('opacity', 1);
+ if (!picker.domElem.hasClass('open')) {
+ picker.domElem.addClass('open');
+ showPicker(picker, plot._width);
+ }
+ },function () {
+ // do nothing on mouseout because using this event doesn't work properly.
+ // instead, the timeout check beneath is used (checkPickerLeave()).
+ }).click(function () {
+ return false;
+ });
+ };
+
+ // show the series picker
+ function showPicker(picker, plotWidth) {
+ var pickerLink = picker.domElem;
+ var pickerPopover = $(document.createElement('div'))
+ .addClass('jqplock-seriespicker-popover');
+
+ var pickerState = {manipulated: false};
+ // headline
+ var title = picker.multiSelect ? picker.lang.metricsToPlot : picker.lang.metricToPlot;
+ pickerPopover.append($(document.createElement('p'))
+ .addClass('headline').html(title));
+
+ if (picker.selectableColumns !== null) {
+ // render the selectable columns
+ for (var i = 0; i < picker.selectableColumns.length; i++) {
+ var column = picker.selectableColumns[i];
+ pickerPopover.append(createPickerPopupItem(picker, column, 'column', pickerState, pickerPopover, pickerLink));
+ }
+ }
+
+ if (picker.selectableRows !== null) {
+ // "records to plot" subheadline
+ pickerPopover.append($(document.createElement('p'))
+ .addClass('headline').addClass('recordsToPlot')
+ .html(picker.lang.recordsToPlot));
+
+ // render the selectable rows
+ for (var i = 0; i < picker.selectableRows.length; i++) {
+ var row = picker.selectableRows[i];
+ pickerPopover.append(createPickerPopupItem(picker, row, 'row', pickerState, pickerPopover, pickerLink));
+ }
+ }
+
+ $('body').prepend(pickerPopover.hide());
+ var neededSpace = pickerPopover.outerWidth() + 10;
+
+ // try to display popover to the right
+ var linkOffset = pickerLink.offset();
+ if (navigator.appVersion.indexOf("MSIE 7.") != -1) {
+ linkOffset.left -= 10;
+ }
+ var margin = (parseInt(pickerLink.css('marginLeft'), 10) - 4);
+ if (margin + neededSpace < plotWidth
+ // make sure it's not too far to the left
+ || margin - neededSpace + 60 < 0) {
+ pickerPopover.css('marginLeft', (linkOffset.left - 4) + 'px').show();
+ } else {
+ // display to the left
+ pickerPopover.addClass('alignright')
+ .css('marginLeft', (linkOffset.left - neededSpace + 38) + 'px')
+ .css('backgroundPosition', (pickerPopover.outerWidth() - 25) + 'px 4px')
+ .show();
+ }
+ pickerPopover.css('marginTop', (linkOffset.top - 5) + 'px').show();
+
+ // hide and replot on mouse leave
+ checkPickerLeave(pickerPopover, function () {
+ var replot = pickerState.manipulated;
+ hidePicker(picker, pickerPopover, pickerLink, replot);
+ });
+ }
+
+ function createPickerPopupItem(picker, config, type, pickerState, pickerPopover, pickerLink) {
+ var checkbox = $(document.createElement('input')).addClass('select')
+ .attr('type', picker.multiSelect ? 'checkbox' : 'radio');
+
+ if (config.displayed && !(!picker.multiSelect && pickerState.oneChecked)) {
+ checkbox.prop('checked', true);
+ pickerState.oneChecked = true;
+ }
+
+ // if we are rendering a column, remember the column name
+ // if it's a row, remember the string that can be used to match the row
+ checkbox.data('name', type == 'column' ? config.column : config.matcher);
+
+ var el = $(document.createElement('p'))
+ .append(checkbox)
+ .append(type == 'column' ? config.translation : config.label)
+ .addClass(type == 'column' ? 'pickColumn' : 'pickRow');
+
+ var replot = function () {
+ unbindPickerLeaveCheck();
+ hidePicker(picker, pickerPopover, pickerLink, true);
+ };
+
+ var checkBox = function (box) {
+ if (!picker.multiSelect) {
+ pickerPopover.find('input.select:not(.current)').prop('checked', false);
+ }
+ box.prop('checked', true);
+ replot();
+ };
+
+ el.click(function (e) {
+ pickerState.manipulated = true;
+ var box = $(this).find('input.select');
+ if (!$(e.target).is('input.select')) {
+ if (box.is(':checked')) {
+ box.prop('checked', false);
+ } else {
+ checkBox(box);
+ }
+ } else {
+ if (box.is(':checked')) {
+ checkBox(box);
+ }
+ }
+ });
+
+ return el;
+ }
+
+ // check whether the mouse has left the picker
+ var onMouseMove;
+
+ function checkPickerLeave(pickerPopover, onLeaveCallback) {
+ var offset = pickerPopover.offset();
+ var minX = offset.left;
+ var minY = offset.top;
+ var maxX = minX + pickerPopover.outerWidth();
+ var maxY = minY + pickerPopover.outerHeight();
+ var currentX, currentY;
+ onMouseMove = function (e) {
+ currentX = e.pageX;
+ currentY = e.pageY;
+ if (currentX < minX || currentX > maxX
+ || currentY < minY || currentY > maxY) {
+ unbindPickerLeaveCheck();
+ onLeaveCallback();
+ }
+ };
+ $(document).mousemove(onMouseMove);
+ }
+
+ function unbindPickerLeaveCheck() {
+ $(document).unbind('mousemove', onMouseMove);
+ }
+
+ function hidePicker(picker, pickerPopover, pickerLink, replot) {
+ // hide picker
+ pickerPopover.hide();
+ pickerLink.trigger('hide').removeClass('open');
+
+ // replot
+ if (replot) {
+ var columns = [];
+ var rows = [];
+ pickerPopover.find('input:checked').each(function () {
+ if ($(this).closest('p').hasClass('pickRow')) {
+ rows.push($(this).data('name'));
+ } else {
+ columns.push($(this).data('name'));
+ }
+ });
+ var noRowSelected = pickerPopover.find('.pickRow').size() > 0
+ && pickerPopover.find('.pickRow input:checked').size() == 0;
+ if (columns.length > 0 && !noRowSelected) {
+
+ $('#' + picker.targetDivId).trigger('changeSeries', [columns, rows]);
+ // inform dashboard widget about changed parameters (to be restored on reload)
+ $('#' + picker.targetDivId).parents('[widgetId]').trigger('setParameters', {columns: columns, rows: rows});
+ }
+ }
+
+ pickerPopover.remove();
+ }
+
+ $.jqplot.preInitHooks.push($.jqplot.SeriesPicker.init);
+ $.jqplot.postDrawHooks.push($.jqplot.SeriesPicker.postDraw);
+
+})(jQuery);
// ------------------------------------------------------------
@@ -1257,136 +1253,136 @@ RowEvolutionSeriesToggle.prototype.beforeReplot = function() {
// Render legend inside the pie graph
// ------------------------------------------------------------
-(function($) {
-
- $.jqplot.PieLegend = function(options) {
- // canvas for the legend
- this.pieLegendCanvas = null;
- // render the legend?
- this.show = false;
-
- $.extend(true, this, options);
- };
-
- $.jqplot.PieLegend.init = function(target, data, opts) {
- // add plugin as an attribute to the plot
- var options = opts || {};
- this.plugins.pieLegend = new $.jqplot.PieLegend(options.pieLegend);
- };
-
- // render the legend
- $.jqplot.PieLegend.postDraw = function() {
- var plot = this;
- var legend = plot.plugins.pieLegend;
-
- if (!legend.show) {
- return;
- }
-
- var series = plot.series[0];
- var angles = series._sliceAngles;
- var radius = series._diameter / 2;
- var center = series._center;
- var colors = this.seriesColors;
-
- // concentric line angles
- var lineAngles = [];
- for (var i = 0; i < angles.length; i++) {
- lineAngles.push((angles[i][0] + angles[i][1]) / 2 + Math.PI / 2);
- }
-
- // labels
- var labels = [];
- var data = series._plotData;
- for (i = 0; i < data.length; i++) {
- labels.push(data[i][0]);
- }
-
- // initialize legend canvas
- legend.pieLegendCanvas = new $.jqplot.GenericCanvas();
- plot.series[0].canvas._elem.before(legend.pieLegendCanvas.createElement(
- plot._gridPadding, 'jqplot-pie-legend-canvas', plot._plotDimensions, plot));
- legend.pieLegendCanvas.setContext();
-
- var ctx = legend.pieLegendCanvas._ctx;
- ctx.save();
-
- ctx.font = '11px Arial';
-
- // render labels
- var height = legend.pieLegendCanvas._elem.height();
- var x1, x2, y1, y2, lastY2 = false, right, lastRight = false;
- for (i = 0; i < labels.length; i++) {
- var label = labels[i];
-
- ctx.strokeStyle = colors[i % colors.length];
- ctx.lineCap = 'round';
- ctx.lineWidth = 1;
-
- // concentric line
- x1 = center[0] + Math.sin(lineAngles[i]) * (radius);
- y1 = center[1] - Math.cos(lineAngles[i]) * (radius);
-
- x2 = center[0] + Math.sin(lineAngles[i]) * (radius + 7);
- y2 = center[1] - Math.cos(lineAngles[i]) * (radius + 7);
-
- right = x2 > center[0];
-
- // move close labels
- if (lastY2 !== false && lastRight == right && (
- (right && y2 - lastY2 < 13) ||
- (!right && lastY2 - y2 < 13))) {
-
- if (x1 > center[0]) {
- // move down if the label is in the right half of the graph
- y2 = lastY2 + 13;
- } else {
- // move up if in left halt
- y2 = lastY2 - 13;
- }
- }
-
- if (y2 < 4 || y2 + 4 > height) {
- continue;
- }
-
- ctx.beginPath();
- ctx.moveTo(x1, y1);
- ctx.lineTo(x2, y2);
-
- ctx.closePath();
- ctx.stroke();
-
- // horizontal line
- ctx.beginPath();
- ctx.moveTo(x2, y2);
- if (right) {
- ctx.lineTo(x2 + 5, y2);
- } else {
- ctx.lineTo(x2 - 5, y2);
- }
-
- ctx.closePath();
- ctx.stroke();
-
- lastY2 = y2;
- lastRight = right;
-
- // text
- if (right) {
- x = x2 + 9;
- } else {
- x = x2 - 9 - ctx.measureText(label).width;
- }
-
- ctx.fillStyle = '#666666';
- ctx.fillText(label, x, y2 + 3);
- }
-
- ctx.restore();
- };
-
- $.jqplot.preInitHooks.push($.jqplot.PieLegend.init);
- $.jqplot.postDrawHooks.push($.jqplot.PieLegend.postDraw);
-
+(function ($) {
+
+ $.jqplot.PieLegend = function (options) {
+ // canvas for the legend
+ this.pieLegendCanvas = null;
+ // render the legend?
+ this.show = false;
+
+ $.extend(true, this, options);
+ };
+
+ $.jqplot.PieLegend.init = function (target, data, opts) {
+ // add plugin as an attribute to the plot
+ var options = opts || {};
+ this.plugins.pieLegend = new $.jqplot.PieLegend(options.pieLegend);
+ };
+
+ // render the legend
+ $.jqplot.PieLegend.postDraw = function () {
+ var plot = this;
+ var legend = plot.plugins.pieLegend;
+
+ if (!legend.show) {
+ return;
+ }
+
+ var series = plot.series[0];
+ var angles = series._sliceAngles;
+ var radius = series._diameter / 2;
+ var center = series._center;
+ var colors = this.seriesColors;
+
+ // concentric line angles
+ var lineAngles = [];
+ for (var i = 0; i < angles.length; i++) {
+ lineAngles.push((angles[i][0] + angles[i][1]) / 2 + Math.PI / 2);
+ }
+
+ // labels
+ var labels = [];
+ var data = series._plotData;
+ for (i = 0; i < data.length; i++) {
+ labels.push(data[i][0]);
+ }
+
+ // initialize legend canvas
+ legend.pieLegendCanvas = new $.jqplot.GenericCanvas();
+ plot.series[0].canvas._elem.before(legend.pieLegendCanvas.createElement(
+ plot._gridPadding, 'jqplot-pie-legend-canvas', plot._plotDimensions, plot));
+ legend.pieLegendCanvas.setContext();
+
+ var ctx = legend.pieLegendCanvas._ctx;
+ ctx.save();
+
+ ctx.font = '11px Arial';
+
+ // render labels
+ var height = legend.pieLegendCanvas._elem.height();
+ var x1, x2, y1, y2, lastY2 = false, right, lastRight = false;
+ for (i = 0; i < labels.length; i++) {
+ var label = labels[i];
+
+ ctx.strokeStyle = colors[i % colors.length];
+ ctx.lineCap = 'round';
+ ctx.lineWidth = 1;
+
+ // concentric line
+ x1 = center[0] + Math.sin(lineAngles[i]) * (radius);
+ y1 = center[1] - Math.cos(lineAngles[i]) * (radius);
+
+ x2 = center[0] + Math.sin(lineAngles[i]) * (radius + 7);
+ y2 = center[1] - Math.cos(lineAngles[i]) * (radius + 7);
+
+ right = x2 > center[0];
+
+ // move close labels
+ if (lastY2 !== false && lastRight == right && (
+ (right && y2 - lastY2 < 13) ||
+ (!right && lastY2 - y2 < 13))) {
+
+ if (x1 > center[0]) {
+ // move down if the label is in the right half of the graph
+ y2 = lastY2 + 13;
+ } else {
+ // move up if in left halt
+ y2 = lastY2 - 13;
+ }
+ }
+
+ if (y2 < 4 || y2 + 4 > height) {
+ continue;
+ }
+
+ ctx.beginPath();
+ ctx.moveTo(x1, y1);
+ ctx.lineTo(x2, y2);
+
+ ctx.closePath();
+ ctx.stroke();
+
+ // horizontal line
+ ctx.beginPath();
+ ctx.moveTo(x2, y2);
+ if (right) {
+ ctx.lineTo(x2 + 5, y2);
+ } else {
+ ctx.lineTo(x2 - 5, y2);
+ }
+
+ ctx.closePath();
+ ctx.stroke();
+
+ lastY2 = y2;
+ lastRight = right;
+
+ // text
+ if (right) {
+ x = x2 + 9;
+ } else {
+ x = x2 - 9 - ctx.measureText(label).width;
+ }
+
+ ctx.fillStyle = '#666666';
+ ctx.fillText(label, x, y2 + 3);
+ }
+
+ ctx.restore();
+ };
+
+ $.jqplot.preInitHooks.push($.jqplot.PieLegend.init);
+ $.jqplot.postDrawHooks.push($.jqplot.PieLegend.postDraw);
+
})(jQuery);
diff --git a/plugins/CoreHome/templates/jquery.ui.autocomplete.css b/plugins/CoreHome/templates/jquery.ui.autocomplete.css
index 9c10a02c22..dfe6df80e8 100644
--- a/plugins/CoreHome/templates/jquery.ui.autocomplete.css
+++ b/plugins/CoreHome/templates/jquery.ui.autocomplete.css
@@ -12,62 +12,65 @@
/* workarounds */
* html .ui-autocomplete {
/* without this, the menu expands to 100% in IE6 */
- width:1px;
+ width: 1px;
}
/* Menu
----------------------------------*/
.ui-menu {
- list-style:none;
- padding: 6px;
- margin: 0;
- display:block;
- position: relative;
- font-family: Arial, Verdana, Arial, Helvetica, sans-serif;
+ list-style: none;
+ padding: 6px;
+ margin: 0;
+ display: block;
+ position: relative;
+ font-family: Arial, Verdana, Arial, Helvetica, sans-serif;
}
+
.ui-menu .ui-menu {
- margin-top: -3px;
+ margin-top: -3px;
margin-bottom: 8px;
}
+
.ui-menu .ui-menu-item {
- line-height:18px;
- padding:0;
- height:auto;
- display:block;
- text-decoration:none;
- white-space:nowrap;
+ line-height: 18px;
+ padding: 0;
+ height: auto;
+ display: block;
+ text-decoration: none;
+ white-space: nowrap;
}
+
.ui-menu .ui-menu-item a {
- line-height:18px;
- color:#255792;
- font-size: 12px;
- padding: 0 5px 0 5px;
- position: relative;
+ line-height: 18px;
+ color: #255792;
+ font-size: 12px;
+ padding: 0 5px 0 5px;
+ position: relative;
}
.ui-menu .ui-menu-item a.ui-state-hover,
.ui-menu .ui-menu-item a.ui-state-active {
- font-weight: normal;
- margin: 0;
+ font-weight: normal;
+ margin: 0;
}
-
-.ui-widget-content {
- background:url(../../../libs/jquery/themes/base/images/ui-bg_flat_75_ffffff_40x100.png) repeat-x scroll 50% 50% #FFFFFF;
+.ui-widget-content {
+ background: url(../../../libs/jquery/themes/base/images/ui-bg_flat_75_ffffff_40x100.png) repeat-x scroll 50% 50% #FFFFFF;
border: 0 solid #D4D4D4;
- color:#222;
+ color: #222;
}
+
.ui-corner-all {
border-radius: 4px;
-moz-border-radius: 4px;
-webkit-border-radius: 4px;
}
-.ui-menu .ui-menu-item a.ui-state-hover{
- background:#ebeae6;
- border: 0;
+.ui-menu .ui-menu-item a.ui-state-hover {
+ background: #ebeae6;
+ border: 0;
border-radius: 0;
- -moz-border-radius: 0;
+ -moz-border-radius: 0;
-webkit-radius-radius: 0;
}
diff --git a/plugins/CoreHome/templates/js_css_includes.tpl b/plugins/CoreHome/templates/js_css_includes.tpl
index 7470a67c18..d89841fd36 100644
--- a/plugins/CoreHome/templates/js_css_includes.tpl
+++ b/plugins/CoreHome/templates/js_css_includes.tpl
@@ -1,5 +1,5 @@
{includeAssets type="css"}
{includeAssets type="js"}
{if 'General_LayoutDirection'|translate =='rtl'}
-<link rel="stylesheet" type="text/css" href="themes/default/rtl.css" />
+ <link rel="stylesheet" type="text/css" href="themes/default/rtl.css"/>
{/if}
diff --git a/plugins/CoreHome/templates/js_disabled_notice.tpl b/plugins/CoreHome/templates/js_disabled_notice.tpl
index bd3bd838df..50d08e0f56 100644
--- a/plugins/CoreHome/templates/js_disabled_notice.tpl
+++ b/plugins/CoreHome/templates/js_disabled_notice.tpl
@@ -1 +1,3 @@
-<noscript><div id="javascriptDisabled">{'CoreHome_JavascriptDisabled'|translate:'<a href="">':'</a>'}</div></noscript>
+<noscript>
+ <div id="javascriptDisabled">{'CoreHome_JavascriptDisabled'|translate:'<a href="">':'</a>'}</div>
+</noscript>
diff --git a/plugins/CoreHome/templates/js_global_variables.tpl b/plugins/CoreHome/templates/js_global_variables.tpl
index f5533c6634..cf8ce09ae2 100644
--- a/plugins/CoreHome/templates/js_global_variables.tpl
+++ b/plugins/CoreHome/templates/js_global_variables.tpl
@@ -1,29 +1,43 @@
<script type="text/javascript">
- var piwik = {literal}{}{/literal};
- piwik.token_auth = "{$token_auth}";
- piwik.piwik_url = "{$piwikUrl}";
- {if isset($userLogin)}piwik.userLogin = "{$userLogin|escape:'javascript'}";{/if}
- {if isset($idSite)}piwik.idSite = "{$idSite}";{/if}
- {if isset($siteName)}piwik.siteName = "{$siteName|escape:'javascript'}";{/if}
- {if isset($siteMainUrl)}piwik.siteMainUrl = "{$siteMainUrl|escape:'javascript'}";{/if}
- {if isset($period)}piwik.period = "{$period}";{/if}
- {* piwik.currentDateString should not be used other than by the calendar Javascript
- (it is not set to the expected value when period=range)
- Use broadcast.getValueFromUrl('date') instead
- *}
- piwik.currentDateString = "{if isset($date)}{$date}{elseif isset($endDate)}{$endDate}{/if}";
- {if isset($startDate)}piwik.startDateString = "{$startDate}";{/if}
- {if isset($endDate)}piwik.endDateString = "{$endDate}";{/if}
- {if isset($minDateYear)}piwik.minDateYear = {$minDateYear};{/if}
- {if isset($minDateMonth)}piwik.minDateMonth = parseInt("{$minDateMonth}", 10);{/if}
- {if isset($minDateDay)}piwik.minDateDay = parseInt("{$minDateDay}", 10);{/if}
- {if isset($maxDateYear)}piwik.maxDateYear = {$maxDateYear};{/if}
- {if isset($maxDateMonth)}piwik.maxDateMonth = parseInt("{$maxDateMonth}", 10);{/if}
- {if isset($maxDateDay)}piwik.maxDateDay = parseInt("{$maxDateDay}", 10);{/if}
- {if isset($language)}piwik.language = "{$language}";{/if}
- {if !empty($config_action_url_category_delimiter)}
- piwik.config = {literal}{}{/literal};
- piwik.config.action_url_category_delimiter = "{$config_action_url_category_delimiter}";
- {/if}
+ var piwik = {literal}{}{/literal};
+ piwik.token_auth = "{$token_auth}";
+ piwik.piwik_url = "{$piwikUrl}";
+ {if isset($userLogin)}piwik.userLogin = "{$userLogin|escape:'javascript'}";
+ {/if}
+ {if isset($idSite)}piwik.idSite = "{$idSite}";
+ {/if}
+ {if isset($siteName)}piwik.siteName = "{$siteName|escape:'javascript'}";
+ {/if}
+ {if isset($siteMainUrl)}piwik.siteMainUrl = "{$siteMainUrl|escape:'javascript'}";
+ {/if}
+ {if isset($period)}piwik.period = "{$period}";
+ {/if}
+ {* piwik.currentDateString should not be used other than by the calendar Javascript
+ (it is not set to the expected value when period=range)
+ Use broadcast.getValueFromUrl('date') instead
+ *}
+ piwik.currentDateString = "{if isset($date)}{$date}{elseif isset($endDate)}{$endDate}{/if}";
+ {if isset($startDate)}piwik.startDateString = "{$startDate}";
+ {/if}
+ {if isset($endDate)}piwik.endDateString = "{$endDate}";
+ {/if}
+ {if isset($minDateYear)}piwik.minDateYear = {$minDateYear};
+ {/if}
+ {if isset($minDateMonth)}piwik.minDateMonth = parseInt("{$minDateMonth}", 10);
+ {/if}
+ {if isset($minDateDay)}piwik.minDateDay = parseInt("{$minDateDay}", 10);
+ {/if}
+ {if isset($maxDateYear)}piwik.maxDateYear = {$maxDateYear};
+ {/if}
+ {if isset($maxDateMonth)}piwik.maxDateMonth = parseInt("{$maxDateMonth}", 10);
+ {/if}
+ {if isset($maxDateDay)}piwik.maxDateDay = parseInt("{$maxDateDay}", 10);
+ {/if}
+ {if isset($language)}piwik.language = "{$language}";
+ {/if}
+ {if !empty($config_action_url_category_delimiter)}
+ piwik.config = {literal}{}{/literal};
+ piwik.config.action_url_category_delimiter = "{$config_action_url_category_delimiter}";
+ {/if}
</script>
-<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=IE8" />
+<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=IE8"/>
diff --git a/plugins/CoreHome/templates/logo.tpl b/plugins/CoreHome/templates/logo.tpl
index 7a95a3e410..50a309f328 100644
--- a/plugins/CoreHome/templates/logo.tpl
+++ b/plugins/CoreHome/templates/logo.tpl
@@ -1,10 +1,11 @@
<span id="logo">
-<a href="index.php" title="{if $isCustomLogo}{'General_PoweredBy'|translate} {/if}Piwik # {'General_OpenSourceWebAnalytics'|translate}" style="text-decoration: none;">
- {if $hasSVGLogo}
- <img src='{$logoSVG}' alt="{if $isCustomLogo}{'General_PoweredBy'|translate} {/if}Piwik" style='margin-left: 10px' height='40' class="ie-hide" />
- <!--[if lt IE 9]>
- {/if}
- <img src='{$logoHeader}' alt="{if $isCustomLogo}{'General_PoweredBy'|translate} {/if}Piwik" style='margin-left:10px' height='50' />
- {if $hasSVGLogo}<![endif]-->{/if}
+<a href="index.php" title="{if $isCustomLogo}{'General_PoweredBy'|translate} {/if}Piwik # {'General_OpenSourceWebAnalytics'|translate}"
+ style="text-decoration: none;">
+ {if $hasSVGLogo}
+<img src='{$logoSVG}' alt="{if $isCustomLogo}{'General_PoweredBy'|translate} {/if}Piwik" style='margin-left: 10px' height='40' class="ie-hide"/>
+ <!--[if lt IE 9]>
+ {/if}
+ <img src='{$logoHeader}' alt="{if $isCustomLogo}{'General_PoweredBy'|translate} {/if}Piwik" style='margin-left:10px' height='50'/>
+ {if $hasSVGLogo}<![endif]-->{/if}
</a>
</span>
diff --git a/plugins/CoreHome/templates/menu.js b/plugins/CoreHome/templates/menu.js
index d9067268c5..3be160b1ca 100644
--- a/plugins/CoreHome/templates/menu.js
+++ b/plugins/CoreHome/templates/menu.js
@@ -5,81 +5,72 @@
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
-function menu()
-{
+function menu() {
this.param = {};
}
menu.prototype =
-{
+{
resetTimer: null,
-
- overMainLI: function ()
- {
+
+ overMainLI: function () {
$(this).siblings().removeClass('sfHover');
$(this).addClass('sfHover');
clearTimeout(menu.prototype.resetTimer);
},
-
- outMainLI: function ()
- {
+
+ outMainLI: function () {
clearTimeout(menu.prototype.resetTimer);
- menu.prototype.resetTimer = setTimeout(function(){
+ menu.prototype.resetTimer = setTimeout(function () {
$('.nav>.sfHover').removeClass('sfHover');
$('.nav>.sfActive').addClass('sfHover');
}, 2000);
},
-
- onItemClick: function (item)
- {
+
+ onItemClick: function (item) {
$('ul.nav').trigger('piwikSwitchPage', item);
broadcast.propagateAjax($(item).attr('name'));
return false;
},
-
- init: function()
- {
+
+ init: function () {
this.menuNode = $('.nav');
-
+
//sub LI auto height
- $('.nav li li a').each(function(){$(this).css({width:$(this).width()+30, paddingLeft:0, paddingRight:0});});
-
+ $('.nav li li a').each(function () {$(this).css({width: $(this).width() + 30, paddingLeft: 0, paddingRight: 0});});
+
this.menuNode.find("li:has(ul)").hover(this.overMainLI, this.outMainLI);
-
+
// add id to all li menu to support menu identification.
// for all sub menu we want to have a unique id based on their module and action
// for main menu we want to add just the module as its id.
- this.menuNode.find('li').each(function(){
+ this.menuNode.find('li').each(function () {
var url = $(this).find('a').attr('name');
- var module = broadcast.getValueFromUrl("module",url);
- var action = broadcast.getValueFromUrl("action",url);
- var moduleId = broadcast.getValueFromUrl("idGoal",url) || broadcast.getValueFromUrl("idDashboard",url);
+ var module = broadcast.getValueFromUrl("module", url);
+ var action = broadcast.getValueFromUrl("action", url);
+ var moduleId = broadcast.getValueFromUrl("idGoal", url) || broadcast.getValueFromUrl("idDashboard", url);
var main_menu = $(this).parent().hasClass('nav') ? true : false;
- if(main_menu)
- {
+ if (main_menu) {
$(this).attr({id: module});
}
// if there's a idGoal or idDashboard, use this in the ID
- else if(moduleId != '')
- {
+ else if (moduleId != '') {
$(this).attr({id: module + '_' + action + '_' + moduleId});
}
- else
- {
+ else {
$(this).attr({id: module + '_' + action});
}
});
},
- activateMenu : function(module,action,id)
- {
+ activateMenu: function (module, action, id) {
this.menuNode.find('li').removeClass('sfHover').removeClass('sfActive');
var $li = this.getSubmenuID(module, id, action);
var mainLi = $("#" + module);
- if(!mainLi.length) {
+ if (!mainLi.length) {
mainLi = $li.parents('li');
}
-
+
mainLi.addClass('sfActive').addClass('sfHover');
$li.addClass('sfHover');
@@ -101,9 +92,8 @@ menu.prototype =
return $li;
},
- loadFirstSection: function()
- {
- if(broadcast.isHashExists() == false) {
+ loadFirstSection: function () {
+ if (broadcast.isHashExists() == false) {
$('li:first a:first', this.menuNode).click().addClass('sfHover').addClass('sfActive');
}
}
diff --git a/plugins/CoreHome/templates/menu.tpl b/plugins/CoreHome/templates/menu.tpl
index e1ae70c720..e9e2fcd853 100644
--- a/plugins/CoreHome/templates/menu.tpl
+++ b/plugins/CoreHome/templates/menu.tpl
@@ -1,14 +1,16 @@
<ul class="nav">
-{foreach from=$menu key=level1 item=level2 name=menu}
-<li>
- <a name='{$level2._url|@urlRewriteWithParameters}' href='#{$level2._url|@urlRewriteWithParameters|substr:1}' onclick='return piwikMenu.onItemClick(this);'>{$level1|translate}</a>
- <ul>
- {foreach from=$level2 key=name item=urlParameters name=level2}
- {if strpos($name, '_') !== 0}
- <li><a name='{$urlParameters._url|@urlRewriteWithParameters}' href='#{$urlParameters._url|@urlRewriteWithParameters|substr:1}' onclick='return piwikMenu.onItemClick(this);'>{$name|translate|escape:'html'}</a></li>
- {/if}
- {/foreach}
- </ul>
-</li>
-{/foreach}
+ {foreach from=$menu key=level1 item=level2 name=menu}
+ <li>
+ <a name='{$level2._url|@urlRewriteWithParameters}' href='#{$level2._url|@urlRewriteWithParameters|substr:1}'
+ onclick='return piwikMenu.onItemClick(this);'>{$level1|translate}</a>
+ <ul>
+ {foreach from=$level2 key=name item=urlParameters name=level2}
+ {if strpos($name, '_') !== 0}
+ <li><a name='{$urlParameters._url|@urlRewriteWithParameters}' href='#{$urlParameters._url|@urlRewriteWithParameters|substr:1}'
+ onclick='return piwikMenu.onItemClick(this);'>{$name|translate|escape:'html'}</a></li>
+ {/if}
+ {/foreach}
+ </ul>
+ </li>
+ {/foreach}
</ul>
diff --git a/plugins/CoreHome/templates/menu_init.js b/plugins/CoreHome/templates/menu_init.js
index 91403b562b..4e9c325301 100644
--- a/plugins/CoreHome/templates/menu_init.js
+++ b/plugins/CoreHome/templates/menu_init.js
@@ -1,7 +1,5 @@
-
-
-$(document).ready( function(){
- if($('.nav').size()) {
+$(document).ready(function () {
+ if ($('.nav').size()) {
piwikMenu = new menu();
piwikMenu.init();
piwikMenu.loadFirstSection();
diff --git a/plugins/CoreHome/templates/misc.js b/plugins/CoreHome/templates/misc.js
index f3e59c5652..7c2ede9c2f 100755
--- a/plugins/CoreHome/templates/misc.js
+++ b/plugins/CoreHome/templates/misc.js
@@ -5,172 +5,157 @@
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
-(function($) {
-
-$(document).ready(function() {
-
- //
- // 'check for updates' behavior
- //
-
- var headerMessageParent = $('#header_message').parent();
-
- // when 'check for updates...' link is clicked, force a check & display the result
- headerMessageParent.on('click', '#updateCheckLinkContainer', function(e) {
- e.preventDefault();
-
- var headerMessage = $(this).closest('#header_message');
-
- var ajaxRequest = new ajaxHelper();
- ajaxRequest.setLoadingElement('#header_message .loadingPiwik');
- ajaxRequest.addParams({
- module: 'CoreHome',
- action: 'checkForUpdates',
- token_auth: piwik.token_auth
- }, 'get');
- ajaxRequest.setCallback(function(response) {
- headerMessage.fadeOut('slow', function() {
- response = $(response);
-
- var newVersionAvailable = response.hasClass('header_alert');
- if (newVersionAvailable)
- {
- headerMessage.replaceWith(response);
- }
- else
- {
- headerMessage.html(_pk_translate('CoreHome_YouAreUsingTheLatestVersion_js')).show();
- setTimeout(function() {
- headerMessage.fadeOut('slow', function() {
- headerMessage.replaceWith(response);
- });
- }, 4000);
- }
- });
- });
- ajaxRequest.setFormat('html');
- ajaxRequest.send(false);
-
- return false;
- });
-
- // when clicking the header message, show the long message w/o needing to hover
- headerMessageParent.on('click', '#header_message', function(e) {
- if (e.target.tagName.toLowerCase() != 'a')
- {
- $(this).toggleClass('active');
- }
- });
-
- //
- // section toggler behavior
- //
-
- var handleSectionToggle = function (self, showType, doHide)
- {
- var sectionId = $(self).attr('data-section-id'),
- section = $('#' + sectionId),
- showText = _pk_translate('General_Show_js'),
- hideText = _pk_translate('General_Hide_js');
-
- if (typeof(doHide) == 'undefined')
- {
- doHide = section.is(':visible');
- }
-
- if (doHide)
- {
- var newText = $(self).text().replace(hideText, showText),
- afterHide = function() { $(self).text(newText); };
-
- if (showType == 'slide')
- {
- section.slideUp(afterHide);
- }
- else if (showType == 'inline')
- {
- section.hide();
- afterHide();
- }
- else
- {
- section.hide(afterHide);
- }
- }
- else
- {
- var newText = $(self).text().replace(showText, hideText);
- $(self).text(newText);
-
- if (showType == 'slide')
- {
- section.slideDown();
- }
- else if (showType == 'inline')
- {
- section.css('display', 'inline-block');
- }
- else
- {
- section.show();
- }
- }
- };
-
- // when click section toggler link, toggle the visibility of the associated section
- $('body').on('click', 'a.section-toggler-link', function (e) {
- e.preventDefault();
- handleSectionToggle(this, 'slide');
- return false;
- });
-
- $('body').on('change', 'input.section-toggler-link', function (e) {
- handleSectionToggle(this, 'inline', !$(this).is(':checked'));
- });
-
- //
- // reports by dimension list behavior
- //
-
- // when a report dimension is clicked, load the appropriate report
- var currentWidgetLoading = null;
- $('body').on('click', '.reportDimension', function (e) {
- var view = $(this).closest('.reportsByDimensionView'),
- report = $('.dimensionReport', view),
- loading = $('.loadingPiwik', view);
-
- // make this dimension the active one
- $('.activeDimension', view).removeClass('activeDimension');
- $(this).addClass('activeDimension');
-
- // hide the visible report & show the loading elem
- report.hide();
- loading.show();
-
- // load the report using the data-url attribute (which holds the URL to the report)
- var widgetParams = broadcast.getValuesFromUrl($(this).attr('data-url'));
- for (var key in widgetParams)
- {
- widgetParams[key] = decodeURIComponent(widgetParams[key]);
- }
-
- var widgetUniqueId = widgetParams.module + widgetParams.action;
- currentWidgetLoading = widgetUniqueId;
-
- widgetsHelper.loadWidgetAjax(widgetUniqueId, widgetParams, function(response) {
- // if the widget that was loaded was not for the latest clicked link, do nothing w/ the response
- if (widgetUniqueId != currentWidgetLoading)
- {
- return;
- }
-
- loading.hide();
- report.html($(response)).css('display', 'inline-block');
-
- // scroll to report
- piwikHelper.lazyScrollTo(report, 400);
- });
- });
-});
+(function ($) {
+
+ $(document).ready(function () {
+
+ //
+ // 'check for updates' behavior
+ //
+
+ var headerMessageParent = $('#header_message').parent();
+
+ // when 'check for updates...' link is clicked, force a check & display the result
+ headerMessageParent.on('click', '#updateCheckLinkContainer', function (e) {
+ e.preventDefault();
+
+ var headerMessage = $(this).closest('#header_message');
+
+ var ajaxRequest = new ajaxHelper();
+ ajaxRequest.setLoadingElement('#header_message .loadingPiwik');
+ ajaxRequest.addParams({
+ module: 'CoreHome',
+ action: 'checkForUpdates',
+ token_auth: piwik.token_auth
+ }, 'get');
+ ajaxRequest.setCallback(function (response) {
+ headerMessage.fadeOut('slow', function () {
+ response = $(response);
+
+ var newVersionAvailable = response.hasClass('header_alert');
+ if (newVersionAvailable) {
+ headerMessage.replaceWith(response);
+ }
+ else {
+ headerMessage.html(_pk_translate('CoreHome_YouAreUsingTheLatestVersion_js')).show();
+ setTimeout(function () {
+ headerMessage.fadeOut('slow', function () {
+ headerMessage.replaceWith(response);
+ });
+ }, 4000);
+ }
+ });
+ });
+ ajaxRequest.setFormat('html');
+ ajaxRequest.send(false);
+
+ return false;
+ });
+
+ // when clicking the header message, show the long message w/o needing to hover
+ headerMessageParent.on('click', '#header_message', function (e) {
+ if (e.target.tagName.toLowerCase() != 'a') {
+ $(this).toggleClass('active');
+ }
+ });
+
+ //
+ // section toggler behavior
+ //
+
+ var handleSectionToggle = function (self, showType, doHide) {
+ var sectionId = $(self).attr('data-section-id'),
+ section = $('#' + sectionId),
+ showText = _pk_translate('General_Show_js'),
+ hideText = _pk_translate('General_Hide_js');
+
+ if (typeof(doHide) == 'undefined') {
+ doHide = section.is(':visible');
+ }
+
+ if (doHide) {
+ var newText = $(self).text().replace(hideText, showText),
+ afterHide = function () { $(self).text(newText); };
+
+ if (showType == 'slide') {
+ section.slideUp(afterHide);
+ }
+ else if (showType == 'inline') {
+ section.hide();
+ afterHide();
+ }
+ else {
+ section.hide(afterHide);
+ }
+ }
+ else {
+ var newText = $(self).text().replace(showText, hideText);
+ $(self).text(newText);
+
+ if (showType == 'slide') {
+ section.slideDown();
+ }
+ else if (showType == 'inline') {
+ section.css('display', 'inline-block');
+ }
+ else {
+ section.show();
+ }
+ }
+ };
+
+ // when click section toggler link, toggle the visibility of the associated section
+ $('body').on('click', 'a.section-toggler-link', function (e) {
+ e.preventDefault();
+ handleSectionToggle(this, 'slide');
+ return false;
+ });
+
+ $('body').on('change', 'input.section-toggler-link', function (e) {
+ handleSectionToggle(this, 'inline', !$(this).is(':checked'));
+ });
+
+ //
+ // reports by dimension list behavior
+ //
+
+ // when a report dimension is clicked, load the appropriate report
+ var currentWidgetLoading = null;
+ $('body').on('click', '.reportDimension', function (e) {
+ var view = $(this).closest('.reportsByDimensionView'),
+ report = $('.dimensionReport', view),
+ loading = $('.loadingPiwik', view);
+
+ // make this dimension the active one
+ $('.activeDimension', view).removeClass('activeDimension');
+ $(this).addClass('activeDimension');
+
+ // hide the visible report & show the loading elem
+ report.hide();
+ loading.show();
+
+ // load the report using the data-url attribute (which holds the URL to the report)
+ var widgetParams = broadcast.getValuesFromUrl($(this).attr('data-url'));
+ for (var key in widgetParams) {
+ widgetParams[key] = decodeURIComponent(widgetParams[key]);
+ }
+
+ var widgetUniqueId = widgetParams.module + widgetParams.action;
+ currentWidgetLoading = widgetUniqueId;
+
+ widgetsHelper.loadWidgetAjax(widgetUniqueId, widgetParams, function (response) {
+ // if the widget that was loaded was not for the latest clicked link, do nothing w/ the response
+ if (widgetUniqueId != currentWidgetLoading) {
+ return;
+ }
+
+ loading.hide();
+ report.html($(response)).css('display', 'inline-block');
+
+ // scroll to report
+ piwikHelper.lazyScrollTo(report, 400);
+ });
+ });
+ });
}(jQuery));
diff --git a/plugins/CoreHome/templates/period_select.tpl b/plugins/CoreHome/templates/period_select.tpl
index e083f136a8..726b277e4d 100644
--- a/plugins/CoreHome/templates/period_select.tpl
+++ b/plugins/CoreHome/templates/period_select.tpl
@@ -1,33 +1,37 @@
{loadJavascriptTranslations plugins='CoreHome'}
<div id="periodString">
- <div id="date">{'General_DateRange'|translate} <b>{$prettyDate}</b> <img src='themes/default/images/icon-calendar.gif' alt="" /></div>
- <div id="periodMore">
- <div class="period-date">
- <h6>{'General_Date'|translate}</h6>
- <div id="datepicker"></div>
- </div>
- <div class="period-range" style="display:none;">
- <div id="calendarRangeFrom">
- <h6>{'General_DateRangeFrom_js'|translate}<input tabindex="1" type="text" id="inputCalendarFrom" name="inputCalendarFrom"/></h6>
- <div id="calendarFrom"></div>
- </div>
- <div id="calendarRangeTo">
- <h6>{'General_DateRangeTo_js'|translate}<input tabindex="2" type="text" id="inputCalendarTo" name="inputCalendarTo"/></h6>
- <div id="calendarTo"></div>
- </div>
- </div>
- <div class="period-type">
- <h6>{'General_Period'|translate}</h6>
+ <div id="date">{'General_DateRange'|translate} <b>{$prettyDate}</b> <img src='themes/default/images/icon-calendar.gif' alt=""/></div>
+ <div id="periodMore">
+ <div class="period-date">
+ <h6>{'General_Date'|translate}</h6>
+
+ <div id="datepicker"></div>
+ </div>
+ <div class="period-range" style="display:none;">
+ <div id="calendarRangeFrom">
+ <h6>{'General_DateRangeFrom_js'|translate}<input tabindex="1" type="text" id="inputCalendarFrom" name="inputCalendarFrom"/></h6>
+
+ <div id="calendarFrom"></div>
+ </div>
+ <div id="calendarRangeTo">
+ <h6>{'General_DateRangeTo_js'|translate}<input tabindex="2" type="text" id="inputCalendarTo" name="inputCalendarTo"/></h6>
+
+ <div id="calendarTo"></div>
+ </div>
+ </div>
+ <div class="period-type">
+ <h6>{'General_Period'|translate}</h6>
<span id="otherPeriods">
{foreach from=$periodsNames key=label item=thisPeriod}
- <input type="radio" name="period" id="period_id_{$label}" value="{url period=$label}"{if $label==$period} checked="checked"{/if} />
- <label for="period_id_{$label}" >{$thisPeriod.singular}</label><br />
- {/foreach}
+ <input type="radio" name="period" id="period_id_{$label}" value="{url period=$label}"{if $label==$period} checked="checked"{/if} />
+ <label for="period_id_{$label}">{$thisPeriod.singular}</label>
+ <br/>
+ {/foreach}
</span>
- <input tabindex="3" type="submit" value="{'General_ApplyDateRange'|translate}" id="calendarRangeApply" />
- {ajaxLoadingDiv id=ajaxLoadingCalendar}
- </div>
- </div>
- <div class="period-click-tooltip" style="display:none;">{'General_ClickToChangePeriod'|translate}</div>
+ <input tabindex="3" type="submit" value="{'General_ApplyDateRange'|translate}" id="calendarRangeApply"/>
+ {ajaxLoadingDiv id=ajaxLoadingCalendar}
+ </div>
+ </div>
+ <div class="period-click-tooltip" style="display:none;">{'General_ClickToChangePeriod'|translate}</div>
</div>
diff --git a/plugins/CoreHome/templates/piwik_tag.tpl b/plugins/CoreHome/templates/piwik_tag.tpl
index e97ca2da76..cacc97d281 100644
--- a/plugins/CoreHome/templates/piwik_tag.tpl
+++ b/plugins/CoreHome/templates/piwik_tag.tpl
@@ -1,36 +1,35 @@
{* Disabled by default, tracks activity of this Piwik instance *}
{if $piwikUrl == 'http://demo.piwik.org/' || $debugTrackVisitsInsidePiwikUI}
+ <div class="clear"></div>
+ {literal}
+ <!-- Piwik -->
+ <script type="text/javascript">
+ var _paq = _paq || [];
+ _paq.push(['setTrackerUrl', 'piwik.php']);
+ _paq.push(['setSiteId', 1]);
+ {/literal}
+_paq.push(['setCookieDomain', '*.piwik.org']);
+ {literal}
+ // set the domain the visitor landed on, in the Custom Variable
+ _paq.push([function () {
+ if (!this.getCustomVariable(1))
+ {
+ this.setCustomVariable(1, "Domain landed", document.domain);
+ }
+ }]);
+ // Set the selected Piwik language in a custom var
+ _paq.push(['setCustomVariable', 2, "Demo language", piwik.languageName]);
+ _paq.push(['setDocumentTitle', document.domain + "/" + document.title]);
+ _paq.push(['trackPageView']);
+ _paq.push(['enableLinkTracking']);
-<div class="clear"></div>
-{literal}
-<!-- Piwik -->
-<script type="text/javascript">
- var _paq = _paq || [];
- _paq.push(['setTrackerUrl', 'piwik.php']);
- _paq.push(['setSiteId', 1]);
-{/literal}{if $piwikUrl == 'http://demo.piwik.org/'}{literal}
- _paq.push(['setCookieDomain', '*.piwik.org']);
-{/literal}{/if}{literal}
- // set the domain the visitor landed on, in the Custom Variable
- _paq.push([function () {
- if (!this.getCustomVariable(1))
- {
- this.setCustomVariable(1, "Domain landed", document.domain);
- }
- }]);
- // Set the selected Piwik language in a custom var
- _paq.push(['setCustomVariable', 2, "Demo language", piwik.languageName]);
- _paq.push(['setDocumentTitle', document.domain + "/" + document.title]);
- _paq.push(['trackPageView']);
- _paq.push(['enableLinkTracking']);
-
- (function() {
- var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0]; g.type='text/javascript';
- g.defer=true; g.async=true; g.src='js/piwik.js'; s.parentNode.insertBefore(g,s);
- })();
-</script>
-<!-- End Piwik Code -->
-{/literal}
+ (function() {
+ var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0]; g.type='text/javascript';
+ g.defer=true; g.async=true; g.src='js/piwik.js'; s.parentNode.insertBefore(g,s);
+ })();
+ </script>
+ <!-- End Piwik Code -->
+ {/literal}
{/if}
diff --git a/plugins/CoreHome/templates/popover.js b/plugins/CoreHome/templates/popover.js
index 4704233a63..d91059ec21 100644
--- a/plugins/CoreHome/templates/popover.js
+++ b/plugins/CoreHome/templates/popover.js
@@ -5,206 +5,207 @@
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
-var Piwik_Popover = (function() {
-
- var container = false;
- var isOpen = false;
- var closeCallback = false;
-
- var createContainer = function() {
- if (container === false) {
- container = $(document.createElement('div')).attr('id', 'Piwik_Popover');
- }
- };
-
- var openPopover = function(title) {
- createContainer();
-
- container.dialog({
- title: title,
- modal: true,
- width: '950px',
- position: ['center', 'center'],
- resizable: false,
- autoOpen: true,
- open: function(event, ui) {
- $('.ui-widget-overlay').on('click.popover', function() {
- container.dialog('close');
- });
- },
- close: function(event, ui) {
- container.find('div.jqplot-target').trigger('piwikDestroyPlot');
- container[0].innerHTML = ''; // IE8 fix
- container.dialog('destroy').remove();
- globalAjaxQueue.abort();
- $('.ui-widget-overlay').off('click.popover');
- isOpen = false;
- broadcast.propagateNewPopoverParameter(false);
- if (typeof closeCallback == 'function') {
- closeCallback();
- closeCallback = false;
- }
- }
- });
-
- isOpen = true;
- };
-
- var centerPopover = function() {
- if (container !== false) {
- container.dialog({position: ['center', 'center']});
- }
- };
-
- return {
-
- /**
- * Open the popover with a loading message
- *
- * @param popoverName string name of the popover
- * @param popoverSubject string subject of the popover (e.g. url, optional)
- * @param height int height of the popover in px (optional)
- */
- showLoading: function(popoverName, popoverSubject, height) {
- var loading = $(document.createElement('div')).addClass('Piwik_Popover_Loading');
-
- var loadingMessage = popoverSubject ? translations.General_LoadingPopoverFor_js :
- translations.General_LoadingPopover_js;
-
- loadingMessage = loadingMessage.replace(/%s/, popoverName);
-
- var p1 = $(document.createElement('p')).addClass('Piwik_Popover_Loading_Name');
- loading.append(p1.text(loadingMessage));
-
- var p2;
- if (popoverSubject) {
- popoverSubject = piwikHelper.addBreakpointsToUrl(popoverSubject);
- p1.addClass('Piwik_Popover_Loading_NameWithSubject');
- p2 = $(document.createElement('p')).addClass('Piwik_Popover_Loading_Subject');
- loading.append(p2.html(popoverSubject));
- }
-
- if (height) {
- loading.height(height);
- }
-
- if (!isOpen) {
- openPopover();
- }
-
- this.setContent(loading);
- this.setTitle('');
-
- if (height) {
- var offset = loading.height() - p1.outerHeight();
- if (popoverSubject) {
- offset -= p2.outerHeight();
- }
- var spacingEl = $(document.createElement('div'));
- spacingEl.height(Math.round(offset / 2));
- loading.prepend(spacingEl);
- }
-
- return container;
- },
-
- /** Add a help button to the current popover */
- addHelpButton: function(helpUrl) {
- if (!isOpen) {
- return;
- }
-
- var titlebar = container.parent().find('.ui-dialog-titlebar');
-
- var button = $(document.createElement('a')).addClass('ui-dialog-titlebar-help');
- button.attr({href: helpUrl, target: '_blank'});
-
- titlebar.append(button);
- },
-
- /** Set the title of the popover */
- setTitle: function(titleHtml) {
- container.dialog({title: titleHtml});
- },
-
- /** Set inner HTML of the popover */
- setContent: function(html) {
- if (typeof closeCallback == 'function') {
- closeCallback();
- closeCallback = false;
- }
-
- container[0].innerHTML = ''; // IE8 fix
- container.html(html);
- centerPopover();
- },
-
- /** Show an error message. All params are HTML! */
- showError: function(title, message, backLabel) {
- var error = $(document.createElement('div')).addClass('Piwik_Popover_Error');
-
- var p = $(document.createElement('p')).addClass('Piwik_Popover_Error_Title');
- error.append(p.html(title));
-
- if (message) {
- p = $(document.createElement('p')).addClass('Piwik_Popover_Error_Message');
- error.append(p.html(message));
- }
-
- if (backLabel) {
- var back = $(document.createElement('a')).addClass('Piwik_Popover_Error_Back');
- back.attr('href', '#').click(function() {
- history.back();
- return false;
- });
- error.append(back.html(backLabel));
- }
-
- if (!isOpen) {
- openPopover();
- }
-
- this.setContent(error);
- },
-
- /** Add a callback for the next time the popover is closed or the content changes */
- onClose: function(callback) {
- closeCallback = callback;
- },
-
- /** Close the popover */
- close: function() {
- if (isOpen) {
- container.dialog('close');
- }
- },
-
- /**
- * Create a Popover and load the specified URL in it
- * @param url
- */
- createPopupAndLoadUrl: function(url, loadingName) {
- // open the popover
- var box = Piwik_Popover.showLoading(loadingName);
-
- var callback = function(html) {
- function setPopoverTitleIfOneFoundInContainer() {
- var title = $('h1,h2', container);
- if (title.length == 1) {
- Piwik_Popover.setTitle(title.text());
- $(title).hide();
- }
+var Piwik_Popover = (function () {
+
+ var container = false;
+ var isOpen = false;
+ var closeCallback = false;
+
+ var createContainer = function () {
+ if (container === false) {
+ container = $(document.createElement('div')).attr('id', 'Piwik_Popover');
+ }
+ };
+
+ var openPopover = function (title) {
+ createContainer();
+
+ container.dialog({
+ title: title,
+ modal: true,
+ width: '950px',
+ position: ['center', 'center'],
+ resizable: false,
+ autoOpen: true,
+ open: function (event, ui) {
+ $('.ui-widget-overlay').on('click.popover', function () {
+ container.dialog('close');
+ });
+ },
+ close: function (event, ui) {
+ container.find('div.jqplot-target').trigger('piwikDestroyPlot');
+ container[0].innerHTML = ''; // IE8 fix
+ container.dialog('destroy').remove();
+ globalAjaxQueue.abort();
+ $('.ui-widget-overlay').off('click.popover');
+ isOpen = false;
+ broadcast.propagateNewPopoverParameter(false);
+ if (typeof closeCallback == 'function') {
+ closeCallback();
+ closeCallback = false;
+ }
+ }
+ });
+
+ isOpen = true;
+ };
+
+ var centerPopover = function () {
+ if (container !== false) {
+ container.dialog({position: ['center', 'center']});
+ }
+ };
+
+ return {
+
+ /**
+ * Open the popover with a loading message
+ *
+ * @param popoverName string name of the popover
+ * @param popoverSubject string subject of the popover (e.g. url, optional)
+ * @param height int height of the popover in px (optional)
+ */
+ showLoading: function (popoverName, popoverSubject, height) {
+ var loading = $(document.createElement('div')).addClass('Piwik_Popover_Loading');
+
+ var loadingMessage = popoverSubject ? translations.General_LoadingPopoverFor_js :
+ translations.General_LoadingPopover_js;
+
+ loadingMessage = loadingMessage.replace(/%s/, popoverName);
+
+ var p1 = $(document.createElement('p')).addClass('Piwik_Popover_Loading_Name');
+ loading.append(p1.text(loadingMessage));
+
+ var p2;
+ if (popoverSubject) {
+ popoverSubject = piwikHelper.addBreakpointsToUrl(popoverSubject);
+ p1.addClass('Piwik_Popover_Loading_NameWithSubject');
+ p2 = $(document.createElement('p')).addClass('Piwik_Popover_Loading_Subject');
+ loading.append(p2.html(popoverSubject));
+ }
+
+ if (height) {
+ loading.height(height);
+ }
+
+ if (!isOpen) {
+ openPopover();
+ }
+
+ this.setContent(loading);
+ this.setTitle('');
+
+ if (height) {
+ var offset = loading.height() - p1.outerHeight();
+ if (popoverSubject) {
+ offset -= p2.outerHeight();
+ }
+ var spacingEl = $(document.createElement('div'));
+ spacingEl.height(Math.round(offset / 2));
+ loading.prepend(spacingEl);
+ }
+
+ return container;
+ },
+
+ /** Add a help button to the current popover */
+ addHelpButton: function (helpUrl) {
+ if (!isOpen) {
+ return;
+ }
+
+ var titlebar = container.parent().find('.ui-dialog-titlebar');
+
+ var button = $(document.createElement('a')).addClass('ui-dialog-titlebar-help');
+ button.attr({href: helpUrl, target: '_blank'});
+
+ titlebar.append(button);
+ },
+
+ /** Set the title of the popover */
+ setTitle: function (titleHtml) {
+ container.dialog({title: titleHtml});
+ },
+
+ /** Set inner HTML of the popover */
+ setContent: function (html) {
+ if (typeof closeCallback == 'function') {
+ closeCallback();
+ closeCallback = false;
+ }
+
+ container[0].innerHTML = ''; // IE8 fix
+ container.html(html);
+ centerPopover();
+ },
+
+ /** Show an error message. All params are HTML! */
+ showError: function (title, message, backLabel) {
+ var error = $(document.createElement('div')).addClass('Piwik_Popover_Error');
+
+ var p = $(document.createElement('p')).addClass('Piwik_Popover_Error_Title');
+ error.append(p.html(title));
+
+ if (message) {
+ p = $(document.createElement('p')).addClass('Piwik_Popover_Error_Message');
+ error.append(p.html(message));
+ }
+
+ if (backLabel) {
+ var back = $(document.createElement('a')).addClass('Piwik_Popover_Error_Back');
+ back.attr('href', '#').click(function () {
+ history.back();
+ return false;
+ });
+ error.append(back.html(backLabel));
+ }
+
+ if (!isOpen) {
+ openPopover();
+ }
+
+ this.setContent(error);
+ },
+
+ /** Add a callback for the next time the popover is closed or the content changes */
+ onClose: function (callback) {
+ closeCallback = callback;
+ },
+
+ /** Close the popover */
+ close: function () {
+ if (isOpen) {
+ container.dialog('close');
+ }
+ },
+
+ /**
+ * Create a Popover and load the specified URL in it
+ * @param url
+ */
+ createPopupAndLoadUrl: function (url, loadingName) {
+ // open the popover
+ var box = Piwik_Popover.showLoading(loadingName);
+
+ var callback = function (html) {
+ function setPopoverTitleIfOneFoundInContainer() {
+ var title = $('h1,h2', container);
+ if (title.length == 1) {
+ Piwik_Popover.setTitle(title.text());
+ $(title).hide();
+ }
+ }
+
+ Piwik_Popover.setContent(html);
+ setPopoverTitleIfOneFoundInContainer();
+ }
+ var ajaxRequest = new ajaxHelper();
+ ajaxRequest.addParams(piwikHelper.getArrayFromQueryString(url), 'get');
+ ajaxRequest.setCallback(callback);
+ ajaxRequest.setFormat('html');
+ ajaxRequest.send(false);
}
- Piwik_Popover.setContent(html);
- setPopoverTitleIfOneFoundInContainer();
- }
- var ajaxRequest = new ajaxHelper();
- ajaxRequest.addParams(piwikHelper.getArrayFromQueryString(url), 'get');
- ajaxRequest.setCallback(callback);
- ajaxRequest.setFormat('html');
- ajaxRequest.send(false);
- }
- };
+ };
})();
diff --git a/plugins/CoreHome/templates/popover_multirowevolution.tpl b/plugins/CoreHome/templates/popover_multirowevolution.tpl
index c060f96d8a..50115c4bf7 100644
--- a/plugins/CoreHome/templates/popover_multirowevolution.tpl
+++ b/plugins/CoreHome/templates/popover_multirowevolution.tpl
@@ -1,35 +1,35 @@
<div class="rowevolution multirowevolution">
- <div class="popover-title">{'RowEvolution_MultiRowEvolutionTitle'|translate|escape:'html'}</div>
- <div class="graph">
- {$graph}
- </div>
- <div class="metrics-container">
- <h2>{$availableRecordsText|translate}</h2>
- <table class="metrics" border="0" cellpadding="0" cellspacing="0">
- {foreach from=$metrics item=metric}
- <tr>
- <td class="sparkline">
- {$metric.sparkline}
- </td>
- <td class="text">
- {logoHtml metadata=$metric alt=""} <span style="color:{$metric.color}">{$metric.label|escape:'html'}</span><br />
- <span class="details">{$metric.details}</span>
- </td>
- </tr>
- {/foreach}
- </table>
- <a href="#" class="rowevolution-startmulti">&raquo; {'RowEvolution_PickAnotherRow'|translate}</a>
- </div>
- {if count($availableMetrics) > 1}
- <div class="metric-selectbox">
- <h2>{'RowEvolution_AvailableMetrics'|translate}</h2>
- <select name="metric" class="multirowevoltion-metric">
- {foreach from=$availableMetrics item=metricName key=metric}
- <option value="{$metric|escape:'html'}"{if $selectedMetric == $metric} selected="selected"{/if}>
- {$metricName|escape:'html'}
- </option>
- {/foreach}
- </select>
- </div>
- {/if}
+ <div class="popover-title">{'RowEvolution_MultiRowEvolutionTitle'|translate|escape:'html'}</div>
+ <div class="graph">
+ {$graph}
+ </div>
+ <div class="metrics-container">
+ <h2>{$availableRecordsText|translate}</h2>
+ <table class="metrics" border="0" cellpadding="0" cellspacing="0">
+ {foreach from=$metrics item=metric}
+ <tr>
+ <td class="sparkline">
+ {$metric.sparkline}
+ </td>
+ <td class="text">
+ {logoHtml metadata=$metric alt=""} <span style="color:{$metric.color}">{$metric.label|escape:'html'}</span><br/>
+ <span class="details">{$metric.details}</span>
+ </td>
+ </tr>
+ {/foreach}
+ </table>
+ <a href="#" class="rowevolution-startmulti">&raquo; {'RowEvolution_PickAnotherRow'|translate}</a>
+ </div>
+ {if count($availableMetrics) > 1}
+ <div class="metric-selectbox">
+ <h2>{'RowEvolution_AvailableMetrics'|translate}</h2>
+ <select name="metric" class="multirowevoltion-metric">
+ {foreach from=$availableMetrics item=metricName key=metric}
+ <option value="{$metric|escape:'html'}"{if $selectedMetric == $metric} selected="selected"{/if}>
+ {$metricName|escape:'html'}
+ </option>
+ {/foreach}
+ </select>
+ </div>
+ {/if}
</div> \ No newline at end of file
diff --git a/plugins/CoreHome/templates/popover_rowevolution.tpl b/plugins/CoreHome/templates/popover_rowevolution.tpl
index c3f6599ca4..a62af4b032 100644
--- a/plugins/CoreHome/templates/popover_rowevolution.tpl
+++ b/plugins/CoreHome/templates/popover_rowevolution.tpl
@@ -1,32 +1,34 @@
<div class="rowevolution">
- <div class="popover-title">{$popoverTitle}</div>
- <div class="graph">
- {$graph}
- </div>
- <div class="metrics-container">
- <h2>{$availableMetricsText}</h2>
- <div class="rowevolution-documentation">
- {'RowEvolution_Documentation'|translate}
- </div>
- <table class="metrics" border="0" cellpadding="0" cellspacing="0">
- {foreach from=$metrics item=metric}
- <tr>
- <td class="sparkline">
- {$metric.sparkline}
- </td>
- <td class="text">
- <span style="color:{$metric.color}">{$metric.label|escape:'html'}</span>{if $metric.details}:
- <span class="details">{$metric.details}</span>{/if}
- </td>
- </tr>
- {/foreach}
- </table>
- </div>
- <div class="compare-container">
- <h2>{'RowEvolution_CompareRows'|translate}</h2>
- <div class="rowevolution-documentation">
- {'RowEvolution_CompareDocumentation'|translate}
- </div>
- <a href="#" class="rowevolution-startmulti">&raquo; {'RowEvolution_PickARow'|translate}</a>
- </div>
+ <div class="popover-title">{$popoverTitle}</div>
+ <div class="graph">
+ {$graph}
+ </div>
+ <div class="metrics-container">
+ <h2>{$availableMetricsText}</h2>
+
+ <div class="rowevolution-documentation">
+ {'RowEvolution_Documentation'|translate}
+ </div>
+ <table class="metrics" border="0" cellpadding="0" cellspacing="0">
+ {foreach from=$metrics item=metric}
+ <tr>
+ <td class="sparkline">
+ {$metric.sparkline}
+ </td>
+ <td class="text">
+ <span style="color:{$metric.color}">{$metric.label|escape:'html'}</span>{if $metric.details}:
+ <span class="details">{$metric.details}</span>{/if}
+ </td>
+ </tr>
+ {/foreach}
+ </table>
+ </div>
+ <div class="compare-container">
+ <h2>{'RowEvolution_CompareRows'|translate}</h2>
+
+ <div class="rowevolution-documentation">
+ {'RowEvolution_CompareDocumentation'|translate}
+ </div>
+ <a href="#" class="rowevolution-startmulti">&raquo; {'RowEvolution_PickARow'|translate}</a>
+ </div>
</div> \ No newline at end of file
diff --git a/plugins/CoreHome/templates/promo_video.tpl b/plugins/CoreHome/templates/promo_video.tpl
index 58494b8299..5b7cb00df4 100755
--- a/plugins/CoreHome/templates/promo_video.tpl
+++ b/plugins/CoreHome/templates/promo_video.tpl
@@ -1,126 +1,130 @@
{literal}
-<script type="text/javascript">
- $(document).ready(function() {
- $('#piwik-promo-thumbnail').click(function() {
- var promoEmbed = $('#piwik-promo-embed'),
- widgetWidth = $(this).closest('.widgetContent').width(),
- height = (266 * widgetWidth) / 421,
- embedHtml = '<iframe width="100%" height="' + height + '" src="http://www.youtube.com/embed/OslfF_EH81g?autoplay=1&vq=hd720&wmode=transparent" frameborder="0" wmode="Opaque"></iframe>';
-
- $(this).hide();
- promoEmbed.height(height).html(embedHtml);
- promoEmbed.show();
- });
- });
-</script>
-<style type="text/css">
-#piwik-promo-thumbnail {
- background: #fff url(plugins/CoreHome/templates/images/promo_splash.png) no-repeat 0 0;
- background-position: center;
- width:321px;
- margin:0 auto 0 auto;
-}
-
-#piwik-promo-embed {
- margin-left:1px;
-}
-
-#piwik-promo-embed>iframe {
- z-index:0;
-}
-
-#piwik-promo-thumbnail {
- height:178px;
-}
-
-#piwik-promo-thumbnail:hover {
- opacity:.75;
- cursor:pointer;
-}
-
-#piwik-promo-thumbnail>img {
- display:block;
- position:relative;
- top:53px;
- left:125px;
-}
-
-#piwik-promo-video {
- margin: 2em 0 2em 0;
-}
-
-#piwik-widget-footer {
- margin: 0 1em 1em 1em;
-}
-
-#piwik-promo-share {
- margin: 0 2em 1em 0;
- background-color: #CCC;
-
- border:1px solid #CCC;
- -webkit-border-radius: 6px;
- -moz-border-radius: 6px;
- border-radius: 6px;
-
- display:inline-block;
-
- padding:0 .5em 0 .5em;
-
- float: right;
-}
-
-#piwik-promo-share > a {
- margin-left: .5em;
- margin-top:4px;
- display:inline-block;
-}
-
-#piwik-promo-share>span {
- display:inline-block;
- vertical-align:top;
- margin-top:4px;
-}
-
-#piwik-promo-videos-link {
- font-size:.8em;
- font-style:italic;
- margin: 1em 0 0 1.25em;
- color:#666;
- display:inline-block;
-}
-#piwik-promo-videos-link:hover {
- text-decoration:none;
-}
-</style>
+ <script type="text/javascript">
+ $(document).ready(function () {
+ $('#piwik-promo-thumbnail').click(function () {
+ var promoEmbed = $('#piwik-promo-embed'),
+ widgetWidth = $(this).closest('.widgetContent').width(),
+ height = (266 * widgetWidth) / 421,
+ embedHtml = '<iframe width="100%" height="' + height + '" src="http://www.youtube.com/embed/OslfF_EH81g?autoplay=1&vq=hd720&wmode=transparent" frameborder="0" wmode="Opaque"></iframe>';
+
+ $(this).hide();
+ promoEmbed.height(height).html(embedHtml);
+ promoEmbed.show();
+ });
+ });
+ </script>
+ <style type="text/css">
+ #piwik-promo-thumbnail {
+ background: #fff url(plugins/CoreHome/templates/images/promo_splash.png) no-repeat 0 0;
+ background-position: center;
+ width: 321px;
+ margin: 0 auto 0 auto;
+ }
+
+ #piwik-promo-embed {
+ margin-left: 1px;
+ }
+
+ #piwik-promo-embed>iframe {
+ z-index: 0;
+ }
+
+ #piwik-promo-thumbnail {
+ height: 178px;
+ }
+
+ #piwik-promo-thumbnail:hover {
+ opacity: .75;
+ cursor: pointer;
+ }
+
+ #piwik-promo-thumbnail>img {
+ display: block;
+ position: relative;
+ top: 53px;
+ left: 125px;
+ }
+
+ #piwik-promo-video {
+ margin: 2em 0 2em 0;
+ }
+
+ #piwik-widget-footer {
+ margin: 0 1em 1em 1em;
+ }
+
+ #piwik-promo-share {
+ margin: 0 2em 1em 0;
+ background-color: #CCC;
+
+ border: 1px solid #CCC;
+ -webkit-border-radius: 6px;
+ -moz-border-radius: 6px;
+ border-radius: 6px;
+
+ display: inline-block;
+
+ padding: 0 .5em 0 .5em;
+
+ float: right;
+ }
+
+ #piwik-promo-share > a {
+ margin-left: .5em;
+ margin-top: 4px;
+ display: inline-block;
+ }
+
+ #piwik-promo-share>span {
+ display: inline-block;
+ vertical-align: top;
+ margin-top: 4px;
+ }
+
+ #piwik-promo-videos-link {
+ font-size: .8em;
+ font-style: italic;
+ margin: 1em 0 0 1.25em;
+ color: #666;
+ display: inline-block;
+ }
+
+ #piwik-promo-videos-link:hover {
+ text-decoration: none;
+ }
+ </style>
{/literal}
<div id="piwik-promo">
- <div id="piwik-promo-video">
- <div id="piwik-promo-thumbnail">
- <img src="themes/default/images/video_play.png"/>
- </div>
-
- <div id="piwik-promo-embed" style="display:none">
- </div>
- </div>
-
- <a id="piwik-promo-videos-link" href="http://piwik.org/blog/2012/12/piwik-how-to-videos/" target="_blank">
- {'CoreHome_ViewAllPiwikVideoTutorials'|translate}
- </a>
-
- <div id="piwik-promo-share">
- <span>{'CoreHome_ShareThis'|translate}:</span>
-
- {* facebook *}
- <a href="http://www.facebook.com/sharer.php?u={$promoVideoUrl|urlencode|escape:'html'}" target="_blank"><img src="plugins/Referers/images/socials/facebook.com.png"/></a>
-
- {* twitter *}
- <a href="http://twitter.com/share?text={$shareText|urlencode|escape:'html'}&url={$promoVideoUrl|urlencode|escape:'html'}" target="_blank"><img src="plugins/Referers/images/socials/twitter.com.png"/></a>
-
- {* email *}
- <a href="mailto:?body={$shareTextLong|rawurlencode|escape:'html'}&subject={$shareText|rawurlencode|escape:'html'}" target="_blank"><img src="themes/default/images/email.png"/></a>
- </div>
-
- <div style="clear:both"></div>
-
- <div id="piwik-widget-footer" style='color:#666'>{'CoreHome_CloseWidgetDirections'|translate}</div>
+ <div id="piwik-promo-video">
+ <div id="piwik-promo-thumbnail">
+ <img src="themes/default/images/video_play.png"/>
+ </div>
+
+ <div id="piwik-promo-embed" style="display:none">
+ </div>
+ </div>
+
+ <a id="piwik-promo-videos-link" href="http://piwik.org/blog/2012/12/piwik-how-to-videos/" target="_blank">
+ {'CoreHome_ViewAllPiwikVideoTutorials'|translate}
+ </a>
+
+ <div id="piwik-promo-share">
+ <span>{'CoreHome_ShareThis'|translate}:</span>
+
+ {* facebook *}
+ <a href="http://www.facebook.com/sharer.php?u={$promoVideoUrl|urlencode|escape:'html'}" target="_blank"><img
+ src="plugins/Referers/images/socials/facebook.com.png"/></a>
+
+ {* twitter *}
+ <a href="http://twitter.com/share?text={$shareText|urlencode|escape:'html'}&url={$promoVideoUrl|urlencode|escape:'html'}" target="_blank"><img
+ src="plugins/Referers/images/socials/twitter.com.png"/></a>
+
+ {* email *}
+ <a href="mailto:?body={$shareTextLong|rawurlencode|escape:'html'}&subject={$shareText|rawurlencode|escape:'html'}" target="_blank"><img
+ src="themes/default/images/email.png"/></a>
+ </div>
+
+ <div style="clear:both"></div>
+
+ <div id="piwik-widget-footer" style='color:#666'>{'CoreHome_CloseWidgetDirections'|translate}</div>
</div>
diff --git a/plugins/CoreHome/templates/reports_by_dimension.tpl b/plugins/CoreHome/templates/reports_by_dimension.tpl
index fd7689138a..e18f560958 100644
--- a/plugins/CoreHome/templates/reports_by_dimension.tpl
+++ b/plugins/CoreHome/templates/reports_by_dimension.tpl
@@ -1,27 +1,28 @@
<div class="reportsByDimensionView">
-<div class="entityList">
-{foreach from=$dimensionCategories key=category item=dimensions name=dimensionCategories}
- <div class='dimensionCategory'>
- {$category|translate}
- <ul class='listCircle'>
- {foreach from=$dimensions key=idx item=dimension}
- <li class="reportDimension {if $idx eq 0 && $smarty.foreach.dimensionCategories.index eq 0}activeDimension{/if}" data-url="{$dimension.url}">
- <span class='dimension'>{$dimension.title|translate}</span>
- </li>
- {/foreach}
- </ul>
- </div>
-{/foreach}
-</div>
+ <div class="entityList">
+ {foreach from=$dimensionCategories key=category item=dimensions name=dimensionCategories}
+ <div class='dimensionCategory'>
+ {$category|translate}
+ <ul class='listCircle'>
+ {foreach from=$dimensions key=idx item=dimension}
+ <li class="reportDimension {if $idx eq 0 && $smarty.foreach.dimensionCategories.index eq 0}activeDimension{/if}"
+ data-url="{$dimension.url}">
+ <span class='dimension'>{$dimension.title|translate}</span>
+ </li>
+ {/foreach}
+ </ul>
+ </div>
+ {/foreach}
+ </div>
-<div style="float:left;">
- <div class="loadingPiwik" style="display:none">
- <img src="themes/default/images/loading-blue.gif" alt="" />{'General_LoadingData'|translate}
- </div>
-
- <div class="dimensionReport">{$firstReport}</div>
-</div>
-<div class="clear"></div>
+ <div style="float:left;">
+ <div class="loadingPiwik" style="display:none">
+ <img src="themes/default/images/loading-blue.gif" alt=""/>{'General_LoadingData'|translate}
+ </div>
+
+ <div class="dimensionReport">{$firstReport}</div>
+ </div>
+ <div class="clear"></div>
</div>
diff --git a/plugins/CoreHome/templates/sites_selection.tpl b/plugins/CoreHome/templates/sites_selection.tpl
index c716bea2fb..f61df0174a 100644
--- a/plugins/CoreHome/templates/sites_selection.tpl
+++ b/plugins/CoreHome/templates/sites_selection.tpl
@@ -14,42 +14,47 @@
* - $idSite The currently selected idSite. Defaults to the first id in $sites set by Piwik_View.
*}
{capture name=sitesSelector_allWebsitesLink assign=sitesSelector_allWebsitesLink}
-<div class="custom_select_all" style="clear: both">
- <a href="#" {if isset($showAllSitesItem) && $showAllSitesItem eq false}style="display:none;"{/if}>
- {if isset($allSitesItemText)}{$allSitesItemText}{else}{'General_MultiSitesSummary'|translate}{/if}
- </a>
-</div>
+ <div class="custom_select_all" style="clear: both">
+ <a href="#" {if isset($showAllSitesItem) && $showAllSitesItem eq false}style="display:none;"{/if}>
+ {if isset($allSitesItemText)}{$allSitesItemText}{else}{'General_MultiSitesSummary'|translate}{/if}
+ </a>
+ </div>
{/capture}
<div class="sites_autocomplete" {if isset($siteSelectorId)}id="{$siteSelectorId}"{/if}
- {if !isset($switchSiteOnSelect) || $switchSiteOnSelect eq true}data-switch-site-on-select="1"{/if}>
+ {if !isset($switchSiteOnSelect) || $switchSiteOnSelect eq true}data-switch-site-on-select="1"{/if}>
<div class="custom_select">
-
- <a href="#" onclick="return false" class="custom_select_main_link" siteid="{if isset($idSite)}{$idSite}{else}{$sites[0].idsite}{/if}">{if isset($siteName)}{$siteName}{else}{$sites[0].name}{/if}</a>
-
+
+ <a href="#" onclick="return false" class="custom_select_main_link"
+ siteid="{if isset($idSite)}{$idSite}{else}{$sites[0].idsite}{/if}">{if isset($siteName)}{$siteName}{else}{$sites[0].name}{/if}</a>
+
<div class="custom_select_block">
{if isset($allWebsitesLinkLocation) && $allWebsitesLinkLocation eq 'top'}
- {$sitesSelector_allWebsitesLink}
+ {$sitesSelector_allWebsitesLink}
{/if}
<div class="custom_select_container">
- <ul class="custom_select_ul_list" >
- {foreach from=$sites item=info}
- <li {if (!isset($showSelectedSite) || $showSelectedSite eq false) && $idSite==$info.idsite} style="display: none"{/if}><a href="#" siteid="{$info.idsite}">{$info.name}</a></li>
- {/foreach}
- </ul>
+ <ul class="custom_select_ul_list">
+ {foreach from=$sites item=info}
+ <li {if (!isset($showSelectedSite) || $showSelectedSite eq false) && $idSite==$info.idsite} style="display: none"{/if}><a href="#"
+ siteid="{$info.idsite}">{$info.name}</a>
+ </li>
+ {/foreach}
+ </ul>
</div>
{if !isset($allWebsitesLinkLocation) || $allWebsitesLinkLocation eq 'bottom'}
- {$sitesSelector_allWebsitesLink}
+ {$sitesSelector_allWebsitesLink}
{/if}
<div class="custom_select_search" {if !$show_autocompleter}style="display:none;"{/if}>
<input type="text" length="15" class="websiteSearch inp"/>
- <input type="hidden" class="max_sitename_width" value="130" />
+ <input type="hidden" class="max_sitename_width" value="130"/>
<input type="submit" value="Search" class="but"/>
- <img title="Clear" class="reset" style="position: relative; top: 4px; left: -44px; cursor: pointer; display: none;" src="plugins/CoreHome/templates/images/reset_search.png"/>
+ <img title="Clear" class="reset" style="position: relative; top: 4px; left: -44px; cursor: pointer; display: none;"
+ src="plugins/CoreHome/templates/images/reset_search.png"/>
</div>
</div>
- </div>
- {if isset($inputName)}<input type="hidden" name="{$inputName}" value="{if isset($idSite)}{$idSite}{else}{$sites[0].idsite}{/if}"/>{/if}
+ </div>
+ {if isset($inputName)}<input type="hidden" name="{$inputName}" value="{if isset($idSite)}{$idSite}{else}{$sites[0].idsite}{/if}"/>{/if}
</div>
<script type="text/javascript">
- {literal}$(document).ready(function() { piwik.initSiteSelectors(); });{/literal}
+ {literal}$(document).ready(function () { piwik.initSiteSelectors(); });
+ {/literal}
</script>
diff --git a/plugins/CoreHome/templates/sparkline.js b/plugins/CoreHome/templates/sparkline.js
index ad5bfd3a88..dcf9d59d7b 100644
--- a/plugins/CoreHome/templates/sparkline.js
+++ b/plugins/CoreHome/templates/sparkline.js
@@ -5,58 +5,54 @@
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
-function initializeSparklines () {
- var sparklineUrlParamsToIgnore = ['module', 'action', 'idSite', 'period', 'date', 'viewDataTable'];
-
- $("a[name='evolutionGraph']").each(function() {
- var graph = $(this);
-
- // try to find sparklines and add them clickable behaviour
- graph.parent().find('div.sparkline').each(function() {
- // find the sparkline and get it's src attribute
- var sparklineUrl = $('img.sparkline', this).attr('src');
-
- if (sparklineUrl != "")
- {
- var params = broadcast.getValuesFromUrl(sparklineUrl);
- for (var i = 0; i != sparklineUrlParamsToIgnore.length; ++i)
- {
- delete params[sparklineUrlParamsToIgnore[i]];
- }
- for (var key in params)
- {
- params[key] = decodeURIComponent(params[key]);
- }
-
- // on click, reload the graph with the new url
- $(this).click(function() {
- var idDataTable = graph.attr('graphId'),
- dataTable = $('#' + idDataTable);
-
- // when the metrics picker is used, the id of the data table might be updated (which is correct behavior).
- // for example, in goal reports it might change from GoalsgetEvolutionGraph to GoalsgetEvolutionGraph1.
- // if this happens, we can't find the graph using $('#'+idDataTable+"Chart");
- // instead, we just use the first evolution graph we can find.
- if (dataTable.length == 0)
- {
- dataTable = $('div.dataTableGraphEvolutionWrapper').first().closest('.dataTable');
- }
-
- // reload the datatable w/ a new column & scroll to the graph
- dataTable.trigger('reload', params);
- });
- $(this).hover(
- function() {
- $(this).css({
- "cursor": "pointer",
- "border-bottom": "1px dashed #C3C3C3"
- });
- },
- function(){
- $(this).css({"border-bottom":"1px solid white"});
- }
- );
- }
- });
- });
+function initializeSparklines() {
+ var sparklineUrlParamsToIgnore = ['module', 'action', 'idSite', 'period', 'date', 'viewDataTable'];
+
+ $("a[name='evolutionGraph']").each(function () {
+ var graph = $(this);
+
+ // try to find sparklines and add them clickable behaviour
+ graph.parent().find('div.sparkline').each(function () {
+ // find the sparkline and get it's src attribute
+ var sparklineUrl = $('img.sparkline', this).attr('src');
+
+ if (sparklineUrl != "") {
+ var params = broadcast.getValuesFromUrl(sparklineUrl);
+ for (var i = 0; i != sparklineUrlParamsToIgnore.length; ++i) {
+ delete params[sparklineUrlParamsToIgnore[i]];
+ }
+ for (var key in params) {
+ params[key] = decodeURIComponent(params[key]);
+ }
+
+ // on click, reload the graph with the new url
+ $(this).click(function () {
+ var idDataTable = graph.attr('graphId'),
+ dataTable = $('#' + idDataTable);
+
+ // when the metrics picker is used, the id of the data table might be updated (which is correct behavior).
+ // for example, in goal reports it might change from GoalsgetEvolutionGraph to GoalsgetEvolutionGraph1.
+ // if this happens, we can't find the graph using $('#'+idDataTable+"Chart");
+ // instead, we just use the first evolution graph we can find.
+ if (dataTable.length == 0) {
+ dataTable = $('div.dataTableGraphEvolutionWrapper').first().closest('.dataTable');
+ }
+
+ // reload the datatable w/ a new column & scroll to the graph
+ dataTable.trigger('reload', params);
+ });
+ $(this).hover(
+ function () {
+ $(this).css({
+ "cursor": "pointer",
+ "border-bottom": "1px dashed #C3C3C3"
+ });
+ },
+ function () {
+ $(this).css({"border-bottom": "1px solid white"});
+ }
+ );
+ }
+ });
+ });
}
diff --git a/plugins/CoreHome/templates/sparkline_footer.tpl b/plugins/CoreHome/templates/sparkline_footer.tpl
index 068acec3e9..71231c74b5 100644
--- a/plugins/CoreHome/templates/sparkline_footer.tpl
+++ b/plugins/CoreHome/templates/sparkline_footer.tpl
@@ -1,7 +1,7 @@
{literal}
-<script type="text/javascript">
- $(document).ready(function() {
- initializeSparklines();
- });
-</script>
+ <script type="text/javascript">
+ $(document).ready(function () {
+ initializeSparklines();
+ });
+ </script>
{/literal}
diff --git a/plugins/CoreHome/templates/styles.css b/plugins/CoreHome/templates/styles.css
index b98b82b386..005e8840bc 100644
--- a/plugins/CoreHome/templates/styles.css
+++ b/plugins/CoreHome/templates/styles.css
@@ -1,148 +1,147 @@
h1 {
- font-size: 18px;
- font-weight:bold;
- color: #7e7363;
- padding:3px 0 7px 0;
- clear:both;
+ font-size: 18px;
+ font-weight: bold;
+ color: #7e7363;
+ padding: 3px 0 7px 0;
+ clear: both;
}
h2 {
- font-size: 18px;
- font-weight:normal;
- color: #7e7363;
- padding:12px 0 7px 0;
- clear:both;
+ font-size: 18px;
+ font-weight: normal;
+ color: #7e7363;
+ padding: 12px 0 7px 0;
+ clear: both;
}
h2 a {
- text-decoration:none;
- color: #7e7363;
+ text-decoration: none;
+ color: #7e7363;
}
h3 {
- font-size: 1.3em;
- margin-top: 2em;
- color: #1D3256;
+ font-size: 1.3em;
+ margin-top: 2em;
+ color: #1D3256;
}
.home p {
- padding-bottom: 1em;
- margin-right: 1em;
- margin-left:1em;
+ padding-bottom: 1em;
+ margin-right: 1em;
+ margin-left: 1em;
}
-.page{
- background: url(../../../themes/default/images/page_border_grad.png) no-repeat 0 20px;
- min-height:10px;
-
-}
+.page {
+ background: url(../../../themes/default/images/page_border_grad.png) no-repeat 0 20px;
+ min-height: 10px;
-.pageWrap{
- padding:15px 15px 0 15px;
- min-height:10px;
- position:relative;
- background: url(../../../themes/default/images/page_border_grad.png) no-repeat right 20px;
-
}
+.pageWrap {
+ padding: 15px 15px 0 15px;
+ min-height: 10px;
+ position: relative;
+ background: url(../../../themes/default/images/page_border_grad.png) no-repeat right 20px;
-
-.nav_sep{
- height:39px;
- margin:-15px -15px 18px -15px;
- border-radius: 0 4px 0 0;
- -moz-border-radius: 0 4px 0 0;
- -webkit-border-radius: 0 4px 0 0;
- border:1px solid #dddddd;
}
+.nav_sep {
+ height: 39px;
+ margin: -15px -15px 18px -15px;
+ border-radius: 0 4px 0 0;
+ -moz-border-radius: 0 4px 0 0;
+ -webkit-border-radius: 0 4px 0 0;
+ border: 1px solid #dddddd;
+}
/* Content */
#content.home {
- padding-top:5px;
- font-size:14px;
+ padding-top: 5px;
+ font-size: 14px;
}
-
/* 2 columns reports */
#leftcolumn {
- float: left;
- width: 50%;
+ float: left;
+ width: 50%;
}
+
#rightcolumn {
- float: right;
- width: 45%;
+ float: right;
+ width: 45%;
}
+
/* not in widget */
.widget #leftcolumn, .widget #rightcolumn {
- float:left;
- padding: 0 10px;
- width:auto;
+ float: left;
+ padding: 0 10px;
+ width: auto;
}
/* Calendar*/
div.ui-datepicker {
- font-size: 62.5%;
+ font-size: 62.5%;
}
.ui-datepicker-current-period a, .ui-datepicker-current-period a:link, .ui-datepicker-current-period a:visited {
- border: 1px solid #2E85FF;
- color: #2E85FF;
+ border: 1px solid #2E85FF;
+ color: #2E85FF;
}
+
#otherPeriods a {
- text-decoration: none;
+ text-decoration: none;
}
#otherPeriods a:hover {
- text-decoration: underline;
+ text-decoration: underline;
}
#currentPeriod {
- border-bottom: 1px dotted #520202;
+ border-bottom: 1px dotted #520202;
}
.hoverPeriod {
- cursor: pointer;
- font-weight: bold;
- border-bottom: 1px solid #520202;
+ cursor: pointer;
+ font-weight: bold;
+ border-bottom: 1px solid #520202;
}
#calendarRangeTo {
- float:left;
+ float: left;
margin-left: 20px;
}
#calendarRangeFrom {
- float:left;
+ float: left;
}
#inputCalendarFrom, #inputCalendarTo {
- margin-left: 10px;
+ margin-left: 10px;
width: 90px;
}
#calendarRangeApply {
- display:none;
- margin-top:10px;
- margin-left:10px;
+ display: none;
+ margin-top: 10px;
+ margin-left: 10px;
}
#invalidDateRange {
- display:none;
+ display: none;
}
div .sparkline {
- float:left;
- clear:both;
- padding-bottom: 1px;
- margin-top:10px;
- border-bottom:1px solid white;
+ float: left;
+ clear: both;
+ padding-bottom: 1px;
+ margin-top: 10px;
+ border-bottom: 1px solid white;
}
.sparkline img {
- vertical-align: middle;
- padding-right: 10px;
- margin-top: 0;
+ vertical-align: middle;
+ padding-right: 10px;
+ margin-top: 0;
}
div.pk-emptyGraph {
@@ -152,85 +151,85 @@ div.pk-emptyGraph {
font-style: italic;
}
-
/**
* Popover
* @see popover.js
*/
#Piwik_Popover {
- font-family: Arial, Helvetica, sans-serif;
+ font-family: Arial, Helvetica, sans-serif;
}
.Piwik_Popover_Loading_Name {
- padding: 50px 0 65px 0;
- font-size: 16px;
- line-height: 20px;
- font-weight: normal;
- text-align: center;
- background: url(../../../themes/default/images/loading-blue.gif) no-repeat center 20px;
+ padding: 50px 0 65px 0;
+ font-size: 16px;
+ line-height: 20px;
+ font-weight: normal;
+ text-align: center;
+ background: url(../../../themes/default/images/loading-blue.gif) no-repeat center 20px;
}
.Piwik_Popover_Loading_NameWithSubject {
- padding-bottom: 30px;
+ padding-bottom: 30px;
}
.Piwik_Popover_Loading_Subject {
- padding: 0 70px 55px 70px;
- color: #7e7363;
- text-align: center;
- font-size: 14px;
+ padding: 0 70px 55px 70px;
+ color: #7e7363;
+ text-align: center;
+ font-size: 14px;
}
.Piwik_Popover_Error {
- padding: 50px 20px 65px 20px;
- text-align: center;
+ padding: 50px 20px 65px 20px;
+ text-align: center;
}
.Piwik_Popover_Error_Title {
- color: #E87500;
- font-weight: bold;
- font-size: 16px;
+ color: #E87500;
+ font-weight: bold;
+ font-size: 16px;
}
.Piwik_Popover_Error_Title span {
- color: #222;
- font-weight: normal;
- font-size: 16px;
+ color: #222;
+ font-weight: normal;
+ font-size: 16px;
}
.Piwik_Popover_Error_Message {
- color: #7e7363;
- padding: 20px 0 0 0;
- font-size: 14px;
+ color: #7e7363;
+ padding: 20px 0 0 0;
+ font-size: 14px;
}
a.Piwik_Popover_Error_Back {
- display: block;
- margin: 20px 0 0 0;
- color: #1D3256;
- font-size: 14px;
+ display: block;
+ margin: 20px 0 0 0;
+ color: #1D3256;
+ font-size: 14px;
}
#alert.ui-confirm input {
display: block;
- margin: 10px auto 5px!important;
+ margin: 10px auto 5px !important;
}
#updateCheckLinkContainer {
- float:right;
- cursor:pointer;
+ float: right;
+ cursor: pointer;
}
#updateCheckLinkContainer>* {
- vertical-align:bottom;
+ vertical-align: bottom;
}
#header_message #checkForUpdates {
- font-size:.8em;
- line-height:16px;
- display:inline-block;
+ font-size: .8em;
+ line-height: 16px;
+ display: inline-block;
}
+
#header_message #checkForUpdates:hover {
- text-decoration:none;
+ text-decoration: none;
}
diff --git a/plugins/CoreHome/templates/tooltip.js b/plugins/CoreHome/templates/tooltip.js
index fbeba46df3..cf1343887c 100644
--- a/plugins/CoreHome/templates/tooltip.js
+++ b/plugins/CoreHome/templates/tooltip.js
@@ -5,114 +5,114 @@
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
-var Piwik_Tooltip = (function() {
-
- var domElement = false;
- var visible = false;
- var addedClass = false;
- var topOffset = 15;
-
- var mouseX, mouseY;
-
- /** Position the tooltip next to the mouse */
- var position = function() {
- var tipWidth = domElement.outerWidth();
- var maxX = $('body').innerWidth() - tipWidth - 25;
- if (mouseX < maxX) {
- // tooltip right of mouse
- domElement.css({
- top: (mouseY - topOffset) + "px",
- left: (mouseX + 15) + "px"
- });
- }
- else {
- // tooltip left of mouse
- domElement.css({
- top: (mouseY - topOffset) + "px",
- left: (mouseX - 15 - tipWidth) + "px"
- });
- }
- };
-
- /** Create and initialize the tooltip */
- var initialize = function() {
- if (domElement !== false) {
- return;
- }
-
- domElement = $(document.createElement('div'));
- domElement.addClass('piwik-tooltip');
- $('body').prepend(domElement);
- domElement.hide();
-
- $(document).mousemove(function(e) {
- mouseX = e.pageX;
- mouseY = e.pageY;
- if (visible) {
- position();
- }
- });
- };
-
- $(document).ready(function() {
- initialize();
- });
-
- return {
-
- /** Show the tooltip with HTML content. */
- show: function(html, addClass, maxWidth) {
- initialize();
-
- if (visible && addedClass != addClass) {
- domElement.removeClass(addedClass);
- } else {
- visible = true;
- position();
- domElement.show();
- }
-
- if (addClass && addedClass != addClass) {
- addedClass = addClass;
- domElement.addClass(addClass);
- }
-
- domElement.css({width: 'auto'});
- domElement.html(html);
- if (domElement.outerWidth() > maxWidth) {
- domElement.css({width: maxWidth + 'px'});
- }
-
- if (domElement.outerHeight() < 25) {
- topOffset = 5;
- } else {
- topOffset = 15;
- }
-
- position();
- },
-
- /** Show the tooltip with title/text content. */
- showWithTitle: function(title, text, addClass) {
- var html = '<span class="tip-title">' + title + '</span><br />' + text;
- this.show(html, addClass);
- },
-
- /** Hide the tooltip */
- hide: function() {
- if (domElement !== false) {
- domElement.hide();
- }
-
- if (addedClass) {
- domElement.removeClass(addedClass);
- addedClass = false;
- }
-
- visible = false;
- }
-
- };
+var Piwik_Tooltip = (function () {
+
+ var domElement = false;
+ var visible = false;
+ var addedClass = false;
+ var topOffset = 15;
+
+ var mouseX, mouseY;
+
+ /** Position the tooltip next to the mouse */
+ var position = function () {
+ var tipWidth = domElement.outerWidth();
+ var maxX = $('body').innerWidth() - tipWidth - 25;
+ if (mouseX < maxX) {
+ // tooltip right of mouse
+ domElement.css({
+ top: (mouseY - topOffset) + "px",
+ left: (mouseX + 15) + "px"
+ });
+ }
+ else {
+ // tooltip left of mouse
+ domElement.css({
+ top: (mouseY - topOffset) + "px",
+ left: (mouseX - 15 - tipWidth) + "px"
+ });
+ }
+ };
+
+ /** Create and initialize the tooltip */
+ var initialize = function () {
+ if (domElement !== false) {
+ return;
+ }
+
+ domElement = $(document.createElement('div'));
+ domElement.addClass('piwik-tooltip');
+ $('body').prepend(domElement);
+ domElement.hide();
+
+ $(document).mousemove(function (e) {
+ mouseX = e.pageX;
+ mouseY = e.pageY;
+ if (visible) {
+ position();
+ }
+ });
+ };
+
+ $(document).ready(function () {
+ initialize();
+ });
+
+ return {
+
+ /** Show the tooltip with HTML content. */
+ show: function (html, addClass, maxWidth) {
+ initialize();
+
+ if (visible && addedClass != addClass) {
+ domElement.removeClass(addedClass);
+ } else {
+ visible = true;
+ position();
+ domElement.show();
+ }
+
+ if (addClass && addedClass != addClass) {
+ addedClass = addClass;
+ domElement.addClass(addClass);
+ }
+
+ domElement.css({width: 'auto'});
+ domElement.html(html);
+ if (domElement.outerWidth() > maxWidth) {
+ domElement.css({width: maxWidth + 'px'});
+ }
+
+ if (domElement.outerHeight() < 25) {
+ topOffset = 5;
+ } else {
+ topOffset = 15;
+ }
+
+ position();
+ },
+
+ /** Show the tooltip with title/text content. */
+ showWithTitle: function (title, text, addClass) {
+ var html = '<span class="tip-title">' + title + '</span><br />' + text;
+ this.show(html, addClass);
+ },
+
+ /** Hide the tooltip */
+ hide: function () {
+ if (domElement !== false) {
+ domElement.hide();
+ }
+
+ if (addedClass) {
+ domElement.removeClass(addedClass);
+ addedClass = false;
+ }
+
+ visible = false;
+ }
+
+ };
})();
diff --git a/plugins/CoreHome/templates/top_bar.tpl b/plugins/CoreHome/templates/top_bar.tpl
index 2b04de21d5..56a8c8937e 100644
--- a/plugins/CoreHome/templates/top_bar.tpl
+++ b/plugins/CoreHome/templates/top_bar.tpl
@@ -1,13 +1,13 @@
<div id="topBars">
- {include file="CoreHome/templates/top_bar_top_menu.tpl"}
- {include file="CoreHome/templates/top_bar_hello_menu.tpl"}
+ {include file="CoreHome/templates/top_bar_top_menu.tpl"}
+ {include file="CoreHome/templates/top_bar_hello_menu.tpl"}
</div>
{if $showSitesSelection}
-<div class="top_bar_sites_selector">
- <label>{'General_Website'|translate}</label>
- {include file="CoreHome/templates/sites_selection.tpl"}
-</div>
+ <div class="top_bar_sites_selector">
+ <label>{'General_Website'|translate}</label>
+ {include file="CoreHome/templates/sites_selection.tpl"}
+ </div>
{/if}
diff --git a/plugins/CoreHome/templates/top_bar_hello_menu.tpl b/plugins/CoreHome/templates/top_bar_hello_menu.tpl
index 4a3fa0dc8f..9a5dc26cbc 100644
--- a/plugins/CoreHome/templates/top_bar_hello_menu.tpl
+++ b/plugins/CoreHome/templates/top_bar_hello_menu.tpl
@@ -1,7 +1,7 @@
-
<div id="topRightBar">
-{capture assign=helloAlias}{if !empty($userAlias)}{$userAlias}{else}{$userLogin}{/if}{/capture}
- <span class="topBarElem">{'General_HelloUser'|translate:"<strong>$helloAlias</strong>"}</span>
-{if $userLogin != 'anonymous'}| <span class="topBarElem"><a href='index.php?module=CoreAdminHome'>{'General_Settings'|translate}</a></span>{/if}
- | <span class="topBarElem">{if $userLogin == 'anonymous'}<a href='index.php?module={$loginModule}'>{'Login_LogIn'|translate}</a>{else}<a href='index.php?module={$loginModule}&amp;action=logout'>{'Login_Logout'|translate}</a>{/if}</span>
+ {capture assign=helloAlias}{if !empty($userAlias)}{$userAlias}{else}{$userLogin}{/if}{/capture}
+ <span class="topBarElem">{'General_HelloUser'|translate:"<strong>$helloAlias</strong>"}</span>
+ {if $userLogin != 'anonymous'}| <span class="topBarElem"><a href='index.php?module=CoreAdminHome'>{'General_Settings'|translate}</a></span>{/if}
+ | <span class="topBarElem">{if $userLogin == 'anonymous'}<a href='index.php?module={$loginModule}'>{'Login_LogIn'|translate}</a>{else}<a
+ href='index.php?module={$loginModule}&amp;action=logout'>{'Login_Logout'|translate}</a>{/if}</span>
</div>
diff --git a/plugins/CoreHome/templates/top_bar_top_menu.tpl b/plugins/CoreHome/templates/top_bar_top_menu.tpl
index 669db27933..844549f8df 100644
--- a/plugins/CoreHome/templates/top_bar_top_menu.tpl
+++ b/plugins/CoreHome/templates/top_bar_top_menu.tpl
@@ -1,13 +1,16 @@
<div id="topLeftBar">
-{foreach from=$topMenu key=label item=menu name=topMenu}
+ {foreach from=$topMenu key=label item=menu name=topMenu}
- {if isset($menu._html)}
- {$menu._html}
- {elseif $menu._url.module == $currentModule && (empty($menu._url.action) || $menu._url.action == $currentAction)}
- <span class="topBarElem"><b>{$label|translate}</b></span> |
- {else}
- <span class="topBarElem" {if isset($menu._tooltip)}title="{$menu._tooltip}"{/if}><a id="topmenu-{$menu._url.module|strtolower}" href="index.php{$menu._url|@urlRewriteWithParameters}">{$label|translate}</a></span> |
- {/if}
+ {if isset($menu._html)}
+ {$menu._html}
+ {elseif $menu._url.module == $currentModule && (empty($menu._url.action) || $menu._url.action == $currentAction)}
+ <span class="topBarElem"><b>{$label|translate}</b></span>
+ |
+ {else}
+ <span class="topBarElem" {if isset($menu._tooltip)}title="{$menu._tooltip}"{/if}><a id="topmenu-{$menu._url.module|strtolower}"
+ href="index.php{$menu._url|@urlRewriteWithParameters}">{$label|translate}</a></span>
+ |
+ {/if}
-{/foreach}
+ {/foreach}
</div> \ No newline at end of file
diff --git a/plugins/CoreHome/templates/top_screen.tpl b/plugins/CoreHome/templates/top_screen.tpl
index e38c5aaf35..771da8bc47 100644
--- a/plugins/CoreHome/templates/top_screen.tpl
+++ b/plugins/CoreHome/templates/top_screen.tpl
@@ -1,4 +1,4 @@
<div id="header">
-{include file="CoreHome/templates/logo.tpl"}
-{include file="CoreHome/templates/js_disabled_notice.tpl"}
+ {include file="CoreHome/templates/logo.tpl"}
+ {include file="CoreHome/templates/js_disabled_notice.tpl"}
</div>
diff --git a/plugins/CoreHome/templates/warning_invalid_host.tpl b/plugins/CoreHome/templates/warning_invalid_host.tpl
index d9a77cde70..0a33082ed6 100644
--- a/plugins/CoreHome/templates/warning_invalid_host.tpl
+++ b/plugins/CoreHome/templates/warning_invalid_host.tpl
@@ -1,9 +1,8 @@
-
{* untrusted host warning *}
{if isset($isValidHost) && isset($invalidHostMessage) && !$isValidHost}
-<div class="ajaxSuccess" style='clear:both;width:800px'>
- <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}:&nbsp;</strong>{$invalidHostMessage}
-</div>
+ <div class="ajaxSuccess" style='clear:both;width:800px'>
+ <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}:&nbsp;</strong>{$invalidHostMessage}
+ </div>
{/if}
diff --git a/plugins/CorePluginsAdmin/Controller.php b/plugins/CorePluginsAdmin/Controller.php
index 5960b4dd41..9896125435 100644
--- a/plugins/CorePluginsAdmin/Controller.php
+++ b/plugins/CorePluginsAdmin/Controller.php
@@ -15,78 +15,71 @@
*/
class Piwik_CorePluginsAdmin_Controller extends Piwik_Controller_Admin
{
- function index()
- {
- Piwik::checkUserIsSuperUser();
+ function index()
+ {
+ Piwik::checkUserIsSuperUser();
- $plugins = array();
+ $plugins = array();
- $listPlugins = array_merge(
- Piwik_PluginsManager::getInstance()->readPluginsDirectory(),
- Piwik_Config::getInstance()->Plugins['Plugins']
- );
- $listPlugins = array_unique($listPlugins);
- foreach($listPlugins as $pluginName)
- {
- Piwik_PluginsManager::getInstance()->loadPlugin($pluginName);
- $plugins[$pluginName] = array(
- 'activated' => Piwik_PluginsManager::getInstance()->isPluginActivated($pluginName),
- 'alwaysActivated' => Piwik_PluginsManager::getInstance()->isPluginAlwaysActivated($pluginName),
- );
- }
- Piwik_PluginsManager::getInstance()->loadPluginTranslations();
+ $listPlugins = array_merge(
+ Piwik_PluginsManager::getInstance()->readPluginsDirectory(),
+ Piwik_Config::getInstance()->Plugins['Plugins']
+ );
+ $listPlugins = array_unique($listPlugins);
+ foreach ($listPlugins as $pluginName) {
+ Piwik_PluginsManager::getInstance()->loadPlugin($pluginName);
+ $plugins[$pluginName] = array(
+ 'activated' => Piwik_PluginsManager::getInstance()->isPluginActivated($pluginName),
+ 'alwaysActivated' => Piwik_PluginsManager::getInstance()->isPluginAlwaysActivated($pluginName),
+ );
+ }
+ Piwik_PluginsManager::getInstance()->loadPluginTranslations();
- $loadedPlugins = Piwik_PluginsManager::getInstance()->getLoadedPlugins();
- foreach($loadedPlugins as $oPlugin)
- {
- $pluginName = $oPlugin->getPluginName();
- $plugins[$pluginName]['info'] = $oPlugin->getInformation();
- }
-
- foreach($plugins as $pluginName => &$plugin)
- {
- if (!isset($plugin['info']))
- {
- $plugin['info'] = array(
- 'description' => '<strong><em>'.Piwik_Translate('CorePluginsAdmin_PluginCannotBeFound')
- .'</strong></em>',
- 'version' => Piwik_Translate('General_Unknown')
- );
- }
- }
+ $loadedPlugins = Piwik_PluginsManager::getInstance()->getLoadedPlugins();
+ foreach ($loadedPlugins as $oPlugin) {
+ $pluginName = $oPlugin->getPluginName();
+ $plugins[$pluginName]['info'] = $oPlugin->getInformation();
+ }
- $view = Piwik_View::factory('manage');
- $view->pluginsName = $plugins;
- $this->setBasicVariablesView($view);
- $view->menu = Piwik_GetAdminMenu();
- if(!Piwik_Config::getInstance()->isFileWritable())
- {
- $view->configFileNotWritable = true;
- }
- echo $view->render();
- }
+ foreach ($plugins as $pluginName => &$plugin) {
+ if (!isset($plugin['info'])) {
+ $plugin['info'] = array(
+ 'description' => '<strong><em>' . Piwik_Translate('CorePluginsAdmin_PluginCannotBeFound')
+ . '</strong></em>',
+ 'version' => Piwik_Translate('General_Unknown')
+ );
+ }
+ }
- public function deactivate($redirectAfter = true)
- {
- Piwik::checkUserIsSuperUser();
- $this->checkTokenInUrl();
- $pluginName = Piwik_Common::getRequestVar('pluginName', null, 'string');
- Piwik_PluginsManager::getInstance()->deactivatePlugin($pluginName);
- if($redirectAfter)
- {
- Piwik_Url::redirectToReferer();
- }
- }
+ $view = Piwik_View::factory('manage');
+ $view->pluginsName = $plugins;
+ $this->setBasicVariablesView($view);
+ $view->menu = Piwik_GetAdminMenu();
+ if (!Piwik_Config::getInstance()->isFileWritable()) {
+ $view->configFileNotWritable = true;
+ }
+ echo $view->render();
+ }
- public function activate($redirectAfter = true)
- {
- Piwik::checkUserIsSuperUser();
- $this->checkTokenInUrl();
- $pluginName = Piwik_Common::getRequestVar('pluginName', null, 'string');
- Piwik_PluginsManager::getInstance()->activatePlugin($pluginName);
- if($redirectAfter)
- {
- Piwik_Url::redirectToReferer();
- }
- }
+ public function deactivate($redirectAfter = true)
+ {
+ Piwik::checkUserIsSuperUser();
+ $this->checkTokenInUrl();
+ $pluginName = Piwik_Common::getRequestVar('pluginName', null, 'string');
+ Piwik_PluginsManager::getInstance()->deactivatePlugin($pluginName);
+ if ($redirectAfter) {
+ Piwik_Url::redirectToReferer();
+ }
+ }
+
+ public function activate($redirectAfter = true)
+ {
+ Piwik::checkUserIsSuperUser();
+ $this->checkTokenInUrl();
+ $pluginName = Piwik_Common::getRequestVar('pluginName', null, 'string');
+ Piwik_PluginsManager::getInstance()->activatePlugin($pluginName);
+ if ($redirectAfter) {
+ Piwik_Url::redirectToReferer();
+ }
+ }
}
diff --git a/plugins/CorePluginsAdmin/CorePluginsAdmin.php b/plugins/CorePluginsAdmin/CorePluginsAdmin.php
index 1630e59bf6..89dd9bb2e6 100644
--- a/plugins/CorePluginsAdmin/CorePluginsAdmin.php
+++ b/plugins/CorePluginsAdmin/CorePluginsAdmin.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_CorePluginsAdmin
*/
@@ -15,27 +15,27 @@
*/
class Piwik_CorePluginsAdmin extends Piwik_Plugin
{
- public function getInformation()
- {
- return array(
- 'description' => Piwik_Translate('CorePluginsAdmin_PluginDescription'),
- 'author' => 'Piwik',
- 'author_homepage' => 'http://piwik.org/',
- 'version' => Piwik_Version::VERSION,
- );
- }
-
- function getListHooksRegistered()
- {
- return array('AdminMenu.add' => 'addMenu');
- }
-
- function addMenu()
- {
- Piwik_AddAdminSubMenu('CorePluginsAdmin_MenuPlugins', null, "", Piwik::isUserIsSuperUser(), $order = 15);
- Piwik_AddAdminSubMenu('CorePluginsAdmin_MenuPlugins', 'CorePluginsAdmin_MenuPluginsInstalled',
- array('module' => 'CorePluginsAdmin', 'action' => 'index'),
- Piwik::isUserIsSuperUser(),
- $order = 1);
- }
+ public function getInformation()
+ {
+ return array(
+ 'description' => Piwik_Translate('CorePluginsAdmin_PluginDescription'),
+ 'author' => 'Piwik',
+ 'author_homepage' => 'http://piwik.org/',
+ 'version' => Piwik_Version::VERSION,
+ );
+ }
+
+ function getListHooksRegistered()
+ {
+ return array('AdminMenu.add' => 'addMenu');
+ }
+
+ function addMenu()
+ {
+ Piwik_AddAdminSubMenu('CorePluginsAdmin_MenuPlugins', null, "", Piwik::isUserIsSuperUser(), $order = 15);
+ Piwik_AddAdminSubMenu('CorePluginsAdmin_MenuPlugins', 'CorePluginsAdmin_MenuPluginsInstalled',
+ array('module' => 'CorePluginsAdmin', 'action' => 'index'),
+ Piwik::isUserIsSuperUser(),
+ $order = 1);
+ }
}
diff --git a/plugins/CorePluginsAdmin/templates/manage.tpl b/plugins/CorePluginsAdmin/templates/manage.tpl
index a381bac550..40d742ea78 100644
--- a/plugins/CorePluginsAdmin/templates/manage.tpl
+++ b/plugins/CorePluginsAdmin/templates/manage.tpl
@@ -2,54 +2,63 @@
<div style="max-width:980px;">
-<h2>{'CorePluginsAdmin_PluginsManagement'|translate}</h2>
-<p>{'CorePluginsAdmin_MainDescription'|translate}</p>
-<div class='entityContainer'>
-<table class="dataTable entityTable">
- <thead>
- <tr>
- <th>{'CorePluginsAdmin_Plugin'|translate}</th>
- <th class="num">{'CorePluginsAdmin_Version'|translate}</th>
- <th>{'General_Description'|translate}</th>
- <th class="status">{'CorePluginsAdmin_Status'|translate}</th>
- <th class="action-links">{'CorePluginsAdmin_Action'|translate}</th>
- </tr>
- </thead>
- <tbody id="plugins">
- {foreach from=$pluginsName key=name item=plugin}
- {if isset($plugin.alwaysActivated) && !$plugin.alwaysActivated}
- <tr {if $plugin.activated}class="highlighted"{/if}>
- <td class="name">
- {if isset($plugin.info.homepage)}<a title="{'CorePluginsAdmin_PluginHomepage'|translate}" href="{$plugin.info.homepage}" target="_blank">{/if}
- {$name}
- {if isset($plugin.info.homepage)}</a>{/if}
- </td>
- <td class="vers">{$plugin.info.version}</td>
- <td class="desc">
- {$plugin.info.description|nl2br}
- {if isset($plugin.info.license)}
- &nbsp;({if isset($plugin.info.license_homepage)}<a title="{'CorePluginsAdmin_LicenseHomepage'|translate}" target="_blank" href="{$plugin.info.license_homepage}">{/if}{$plugin.info.license}{if isset($plugin.info.license_homepage)}</a>){/if}
- {/if}
- {if isset($plugin.info.author)}
- &nbsp;<cite>By
- {if isset($plugin.info.author_homepage)}<a title="{'CorePluginsAdmin_AuthorHomepage'|translate}" href="{$plugin.info.author_homepage}" target="_blank">{/if}{$plugin.info.author}{if isset($plugin.info.author_homepage)}</a>{/if}.</cite>
- {/if}
- </td>
- <td class="status">
- {if $plugin.activated}{'CorePluginsAdmin_Active'|translate}
- {else}{'CorePluginsAdmin_Inactive'|translate}{/if}
- </td>
-
- <td class="togl action-links">
- {if $plugin.activated}<a href='index.php?module=CorePluginsAdmin&action=deactivate&pluginName={$name}&token_auth={$token_auth}'>{'CorePluginsAdmin_Deactivate'|translate}</a>
- {else}<a href='index.php?module=CorePluginsAdmin&action=activate&pluginName={$name}&token_auth={$token_auth}'>{'CorePluginsAdmin_Activate'|translate}</a>{/if}
- </td>
- </tr>
- {/if}
-{/foreach}
-</tbody>
-</table>
-</div>
+ <h2>{'CorePluginsAdmin_PluginsManagement'|translate}</h2>
+
+ <p>{'CorePluginsAdmin_MainDescription'|translate}</p>
+
+ <div class='entityContainer'>
+ <table class="dataTable entityTable">
+ <thead>
+ <tr>
+ <th>{'CorePluginsAdmin_Plugin'|translate}</th>
+ <th class="num">{'CorePluginsAdmin_Version'|translate}</th>
+ <th>{'General_Description'|translate}</th>
+ <th class="status">{'CorePluginsAdmin_Status'|translate}</th>
+ <th class="action-links">{'CorePluginsAdmin_Action'|translate}</th>
+ </tr>
+ </thead>
+ <tbody id="plugins">
+ {foreach from=$pluginsName key=name item=plugin}
+ {if isset($plugin.alwaysActivated) && !$plugin.alwaysActivated}
+ <tr {if $plugin.activated}class="highlighted"{/if}>
+ <td class="name">
+ {if isset($plugin.info.homepage)}<a title="{'CorePluginsAdmin_PluginHomepage'|translate}" href="{$plugin.info.homepage}"
+ target="_blank">{/if}
+ {$name}
+ {if isset($plugin.info.homepage)}</a>{/if}
+ </td>
+ <td class="vers">{$plugin.info.version}</td>
+ <td class="desc">
+ {$plugin.info.description|nl2br}
+ {if isset($plugin.info.license)}
+ &nbsp;({if isset($plugin.info.license_homepage)}<a title="{'CorePluginsAdmin_LicenseHomepage'|translate}" target="_blank" href="{$plugin.info.license_homepage}">{/if}{$plugin.info.license}{if isset($plugin.info.license_homepage)}</a>){/if}
+ {/if}
+ {if isset($plugin.info.author)}
+ &nbsp;
+ <cite>By
+ {if isset($plugin.info.author_homepage)}<a title="{'CorePluginsAdmin_AuthorHomepage'|translate}"
+ href="{$plugin.info.author_homepage}"
+ target="_blank">{/if}{$plugin.info.author}{if isset($plugin.info.author_homepage)}</a>{/if}
+ .</cite>
+ {/if}
+ </td>
+ <td class="status">
+ {if $plugin.activated}{'CorePluginsAdmin_Active'|translate}
+ {else}{'CorePluginsAdmin_Inactive'|translate}{/if}
+ </td>
+
+ <td class="togl action-links">
+ {if $plugin.activated}<a
+ href='index.php?module=CorePluginsAdmin&action=deactivate&pluginName={$name}&token_auth={$token_auth}'>{'CorePluginsAdmin_Deactivate'|translate}</a>
+ {else}<a
+ href='index.php?module=CorePluginsAdmin&action=activate&pluginName={$name}&token_auth={$token_auth}'>{'CorePluginsAdmin_Activate'|translate}</a>{/if}
+ </td>
+ </tr>
+ {/if}
+ {/foreach}
+ </tbody>
+ </table>
+ </div>
</div>
{include file="CoreAdminHome/templates/footer.tpl"}
diff --git a/plugins/CoreUpdater/Controller.php b/plugins/CoreUpdater/Controller.php
index b8830a8711..8e20f9c82a 100644
--- a/plugins/CoreUpdater/Controller.php
+++ b/plugins/CoreUpdater/Controller.php
@@ -15,368 +15,336 @@
*/
class Piwik_CoreUpdater_Controller extends Piwik_Controller
{
- const CONFIG_FILE_BACKUP = '/config/global.ini.auto-backup-before-update.php';
- const PATH_TO_EXTRACT_LATEST_VERSION = '/tmp/latest/';
-
- private $coreError = false;
- private $warningMessages = array();
- private $errorMessages = array();
- private $deactivatedPlugins = array();
-
- static protected function getLatestZipUrl($newVersion)
- {
- if(@Piwik_Config::getInstance()->Debug['allow_upgrades_to_beta'])
- {
- return 'http://builds.piwik.org/piwik-'.$newVersion.'.zip';
- }
- return Piwik_Config::getInstance()->General['latest_version_url'];
- }
- public function newVersionAvailable()
- {
- Piwik::checkUserIsSuperUser();
-
- $newVersion = $this->checkNewVersionIsAvailableOrDie();
-
- $view = Piwik_View::factory('update_new_version_available');
- $view->piwik_version = Piwik_Version::VERSION;
- $view->piwik_new_version = $newVersion;
- $view->piwik_latest_version_url = self::getLatestZipUrl($newVersion);
- $view->can_auto_update = Piwik::canAutoUpdate();
- $view->makeWritableCommands = Piwik::getAutoUpdateMakeWritableMessage();
- echo $view->render();
- }
-
- public function oneClickUpdate()
- {
- Piwik::checkUserIsSuperUser();
- $this->newVersion = $this->checkNewVersionIsAvailableOrDie();
-
- Piwik::setMaxExecutionTime(0);
-
- $url = self::getLatestZipUrl($this->newVersion);
- $steps = array(
- array('oneClick_Download', Piwik_Translate('CoreUpdater_DownloadingUpdateFromX', $url)),
- array('oneClick_Unpack', Piwik_Translate('CoreUpdater_UnpackingTheUpdate')),
- array('oneClick_Verify', Piwik_Translate('CoreUpdater_VerifyingUnpackedFiles')),
- array('oneClick_CreateConfigFileBackup', Piwik_Translate('CoreUpdater_CreatingBackupOfConfigurationFile', self::CONFIG_FILE_BACKUP)),
- array('oneClick_Copy', Piwik_Translate('CoreUpdater_InstallingTheLatestVersion')),
- array('oneClick_Finished', Piwik_Translate('CoreUpdater_PiwikUpdatedSuccessfully')),
- );
-
- $errorMessage = false;
- $messages = array();
- foreach($steps as $step) {
- try {
- $method = $step[0];
- $message = $step[1];
- $this->$method();
- $messages[] = $message;
- } catch(Exception $e) {
- $errorMessage = $e->getMessage();
- break;
- }
- }
-
- // this is a magic template to trigger the Piwik_View_Update
- $view = Piwik_View::factory(Piwik_View::COREUPDATER_ONE_CLICK_DONE);
- $view->coreError = $errorMessage;
- $view->feedbackMessages = $messages;
- echo $view->render();
- }
-
- public function oneClickResults()
- {
- Piwik_API_Request::reloadAuthUsingTokenAuth($_POST);
- Piwik::checkUserIsSuperUser();
-
- $view = Piwik_View::factory('update_one_click_results');
- $view->coreError = Piwik_Common::getRequestVar('error', '', 'string', $_POST);
- $view->feedbackMessages = safe_unserialize(Piwik_Common::unsanitizeInputValue(Piwik_Common::getRequestVar('messages', '', 'string', $_POST)));
- echo $view->render();
- }
-
- private function checkNewVersionIsAvailableOrDie()
- {
- $newVersion = Piwik_UpdateCheck::isNewestVersionAvailable();
- if(!$newVersion)
- {
- throw new Exception(Piwik_TranslateException('CoreUpdater_ExceptionAlreadyLatestVersion', Piwik_Version::VERSION));
- }
- return $newVersion;
- }
-
- private function oneClick_Download()
- {
- $this->pathPiwikZip = PIWIK_USER_PATH . self::PATH_TO_EXTRACT_LATEST_VERSION . 'latest.zip';
- Piwik::checkDirectoriesWritableOrDie( array(self::PATH_TO_EXTRACT_LATEST_VERSION) );
-
- // we catch exceptions in the caller (i.e., oneClickUpdate)
- $url = self::getLatestZipUrl($this->newVersion) . '?cb=' . $this->newVersion;
- $fetched = Piwik_Http::fetchRemoteFile($url, $this->pathPiwikZip);
- }
-
- private function oneClick_Unpack()
- {
- $pathExtracted = PIWIK_USER_PATH . self::PATH_TO_EXTRACT_LATEST_VERSION;
- $this->pathRootExtractedPiwik = $pathExtracted . 'piwik';
-
- if(file_exists($this->pathRootExtractedPiwik))
- {
- Piwik::unlinkRecursive($this->pathRootExtractedPiwik, true);
- }
-
- $archive = Piwik_Unzip::factory('PclZip', $this->pathPiwikZip);
-
- if ( 0 == ($archive_files = $archive->extract($pathExtracted) ) )
- {
- throw new Exception(Piwik_TranslateException('CoreUpdater_ExceptionArchiveIncompatible', $archive->errorInfo()));
- }
-
- if ( 0 == count($archive_files) )
- {
- throw new Exception(Piwik_TranslateException('CoreUpdater_ExceptionArchiveEmpty'));
- }
- unlink($this->pathPiwikZip);
- }
-
- private function oneClick_Verify()
- {
- $someExpectedFiles = array(
- '/config/global.ini.php',
- '/index.php',
- '/core/Piwik.php',
- '/piwik.php',
- '/plugins/API/API.php'
- );
- foreach($someExpectedFiles as $file)
- {
- if(!is_file($this->pathRootExtractedPiwik . $file))
- {
- throw new Exception(Piwik_TranslateException('CoreUpdater_ExceptionArchiveIncomplete', $file));
- }
- }
- }
-
- private function oneClick_CreateConfigFileBackup()
- {
- $configFileBefore = PIWIK_USER_PATH . '/config/global.ini.php';
- $configFileAfter = PIWIK_USER_PATH . self::CONFIG_FILE_BACKUP;
- Piwik::copy($configFileBefore, $configFileAfter);
- }
-
- private function oneClick_Copy()
- {
- /*
- * Make sure the execute bit is set for this shell script
- */
- if(!Piwik_ArchiveProcessing::isBrowserTriggerArchivingEnabled())
- {
- @chmod($this->pathRootExtractedPiwik . '/misc/cron/archive.sh', 0755);
- }
-
- /*
- * Copy all files to PIWIK_INCLUDE_PATH.
- * These files are accessed through the dispatcher.
- */
- Piwik::copyRecursive($this->pathRootExtractedPiwik, PIWIK_INCLUDE_PATH);
-
- /*
- * These files are visible in the web root and are generally
- * served directly by the web server. May be shared.
- */
- if(PIWIK_INCLUDE_PATH !== PIWIK_DOCUMENT_ROOT)
- {
- /*
- * Copy PHP files that expect to be in the document root
- */
- $specialCases = array(
- '/index.php',
- '/piwik.php',
- '/js/index.php',
- );
-
- foreach($specialCases as $file)
- {
- Piwik::copy($this->pathRootExtractedPiwik . $file, PIWIK_DOCUMENT_ROOT . $file);
- }
-
- /*
- * Copy the non-PHP files (e.g., images, css, javascript)
- */
- Piwik::copyRecursive($this->pathRootExtractedPiwik, PIWIK_DOCUMENT_ROOT, true);
- }
-
- /*
- * Config files may be user (account) specific
- */
- if(PIWIK_INCLUDE_PATH !== PIWIK_USER_PATH)
- {
- Piwik::copyRecursive($this->pathRootExtractedPiwik . '/config', PIWIK_USER_PATH . '/config');
- }
-
- Piwik::unlinkRecursive($this->pathRootExtractedPiwik, true);
-
- if(function_exists('apc_clear_cache'))
- {
- apc_clear_cache(); // clear the system (aka 'opcode') cache
- }
- }
-
- private function oneClick_Finished()
- {
- }
-
- public function index()
- {
- $language = Piwik_Common::getRequestVar('language', '');
- if(!empty($language))
- {
- Piwik_LanguagesManager::setLanguageForSession($language);
- }
- $this->runUpdaterAndExit();
- }
-
- protected function runUpdaterAndExit()
- {
- $updater = new Piwik_Updater();
+ const CONFIG_FILE_BACKUP = '/config/global.ini.auto-backup-before-update.php';
+ const PATH_TO_EXTRACT_LATEST_VERSION = '/tmp/latest/';
+
+ private $coreError = false;
+ private $warningMessages = array();
+ private $errorMessages = array();
+ private $deactivatedPlugins = array();
+
+ static protected function getLatestZipUrl($newVersion)
+ {
+ if (@Piwik_Config::getInstance()->Debug['allow_upgrades_to_beta']) {
+ return 'http://builds.piwik.org/piwik-' . $newVersion . '.zip';
+ }
+ return Piwik_Config::getInstance()->General['latest_version_url'];
+ }
+
+ public function newVersionAvailable()
+ {
+ Piwik::checkUserIsSuperUser();
+
+ $newVersion = $this->checkNewVersionIsAvailableOrDie();
+
+ $view = Piwik_View::factory('update_new_version_available');
+ $view->piwik_version = Piwik_Version::VERSION;
+ $view->piwik_new_version = $newVersion;
+ $view->piwik_latest_version_url = self::getLatestZipUrl($newVersion);
+ $view->can_auto_update = Piwik::canAutoUpdate();
+ $view->makeWritableCommands = Piwik::getAutoUpdateMakeWritableMessage();
+ echo $view->render();
+ }
+
+ public function oneClickUpdate()
+ {
+ Piwik::checkUserIsSuperUser();
+ $this->newVersion = $this->checkNewVersionIsAvailableOrDie();
+
+ Piwik::setMaxExecutionTime(0);
+
+ $url = self::getLatestZipUrl($this->newVersion);
+ $steps = array(
+ array('oneClick_Download', Piwik_Translate('CoreUpdater_DownloadingUpdateFromX', $url)),
+ array('oneClick_Unpack', Piwik_Translate('CoreUpdater_UnpackingTheUpdate')),
+ array('oneClick_Verify', Piwik_Translate('CoreUpdater_VerifyingUnpackedFiles')),
+ array('oneClick_CreateConfigFileBackup', Piwik_Translate('CoreUpdater_CreatingBackupOfConfigurationFile', self::CONFIG_FILE_BACKUP)),
+ array('oneClick_Copy', Piwik_Translate('CoreUpdater_InstallingTheLatestVersion')),
+ array('oneClick_Finished', Piwik_Translate('CoreUpdater_PiwikUpdatedSuccessfully')),
+ );
+
+ $errorMessage = false;
+ $messages = array();
+ foreach ($steps as $step) {
+ try {
+ $method = $step[0];
+ $message = $step[1];
+ $this->$method();
+ $messages[] = $message;
+ } catch (Exception $e) {
+ $errorMessage = $e->getMessage();
+ break;
+ }
+ }
+
+ // this is a magic template to trigger the Piwik_View_Update
+ $view = Piwik_View::factory(Piwik_View::COREUPDATER_ONE_CLICK_DONE);
+ $view->coreError = $errorMessage;
+ $view->feedbackMessages = $messages;
+ echo $view->render();
+ }
+
+ public function oneClickResults()
+ {
+ Piwik_API_Request::reloadAuthUsingTokenAuth($_POST);
+ Piwik::checkUserIsSuperUser();
+
+ $view = Piwik_View::factory('update_one_click_results');
+ $view->coreError = Piwik_Common::getRequestVar('error', '', 'string', $_POST);
+ $view->feedbackMessages = safe_unserialize(Piwik_Common::unsanitizeInputValue(Piwik_Common::getRequestVar('messages', '', 'string', $_POST)));
+ echo $view->render();
+ }
+
+ private function checkNewVersionIsAvailableOrDie()
+ {
+ $newVersion = Piwik_UpdateCheck::isNewestVersionAvailable();
+ if (!$newVersion) {
+ throw new Exception(Piwik_TranslateException('CoreUpdater_ExceptionAlreadyLatestVersion', Piwik_Version::VERSION));
+ }
+ return $newVersion;
+ }
+
+ private function oneClick_Download()
+ {
+ $this->pathPiwikZip = PIWIK_USER_PATH . self::PATH_TO_EXTRACT_LATEST_VERSION . 'latest.zip';
+ Piwik::checkDirectoriesWritableOrDie(array(self::PATH_TO_EXTRACT_LATEST_VERSION));
+
+ // we catch exceptions in the caller (i.e., oneClickUpdate)
+ $url = self::getLatestZipUrl($this->newVersion) . '?cb=' . $this->newVersion;
+ $fetched = Piwik_Http::fetchRemoteFile($url, $this->pathPiwikZip);
+ }
+
+ private function oneClick_Unpack()
+ {
+ $pathExtracted = PIWIK_USER_PATH . self::PATH_TO_EXTRACT_LATEST_VERSION;
+ $this->pathRootExtractedPiwik = $pathExtracted . 'piwik';
+
+ if (file_exists($this->pathRootExtractedPiwik)) {
+ Piwik::unlinkRecursive($this->pathRootExtractedPiwik, true);
+ }
+
+ $archive = Piwik_Unzip::factory('PclZip', $this->pathPiwikZip);
+
+ if (0 == ($archive_files = $archive->extract($pathExtracted))) {
+ throw new Exception(Piwik_TranslateException('CoreUpdater_ExceptionArchiveIncompatible', $archive->errorInfo()));
+ }
+
+ if (0 == count($archive_files)) {
+ throw new Exception(Piwik_TranslateException('CoreUpdater_ExceptionArchiveEmpty'));
+ }
+ unlink($this->pathPiwikZip);
+ }
+
+ private function oneClick_Verify()
+ {
+ $someExpectedFiles = array(
+ '/config/global.ini.php',
+ '/index.php',
+ '/core/Piwik.php',
+ '/piwik.php',
+ '/plugins/API/API.php'
+ );
+ foreach ($someExpectedFiles as $file) {
+ if (!is_file($this->pathRootExtractedPiwik . $file)) {
+ throw new Exception(Piwik_TranslateException('CoreUpdater_ExceptionArchiveIncomplete', $file));
+ }
+ }
+ }
+
+ private function oneClick_CreateConfigFileBackup()
+ {
+ $configFileBefore = PIWIK_USER_PATH . '/config/global.ini.php';
+ $configFileAfter = PIWIK_USER_PATH . self::CONFIG_FILE_BACKUP;
+ Piwik::copy($configFileBefore, $configFileAfter);
+ }
+
+ private function oneClick_Copy()
+ {
+ /*
+ * Make sure the execute bit is set for this shell script
+ */
+ if (!Piwik_ArchiveProcessing::isBrowserTriggerArchivingEnabled()) {
+ @chmod($this->pathRootExtractedPiwik . '/misc/cron/archive.sh', 0755);
+ }
+
+ /*
+ * Copy all files to PIWIK_INCLUDE_PATH.
+ * These files are accessed through the dispatcher.
+ */
+ Piwik::copyRecursive($this->pathRootExtractedPiwik, PIWIK_INCLUDE_PATH);
+
+ /*
+ * These files are visible in the web root and are generally
+ * served directly by the web server. May be shared.
+ */
+ if (PIWIK_INCLUDE_PATH !== PIWIK_DOCUMENT_ROOT) {
+ /*
+ * Copy PHP files that expect to be in the document root
+ */
+ $specialCases = array(
+ '/index.php',
+ '/piwik.php',
+ '/js/index.php',
+ );
+
+ foreach ($specialCases as $file) {
+ Piwik::copy($this->pathRootExtractedPiwik . $file, PIWIK_DOCUMENT_ROOT . $file);
+ }
+
+ /*
+ * Copy the non-PHP files (e.g., images, css, javascript)
+ */
+ Piwik::copyRecursive($this->pathRootExtractedPiwik, PIWIK_DOCUMENT_ROOT, true);
+ }
+
+ /*
+ * Config files may be user (account) specific
+ */
+ if (PIWIK_INCLUDE_PATH !== PIWIK_USER_PATH) {
+ Piwik::copyRecursive($this->pathRootExtractedPiwik . '/config', PIWIK_USER_PATH . '/config');
+ }
+
+ Piwik::unlinkRecursive($this->pathRootExtractedPiwik, true);
+
+ if (function_exists('apc_clear_cache')) {
+ apc_clear_cache(); // clear the system (aka 'opcode') cache
+ }
+ }
+
+ private function oneClick_Finished()
+ {
+ }
+
+ public function index()
+ {
+ $language = Piwik_Common::getRequestVar('language', '');
+ if (!empty($language)) {
+ Piwik_LanguagesManager::setLanguageForSession($language);
+ }
+ $this->runUpdaterAndExit();
+ }
+
+ protected function runUpdaterAndExit()
+ {
+ $updater = new Piwik_Updater();
$componentsWithUpdateFile = Piwik_CoreUpdater::getComponentUpdates($updater);
- if(empty($componentsWithUpdateFile))
- {
- Piwik::redirectToModule('CoreHome');
- }
-
- Piwik::setMaxExecutionTime(0);
-
- $sqlQueries = $updater->getSqlQueriesToExecute();
- if(Piwik_Common::isPhpCliMode())
- {
- $view = Piwik_View::factory('update_welcome');
- $this->doWelcomeUpdates($view, $componentsWithUpdateFile);
- echo $view->render();
-
- if(!$this->coreError
- && Piwik::getModule() == 'CoreUpdater')
- {
- $view = Piwik_View::factory('update_database_done');
- $this->doExecuteUpdates($view, $updater, $componentsWithUpdateFile);
- echo $view->render();
- }
- }
- else if(Piwik_Common::getRequestVar('updateCorePlugins', 0, 'integer') == 1)
- {
- $this->warningMessages = array();
- $view = Piwik_View::factory('update_database_done');
- $this->doExecuteUpdates($view, $updater, $componentsWithUpdateFile);
-
- if(count($sqlQueries)== 1 && !$this->coreError)
- {
- Piwik::redirectToModule('CoreHome');
- }
-
- echo $view->render();
- }
- else
- {
- $view = Piwik_View::factory('update_welcome');
- $view->queries = $sqlQueries;
- $view->isMajor = $updater->hasMajorDbUpdate();
- $this->doWelcomeUpdates($view, $componentsWithUpdateFile);
- echo $view->render();
- }
- exit;
- }
-
- private function doWelcomeUpdates($view, $componentsWithUpdateFile)
- {
- $view->new_piwik_version = Piwik_Version::VERSION;
- $view->commandUpgradePiwik = "<br /><code>php ".Piwik_Common::getPathToPiwikRoot()."/index.php -- \"module=CoreUpdater\" </code>";
- $pluginNamesToUpdate = array();
- $coreToUpdate = false;
-
- // handle case of existing database with no tables
- if(!Piwik::isInstalled())
- {
- $this->errorMessages[] = Piwik_Translate('CoreUpdater_EmptyDatabaseError', Piwik_Config::getInstance()->database['dbname']);
- $this->coreError = true;
- $currentVersion = 'N/A';
- }
- else
- {
- $this->errorMessages = array();
- try {
- $currentVersion = Piwik_GetOption('version_core');
- } catch( Exception $e) {
- $currentVersion = '<= 0.2.9';
- }
-
- foreach($componentsWithUpdateFile as $name => $filenames)
- {
- if($name == 'core')
- {
- $coreToUpdate = true;
- }
- else
- {
- $pluginNamesToUpdate[] = $name;
- }
- }
- }
-
- // check file integrity
- $integrityInfo = Piwik::getFileIntegrityInformation();
- if(isset($integrityInfo[1]))
- {
- if($integrityInfo[0] == false)
- {
- $this->warningMessages[] = '<b>'.Piwik_Translate('General_FileIntegrityWarningExplanation').'</b>';
- }
- $this->warningMessages = array_merge($this->warningMessages, array_slice($integrityInfo, 1));
- }
- Piwik::deleteAllCacheOnUpdate();
-
- $view->coreError = $this->coreError;
- $view->warningMessages = $this->warningMessages;
- $view->errorMessages = $this->errorMessages;
- $view->current_piwik_version = $currentVersion;
- $view->pluginNamesToUpdate = $pluginNamesToUpdate;
- $view->coreToUpdate = $coreToUpdate;
- }
-
- private function doExecuteUpdates($view, $updater, $componentsWithUpdateFile)
- {
- $this->loadAndExecuteUpdateFiles($updater, $componentsWithUpdateFile);
-
- Piwik::deleteAllCacheOnUpdate();
-
- $view->coreError = $this->coreError;
- $view->warningMessages = $this->warningMessages;
- $view->errorMessages = $this->errorMessages;
- $view->deactivatedPlugins = $this->deactivatedPlugins;
- }
-
- private function loadAndExecuteUpdateFiles($updater, $componentsWithUpdateFile)
- {
- // if error in any core update, show message + help message + EXIT
- // if errors in any plugins updates, show them on screen, disable plugins that errored + CONTINUE
- // if warning in any core update or in any plugins update, show message + CONTINUE
- // if no error or warning, success message + CONTINUE
- foreach($componentsWithUpdateFile as $name => $filenames)
- {
- try {
- $this->warningMessages = array_merge($this->warningMessages, $updater->update($name));
- } catch (Piwik_Updater_UpdateErrorException $e) {
- $this->errorMessages[] = $e->getMessage();
- if($name == 'core')
- {
- $this->coreError = true;
- break;
- }
- else
- {
- Piwik_PluginsManager::getInstance()->deactivatePlugin($name);
- $this->deactivatedPlugins[] = $name;
- }
- }
- }
- }
+ if (empty($componentsWithUpdateFile)) {
+ Piwik::redirectToModule('CoreHome');
+ }
+
+ Piwik::setMaxExecutionTime(0);
+
+ $sqlQueries = $updater->getSqlQueriesToExecute();
+ if (Piwik_Common::isPhpCliMode()) {
+ $view = Piwik_View::factory('update_welcome');
+ $this->doWelcomeUpdates($view, $componentsWithUpdateFile);
+ echo $view->render();
+
+ if (!$this->coreError
+ && Piwik::getModule() == 'CoreUpdater'
+ ) {
+ $view = Piwik_View::factory('update_database_done');
+ $this->doExecuteUpdates($view, $updater, $componentsWithUpdateFile);
+ echo $view->render();
+ }
+ } else if (Piwik_Common::getRequestVar('updateCorePlugins', 0, 'integer') == 1) {
+ $this->warningMessages = array();
+ $view = Piwik_View::factory('update_database_done');
+ $this->doExecuteUpdates($view, $updater, $componentsWithUpdateFile);
+
+ if (count($sqlQueries) == 1 && !$this->coreError) {
+ Piwik::redirectToModule('CoreHome');
+ }
+
+ echo $view->render();
+ } else {
+ $view = Piwik_View::factory('update_welcome');
+ $view->queries = $sqlQueries;
+ $view->isMajor = $updater->hasMajorDbUpdate();
+ $this->doWelcomeUpdates($view, $componentsWithUpdateFile);
+ echo $view->render();
+ }
+ exit;
+ }
+
+ private function doWelcomeUpdates($view, $componentsWithUpdateFile)
+ {
+ $view->new_piwik_version = Piwik_Version::VERSION;
+ $view->commandUpgradePiwik = "<br /><code>php " . Piwik_Common::getPathToPiwikRoot() . "/index.php -- \"module=CoreUpdater\" </code>";
+ $pluginNamesToUpdate = array();
+ $coreToUpdate = false;
+
+ // handle case of existing database with no tables
+ if (!Piwik::isInstalled()) {
+ $this->errorMessages[] = Piwik_Translate('CoreUpdater_EmptyDatabaseError', Piwik_Config::getInstance()->database['dbname']);
+ $this->coreError = true;
+ $currentVersion = 'N/A';
+ } else {
+ $this->errorMessages = array();
+ try {
+ $currentVersion = Piwik_GetOption('version_core');
+ } catch (Exception $e) {
+ $currentVersion = '<= 0.2.9';
+ }
+
+ foreach ($componentsWithUpdateFile as $name => $filenames) {
+ if ($name == 'core') {
+ $coreToUpdate = true;
+ } else {
+ $pluginNamesToUpdate[] = $name;
+ }
+ }
+ }
+
+ // check file integrity
+ $integrityInfo = Piwik::getFileIntegrityInformation();
+ if (isset($integrityInfo[1])) {
+ if ($integrityInfo[0] == false) {
+ $this->warningMessages[] = '<b>' . Piwik_Translate('General_FileIntegrityWarningExplanation') . '</b>';
+ }
+ $this->warningMessages = array_merge($this->warningMessages, array_slice($integrityInfo, 1));
+ }
+ Piwik::deleteAllCacheOnUpdate();
+
+ $view->coreError = $this->coreError;
+ $view->warningMessages = $this->warningMessages;
+ $view->errorMessages = $this->errorMessages;
+ $view->current_piwik_version = $currentVersion;
+ $view->pluginNamesToUpdate = $pluginNamesToUpdate;
+ $view->coreToUpdate = $coreToUpdate;
+ }
+
+ private function doExecuteUpdates($view, $updater, $componentsWithUpdateFile)
+ {
+ $this->loadAndExecuteUpdateFiles($updater, $componentsWithUpdateFile);
+
+ Piwik::deleteAllCacheOnUpdate();
+
+ $view->coreError = $this->coreError;
+ $view->warningMessages = $this->warningMessages;
+ $view->errorMessages = $this->errorMessages;
+ $view->deactivatedPlugins = $this->deactivatedPlugins;
+ }
+
+ private function loadAndExecuteUpdateFiles($updater, $componentsWithUpdateFile)
+ {
+ // if error in any core update, show message + help message + EXIT
+ // if errors in any plugins updates, show them on screen, disable plugins that errored + CONTINUE
+ // if warning in any core update or in any plugins update, show message + CONTINUE
+ // if no error or warning, success message + CONTINUE
+ foreach ($componentsWithUpdateFile as $name => $filenames) {
+ try {
+ $this->warningMessages = array_merge($this->warningMessages, $updater->update($name));
+ } catch (Piwik_Updater_UpdateErrorException $e) {
+ $this->errorMessages[] = $e->getMessage();
+ if ($name == 'core') {
+ $this->coreError = true;
+ break;
+ } else {
+ Piwik_PluginsManager::getInstance()->deactivatePlugin($name);
+ $this->deactivatedPlugins[] = $name;
+ }
+ }
+ }
+ }
}
diff --git a/plugins/CoreUpdater/CoreUpdater.php b/plugins/CoreUpdater/CoreUpdater.php
index 60168b83e7..30a28f76a6 100644
--- a/plugins/CoreUpdater/CoreUpdater.php
+++ b/plugins/CoreUpdater/CoreUpdater.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_CoreUpdater
*/
@@ -15,75 +15,69 @@
*/
class Piwik_CoreUpdater extends Piwik_Plugin
{
- public function getInformation()
- {
- return array(
- 'description' => Piwik_Translate('CoreUpdater_PluginDescription'),
- 'author' => 'Piwik',
- 'author_homepage' => 'http://piwik.org/',
- 'version' => Piwik_Version::VERSION,
- );
- }
+ public function getInformation()
+ {
+ return array(
+ 'description' => Piwik_Translate('CoreUpdater_PluginDescription'),
+ 'author' => 'Piwik',
+ 'author_homepage' => 'http://piwik.org/',
+ 'version' => Piwik_Version::VERSION,
+ );
+ }
+
+ function getListHooksRegistered()
+ {
+ $hooks = array(
+ 'FrontController.dispatchCoreAndPluginUpdatesScreen' => 'dispatch',
+ 'FrontController.checkForUpdates' => 'updateCheck',
+ );
+ return $hooks;
+ }
+
+ public static function getComponentUpdates($updater)
+ {
+ $updater->addComponentToCheck('core', Piwik_Version::VERSION);
+ $plugins = Piwik_PluginsManager::getInstance()->getLoadedPlugins();
+ foreach ($plugins as $pluginName => $plugin) {
+ $updater->addComponentToCheck($pluginName, $plugin->getVersion());
+ }
+
+ $componentsWithUpdateFile = $updater->getComponentsWithUpdateFile();
+ if (count($componentsWithUpdateFile) == 0 && !$updater->hasNewVersion('core')) {
+ return null;
+ }
- function getListHooksRegistered()
- {
- $hooks = array(
- 'FrontController.dispatchCoreAndPluginUpdatesScreen' => 'dispatch',
- 'FrontController.checkForUpdates' => 'updateCheck',
- );
- return $hooks;
- }
+ return $componentsWithUpdateFile;
+ }
- public static function getComponentUpdates($updater)
- {
- $updater->addComponentToCheck('core', Piwik_Version::VERSION);
- $plugins = Piwik_PluginsManager::getInstance()->getLoadedPlugins();
- foreach($plugins as $pluginName => $plugin)
- {
- $updater->addComponentToCheck($pluginName, $plugin->getVersion());
- }
-
- $componentsWithUpdateFile = $updater->getComponentsWithUpdateFile();
- if(count($componentsWithUpdateFile) == 0 && !$updater->hasNewVersion('core'))
- {
- return null;
- }
+ function dispatch()
+ {
+ $module = Piwik_Common::getRequestVar('module', '', 'string');
+ $action = Piwik_Common::getRequestVar('action', '', 'string');
- return $componentsWithUpdateFile;
- }
-
- function dispatch()
- {
- $module = Piwik_Common::getRequestVar('module', '', 'string');
- $action = Piwik_Common::getRequestVar('action', '', 'string');
-
- $updater = new Piwik_Updater();
- $updater->addComponentToCheck('core', Piwik_Version::VERSION);
- $updates = $updater->getComponentsWithNewVersion();
- if(!empty($updates))
- {
- Piwik::deleteAllCacheOnUpdate();
- }
- if(self::getComponentUpdates($updater) !== null
- && $module != 'CoreUpdater'
- // Proxy module is used to redirect users to piwik.org, should still work when Piwik must be updated
- && $module != 'Proxy'
- && !($module == 'LanguagesManager'
- && $action == 'saveLanguage'))
- {
- if(Piwik_FrontController::shouldRethrowException())
- {
- throw new Exception("Piwik and/or some plugins have been upgraded to a new version. Please run the update process first. See documentation: http://piwik.org/docs/update/");
- }
- else
- {
- Piwik::redirectToModule('CoreUpdater');
- }
- }
- }
+ $updater = new Piwik_Updater();
+ $updater->addComponentToCheck('core', Piwik_Version::VERSION);
+ $updates = $updater->getComponentsWithNewVersion();
+ if (!empty($updates)) {
+ Piwik::deleteAllCacheOnUpdate();
+ }
+ if (self::getComponentUpdates($updater) !== null
+ && $module != 'CoreUpdater'
+ // Proxy module is used to redirect users to piwik.org, should still work when Piwik must be updated
+ && $module != 'Proxy'
+ && !($module == 'LanguagesManager'
+ && $action == 'saveLanguage')
+ ) {
+ if (Piwik_FrontController::shouldRethrowException()) {
+ throw new Exception("Piwik and/or some plugins have been upgraded to a new version. Please run the update process first. See documentation: http://piwik.org/docs/update/");
+ } else {
+ Piwik::redirectToModule('CoreUpdater');
+ }
+ }
+ }
- function updateCheck()
- {
- Piwik_UpdateCheck::check();
- }
+ function updateCheck()
+ {
+ Piwik_UpdateCheck::check();
+ }
}
diff --git a/plugins/CoreUpdater/templates/cli_update_database_done.tpl b/plugins/CoreUpdater/templates/cli_update_database_done.tpl
index 19b5554b3b..099960be4d 100644
--- a/plugins/CoreUpdater/templates/cli_update_database_done.tpl
+++ b/plugins/CoreUpdater/templates/cli_update_database_done.tpl
@@ -1,64 +1,64 @@
{textformat}
-{assign var='helpMessage' value='CoreUpdater_HelpMessageContent'|translate:'[':']':"\n\n* "|unescape}
+ {assign var='helpMessage' value='CoreUpdater_HelpMessageContent'|translate:'[':']':"\n\n* "|unescape}
-{if $coreError}
- [X] {'CoreUpdater_CriticalErrorDuringTheUpgradeProcess'|translate|unescape}
+ {if $coreError}
+ [X] {'CoreUpdater_CriticalErrorDuringTheUpgradeProcess'|translate|unescape}
- {foreach from=$errorMessages item=message}
- * {$message}
+ {foreach from=$errorMessages item=message}
+ * {$message}
- {/foreach}
+ {/foreach}
- {'CoreUpdater_HelpMessageIntroductionWhenError'|translate|unescape}
+ {'CoreUpdater_HelpMessageIntroductionWhenError'|translate|unescape}
- * {$helpMessage}
+ * {$helpMessage}
- {'CoreUpdater_ErrorDIYHelp'|translate}
+ {'CoreUpdater_ErrorDIYHelp'|translate}
- * {'CoreUpdater_ErrorDIYHelp_1'|translate}
+ * {'CoreUpdater_ErrorDIYHelp_1'|translate}
- * {'CoreUpdater_ErrorDIYHelp_2'|translate}
+ * {'CoreUpdater_ErrorDIYHelp_2'|translate}
- * {'CoreUpdater_ErrorDIYHelp_3'|translate}
+ * {'CoreUpdater_ErrorDIYHelp_3'|translate}
- * {'CoreUpdater_ErrorDIYHelp_4'|translate}
+ * {'CoreUpdater_ErrorDIYHelp_4'|translate}
- * {'CoreUpdater_ErrorDIYHelp_5'|translate}
+ * {'CoreUpdater_ErrorDIYHelp_5'|translate}
-{else}
- {if count($warningMessages) > 0}
- [!] {'CoreUpdater_WarningMessages'|translate|unescape}
+ {else}
+ {if count($warningMessages) > 0}
+ [!] {'CoreUpdater_WarningMessages'|translate|unescape}
- {foreach from=$warningMessages item=message}
- * {$message}
+ {foreach from=$warningMessages item=message}
+ * {$message}
- {/foreach}
- {/if}
-
- {if count($errorMessages) > 0}
- [X] {'CoreUpdater_ErrorDuringPluginsUpdates'|translate|unescape}
+ {/foreach}
+ {/if}
- {foreach from=$errorMessages item=message}
- * {$message}
+ {if count($errorMessages) > 0}
+ [X] {'CoreUpdater_ErrorDuringPluginsUpdates'|translate|unescape}
- {/foreach}
-
- {if isset($deactivatedPlugins) && count($deactivatedPlugins) > 0}
- {assign var=listOfDeactivatedPlugins value=$deactivatedPlugins|@implode:', '}
- [!] {'CoreUpdater_WeAutomaticallyDeactivatedTheFollowingPlugins'|translate:$listOfDeactivatedPlugins|unescape}
+ {foreach from=$errorMessages item=message}
+ * {$message}
- {/if}
- {/if}
- {if count($errorMessages) > 0 || count($warningMessages) > 0}
- {'CoreUpdater_HelpMessageIntroductionWhenWarning'|translate|unescape}
+ {/foreach}
- * {$helpMessage}
- {else}
- {'CoreUpdater_PiwikHasBeenSuccessfullyUpgraded'|translate|unescape}
+ {if isset($deactivatedPlugins) && count($deactivatedPlugins) > 0}
+ {assign var=listOfDeactivatedPlugins value=$deactivatedPlugins|@implode:', '}
+ [!] {'CoreUpdater_WeAutomaticallyDeactivatedTheFollowingPlugins'|translate:$listOfDeactivatedPlugins|unescape}
- {/if}
-{/if}
+ {/if}
+ {/if}
+ {if count($errorMessages) > 0 || count($warningMessages) > 0}
+ {'CoreUpdater_HelpMessageIntroductionWhenWarning'|translate|unescape}
+
+ * {$helpMessage}
+ {else}
+ {'CoreUpdater_PiwikHasBeenSuccessfullyUpgraded'|translate|unescape}
+
+ {/if}
+ {/if}
{/textformat}
diff --git a/plugins/CoreUpdater/templates/cli_update_welcome.tpl b/plugins/CoreUpdater/templates/cli_update_welcome.tpl
index 1e4afc0da2..b3802e024a 100644
--- a/plugins/CoreUpdater/templates/cli_update_welcome.tpl
+++ b/plugins/CoreUpdater/templates/cli_update_welcome.tpl
@@ -1,38 +1,38 @@
{assign var='helpMessage' value='CoreUpdater_HelpMessageContent'|translate:'[':']':"\n\n* "|unescape}
{textformat}
-*** {'CoreUpdater_UpdateTitle'|translate|unescape} ***
+ *** {'CoreUpdater_UpdateTitle'|translate|unescape} ***
-{if $coreError}
- [X] {'CoreUpdater_CriticalErrorDuringTheUpgradeProcess'|translate|unescape}
+ {if $coreError}
+ [X] {'CoreUpdater_CriticalErrorDuringTheUpgradeProcess'|translate|unescape}
- {foreach from=$errorMessages item=message}
- * {$message}
+ {foreach from=$errorMessages item=message}
+ * {$message}
- {/foreach}
+ {/foreach}
- {'CoreUpdater_HelpMessageIntroductionWhenError'|translate|unescape}
+ {'CoreUpdater_HelpMessageIntroductionWhenError'|translate|unescape}
- * {$helpMessage}
+ * {$helpMessage}
-{else}
- {if $coreToUpdate || count($pluginNamesToUpdate) > 0}
- {'CoreUpdater_DatabaseUpgradeRequired'|translate|unescape}
+ {else}
+ {if $coreToUpdate || count($pluginNamesToUpdate) > 0}
+ {'CoreUpdater_DatabaseUpgradeRequired'|translate|unescape}
- {'CoreUpdater_YourDatabaseIsOutOfDate'|translate|unescape}
+ {'CoreUpdater_YourDatabaseIsOutOfDate'|translate|unescape}
- {if $coreToUpdate}
- {'CoreUpdater_PiwikWillBeUpgradedFromVersionXToVersionY'|translate:$current_piwik_version:$new_piwik_version|unescape}
+ {if $coreToUpdate}
+ {'CoreUpdater_PiwikWillBeUpgradedFromVersionXToVersionY'|translate:$current_piwik_version:$new_piwik_version|unescape}
- {/if}
- {if count($pluginNamesToUpdate) > 0}
- {assign var=listOfPlugins value=$pluginNamesToUpdate|@implode:', '}
- {'CoreUpdater_TheFollowingPluginsWillBeUpgradedX'|translate:$listOfPlugins|unescape}
+ {/if}
+ {if count($pluginNamesToUpdate) > 0}
+ {assign var=listOfPlugins value=$pluginNamesToUpdate|@implode:', '}
+ {'CoreUpdater_TheFollowingPluginsWillBeUpgradedX'|translate:$listOfPlugins|unescape}
- {/if}
- {'CoreUpdater_TheUpgradeProcessMayTakeAWhilePleaseBePatient'|translate|unescape}
+ {/if}
+ {'CoreUpdater_TheUpgradeProcessMayTakeAWhilePleaseBePatient'|translate|unescape}
- {/if}
-{/if}
+ {/if}
+ {/if}
{/textformat}
diff --git a/plugins/CoreUpdater/templates/header.tpl b/plugins/CoreUpdater/templates/header.tpl
index 65670a7728..e0023e6389 100644
--- a/plugins/CoreUpdater/templates/header.tpl
+++ b/plugins/CoreUpdater/templates/header.tpl
@@ -1,42 +1,44 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
- "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
- <title>Piwik &rsaquo; {'CoreUpdater_UpdateTitle'|translate}</title>
- <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
- <link rel="shortcut icon" href="plugins/CoreHome/templates/images/favicon.ico" />
+ <title>Piwik &rsaquo; {'CoreUpdater_UpdateTitle'|translate}</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
+ <link rel="shortcut icon" href="plugins/CoreHome/templates/images/favicon.ico"/>
- <link rel="stylesheet" type="text/css" href="themes/default/simple_structure.css" />
- <link rel="stylesheet" type="text/css" href="libs/jquery/themes/base/jquery-ui.css" />
- <link rel="stylesheet" type="text/css" href="themes/default/styles.css" />
- <link rel="stylesheet" type="text/css" href="plugins/CoreHome/templates/donate.css"></link>
- <link rel="stylesheet" type="text/css" href="plugins/CoreHome/templates/jquery.ui.autocomplete.css"></link>
-{literal}
-<style type="text/css">
-* {
- margin: 0;
- padding: 0;
-}
-.topBarElem {
- font-family:arial,sans-serif !important;
- font-size:13px;
- line-height:1.33;
-}
+ <link rel="stylesheet" type="text/css" href="themes/default/simple_structure.css"/>
+ <link rel="stylesheet" type="text/css" href="libs/jquery/themes/base/jquery-ui.css"/>
+ <link rel="stylesheet" type="text/css" href="themes/default/styles.css"/>
+ <link rel="stylesheet" type="text/css" href="plugins/CoreHome/templates/donate.css"></link>
+ <link rel="stylesheet" type="text/css" href="plugins/CoreHome/templates/jquery.ui.autocomplete.css"></link>
+ {literal}
+ <style type="text/css">
+ * {
+ margin: 0;
+ padding: 0;
+ }
-#donate-form-container {
- margin: 0 0 2em 2em;
-}
-</style>
-{/literal}
+ .topBarElem {
+ font-family: arial, sans-serif !important;
+ font-size: 13px;
+ line-height: 1.33;
+ }
- <script type="text/javascript" src="libs/jquery/jquery.js"></script>
- <script type="text/javascript" src="libs/jquery/jquery-ui.js"></script>
- <script type="text/javascript" src="plugins/CoreHome/templates/donate.js"></script>
-{if 'General_LayoutDirection'|translate =='rtl'}
-<link rel="stylesheet" type="text/css" href="themes/default/rtl.css" />
-{/if}
-{loadJavascriptTranslations plugins='CoreHome'}
+ #donate-form-container {
+ margin: 0 0 2em 2em;
+ }
+ </style>
+ {/literal}
+
+ <script type="text/javascript" src="libs/jquery/jquery.js"></script>
+ <script type="text/javascript" src="libs/jquery/jquery-ui.js"></script>
+ <script type="text/javascript" src="plugins/CoreHome/templates/donate.js"></script>
+ {if 'General_LayoutDirection'|translate =='rtl'}
+ <link rel="stylesheet" type="text/css" href="themes/default/rtl.css"/>
+ {/if}
+ {loadJavascriptTranslations plugins='CoreHome'}
</head>
<body id="simple">
<div id="contentsimple">
- <div id="title"><img title='Piwik' alt="Piwik" src='themes/default/images/logo-header.png' style='margin-left:10px' /><span id="subh1"> # {'General_OpenSourceWebAnalytics'|translate}</span></div>
+ <div id="title"><img title='Piwik' alt="Piwik" src='themes/default/images/logo-header.png' style='margin-left:10px'/><span
+ id="subh1"> # {'General_OpenSourceWebAnalytics'|translate}</span></div>
diff --git a/plugins/CoreUpdater/templates/update_database_done.tpl b/plugins/CoreUpdater/templates/update_database_done.tpl
index 649321c57f..0ab5d72517 100644
--- a/plugins/CoreUpdater/templates/update_database_done.tpl
+++ b/plugins/CoreUpdater/templates/update_database_done.tpl
@@ -2,64 +2,75 @@
{assign var='helpMessage' value='CoreUpdater_HelpMessageContent'|translate:'<a target="_blank" href="?module=Proxy&action=redirect&url=http://piwik.org/faq/">':'</a>':'</li><li>'}
{if $coreError}
- <br /><br />
- <div class="error">
- <img src="themes/default/images/error_medium.png" /> {'CoreUpdater_CriticalErrorDuringTheUpgradeProcess'|translate}
- {foreach from=$errorMessages item=message}
- <pre>{$message}</pre><br />
- {/foreach}
- </div>
- <br />
- <p>{'CoreUpdater_HelpMessageIntroductionWhenError'|translate}
- <ul><li>{$helpMessage}</li></ul></p>
-
- <p>{'CoreUpdater_ErrorDIYHelp'|translate}
- <ul><li>{'CoreUpdater_ErrorDIYHelp_1'|translate}</li>
- <li>{'CoreUpdater_ErrorDIYHelp_2'|translate}</li>
- <li>{'CoreUpdater_ErrorDIYHelp_3'|translate}</li>
- <li>{'CoreUpdater_ErrorDIYHelp_4'|translate}</li>
- <li>{'CoreUpdater_ErrorDIYHelp_5'|translate}</li></ul></p>
+ <br/>
+ <br/>
+ <div class="error">
+ <img src="themes/default/images/error_medium.png"/> {'CoreUpdater_CriticalErrorDuringTheUpgradeProcess'|translate}
+ {foreach from=$errorMessages item=message}
+ <pre>{$message}</pre>
+ <br/>
+ {/foreach}
+ </div>
+ <br/>
+ <p>{'CoreUpdater_HelpMessageIntroductionWhenError'|translate}
+ <ul>
+ <li>{$helpMessage}</li>
+ </ul>
+ </p>
+ <p>{'CoreUpdater_ErrorDIYHelp'|translate}
+ <ul>
+ <li>{'CoreUpdater_ErrorDIYHelp_1'|translate}</li>
+ <li>{'CoreUpdater_ErrorDIYHelp_2'|translate}</li>
+ <li>{'CoreUpdater_ErrorDIYHelp_3'|translate}</li>
+ <li>{'CoreUpdater_ErrorDIYHelp_4'|translate}</li>
+ <li>{'CoreUpdater_ErrorDIYHelp_5'|translate}</li>
+ </ul>
+ </p>
{else}
-
- {if count($warningMessages) > 0}
- <div class="warning">
- <p><img src="themes/default/images/warning_medium.png" /> {'CoreUpdater_WarningMessages'|translate}</p>
- {foreach from=$warningMessages item=message}
- <pre>{$message}</pre><br />
- {/foreach}
- </div>
- {/if}
-
- {if count($errorMessages) > 0}
- <div class="warning">
- <p><img src="themes/default/images/error_medium.png" /> {'CoreUpdater_ErrorDuringPluginsUpdates'|translate}</p>
- {foreach from=$errorMessages item=message}
- <pre>{$message}</pre><br />
- {/foreach}
-
- {if isset($deactivatedPlugins) && count($deactivatedPlugins) > 0}
- {assign var=listOfDeactivatedPlugins value=$deactivatedPlugins|@implode:', '}
- <p style="color:red"><img src="themes/default/images/error_medium.png" /> {'CoreUpdater_WeAutomaticallyDeactivatedTheFollowingPlugins'|translate:$listOfDeactivatedPlugins}</p>
- {/if}
- </div>
- {/if}
-
- {if count($errorMessages) > 0 || count($warningMessages) > 0}
- <br />
- <p>{'CoreUpdater_HelpMessageIntroductionWhenWarning'|translate}
- <ul><li>{$helpMessage}</li></ul>
- </p>
- {else}
- <p class="success">{'CoreUpdater_PiwikHasBeenSuccessfullyUpgraded'|translate}</p>
-
- <div id="donate-form-container">
- {include file="CoreHome/templates/donate.tpl"}
- </div>
- {/if}
- <form action="index.php">
- <input type="submit" class="submit" value="{'CoreUpdater_ContinueToPiwik'|translate}" />
- </form>
+ {if count($warningMessages) > 0}
+ <div class="warning">
+ <p><img src="themes/default/images/warning_medium.png"/> {'CoreUpdater_WarningMessages'|translate}</p>
+ {foreach from=$warningMessages item=message}
+ <pre>{$message}</pre>
+ <br/>
+ {/foreach}
+ </div>
+ {/if}
+
+ {if count($errorMessages) > 0}
+ <div class="warning">
+ <p><img src="themes/default/images/error_medium.png"/> {'CoreUpdater_ErrorDuringPluginsUpdates'|translate}</p>
+ {foreach from=$errorMessages item=message}
+ <pre>{$message}</pre>
+ <br/>
+ {/foreach}
+
+ {if isset($deactivatedPlugins) && count($deactivatedPlugins) > 0}
+ {assign var=listOfDeactivatedPlugins value=$deactivatedPlugins|@implode:', '}
+ <p style="color:red"><img
+ src="themes/default/images/error_medium.png"/> {'CoreUpdater_WeAutomaticallyDeactivatedTheFollowingPlugins'|translate:$listOfDeactivatedPlugins}
+ </p>
+ {/if}
+ </div>
+ {/if}
+
+ {if count($errorMessages) > 0 || count($warningMessages) > 0}
+ <br/>
+ <p>{'CoreUpdater_HelpMessageIntroductionWhenWarning'|translate}
+ <ul>
+ <li>{$helpMessage}</li>
+ </ul>
+ </p>
+ {else}
+ <p class="success">{'CoreUpdater_PiwikHasBeenSuccessfullyUpgraded'|translate}</p>
+ <div id="donate-form-container">
+ {include file="CoreHome/templates/donate.tpl"}
+ </div>
+ {/if}
+ <form action="index.php">
+ <input type="submit" class="submit" value="{'CoreUpdater_ContinueToPiwik'|translate}"/>
+ </form>
{/if}
{include file="CoreUpdater/templates/footer.tpl"}
diff --git a/plugins/CoreUpdater/templates/update_new_version_available.tpl b/plugins/CoreUpdater/templates/update_new_version_available.tpl
index d8be6033a6..15541aa48d 100644
--- a/plugins/CoreUpdater/templates/update_new_version_available.tpl
+++ b/plugins/CoreUpdater/templates/update_new_version_available.tpl
@@ -4,25 +4,26 @@
<p><b>{'CoreUpdater_ThereIsNewVersionAvailableForUpdate'|translate}</b></p>
{if $can_auto_update}
- <p>{'CoreUpdater_YouCanUpgradeAutomaticallyOrDownloadPackage'|translate:$piwik_new_version}</p>
+ <p>{'CoreUpdater_YouCanUpgradeAutomaticallyOrDownloadPackage'|translate:$piwik_new_version}</p>
{else}
- <p>{'Installation_SystemCheckAutoUpdateHelp'|translate}</p>
- <p>{'CoreUpdater_YouMustDownloadPackageOrFixPermissions'|translate:$piwik_new_version}
- {$makeWritableCommands}
- </p>
+ <p>{'Installation_SystemCheckAutoUpdateHelp'|translate}</p>
+ <p>{'CoreUpdater_YouMustDownloadPackageOrFixPermissions'|translate:$piwik_new_version}
+ {$makeWritableCommands}
+ </p>
{/if}
{if $can_auto_update}
- <form action="index.php">
- <input type="hidden" name="module" value="CoreUpdater" />
- <input type="hidden" name="action" value="oneClickUpdate" />
- <input type="submit" class="submit" value="{'CoreUpdater_UpdateAutomatically'|translate}" />
+<form action="index.php">
+ <input type="hidden" name="module" value="CoreUpdater"/>
+ <input type="hidden" name="action" value="oneClickUpdate"/>
+ <input type="submit" class="submit" value="{'CoreUpdater_UpdateAutomatically'|translate}"/>
+ {/if}
+ <a style="margin-left:50px" class="submit button"
+ href="{$piwik_latest_version_url}?cb={$piwik_new_version}">{'CoreUpdater_DownloadX'|translate:$piwik_new_version}</a><br/>
+ {if $can_auto_update}
+</form>
{/if}
- <a style="margin-left:50px" class="submit button" href="{$piwik_latest_version_url}?cb={$piwik_new_version}">{'CoreUpdater_DownloadX'|translate:$piwik_new_version}</a><br />
-{if $can_auto_update}
- </form>
-{/if}
-<br />
+<br/>
<a href='index.php'>&laquo; {'General_BackToPiwik'|translate}</a>
{include file="CoreUpdater/templates/footer.tpl"}
diff --git a/plugins/CoreUpdater/templates/update_one_click_results.tpl b/plugins/CoreUpdater/templates/update_one_click_results.tpl
index db463db066..7ba1bf1627 100644
--- a/plugins/CoreUpdater/templates/update_one_click_results.tpl
+++ b/plugins/CoreUpdater/templates/update_one_click_results.tpl
@@ -2,18 +2,23 @@
<br/>
{foreach from=$feedbackMessages item=message}
- <p>{$message|escape:'html'}</p>
+ <p>{$message|escape:'html'}</p>
{/foreach}
{if $coreError}
- <br /><br />
- <div class="error"><img src="themes/default/images/error_medium.png" /> {$coreError|escape:'html'}</div>
- <br /><br />
- <div class="warning"><img src="themes/default/images/warning_medium.png" /> {'CoreUpdater_UpdateHasBeenCancelledExplanation'|translate:"<br /><br />":"<a target='_blank' href='?module=Proxy&action=redirect&url=http://piwik.org/docs/update/'>":"</a>"}</div>
- <br /><br />
+ <br/>
+ <br/>
+ <div class="error"><img src="themes/default/images/error_medium.png"/> {$coreError|escape:'html'}</div>
+ <br/>
+ <br/>
+ <div class="warning"><img
+ src="themes/default/images/warning_medium.png"/> {'CoreUpdater_UpdateHasBeenCancelledExplanation'|translate:"<br /><br />":"<a target='_blank' href='?module=Proxy&action=redirect&url=http://piwik.org/docs/update/'>":"</a>"}
+ </div>
+ <br/>
+ <br/>
{/if}
<form action="index.php">
-<input type="submit" class="submit" value="{'CoreUpdater_ContinueToPiwik'|translate}" />
+ <input type="submit" class="submit" value="{'CoreUpdater_ContinueToPiwik'|translate}"/>
</form>
{include file="CoreUpdater/templates/footer.tpl"}
diff --git a/plugins/CoreUpdater/templates/update_welcome.tpl b/plugins/CoreUpdater/templates/update_welcome.tpl
index 434b8ad99c..aeaa73c627 100644
--- a/plugins/CoreUpdater/templates/update_welcome.tpl
+++ b/plugins/CoreUpdater/templates/update_welcome.tpl
@@ -3,116 +3,124 @@
{assign var='helpMessage' value='CoreUpdater_HelpMessageContent'|translate:'<a target="_blank" href="?module=Proxy&action=redirect&url=http://piwik.org/faq/">':'</a>':'</li><li>'}
{if $coreError}
- <br /><br />
- <div class="error">
- <img src="themes/default/images/error_medium.png" /> {'CoreUpdater_CriticalErrorDuringTheUpgradeProcess'|translate}
- {foreach from=$errorMessages item=message}
- <pre>{$message}</pre>
- {/foreach}
- </div>
- <br />
- <p>{'CoreUpdater_HelpMessageIntroductionWhenError'|translate}
- <ul><li>{$helpMessage}</li></ul></p>
+ <br/>
+ <br/>
+ <div class="error">
+ <img src="themes/default/images/error_medium.png"/> {'CoreUpdater_CriticalErrorDuringTheUpgradeProcess'|translate}
+ {foreach from=$errorMessages item=message}
+ <pre>{$message}</pre>
+ {/foreach}
+ </div>
+ <br/>
+ <p>{'CoreUpdater_HelpMessageIntroductionWhenError'|translate}
+ <ul>
+ <li>{$helpMessage}</li>
+ </ul>
+ </p>
{else}
- {if $coreToUpdate || count($pluginNamesToUpdate) > 0}
- <p style='font-size:110%;padding-top:1em;'><b id='titleUpdate'>{'CoreUpdater_DatabaseUpgradeRequired'|translate}</b></p>
- <p>{'CoreUpdater_YourDatabaseIsOutOfDate'|translate}</p>
+ {if $coreToUpdate || count($pluginNamesToUpdate) > 0}
+ <p style='font-size:110%;padding-top:1em;'><b id='titleUpdate'>{'CoreUpdater_DatabaseUpgradeRequired'|translate}</b></p>
+ <p>{'CoreUpdater_YourDatabaseIsOutOfDate'|translate}</p>
+ {if $coreToUpdate}
+ <p>{'CoreUpdater_PiwikWillBeUpgradedFromVersionXToVersionY'|translate:$current_piwik_version:$new_piwik_version}</p>
+ {/if}
- {if $coreToUpdate}
- <p>{'CoreUpdater_PiwikWillBeUpgradedFromVersionXToVersionY'|translate:$current_piwik_version:$new_piwik_version}</p>
- {/if}
+ {if count($pluginNamesToUpdate) > 0}
+ {assign var=listOfPlugins value=$pluginNamesToUpdate|@implode:', '}
+ <p>{'CoreUpdater_TheFollowingPluginsWillBeUpgradedX'|translate:$listOfPlugins}</p>
+ {/if}
+ <h3 id='titleUpdate'>{'CoreUpdater_NoteForLargePiwikInstances'|translate}</h3>
+ {if $isMajor}
+ <p class="warning normalFontSize">
+ {'CoreUpdater_MajorUpdateWarning1'|translate}<br/>
+ {'CoreUpdater_MajorUpdateWarning2'|translate}
+ </p>
+ {/if}
+ <ul>
+ <li>{'CoreUpdater_TheUpgradeProcessMayFailExecuteCommand'|translate:$commandUpgradePiwik}</li>
+ <li>It is also recommended for high traffic Piwik servers to <a target='_blank'
+ href='?module=Proxy&action=redirect&url={"http://piwik.org/faq/how-to/#faq_111"|escape:"url"}'>momentarily
+ disable visitor Tracking and put the Piwik User Interface in maintenance mode</a>.
+ </li>
+ <li>{'CoreUpdater_YouCouldManuallyExecuteSqlQueries'|translate}<br/>
+ <a href='#' id='showSql' style='margin-left:20px'>› {'CoreUpdater_ClickHereToViewSqlQueries'|translate}</a>
- {if count($pluginNamesToUpdate) > 0}
- {assign var=listOfPlugins value=$pluginNamesToUpdate|@implode:', '}
- <p>{'CoreUpdater_TheFollowingPluginsWillBeUpgradedX'|translate:$listOfPlugins}</p>
- {/if}
+ <div id='sqlQueries' style='display:none'>
+ <br/>
+ <code>
+ # {'CoreUpdater_NoteItIsExpectedThatQueriesFail'|translate}<br/><br/>
+ {foreach from=$queries item=query}&nbsp;&nbsp;&nbsp;{$query}
+ <br/>
+ {/foreach}
+ </code>
+ </div>
+ </li>
+ </ul>
+ <br/>
+ <br/>
+ <h4 id='titleUpdate'>{'CoreUpdater_ReadyToGo'|translate}</h4>
+ <p>{'CoreUpdater_TheUpgradeProcessMayTakeAWhilePleaseBePatient'|translate}</p>
+ {/if}
- <h3 id='titleUpdate'>{'CoreUpdater_NoteForLargePiwikInstances'|translate}</h3>
- {if $isMajor}
- <p class="warning normalFontSize">
- {'CoreUpdater_MajorUpdateWarning1'|translate}<br />
- {'CoreUpdater_MajorUpdateWarning2'|translate}
- </p>
- {/if}
- <ul>
- <li>{'CoreUpdater_TheUpgradeProcessMayFailExecuteCommand'|translate:$commandUpgradePiwik}</li>
- <li>It is also recommended for high traffic Piwik servers to <a target='_blank' href='?module=Proxy&action=redirect&url={"http://piwik.org/faq/how-to/#faq_111"|escape:"url"}'>momentarily disable visitor Tracking and put the Piwik User Interface in maintenance mode</a>.</li>
- <li>{'CoreUpdater_YouCouldManuallyExecuteSqlQueries'|translate}<br />
- <a href='#' id='showSql' style='margin-left:20px'>› {'CoreUpdater_ClickHereToViewSqlQueries'|translate}</a>
- <div id='sqlQueries' style='display:none'>
- <br />
- <code>
- # {'CoreUpdater_NoteItIsExpectedThatQueriesFail'|translate}<br /><br />
- {foreach from=$queries item=query}&nbsp;&nbsp;&nbsp;{$query}<br />
- {/foreach}
- </code>
- </div>
- </li>
- </ul>
- <br /><br />
- <h4 id='titleUpdate'>{'CoreUpdater_ReadyToGo'|translate}</h4>
- <p>{'CoreUpdater_TheUpgradeProcessMayTakeAWhilePleaseBePatient'|translate}</p>
- {/if}
+ {if count($warningMessages) > 0}
+ <p><i>{$warningMessages[0]}</i>
+ {if count($warningMessages) > 1}
+ <button id="more-results" class="ui-button ui-state-default ui-corner-all">{'General_Details'|translate}</button>
+ {/if}
+ </p>
+ {/if}
- {if count($warningMessages) > 0}
- <p><i>{$warningMessages[0]}</i>
- {if count($warningMessages) > 1}
- <button id="more-results" class="ui-button ui-state-default ui-corner-all">{'General_Details'|translate}</button>
- {/if}
- </p>
- {/if}
-
- {if $coreToUpdate || count($pluginNamesToUpdate) > 0}
- <br />
- <form action="index.php" id="upgradeCorePluginsForm">
- <input type="hidden" name="updateCorePlugins" value="1" />
- {if count($queries) == 1}
- <input type="submit" class="submit" value="{'CoreUpdater_ContinueToPiwik'|translate}" />
- {else}
- <input type="submit" class="submit" value="{'CoreUpdater_UpgradePiwik'|translate}" />
- {/if}
- </form>
- {else}
- {if count($warningMessages) == 0}
- <p class="success">{'CoreUpdater_PiwikHasBeenSuccessfullyUpgraded'|translate}</p>
- {/if}
-
- <br />
- <form action="index.php">
- <input type="submit" class="submit" value="{'CoreUpdater_ContinueToPiwik'|translate}" />
- </form>
- {/if}
+ {if $coreToUpdate || count($pluginNamesToUpdate) > 0}
+ <br/>
+ <form action="index.php" id="upgradeCorePluginsForm">
+ <input type="hidden" name="updateCorePlugins" value="1"/>
+ {if count($queries) == 1}
+ <input type="submit" class="submit" value="{'CoreUpdater_ContinueToPiwik'|translate}"/>
+ {else}
+ <input type="submit" class="submit" value="{'CoreUpdater_UpgradePiwik'|translate}"/>
+ {/if}
+ </form>
+ {else}
+ {if count($warningMessages) == 0}
+ <p class="success">{'CoreUpdater_PiwikHasBeenSuccessfullyUpgraded'|translate}</p>
+ {/if}
+ <br/>
+ <form action="index.php">
+ <input type="submit" class="submit" value="{'CoreUpdater_ContinueToPiwik'|translate}"/>
+ </form>
+ {/if}
{/if}
{include file="Installation/templates/integrityDetails.tpl"}
{literal}
-<style type="text/css">
-code {
- background-color:#F0F7FF;
- border: 1px dashed #00008B;
- border-left: 5px solid;
- direction:ltr;
- display:block;
- margin:2px 2px 20px;
- padding:4px;
- text-align:left;
-}
-li {
- margin-top:10px;
- margin-left:30px;
-}
-</style>
-<script type="text/javascript">
-$(document).ready(function() {
- $('#showSql').click( function () {
- $('#sqlQueries').toggle();
- });
- $('#upgradeCorePluginsForm').submit(function(){
- $('input[type=submit]', this).prop('disabled', 'disabled');
- });
-});
-</script>
+ <style type="text/css">
+ code {
+ background-color: #F0F7FF;
+ border: 1px dashed #00008B;
+ border-left: 5px solid;
+ direction: ltr;
+ display: block;
+ margin: 2px 2px 20px;
+ padding: 4px;
+ text-align: left;
+ }
+
+ li {
+ margin-top: 10px;
+ margin-left: 30px;
+ }
+ </style>
+ <script type="text/javascript">
+ $(document).ready(function () {
+ $('#showSql').click(function () {
+ $('#sqlQueries').toggle();
+ });
+ $('#upgradeCorePluginsForm').submit(function () {
+ $('input[type=submit]', this).prop('disabled', 'disabled');
+ });
+ });
+ </script>
{/literal}
{include file="CoreUpdater/templates/footer.tpl"}
diff --git a/plugins/CustomVariables/API.php b/plugins/CustomVariables/API.php
index 1a340e4964..3a47c837c5 100644
--- a/plugins/CustomVariables/API.php
+++ b/plugins/CustomVariables/API.php
@@ -1,111 +1,105 @@
<?php
/**
* Piwik - Open source web analytics
- *
+ *
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
- *
+ *
* @category Piwik_Plugins
* @package Piwik_CustomVariables
*/
/**
* The Custom Variables API lets you access reports for your <a href='http://piwik.org/docs/custom-variables/' target='_blank'>Custom Variables</a> names and values.
- *
+ *
* @package Piwik_CustomVariables
*/
-class Piwik_CustomVariables_API
+class Piwik_CustomVariables_API
{
- static private $instance = null;
-
- /**
- * @return Piwik_CustomVariables_API
- */
- static public function getInstance()
- {
- if (self::$instance == null)
- {
- self::$instance = new self;
- }
- return self::$instance;
- }
+ static private $instance = null;
/**
- * @param int $idSite
- * @param string $period
+ * @return Piwik_CustomVariables_API
+ */
+ static public function getInstance()
+ {
+ if (self::$instance == null) {
+ self::$instance = new self;
+ }
+ return self::$instance;
+ }
+
+ /**
+ * @param int $idSite
+ * @param string $period
* @param Piwik_Date $date
- * @param string $segment
- * @param bool $expanded
- * @param int $idSubtable
+ * @param string $segment
+ * @param bool $expanded
+ * @param int $idSubtable
*
* @return Piwik_DataTable|Piwik_DataTable_Array
*/
- protected function getDataTable($idSite, $period, $date, $segment, $expanded, $idSubtable)
- {
- $dataTable = Piwik_Archive::getDataTableFromArchive('CustomVariables_valueByName', $idSite, $period, $date, $segment, $expanded, $idSubtable);
- $dataTable->filter('Sort', array(Piwik_Archive::INDEX_NB_VISITS, 'desc', $naturalSort = false, $expanded));
- $dataTable->queueFilter('ReplaceColumnNames');
- return $dataTable;
- }
+ protected function getDataTable($idSite, $period, $date, $segment, $expanded, $idSubtable)
+ {
+ $dataTable = Piwik_Archive::getDataTableFromArchive('CustomVariables_valueByName', $idSite, $period, $date, $segment, $expanded, $idSubtable);
+ $dataTable->filter('Sort', array(Piwik_Archive::INDEX_NB_VISITS, 'desc', $naturalSort = false, $expanded));
+ $dataTable->queueFilter('ReplaceColumnNames');
+ return $dataTable;
+ }
/**
- * @param int $idSite
- * @param string $period
- * @param Piwik_Date $date
- * @param string|bool $segment
- * @param bool $expanded
- * @param bool $_leavePiwikCoreVariables
+ * @param int $idSite
+ * @param string $period
+ * @param Piwik_Date $date
+ * @param string|bool $segment
+ * @param bool $expanded
+ * @param bool $_leavePiwikCoreVariables
*
* @return Piwik_DataTable|Piwik_DataTable_Array
*/
- public function getCustomVariables($idSite, $period, $date, $segment = false, $expanded = false, $_leavePiwikCoreVariables = false)
- {
- $dataTable = $this->getDataTable($idSite, $period, $date, $segment, $expanded, $idSubtable = null);
+ public function getCustomVariables($idSite, $period, $date, $segment = false, $expanded = false, $_leavePiwikCoreVariables = false)
+ {
+ $dataTable = $this->getDataTable($idSite, $period, $date, $segment, $expanded, $idSubtable = null);
- if($dataTable instanceof Piwik_DataTable
- && !$_leavePiwikCoreVariables)
- {
- $mapping = array('_pks', '_pkn', '_pkc', '_pkp', Piwik_Tracker_Action::CVAR_KEY_SEARCH_COUNT, Piwik_Tracker_Action::CVAR_KEY_SEARCH_CATEGORY );
- foreach($mapping as $name)
- {
- $row = $dataTable->getRowFromLabel($name);
- if($row)
- {
- $dataTable->deleteRow($dataTable->getRowIdFromLabel($name));
- }
- }
- }
- return $dataTable;
- }
+ if ($dataTable instanceof Piwik_DataTable
+ && !$_leavePiwikCoreVariables
+ ) {
+ $mapping = array('_pks', '_pkn', '_pkc', '_pkp', Piwik_Tracker_Action::CVAR_KEY_SEARCH_COUNT, Piwik_Tracker_Action::CVAR_KEY_SEARCH_CATEGORY);
+ foreach ($mapping as $name) {
+ $row = $dataTable->getRowFromLabel($name);
+ if ($row) {
+ $dataTable->deleteRow($dataTable->getRowIdFromLabel($name));
+ }
+ }
+ }
+ return $dataTable;
+ }
/**
- * @param int $idSite
- * @param string $period
- * @param Piwik_Date $date
- * @param int $idSubtable
- * @param string|bool $segment
- * @param bool $_leavePriceViewedColumn
+ * @param int $idSite
+ * @param string $period
+ * @param Piwik_Date $date
+ * @param int $idSubtable
+ * @param string|bool $segment
+ * @param bool $_leavePriceViewedColumn
*
* @return Piwik_DataTable|Piwik_DataTable_Array
*/
- public function getCustomVariablesValuesFromNameId($idSite, $period, $date, $idSubtable, $segment = false, $_leavePriceViewedColumn = false)
- {
- $dataTable = $this->getDataTable($idSite, $period, $date, $segment, $expanded = false, $idSubtable);
+ public function getCustomVariablesValuesFromNameId($idSite, $period, $date, $idSubtable, $segment = false, $_leavePriceViewedColumn = false)
+ {
+ $dataTable = $this->getDataTable($idSite, $period, $date, $segment, $expanded = false, $idSubtable);
- if(!$_leavePriceViewedColumn)
- {
- $dataTable->deleteColumn('price_viewed');
- }
- else
- {
- // Hack Ecommerce product price tracking to display correctly
- $dataTable->renameColumn('price_viewed', 'price');
- }
- $dataTable->queueFilter('ColumnCallbackReplace', array('label', create_function('$label', '
+ if (!$_leavePriceViewedColumn) {
+ $dataTable->deleteColumn('price_viewed');
+ } else {
+ // Hack Ecommerce product price tracking to display correctly
+ $dataTable->renameColumn('price_viewed', 'price');
+ }
+ $dataTable->queueFilter('ColumnCallbackReplace', array('label', create_function('$label', '
return $label == Piwik_CustomVariables::LABEL_CUSTOM_VALUE_NOT_DEFINED
- ? "'. Piwik_Translate( 'General_NotDefined', Piwik_Translate('CustomVariables_ColumnCustomVariableValue')) .'"
+ ? "' . Piwik_Translate('General_NotDefined', Piwik_Translate('CustomVariables_ColumnCustomVariableValue')) . '"
: $label;')));
- return $dataTable;
- }
+ return $dataTable;
+ }
}
diff --git a/plugins/CustomVariables/Controller.php b/plugins/CustomVariables/Controller.php
index eca9bc2b62..ea00259d3e 100644
--- a/plugins/CustomVariables/Controller.php
+++ b/plugins/CustomVariables/Controller.php
@@ -15,44 +15,44 @@
*/
class Piwik_CustomVariables_Controller extends Piwik_Controller
{
-
- function index($fetch = false)
- {
- return Piwik_View::singleReport(
- Piwik_Translate('CustomVariables_CustomVariables'),
- $this->getCustomVariables(true), $fetch);
- }
-
- function getCustomVariables($fetch = false)
- {
- $view = Piwik_ViewDataTable::factory();
- $view->init( $this->pluginName, __FUNCTION__, "CustomVariables.getCustomVariables", "getCustomVariablesValuesFromNameId" );
-
- $this->setPeriodVariablesView($view);
- $view->enableShowGoals();
-
- $view->setColumnsToDisplay( array('label','nb_visits', 'nb_actions') );
- $view->setColumnTranslation('label', Piwik_Translate('CustomVariables_ColumnCustomVariableName'));
- $view->setSortedColumn( 'nb_visits' );
- $view->setLimit( 10 );
- $view->setFooterMessage( Piwik_Translate('CustomVariables_TrackingHelp', array('<a target="_blank" href="http://piwik.org/docs/custom-variables/">', '</a>')) );
- $this->setMetricsVariablesView($view);
- return $this->renderView($view, $fetch);
- }
-
- function getCustomVariablesValuesFromNameId( $fetch = false)
- {
- $view = Piwik_ViewDataTable::factory();
- $view->init( $this->pluginName, __FUNCTION__, 'CustomVariables.getCustomVariablesValuesFromNameId' );
-
- $view->disableSearchBox();
- $view->enableShowGoals();
- $view->disableExcludeLowPopulation();
- $view->setColumnsToDisplay( array('label','nb_visits', 'nb_actions') );
- $view->setColumnTranslation('label', Piwik_Translate('CustomVariables_ColumnCustomVariableValue'));
-
- return $this->renderView($view, $fetch);
- }
-
+
+ function index($fetch = false)
+ {
+ return Piwik_View::singleReport(
+ Piwik_Translate('CustomVariables_CustomVariables'),
+ $this->getCustomVariables(true), $fetch);
+ }
+
+ function getCustomVariables($fetch = false)
+ {
+ $view = Piwik_ViewDataTable::factory();
+ $view->init($this->pluginName, __FUNCTION__, "CustomVariables.getCustomVariables", "getCustomVariablesValuesFromNameId");
+
+ $this->setPeriodVariablesView($view);
+ $view->enableShowGoals();
+
+ $view->setColumnsToDisplay(array('label', 'nb_visits', 'nb_actions'));
+ $view->setColumnTranslation('label', Piwik_Translate('CustomVariables_ColumnCustomVariableName'));
+ $view->setSortedColumn('nb_visits');
+ $view->setLimit(10);
+ $view->setFooterMessage(Piwik_Translate('CustomVariables_TrackingHelp', array('<a target="_blank" href="http://piwik.org/docs/custom-variables/">', '</a>')));
+ $this->setMetricsVariablesView($view);
+ return $this->renderView($view, $fetch);
+ }
+
+ function getCustomVariablesValuesFromNameId($fetch = false)
+ {
+ $view = Piwik_ViewDataTable::factory();
+ $view->init($this->pluginName, __FUNCTION__, 'CustomVariables.getCustomVariablesValuesFromNameId');
+
+ $view->disableSearchBox();
+ $view->enableShowGoals();
+ $view->disableExcludeLowPopulation();
+ $view->setColumnsToDisplay(array('label', 'nb_visits', 'nb_actions'));
+ $view->setColumnTranslation('label', Piwik_Translate('CustomVariables_ColumnCustomVariableValue'));
+
+ return $this->renderView($view, $fetch);
+ }
+
}
diff --git a/plugins/CustomVariables/CustomVariables.php b/plugins/CustomVariables/CustomVariables.php
index 3bf3d184c1..d76adab5ea 100644
--- a/plugins/CustomVariables/CustomVariables.php
+++ b/plugins/CustomVariables/CustomVariables.php
@@ -14,324 +14,315 @@
*/
class Piwik_CustomVariables extends Piwik_Plugin
{
- public $archiveProcessing;
- protected $columnToSortByBeforeTruncation;
- protected $maximumRowsInDataTableLevelZero;
- protected $maximumRowsInSubDataTable;
-
- public function getInformation()
- {
- $info = array(
- 'description' => Piwik_Translate('CustomVariables_PluginDescription')
- . ' <br/>Required to use <a href="http://piwik.org/docs/ecommerce-analytics/">Ecommerce Analytics</a> feature!',
- '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;
- }
-
- function addWidgets()
- {
- Piwik_AddWidget( 'General_Visitors', 'CustomVariables_CustomVariables', 'CustomVariables', 'getCustomVariables');
- }
-
- function addMenus()
- {
- Piwik_AddMenu('General_Visitors', 'CustomVariables_CustomVariables', array('module' => 'CustomVariables', 'action' => 'index'), $display = true, $order = 50);
- }
-
- /**
- * Returns metadata for available reports
- *
- * @param Piwik_Event_Notification $notification notification object
- */
- public function getReportMetadata($notification)
- {
- $reports = &$notification->getNotificationObject();
-
- $documentation = Piwik_Translate('CustomVariables_CustomVariablesReportDocumentation',
- array('<br />', '<a href="http://piwik.org/docs/custom-variables/" target="_blank">', '</a>'));
-
- $reports[] = array( 'category' => Piwik_Translate('General_Visitors'),
- 'name' => Piwik_Translate('CustomVariables_CustomVariables'),
- 'module' => 'CustomVariables',
- 'action' => 'getCustomVariables',
- 'actionToLoadSubTables' => 'getCustomVariablesValuesFromNameId',
- 'dimension' => Piwik_Translate('CustomVariables_ColumnCustomVariableName'),
- 'documentation' => $documentation,
- 'order' => 10 );
-
- $reports[] = array( 'category' => Piwik_Translate('General_Visitors'),
- 'name' => Piwik_Translate('CustomVariables_CustomVariables'),
- 'module' => 'CustomVariables',
- 'action' => 'getCustomVariablesValuesFromNameId',
- 'dimension' => Piwik_Translate('CustomVariables_ColumnCustomVariableValue'),
- 'documentation' => $documentation,
- 'isSubtableReport' => true,
- 'order' => 15 );
- }
-
- /**
- * @param Piwik_Event_Notification $notification notification object
- */
- public function getSegmentsMetadata($notification)
- {
- $segments =& $notification->getNotificationObject();
- for($i=1; $i <= Piwik_Tracker::MAX_CUSTOM_VARIABLES; $i++)
- {
- $segments[] = array(
- 'type' => 'dimension',
- 'category' => 'CustomVariables_CustomVariables',
- 'name' => Piwik_Translate('CustomVariables_ColumnCustomVariableName').' '.$i
- .' ('.Piwik_Translate('CustomVariables_ScopeVisit').')',
- 'segment' => 'customVariableName'.$i,
- 'sqlSegment' => 'log_visit.custom_var_k'.$i,
- );
- $segments[] = array(
- 'type' => 'dimension',
- 'category' => 'CustomVariables_CustomVariables',
- 'name' => Piwik_Translate('CustomVariables_ColumnCustomVariableValue').' '.$i
- .' ('.Piwik_Translate('CustomVariables_ScopeVisit').')',
- 'segment' => 'customVariableValue'.$i,
- 'sqlSegment' => 'log_visit.custom_var_v'.$i,
- );
- $segments[] = array(
- 'type' => 'dimension',
- 'category' => 'CustomVariables_CustomVariables',
- 'name' => Piwik_Translate('CustomVariables_ColumnCustomVariableName').' '.$i
- .' ('.Piwik_Translate('CustomVariables_ScopePage').')',
- 'segment' => 'customVariablePageName'.$i,
- 'sqlSegment' => 'log_link_visit_action.custom_var_k'.$i,
- );
- $segments[] = array(
- 'type' => 'dimension',
- 'category' => 'CustomVariables_CustomVariables',
- 'name' => Piwik_Translate('CustomVariables_ColumnCustomVariableValue').' '.$i
- .' ('.Piwik_Translate('CustomVariables_ScopePage').')',
- 'segment' => 'customVariablePageValue'.$i,
- 'sqlSegment' => 'log_link_visit_action.custom_var_v'.$i,
- );
- }
- }
-
- /**
- * Adds Goal dimensions, so that the dimensions are displayed in the UI Goal Overview page
- *
- * @param Piwik_Event_Notification $notification notification object
- */
- function getReportsWithGoalMetrics( $notification )
- {
- $dimensions =& $notification->getNotificationObject();
- $dimensions = array_merge($dimensions, array(
- array( 'category' => Piwik_Translate('General_Visit'),
- 'name' => Piwik_Translate('CustomVariables_CustomVariables'),
- 'module' => 'CustomVariables',
- 'action' => 'getCustomVariables',
- ),
- ));
- }
-
- function __construct()
- {
- $this->maximumRowsInDataTableLevelZero = Piwik_Config::getInstance()->General['datatable_archiving_maximum_rows_referers'];
- $this->maximumRowsInSubDataTable = Piwik_Config::getInstance()->General['datatable_archiving_maximum_rows_subtable_referers'];
- }
-
- protected $interestByCustomVariables = array();
- protected $interestByCustomVariablesAndValue = array();
-
- /**
- * Hooks on daily archive to trigger various log processing
- *
- * @param Piwik_Event_Notification $notification notification object
- * @return void
- */
- public function archiveDay( $notification )
- {
- $this->interestByCustomVariables = $this->interestByCustomVariablesAndValue = array();
-
- /**
- * @var Piwik_ArchiveProcessing_Day
- */
- $this->archiveProcessing = $notification->getNotificationObject();
-
- if(!$this->archiveProcessing->shouldProcessReportsForPlugin($this->getPluginName())) return;
-
- $this->archiveDayAggregate($this->archiveProcessing);
- $this->archiveDayRecordInDatabase($this->archiveProcessing);
- destroy($this->interestByCustomVariables);
- destroy($this->interestByCustomVariablesAndValue);
- }
-
- const LABEL_CUSTOM_VALUE_NOT_DEFINED = "Value not defined";
- /**
- * @param Piwik_ArchiveProcessing_Day $archiveProcessing
- * @return void
- */
- protected function archiveDayAggregate(Piwik_ArchiveProcessing_Day $archiveProcessing)
- {
- for($i = 1; $i <= Piwik_Tracker::MAX_CUSTOM_VARIABLES; $i++ )
- {
- $keyField = "custom_var_k".$i;
- $valueField = "custom_var_v".$i;
- $dimensions = array($keyField, $valueField);
- $where = "%s.$keyField != ''";
-
- // Custom Vars names and values metrics for visits
- $query = $archiveProcessing->queryVisitsByDimension($dimensions, $where);
-
- while($row = $query->fetch() )
- {
- // Handle case custom var value is empty
- $row[$valueField] = $this->cleanCustomVarValue($row[$valueField]);
-
- // Aggregate
- if(!isset($this->interestByCustomVariables[$row[$keyField]])) $this->interestByCustomVariables[$row[$keyField]]= $archiveProcessing->getNewInterestRow();
- if(!isset($this->interestByCustomVariablesAndValue[$row[$keyField]][$row[$valueField]])) $this->interestByCustomVariablesAndValue[$row[$keyField]][$row[$valueField]] = $archiveProcessing->getNewInterestRow();
- $archiveProcessing->updateInterestStats( $row, $this->interestByCustomVariables[$row[$keyField]]);
- $archiveProcessing->updateInterestStats( $row, $this->interestByCustomVariablesAndValue[$row[$keyField]][$row[$valueField]]);
- }
-
- // Custom Vars names and values metrics for page views
- $query = $archiveProcessing->queryActionsByDimension($dimensions, $where);
- $onlyMetricsAvailableInActionsTable = true;
- while($row = $query->fetch() )
- {
- // Handle case custom var value is empty
- $row[$valueField] = $this->cleanCustomVarValue($row[$valueField]);
-
- $label = $row[$valueField];
-
- // Remove price tracked if it's zero or we if we are not currently tracking an ecommerce var
- if(!in_array($row[$keyField], array('_pks', '_pkn', '_pkc')))
- {
- unset($row[Piwik_Archive::INDEX_ECOMMERCE_ITEM_PRICE_VIEWED]);
- }
-
- // when custom variable value is a JSON array of categories
- // possibly JSON value
- $mustInsertCustomVariableValue = true;
- if($row[$keyField] == '_pkc'
- && $label[0] == '[' && $label[1] == '"')
- {
- // In case categories were truncated, try closing the array
- if(substr($label, -2) != '"]') {
- $label .= '"]';
- }
- $decoded = @Piwik_Common::json_decode($label);
- if(is_array($decoded))
- {
- $count = 0;
- foreach($decoded as $category)
- {
- if(empty($category)
- || $count >= Piwik_Tracker_GoalManager::MAXIMUM_PRODUCT_CATEGORIES) {
- continue;
- }
- if(!isset($this->interestByCustomVariablesAndValue[$row[$keyField]][$category])) {
- $this->interestByCustomVariablesAndValue[$row[$keyField]][$category] = $archiveProcessing->getNewInterestRow($onlyMetricsAvailableInActionsTable);
- }
- $archiveProcessing->updateInterestStats( $row, $this->interestByCustomVariablesAndValue[$row[$keyField]][$category], $onlyMetricsAvailableInActionsTable);
- $mustInsertCustomVariableValue = false;
- $count++;
- }
- }
- } // end multi categories hack
-
- if($mustInsertCustomVariableValue)
- {
- if(!isset($this->interestByCustomVariablesAndValue[$row[$keyField]][$row[$valueField]])) $this->interestByCustomVariablesAndValue[$row[$keyField]][$row[$valueField]] = $archiveProcessing->getNewInterestRow($onlyMetricsAvailableInActionsTable);
- $archiveProcessing->updateInterestStats( $row, $this->interestByCustomVariablesAndValue[$row[$keyField]][$row[$valueField]], $onlyMetricsAvailableInActionsTable);
- }
-
- // Do not report on Price viewed for the Custom Variable names
- unset($row[Piwik_Archive::INDEX_ECOMMERCE_ITEM_PRICE_VIEWED]);
-
- // When tracking Custom Variables with scope=page we do not add up visits numbers
- // as it is incorrect to sum visits this way
- // for scope=visit this is allowed, since there is supposed to be one custom var value per custom variable name for a given visit
- $doNotSumVisits = true;
-
- if(!isset($this->interestByCustomVariables[$row[$keyField]])) $this->interestByCustomVariables[$row[$keyField]]= $archiveProcessing->getNewInterestRow($onlyMetricsAvailableInActionsTable, $doNotSumVisits);
- $archiveProcessing->updateInterestStats( $row, $this->interestByCustomVariables[$row[$keyField]], $onlyMetricsAvailableInActionsTable, $doNotSumVisits);
- }
-
- // Custom Vars names and values metrics for Goals
- $query = $archiveProcessing->queryConversionsByDimension($dimensions, $where);
-
- if($query !== false)
- {
- while($row = $query->fetch() )
- {
- // Handle case custom var value is empty
- $row[$valueField] = $this->cleanCustomVarValue($row[$valueField]);
-
- if(!isset($this->interestByCustomVariables[$row[$keyField]][Piwik_Archive::INDEX_GOALS][$row['idgoal']])) $this->interestByCustomVariables[$row[$keyField]][Piwik_Archive::INDEX_GOALS][$row['idgoal']] = $archiveProcessing->getNewGoalRow($row['idgoal']);
- if(!isset($this->interestByCustomVariablesAndValue[$row[$keyField]][$row[$valueField]][Piwik_Archive::INDEX_GOALS][$row['idgoal']])) $this->interestByCustomVariablesAndValue[$row[$keyField]][$row[$valueField]][Piwik_Archive::INDEX_GOALS][$row['idgoal']] = $archiveProcessing->getNewGoalRow($row['idgoal']);
-
- $archiveProcessing->updateGoalStats( $row, $this->interestByCustomVariables[$row[$keyField]][Piwik_Archive::INDEX_GOALS][$row['idgoal']]);
- $archiveProcessing->updateGoalStats( $row, $this->interestByCustomVariablesAndValue[$row[$keyField]][$row[$valueField]][Piwik_Archive::INDEX_GOALS][$row['idgoal']]);
- }
- }
- }
- $archiveProcessing->enrichConversionsByLabelArray($this->interestByCustomVariables);
- $archiveProcessing->enrichConversionsByLabelArrayHasTwoLevels($this->interestByCustomVariablesAndValue);
- }
-
- protected function cleanCustomVarValue($value)
- {
- if(strlen($value))
- {
- return $value;
- }
- return self::LABEL_CUSTOM_VALUE_NOT_DEFINED;
- }
-
- /**
- * @param Piwik_ArchiveProcessing $archiveProcessing
- * @return void
- */
- protected function archiveDayRecordInDatabase($archiveProcessing)
- {
- $recordName = 'CustomVariables_valueByName';
- $table = $archiveProcessing->getDataTableWithSubtablesFromArraysIndexedByLabel($this->interestByCustomVariablesAndValue, $this->interestByCustomVariables);
-
- $blob = $table->getSerialized(
- $this->maximumRowsInDataTableLevelZero, $this->maximumRowsInSubDataTable,
- $columnToSort = Piwik_Archive::INDEX_NB_VISITS);
- $archiveProcessing->insertBlobRecord($recordName, $blob);
- destroy($table);
- }
-
- /**
- * @param Piwik_Event_Notification $notification notification object
- * @return mixed
- */
- function archivePeriod( $notification )
- {
- $archiveProcessing = $notification->getNotificationObject();
-
- if(!$archiveProcessing->shouldProcessReportsForPlugin($this->getPluginName())) return;
-
- $dataTableToSum = 'CustomVariables_valueByName';
- $nameToCount = $archiveProcessing->archiveDataTable(
- $dataTableToSum, null, $this->maximumRowsInDataTableLevelZero, $this->maximumRowsInSubDataTable,
- $columnToSort = Piwik_Archive::INDEX_NB_VISITS);
- }
+ public $archiveProcessing;
+ protected $columnToSortByBeforeTruncation;
+ protected $maximumRowsInDataTableLevelZero;
+ protected $maximumRowsInSubDataTable;
+
+ public function getInformation()
+ {
+ $info = array(
+ 'description' => Piwik_Translate('CustomVariables_PluginDescription')
+ . ' <br/>Required to use <a href="http://piwik.org/docs/ecommerce-analytics/">Ecommerce Analytics</a> feature!',
+ '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;
+ }
+
+ function addWidgets()
+ {
+ Piwik_AddWidget('General_Visitors', 'CustomVariables_CustomVariables', 'CustomVariables', 'getCustomVariables');
+ }
+
+ function addMenus()
+ {
+ Piwik_AddMenu('General_Visitors', 'CustomVariables_CustomVariables', array('module' => 'CustomVariables', 'action' => 'index'), $display = true, $order = 50);
+ }
+
+ /**
+ * Returns metadata for available reports
+ *
+ * @param Piwik_Event_Notification $notification notification object
+ */
+ public function getReportMetadata($notification)
+ {
+ $reports = & $notification->getNotificationObject();
+
+ $documentation = Piwik_Translate('CustomVariables_CustomVariablesReportDocumentation',
+ array('<br />', '<a href="http://piwik.org/docs/custom-variables/" target="_blank">', '</a>'));
+
+ $reports[] = array('category' => Piwik_Translate('General_Visitors'),
+ 'name' => Piwik_Translate('CustomVariables_CustomVariables'),
+ 'module' => 'CustomVariables',
+ 'action' => 'getCustomVariables',
+ 'actionToLoadSubTables' => 'getCustomVariablesValuesFromNameId',
+ 'dimension' => Piwik_Translate('CustomVariables_ColumnCustomVariableName'),
+ 'documentation' => $documentation,
+ 'order' => 10);
+
+ $reports[] = array('category' => Piwik_Translate('General_Visitors'),
+ 'name' => Piwik_Translate('CustomVariables_CustomVariables'),
+ 'module' => 'CustomVariables',
+ 'action' => 'getCustomVariablesValuesFromNameId',
+ 'dimension' => Piwik_Translate('CustomVariables_ColumnCustomVariableValue'),
+ 'documentation' => $documentation,
+ 'isSubtableReport' => true,
+ 'order' => 15);
+ }
+
+ /**
+ * @param Piwik_Event_Notification $notification notification object
+ */
+ public function getSegmentsMetadata($notification)
+ {
+ $segments =& $notification->getNotificationObject();
+ for ($i = 1; $i <= Piwik_Tracker::MAX_CUSTOM_VARIABLES; $i++) {
+ $segments[] = array(
+ 'type' => 'dimension',
+ 'category' => 'CustomVariables_CustomVariables',
+ 'name' => Piwik_Translate('CustomVariables_ColumnCustomVariableName') . ' ' . $i
+ . ' (' . Piwik_Translate('CustomVariables_ScopeVisit') . ')',
+ 'segment' => 'customVariableName' . $i,
+ 'sqlSegment' => 'log_visit.custom_var_k' . $i,
+ );
+ $segments[] = array(
+ 'type' => 'dimension',
+ 'category' => 'CustomVariables_CustomVariables',
+ 'name' => Piwik_Translate('CustomVariables_ColumnCustomVariableValue') . ' ' . $i
+ . ' (' . Piwik_Translate('CustomVariables_ScopeVisit') . ')',
+ 'segment' => 'customVariableValue' . $i,
+ 'sqlSegment' => 'log_visit.custom_var_v' . $i,
+ );
+ $segments[] = array(
+ 'type' => 'dimension',
+ 'category' => 'CustomVariables_CustomVariables',
+ 'name' => Piwik_Translate('CustomVariables_ColumnCustomVariableName') . ' ' . $i
+ . ' (' . Piwik_Translate('CustomVariables_ScopePage') . ')',
+ 'segment' => 'customVariablePageName' . $i,
+ 'sqlSegment' => 'log_link_visit_action.custom_var_k' . $i,
+ );
+ $segments[] = array(
+ 'type' => 'dimension',
+ 'category' => 'CustomVariables_CustomVariables',
+ 'name' => Piwik_Translate('CustomVariables_ColumnCustomVariableValue') . ' ' . $i
+ . ' (' . Piwik_Translate('CustomVariables_ScopePage') . ')',
+ 'segment' => 'customVariablePageValue' . $i,
+ 'sqlSegment' => 'log_link_visit_action.custom_var_v' . $i,
+ );
+ }
+ }
+
+ /**
+ * Adds Goal dimensions, so that the dimensions are displayed in the UI Goal Overview page
+ *
+ * @param Piwik_Event_Notification $notification notification object
+ */
+ function getReportsWithGoalMetrics($notification)
+ {
+ $dimensions =& $notification->getNotificationObject();
+ $dimensions = array_merge($dimensions, array(
+ array('category' => Piwik_Translate('General_Visit'),
+ 'name' => Piwik_Translate('CustomVariables_CustomVariables'),
+ 'module' => 'CustomVariables',
+ 'action' => 'getCustomVariables',
+ ),
+ ));
+ }
+
+ function __construct()
+ {
+ $this->maximumRowsInDataTableLevelZero = Piwik_Config::getInstance()->General['datatable_archiving_maximum_rows_referers'];
+ $this->maximumRowsInSubDataTable = Piwik_Config::getInstance()->General['datatable_archiving_maximum_rows_subtable_referers'];
+ }
+
+ protected $interestByCustomVariables = array();
+ protected $interestByCustomVariablesAndValue = array();
+
+ /**
+ * Hooks on daily archive to trigger various log processing
+ *
+ * @param Piwik_Event_Notification $notification notification object
+ * @return void
+ */
+ public function archiveDay($notification)
+ {
+ $this->interestByCustomVariables = $this->interestByCustomVariablesAndValue = array();
+
+ /**
+ * @var Piwik_ArchiveProcessing_Day
+ */
+ $this->archiveProcessing = $notification->getNotificationObject();
+
+ if (!$this->archiveProcessing->shouldProcessReportsForPlugin($this->getPluginName())) return;
+
+ $this->archiveDayAggregate($this->archiveProcessing);
+ $this->archiveDayRecordInDatabase($this->archiveProcessing);
+ destroy($this->interestByCustomVariables);
+ destroy($this->interestByCustomVariablesAndValue);
+ }
+
+ const LABEL_CUSTOM_VALUE_NOT_DEFINED = "Value not defined";
+
+ /**
+ * @param Piwik_ArchiveProcessing_Day $archiveProcessing
+ * @return void
+ */
+ protected function archiveDayAggregate(Piwik_ArchiveProcessing_Day $archiveProcessing)
+ {
+ for ($i = 1; $i <= Piwik_Tracker::MAX_CUSTOM_VARIABLES; $i++) {
+ $keyField = "custom_var_k" . $i;
+ $valueField = "custom_var_v" . $i;
+ $dimensions = array($keyField, $valueField);
+ $where = "%s.$keyField != ''";
+
+ // Custom Vars names and values metrics for visits
+ $query = $archiveProcessing->queryVisitsByDimension($dimensions, $where);
+
+ while ($row = $query->fetch()) {
+ // Handle case custom var value is empty
+ $row[$valueField] = $this->cleanCustomVarValue($row[$valueField]);
+
+ // Aggregate
+ if (!isset($this->interestByCustomVariables[$row[$keyField]])) $this->interestByCustomVariables[$row[$keyField]] = $archiveProcessing->getNewInterestRow();
+ if (!isset($this->interestByCustomVariablesAndValue[$row[$keyField]][$row[$valueField]])) $this->interestByCustomVariablesAndValue[$row[$keyField]][$row[$valueField]] = $archiveProcessing->getNewInterestRow();
+ $archiveProcessing->updateInterestStats($row, $this->interestByCustomVariables[$row[$keyField]]);
+ $archiveProcessing->updateInterestStats($row, $this->interestByCustomVariablesAndValue[$row[$keyField]][$row[$valueField]]);
+ }
+
+ // Custom Vars names and values metrics for page views
+ $query = $archiveProcessing->queryActionsByDimension($dimensions, $where);
+ $onlyMetricsAvailableInActionsTable = true;
+ while ($row = $query->fetch()) {
+ // Handle case custom var value is empty
+ $row[$valueField] = $this->cleanCustomVarValue($row[$valueField]);
+
+ $label = $row[$valueField];
+
+ // Remove price tracked if it's zero or we if we are not currently tracking an ecommerce var
+ if (!in_array($row[$keyField], array('_pks', '_pkn', '_pkc'))) {
+ unset($row[Piwik_Archive::INDEX_ECOMMERCE_ITEM_PRICE_VIEWED]);
+ }
+
+ // when custom variable value is a JSON array of categories
+ // possibly JSON value
+ $mustInsertCustomVariableValue = true;
+ if ($row[$keyField] == '_pkc'
+ && $label[0] == '[' && $label[1] == '"'
+ ) {
+ // In case categories were truncated, try closing the array
+ if (substr($label, -2) != '"]') {
+ $label .= '"]';
+ }
+ $decoded = @Piwik_Common::json_decode($label);
+ if (is_array($decoded)) {
+ $count = 0;
+ foreach ($decoded as $category) {
+ if (empty($category)
+ || $count >= Piwik_Tracker_GoalManager::MAXIMUM_PRODUCT_CATEGORIES
+ ) {
+ continue;
+ }
+ if (!isset($this->interestByCustomVariablesAndValue[$row[$keyField]][$category])) {
+ $this->interestByCustomVariablesAndValue[$row[$keyField]][$category] = $archiveProcessing->getNewInterestRow($onlyMetricsAvailableInActionsTable);
+ }
+ $archiveProcessing->updateInterestStats($row, $this->interestByCustomVariablesAndValue[$row[$keyField]][$category], $onlyMetricsAvailableInActionsTable);
+ $mustInsertCustomVariableValue = false;
+ $count++;
+ }
+ }
+ } // end multi categories hack
+
+ if ($mustInsertCustomVariableValue) {
+ if (!isset($this->interestByCustomVariablesAndValue[$row[$keyField]][$row[$valueField]])) $this->interestByCustomVariablesAndValue[$row[$keyField]][$row[$valueField]] = $archiveProcessing->getNewInterestRow($onlyMetricsAvailableInActionsTable);
+ $archiveProcessing->updateInterestStats($row, $this->interestByCustomVariablesAndValue[$row[$keyField]][$row[$valueField]], $onlyMetricsAvailableInActionsTable);
+ }
+
+ // Do not report on Price viewed for the Custom Variable names
+ unset($row[Piwik_Archive::INDEX_ECOMMERCE_ITEM_PRICE_VIEWED]);
+
+ // When tracking Custom Variables with scope=page we do not add up visits numbers
+ // as it is incorrect to sum visits this way
+ // for scope=visit this is allowed, since there is supposed to be one custom var value per custom variable name for a given visit
+ $doNotSumVisits = true;
+
+ if (!isset($this->interestByCustomVariables[$row[$keyField]])) $this->interestByCustomVariables[$row[$keyField]] = $archiveProcessing->getNewInterestRow($onlyMetricsAvailableInActionsTable, $doNotSumVisits);
+ $archiveProcessing->updateInterestStats($row, $this->interestByCustomVariables[$row[$keyField]], $onlyMetricsAvailableInActionsTable, $doNotSumVisits);
+ }
+
+ // Custom Vars names and values metrics for Goals
+ $query = $archiveProcessing->queryConversionsByDimension($dimensions, $where);
+
+ if ($query !== false) {
+ while ($row = $query->fetch()) {
+ // Handle case custom var value is empty
+ $row[$valueField] = $this->cleanCustomVarValue($row[$valueField]);
+
+ if (!isset($this->interestByCustomVariables[$row[$keyField]][Piwik_Archive::INDEX_GOALS][$row['idgoal']])) $this->interestByCustomVariables[$row[$keyField]][Piwik_Archive::INDEX_GOALS][$row['idgoal']] = $archiveProcessing->getNewGoalRow($row['idgoal']);
+ if (!isset($this->interestByCustomVariablesAndValue[$row[$keyField]][$row[$valueField]][Piwik_Archive::INDEX_GOALS][$row['idgoal']])) $this->interestByCustomVariablesAndValue[$row[$keyField]][$row[$valueField]][Piwik_Archive::INDEX_GOALS][$row['idgoal']] = $archiveProcessing->getNewGoalRow($row['idgoal']);
+
+ $archiveProcessing->updateGoalStats($row, $this->interestByCustomVariables[$row[$keyField]][Piwik_Archive::INDEX_GOALS][$row['idgoal']]);
+ $archiveProcessing->updateGoalStats($row, $this->interestByCustomVariablesAndValue[$row[$keyField]][$row[$valueField]][Piwik_Archive::INDEX_GOALS][$row['idgoal']]);
+ }
+ }
+ }
+ $archiveProcessing->enrichConversionsByLabelArray($this->interestByCustomVariables);
+ $archiveProcessing->enrichConversionsByLabelArrayHasTwoLevels($this->interestByCustomVariablesAndValue);
+ }
+
+ protected function cleanCustomVarValue($value)
+ {
+ if (strlen($value)) {
+ return $value;
+ }
+ return self::LABEL_CUSTOM_VALUE_NOT_DEFINED;
+ }
+
+ /**
+ * @param Piwik_ArchiveProcessing $archiveProcessing
+ * @return void
+ */
+ protected function archiveDayRecordInDatabase($archiveProcessing)
+ {
+ $recordName = 'CustomVariables_valueByName';
+ $table = $archiveProcessing->getDataTableWithSubtablesFromArraysIndexedByLabel($this->interestByCustomVariablesAndValue, $this->interestByCustomVariables);
+
+ $blob = $table->getSerialized(
+ $this->maximumRowsInDataTableLevelZero, $this->maximumRowsInSubDataTable,
+ $columnToSort = Piwik_Archive::INDEX_NB_VISITS);
+ $archiveProcessing->insertBlobRecord($recordName, $blob);
+ destroy($table);
+ }
+
+ /**
+ * @param Piwik_Event_Notification $notification notification object
+ * @return mixed
+ */
+ function archivePeriod($notification)
+ {
+ $archiveProcessing = $notification->getNotificationObject();
+
+ if (!$archiveProcessing->shouldProcessReportsForPlugin($this->getPluginName())) return;
+
+ $dataTableToSum = 'CustomVariables_valueByName';
+ $nameToCount = $archiveProcessing->archiveDataTable(
+ $dataTableToSum, null, $this->maximumRowsInDataTableLevelZero, $this->maximumRowsInSubDataTable,
+ $columnToSort = Piwik_Archive::INDEX_NB_VISITS);
+ }
}
diff --git a/plugins/DBStats/API.php b/plugins/DBStats/API.php
index b72d0a2e49..fec0e0fe71 100644
--- a/plugins/DBStats/API.php
+++ b/plugins/DBStats/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_DBStats
*/
@@ -16,304 +16,292 @@ require_once PIWIK_INCLUDE_PATH . '/plugins/DBStats/MySQLMetadataProvider.php';
/**
* DBStats API is used to request the overall status of the Mysql tables in use by Piwik.
- *
+ *
* @package Piwik_DBStats
*/
class Piwik_DBStats_API
{
- /** Singleton instance of this class. */
- static private $instance = null;
-
- /**
- * Gets or creates the DBStats API singleton.
- */
- static public function getInstance()
- {
- if (self::$instance == null)
- {
- self::$instance = new self;
- }
- return self::$instance;
- }
-
- /**
- * The MySQLMetadataProvider instance that fetches table/db status information.
- */
- private $metadataProvider;
-
- /**
- * Constructor.
- */
- public function __construct()
- {
- $this->metadataProvider = new Piwik_DBStats_MySQLMetadataProvider();
- }
-
- /**
- * Forces the next table status request to issue a query by reseting the table status cache.
- */
- public function resetTableStatuses()
- {
- Piwik::checkUserIsSuperUser();
- self::getInstance()->metadataProvider = new Piwik_DBStats_MySQLMetadataProvider();
- }
-
- /**
- * Gets some general information about this Piwik installation, including the count of
- * websites tracked, the count of users and the total space used by the database.
- *
- * @return array Contains the website count, user count and total space used by the database.
- */
- public function getGeneralInformation()
- {
- Piwik::checkUserIsSuperUser();
- // calculate total size
- $totalSpaceUsed = 0;
- foreach ($this->metadataProvider->getAllTablesStatus() as $status)
- {
- $totalSpaceUsed += $status['Data_length'] + $status['Index_length'];
- }
-
- $siteTableStatus = $this->metadataProvider->getTableStatus('site');
- $userTableStatus = $this->metadataProvider->getTableStatus('user');
-
- $siteCount = $siteTableStatus['Rows'];
- $userCount = $userTableStatus['Rows'];
-
- return array($siteCount, $userCount, $totalSpaceUsed);
- }
-
- /**
- * Gets general database info that is not specific to any table.
- *
- * @return array See http://dev.mysql.com/doc/refman/5.1/en/show-status.html .
- */
- public function getDBStatus()
- {
- Piwik::checkUserIsSuperUser();
- return $this->metadataProvider->getDBStatus();
- }
-
- /**
- * Returns a datatable summarizing how data is distributed among Piwik tables.
- *
- * This function will group tracker tables, numeric archive tables, blob archive tables
- * and other tables together so only four rows are shown.
- *
- * @return Piwik_DataTable A datatable with three columns: 'data_size', 'index_size', 'row_count'.
- */
- public function getDatabaseUsageSummary()
- {
- Piwik::checkUserIsSuperUser();
-
- $emptyRow = array('data_size' => 0, 'index_size' => 0, 'row_count' => 0);
- $rows = array(
- 'tracker_data' => $emptyRow,
- 'metric_data' => $emptyRow,
- 'report_data' => $emptyRow,
- 'other_data' => $emptyRow
- );
-
- foreach ($this->metadataProvider->getAllTablesStatus() as $status)
- {
- if ($this->isNumericArchiveTable($status['Name']))
- {
- $rowToAddTo = &$rows['metric_data'];
- }
- else if ($this->isBlobArchiveTable($status['Name']))
- {
- $rowToAddTo = &$rows['report_data'];
- }
- else if ($this->isTrackerTable($status['Name']))
- {
- $rowToAddTo = &$rows['tracker_data'];
- }
- else
- {
- $rowToAddTo = &$rows['other_data'];
- }
-
- $rowToAddTo['data_size'] += $status['Data_length'];
- $rowToAddTo['index_size'] += $status['Index_length'];
- $rowToAddTo['row_count'] += $status['Rows'];
- }
-
- $result = new Piwik_DataTable();
- $result->addRowsFromArrayWithIndexLabel($rows);
- return $result;
- }
-
- /**
- * Returns a datatable describing how much space is taken up by each log table.
- *
- * @return Piwik_DataTable A datatable with three columns: 'data_size', 'index_size', 'row_count'.
- */
- public function getTrackerDataSummary()
- {
- Piwik::checkUserIsSuperUser();
- return $this->getTablesSummary($this->metadataProvider->getAllLogTableStatus());
- }
-
- /**
- * Returns a datatable describing how much space is taken up by each numeric
- * archive table.
- *
- * @return Piwik_DataTable A datatable with three columns: 'data_size', 'index_size', 'row_count'.
- */
- public function getMetricDataSummary()
- {
- Piwik::checkUserIsSuperUser();
- return $this->getTablesSummary($this->metadataProvider->getAllNumericArchiveStatus());
- }
-
- /**
- * Returns a datatable describing how much space is taken up by each numeric
- * archive table, grouped by year.
- *
- * @return Piwik_DataTable A datatable with three columns: 'data_size', 'index_size', 'row_count'.
- */
- public function getMetricDataSummaryByYear()
- {
- Piwik::checkUserIsSuperUser();
-
- $dataTable = $this->getMetricDataSummary();
-
- $getTableYear = array('Piwik_DBStats_API', 'getArchiveTableYear');
- $dataTable->filter('GroupBy', array('label', $getTableYear));
-
- return $dataTable;
- }
-
- /**
- * Returns a datatable describing how much space is taken up by each blob
- * archive table.
- *
- * @return Piwik_DataTable A datatable with three columns: 'data_size', 'index_size', 'row_count'.
- */
- public function getReportDataSummary()
- {
- Piwik::checkUserIsSuperUser();
- return $this->getTablesSummary($this->metadataProvider->getAllBlobArchiveStatus());
- }
-
- /**
- * Returns a datatable describing how much space is taken up by each blob
- * archive table, grouped by year.
- *
- * @return Piwik_DataTable A datatable with three columns: 'data_size', 'index_size', 'row_count'.
- */
- public function getReportDataSummaryByYear()
- {
- Piwik::checkUserIsSuperUser();
-
- $dataTable = $this->getReportDataSummary();
-
- $getTableYear = array('Piwik_DBStats_API', 'getArchiveTableYear');
- $dataTable->filter('GroupBy', array('label', $getTableYear));
-
- return $dataTable;
- }
-
- /**
- * Returns a datatable describing how much space is taken up by 'admin' tables.
- *
- * An 'admin' table is a table that is not central to analytics functionality.
- * So any table that isn't an archive table or a log table is an 'admin' table.
- *
- * @return Piwik_DataTable A datatable with three columns: 'data_size', 'index_size', 'row_count'.
- */
- public function getAdminDataSummary()
- {
- Piwik::checkUserIsSuperUser();
- return $this->getTablesSummary($this->metadataProvider->getAllAdminTableStatus());
- }
-
- /**
- * Returns a datatable describing how much total space is taken up by each
- * individual report type.
- *
- * Goal reports and reports of the format .*_[0-9]+ are grouped together.
- *
- * @param bool $forceCache false to use the cached result, true to run the queries again and
- * cache the result.
- * @return Piwik_DataTable A datatable with three columns: 'data_size', 'index_size', 'row_count'.
- */
- public function getIndividualReportsSummary( $forceCache = false )
- {
- Piwik::checkUserIsSuperUser();
- return $this->metadataProvider->getRowCountsAndSizeByBlobName($forceCache);
- }
-
- /**
- * Returns a datatable describing how much total space is taken up by each
- * individual metric type.
- *
- * Goal metrics, metrics of the format .*_[0-9]+ and 'done...' metrics are grouped together.
- *
- * @param bool $forceCache false to use the cached result, true to run the queries again and
- * cache the result.
- * @return Piwik_DataTable A datatable with three columns: 'data_size', 'index_size', 'row_count'.
- */
- public function getIndividualMetricsSummary( $forceCache = false )
- {
- Piwik::checkUserIsSuperUser();
- return $this->metadataProvider->getRowCountsAndSizeByMetricName($forceCache);
- }
-
- /**
- * Returns a datatable representation of a set of table statuses.
- *
- * @param array $statuses The table statuses to summarize.
- * @return Piwik_DataTable
- */
- private function getTablesSummary( $statuses )
- {
- $dataTable = new Piwik_DataTable();
- foreach ($statuses as $status)
- {
- $dataTable->addRowFromSimpleArray(array(
- 'label' => $status['Name'],
- 'data_size' => $status['Data_length'],
- 'index_size' => $status['Index_length'],
- 'row_count' => $status['Rows']
- ));
- }
- return $dataTable;
- }
-
- /** Returns true if $name is the name of a numeric archive table, false if otherwise. */
- private function isNumericArchiveTable( $name )
- {
- return strpos($name, Piwik_Common::prefixTable('archive_numeric_')) === 0;
- }
-
- /** Returns true if $name is the name of a blob archive table, false if otherwise. */
- private function isBlobArchiveTable( $name )
- {
- return strpos($name, Piwik_Common::prefixTable('archive_blob_')) === 0;
- }
-
- /** Returns true if $name is the name of a log table, false if otherwise. */
- private function isTrackerTable( $name )
- {
- return strpos($name, Piwik_Common::prefixTable('log_')) === 0;
- }
-
- /**
- * Gets the year of an archive table from its name.
- *
- * @param string $tableName
- * @param string The year.
- *
- * @ignore
- */
- public static function getArchiveTableYear( $tableName )
- {
- if (preg_match("/archive_(?:numeric|blob)_([0-9]+)_/", $tableName, $matches) === 0)
- {
- return '';
- }
-
- return $matches[1];
- }
+ /** Singleton instance of this class. */
+ static private $instance = null;
+
+ /**
+ * Gets or creates the DBStats API singleton.
+ */
+ static public function getInstance()
+ {
+ if (self::$instance == null) {
+ self::$instance = new self;
+ }
+ return self::$instance;
+ }
+
+ /**
+ * The MySQLMetadataProvider instance that fetches table/db status information.
+ */
+ private $metadataProvider;
+
+ /**
+ * Constructor.
+ */
+ public function __construct()
+ {
+ $this->metadataProvider = new Piwik_DBStats_MySQLMetadataProvider();
+ }
+
+ /**
+ * Forces the next table status request to issue a query by reseting the table status cache.
+ */
+ public function resetTableStatuses()
+ {
+ Piwik::checkUserIsSuperUser();
+ self::getInstance()->metadataProvider = new Piwik_DBStats_MySQLMetadataProvider();
+ }
+
+ /**
+ * Gets some general information about this Piwik installation, including the count of
+ * websites tracked, the count of users and the total space used by the database.
+ *
+ * @return array Contains the website count, user count and total space used by the database.
+ */
+ public function getGeneralInformation()
+ {
+ Piwik::checkUserIsSuperUser();
+ // calculate total size
+ $totalSpaceUsed = 0;
+ foreach ($this->metadataProvider->getAllTablesStatus() as $status) {
+ $totalSpaceUsed += $status['Data_length'] + $status['Index_length'];
+ }
+
+ $siteTableStatus = $this->metadataProvider->getTableStatus('site');
+ $userTableStatus = $this->metadataProvider->getTableStatus('user');
+
+ $siteCount = $siteTableStatus['Rows'];
+ $userCount = $userTableStatus['Rows'];
+
+ return array($siteCount, $userCount, $totalSpaceUsed);
+ }
+
+ /**
+ * Gets general database info that is not specific to any table.
+ *
+ * @return array See http://dev.mysql.com/doc/refman/5.1/en/show-status.html .
+ */
+ public function getDBStatus()
+ {
+ Piwik::checkUserIsSuperUser();
+ return $this->metadataProvider->getDBStatus();
+ }
+
+ /**
+ * Returns a datatable summarizing how data is distributed among Piwik tables.
+ *
+ * This function will group tracker tables, numeric archive tables, blob archive tables
+ * and other tables together so only four rows are shown.
+ *
+ * @return Piwik_DataTable A datatable with three columns: 'data_size', 'index_size', 'row_count'.
+ */
+ public function getDatabaseUsageSummary()
+ {
+ Piwik::checkUserIsSuperUser();
+
+ $emptyRow = array('data_size' => 0, 'index_size' => 0, 'row_count' => 0);
+ $rows = array(
+ 'tracker_data' => $emptyRow,
+ 'metric_data' => $emptyRow,
+ 'report_data' => $emptyRow,
+ 'other_data' => $emptyRow
+ );
+
+ foreach ($this->metadataProvider->getAllTablesStatus() as $status) {
+ if ($this->isNumericArchiveTable($status['Name'])) {
+ $rowToAddTo = & $rows['metric_data'];
+ } else if ($this->isBlobArchiveTable($status['Name'])) {
+ $rowToAddTo = & $rows['report_data'];
+ } else if ($this->isTrackerTable($status['Name'])) {
+ $rowToAddTo = & $rows['tracker_data'];
+ } else {
+ $rowToAddTo = & $rows['other_data'];
+ }
+
+ $rowToAddTo['data_size'] += $status['Data_length'];
+ $rowToAddTo['index_size'] += $status['Index_length'];
+ $rowToAddTo['row_count'] += $status['Rows'];
+ }
+
+ $result = new Piwik_DataTable();
+ $result->addRowsFromArrayWithIndexLabel($rows);
+ return $result;
+ }
+
+ /**
+ * Returns a datatable describing how much space is taken up by each log table.
+ *
+ * @return Piwik_DataTable A datatable with three columns: 'data_size', 'index_size', 'row_count'.
+ */
+ public function getTrackerDataSummary()
+ {
+ Piwik::checkUserIsSuperUser();
+ return $this->getTablesSummary($this->metadataProvider->getAllLogTableStatus());
+ }
+
+ /**
+ * Returns a datatable describing how much space is taken up by each numeric
+ * archive table.
+ *
+ * @return Piwik_DataTable A datatable with three columns: 'data_size', 'index_size', 'row_count'.
+ */
+ public function getMetricDataSummary()
+ {
+ Piwik::checkUserIsSuperUser();
+ return $this->getTablesSummary($this->metadataProvider->getAllNumericArchiveStatus());
+ }
+
+ /**
+ * Returns a datatable describing how much space is taken up by each numeric
+ * archive table, grouped by year.
+ *
+ * @return Piwik_DataTable A datatable with three columns: 'data_size', 'index_size', 'row_count'.
+ */
+ public function getMetricDataSummaryByYear()
+ {
+ Piwik::checkUserIsSuperUser();
+
+ $dataTable = $this->getMetricDataSummary();
+
+ $getTableYear = array('Piwik_DBStats_API', 'getArchiveTableYear');
+ $dataTable->filter('GroupBy', array('label', $getTableYear));
+
+ return $dataTable;
+ }
+
+ /**
+ * Returns a datatable describing how much space is taken up by each blob
+ * archive table.
+ *
+ * @return Piwik_DataTable A datatable with three columns: 'data_size', 'index_size', 'row_count'.
+ */
+ public function getReportDataSummary()
+ {
+ Piwik::checkUserIsSuperUser();
+ return $this->getTablesSummary($this->metadataProvider->getAllBlobArchiveStatus());
+ }
+
+ /**
+ * Returns a datatable describing how much space is taken up by each blob
+ * archive table, grouped by year.
+ *
+ * @return Piwik_DataTable A datatable with three columns: 'data_size', 'index_size', 'row_count'.
+ */
+ public function getReportDataSummaryByYear()
+ {
+ Piwik::checkUserIsSuperUser();
+
+ $dataTable = $this->getReportDataSummary();
+
+ $getTableYear = array('Piwik_DBStats_API', 'getArchiveTableYear');
+ $dataTable->filter('GroupBy', array('label', $getTableYear));
+
+ return $dataTable;
+ }
+
+ /**
+ * Returns a datatable describing how much space is taken up by 'admin' tables.
+ *
+ * An 'admin' table is a table that is not central to analytics functionality.
+ * So any table that isn't an archive table or a log table is an 'admin' table.
+ *
+ * @return Piwik_DataTable A datatable with three columns: 'data_size', 'index_size', 'row_count'.
+ */
+ public function getAdminDataSummary()
+ {
+ Piwik::checkUserIsSuperUser();
+ return $this->getTablesSummary($this->metadataProvider->getAllAdminTableStatus());
+ }
+
+ /**
+ * Returns a datatable describing how much total space is taken up by each
+ * individual report type.
+ *
+ * Goal reports and reports of the format .*_[0-9]+ are grouped together.
+ *
+ * @param bool $forceCache false to use the cached result, true to run the queries again and
+ * cache the result.
+ * @return Piwik_DataTable A datatable with three columns: 'data_size', 'index_size', 'row_count'.
+ */
+ public function getIndividualReportsSummary($forceCache = false)
+ {
+ Piwik::checkUserIsSuperUser();
+ return $this->metadataProvider->getRowCountsAndSizeByBlobName($forceCache);
+ }
+
+ /**
+ * Returns a datatable describing how much total space is taken up by each
+ * individual metric type.
+ *
+ * Goal metrics, metrics of the format .*_[0-9]+ and 'done...' metrics are grouped together.
+ *
+ * @param bool $forceCache false to use the cached result, true to run the queries again and
+ * cache the result.
+ * @return Piwik_DataTable A datatable with three columns: 'data_size', 'index_size', 'row_count'.
+ */
+ public function getIndividualMetricsSummary($forceCache = false)
+ {
+ Piwik::checkUserIsSuperUser();
+ return $this->metadataProvider->getRowCountsAndSizeByMetricName($forceCache);
+ }
+
+ /**
+ * Returns a datatable representation of a set of table statuses.
+ *
+ * @param array $statuses The table statuses to summarize.
+ * @return Piwik_DataTable
+ */
+ private function getTablesSummary($statuses)
+ {
+ $dataTable = new Piwik_DataTable();
+ foreach ($statuses as $status) {
+ $dataTable->addRowFromSimpleArray(array(
+ 'label' => $status['Name'],
+ 'data_size' => $status['Data_length'],
+ 'index_size' => $status['Index_length'],
+ 'row_count' => $status['Rows']
+ ));
+ }
+ return $dataTable;
+ }
+
+ /** Returns true if $name is the name of a numeric archive table, false if otherwise. */
+ private function isNumericArchiveTable($name)
+ {
+ return strpos($name, Piwik_Common::prefixTable('archive_numeric_')) === 0;
+ }
+
+ /** Returns true if $name is the name of a blob archive table, false if otherwise. */
+ private function isBlobArchiveTable($name)
+ {
+ return strpos($name, Piwik_Common::prefixTable('archive_blob_')) === 0;
+ }
+
+ /** Returns true if $name is the name of a log table, false if otherwise. */
+ private function isTrackerTable($name)
+ {
+ return strpos($name, Piwik_Common::prefixTable('log_')) === 0;
+ }
+
+ /**
+ * Gets the year of an archive table from its name.
+ *
+ * @param string $tableName
+ * @param string The year.
+ *
+ * @ignore
+ */
+ public static function getArchiveTableYear($tableName)
+ {
+ if (preg_match("/archive_(?:numeric|blob)_([0-9]+)_/", $tableName, $matches) === 0) {
+ return '';
+ }
+
+ return $matches[1];
+ }
}
diff --git a/plugins/DBStats/Controller.php b/plugins/DBStats/Controller.php
index 06bae7232b..ab0de6a292 100644
--- a/plugins/DBStats/Controller.php
+++ b/plugins/DBStats/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_DBStats
*/
@@ -15,366 +15,355 @@
*/
class Piwik_DBStats_Controller extends Piwik_Controller_Admin
{
- /**
- * Returns the index for this plugin. Shows every other report defined by this plugin,
- * except the '...ByYear' reports. These can be loaded as related reports.
- *
- * Also, the 'getIndividual...Summary' reports are loaded by AJAX, as they can take
- * a significant amount of time to load on setups w/ lots of websites.
- */
- public function index()
- {
- Piwik::checkUserIsSuperUser();
- $view = Piwik_View::factory('index');
- $this->setBasicVariablesView($view);
- $view->menu = Piwik_GetAdminMenu();
-
- $view->databaseUsageSummary = $this->getDatabaseUsageSummary(true);
- $view->trackerDataSummary = $this->getTrackerDataSummary(true);
- $view->metricDataSummary = $this->getMetricDataSummary(true);
- $view->reportDataSummary = $this->getReportDataSummary(true);
- $view->adminDataSummary = $this->getAdminDataSummary(true);
-
- list($siteCount, $userCount, $totalSpaceUsed) = Piwik_DBStats_API::getInstance()->getGeneralInformation();
- $view->siteCount = Piwik::getPrettyNumber($siteCount);
- $view->userCount = Piwik::getPrettyNumber($userCount);
- $view->totalSpaceUsed = Piwik::getPrettySizeFromBytes($totalSpaceUsed);
-
- echo $view->render();
- }
-
- /**
- * Shows a datatable that displays how much space the tracker tables, numeric
- * archive tables, report tables and other tables take up in the MySQL database.
- *
- * @param bool $fetch If true, the rendered HTML datatable is returned, otherwise,
- * it is echoed.
- */
- public function getDatabaseUsageSummary( $fetch = false )
- {
- Piwik::checkUserIsSuperUser();
-
- $view = $this->getDataTableView(__FUNCTION__, $viewType = 'graphPie', $orderDir = 'desc',
- $addPercentColumn = true);
- $view->disableOffsetInformationAndPaginationControls();
-
- if ($view instanceof Piwik_ViewDataTable_GenerateGraphHTML)
- {
- $view->showAllTicks();
- }
-
- // translate the labels themselves
- $translateSummaryLabel = array($this, 'translateSummarylabel');
- $view->queueFilter('ColumnCallbackReplace', array(array('label'), $translateSummaryLabel),
- $runBeforeGenericFilters = true);
-
- return $this->renderView($view, $fetch);
- }
-
- /**
- * Shows a datatable that displays the amount of space each individual log table
- * takes up in the MySQL database.
- *
- * @param bool $fetch If true, the rendered HTML datatable is returned, otherwise,
- * it is echoed.
- */
- public function getTrackerDataSummary( $fetch = false )
- {
- Piwik::checkUserIsSuperUser();
-
- $view = $this->getDataTableView(__FUNCTION__);
- $view->disableOffsetInformationAndPaginationControls();
- return $this->renderView($view, $fetch);
- }
-
- /**
- * Shows a datatable that displays the amount of space each numeric archive table
- * takes up in the MySQL database.
- *
- * @param bool $fetch If true, the rendered HTML datatable is returned, otherwise,
- * it is echoed.
- */
- public function getMetricDataSummary( $fetch = false )
- {
- Piwik::checkUserIsSuperUser();
- $view = $this->getDataTableView(__FUNCTION__, $viewType = 'table', $orderDir = 'desc');
- $view->addRelatedReports(Piwik_Translate('DBStats_MetricTables'), array(
- 'DBStats.getMetricDataSummaryByYear' => Piwik_Translate('DBStats_MetricDataByYear')
- ));
- return $this->renderView($view, $fetch);
- }
-
- /**
- * Shows a datatable that displays the amount of space each numeric archive table
- * takes up in the MySQL database, for each year of numeric data.
- *
- * @param bool $fetch If true, the rendered HTML datatable is returned, otherwise,
- * it is echoed.
- */
- public function getMetricDataSummaryByYear( $fetch = false )
- {
- Piwik::checkUserIsSuperUser();
- $view = $this->getDataTableView(__FUNCTION__, $viewType = 'table', $orderDir = 'desc',
- $addPercentColumn = false, $labelKey = 'CoreHome_PeriodYear');
- $view->addRelatedReports(Piwik_Translate('DBStats_MetricDataByYear'), array(
- 'DBStats.getMetricDataSummary' => Piwik_Translate('DBStats_MetricTables')
- ));
- return $this->renderView($view, $fetch);
- }
-
- /**
- * Shows a datatable that displays the amount of space each blob archive table
- * takes up in the MySQL database.
- *
- * @param bool $fetch If true, the rendered HTML datatable is returned, otherwise,
- * it is echoed.
- */
- public function getReportDataSummary( $fetch = false )
- {
- Piwik::checkUserIsSuperUser();
- $view = $this->getDataTableView(__FUNCTION__, $viewType = 'table', $orderDir = 'desc');
- $view->addRelatedReports(Piwik_Translate('DBStats_ReportTables'), array(
- 'DBStats.getReportDataSummaryByYear' => Piwik_Translate('DBStats_ReportDataByYear')
- ));
- return $this->renderView($view, $fetch);
- }
-
- /**
- * Shows a datatable that displays the amount of space each blob archive table
- * takes up in the MySQL database, for each year of blob data.
- *
- * @param bool $fetch If true, the rendered HTML datatable is returned, otherwise,
- * it is echoed.
- */
- public function getReportDataSummaryByYear( $fetch = false )
- {
- Piwik::checkUserIsSuperUser();
- $view = $this->getDataTableView(__FUNCTION__, $viewType = 'table', $orderDir = 'desc',
- $addPercentColumn = false, $labelKey = 'CoreHome_PeriodYear');
- $view->addRelatedReports(Piwik_Translate('DBStats_ReportDataByYear'), array(
- 'DBStats.getReportDataSummary' => Piwik_Translate('DBStats_ReportTables')
- ));
- return $this->renderView($view, $fetch);
- }
-
- /**
- * Shows a datatable that displays how many occurances there are of each individual
- * report type stored in the MySQL database.
- *
- * Goal reports and reports of the format: .*_[0-9]+ are grouped together.
- *
- * @param bool $fetch If true, the rendered HTML datatable is returned, otherwise,
- * it is echoed.
- */
- public function getIndividualReportsSummary( $fetch = false )
- {
- Piwik::checkUserIsSuperUser();
- $view = $this->getDataTableView(__FUNCTION__, $viewType = 'table', $orderDir = 'asc',
- $addPercentColumn = false, $labelKey = 'General_Report',
- $sizeColumns = array('estimated_size'));
-
- // this report table has some extra columns that shouldn't be shown
- if ($view instanceof Piwik_ViewDataTable_HtmlTable)
- {
- $view->setColumnsToDisplay(array('label', 'row_count', 'estimated_size'));
- }
-
- $this->setIndividualSummaryFooterMessage($view);
-
- return $this->renderView($view, $fetch);
- }
-
- /**
- * Shows a datatable that displays how many occurances there are of each individual
- * metric type stored in the MySQL database.
- *
- * Goal metrics, metrics of the format .*_[0-9]+ and 'done...' metrics are grouped together.
- *
- * @param bool $fetch If true, the rendered HTML datatable is returned, otherwise,
- * it is echoed.
- */
- public function getIndividualMetricsSummary( $fetch = false )
- {
- Piwik::checkUserIsSuperUser();
- $view = $this->getDataTableView(__FUNCTION__, $viewType = 'table', $orderDir = 'asc',
- $addPercentColumn = false, $labelKey = 'General_Metric',
- $sizeColumns = array('estimated_size'));
-
- $this->setIndividualSummaryFooterMessage($view);
-
- return $this->renderView($view, $fetch);
- }
-
- /**
- * Shows a datatable that displays the amount of space each 'admin' table takes
- * up in the MySQL database.
- *
- * An 'admin' table is a table that is not central to analytics functionality.
- * So any table that isn't an archive table or a log table is an 'admin' table.
- *
- * @param bool $fetch If true, the rendered HTML datatable is returned, otherwise,
- * it is echoed.
- */
- public function getAdminDataSummary( $fetch = false )
- {
- Piwik::checkUserIsSuperUser();
- $view = $this->getDataTableView(__FUNCTION__, $viewType = 'table');
- $view->disableOffsetInformationAndPaginationControls();
- return $this->renderView($view, $fetch);
- }
-
- /**
- * Utility function that creates and prepares a ViewDataTable for this plugin.
- */
- private function getDataTableView( $function, $viewType = 'table', $orderDir = 'asc', $addPercentColumn = false,
- $labelKey = 'DBStats_Table', $sizeColumns = array('data_size', 'index_size'),
- $limit = 25 )
- {
- $columnTranslations = array(
- 'label' => Piwik_Translate($labelKey),
- 'year' => Piwik_Translate('CoreHome_PeriodYear'),
- 'data_size' => Piwik_Translate('DBStats_DataSize'),
- 'index_size' => Piwik_Translate('DBStats_IndexSize'),
- 'total_size' => Piwik_Translate('DBStats_TotalSize'),
- 'row_count' => Piwik_Translate('DBStats_RowCount'),
- 'percent_total' => '%&nbsp;'.Piwik_Translate('DBStats_DBSize'),
- 'estimated_size' => Piwik_Translate('DBStats_EstimatedSize')
- );
-
- $view = Piwik_ViewDataTable::factory($viewType);
- $view->init($this->pluginName, $function, "DBStats.$function");
- $view->setSortedColumn('label', $orderDir);
- $view->setLimit($limit);
- $view->setHighlightSummaryRow(true);
- $view->disableSearchBox();
- $view->disableExcludeLowPopulation();
- $view->disableTagCloud();
- $view->disableShowAllColumns();
- $view->alwaysShowSummaryRow();
-
- // translate columns
- foreach ($columnTranslations as $columnName => $translation)
- {
- $view->setColumnTranslation($columnName, $translation);
- }
-
- // add total_size column (if necessary columns are present)
- if (in_array('data_size', $sizeColumns) && in_array('index_size', $sizeColumns))
- {
- $getTotalTableSize = array($this, 'getTotalTableSize');
- $view->queueFilter('ColumnCallbackAddColumn',
- array(array('data_size', 'index_size'), 'total_size', $getTotalTableSize),
- $runBeforeGenericFilters = true);
-
- $sizeColumns[] = 'total_size';
- }
-
- $runPrettySizeFilterBeforeGeneric = false;
- $fixedMemoryUnit = false;
- if ($view instanceof Piwik_ViewDataTable_HtmlTable) // if displaying a table
- {
- $view->disableRowEvolution();
-
- // add summary row only if displaying a table
- $view->queueFilter('AddSummaryRow', array(0, Piwik_Translate('General_Total'), 'label', false),
- $runBeforeGenericFilters = true);
-
- // add other filters
- if ($addPercentColumn && in_array('total_size', $sizeColumns))
- {
- $view->queueFilter('ColumnCallbackAddColumnPercentage',
- array('percent_total', 'total_size', 'total_size', $quotientPrecision = 0, $shouldSkipRows = false,
- $getDivisorFromSummaryRow = true),
- $runBeforeGenericFilters = true);
- $view->setSortedColumn('percent_total', $orderDir);
- }
- }
- else if ($view instanceof Piwik_ViewDataTable_GenerateGraphData) // if displaying a graph
- {
- if (in_array('total_size', $sizeColumns))
- {
- $view->setColumnsToDisplay(array('label', 'total_size'));
-
- // when displaying a graph, we force sizes to be shown as the same unit so axis labels
- // will be readable. NOTE: The unit should depend on the smallest value of the data table,
- // however there's no way to know this information, short of creating a custom filter. For
- // now, just assume KB.
- $fixedMemoryUnit = 'K';
- $view->setAxisYUnit(' K');
-
- $view->setSortedColumn('total_size', 'desc');
-
- $runPrettySizeFilterBeforeGeneric = true;
- }
- else
- {
- $view->setColumnsToDisplay(array('label', 'row_count'));
- $view->setAxisYUnit(' '.Piwik_Translate('General_Rows'));
-
- $view->setSortedColumn('row_count', 'desc');
- }
- }
-
- $getPrettySize = array('Piwik', 'getPrettySizeFromBytes');
- $params = $fixedMemoryUnit === false ? array() : array($fixedMemoryUnit);
- $view->queueFilter(
- 'ColumnCallbackReplace', array($sizeColumns, $getPrettySize, $params), $runPrettySizeFilterBeforeGeneric);
-
- // jqPlot will display &nbsp; as, well, '&nbsp;', so don't replace the spaces when rendering as a graph
- if (!($view instanceof Piwik_ViewDataTable_GenerateGraphData))
- {
- $replaceSpaces = array($this, 'replaceColumnSpaces');
- $view->queueFilter('ColumnCallbackReplace', array($sizeColumns, $replaceSpaces));
- }
-
- $getPrettyNumber = array('Piwik', 'getPrettyNumber');
- $view->queueFilter('ColumnCallbackReplace', array(array('row_count'), $getPrettyNumber));
-
- return $view;
- }
-
- /**
- * Replaces spaces w/ &nbsp; for correct HTML output.
- */
- public function replaceColumnSpaces( $value )
- {
- return str_replace(' ', '&nbsp;', $value);
- }
-
- /**
- * Row callback function that calculates a tables total size.
- */
- public function getTotalTableSize( $dataSize, $indexSize )
- {
- return $dataSize + $indexSize;
- }
-
- /**
- * Column callback used to translate the column values in the database usage summary table.
- */
- public function translateSummarylabel( $value )
- {
- static $valueToTranslationStr = array(
- 'tracker_data' => 'DBStats_TrackerTables',
- 'report_data' => 'DBStats_ReportTables',
- 'metric_data' => 'DBStats_MetricTables',
- 'other_data' => 'DBStats_OtherTables'
- );
-
- return isset($valueToTranslationStr[$value])
- ? Piwik_Translate($valueToTranslationStr[$value])
- : $value;
- }
-
- /**
- * Sets the footer message for the Individual...Summary reports.
- */
- private function setIndividualSummaryFooterMessage( $view )
- {
- $lastGenerated = Piwik_DBStats::getDateOfLastCachingRun();
- if ($lastGenerated !== false)
- {
- $view->setFooterMessage(Piwik_Translate('Mobile_LastUpdated', $lastGenerated));
- }
- }
+ /**
+ * Returns the index for this plugin. Shows every other report defined by this plugin,
+ * except the '...ByYear' reports. These can be loaded as related reports.
+ *
+ * Also, the 'getIndividual...Summary' reports are loaded by AJAX, as they can take
+ * a significant amount of time to load on setups w/ lots of websites.
+ */
+ public function index()
+ {
+ Piwik::checkUserIsSuperUser();
+ $view = Piwik_View::factory('index');
+ $this->setBasicVariablesView($view);
+ $view->menu = Piwik_GetAdminMenu();
+
+ $view->databaseUsageSummary = $this->getDatabaseUsageSummary(true);
+ $view->trackerDataSummary = $this->getTrackerDataSummary(true);
+ $view->metricDataSummary = $this->getMetricDataSummary(true);
+ $view->reportDataSummary = $this->getReportDataSummary(true);
+ $view->adminDataSummary = $this->getAdminDataSummary(true);
+
+ list($siteCount, $userCount, $totalSpaceUsed) = Piwik_DBStats_API::getInstance()->getGeneralInformation();
+ $view->siteCount = Piwik::getPrettyNumber($siteCount);
+ $view->userCount = Piwik::getPrettyNumber($userCount);
+ $view->totalSpaceUsed = Piwik::getPrettySizeFromBytes($totalSpaceUsed);
+
+ echo $view->render();
+ }
+
+ /**
+ * Shows a datatable that displays how much space the tracker tables, numeric
+ * archive tables, report tables and other tables take up in the MySQL database.
+ *
+ * @param bool $fetch If true, the rendered HTML datatable is returned, otherwise,
+ * it is echoed.
+ */
+ public function getDatabaseUsageSummary($fetch = false)
+ {
+ Piwik::checkUserIsSuperUser();
+
+ $view = $this->getDataTableView(__FUNCTION__, $viewType = 'graphPie', $orderDir = 'desc',
+ $addPercentColumn = true);
+ $view->disableOffsetInformationAndPaginationControls();
+
+ if ($view instanceof Piwik_ViewDataTable_GenerateGraphHTML) {
+ $view->showAllTicks();
+ }
+
+ // translate the labels themselves
+ $translateSummaryLabel = array($this, 'translateSummarylabel');
+ $view->queueFilter('ColumnCallbackReplace', array(array('label'), $translateSummaryLabel),
+ $runBeforeGenericFilters = true);
+
+ return $this->renderView($view, $fetch);
+ }
+
+ /**
+ * Shows a datatable that displays the amount of space each individual log table
+ * takes up in the MySQL database.
+ *
+ * @param bool $fetch If true, the rendered HTML datatable is returned, otherwise,
+ * it is echoed.
+ */
+ public function getTrackerDataSummary($fetch = false)
+ {
+ Piwik::checkUserIsSuperUser();
+
+ $view = $this->getDataTableView(__FUNCTION__);
+ $view->disableOffsetInformationAndPaginationControls();
+ return $this->renderView($view, $fetch);
+ }
+
+ /**
+ * Shows a datatable that displays the amount of space each numeric archive table
+ * takes up in the MySQL database.
+ *
+ * @param bool $fetch If true, the rendered HTML datatable is returned, otherwise,
+ * it is echoed.
+ */
+ public function getMetricDataSummary($fetch = false)
+ {
+ Piwik::checkUserIsSuperUser();
+ $view = $this->getDataTableView(__FUNCTION__, $viewType = 'table', $orderDir = 'desc');
+ $view->addRelatedReports(Piwik_Translate('DBStats_MetricTables'), array(
+ 'DBStats.getMetricDataSummaryByYear' => Piwik_Translate('DBStats_MetricDataByYear')
+ ));
+ return $this->renderView($view, $fetch);
+ }
+
+ /**
+ * Shows a datatable that displays the amount of space each numeric archive table
+ * takes up in the MySQL database, for each year of numeric data.
+ *
+ * @param bool $fetch If true, the rendered HTML datatable is returned, otherwise,
+ * it is echoed.
+ */
+ public function getMetricDataSummaryByYear($fetch = false)
+ {
+ Piwik::checkUserIsSuperUser();
+ $view = $this->getDataTableView(__FUNCTION__, $viewType = 'table', $orderDir = 'desc',
+ $addPercentColumn = false, $labelKey = 'CoreHome_PeriodYear');
+ $view->addRelatedReports(Piwik_Translate('DBStats_MetricDataByYear'), array(
+ 'DBStats.getMetricDataSummary' => Piwik_Translate('DBStats_MetricTables')
+ ));
+ return $this->renderView($view, $fetch);
+ }
+
+ /**
+ * Shows a datatable that displays the amount of space each blob archive table
+ * takes up in the MySQL database.
+ *
+ * @param bool $fetch If true, the rendered HTML datatable is returned, otherwise,
+ * it is echoed.
+ */
+ public function getReportDataSummary($fetch = false)
+ {
+ Piwik::checkUserIsSuperUser();
+ $view = $this->getDataTableView(__FUNCTION__, $viewType = 'table', $orderDir = 'desc');
+ $view->addRelatedReports(Piwik_Translate('DBStats_ReportTables'), array(
+ 'DBStats.getReportDataSummaryByYear' => Piwik_Translate('DBStats_ReportDataByYear')
+ ));
+ return $this->renderView($view, $fetch);
+ }
+
+ /**
+ * Shows a datatable that displays the amount of space each blob archive table
+ * takes up in the MySQL database, for each year of blob data.
+ *
+ * @param bool $fetch If true, the rendered HTML datatable is returned, otherwise,
+ * it is echoed.
+ */
+ public function getReportDataSummaryByYear($fetch = false)
+ {
+ Piwik::checkUserIsSuperUser();
+ $view = $this->getDataTableView(__FUNCTION__, $viewType = 'table', $orderDir = 'desc',
+ $addPercentColumn = false, $labelKey = 'CoreHome_PeriodYear');
+ $view->addRelatedReports(Piwik_Translate('DBStats_ReportDataByYear'), array(
+ 'DBStats.getReportDataSummary' => Piwik_Translate('DBStats_ReportTables')
+ ));
+ return $this->renderView($view, $fetch);
+ }
+
+ /**
+ * Shows a datatable that displays how many occurances there are of each individual
+ * report type stored in the MySQL database.
+ *
+ * Goal reports and reports of the format: .*_[0-9]+ are grouped together.
+ *
+ * @param bool $fetch If true, the rendered HTML datatable is returned, otherwise,
+ * it is echoed.
+ */
+ public function getIndividualReportsSummary($fetch = false)
+ {
+ Piwik::checkUserIsSuperUser();
+ $view = $this->getDataTableView(__FUNCTION__, $viewType = 'table', $orderDir = 'asc',
+ $addPercentColumn = false, $labelKey = 'General_Report',
+ $sizeColumns = array('estimated_size'));
+
+ // this report table has some extra columns that shouldn't be shown
+ if ($view instanceof Piwik_ViewDataTable_HtmlTable) {
+ $view->setColumnsToDisplay(array('label', 'row_count', 'estimated_size'));
+ }
+
+ $this->setIndividualSummaryFooterMessage($view);
+
+ return $this->renderView($view, $fetch);
+ }
+
+ /**
+ * Shows a datatable that displays how many occurances there are of each individual
+ * metric type stored in the MySQL database.
+ *
+ * Goal metrics, metrics of the format .*_[0-9]+ and 'done...' metrics are grouped together.
+ *
+ * @param bool $fetch If true, the rendered HTML datatable is returned, otherwise,
+ * it is echoed.
+ */
+ public function getIndividualMetricsSummary($fetch = false)
+ {
+ Piwik::checkUserIsSuperUser();
+ $view = $this->getDataTableView(__FUNCTION__, $viewType = 'table', $orderDir = 'asc',
+ $addPercentColumn = false, $labelKey = 'General_Metric',
+ $sizeColumns = array('estimated_size'));
+
+ $this->setIndividualSummaryFooterMessage($view);
+
+ return $this->renderView($view, $fetch);
+ }
+
+ /**
+ * Shows a datatable that displays the amount of space each 'admin' table takes
+ * up in the MySQL database.
+ *
+ * An 'admin' table is a table that is not central to analytics functionality.
+ * So any table that isn't an archive table or a log table is an 'admin' table.
+ *
+ * @param bool $fetch If true, the rendered HTML datatable is returned, otherwise,
+ * it is echoed.
+ */
+ public function getAdminDataSummary($fetch = false)
+ {
+ Piwik::checkUserIsSuperUser();
+ $view = $this->getDataTableView(__FUNCTION__, $viewType = 'table');
+ $view->disableOffsetInformationAndPaginationControls();
+ return $this->renderView($view, $fetch);
+ }
+
+ /**
+ * Utility function that creates and prepares a ViewDataTable for this plugin.
+ */
+ private function getDataTableView($function, $viewType = 'table', $orderDir = 'asc', $addPercentColumn = false,
+ $labelKey = 'DBStats_Table', $sizeColumns = array('data_size', 'index_size'),
+ $limit = 25)
+ {
+ $columnTranslations = array(
+ 'label' => Piwik_Translate($labelKey),
+ 'year' => Piwik_Translate('CoreHome_PeriodYear'),
+ 'data_size' => Piwik_Translate('DBStats_DataSize'),
+ 'index_size' => Piwik_Translate('DBStats_IndexSize'),
+ 'total_size' => Piwik_Translate('DBStats_TotalSize'),
+ 'row_count' => Piwik_Translate('DBStats_RowCount'),
+ 'percent_total' => '%&nbsp;' . Piwik_Translate('DBStats_DBSize'),
+ 'estimated_size' => Piwik_Translate('DBStats_EstimatedSize')
+ );
+
+ $view = Piwik_ViewDataTable::factory($viewType);
+ $view->init($this->pluginName, $function, "DBStats.$function");
+ $view->setSortedColumn('label', $orderDir);
+ $view->setLimit($limit);
+ $view->setHighlightSummaryRow(true);
+ $view->disableSearchBox();
+ $view->disableExcludeLowPopulation();
+ $view->disableTagCloud();
+ $view->disableShowAllColumns();
+ $view->alwaysShowSummaryRow();
+
+ // translate columns
+ foreach ($columnTranslations as $columnName => $translation) {
+ $view->setColumnTranslation($columnName, $translation);
+ }
+
+ // add total_size column (if necessary columns are present)
+ if (in_array('data_size', $sizeColumns) && in_array('index_size', $sizeColumns)) {
+ $getTotalTableSize = array($this, 'getTotalTableSize');
+ $view->queueFilter('ColumnCallbackAddColumn',
+ array(array('data_size', 'index_size'), 'total_size', $getTotalTableSize),
+ $runBeforeGenericFilters = true);
+
+ $sizeColumns[] = 'total_size';
+ }
+
+ $runPrettySizeFilterBeforeGeneric = false;
+ $fixedMemoryUnit = false;
+ if ($view instanceof Piwik_ViewDataTable_HtmlTable) // if displaying a table
+ {
+ $view->disableRowEvolution();
+
+ // add summary row only if displaying a table
+ $view->queueFilter('AddSummaryRow', array(0, Piwik_Translate('General_Total'), 'label', false),
+ $runBeforeGenericFilters = true);
+
+ // add other filters
+ if ($addPercentColumn && in_array('total_size', $sizeColumns)) {
+ $view->queueFilter('ColumnCallbackAddColumnPercentage',
+ array('percent_total', 'total_size', 'total_size', $quotientPrecision = 0, $shouldSkipRows = false,
+ $getDivisorFromSummaryRow = true),
+ $runBeforeGenericFilters = true);
+ $view->setSortedColumn('percent_total', $orderDir);
+ }
+ } else if ($view instanceof Piwik_ViewDataTable_GenerateGraphData) // if displaying a graph
+ {
+ if (in_array('total_size', $sizeColumns)) {
+ $view->setColumnsToDisplay(array('label', 'total_size'));
+
+ // when displaying a graph, we force sizes to be shown as the same unit so axis labels
+ // will be readable. NOTE: The unit should depend on the smallest value of the data table,
+ // however there's no way to know this information, short of creating a custom filter. For
+ // now, just assume KB.
+ $fixedMemoryUnit = 'K';
+ $view->setAxisYUnit(' K');
+
+ $view->setSortedColumn('total_size', 'desc');
+
+ $runPrettySizeFilterBeforeGeneric = true;
+ } else {
+ $view->setColumnsToDisplay(array('label', 'row_count'));
+ $view->setAxisYUnit(' ' . Piwik_Translate('General_Rows'));
+
+ $view->setSortedColumn('row_count', 'desc');
+ }
+ }
+
+ $getPrettySize = array('Piwik', 'getPrettySizeFromBytes');
+ $params = $fixedMemoryUnit === false ? array() : array($fixedMemoryUnit);
+ $view->queueFilter(
+ 'ColumnCallbackReplace', array($sizeColumns, $getPrettySize, $params), $runPrettySizeFilterBeforeGeneric);
+
+ // jqPlot will display &nbsp; as, well, '&nbsp;', so don't replace the spaces when rendering as a graph
+ if (!($view instanceof Piwik_ViewDataTable_GenerateGraphData)) {
+ $replaceSpaces = array($this, 'replaceColumnSpaces');
+ $view->queueFilter('ColumnCallbackReplace', array($sizeColumns, $replaceSpaces));
+ }
+
+ $getPrettyNumber = array('Piwik', 'getPrettyNumber');
+ $view->queueFilter('ColumnCallbackReplace', array(array('row_count'), $getPrettyNumber));
+
+ return $view;
+ }
+
+ /**
+ * Replaces spaces w/ &nbsp; for correct HTML output.
+ */
+ public function replaceColumnSpaces($value)
+ {
+ return str_replace(' ', '&nbsp;', $value);
+ }
+
+ /**
+ * Row callback function that calculates a tables total size.
+ */
+ public function getTotalTableSize($dataSize, $indexSize)
+ {
+ return $dataSize + $indexSize;
+ }
+
+ /**
+ * Column callback used to translate the column values in the database usage summary table.
+ */
+ public function translateSummarylabel($value)
+ {
+ static $valueToTranslationStr = array(
+ 'tracker_data' => 'DBStats_TrackerTables',
+ 'report_data' => 'DBStats_ReportTables',
+ 'metric_data' => 'DBStats_MetricTables',
+ 'other_data' => 'DBStats_OtherTables'
+ );
+
+ return isset($valueToTranslationStr[$value])
+ ? Piwik_Translate($valueToTranslationStr[$value])
+ : $value;
+ }
+
+ /**
+ * Sets the footer message for the Individual...Summary reports.
+ */
+ private function setIndividualSummaryFooterMessage($view)
+ {
+ $lastGenerated = Piwik_DBStats::getDateOfLastCachingRun();
+ if ($lastGenerated !== false) {
+ $view->setFooterMessage(Piwik_Translate('Mobile_LastUpdated', $lastGenerated));
+ }
+ }
}
diff --git a/plugins/DBStats/DBStats.php b/plugins/DBStats/DBStats.php
index 01a745c3ef..479596f30a 100644
--- a/plugins/DBStats/DBStats.php
+++ b/plugins/DBStats/DBStats.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_DBStats
*/
@@ -15,70 +15,70 @@
*/
class Piwik_DBStats extends Piwik_Plugin
{
- const TIME_OF_LAST_TASK_RUN_OPTION = 'dbstats_time_of_last_cache_task_run';
-
- public function getInformation()
- {
- return array(
- 'description' => Piwik_Translate('DBStats_PluginDescription'),
- 'author' => 'Piwik',
- 'author_homepage' => 'http://piwik.org/',
- 'version' => Piwik_Version::VERSION,
- );
- }
+ const TIME_OF_LAST_TASK_RUN_OPTION = 'dbstats_time_of_last_cache_task_run';
- function getListHooksRegistered()
- {
- return array(
- 'AdminMenu.add' => 'addMenu',
+ public function getInformation()
+ {
+ return array(
+ 'description' => Piwik_Translate('DBStats_PluginDescription'),
+ 'author' => 'Piwik',
+ 'author_homepage' => 'http://piwik.org/',
+ 'version' => Piwik_Version::VERSION,
+ );
+ }
+
+ function getListHooksRegistered()
+ {
+ return array(
+ 'AdminMenu.add' => 'addMenu',
'TaskScheduler.getScheduledTasks' => 'getScheduledTasks',
- );
- }
-
- function addMenu()
- {
- Piwik_AddAdminSubMenu('CoreAdminHome_MenuDiagnostic', 'DBStats_DatabaseUsage',
- array('module' => 'DBStats', 'action' => 'index'),
- Piwik::isUserIsSuperUser(),
- $order = 9);
- }
+ );
+ }
+
+ function addMenu()
+ {
+ Piwik_AddAdminSubMenu('CoreAdminHome_MenuDiagnostic', 'DBStats_DatabaseUsage',
+ array('module' => 'DBStats', 'action' => 'index'),
+ Piwik::isUserIsSuperUser(),
+ $order = 9);
+ }
+
+ /**
+ * Gets all scheduled tasks executed by this plugin.
+ *
+ * @param Piwik_Event_Notification $notification notification object
+ */
+ public function getScheduledTasks($notification)
+ {
+ $tasks = & $notification->getNotificationObject();
+
+ $cacheDataByArchiveNameReportsTask = new Piwik_ScheduledTask(
+ $this,
+ 'cacheDataByArchiveNameReports',
+ null,
+ new Piwik_ScheduledTime_Weekly(),
+ Piwik_ScheduledTask::LOWEST_PRIORITY
+ );
+ $tasks[] = $cacheDataByArchiveNameReportsTask;
+ }
+
+ /**
+ * Caches the intermediate DataTables used in the getIndividualReportsSummary and
+ * getIndividualMetricsSummary reports in the option table.
+ */
+ public function cacheDataByArchiveNameReports()
+ {
+ $api = Piwik_DBStats_API::getInstance();
+ $api->getIndividualReportsSummary(true);
+ $api->getIndividualMetricsSummary(true);
- /**
- * Gets all scheduled tasks executed by this plugin.
- *
- * @param Piwik_Event_Notification $notification notification object
- */
- public function getScheduledTasks($notification)
- {
- $tasks = &$notification->getNotificationObject();
+ $now = Piwik_Date::now()->getLocalized("%longYear%, %shortMonth% %day%");
+ Piwik_SetOption(self::TIME_OF_LAST_TASK_RUN_OPTION, $now);
+ }
- $cacheDataByArchiveNameReportsTask = new Piwik_ScheduledTask(
- $this,
- 'cacheDataByArchiveNameReports',
- null,
- new Piwik_ScheduledTime_Weekly(),
- Piwik_ScheduledTask::LOWEST_PRIORITY
- );
- $tasks[] = $cacheDataByArchiveNameReportsTask;
- }
-
- /**
- * Caches the intermediate DataTables used in the getIndividualReportsSummary and
- * getIndividualMetricsSummary reports in the option table.
- */
- public function cacheDataByArchiveNameReports()
- {
- $api = Piwik_DBStats_API::getInstance();
- $api->getIndividualReportsSummary(true);
- $api->getIndividualMetricsSummary(true);
-
- $now = Piwik_Date::now()->getLocalized("%longYear%, %shortMonth% %day%");
- Piwik_SetOption(self::TIME_OF_LAST_TASK_RUN_OPTION, $now);
- }
-
- /** Returns the date when the cacheDataByArchiveNameReports was last run. */
- public static function getDateOfLastCachingRun()
- {
- return Piwik_GetOption(self::TIME_OF_LAST_TASK_RUN_OPTION);
- }
+ /** Returns the date when the cacheDataByArchiveNameReports was last run. */
+ public static function getDateOfLastCachingRun()
+ {
+ return Piwik_GetOption(self::TIME_OF_LAST_TASK_RUN_OPTION);
+ }
}
diff --git a/plugins/DBStats/MySQLMetadataProvider.php b/plugins/DBStats/MySQLMetadataProvider.php
index ccb3ae2431..6240a24b19 100755
--- a/plugins/DBStats/MySQLMetadataProvider.php
+++ b/plugins/DBStats/MySQLMetadataProvider.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_DBStats
*/
@@ -13,374 +13,348 @@
* Utility class that provides general information about databases, including the size of
* the entire database, the size and row count of each table and the size and row count
* of each metric/report type currently stored.
- *
+ *
* This class will cache the table information it retrieves from the database. In order to
* issue a new query instead of using this cache, you must create a new instance of this type.
*/
class Piwik_DBStats_MySQLMetadataProvider
{
- /**
- * Cached MySQL table statuses. So we won't needlessly re-issue SHOW TABLE STATUS queries.
- */
- private $tableStatuses = null;
-
- /**
- * Constructor.
- */
- public function __construct()
- {
- // empty
- }
-
- /**
- * Gets general database info that is not specific to any table.
- *
- * @return array See http://dev.mysql.com/doc/refman/5.1/en/show-status.html .
- */
- public function getDBStatus()
- {
- if (function_exists('mysql_connect'))
- {
- $configDb = Piwik_Config::getInstance()->database;
- $link = mysql_connect($configDb['host'], $configDb['username'], $configDb['password']);
- $status = mysql_stat($link);
- mysql_close($link);
- $status = explode(" ", $status);
- }
- else
- {
- $fullStatus = Piwik_FetchAssoc('SHOW STATUS');
- if (empty($fullStatus))
- {
- throw new Exception('Error, SHOW STATUS failed');
- }
-
- $status = array(
- 'Uptime' => $fullStatus['Uptime']['Value'],
- 'Threads' => $fullStatus['Threads_running']['Value'],
- 'Questions' => $fullStatus['Questions']['Value'],
- 'Slow queries' => $fullStatus['Slow_queries']['Value'],
- 'Flush tables' => $fullStatus['Flush_commands']['Value'],
- 'Open tables' => $fullStatus['Open_tables']['Value'],
- 'Opens' => 'unavailable', // not available via SHOW STATUS
- 'Queries per second avg' => 'unavailable' // not available via SHOW STATUS
- );
- }
-
- return $status;
- }
-
- /**
- * Gets the MySQL table status of the requested Piwik table.
- *
- * @param string $table The name of the table. Should not be prefixed (ie, 'log_visit' is
- * correct, 'piwik_log_visit' is not).
- * @return array See http://dev.mysql.com/doc/refman/5.1/en/show-table-status.html .
- */
- public function getTableStatus( $table )
- {
- $prefixed = Piwik_Common::prefixTable($table);
-
- // if we've already gotten every table status, don't issue an uneeded query
- if (!is_null($this->tableStatuses) && isset($this->tableStatuses[$prefixed]))
- {
- return $this->tableStatuses[$prefixed];
- }
- else
- {
- return Piwik_FetchRow("SHOW TABLE STATUS LIKE ?", array($prefixed));
- }
- }
-
- /**
- * Gets the result of a SHOW TABLE STATUS query for every Piwik table in the DB.
- * Non-piwik tables are ignored.
- *
- * @param string $matchingRegex Regex used to filter out tables whose name doesn't
- * match it.
- * @return array The table information. See http://dev.mysql.com/doc/refman/5.5/en/show-table-status.html
- * for specifics.
- */
- public function getAllTablesStatus( $matchingRegex = null )
- {
- if (is_null($this->tableStatuses))
- {
- $tablesPiwik = Piwik::getTablesInstalled();
-
- $this->tableStatuses = array();
- foreach(Piwik_FetchAll("SHOW TABLE STATUS") as $t)
- {
- if (in_array($t['Name'], $tablesPiwik))
- {
- $this->tableStatuses[$t['Name']] = $t;
- }
- }
- }
-
- if (is_null($matchingRegex))
- {
- return $this->tableStatuses;
- }
-
- $result = array();
- foreach ($this->tableStatuses as $status)
- {
- if (preg_match($matchingRegex, $status['Name']))
- {
- $result[] = $status;
- }
- }
- return $result;
- }
-
- /**
- * Returns table statuses for every log table.
- *
- * @return array An array of status arrays. See http://dev.mysql.com/doc/refman/5.5/en/show-table-status.html.
- */
- public function getAllLogTableStatus()
- {
- $regex = "/^".Piwik_Common::prefixTable('log_')."(?!profiling)/";
- return $this->getAllTablesStatus($regex);
- }
-
- /**
- * Returns table statuses for every numeric archive table.
- *
- * @return array An array of status arrays. See http://dev.mysql.com/doc/refman/5.5/en/show-table-status.html.
- */
- public function getAllNumericArchiveStatus()
- {
- $regex = "/^".Piwik_Common::prefixTable('archive_numeric')."_/";
- return $this->getAllTablesStatus($regex);
- }
-
- /**
- * Returns table statuses for every blob archive table.
- *
- * @return array An array of status arrays. See http://dev.mysql.com/doc/refman/5.5/en/show-table-status.html.
- */
- public function getAllBlobArchiveStatus()
- {
- $regex = "/^".Piwik_Common::prefixTable('archive_blob')."_/";
- return $this->getAllTablesStatus($regex);
- }
-
- /**
- * Retruns table statuses for every admin table.
- *
- * @return array An array of status arrays. See http://dev.mysql.com/doc/refman/5.5/en/show-table-status.html.
- */
- public function getAllAdminTableStatus()
- {
- $regex = "/^".Piwik_Common::prefixTable('')."(?!archive_|(?:log_(?!profiling)))/";
- return $this->getAllTablesStatus($regex);
- }
-
- /**
- * Returns a DataTable that lists the number of rows and the estimated amount of space
- * each blob archive type takes up in the database.
- *
- * Blob types are differentiated by name.
- *
- * @param bool $forceCache false to use the cached result, true to run the queries again and
- * cache the result.
- * @return Piwik_DataTable
- */
- public function getRowCountsAndSizeByBlobName( $forceCache = false )
- {
- $extraSelects = array("SUM(OCTET_LENGTH(value)) AS 'blob_size'", "SUM(LENGTH(name)) AS 'name_size'");
- $extraCols = array('blob_size', 'name_size');
- return $this->getRowCountsByArchiveName(
- $this->getAllBlobArchiveStatus(), 'getEstimatedBlobArchiveRowSize', $forceCache, $extraSelects,
- $extraCols);
- }
-
- /**
- * Returns a DataTable that lists the number of rows and the estimated amount of space
- * each metric archive type takes up in the database.
- *
- * Metric types are differentiated by name.
- *
- * @param bool $forceCache false to use the cached result, true to run the queries again and
- * cache the result.
- * @return Piwik_DataTable
- */
- public function getRowCountsAndSizeByMetricName( $forceCache = false )
- {
- return $this->getRowCountsByArchiveName(
- $this->getAllNumericArchiveStatus(), 'getEstimatedRowsSize', $forceCache);
- }
-
- /**
- * Utility function. Gets row count of a set of tables grouped by the 'name' column.
- * This is the implementation of the getRowCountsAndSizeBy... functions.
- */
- private function getRowCountsByArchiveName( $statuses, $getRowSizeMethod, $forceCache = false,
- $otherSelects = array(), $otherDataTableColumns = array() )
- {
- $extraCols = '';
- if (!empty($otherSelects))
- {
- $extraCols = ', '.implode(', ', $otherSelects);
- }
-
- $cols = array_merge(array('row_count'), $otherDataTableColumns);
-
- $dataTable = new Piwik_DataTable();
- foreach ($statuses as $status)
- {
- $dataTableOptionName = $this->getCachedOptionName($status['Name'], 'byArchiveName');
-
- // if option exists && !$forceCache, use the cached data, otherwise create the
- $cachedData = Piwik_GetOption($dataTableOptionName);
- if ($cachedData !== false && !$forceCache)
- {
- $table = new Piwik_DataTable();
- $table->addRowsFromSerializedArray($cachedData);
- }
- else
- {
- // otherwise, create data table & cache it
- $sql = "SELECT name as 'label', COUNT(*) as 'row_count'$extraCols FROM {$status['Name']} GROUP BY name";
-
- $table = new Piwik_DataTable();
- $table->addRowsFromSimpleArray(Piwik_FetchAll($sql));
-
- $reduceArchiveRowName = array($this, 'reduceArchiveRowName');
- $table->filter('GroupBy', array('label', $reduceArchiveRowName));
-
- $serializedTables = $table->getSerialized();
- $serializedTable = reset($serializedTables);
- Piwik_SetOption($dataTableOptionName, $serializedTable);
- }
-
- // add estimated_size column
- $getEstimatedSize = array($this, $getRowSizeMethod);
- $table->filter('ColumnCallbackAddColumn',
- array($cols, 'estimated_size', $getEstimatedSize, array($status)));
-
- $dataTable->addDataTable($table);
- destroy($table);
- }
- return $dataTable;
- }
-
- /**
- * Gets the estimated database size a count of rows takes in a table.
- */
- public function getEstimatedRowsSize( $row_count, $status )
- {
- if($status['Rows'] == 0)
- {
- return 0;
- }
- $avgRowSize = ($status['Data_length'] + $status['Index_length']) / $status['Rows'];
- return $avgRowSize * $row_count;
- }
-
- /**
- * Gets the estimated database size a count of rows in a blob_archive table. Depends on
- * the data table row to contain the size of all blobs & name strings in the row set it
- * represents.
- */
- public function getEstimatedBlobArchiveRowSize( $row_count, $blob_size, $name_size, $status )
- {
- // calculate the size of each fixed size column in a blob archive table
- static $fixedSizeColumnLength = null;
- if (is_null($fixedSizeColumnLength))
- {
- $fixedSizeColumnLength = 0;
- foreach (Piwik_FetchAll("SHOW COLUMNS FROM ".$status['Name']) as $column)
- {
- $columnType = $column['Type'];
-
- if (($paren = strpos($columnType, '(')) !== false)
- {
- $columnType = substr($columnType, 0, $paren);
- }
-
- $fixedSizeColumnLength += $this->sizeOfMySQLColumn($columnType);
- }
- }
- // calculate the average row size
- if($status['Rows'] == 0) {
- $avgRowSize = 0;
- } else {
- $avgRowSize = $status['Index_length'] / $status['Rows'] + $fixedSizeColumnLength;
- }
-
- // calculate the row set's size
- return $avgRowSize * $row_count + $blob_size + $name_size;
- }
-
- /** Returns the size in bytes of a fixed size MySQL data type. Returns 0 for unsupported data type. */
- private function sizeOfMySQLColumn( $columnType )
- {
- switch (strtolower($columnType))
- {
- case "tinyint":
- case "year":
- return 1;
- case "smallint":
- return 2;
- case "mediumint":
- case "date":
- case "time":
- return 3;
- case "int":
- case "float": // assumes precision isn't used
- case "timestamp":
- return 4;
- case "bigint":
- case "double":
- case "real":
- case "datetime":
- return 8;
- default:
- return 0;
- }
- }
-
- /**
- * Gets the option name used to cache the result of an intensive query.
- */
- private function getCachedOptionName( $tableName, $suffix )
- {
- return 'dbstats_cached_'.$tableName.'_'.$suffix;
- }
-
- /**
- * Reduces the given metric name. Used to simplify certain reports.
- *
- * Some metrics, like goal metrics, can have different string names. For goal metrics,
- * there's one name per goal ID. Grouping metrics and reports like these together
- * simplifies the tables that display them.
- *
- * This function makes goal names, 'done...' names and names of the format .*_[0-9]+
- * equivalent.
- */
- public function reduceArchiveRowName( $name )
- {
- // all 'done...' fields are considered the same
- if (strpos($name, 'done') === 0)
- {
- return 'done';
- }
-
- // check for goal id, if present (Goals_... reports should not be reduced here, just Goal_... ones)
- if (preg_match("/^Goal_(?:-?[0-9]+_)?(.*)/", $name, $matches))
- {
- $name = "Goal_*_".$matches[1];
- }
-
- // remove subtable id suffix, if present
- if (preg_match("/^(.*)_[0-9]+$/", $name, $matches))
- {
- $name = $matches[1]."_*";
- }
-
- return $name;
- }
+ /**
+ * Cached MySQL table statuses. So we won't needlessly re-issue SHOW TABLE STATUS queries.
+ */
+ private $tableStatuses = null;
+
+ /**
+ * Constructor.
+ */
+ public function __construct()
+ {
+ // empty
+ }
+
+ /**
+ * Gets general database info that is not specific to any table.
+ *
+ * @return array See http://dev.mysql.com/doc/refman/5.1/en/show-status.html .
+ */
+ public function getDBStatus()
+ {
+ if (function_exists('mysql_connect')) {
+ $configDb = Piwik_Config::getInstance()->database;
+ $link = mysql_connect($configDb['host'], $configDb['username'], $configDb['password']);
+ $status = mysql_stat($link);
+ mysql_close($link);
+ $status = explode(" ", $status);
+ } else {
+ $fullStatus = Piwik_FetchAssoc('SHOW STATUS');
+ if (empty($fullStatus)) {
+ throw new Exception('Error, SHOW STATUS failed');
+ }
+
+ $status = array(
+ 'Uptime' => $fullStatus['Uptime']['Value'],
+ 'Threads' => $fullStatus['Threads_running']['Value'],
+ 'Questions' => $fullStatus['Questions']['Value'],
+ 'Slow queries' => $fullStatus['Slow_queries']['Value'],
+ 'Flush tables' => $fullStatus['Flush_commands']['Value'],
+ 'Open tables' => $fullStatus['Open_tables']['Value'],
+ 'Opens' => 'unavailable', // not available via SHOW STATUS
+ 'Queries per second avg' => 'unavailable' // not available via SHOW STATUS
+ );
+ }
+
+ return $status;
+ }
+
+ /**
+ * Gets the MySQL table status of the requested Piwik table.
+ *
+ * @param string $table The name of the table. Should not be prefixed (ie, 'log_visit' is
+ * correct, 'piwik_log_visit' is not).
+ * @return array See http://dev.mysql.com/doc/refman/5.1/en/show-table-status.html .
+ */
+ public function getTableStatus($table)
+ {
+ $prefixed = Piwik_Common::prefixTable($table);
+
+ // if we've already gotten every table status, don't issue an uneeded query
+ if (!is_null($this->tableStatuses) && isset($this->tableStatuses[$prefixed])) {
+ return $this->tableStatuses[$prefixed];
+ } else {
+ return Piwik_FetchRow("SHOW TABLE STATUS LIKE ?", array($prefixed));
+ }
+ }
+
+ /**
+ * Gets the result of a SHOW TABLE STATUS query for every Piwik table in the DB.
+ * Non-piwik tables are ignored.
+ *
+ * @param string $matchingRegex Regex used to filter out tables whose name doesn't
+ * match it.
+ * @return array The table information. See http://dev.mysql.com/doc/refman/5.5/en/show-table-status.html
+ * for specifics.
+ */
+ public function getAllTablesStatus($matchingRegex = null)
+ {
+ if (is_null($this->tableStatuses)) {
+ $tablesPiwik = Piwik::getTablesInstalled();
+
+ $this->tableStatuses = array();
+ foreach (Piwik_FetchAll("SHOW TABLE STATUS") as $t) {
+ if (in_array($t['Name'], $tablesPiwik)) {
+ $this->tableStatuses[$t['Name']] = $t;
+ }
+ }
+ }
+
+ if (is_null($matchingRegex)) {
+ return $this->tableStatuses;
+ }
+
+ $result = array();
+ foreach ($this->tableStatuses as $status) {
+ if (preg_match($matchingRegex, $status['Name'])) {
+ $result[] = $status;
+ }
+ }
+ return $result;
+ }
+
+ /**
+ * Returns table statuses for every log table.
+ *
+ * @return array An array of status arrays. See http://dev.mysql.com/doc/refman/5.5/en/show-table-status.html.
+ */
+ public function getAllLogTableStatus()
+ {
+ $regex = "/^" . Piwik_Common::prefixTable('log_') . "(?!profiling)/";
+ return $this->getAllTablesStatus($regex);
+ }
+
+ /**
+ * Returns table statuses for every numeric archive table.
+ *
+ * @return array An array of status arrays. See http://dev.mysql.com/doc/refman/5.5/en/show-table-status.html.
+ */
+ public function getAllNumericArchiveStatus()
+ {
+ $regex = "/^" . Piwik_Common::prefixTable('archive_numeric') . "_/";
+ return $this->getAllTablesStatus($regex);
+ }
+
+ /**
+ * Returns table statuses for every blob archive table.
+ *
+ * @return array An array of status arrays. See http://dev.mysql.com/doc/refman/5.5/en/show-table-status.html.
+ */
+ public function getAllBlobArchiveStatus()
+ {
+ $regex = "/^" . Piwik_Common::prefixTable('archive_blob') . "_/";
+ return $this->getAllTablesStatus($regex);
+ }
+
+ /**
+ * Retruns table statuses for every admin table.
+ *
+ * @return array An array of status arrays. See http://dev.mysql.com/doc/refman/5.5/en/show-table-status.html.
+ */
+ public function getAllAdminTableStatus()
+ {
+ $regex = "/^" . Piwik_Common::prefixTable('') . "(?!archive_|(?:log_(?!profiling)))/";
+ return $this->getAllTablesStatus($regex);
+ }
+
+ /**
+ * Returns a DataTable that lists the number of rows and the estimated amount of space
+ * each blob archive type takes up in the database.
+ *
+ * Blob types are differentiated by name.
+ *
+ * @param bool $forceCache false to use the cached result, true to run the queries again and
+ * cache the result.
+ * @return Piwik_DataTable
+ */
+ public function getRowCountsAndSizeByBlobName($forceCache = false)
+ {
+ $extraSelects = array("SUM(OCTET_LENGTH(value)) AS 'blob_size'", "SUM(LENGTH(name)) AS 'name_size'");
+ $extraCols = array('blob_size', 'name_size');
+ return $this->getRowCountsByArchiveName(
+ $this->getAllBlobArchiveStatus(), 'getEstimatedBlobArchiveRowSize', $forceCache, $extraSelects,
+ $extraCols);
+ }
+
+ /**
+ * Returns a DataTable that lists the number of rows and the estimated amount of space
+ * each metric archive type takes up in the database.
+ *
+ * Metric types are differentiated by name.
+ *
+ * @param bool $forceCache false to use the cached result, true to run the queries again and
+ * cache the result.
+ * @return Piwik_DataTable
+ */
+ public function getRowCountsAndSizeByMetricName($forceCache = false)
+ {
+ return $this->getRowCountsByArchiveName(
+ $this->getAllNumericArchiveStatus(), 'getEstimatedRowsSize', $forceCache);
+ }
+
+ /**
+ * Utility function. Gets row count of a set of tables grouped by the 'name' column.
+ * This is the implementation of the getRowCountsAndSizeBy... functions.
+ */
+ private function getRowCountsByArchiveName($statuses, $getRowSizeMethod, $forceCache = false,
+ $otherSelects = array(), $otherDataTableColumns = array())
+ {
+ $extraCols = '';
+ if (!empty($otherSelects)) {
+ $extraCols = ', ' . implode(', ', $otherSelects);
+ }
+
+ $cols = array_merge(array('row_count'), $otherDataTableColumns);
+
+ $dataTable = new Piwik_DataTable();
+ foreach ($statuses as $status) {
+ $dataTableOptionName = $this->getCachedOptionName($status['Name'], 'byArchiveName');
+
+ // if option exists && !$forceCache, use the cached data, otherwise create the
+ $cachedData = Piwik_GetOption($dataTableOptionName);
+ if ($cachedData !== false && !$forceCache) {
+ $table = new Piwik_DataTable();
+ $table->addRowsFromSerializedArray($cachedData);
+ } else {
+ // otherwise, create data table & cache it
+ $sql = "SELECT name as 'label', COUNT(*) as 'row_count'$extraCols FROM {$status['Name']} GROUP BY name";
+
+ $table = new Piwik_DataTable();
+ $table->addRowsFromSimpleArray(Piwik_FetchAll($sql));
+
+ $reduceArchiveRowName = array($this, 'reduceArchiveRowName');
+ $table->filter('GroupBy', array('label', $reduceArchiveRowName));
+
+ $serializedTables = $table->getSerialized();
+ $serializedTable = reset($serializedTables);
+ Piwik_SetOption($dataTableOptionName, $serializedTable);
+ }
+
+ // add estimated_size column
+ $getEstimatedSize = array($this, $getRowSizeMethod);
+ $table->filter('ColumnCallbackAddColumn',
+ array($cols, 'estimated_size', $getEstimatedSize, array($status)));
+
+ $dataTable->addDataTable($table);
+ destroy($table);
+ }
+ return $dataTable;
+ }
+
+ /**
+ * Gets the estimated database size a count of rows takes in a table.
+ */
+ public function getEstimatedRowsSize($row_count, $status)
+ {
+ if ($status['Rows'] == 0) {
+ return 0;
+ }
+ $avgRowSize = ($status['Data_length'] + $status['Index_length']) / $status['Rows'];
+ return $avgRowSize * $row_count;
+ }
+
+ /**
+ * Gets the estimated database size a count of rows in a blob_archive table. Depends on
+ * the data table row to contain the size of all blobs & name strings in the row set it
+ * represents.
+ */
+ public function getEstimatedBlobArchiveRowSize($row_count, $blob_size, $name_size, $status)
+ {
+ // calculate the size of each fixed size column in a blob archive table
+ static $fixedSizeColumnLength = null;
+ if (is_null($fixedSizeColumnLength)) {
+ $fixedSizeColumnLength = 0;
+ foreach (Piwik_FetchAll("SHOW COLUMNS FROM " . $status['Name']) as $column) {
+ $columnType = $column['Type'];
+
+ if (($paren = strpos($columnType, '(')) !== false) {
+ $columnType = substr($columnType, 0, $paren);
+ }
+
+ $fixedSizeColumnLength += $this->sizeOfMySQLColumn($columnType);
+ }
+ }
+ // calculate the average row size
+ if ($status['Rows'] == 0) {
+ $avgRowSize = 0;
+ } else {
+ $avgRowSize = $status['Index_length'] / $status['Rows'] + $fixedSizeColumnLength;
+ }
+
+ // calculate the row set's size
+ return $avgRowSize * $row_count + $blob_size + $name_size;
+ }
+
+ /** Returns the size in bytes of a fixed size MySQL data type. Returns 0 for unsupported data type. */
+ private function sizeOfMySQLColumn($columnType)
+ {
+ switch (strtolower($columnType)) {
+ case "tinyint":
+ case "year":
+ return 1;
+ case "smallint":
+ return 2;
+ case "mediumint":
+ case "date":
+ case "time":
+ return 3;
+ case "int":
+ case "float": // assumes precision isn't used
+ case "timestamp":
+ return 4;
+ case "bigint":
+ case "double":
+ case "real":
+ case "datetime":
+ return 8;
+ default:
+ return 0;
+ }
+ }
+
+ /**
+ * Gets the option name used to cache the result of an intensive query.
+ */
+ private function getCachedOptionName($tableName, $suffix)
+ {
+ return 'dbstats_cached_' . $tableName . '_' . $suffix;
+ }
+
+ /**
+ * Reduces the given metric name. Used to simplify certain reports.
+ *
+ * Some metrics, like goal metrics, can have different string names. For goal metrics,
+ * there's one name per goal ID. Grouping metrics and reports like these together
+ * simplifies the tables that display them.
+ *
+ * This function makes goal names, 'done...' names and names of the format .*_[0-9]+
+ * equivalent.
+ */
+ public function reduceArchiveRowName($name)
+ {
+ // all 'done...' fields are considered the same
+ if (strpos($name, 'done') === 0) {
+ return 'done';
+ }
+
+ // check for goal id, if present (Goals_... reports should not be reduced here, just Goal_... ones)
+ if (preg_match("/^Goal_(?:-?[0-9]+_)?(.*)/", $name, $matches)) {
+ $name = "Goal_*_" . $matches[1];
+ }
+
+ // remove subtable id suffix, if present
+ if (preg_match("/^(.*)_[0-9]+$/", $name, $matches)) {
+ $name = $matches[1] . "_*";
+ }
+
+ return $name;
+ }
}
diff --git a/plugins/DBStats/templates/index.tpl b/plugins/DBStats/templates/index.tpl
index efe3550e89..5c85eea7d9 100755
--- a/plugins/DBStats/templates/index.tpl
+++ b/plugins/DBStats/templates/index.tpl
@@ -2,145 +2,154 @@
{loadJavascriptTranslations plugins='CoreAdminHome CoreHome'}
{literal}
-<style>
-.dbstatsTable {
- display: inline-block;
-}
-.dbstatsTable>tbody>tr>td:first-child {
- width: 550px;
-}
-.dbstatsTable h2 {
- width: 500px;
-}
-.adminTable.dbstatsTable a {
- color: black;
- text-decoration: underline;
-}
-</style>
+ <style>
+ .dbstatsTable {
+ display: inline-block;
+ }
+
+ .dbstatsTable>tbody>tr>td:first-child {
+ width: 550px;
+ }
+
+ .dbstatsTable h2 {
+ width: 500px;
+ }
+
+ .adminTable.dbstatsTable a {
+ color: black;
+ text-decoration: underline;
+ }
+ </style>
{/literal}
<a name="databaseUsageSummary"></a>
<h2>{'DBStats_DatabaseUsage'|translate}</h2>
<p>
- {'DBStats_MainDescription'|translate:$totalSpaceUsed}<br/>
- {'DBStats_LearnMore'|translate:"<a href='?module=Proxy&action=redirect&url=http://piwik.org/docs/setup-auto-archiving/' target='_blank'>Piwik Auto Archiving</a>"}<br/>
- <br/>
+ {'DBStats_MainDescription'|translate:$totalSpaceUsed}<br/>
+ {'DBStats_LearnMore'|translate:"<a href='?module=Proxy&action=redirect&url=http://piwik.org/docs/setup-auto-archiving/' target='_blank'>Piwik Auto Archiving</a>"}
+ <br/>
+ <br/>
</p>
<table class="adminTable dbstatsTable">
- <tbody>
- <tr>
- <td>{$databaseUsageSummary}</td>
- <td>
- <h3 style="margin-top:0">{'General_GeneralInformation'|translate}</h3><br/>
- <p style="font-size:1.4em;padding-left:21px;line-height:1.8em">
- <strong><em>{$userCount}</strong></em>&nbsp;{if $userCount == 1}{'UsersManager_User'|translate}{else}{'UsersManager_MenuUsers'|translate}{/if}<br/>
- <strong><em>{$siteCount}</strong></em>&nbsp;{if $siteCount == 1}{'General_Website'|translate}{else}{'Referers_Websites'|translate}{/if}
- </p><br/>
- {capture assign=clickDeleteLogSettings}{'PrivacyManager_DeleteDataSettings'|translate}{/capture}
- <h3 style="margin-top:0">{'PrivacyManager_DeleteDataSettings'|translate}</h3><br/>
- <p>
- {'PrivacyManager_DeleteDataDescription'|translate}
- <br/>
- <a href='{url module="PrivacyManager" action="privacySettings"}#deleteLogsAnchor'>
- {'PrivacyManager_ClickHereSettings'|translate:"'$clickDeleteLogSettings'"}
- </a>
- </p>
- </td>
- </tr>
- </tbody>
+ <tbody>
+ <tr>
+ <td>{$databaseUsageSummary}</td>
+ <td>
+ <h3 style="margin-top:0">{'General_GeneralInformation'|translate}</h3><br/>
+
+ <p style="font-size:1.4em;padding-left:21px;line-height:1.8em">
+ <strong><em>{$userCount}</strong></em>&nbsp;{if $userCount == 1}{'UsersManager_User'|translate}{else}{'UsersManager_MenuUsers'|translate}{/if}
+ <br/>
+ <strong><em>{$siteCount}</strong></em>&nbsp;{if $siteCount == 1}{'General_Website'|translate}{else}{'Referers_Websites'|translate}{/if}
+ </p><br/>
+ {capture assign=clickDeleteLogSettings}{'PrivacyManager_DeleteDataSettings'|translate}{/capture}
+ <h3 style="margin-top:0">{'PrivacyManager_DeleteDataSettings'|translate}</h3><br/>
+
+ <p>
+ {'PrivacyManager_DeleteDataDescription'|translate}
+ <br/>
+ <a href='{url module="PrivacyManager" action="privacySettings"}#deleteLogsAnchor'>
+ {'PrivacyManager_ClickHereSettings'|translate:"'$clickDeleteLogSettings'"}
+ </a>
+ </p>
+ </td>
+ </tr>
+ </tbody>
</table>
<br/>
<a name="trackerDataSummary"></a>
<table class="adminTable dbstatsTable">
- <tbody>
- <tr>
- <td>
- <h2>{'DBStats_TrackerTables'|translate}</h2>
- {$trackerDataSummary}
- </td>
- <td>&nbsp;</td>
- </tr>
- </tbody>
+ <tbody>
+ <tr>
+ <td>
+ <h2>{'DBStats_TrackerTables'|translate}</h2>
+ {$trackerDataSummary}
+ </td>
+ <td>&nbsp;</td>
+ </tr>
+ </tbody>
</table>
<a name="reportDataSummary"></a>
<table class="adminTable dbstatsTable">
- <tbody>
- <tr>
- <td>
- <h2>{'DBStats_ReportTables'|translate}</h2>
- {$reportDataSummary}
- </td>
- <td>
- <h2>{'General_Reports'|translate}</h2>
- <div class="ajaxLoad" action="getIndividualReportsSummary">
- <span class="loadingPiwik"><img src="themes/default/images/loading-blue.gif" />{'General_LoadingData'|translate}</span>
- </div>
- </td>
- </tr>
- </tbody>
+ <tbody>
+ <tr>
+ <td>
+ <h2>{'DBStats_ReportTables'|translate}</h2>
+ {$reportDataSummary}
+ </td>
+ <td>
+ <h2>{'General_Reports'|translate}</h2>
+
+ <div class="ajaxLoad" action="getIndividualReportsSummary">
+ <span class="loadingPiwik"><img src="themes/default/images/loading-blue.gif"/>{'General_LoadingData'|translate}</span>
+ </div>
+ </td>
+ </tr>
+ </tbody>
</table>
<a name="metricDataSummary"></a>
<table class="adminTable dbstatsTable">
- <tbody>
- <tr>
- <td>
- <h2>{'DBStats_MetricTables'|translate}</h2>
- {$metricDataSummary}
- </td>
- <td>
- <h2>{'General_Metrics'|translate}</h2>
- <div class="ajaxLoad" action="getIndividualMetricsSummary">
- <span class="loadingPiwik"><img src="themes/default/images/loading-blue.gif" />{'General_LoadingData'|translate}</span>
- </div>
- </td>
- </tr>
- </tbody>
+ <tbody>
+ <tr>
+ <td>
+ <h2>{'DBStats_MetricTables'|translate}</h2>
+ {$metricDataSummary}
+ </td>
+ <td>
+ <h2>{'General_Metrics'|translate}</h2>
+
+ <div class="ajaxLoad" action="getIndividualMetricsSummary">
+ <span class="loadingPiwik"><img src="themes/default/images/loading-blue.gif"/>{'General_LoadingData'|translate}</span>
+ </div>
+ </td>
+ </tr>
+ </tbody>
</table>
<a name="adminDataSummary"></a>
<table class="adminTable dbstatsTable">
- <tbody>
- <tr>
- <td>
- <h2>{'DBStats_OtherTables'|translate}</h2>
- {$adminDataSummary}
- </td>
- <td>&nbsp;</td>
- </tr>
- </tbody>
+ <tbody>
+ <tr>
+ <td>
+ <h2>{'DBStats_OtherTables'|translate}</h2>
+ {$adminDataSummary}
+ </td>
+ <td>&nbsp;</td>
+ </tr>
+ </tbody>
</table>
{literal}
-<script type="text/javascript">
-(function( $ ){
- $(document).ready(function() {
- $('.ajaxLoad').each(function() {
- var self = this;
- var action = $(this).attr('action');
-
- // build & execute AJAX request
- var ajaxRequest = new ajaxHelper();
- ajaxRequest.addParams({
- module: 'DBStats',
- action: action,
- viewDataTable: 'table'
- }, 'get');
- ajaxRequest.setCallback(
- function (data) {
- $('.loadingPiwik', self).remove();
- $(self).html(data);
- }
- );
- ajaxRequest.setFormat('html');
- ajaxRequest.send(false);
- });
- });
-})( jQuery );
-</script>
+ <script type="text/javascript">
+ (function ($) {
+ $(document).ready(function () {
+ $('.ajaxLoad').each(function () {
+ var self = this;
+ var action = $(this).attr('action');
+
+ // build & execute AJAX request
+ var ajaxRequest = new ajaxHelper();
+ ajaxRequest.addParams({
+ module: 'DBStats',
+ action: action,
+ viewDataTable: 'table'
+ }, 'get');
+ ajaxRequest.setCallback(
+ function (data) {
+ $('.loadingPiwik', self).remove();
+ $(self).html(data);
+ }
+ );
+ ajaxRequest.setFormat('html');
+ ajaxRequest.send(false);
+ });
+ });
+ })(jQuery);
+ </script>
{/literal}
{include file="CoreAdminHome/templates/footer.tpl"}
diff --git a/plugins/Dashboard/Controller.php b/plugins/Dashboard/Controller.php
index cb4e3fe1ee..be49ce1ade 100644
--- a/plugins/Dashboard/Controller.php
+++ b/plugins/Dashboard/Controller.php
@@ -23,7 +23,7 @@ class Piwik_Dashboard_Controller extends Piwik_Controller
$view->availableWidgets = Piwik_Common::json_encode(Piwik_GetWidgetsList());
$view->availableLayouts = $this->getAvailableLayouts();
- $view->dashboardId = Piwik_Common::getRequestVar('idDashboard', 1, 'int');
+ $view->dashboardId = Piwik_Common::getRequestVar('idDashboard', 1, 'int');
$view->dashboardLayout = $this->getLayout($view->dashboardId);
return $view;
@@ -38,7 +38,7 @@ class Piwik_Dashboard_Controller extends Piwik_Controller
public function index()
{
- $view = $this->_getDashboardView('standalone');
+ $view = $this->_getDashboardView('standalone');
$view->dashboards = array();
if (!Piwik::isUserIsAnonymous()) {
$login = Piwik::getCurrentUserLogin();
@@ -52,7 +52,7 @@ class Piwik_Dashboard_Controller extends Piwik_Controller
{
$this->checkTokenInUrl();
- Piwik_DataTable_Renderer_Json::sendHeaderJSON();
+ Piwik_DataTable_Renderer_Json::sendHeaderJSON();
echo Piwik_Common::json_encode(Piwik_GetWidgetsList());
}
@@ -73,10 +73,10 @@ class Piwik_Dashboard_Controller extends Piwik_Controller
public function resetLayout()
{
$this->checkTokenInUrl();
- $layout = $this->getDefaultLayout();
+ $layout = $this->getDefaultLayout();
$idDashboard = Piwik_Common::getRequestVar('idDashboard', 1, 'int');
if (Piwik::isUserIsAnonymous()) {
- $session = new Piwik_Session_Namespace("Piwik_Dashboard");
+ $session = new Piwik_Session_Namespace("Piwik_Dashboard");
$session->dashboardLayout = $layout;
$session->setExpirationSeconds(1800);
} else {
@@ -88,14 +88,14 @@ class Piwik_Dashboard_Controller extends Piwik_Controller
* Records the layout in the DB for the given user.
*
* @param string $login
- * @param int $idDashboard
+ * @param int $idDashboard
* @param string $layout
*/
protected function saveLayoutForUser($login, $idDashboard, $layout)
{
$paramsBind = array($login, $idDashboard, $layout, $layout);
- $query = sprintf('INSERT INTO %s (login, iddashboard, layout) VALUES (?,?,?) ON DUPLICATE KEY UPDATE layout=?',
- Piwik_Common::prefixTable('user_dashboard'));
+ $query = sprintf('INSERT INTO %s (login, iddashboard, layout) VALUES (?,?,?) ON DUPLICATE KEY UPDATE layout=?',
+ Piwik_Common::prefixTable('user_dashboard'));
Piwik_Query($query, $paramsBind);
}
@@ -103,14 +103,14 @@ class Piwik_Dashboard_Controller extends Piwik_Controller
* Updates the name of a dashboard
*
* @param string $login
- * @param int $idDashboard
+ * @param int $idDashboard
* @param string $name
*/
protected function updateDashboardName($login, $idDashboard, $name)
{
$paramsBind = array($name, $login, $idDashboard);
- $query = sprintf('UPDATE %s SET name = ? WHERE login = ? AND iddashboard = ?',
- Piwik_Common::prefixTable('user_dashboard'));
+ $query = sprintf('UPDATE %s SET name = ? WHERE login = ? AND iddashboard = ?',
+ Piwik_Common::prefixTable('user_dashboard'));
Piwik_Query($query, $paramsBind);
}
@@ -119,16 +119,16 @@ class Piwik_Dashboard_Controller extends Piwik_Controller
* Parameters must be checked BEFORE this function call
*
* @param string $login
- * @param int $idDashboard
+ * @param int $idDashboard
*
* @return bool
*/
protected function _getLayoutForUser($login, $idDashboard)
{
$paramsBind = array($login, $idDashboard);
- $query = sprintf('SELECT layout FROM %s WHERE login = ? AND iddashboard = ?',
- Piwik_Common::prefixTable('user_dashboard'));
- $return = Piwik_FetchAll($query, $paramsBind);
+ $query = sprintf('SELECT layout FROM %s WHERE login = ? AND iddashboard = ?',
+ Piwik_Common::prefixTable('user_dashboard'));
+ $return = Piwik_FetchAll($query, $paramsBind);
if (count($return) == 0) {
return false;
@@ -153,7 +153,7 @@ class Piwik_Dashboard_Controller extends Piwik_Controller
// first layout can't be removed
if ($idDashboard != 1) {
$query = sprintf('DELETE FROM %s WHERE iddashboard = ? AND login = ?',
- Piwik_Common::prefixTable('user_dashboard'));
+ Piwik_Common::prefixTable('user_dashboard'));
Piwik_Query($query, array($idDashboard, Piwik::getCurrentUserLogin()));
}
}
@@ -165,8 +165,8 @@ class Piwik_Dashboard_Controller extends Piwik_Controller
{
$this->checkTokenInUrl();
if (Piwik::isUserIsAnonymous()) {
- echo '[]';
- return;
+ echo '[]';
+ return;
}
$login = Piwik::getCurrentUserLogin();
@@ -185,14 +185,14 @@ class Piwik_Dashboard_Controller extends Piwik_Controller
$this->checkTokenInUrl();
if (Piwik::isUserIsAnonymous()) {
- echo '0';
- return;
+ echo '0';
+ return;
}
- $user = Piwik::getCurrentUserLogin();
+ $user = Piwik::getCurrentUserLogin();
$nextId = $this->getNextIdDashboard($user);
- $name = urldecode(Piwik_Common::getRequestVar('name', '', 'string'));
- $type = urldecode(Piwik_Common::getRequestVar('type', 'default', 'string'));
+ $name = urldecode(Piwik_Common::getRequestVar('name', '', 'string'));
+ $type = urldecode(Piwik_Common::getRequestVar('type', 'default', 'string'));
$layout = '{}';
if ($type == 'default') {
@@ -200,50 +200,50 @@ class Piwik_Dashboard_Controller extends Piwik_Controller
}
$query = sprintf('INSERT INTO %s (login, iddashboard, name, layout) VALUES (?, ?, ?, ?)',
- Piwik_Common::prefixTable('user_dashboard'));
+ Piwik_Common::prefixTable('user_dashboard'));
Piwik_Query($query, array($user, $nextId, $name, $layout));
Piwik_DataTable_Renderer_Json::sendHeaderJSON();
echo Piwik_Common::json_encode($nextId);
}
- private function getNextIdDashboard($login)
- {
- $nextIdQuery = sprintf('SELECT MAX(iddashboard)+1 FROM %s WHERE login = ?',
- Piwik_Common::prefixTable('user_dashboard'));
- $nextId = Piwik_FetchOne($nextIdQuery, array($login));
+ private function getNextIdDashboard($login)
+ {
+ $nextIdQuery = sprintf('SELECT MAX(iddashboard)+1 FROM %s WHERE login = ?',
+ Piwik_Common::prefixTable('user_dashboard'));
+ $nextId = Piwik_FetchOne($nextIdQuery, array($login));
- if (empty($nextId)) {
- $nextId = 1;
- return $nextId;
- }
- return $nextId;
- }
+ if (empty($nextId)) {
+ $nextId = 1;
+ return $nextId;
+ }
+ return $nextId;
+ }
- public function copyDashboardToUser()
+ public function copyDashboardToUser()
{
$this->checkTokenInUrl();
if (!Piwik::isUserIsSuperUser()) {
- echo '0';
- return;
+ echo '0';
+ return;
}
- $login = Piwik::getCurrentUserLogin();
- $name = urldecode(Piwik_Common::getRequestVar('name', '', 'string'));
- $user = urldecode(Piwik_Common::getRequestVar('user', '', 'string'));
+ $login = Piwik::getCurrentUserLogin();
+ $name = urldecode(Piwik_Common::getRequestVar('name', '', 'string'));
+ $user = urldecode(Piwik_Common::getRequestVar('user', '', 'string'));
$idDashboard = Piwik_Common::getRequestVar('dashboardId', 0, 'int');
- $layout = $this->_getLayoutForUser($login, $idDashboard);
+ $layout = $this->_getLayoutForUser($login, $idDashboard);
- if($layout !== false) {
- $nextId = $this->getNextIdDashboard($user);
+ if ($layout !== false) {
+ $nextId = $this->getNextIdDashboard($user);
- $query = sprintf('INSERT INTO %s (login, iddashboard, name, layout) VALUES (?, ?, ?, ?)',
- Piwik_Common::prefixTable('user_dashboard'));
- Piwik_Query($query, array($user, $nextId, $name, $layout));
+ $query = sprintf('INSERT INTO %s (login, iddashboard, name, layout) VALUES (?, ?, ?, ?)',
+ Piwik_Common::prefixTable('user_dashboard'));
+ Piwik_Query($query, array($user, $nextId, $name, $layout));
- Piwik_DataTable_Renderer_Json::sendHeaderJSON();
- echo Piwik_Common::json_encode($nextId);
- return;
+ Piwik_DataTable_Renderer_Json::sendHeaderJSON();
+ echo Piwik_Common::json_encode($nextId);
+ return;
}
}
@@ -256,11 +256,11 @@ class Piwik_Dashboard_Controller extends Piwik_Controller
{
$this->checkTokenInUrl();
- $layout = Piwik_Common::unsanitizeInputValue(Piwik_Common::getRequestVar('layout'));
+ $layout = Piwik_Common::unsanitizeInputValue(Piwik_Common::getRequestVar('layout'));
$idDashboard = Piwik_Common::getRequestVar('idDashboard', 1, 'int');
- $name = Piwik_Common::getRequestVar('name', '', 'string');
+ $name = Piwik_Common::getRequestVar('name', '', 'string');
if (Piwik::isUserIsAnonymous()) {
- $session = new Piwik_Session_Namespace("Piwik_Dashboard");
+ $session = new Piwik_Session_Namespace("Piwik_Dashboard");
$session->dashboardLayout = $layout;
$session->setExpirationSeconds(1800);
} else {
@@ -279,10 +279,10 @@ class Piwik_Dashboard_Controller extends Piwik_Controller
$this->checkTokenInUrl();
if (Piwik::isUserIsSuperUser()) {
- $layout = Piwik_Common::unsanitizeInputValue(Piwik_Common::getRequestVar('layout'));
+ $layout = Piwik_Common::unsanitizeInputValue(Piwik_Common::getRequestVar('layout'));
$paramsBind = array('', '1', $layout, $layout);
- $query = sprintf('INSERT INTO %s (login, iddashboard, layout) VALUES (?,?,?) ON DUPLICATE KEY UPDATE layout=?',
- Piwik_Common::prefixTable('user_dashboard'));
+ $query = sprintf('INSERT INTO %s (login, iddashboard, layout) VALUES (?,?,?) ON DUPLICATE KEY UPDATE layout=?',
+ Piwik_Common::prefixTable('user_dashboard'));
Piwik_Query($query, $paramsBind);
}
}
@@ -344,7 +344,7 @@ class Piwik_Dashboard_Controller extends Piwik_Controller
foreach ($row as $widgetId => $widget) {
if (isset($widget->parameters->module)) {
- $controllerName = $widget->parameters->module;
+ $controllerName = $widget->parameters->module;
$controllerAction = $widget->parameters->action;
if (!Piwik_IsWidgetDefined($controllerName, $controllerAction)) {
unset($row[$widgetId]);
@@ -363,18 +363,15 @@ class Piwik_Dashboard_Controller extends Piwik_Controller
$defaultLayout = $this->_getLayoutForUser('', 1);
if (empty($defaultLayout)) {
- $topWidget = '';
- if (Piwik::isUserIsSuperUser())
- {
- $topWidget = '{"uniqueId":"widgetCoreHomegetDonateForm",'
- . '"parameters":{"module":"CoreHome","action":"getDonateForm"}},';
- }
- else
- {
- $topWidget = '{"uniqueId":"widgetCoreHomegetPromoVideo",'
- . '"parameters":{"module":"CoreHome","action":"getPromoVideo"}},';
- }
-
+ $topWidget = '';
+ if (Piwik::isUserIsSuperUser()) {
+ $topWidget = '{"uniqueId":"widgetCoreHomegetDonateForm",'
+ . '"parameters":{"module":"CoreHome","action":"getDonateForm"}},';
+ } else {
+ $topWidget = '{"uniqueId":"widgetCoreHomegetPromoVideo",'
+ . '"parameters":{"module":"CoreHome","action":"getPromoVideo"}},';
+ }
+
$defaultLayout = '[
[
{"uniqueId":"widgetVisitsSummarygetEvolutionGraphcolumnsArray","parameters":{"module":"VisitsSummary","action":"getEvolutionGraph","columns":"nb_visits"}},
@@ -382,7 +379,7 @@ class Piwik_Dashboard_Controller extends Piwik_Controller
{"uniqueId":"widgetVisitorInterestgetNumberOfVisitsPerVisitDuration","parameters":{"module":"VisitorInterest","action":"getNumberOfVisitsPerVisitDuration"}}
],
[
- '.$topWidget.'
+ ' . $topWidget . '
{"uniqueId":"widgetReferersgetKeywords","parameters":{"module":"Referers","action":"getKeywords"}},
{"uniqueId":"widgetReferersgetWebsites","parameters":{"module":"Referers","action":"getWebsites"}}
],
diff --git a/plugins/Dashboard/Dashboard.php b/plugins/Dashboard/Dashboard.php
index 10afa65382..50a71bf28c 100644
--- a/plugins/Dashboard/Dashboard.php
+++ b/plugins/Dashboard/Dashboard.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_Dashboard
*/
@@ -14,153 +14,146 @@
*/
class Piwik_Dashboard extends Piwik_Plugin
{
- public function getInformation()
- {
- return array(
- 'description' => Piwik_Translate('Dashboard_PluginDescription'),
- 'author' => 'Piwik',
- 'author_homepage' => 'http://piwik.org/',
- 'version' => Piwik_Version::VERSION,
- );
- }
-
- public function getListHooksRegistered()
- {
- return array(
- 'AssetManager.getJsFiles' => 'getJsFiles',
- 'AssetManager.getCssFiles' => 'getCssFiles',
- 'UsersManager.deleteUser' => 'deleteDashboardLayout',
- 'Menu.add' => 'addMenus',
- 'TopMenu.add' => 'addTopMenu',
- );
- }
-
- public static function getAllDashboards($login) {
- $dashboards = Piwik_FetchAll('SELECT iddashboard, name
- FROM '.Piwik_Common::prefixTable('user_dashboard') .
- ' WHERE login = ? ORDER BY iddashboard', array($login));
- $pos = 0;
- $nameless = 1;
- foreach ($dashboards AS &$dashboard) {
- if (empty($dashboard['name'])) {
- $dashboard['name'] = Piwik_Translate('Dashboard_DashboardOf', $login);
- if($nameless > 1) {
- $dashboard['name'] .= " ($nameless)";
- }
- if(empty($dashboard['layout']))
- {
- $layout = '[]';
- }
- else
- {
- $layout = html_entity_decode($dashboard['layout']);
- $layout = str_replace("\\\"", "\"", $layout);
- }
- $dashboard['layout'] = Piwik_Common::json_decode($layout);
- $nameless++;
- }
- $dashboard['name'] = Piwik_Common::unsanitizeInputValue($dashboard['name']);
- $pos++;
- }
- return $dashboards;
- }
-
- public function addMenus()
- {
- Piwik_AddMenu('Dashboard_Dashboard', '', array('module' => 'Dashboard', 'action' => 'embeddedIndex', 'idDashboard' => 1), true, 5);
-
- if (!Piwik::isUserIsAnonymous()) {
- $login = Piwik::getCurrentUserLogin();
-
- $dashboards = self::getAllDashboards($login);
- if (count($dashboards) > 1)
- {
- $pos = 0;
- foreach ($dashboards AS $dashboard) {
- Piwik_AddMenu('Dashboard_Dashboard', $dashboard['name'], array('module' => 'Dashboard', 'action' => 'embeddedIndex', 'idDashboard' => $dashboard['iddashboard']), true, $pos);
- $pos++;
- }
- }
-
- }
- }
-
- public function addTopMenu()
- {
- $tooltip = false;
- try
- {
- $idSite = Piwik_Common::getRequestVar('idSite');
- $tooltip = Piwik_Translate('Dashboard_TopLinkTooltip', Piwik_Site::getNameFor($idSite));
- }
- catch (Exception $ex)
- {
- // if no idSite parameter, show no tooltip
- }
-
- $urlParams = array('module' => 'CoreHome', 'action' => 'index');
- Piwik_AddTopMenu('General_Dashboard', $urlParams, true, 1, $isHTML = false, $tooltip);
- }
-
- /**
- * @param Piwik_Event_Notification $notification notification object
- */
- function getJsFiles( $notification )
- {
- $jsFiles = &$notification->getNotificationObject();
-
- $jsFiles[] = "plugins/Dashboard/templates/widgetMenu.js";
- $jsFiles[] = "libs/javascript/json2.js";
- $jsFiles[] = "plugins/Dashboard/templates/dashboardObject.js";
- $jsFiles[] = "plugins/Dashboard/templates/dashboardWidget.js";
- $jsFiles[] = "plugins/Dashboard/templates/dashboard.js";
- }
-
- /**
- * @param Piwik_Event_Notification $notification notification object
- */
- function getCssFiles( $notification )
- {
- $cssFiles = &$notification->getNotificationObject();
-
- $cssFiles[] = "plugins/CoreHome/templates/datatable.css";
- $cssFiles[] = "plugins/Dashboard/templates/dashboard.css";
- }
-
- /**
- * @param Piwik_Event_Notification $notification notification object
- */
- function deleteDashboardLayout($notification)
- {
- $userLogin = $notification->getNotificationObject();
- Piwik_Query('DELETE FROM ' . Piwik_Common::prefixTable('user_dashboard') . ' WHERE login = ?', array($userLogin));
- }
-
- public function install()
- {
- // we catch the exception
- try{
- $sql = "CREATE TABLE ". Piwik_Common::prefixTable('user_dashboard')." (
+ public function getInformation()
+ {
+ return array(
+ 'description' => Piwik_Translate('Dashboard_PluginDescription'),
+ 'author' => 'Piwik',
+ 'author_homepage' => 'http://piwik.org/',
+ 'version' => Piwik_Version::VERSION,
+ );
+ }
+
+ public function getListHooksRegistered()
+ {
+ return array(
+ 'AssetManager.getJsFiles' => 'getJsFiles',
+ 'AssetManager.getCssFiles' => 'getCssFiles',
+ 'UsersManager.deleteUser' => 'deleteDashboardLayout',
+ 'Menu.add' => 'addMenus',
+ 'TopMenu.add' => 'addTopMenu',
+ );
+ }
+
+ public static function getAllDashboards($login)
+ {
+ $dashboards = Piwik_FetchAll('SELECT iddashboard, name
+ FROM ' . Piwik_Common::prefixTable('user_dashboard') .
+ ' WHERE login = ? ORDER BY iddashboard', array($login));
+ $pos = 0;
+ $nameless = 1;
+ foreach ($dashboards AS &$dashboard) {
+ if (empty($dashboard['name'])) {
+ $dashboard['name'] = Piwik_Translate('Dashboard_DashboardOf', $login);
+ if ($nameless > 1) {
+ $dashboard['name'] .= " ($nameless)";
+ }
+ if (empty($dashboard['layout'])) {
+ $layout = '[]';
+ } else {
+ $layout = html_entity_decode($dashboard['layout']);
+ $layout = str_replace("\\\"", "\"", $layout);
+ }
+ $dashboard['layout'] = Piwik_Common::json_decode($layout);
+ $nameless++;
+ }
+ $dashboard['name'] = Piwik_Common::unsanitizeInputValue($dashboard['name']);
+ $pos++;
+ }
+ return $dashboards;
+ }
+
+ public function addMenus()
+ {
+ Piwik_AddMenu('Dashboard_Dashboard', '', array('module' => 'Dashboard', 'action' => 'embeddedIndex', 'idDashboard' => 1), true, 5);
+
+ if (!Piwik::isUserIsAnonymous()) {
+ $login = Piwik::getCurrentUserLogin();
+
+ $dashboards = self::getAllDashboards($login);
+ if (count($dashboards) > 1) {
+ $pos = 0;
+ foreach ($dashboards AS $dashboard) {
+ Piwik_AddMenu('Dashboard_Dashboard', $dashboard['name'], array('module' => 'Dashboard', 'action' => 'embeddedIndex', 'idDashboard' => $dashboard['iddashboard']), true, $pos);
+ $pos++;
+ }
+ }
+
+ }
+ }
+
+ public function addTopMenu()
+ {
+ $tooltip = false;
+ try {
+ $idSite = Piwik_Common::getRequestVar('idSite');
+ $tooltip = Piwik_Translate('Dashboard_TopLinkTooltip', Piwik_Site::getNameFor($idSite));
+ } catch (Exception $ex) {
+ // if no idSite parameter, show no tooltip
+ }
+
+ $urlParams = array('module' => 'CoreHome', 'action' => 'index');
+ Piwik_AddTopMenu('General_Dashboard', $urlParams, true, 1, $isHTML = false, $tooltip);
+ }
+
+ /**
+ * @param Piwik_Event_Notification $notification notification object
+ */
+ function getJsFiles($notification)
+ {
+ $jsFiles = & $notification->getNotificationObject();
+
+ $jsFiles[] = "plugins/Dashboard/templates/widgetMenu.js";
+ $jsFiles[] = "libs/javascript/json2.js";
+ $jsFiles[] = "plugins/Dashboard/templates/dashboardObject.js";
+ $jsFiles[] = "plugins/Dashboard/templates/dashboardWidget.js";
+ $jsFiles[] = "plugins/Dashboard/templates/dashboard.js";
+ }
+
+ /**
+ * @param Piwik_Event_Notification $notification notification object
+ */
+ function getCssFiles($notification)
+ {
+ $cssFiles = & $notification->getNotificationObject();
+
+ $cssFiles[] = "plugins/CoreHome/templates/datatable.css";
+ $cssFiles[] = "plugins/Dashboard/templates/dashboard.css";
+ }
+
+ /**
+ * @param Piwik_Event_Notification $notification notification object
+ */
+ function deleteDashboardLayout($notification)
+ {
+ $userLogin = $notification->getNotificationObject();
+ Piwik_Query('DELETE FROM ' . Piwik_Common::prefixTable('user_dashboard') . ' WHERE login = ?', array($userLogin));
+ }
+
+ public function install()
+ {
+ // we catch the exception
+ try {
+ $sql = "CREATE TABLE " . Piwik_Common::prefixTable('user_dashboard') . " (
login VARCHAR( 100 ) NOT NULL ,
iddashboard INT NOT NULL ,
name VARCHAR( 100 ) NULL DEFAULT NULL ,
layout TEXT NOT NULL,
PRIMARY KEY ( login , iddashboard )
- ) DEFAULT CHARSET=utf8 " ;
- Piwik_Exec($sql);
- } catch(Exception $e){
- // mysql code error 1050:table already exists
- // see bug #153 http://dev.piwik.org/trac/ticket/153
- if(!Zend_Registry::get('db')->isErrNo($e, '1050'))
- {
- throw $e;
- }
- }
- }
-
- public function uninstall()
- {
- Piwik_DropTables(Piwik_Common::prefixTable('user_dashboard'));
- }
-
+ ) DEFAULT CHARSET=utf8 ";
+ Piwik_Exec($sql);
+ } catch (Exception $e) {
+ // mysql code error 1050:table already exists
+ // see bug #153 http://dev.piwik.org/trac/ticket/153
+ if (!Zend_Registry::get('db')->isErrNo($e, '1050')) {
+ throw $e;
+ }
+ }
+ }
+
+ public function uninstall()
+ {
+ Piwik_DropTables(Piwik_Common::prefixTable('user_dashboard'));
+ }
+
}
diff --git a/plugins/Dashboard/templates/dashboard.css b/plugins/Dashboard/templates/dashboard.css
index 0fd262308d..80b2c4e23a 100644
--- a/plugins/Dashboard/templates/dashboard.css
+++ b/plugins/Dashboard/templates/dashboard.css
@@ -1,67 +1,73 @@
-
-#dashboard{
- padding:0 0 0 7px;
- margin:-4px -14px 0 -14px;
+#dashboard {
+ padding: 0 0 0 7px;
+ margin: -4px -14px 0 -14px;
}
-#dashboardWidgetsArea{
- padding:2px 0 0 0;
+#dashboardWidgetsArea {
+ padding: 2px 0 0 0;
}
.col {
- float: left;
- padding-bottom: 100px;
- min-height: 100px;
- -height: 100px;
+ float: left;
+ padding-bottom: 100px;
+ min-height: 100px;
+ -height: 100px;
}
.col.width-100 {
width: 100%;
}
+
.col.width-75 {
width: 75%;
}
+
.col.width-67 {
width: 67%;
}
+
.col.width-50 {
width: 50%;
}
+
.col.width-40 {
width: 40%;
}
+
.col.width-33 {
width: 33%;
}
+
.col.width-30 {
width: 30%;
}
+
.col.width-25 {
width: 25%;
}
.sortable {
- background: white;
+ background: white;
}
.hover {
- border: 2px dashed #E3E3E3;
+ border: 2px dashed #E3E3E3;
}
.widget {
- background:#fff;
- border: 1px solid #bbb6ad;
- margin:10px 7px;
- overflow: hidden;
- border-radius:4px;
- font-size:14px;
- border-radius:4px;
- -moz-border-radius:4px;
- -webkit-border-radius:4px;
+ background: #fff;
+ border: 1px solid #bbb6ad;
+ margin: 10px 7px;
+ overflow: hidden;
+ border-radius: 4px;
+ font-size: 14px;
+ border-radius: 4px;
+ -moz-border-radius: 4px;
+ -webkit-border-radius: 4px;
}
.widgetHover {
- border: 1px solid #aba494;
+ border: 1px solid #aba494;
}
.widgetContent.hidden {
@@ -71,95 +77,97 @@
.widgetContent.loading {
opacity: 0.5;
- filter:Alpha(opacity:50);
+ filter: Alpha(opacity:50);
background: url(../../../themes/default/images/loading-blue.gif) no-repeat top right;
}
.widget h2 {
- font-size:1.2em;
- margin-left:10px;
- font-weight:bold;
+ font-size: 1.2em;
+ margin-left: 10px;
+ font-weight: bold;
}
+
.widgetTop {
- background:#b5b0a7 url(../../../themes/default/images/dashboard_h_bg.png) repeat-x 0 0;
- border-radius:4px 4px 0 0;
- -moz-border-radius:4px 4px 0 0;
- -webkit-border-radius:4px 4px 0 0;
- width: 100%;
- cursor: move;
- font-size: 10pt;
- font-weight: bold;
- padding-bottom: 4px;
+ background: #b5b0a7 url(../../../themes/default/images/dashboard_h_bg.png) repeat-x 0 0;
+ border-radius: 4px 4px 0 0;
+ -moz-border-radius: 4px 4px 0 0;
+ -webkit-border-radius: 4px 4px 0 0;
+ width: 100%;
+ cursor: move;
+ font-size: 10pt;
+ font-weight: bold;
+ padding-bottom: 4px;
}
.widgetTopHover {
- background:#B0A798 url(../../../themes/default/images/dashboard_h_bg_hover.png) repeat-x 0 0;
+ background: #B0A798 url(../../../themes/default/images/dashboard_h_bg_hover.png) repeat-x 0 0;
}
.widgetName {
- font-size: 18px;
- padding:2px 0 0 10px;
- font-weight:normal;
- color:#fff;
- text-shadow:1px 1px 2px #7e7363;
+ font-size: 18px;
+ padding: 2px 0 0 10px;
+ font-weight: normal;
+ color: #fff;
+ text-shadow: 1px 1px 2px #7e7363;
}
.button {
- cursor: pointer;
+ cursor: pointer;
}
#close.button, #maximise.button, #minimise.button, #refresh.button {
- float: right;
- display: none;
- margin: 6px 6px 0 0;
+ float: right;
+ display: none;
+ margin: 6px 6px 0 0;
}
.ui-confirm {
- display: none;
- width:630px;
- background:#fff;
- color: #444;
- cursor: default;
- font-size: 12px!important;
- font-family:Arial,Verdana,Arial,Helvetica,sans-serif;
- border-radius:4px;
- padding:20px 10px;
- border-radius:4px;
- -moz-border-radius:4px;
- -webkit-border-radius:4px;
- min-height:0!important;
-}
-.ui-confirm h2{
+ display: none;
+ width: 630px;
+ background: #fff;
+ color: #444;
+ cursor: default;
+ font-size: 12px !important;
+ font-family: Arial, Verdana, Arial, Helvetica, sans-serif;
+ border-radius: 4px;
+ padding: 20px 10px;
+ border-radius: 4px;
+ -moz-border-radius: 4px;
+ -webkit-border-radius: 4px;
+ min-height: 0 !important;
+}
+
+.ui-confirm h2 {
text-align: center;
- font-weight:bold;
- padding:0;
+ font-weight: bold;
+ padding: 0;
}
-.ui-dialog .ui-dialog-buttonpane{
+.ui-dialog .ui-dialog-buttonpane {
text-align: center;
border: none;
}
-.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset{
+.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset {
float: none;
}
-.ui-dialog input[type=button], .ui-dialog button{
- background: #B5B0A7 url(../../../themes/default/images/dashboard_h_bg_hover.png) repeat-x 0 0!important;
- color:#fff!important;
- border:0!important;
- font-size: 12px!important;
- padding:5px 20px!important;
- border-radius:3px;
- -moz-border-radius:3px;
- -webkit-border-radius:3px;
- cursor:pointer;
- display:inline-block;
- margin:0 8px 3px 8px!important;
+.ui-dialog input[type=button], .ui-dialog button {
+ background: #B5B0A7 url(../../../themes/default/images/dashboard_h_bg_hover.png) repeat-x 0 0 !important;
+ color: #fff !important;
+ border: 0 !important;
+ font-size: 12px !important;
+ padding: 5px 20px !important;
+ border-radius: 3px;
+ -moz-border-radius: 3px;
+ -webkit-border-radius: 3px;
+ cursor: pointer;
+ display: inline-block;
+ margin: 0 8px 3px 8px !important;
}
-.ui-dialog .ui-button-text{
- padding: 0!important;
+.ui-dialog .ui-button-text {
+ padding: 0 !important;
}
.ui-widget-overlay {
@@ -169,43 +177,44 @@
}
.menu {
- display: none;
+ display: none;
}
.widgetLoading {
- cursor: wait;
- padding: 10px;
- text-align: center;
- font-size: 10pt;
+ cursor: wait;
+ padding: 10px;
+ text-align: center;
+ font-size: 10pt;
}
#closeMenuIcon {
- float: right;
- margin: 3px;
- cursor: pointer;
+ float: right;
+ margin: 3px;
+ cursor: pointer;
}
.menuClear {
- clear: both;
- height: 30px;
+ clear: both;
+ height: 30px;
}
#dashboardSettings {
- position:absolute;
- z-index: 998;
- background: #f7f7f7;
- border: 1px solid #e4e5e4;
- padding:5px 10px 6px 10px;
- border-radius:4px;
- -moz-border-radius:4px;
- -webkit-border-radius:4px;
- color:#444;
- font-size:14px;
+ position: absolute;
+ z-index: 998;
+ background: #f7f7f7;
+ border: 1px solid #e4e5e4;
+ padding: 5px 10px 6px 10px;
+ border-radius: 4px;
+ -moz-border-radius: 4px;
+ -webkit-border-radius: 4px;
+ color: #444;
+ font-size: 14px;
cursor: pointer;
}
+
#dashboardSettings:hover {
- background:#f1f0eb;
- border-color:#a9a399;
+ background: #f1f0eb;
+ border-color: #a9a399;
}
#dashboardSettings > span {
@@ -250,20 +259,20 @@
float: left;
opacity: 0.4;
cursor: pointer;
- filter:Alpha(opacity:40);
+ filter: Alpha(opacity:40);
}
#columnPreview>div:hover, #columnPreview>div.choosen {
opacity: 1;
- filter:Alpha(opacity:100);
+ filter: Alpha(opacity:100);
}
-#columnPreview div div{
+#columnPreview div div {
height: 120px;
float: left;
}
-#columnPreview div div span{
+#columnPreview div div span {
background-color: #ddd;
width: 100%;
height: 100%;
@@ -272,31 +281,38 @@
margin: 0 1px;
}
-#columnPreview div.choosen div span, #columnPreview div:hover div span{
+#columnPreview div.choosen div span, #columnPreview div:hover div span {
border-style: solid;
}
#columnPreview .width-100 {
width: 120px;
}
+
#columnPreview .width-75 {
width: 90px;
}
+
#columnPreview .width-67 {
width: 80.4px;
}
+
#columnPreview .width-50 {
width: 60px;
}
+
#columnPreview .width-40 {
width: 48px;
}
+
#columnPreview .width-33 {
width: 40px;
}
+
#columnPreview .width-30 {
width: 36px;
}
+
#columnPreview .width-25 {
width: 30px;
}
@@ -312,17 +328,17 @@
}
#addWidget, #manageDashboard {
- cursor:default;
+ cursor: default;
}
ul.widgetpreview-widgetlist,
ul.widgetpreview-categorylist {
color: #5d5342;
- list-style:none;
- font-size:11px;
- line-height:20px;
+ list-style: none;
+ font-size: 11px;
+ line-height: 20px;
float: left;
- margin-right:20px;
+ margin-right: 20px;
}
ul.widgetpreview-categorylist {
@@ -331,16 +347,16 @@ ul.widgetpreview-categorylist {
ul.widgetpreview-categorylist li,
ul.widgetpreview-widgetlist li {
- line-height:20px;
- padding:0 25px 0 5px;
- border-radius:2px;
- -moz-border-radius:2px;
- -webkit-border-radius:2px;
+ line-height: 20px;
+ padding: 0 25px 0 5px;
+ border-radius: 2px;
+ -moz-border-radius: 2px;
+ -webkit-border-radius: 2px;
}
.widgetpreview-base li.widgetpreview-choosen {
- background:#e4e2d7 url(../../../themes/default/images/arr_r.png) no-repeat right 6px;
- color:#255792;
+ background: #e4e2d7 url(../../../themes/default/images/arr_r.png) no-repeat right 6px;
+ color: #255792;
font-weight: bold;
}
@@ -372,15 +388,15 @@ div.widgetpreview-preview {
}
#dashboardSettings .submenu {
- font-weight:bold;
- color:#255792;
+ font-weight: bold;
+ color: #255792;
}
#dashboardSettings .submenu ul {
float: none;
- font-weight:normal;
- padding-top:10px;
- margin-left: 10px;
+ font-weight: normal;
+ padding-top: 10px;
+ margin-left: 10px;
color: #5D5342;
list-style: none;
font-size: 11px;
@@ -399,12 +415,12 @@ div.widgetpreview-preview {
}
#dashboardSettings .widgetpreview-widgetlist {
- width: 228px;
- font-weight:normal;
+ width: 228px;
+ font-weight: normal;
}
#dashboardSettings .widgetTop {
- cursor:pointer;
+ cursor: pointer;
}
#dashboardSettings .widgetpreview-widgetlist,
diff --git a/plugins/Dashboard/templates/dashboard.js b/plugins/Dashboard/templates/dashboard.js
index 1285834105..96ea45c28d 100644
--- a/plugins/Dashboard/templates/dashboard.js
+++ b/plugins/Dashboard/templates/dashboard.js
@@ -8,21 +8,19 @@
function initDashboard(dashboardId, dashboardLayout) {
// Standard dashboard
- if($('#periodString').length)
- {
+ if ($('#periodString').length) {
$('#periodString').after($('#dashboardSettings'));
- $('#dashboardSettings').css({left:$('#periodString')[0].offsetWidth});
+ $('#dashboardSettings').css({left: $('#periodString')[0].offsetWidth});
}
// Embed dashboard
- if(!$('#topBars').length)
- {
- $('#dashboardSettings').css({left:0});
+ if (!$('#topBars').length) {
+ $('#dashboardSettings').css({left: 0});
$('#dashboardSettings').after($('#Dashboard'));
- $('#Dashboard > ul li a').each(function(){$(this).css({width:this.offestWidth+30, paddingLeft:0, paddingRight:0});});
- $('#Dashboard_embeddedIndex_'+dashboardId).addClass('sfHover');
+ $('#Dashboard > ul li a').each(function () {$(this).css({width: this.offestWidth + 30, paddingLeft: 0, paddingRight: 0});});
+ $('#Dashboard_embeddedIndex_' + dashboardId).addClass('sfHover');
}
- $('#dashboardSettings').on('click', function(){
+ $('#dashboardSettings').on('click', function () {
$('#dashboardSettings').toggleClass('visible');
if ($('#dashboardWidgetsArea').dashboard('isDefaultDashboard')) {
$('#removeDashboardLink').hide();
@@ -32,8 +30,8 @@ function initDashboard(dashboardId, dashboardLayout) {
// fix position
$('#dashboardSettings .widgetpreview-widgetlist').css('paddingTop', $('#dashboardSettings .widgetpreview-categorylist').parent('li').position().top);
});
- $('body').on('mouseup', function(e) {
- if(!$(e.target).parents('#dashboardSettings').length && !$(e.target).is('#dashboardSettings')) {
+ $('body').on('mouseup', function (e) {
+ if (!$(e.target).parents('#dashboardSettings').length && !$(e.target).is('#dashboardSettings')) {
$('#dashboardSettings').widgetPreview('reset');
$('#dashboardSettings').removeClass('visible');
}
@@ -49,10 +47,10 @@ function initDashboard(dashboardId, dashboardLayout) {
});
$('#dashboardSettings').widgetPreview({
- isWidgetAvailable: function(widgetUniqueId) {
+ isWidgetAvailable: function (widgetUniqueId) {
return !$('#dashboardWidgetsArea [widgetId=' + widgetUniqueId + ']').length;
},
- onSelect: function(widgetUniqueId) {
+ onSelect: function (widgetUniqueId) {
var widget = widgetsHelper.getWidgetObjectFromUniqueId(widgetUniqueId);
$('#dashboardWidgetsArea').dashboard('addWidget', widget.uniqueId, 1, widget.parameters, true, false);
$('#dashboardSettings').removeClass('visible');
@@ -60,20 +58,20 @@ function initDashboard(dashboardId, dashboardLayout) {
resetOnSelect: true
});
- $('#columnPreview>div').each(function(){
+ $('#columnPreview>div').each(function () {
var width = [];
- $('div', this).each(function(){
+ $('div', this).each(function () {
width.push(this.className.replace(/width-/, ''));
});
$(this).attr('layout', width.join('-'));
});
- $('#columnPreview>div').on('click', function(){
+ $('#columnPreview>div').on('click', function () {
$('#columnPreview>div').removeClass('choosen');
$(this).addClass('choosen');
});
- $('.submenu>li').on('mouseenter', function(event){
+ $('.submenu>li').on('mouseenter', function (event) {
if (!$('.widgetpreview-categorylist', event.target).length) {
$('#dashboardSettings').widgetPreview('reset');
}
@@ -83,7 +81,7 @@ function initDashboard(dashboardId, dashboardLayout) {
function createDashboard() {
$('#createDashboardName').attr('value', '');
- piwikHelper.modalConfirm('#createDashboardConfirm', {yes: function(){
+ piwikHelper.modalConfirm('#createDashboardConfirm', {yes: function () {
var dashboardName = $('#createDashboardName').attr('value');
var type = ($('#dashboard_type_empty:checked').length > 0) ? 'empty' : 'default';
@@ -107,37 +105,37 @@ function createDashboard() {
}
function resetDashboard() {
- piwikHelper.modalConfirm('#resetDashboardConfirm', {yes: function(){ $('#dashboardWidgetsArea').dashboard('resetLayout'); }});
+ piwikHelper.modalConfirm('#resetDashboardConfirm', {yes: function () { $('#dashboardWidgetsArea').dashboard('resetLayout'); }});
}
function renameDashboard() {
$('#newDashboardName').attr('value', $('#dashboardWidgetsArea').dashboard('getDashboardName'));
- piwikHelper.modalConfirm('#renameDashboardConfirm', {yes: function(){ $('#dashboardWidgetsArea').dashboard('setDashboardName', $('#newDashboardName').attr('value')); }});
+ piwikHelper.modalConfirm('#renameDashboardConfirm', {yes: function () { $('#dashboardWidgetsArea').dashboard('setDashboardName', $('#newDashboardName').attr('value')); }});
}
function removeDashboard() {
$('#removeDashboardConfirm h2 span').html($('#dashboardWidgetsArea').dashboard('getDashboardName'));
- piwikHelper.modalConfirm('#removeDashboardConfirm', {yes: function(){ $('#dashboardWidgetsArea').dashboard('removeDashboard'); }});
+ piwikHelper.modalConfirm('#removeDashboardConfirm', {yes: function () { $('#dashboardWidgetsArea').dashboard('removeDashboard'); }});
}
function showChangeDashboardLayoutDialog() {
$('#columnPreview>div').removeClass('choosen');
- $('#columnPreview>div[layout='+$('#dashboardWidgetsArea').dashboard('getColumnLayout')+']').addClass('choosen');
- piwikHelper.modalConfirm('#changeDashboardLayout', {yes: function(){
+ $('#columnPreview>div[layout=' + $('#dashboardWidgetsArea').dashboard('getColumnLayout') + ']').addClass('choosen');
+ piwikHelper.modalConfirm('#changeDashboardLayout', {yes: function () {
$('#dashboardWidgetsArea').dashboard('setColumnLayout', $('#changeDashboardLayout .choosen').attr('layout'));
}});
}
function showEmptyDashboardNotification() {
piwikHelper.modalConfirm('#dashboardEmptyNotification', {
- resetDashboard: function() { $('#dashboardWidgetsArea').dashboard('resetLayout'); },
- addWidget: function(){ $('#dashboardSettings').trigger('click'); }
+ resetDashboard: function () { $('#dashboardWidgetsArea').dashboard('resetLayout'); },
+ addWidget: function () { $('#dashboardSettings').trigger('click'); }
});
}
function setAsDefaultWidgets() {
piwikHelper.modalConfirm('#setAsDefaultWidgetsConfirm', {
- yes: function(){ $('#dashboardWidgetsArea').dashboard('saveLayoutAsDefaultWidgetLayout'); }
+ yes: function () { $('#dashboardWidgetsArea').dashboard('saveLayoutAsDefaultWidgetLayout'); }
});
}
@@ -145,9 +143,9 @@ function copyDashboardToUser() {
$('#copyDashboardName').attr('value', $('#dashboardWidgetsArea').dashboard('getDashboardName'));
var ajaxRequest = new ajaxHelper();
ajaxRequest.addParams({
- module: 'API',
- method: 'UsersManager.getUsers',
- format: 'json'
+ module: 'API',
+ method: 'UsersManager.getUsers',
+ format: 'json'
}, 'get');
ajaxRequest.setCallback(
function (availableUsers) {
@@ -155,10 +153,10 @@ function copyDashboardToUser() {
$('#copyDashboardUser').append(
$('<option></option>').val(piwik.userLogin).html(piwik.userLogin)
);
- $.each(availableUsers, function(index, user) {
+ $.each(availableUsers, function (index, user) {
if (user.login != 'anonymous' && user.login != piwik.userLogin) {
$('#copyDashboardUser').append(
- $('<option></option>').val(user.login).html(user.login + ' ('+user.alias+')')
+ $('<option></option>').val(user.login).html(user.login + ' (' + user.alias + ')')
);
}
});
@@ -167,7 +165,7 @@ function copyDashboardToUser() {
ajaxRequest.send(true);
piwikHelper.modalConfirm('#copyDashboardToUserConfirm', {
- yes: function() {
+ yes: function () {
var copyDashboardName = $('#copyDashboardName').attr('value');
var copyDashboardUser = $('#copyDashboardUser').attr('value');
diff --git a/plugins/Dashboard/templates/dashboardObject.js b/plugins/Dashboard/templates/dashboardObject.js
index 55c4e452fb..918a4be23f 100644
--- a/plugins/Dashboard/templates/dashboardObject.js
+++ b/plugins/Dashboard/templates/dashboardObject.js
@@ -4,33 +4,33 @@
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
-(function( $ ){
+(function ($) {
/**
* Current dashboard column layout
* @type {object}
*/
- var dashboardLayout = {};
+ var dashboardLayout = {};
/**
* Id of current dashboard
* @type {int}
*/
- var dashboardId = 1;
+ var dashboardId = 1;
/**
* Name of current dashboard
* @type {string}
*/
- var dashboardName = '';
+ var dashboardName = '';
/**
* Holds a reference to the dashboard element
* @type {object}
*/
- var dashboardElement = null;
+ var dashboardElement = null;
/**
* Boolean indicating wether the layout config has been changed or not
* @type {boolean}
*/
- var dashboardChanged = false;
+ var dashboardChanged = false;
/**
* public methods of dashboard plugin
@@ -43,7 +43,7 @@
*
* @param {object} options
*/
- init: function(options) {
+ init: function (options) {
dashboardElement = this;
@@ -70,11 +70,11 @@
*
* @return void
*/
- destroy: function() {
+ destroy: function () {
$(dashboardElement).remove();
dashboardElement = null;
var widgets = $('[widgetId]');
- for (var i=0; i < widgets.length; i++) {
+ for (var i = 0; i < widgets.length; i++) {
$(widgets[i]).dashboardWidget('destroy');
}
},
@@ -84,15 +84,15 @@
*
* @param {int} dashboardIdToLoad
*/
- loadDashboard: function(dashboardIdToLoad) {
+ loadDashboard: function (dashboardIdToLoad) {
$(dashboardElement).empty();
- dashboardName = '';
+ dashboardName = '';
dashboardLayout = null;
- dashboardId = dashboardIdToLoad;
+ dashboardId = dashboardIdToLoad;
piwikHelper.showAjaxLoading();
broadcast.updateHashOnly = true;
- broadcast.propagateAjax('?idDashboard='+dashboardIdToLoad);
+ broadcast.propagateAjax('?idDashboard=' + dashboardIdToLoad);
fetchLayout(generateLayout);
buildMenu();
return this;
@@ -103,7 +103,7 @@
*
* @param {String} newLayout
*/
- setColumnLayout: function(newLayout) {
+ setColumnLayout: function (newLayout) {
adjustDashboardColumns(newLayout);
},
@@ -112,7 +112,7 @@
*
* @return {String}
*/
- getColumnLayout: function() {
+ getColumnLayout: function () {
return dashboardLayout.config.layout;
},
@@ -121,7 +121,7 @@
*
* @return {String}
*/
- getDashboardName: function() {
+ getDashboardName: function () {
return dashboardName;
},
@@ -130,7 +130,7 @@
*
* @return {int}
*/
- getDashboardId: function() {
+ getDashboardId: function () {
return dashboardId;
},
@@ -139,8 +139,8 @@
*
* @param {String} newName
*/
- setDashboardName: function(newName) {
- dashboardName = newName;
+ setDashboardName: function (newName) {
+ dashboardName = newName;
dashboardChanged = true;
saveLayout();
},
@@ -154,7 +154,7 @@
* @param {boolean} addWidgetOnTop
* @param {boolean} isHidden
*/
- addWidget: function(uniqueId, columnNumber, widgetParameters, addWidgetOnTop, isHidden) {
+ addWidget: function (uniqueId, columnNumber, widgetParameters, addWidgetOnTop, isHidden) {
addWidgetTemplate(uniqueId, columnNumber, widgetParameters, addWidgetOnTop, isHidden);
reloadWidget(uniqueId);
saveLayout();
@@ -163,12 +163,11 @@
/**
* Resets the current layout to the defaults
*/
- resetLayout: function()
- {
+ resetLayout: function () {
var ajaxRequest = new ajaxHelper();
ajaxRequest.addParams({
- module: 'Dashboard',
- action: 'resetLayout',
+ module: 'Dashboard',
+ action: 'resetLayout',
idDashboard: dashboardId
}, 'get');
ajaxRequest.setCallback(
@@ -184,21 +183,21 @@
/**
* Removes the current dashboard
*/
- removeDashboard: function() {
+ removeDashboard: function () {
removeDashboard();
},
/**
* Saves the current layout aus new default widget layout
*/
- saveLayoutAsDefaultWidgetLayout: function() {
+ saveLayoutAsDefaultWidgetLayout: function () {
saveLayout('saveLayoutAsDefault');
},
/**
* Returns if the current loaded dashboard is the default dashboard
*/
- isDefaultDashboard: function() {
+ isDefaultDashboard: function () {
return (dashboardId == 1);
}
};
@@ -215,16 +214,16 @@
adjustDashboardColumns(dashboardLayout.config.layout);
var dashboardContainsWidgets = false;
- for (var column=0; column < dashboardLayout.columns.length; column++) {
+ for (var column = 0; column < dashboardLayout.columns.length; column++) {
for (var i in dashboardLayout.columns[column]) {
- if (typeof dashboardLayout.columns[column][i] != 'object') {
- // Fix IE8 bug: the "i in" loop contains i="indexOf", which would yield type function.
- // If we would continue with i="indexOf", an invalid widget would be created.
- continue;
- }
+ if (typeof dashboardLayout.columns[column][i] != 'object') {
+ // Fix IE8 bug: the "i in" loop contains i="indexOf", which would yield type function.
+ // If we would continue with i="indexOf", an invalid widget would be created.
+ continue;
+ }
var widget = dashboardLayout.columns[column][i];
dashboardContainsWidgets = true;
- addWidgetTemplate(widget.uniqueId, column+1, widget.parameters, false, widget.isHidden)
+ addWidgetTemplate(widget.uniqueId, column + 1, widget.parameters, false, widget.isHidden)
}
}
@@ -241,8 +240,7 @@
*
* @param {function} callback
*/
- function fetchLayout(callback)
- {
+ function fetchLayout(callback) {
globalAjaxQueue.abort();
var ajaxRequest = new ajaxHelper();
ajaxRequest.addParams({
@@ -261,8 +259,7 @@
* @param {String} layout new layout in format xx-xx-xx
* @return {void}
*/
- function adjustDashboardColumns(layout)
- {
+ function adjustDashboardColumns(layout) {
var columnWidth = layout.split('-');
var columnCount = columnWidth.length;
@@ -270,7 +267,7 @@
if (currentCount < columnCount) {
$('.menuClear', dashboardElement).remove();
- for (var i=currentCount;i<columnCount;i++) {
+ for (var i = currentCount; i < columnCount; i++) {
if (dashboardLayout.columns.length < i) {
dashboardLayout.columns.push({});
}
@@ -278,16 +275,16 @@
}
$(dashboardElement).append('<div class="menuClear"> </div>');
} else if (currentCount > columnCount) {
- for (var i=columnCount;i<currentCount;i++) {
- if(dashboardLayout.columns.length >= i) {
+ for (var i = columnCount; i < currentCount; i++) {
+ if (dashboardLayout.columns.length >= i) {
dashboardLayout.columns.pop();
}
// move widgets to other columns depending on columns height
- $('[widgetId]', $('.col:last')).each(function(id, elem){
+ $('[widgetId]', $('.col:last')).each(function (id, elem) {
var cols = $('.col').slice(0, columnCount);
var smallestColumn = $(cols[0]);
var smallestColumnHeight = null;
- cols.each(function(colId, col){
+ cols.each(function (colId, col) {
if (smallestColumnHeight == null || smallestColumnHeight > $(col).height()) {
smallestColumnHeight = $(col).height();
smallestColumn = $(col);
@@ -301,21 +298,21 @@
}
}
- for (var i=0; i < columnCount; i++) {
- $('.col', dashboardElement)[i].className = 'col width-'+columnWidth[i];
+ for (var i = 0; i < columnCount; i++) {
+ $('.col', dashboardElement)[i].className = 'col width-' + columnWidth[i];
}
makeWidgetsSortable();
// if dashboard column count is changed (not on initial load)
- if(currentCount > 0 && dashboardLayout.config.layout != layout) {
- dashboardChanged = true;
+ if (currentCount > 0 && dashboardLayout.config.layout != layout) {
+ dashboardChanged = true;
dashboardLayout.config.layout = layout;
saveLayout();
}
// reload all widgets containing a graph to make them display correct
- $('.widget:has(".piwik-graph")').each(function(id, elem){
+ $('.widget:has(".piwik-graph")').each(function (id, elem) {
reloadWidget($(elem).attr('id'));
});
}
@@ -333,8 +330,8 @@
// column count was always 3, so use layout 33-33-33 as default
if ($.isArray(layout)) {
layout = {
- config: {layout: '33-33-33'},
- columns: layout
+ config: {layout: '33-33-33'},
+ columns: layout
};
}
@@ -351,7 +348,7 @@
* @param {String} uniqueId
*/
function reloadWidget(uniqueId) {
- $('[widgetId='+uniqueId+']', dashboardElement).dashboardWidget('reload');
+ $('[widgetId=' + uniqueId + ']', dashboardElement).dashboardWidget('reload');
}
/**
@@ -368,22 +365,22 @@
}
// do not try to add widget if given columnnumber is to high
- if(columnNumber > $('.col', dashboardElement).length) {
+ if (columnNumber > $('.col', dashboardElement).length) {
return;
}
- var widgetContent = '<div class="sortable" widgetId="'+uniqueId+'"></div>';
+ var widgetContent = '<div class="sortable" widgetId="' + uniqueId + '"></div>';
if (addWidgetOnTop) {
- $('.col:nth-child('+columnNumber+')', dashboardElement).prepend(widgetContent);
+ $('.col:nth-child(' + columnNumber + ')', dashboardElement).prepend(widgetContent);
} else {
- $('.col:nth-child('+columnNumber+')', dashboardElement).append(widgetContent);
+ $('.col:nth-child(' + columnNumber + ')', dashboardElement).append(widgetContent);
}
- $('[widgetId='+uniqueId+']', dashboardElement).dashboardWidget({
+ $('[widgetId=' + uniqueId + ']', dashboardElement).dashboardWidget({
uniqueId: uniqueId,
widgetParameters: widgetParameters,
- onChange: function() {
+ onChange: function () {
saveLayout();
},
isHidden: isHidden
@@ -393,8 +390,7 @@
/**
* Make all widgets on the dashboard sortable
*/
- function makeWidgetsSortable()
- {
+ function makeWidgetsSortable() {
function onStart(event, ui) {
if (!jQuery.support.noCloneEvent) {
$('object', this).hide();
@@ -406,7 +402,7 @@
$('.widgetHover', this).removeClass('widgetHover');
$('.widgetTopHover', this).removeClass('widgetTopHover');
$('.button#close, .button#maximise', this).hide();
- if($('.widget:has(".piwik-graph")', ui.item).length) {
+ if ($('.widget:has(".piwik-graph")', ui.item).length) {
reloadWidget($('.widget', ui.item).attr('id'));
}
saveLayout();
@@ -414,19 +410,19 @@
//launch 'sortable' property on every dashboard widgets
$('div.col', dashboardElement)
- .sortable('destroy')
- .sortable({
- items: 'div.sortable',
- opacity: 0.6,
- forceHelperSize: true,
- forcePlaceholderSize: true,
- placeholder: 'hover',
- handle: '.widgetTop',
- helper: 'clone',
- start: onStart,
- stop: onStop,
- connectWith: 'div.col'
- });
+ .sortable('destroy')
+ .sortable({
+ items: 'div.sortable',
+ opacity: 0.6,
+ forceHelperSize: true,
+ forcePlaceholderSize: true,
+ placeholder: 'hover',
+ handle: '.widgetTop',
+ helper: 'clone',
+ start: onStart,
+ stop: onStop,
+ connectWith: 'div.col'
+ });
}
/**
@@ -434,24 +430,24 @@
*/
function buildMenu() {
- var success = function(dashboards) {
+ var success = function (dashboards) {
var dashboardMenuList = $('#Dashboard > ul');
dashboardMenuList.empty();
if (dashboards.length > 1) {
dashboardMenuList.show();
- for (var i=0; i<dashboards.length; i++) {
- dashboardMenuList.append('<li id="Dashboard_embeddedIndex_'+dashboards[i].iddashboard+'" class="dashboardMenuItem"><a dashboardId="'+dashboards[i].iddashboard+'">'+ piwikHelper.htmlEntities( dashboards[i].name ) +'</a></li>');
- if(dashboards[i].iddashboard == dashboardId) {
+ for (var i = 0; i < dashboards.length; i++) {
+ dashboardMenuList.append('<li id="Dashboard_embeddedIndex_' + dashboards[i].iddashboard + '" class="dashboardMenuItem"><a dashboardId="' + dashboards[i].iddashboard + '">' + piwikHelper.htmlEntities(dashboards[i].name) + '</a></li>');
+ if (dashboards[i].iddashboard == dashboardId) {
dashboardName = dashboards[i].name;
}
}
- $('li a', dashboardMenuList).each(function(){$(this).css({width:$(this).width()+30, paddingLeft:0, paddingRight:0});});
- $('#Dashboard_embeddedIndex_'+dashboardId).addClass('sfHover');
+ $('li a', dashboardMenuList).each(function () {$(this).css({width: $(this).width() + 30, paddingLeft: 0, paddingRight: 0});});
+ $('#Dashboard_embeddedIndex_' + dashboardId).addClass('sfHover');
} else {
dashboardMenuList.hide();
}
- $('.dashboardMenuItem').on('click', function() {
+ $('.dashboardMenuItem').on('click', function () {
if (typeof piwikMenu != 'undefined') {
piwikMenu.activateMenu('Dashboard', 'embeddedIndex');
}
@@ -459,7 +455,7 @@
if ($(dashboardElement).length) {
$(dashboardElement).dashboard('loadDashboard', $('a', this).attr('dashboardId'));
} else {
- broadcast.propagateAjax('module=Dashboard&action=embeddedIndex&idDashboard='+$('a', this).attr('dashboardId'));
+ broadcast.propagateAjax('module=Dashboard&action=embeddedIndex&idDashboard=' + $('a', this).attr('dashboardId'));
}
$(this).addClass('sfHover');
});
@@ -483,15 +479,15 @@
var columns = [];
var columnNumber = 0;
- $('.col').each(function() {
+ $('.col').each(function () {
columns[columnNumber] = new Array;
var items = $('[widgetId]', this);
- for (var j=0; j<items.size(); j++) {
+ for (var j = 0; j < items.size(); j++) {
columns[columnNumber][j] = $(items[j]).dashboardWidget('getWidgetObject');
-
+
// Do not store segment in the dashboard layout
delete columns[columnNumber][j].parameters.segment;
-
+
}
columnNumber++;
});
@@ -517,7 +513,7 @@
}, 'post');
ajaxRequest.setCallback(
function () {
- if(dashboardChanged) {
+ if (dashboardChanged) {
dashboardChanged = false;
buildMenu();
}
@@ -555,14 +551,14 @@
/**
* Make plugin methods available
*/
- $.fn.dashboard = function( method ) {
- if ( methods[method] ) {
- return methods[method].apply( this, Array.prototype.slice.call( arguments, 1 ));
- } else if ( typeof method === 'object' || ! method ) {
- return methods.init.apply( this, arguments );
+ $.fn.dashboard = function (method) {
+ if (methods[method]) {
+ return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
+ } else if (typeof method === 'object' || !method) {
+ return methods.init.apply(this, arguments);
} else {
- $.error('Method ' + method + ' does not exist on jQuery.dashboard');
+ $.error('Method ' + method + ' does not exist on jQuery.dashboard');
}
}
-})( jQuery ); \ No newline at end of file
+})(jQuery); \ No newline at end of file
diff --git a/plugins/Dashboard/templates/dashboardWidget.js b/plugins/Dashboard/templates/dashboardWidget.js
index 7ac8a8f6eb..617f03d4c6 100755
--- a/plugins/Dashboard/templates/dashboardWidget.js
+++ b/plugins/Dashboard/templates/dashboardWidget.js
@@ -4,7 +4,7 @@
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
-(function( $ ){
+(function ($) {
$.widget('piwik.dashboardWidget', {
@@ -12,17 +12,17 @@
* Boolean indicating wether the widget is currently maximised
* @type {Boolean}
*/
- isMaximised: false,
+ isMaximised: false,
/**
* Unique Id of the widget
* @type {String}
*/
- uniqueId: null,
+ uniqueId: null,
/**
* Object holding the widget parameters
* @type {Object}
*/
- widgetParameters: {},
+ widgetParameters: {},
/**
* Options available for initialization
@@ -37,23 +37,23 @@
/**
* creates a widget object
*/
- _create: function() {
+ _create: function () {
- if(!this.options.uniqueId) {
+ if (!this.options.uniqueId) {
piwikHelper.error('widgets can\'t be created without an uniqueId');
return;
} else {
this.uniqueId = this.options.uniqueId;
}
- if(this.options.widgetParameters) {
+ if (this.options.widgetParameters) {
this.widgetParameters = this.options.widgetParameters;
}
this._createDashboardWidget(this.uniqueId);
var self = this;
- this.element.on('setParameters.dashboardWidget', function(e, params) { self.setParameters(params); });
+ this.element.on('setParameters.dashboardWidget', function (e, params) { self.setParameters(params); });
this.reload(true, true);
},
@@ -62,9 +62,9 @@
* Cleanup some events and dialog
* Called automatically upon removing the widgets domNode
*/
- destroy: function() {
- if(this.isMaximised) {
- $('[widgetId='+this.uniqueId+']').dialog('destroy');
+ destroy: function () {
+ if (this.isMaximised) {
+ $('[widgetId=' + this.uniqueId + ']').dialog('destroy');
}
$('*', this.element).off('.dashboardWidget'); // unbind all events
$('.widgetContent', this.element).trigger('widget:destroy');
@@ -75,7 +75,7 @@
* Returns the data currently set for the widget
* @return {object}
*/
- getWidgetObject: function() {
+ getWidgetObject: function () {
return {
uniqueId: this.uniqueId,
parameters: this.widgetParameters,
@@ -86,13 +86,13 @@
/**
* Show the current widget in an ui.dialog
*/
- maximise: function() {
+ maximise: function () {
this.isMaximised = true;
$('.button#close, .button#maximise', this.element).hide();
- this.element.before('<div id="'+this.uniqueId+'-placeholder" class="widgetPlaceholder widget"> </div>');
- $('#'+this.uniqueId+'-placeholder').height(this.element.height());
- $('#'+this.uniqueId+'-placeholder').width(this.element.width()-16);
+ this.element.before('<div id="' + this.uniqueId + '-placeholder" class="widgetPlaceholder widget"> </div>');
+ $('#' + this.uniqueId + '-placeholder').height(this.element.height());
+ $('#' + this.uniqueId + '-placeholder').width(this.element.width() - 16);
var width = Math.floor($('body').width() * 0.7);
@@ -104,12 +104,12 @@
position: ['center', 'center'],
resizable: true,
autoOpen: true,
- close: function(event, ui) {
+ close: function (event, ui) {
self.isMaximised = false;
$('.button#minimise, .button#refresh', $(this)).hide();
$('body').off('.dashboardWidget');
$(this).dialog("destroy");
- $('#'+self.uniqueId+'-placeholder').replaceWith(this);
+ $('#' + self.uniqueId + '-placeholder').replaceWith(this);
$(this).removeAttr('style');
self.options.onChange();
$(this).find('div.piwik-graph').trigger('resizeGraph');
@@ -119,8 +119,8 @@
this.element.find('div.piwik-graph').trigger('resizeGraph');
var currentWidget = this.element;
- $('body').on('click.dashboardWidget', function(ev) {
- if(ev.target.className == "ui-widget-overlay") {
+ $('body').on('click.dashboardWidget', function (ev) {
+ if (ev.target.className == "ui-widget-overlay") {
$(currentWidget).dialog("close");
}
});
@@ -131,15 +131,15 @@
/**
* Reloads the widgets content with the currently set parameters
*/
- reload: function(hideLoading, notJQueryUI) {
+ reload: function (hideLoading, notJQueryUI) {
if (!notJQueryUI) {
piwikHelper.log('widget.reload() was called by jquery.ui, ignoring', arguments.callee.caller);
return;
}
var self = this, currentWidget = this.element;
- function onWidgetLoadedReplaceElementWithContent(loadedContent)
- {
+
+ function onWidgetLoadedReplaceElementWithContent(loadedContent) {
$('.widgetContent', currentWidget).html(loadedContent);
$('.widgetContent', currentWidget).removeClass('loading');
$('.widgetContent', currentWidget).trigger('widget:create', [self]);
@@ -147,7 +147,7 @@
// Reading segment from hash tag (standard case) or from the URL (when embedding dashboard)
var segment = broadcast.getValueFromHash('segment') || broadcast.getValueFromUrl('segment');
- if(segment.length) {
+ if (segment.length) {
this.widgetParameters.segment = segment;
}
@@ -165,7 +165,7 @@
*
* @param {object} parameters
*/
- setParameters: function(parameters) {
+ setParameters: function (parameters) {
if (!this.isMaximised && (parameters.viewDataTable == 'tableAllColumns' || parameters.viewDataTable == 'tableGoals')) {
this.maximise();
@@ -185,7 +185,7 @@
*
* @param {String} uniqueId
*/
- _createDashboardWidget: function(uniqueId) {
+ _createDashboardWidget: function (uniqueId) {
var widgetName = widgetsHelper.getWidgetNameFromUniqueId(uniqueId);
if (!widgetName) {
@@ -195,42 +195,42 @@
var emptyWidgetContent = widgetsHelper.getEmptyWidgetHtml(uniqueId, widgetName);
this.element.html(emptyWidgetContent);
- var widgetElement = $('#'+ uniqueId, this.element);
+ var widgetElement = $('#' + uniqueId, this.element);
var self = this;
widgetElement
- .on( 'mouseenter.dashboardWidget', function() {
- if(!self.isMaximised) {
+ .on('mouseenter.dashboardWidget', function () {
+ if (!self.isMaximised) {
$(this).addClass('widgetHover');
$('.widgetTop', this).addClass('widgetTopHover');
$('.button#close, .button#maximise', this).show();
- if(!$('.widgetContent', this).hasClass('hidden')) {
+ if (!$('.widgetContent', this).hasClass('hidden')) {
$('.button#minimise, .button#refresh', this).show();
}
}
})
- .on( 'mouseleave.dashboardWidget', function() {
- if(!self.isMaximised) {
+ .on('mouseleave.dashboardWidget', function () {
+ if (!self.isMaximised) {
$(this).removeClass('widgetHover');
$('.widgetTop', this).removeClass('widgetTopHover');
$('.button#close, .button#maximise, .button#minimise, .button#refresh', this).hide();
}
});
- if(this.options.isHidden) {
+ if (this.options.isHidden) {
$('.widgetContent', widgetElement).toggleClass('hidden');
}
$('.button#close', widgetElement)
- .on( 'click.dashboardWidget', function(ev){
- piwikHelper.modalConfirm('#confirm',{yes: function(){
+ .on('click.dashboardWidget', function (ev) {
+ piwikHelper.modalConfirm('#confirm', {yes: function () {
self.element.remove();
self.options.onChange();
}});
});
$('.button#maximise', widgetElement)
- .on( 'click.dashboardWidget', function(ev){
- if($('.widgetContent', $(this).parents('.widget')).hasClass('hidden')) {
+ .on('click.dashboardWidget', function (ev) {
+ if ($('.widgetContent', $(this).parents('.widget')).hasClass('hidden')) {
self.isMaximised = false;
self.options.isHidden = false;
$('.widgetContent', $(this).parents('.widget')).removeClass('hidden');
@@ -244,8 +244,8 @@
});
$('.button#minimise', widgetElement)
- .on( 'click.dashboardWidget', function(ev){
- if(!self.isMaximised) {
+ .on('click.dashboardWidget', function (ev) {
+ if (!self.isMaximised) {
$('.widgetContent', $(this).parents('.widget')).addClass('hidden');
$('.button#minimise, .button#refresh', $(this).parents('.widget')).hide();
self.options.isHidden = true;
@@ -256,7 +256,7 @@
});
$('.button#refresh', widgetElement)
- .on('click.dashboardWidget', function(ev){
+ .on('click.dashboardWidget', function (ev) {
self.reload(false, true);
});
@@ -265,4 +265,4 @@
});
-})( jQuery );
+})(jQuery);
diff --git a/plugins/Dashboard/templates/header.tpl b/plugins/Dashboard/templates/header.tpl
index 51b3738b6a..721b31e7ba 100644
--- a/plugins/Dashboard/templates/header.tpl
+++ b/plugins/Dashboard/templates/header.tpl
@@ -1,94 +1,97 @@
{* This header is for loading the dashboard in stand alone mode*}
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
- "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
-<title>{'General_Dashboard'|translate} - {'CoreHome_WebAnalyticsReports'|translate}</title>
-<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
-{loadJavascriptTranslations plugins='CoreHome Dashboard'}
-<!--[if lt IE 9]>
-<script language="javascript" type="text/javascript" src="libs/jqplot/excanvas.min.js"></script>
-<![endif]-->
-{include file="CoreHome/templates/js_global_variables.tpl"}
-{include file="CoreHome/templates/js_css_includes.tpl"}
-{literal}
-<style type="text/css">
- body {
- padding-left:7px;
- }
- #dashboard {
- margin: 30px -6px 0 -12px;
- width: 100%;
- padding-top:8px;
- }
- #menuHead {
- position: absolute;
- top: 0;
- padding: 7px 0 0 2px;
- }
+ <title>{'General_Dashboard'|translate} - {'CoreHome_WebAnalyticsReports'|translate}</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
+ {loadJavascriptTranslations plugins='CoreHome Dashboard'}
+ <!--[if lt IE 9]>
+ <script language="javascript" type="text/javascript" src="libs/jqplot/excanvas.min.js"></script>
+ <![endif]-->
+ {include file="CoreHome/templates/js_global_variables.tpl"}
+ {include file="CoreHome/templates/js_css_includes.tpl"}
+ {literal}
+ <style type="text/css">
+ body {
+ padding-left: 7px;
+ }
- #Dashboard {
- z-index:5;
- font-size:14px;
- cursor: pointer;
- }
+ #dashboard {
+ margin: 30px -6px 0 -12px;
+ width: 100%;
+ padding-top: 8px;
+ }
- #Dashboard > ul {
- list-style: square inside none;
- background: #f7f7f7;
- border: 1px solid #e4e5e4;
- padding:5px 10px 6px 10px;
- border-radius:4px;
- -moz-border-radius:4px;
- -webkit-border-radius:4px;
- color:#444;
- height: 18px;
- }
+ #menuHead {
+ position: absolute;
+ top: 0;
+ padding: 7px 0 0 2px;
+ }
- #Dashboard:hover ul {
- background:#f1f0eb;
- border-color:#a9a399;
- }
+ #Dashboard {
+ z-index: 5;
+ font-size: 14px;
+ cursor: pointer;
+ }
- #Dashboard > ul > li {
- float: left;
- text-align: center;
- margin: 0 15px;
- }
+ #Dashboard > ul {
+ list-style: square inside none;
+ background: #f7f7f7;
+ border: 1px solid #e4e5e4;
+ padding: 5px 10px 6px 10px;
+ border-radius: 4px;
+ -moz-border-radius: 4px;
+ -webkit-border-radius: 4px;
+ color: #444;
+ height: 18px;
+ }
- #Dashboard a {
- color: #444;
- text-decoration: none;
- font-weight: normal;
- display: inline-block;
- margin: 0 -15px;
- }
+ #Dashboard:hover ul {
+ background: #f1f0eb;
+ border-color: #a9a399;
+ }
- #Dashboard > ul > li:hover , #Dashboard > ul > li:hover a,
- #Dashboard > ul > li.sfHover, #Dashboard > ul > li.sfHover a {
- color: #e87500;
- }
+ #Dashboard > ul > li {
+ float: left;
+ text-align: center;
+ margin: 0 15px;
+ }
- #Dashboard > ul > li.sfHover, #Dashboard > ul > li.sfHover a {
- font-weight: bold;
- }
+ #Dashboard a {
+ color: #444;
+ text-decoration: none;
+ font-weight: normal;
+ display: inline-block;
+ margin: 0 -15px;
+ }
- #Dashboard, #periodString, #dashboardSettings {
- float: left;
- clear: none;
- position: relative;
- margin-left: 0;
- margin-right: 10px;
- }
- .jqplock-seriespicker-popover {
- top: 0;
- }
+ #Dashboard > ul > li:hover, #Dashboard > ul > li:hover a,
+ #Dashboard > ul > li.sfHover, #Dashboard > ul > li.sfHover a {
+ color: #e87500;
+ }
- #ajaxLoading {
- margin: 40px 0 -30px 0;
- }
+ #Dashboard > ul > li.sfHover, #Dashboard > ul > li.sfHover a {
+ font-weight: bold;
+ }
-</style>
-{/literal}
+ #Dashboard, #periodString, #dashboardSettings {
+ float: left;
+ clear: none;
+ position: relative;
+ margin-left: 0;
+ margin-right: 10px;
+ }
+
+ .jqplock-seriespicker-popover {
+ top: 0;
+ }
+
+ #ajaxLoading {
+ margin: 40px 0 -30px 0;
+ }
+
+ </style>
+ {/literal}
</head>
<body>
diff --git a/plugins/Dashboard/templates/index.tpl b/plugins/Dashboard/templates/index.tpl
index 60a675ac38..43b0fea5f2 100644
--- a/plugins/Dashboard/templates/index.tpl
+++ b/plugins/Dashboard/templates/index.tpl
@@ -2,89 +2,98 @@
{literal}
<script type="text/javascript">
-widgetsHelper.availableWidgets = {/literal}{$availableWidgets}{literal};
-$(document).ready(function() {
- initDashboard({/literal}{$dashboardId},{$dashboardLayout}{literal});
-});
+ widgetsHelper.availableWidgets = {/literal}{$availableWidgets}{literal};
+ $(document).ready(function () {
+ initDashboard({/literal}{$dashboardId}, {$dashboardLayout}{literal});
+ });
</script>
{/literal}
<div id="dashboard">
-
+
<div class="ui-confirm" id="confirm">
<h2>{'Dashboard_DeleteWidgetConfirm'|translate}</h2>
- <input role="yes" type="button" value="{'General_Yes'|translate}" />
- <input role="no" type="button" value="{'General_Cancel'|translate}" />
+ <input role="yes" type="button" value="{'General_Yes'|translate}"/>
+ <input role="no" type="button" value="{'General_Cancel'|translate}"/>
</div>
<div class="ui-confirm" id="setAsDefaultWidgetsConfirm">
<h2>{'Dashboard_SetAsDefaultWidgetsConfirm'|translate}</h2>
{capture assign=resetDashboard}{'Dashboard_ResetDashboard'|translate}{/capture}
<div class="popoverSubMessage">{'Dashboard_SetAsDefaultWidgetsConfirmHelp'|translate:$resetDashboard}</div>
- <input role="yes" type="button" value="{'General_Yes'|translate}" />
- <input role="no" type="button" value="{'General_Cancel'|translate}" />
+ <input role="yes" type="button" value="{'General_Yes'|translate}"/>
+ <input role="no" type="button" value="{'General_Cancel'|translate}"/>
</div>
<div class="ui-confirm" id="resetDashboardConfirm">
<h2>{'Dashboard_ResetDashboardConfirm'|translate}</h2>
- <input role="yes" type="button" value="{'General_Yes'|translate}" />
- <input role="no" type="button" value="{'General_Cancel'|translate}" />
- </div>
+ <input role="yes" type="button" value="{'General_Yes'|translate}"/>
+ <input role="no" type="button" value="{'General_Cancel'|translate}"/>
+ </div>
<div class="ui-confirm" id="dashboardEmptyNotification">
<h2>{'Dashboard_DashboardEmptyNotification'|translate}</h2>
- <input role="addWidget" type="button" value="{'Dashboard_AddAWidget'|translate}" />
- <input role="resetDashboard" type="button" value="{'Dashboard_ResetDashboard'|translate}" />
+ <input role="addWidget" type="button" value="{'Dashboard_AddAWidget'|translate}"/>
+ <input role="resetDashboard" type="button" value="{'Dashboard_ResetDashboard'|translate}"/>
</div>
<div class="ui-confirm" id="changeDashboardLayout">
<h2>{'Dashboard_SelectDashboardLayout'|translate}</h2>
+
<div id="columnPreview">
- {foreach from=$availableLayouts item=layout}
- <div>
- {foreach from=$layout item=column}
- <div class="width-{$column}"><span></span></div>
+ {foreach from=$availableLayouts item=layout}
+ <div>
+ {foreach from=$layout item=column}
+ <div class="width-{$column}"><span></span></div>
+ {/foreach}
+ </div>
{/foreach}
- </div>
- {/foreach}
</div>
- <input role="yes" type="button" value="{'General_Save'|translate}" />
+ <input role="yes" type="button" value="{'General_Save'|translate}"/>
</div>
<div class="ui-confirm" id="renameDashboardConfirm">
<h2>{'Dashboard_RenameDashboard'|translate}</h2>
- <div id="newDashboardNameInput"><label for="newDashboardName">{'Dashboard_DashboardName'|translate} </label><input type="input" name="newDashboardName" id="newDashboardName" value=""/></div>
- <input role="yes" type="button" value="{'General_Save'|translate}" />
- <input role="cancel" type="button" value="{'General_Cancel'|translate}" />
+
+ <div id="newDashboardNameInput"><label for="newDashboardName">{'Dashboard_DashboardName'|translate} </label><input type="input" name="newDashboardName"
+ id="newDashboardName" value=""/>
+ </div>
+ <input role="yes" type="button" value="{'General_Save'|translate}"/>
+ <input role="cancel" type="button" value="{'General_Cancel'|translate}"/>
</div>
{if $isSuperUser}
- <div class="ui-confirm" id="copyDashboardToUserConfirm">
- <h2>{'Dashboard_CopyDashboardToUser'|translate}</h2>
- <div class="inputs"><label for="copyDashboardName">{'Dashboard_DashboardName'|translate} </label><input type="input" name="copyDashboardName" id="copyDashboardName" value=""/>
- <label for="copyDashboardUser">{'General_Username'|translate} </label>
- <select name="copyDashboardUser" id="copyDashboardUser">
- </select></div>
- <input role="yes" type="button" value="{'General_Ok'|translate}" />
- <input role="cancel" type="button" value="{'General_Cancel'|translate}" />
- </div>
+ <div class="ui-confirm" id="copyDashboardToUserConfirm">
+ <h2>{'Dashboard_CopyDashboardToUser'|translate}</h2>
+
+ <div class="inputs"><label for="copyDashboardName">{'Dashboard_DashboardName'|translate} </label><input type="input" name="copyDashboardName"
+ id="copyDashboardName" value=""/>
+ <label for="copyDashboardUser">{'General_Username'|translate} </label>
+ <select name="copyDashboardUser" id="copyDashboardUser">
+ </select></div>
+ <input role="yes" type="button" value="{'General_Ok'|translate}"/>
+ <input role="cancel" type="button" value="{'General_Cancel'|translate}"/>
+ </div>
{/if}
<div class="ui-confirm" id="createDashboardConfirm">
<h2>{'Dashboard_CreateNewDashboard'|translate}</h2>
+
<div id="createDashboardNameInput">
- <label>{'Dashboard_DashboardName'|translate} <input type="input" name="newDashboardName" id="createDashboardName" value=""/></label><br />
- <label><input type="radio" checked="checked" name="type" value="default" id="dashboard_type_default">{'Dashboard_DefaultDashboard'|translate}</label><br />
+ <label>{'Dashboard_DashboardName'|translate} <input type="input" name="newDashboardName" id="createDashboardName" value=""/></label><br/>
+ <label><input type="radio" checked="checked" name="type" value="default" id="dashboard_type_default">{'Dashboard_DefaultDashboard'|translate}
+ </label><br/>
<label><input type="radio" name="type" value="empty" id="dashboard_type_empty">{'Dashboard_EmptyDashboard'|translate}</label>
</div>
- <input role="yes" type="button" value="{'General_Yes'|translate}" />
- <input role="no" type="button" value="{'General_Cancel'|translate}" />
+ <input role="yes" type="button" value="{'General_Yes'|translate}"/>
+ <input role="no" type="button" value="{'General_Cancel'|translate}"/>
</div>
<div class="ui-confirm" id="removeDashboardConfirm">
<h2>{'Dashboard_RemoveDashboardConfirm'|translate:'<span></span>'}</h2>
+
<div class="popoverSubMessage">{'Dashboard_NotUndo'|translate:$resetDashboard}</div>
- <input role="yes" type="button" value="{'General_Yes'|translate}" />
- <input role="no" type="button" value="{'General_Cancel'|translate}" />
+ <input role="yes" type="button" value="{'General_Yes'|translate}"/>
+ <input role="no" type="button" value="{'General_Cancel'|translate}"/>
</div>
<div id="dashboardSettings">
@@ -100,24 +109,24 @@ $(document).ready(function() {
<li onclick="resetDashboard();">{'Dashboard_ResetDashboard'|translate}</li>
<li onclick="showChangeDashboardLayoutDialog();">{'Dashboard_ChangeDashboardLayout'|translate}</li>
{if ($userLogin && 'anonymous' != $userLogin)}
- <li onclick="renameDashboard();">{'Dashboard_RenameDashboard'|translate}</li>
- <li onclick="removeDashboard();" id="removeDashboardLink">{'Dashboard_RemoveDashboard'|translate}</li>
- {if ($isSuperUser)}
- <li onclick="setAsDefaultWidgets();">{'Dashboard_SetAsDefaultWidgets'|translate}</li>
- <li onclick="copyDashboardToUser();">{'Dashboard_CopyDashboardToUser'|translate}</li>
- {/if}
+ <li onclick="renameDashboard();">{'Dashboard_RenameDashboard'|translate}</li>
+ <li onclick="removeDashboard();" id="removeDashboardLink">{'Dashboard_RemoveDashboard'|translate}</li>
+ {if ($isSuperUser)}
+ <li onclick="setAsDefaultWidgets();">{'Dashboard_SetAsDefaultWidgets'|translate}</li>
+ <li onclick="copyDashboardToUser();">{'Dashboard_CopyDashboardToUser'|translate}</li>
+ {/if}
{/if}
</ul>
</li>
{if ($userLogin && 'anonymous' != $userLogin)}
- <li onclick="createDashboard();">{'Dashboard_CreateNewDashboard'|translate}</li>
+ <li onclick="createDashboard();">{'Dashboard_CreateNewDashboard'|translate}</li>
{/if}
</ul>
<ul class="widgetpreview-widgetlist"></ul>
<div class="widgetpreview-preview"></div>
</div>
-
+
<div class="clear"></div>
-
+
<div id="dashboardWidgetsArea"></div>
</div>
diff --git a/plugins/Dashboard/templates/standalone.tpl b/plugins/Dashboard/templates/standalone.tpl
index 4ef3feeb9c..bdfffaf095 100644
--- a/plugins/Dashboard/templates/standalone.tpl
+++ b/plugins/Dashboard/templates/standalone.tpl
@@ -1,11 +1,14 @@
{include file="Dashboard/templates/header.tpl"}
<div id="menuHead">
{include file="CoreHome/templates/period_select.tpl"}
- <div id="Dashboard"><ul>
- {foreach from=$dashboards item=dashboard}
- <li class="dashboardMenuItem" id="Dashboard_embeddedIndex_{$dashboard.iddashboard}"><a href="javascript:$('#dashboardWidgetsArea').dashboard('loadDashboard', {$dashboard.iddashboard});">{$dashboard.name|escape}</a></li>
- {/foreach}
- </ul></div>
+ <div id="Dashboard">
+ <ul>
+ {foreach from=$dashboards item=dashboard}
+ <li class="dashboardMenuItem" id="Dashboard_embeddedIndex_{$dashboard.iddashboard}"><a
+ href="javascript:$('#dashboardWidgetsArea').dashboard('loadDashboard', {$dashboard.iddashboard});">{$dashboard.name|escape}</a></li>
+ {/foreach}
+ </ul>
+ </div>
<div class="clear"></div>
</div>
{ajaxLoadingDiv}
diff --git a/plugins/Dashboard/templates/widgetMenu.js b/plugins/Dashboard/templates/widgetMenu.js
index f8d1f4a96e..7a8ac082d1 100644
--- a/plugins/Dashboard/templates/widgetMenu.js
+++ b/plugins/Dashboard/templates/widgetMenu.js
@@ -5,8 +5,7 @@
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
-function widgetsHelper()
-{
+function widgetsHelper() {
}
/**
@@ -14,9 +13,8 @@ function widgetsHelper()
*
* @return {object} object containing available widgets
*/
-widgetsHelper.getAvailableWidgets = function ()
-{
- if(!widgetsHelper.availableWidgets) {
+widgetsHelper.getAvailableWidgets = function () {
+ if (!widgetsHelper.availableWidgets) {
var ajaxRequest = new ajaxHelper();
ajaxRequest.addParams({
module: 'Dashboard',
@@ -29,7 +27,7 @@ widgetsHelper.getAvailableWidgets = function ()
);
ajaxRequest.send(true);
}
-
+
return widgetsHelper.availableWidgets;
};
@@ -39,18 +37,17 @@ widgetsHelper.getAvailableWidgets = function ()
* @param {string} uniqueId
* @return {object} widget object
*/
-widgetsHelper.getWidgetObjectFromUniqueId = function (uniqueId)
-{
- var widgets = widgetsHelper.getAvailableWidgets();
- for(var widgetCategory in widgets) {
- var widgetInCategory = widgets[widgetCategory];
- for(var i in widgetInCategory) {
- if(widgetInCategory[i]["uniqueId"] == uniqueId) {
- return widgetInCategory[i];
- }
- }
- }
- return false;
+widgetsHelper.getWidgetObjectFromUniqueId = function (uniqueId) {
+ var widgets = widgetsHelper.getAvailableWidgets();
+ for (var widgetCategory in widgets) {
+ var widgetInCategory = widgets[widgetCategory];
+ for (var i in widgetInCategory) {
+ if (widgetInCategory[i]["uniqueId"] == uniqueId) {
+ return widgetInCategory[i];
+ }
+ }
+ }
+ return false;
};
/**
@@ -59,13 +56,12 @@ widgetsHelper.getWidgetObjectFromUniqueId = function (uniqueId)
* @param {string} uniqueId unique id of the widget
* @return {string}
*/
-widgetsHelper.getWidgetNameFromUniqueId = function (uniqueId)
-{
- var widget = this.getWidgetObjectFromUniqueId(uniqueId);
- if(widget == false) {
- return false;
- }
- return widget["name"];
+widgetsHelper.getWidgetNameFromUniqueId = function (uniqueId) {
+ var widget = this.getWidgetObjectFromUniqueId(uniqueId);
+ if (widget == false) {
+ return false;
+ }
+ return widget["name"];
};
/**
@@ -77,29 +73,26 @@ widgetsHelper.getWidgetNameFromUniqueId = function (uniqueId)
* @return {object}
* @deprecated since 1.9.3 - will be removed in next major release. use widgetsHelper.loadWidgetAjax
*/
-widgetsHelper.getLoadWidgetAjaxRequest = function (widgetUniqueId, widgetParameters, onWidgetLoadedCallback)
-{
+widgetsHelper.getLoadWidgetAjaxRequest = function (widgetUniqueId, widgetParameters, onWidgetLoadedCallback) {
var token_auth = broadcast.getValueFromUrl('token_auth');
- if(token_auth.length && token_auth != 'anonymous')
- {
- widgetParameters['token_auth'] = token_auth;
+ if (token_auth.length && token_auth != 'anonymous') {
+ widgetParameters['token_auth'] = token_auth;
}
var disableLink = broadcast.getValueFromUrl('disableLink');
- if(disableLink.length)
- {
- widgetParameters['disableLink'] = disableLink;
+ if (disableLink.length) {
+ widgetParameters['disableLink'] = disableLink;
}
- return {
- widgetUniqueId:widgetUniqueId,
- type: 'GET',
- url: 'index.php',
- dataType: 'html',
- async: true,
- error: piwikHelper.ajaxHandleError,
- success: onWidgetLoadedCallback,
- data: piwikHelper.getQueryStringFromParameters(widgetParameters) + "&widget=1&idSite="+piwik.idSite+"&period="+piwik.period+"&date="+broadcast.getValueFromUrl('date') + "&token_auth=" + piwik.token_auth
- };
+ return {
+ widgetUniqueId: widgetUniqueId,
+ type: 'GET',
+ url: 'index.php',
+ dataType: 'html',
+ async: true,
+ error: piwikHelper.ajaxHandleError,
+ success: onWidgetLoadedCallback,
+ data: piwikHelper.getQueryStringFromParameters(widgetParameters) + "&widget=1&idSite=" + piwik.idSite + "&period=" + piwik.period + "&date=" + broadcast.getValueFromUrl('date') + "&token_auth=" + piwik.token_auth
+ };
};
/**
@@ -110,12 +103,10 @@ widgetsHelper.getLoadWidgetAjaxRequest = function (widgetUniqueId, widgetParamet
* @param {function} onWidgetLoadedCallback callback to be executed after widget is loaded
* @return {object}
*/
-widgetsHelper.loadWidgetAjax = function (widgetUniqueId, widgetParameters, onWidgetLoadedCallback)
-{
+widgetsHelper.loadWidgetAjax = function (widgetUniqueId, widgetParameters, onWidgetLoadedCallback) {
var disableLink = broadcast.getValueFromUrl('disableLink');
- if(disableLink.length)
- {
- widgetParameters['disableLink'] = disableLink;
+ if (disableLink.length) {
+ widgetParameters['disableLink'] = disableLink;
}
widgetParameters['widget'] = 1;
@@ -135,42 +126,41 @@ widgetsHelper.loadWidgetAjax = function (widgetUniqueId, widgetParameters, onWid
* @param {string} widgetName name of the widget
* @return {string} html for empty widget
*/
-widgetsHelper.getEmptyWidgetHtml = function (uniqueId, widgetName)
-{
- return '<div id="'+uniqueId+'" class="widget">'+
- '<div class="widgetTop">'+
- '<div class="button" id="close">'+
- '<img src="themes/default/images/close.png" title="'+_pk_translate('Dashboard_Close_js')+'" />'+
- '</div>'+
- '<div class="button" id="maximise">'+
- '<img src="themes/default/images/maximise.png" title="'+_pk_translate('Dashboard_Maximise_js')+'" />'+
- '</div>'+
- '<div class="button" id="minimise">'+
- '<img src="themes/default/images/minimise.png" title="'+_pk_translate('Dashboard_Minimise_js')+'" />'+
- '</div>'+
- '<div class="button" id="refresh">'+
- '<img src="themes/default/images/refresh.png" title="'+_pk_translate('Dashboard_Refresh_js')+'" />'+
- '</div>'+
- '<div class="widgetName">'+widgetName+'</div>'+
- '</div>'+
- '<div class="widgetContent">'+
- '<div class="widgetLoading">'+
- _pk_translate('Dashboard_LoadingWidget_js') +
- '</div>'+
- '</div>'+
- '</div>';
+widgetsHelper.getEmptyWidgetHtml = function (uniqueId, widgetName) {
+ return '<div id="' + uniqueId + '" class="widget">' +
+ '<div class="widgetTop">' +
+ '<div class="button" id="close">' +
+ '<img src="themes/default/images/close.png" title="' + _pk_translate('Dashboard_Close_js') + '" />' +
+ '</div>' +
+ '<div class="button" id="maximise">' +
+ '<img src="themes/default/images/maximise.png" title="' + _pk_translate('Dashboard_Maximise_js') + '" />' +
+ '</div>' +
+ '<div class="button" id="minimise">' +
+ '<img src="themes/default/images/minimise.png" title="' + _pk_translate('Dashboard_Minimise_js') + '" />' +
+ '</div>' +
+ '<div class="button" id="refresh">' +
+ '<img src="themes/default/images/refresh.png" title="' + _pk_translate('Dashboard_Refresh_js') + '" />' +
+ '</div>' +
+ '<div class="widgetName">' + widgetName + '</div>' +
+ '</div>' +
+ '<div class="widgetContent">' +
+ '<div class="widgetLoading">' +
+ _pk_translate('Dashboard_LoadingWidget_js') +
+ '</div>' +
+ '</div>' +
+ '</div>';
};
/**
* widgetPreview jQuery Extension
- *
+ *
* Converts an dom element to a widget preview
* Widget preview contains an categorylist, widgetlist and a preview
*/
-(function($) {
+(function ($) {
$.extend({
- widgetPreview: new function() {
-
+ widgetPreview: new function () {
+
/**
* Default settings for widgetPreview
* @type {object}
@@ -180,18 +170,18 @@ widgetsHelper.getEmptyWidgetHtml = function (uniqueId, widgetName)
* handler called after a widget preview is loaded in preview element
* @type {function}
*/
- onPreviewLoaded: function() {},
+ onPreviewLoaded: function () {},
/**
* handler called on click on element in widgetlist or widget header
* @type {function}
*/
- onSelect: function() {},
+ onSelect: function () {},
/**
* callback used to determine if a widget is available or not
* unavailable widgets aren't chooseable in widgetlist
* @type {function}
*/
- isWidgetAvailable: function(widgetUniqueId){ return true; },
+ isWidgetAvailable: function (widgetUniqueId) { return true; },
/**
* should the lists and preview be reset on widget selection?
* @type {boolean}
@@ -208,58 +198,58 @@ widgetsHelper.getEmptyWidgetHtml = function (uniqueId, widgetName)
choosenClass: 'widgetpreview-choosen',
unavailableClass: 'widgetpreview-unavailable'
};
-
+
var availableWidgets, widgetPreview, widgetAjaxRequest = null;
-
+
/**
* Returns the div to show category list in
* - if element doesn't exist it will be created and added
* - if element already exist it's content will be removed
- *
+ *
* @return {$} category list element
*/
function createWidgetCategoryList() {
-
- if(!$('.'+settings.categorylistClass, widgetPreview).length) {
- $(widgetPreview).append('<ul class="'+ settings.categorylistClass +'"></ul>');
+
+ if (!$('.' + settings.categorylistClass, widgetPreview).length) {
+ $(widgetPreview).append('<ul class="' + settings.categorylistClass + '"></ul>');
} else {
- $('.'+settings.categorylistClass, widgetPreview).empty();
+ $('.' + settings.categorylistClass, widgetPreview).empty();
}
-
+
for (var widgetCategory in availableWidgets) {
-
- $('.'+settings.categorylistClass, widgetPreview).append('<li>'+ widgetCategory +'</li>');
+
+ $('.' + settings.categorylistClass, widgetPreview).append('<li>' + widgetCategory + '</li>');
}
-
- return $('.'+settings.categorylistClass, widgetPreview);
+
+ return $('.' + settings.categorylistClass, widgetPreview);
};
-
+
/**
* Returns the div to show widget list in
* - if element doesn't exist it will be created and added
* - if element already exist it's content will be removed
- *
+ *
* @return {$} widget list element
*/
function createWidgetList() {
-
- if(!$('.'+settings.widgetlistClass, widgetPreview).length) {
- $(widgetPreview).append('<ul class="'+ settings.widgetlistClass +'"></ul>');
+
+ if (!$('.' + settings.widgetlistClass, widgetPreview).length) {
+ $(widgetPreview).append('<ul class="' + settings.widgetlistClass + '"></ul>');
} else {
- $('.'+settings.widgetlistClass+' li', widgetPreview).off('mouseover');
- $('.'+settings.widgetlistClass+' li', widgetPreview).off('click');
- $('.'+settings.widgetlistClass, widgetPreview).empty();
+ $('.' + settings.widgetlistClass + ' li', widgetPreview).off('mouseover');
+ $('.' + settings.widgetlistClass + ' li', widgetPreview).off('click');
+ $('.' + settings.widgetlistClass, widgetPreview).empty();
}
-
- if($('.'+settings.categorylistClass+' .'+settings.choosenClass, widgetPreview).length) {
- var position = $('.'+settings.categorylistClass+' .'+settings.choosenClass, widgetPreview).position().top -
- $('.'+settings.categorylistClass).position().top;
-
- $('.'+settings.widgetlistClass, widgetPreview).css('top', position);
- $('.'+settings.widgetlistClass, widgetPreview).css('marginBottom', position);
+
+ if ($('.' + settings.categorylistClass + ' .' + settings.choosenClass, widgetPreview).length) {
+ var position = $('.' + settings.categorylistClass + ' .' + settings.choosenClass, widgetPreview).position().top -
+ $('.' + settings.categorylistClass).position().top;
+
+ $('.' + settings.widgetlistClass, widgetPreview).css('top', position);
+ $('.' + settings.widgetlistClass, widgetPreview).css('marginBottom', position);
}
-
- return $('.'+settings.widgetlistClass, widgetPreview);
+
+ return $('.' + settings.widgetlistClass, widgetPreview);
};
/**
@@ -270,27 +260,27 @@ widgetsHelper.getEmptyWidgetHtml = function (uniqueId, widgetName)
*/
function showWidgetList(widgets) {
- var widgetList = createWidgetList(),
+ var widgetList = createWidgetList(),
widgetPreviewTimer;
- for (var j = 0; j < widgets.length; j++) {
- var widgetName = widgets[j]["name"];
- var widgetUniqueId = widgets[j]["uniqueId"];
+ for (var j = 0; j < widgets.length; j++) {
+ var widgetName = widgets[j]["name"];
+ var widgetUniqueId = widgets[j]["uniqueId"];
var widgetParameters = widgets[j]["parameters"];
- var widgetClass = '';
- if(!settings.isWidgetAvailable(widgetUniqueId)) {
+ var widgetClass = '';
+ if (!settings.isWidgetAvailable(widgetUniqueId)) {
widgetClass += ' ' + settings.unavailableClass;
}
- widgetList.append('<li class="'+ widgetClass +'" uniqueid="'+ widgetUniqueId +'">'+ widgetName +'</li>');
+ widgetList.append('<li class="' + widgetClass + '" uniqueid="' + widgetUniqueId + '">' + widgetName + '</li>');
}
// delay widget preview a few millisconds
- $('li:not(.'+settings.unavailableClass+')', widgetList).on('mouseenter', function(){
+ $('li:not(.' + settings.unavailableClass + ')', widgetList).on('mouseenter', function () {
var that = this,
widgetUniqueId = $(this).attr('uniqueid');
clearTimeout(widgetPreview);
- widgetPreviewTimer = setTimeout(function() {
+ widgetPreviewTimer = setTimeout(function () {
$('li', widgetList).removeClass(settings.choosenClass);
$(that).addClass(settings.choosenClass);
@@ -299,14 +289,14 @@ widgetsHelper.getEmptyWidgetHtml = function (uniqueId, widgetName)
});
// clear timeout after mouse has left
- $('li:not(.'+settings.unavailableClass+')', widgetList).on('mouseleave', function(){
+ $('li:not(.' + settings.unavailableClass + ')', widgetList).on('mouseleave', function () {
clearTimeout(widgetPreview);
});
- $('li:not(.'+settings.unavailableClass+')', widgetList).on('click', function(){
- if(!$('.widgetLoading', widgetPreview).length) {
+ $('li:not(.' + settings.unavailableClass + ')', widgetList).on('click', function () {
+ if (!$('.widgetLoading', widgetPreview).length) {
settings.onSelect($(this).attr('uniqueid'));
- if(settings.resetOnSelect) {
+ if (settings.resetOnSelect) {
resetWidgetPreview();
}
}
@@ -322,107 +312,107 @@ widgetsHelper.getEmptyWidgetHtml = function (uniqueId, widgetName)
* @return {$} preview element
*/
function createPreviewElement() {
-
- if(!$('.'+settings.widgetpreviewClass, widgetPreview).length) {
- $(widgetPreview).append('<div class="'+ settings.widgetpreviewClass +'"></div>');
+
+ if (!$('.' + settings.widgetpreviewClass, widgetPreview).length) {
+ $(widgetPreview).append('<div class="' + settings.widgetpreviewClass + '"></div>');
} else {
- $('.'+settings.widgetpreviewClass+' .widgetTop', widgetPreview).off('click');
- $('.'+settings.widgetpreviewClass, widgetPreview).empty();
+ $('.' + settings.widgetpreviewClass + ' .widgetTop', widgetPreview).off('click');
+ $('.' + settings.widgetpreviewClass, widgetPreview).empty();
}
-
- return $('.'+settings.widgetpreviewClass, widgetPreview);
+
+ return $('.' + settings.widgetpreviewClass, widgetPreview);
};
-
+
/**
* Show widget with the given uniqueId in preview
- *
+ *
* @param {string} widgetUniqueId unique id of widget to display
* @return {void}
*/
function showPreview(widgetUniqueId) {
// do not reload id widget already displayed
- if($('#'+widgetUniqueId, widgetPreview).length) return;
-
+ if ($('#' + widgetUniqueId, widgetPreview).length) return;
+
var previewElement = createPreviewElement();
-
+
var widget = widgetsHelper.getWidgetObjectFromUniqueId(widgetUniqueId);
var widgetParameters = widget['parameters'];
-
+
var emptyWidgetHtml = widgetsHelper.getEmptyWidgetHtml(
- widgetUniqueId,
- '<div title="'+_pk_translate("Dashboard_AddPreviewedWidget_js")+'">'+
- _pk_translate('Dashboard_WidgetPreview_js')+
- '</div>'
+ widgetUniqueId,
+ '<div title="' + _pk_translate("Dashboard_AddPreviewedWidget_js") + '">' +
+ _pk_translate('Dashboard_WidgetPreview_js') +
+ '</div>'
);
previewElement.html(emptyWidgetHtml);
-
+
var onWidgetLoadedCallback = function (response) {
- var widgetElement = $('#'+widgetUniqueId);
+ var widgetElement = $('#' + widgetUniqueId);
$('.widgetContent', widgetElement).html($(response));
$('.widgetContent', widgetElement).trigger('widget:create');
settings.onPreviewLoaded(widgetUniqueId, widgetElement);
- $('.'+settings.widgetpreviewClass+' .widgetTop', widgetPreview).on('click', function(){
+ $('.' + settings.widgetpreviewClass + ' .widgetTop', widgetPreview).on('click', function () {
settings.onSelect(widgetUniqueId);
- if(settings.resetOnSelect) {
+ if (settings.resetOnSelect) {
resetWidgetPreview();
}
return false;
});
};
-
+
// abort previous sent request
- if(widgetAjaxRequest) {
+ if (widgetAjaxRequest) {
widgetAjaxRequest.abort();
}
-
+
widgetAjaxRequest = widgetsHelper.loadWidgetAjax(widgetUniqueId, widgetParameters, onWidgetLoadedCallback);
};
-
+
/**
* Reset function
- *
+ *
* @return {void}
*/
function resetWidgetPreview() {
- $('.'+settings.categorylistClass+' li', widgetPreview).removeClass(settings.choosenClass);
+ $('.' + settings.categorylistClass + ' li', widgetPreview).removeClass(settings.choosenClass);
createWidgetList();
createPreviewElement();
};
-
+
/**
* Constructor
- *
+ *
* @param {object} userSettings Settings to be used
* @return {void}
*/
- this.construct = function(userSettings) {
-
- if(widgetPreview && userSettings == 'reset') {
+ this.construct = function (userSettings) {
+
+ if (widgetPreview && userSettings == 'reset') {
resetWidgetPreview();
return;
}
-
+
widgetPreview = this;
-
+
$(this).addClass('widgetpreview-base');
-
+
settings = jQuery.extend(settings, userSettings);
-
+
// set onSelect callback
- if(typeof settings.onSelect == 'function') {
+ if (typeof settings.onSelect == 'function') {
this.onSelect = settings.onSelect;
}
-
+
// set onPreviewLoaded callback
- if(typeof settings.onPreviewLoaded == 'function') {
+ if (typeof settings.onPreviewLoaded == 'function') {
this.onPreviewLoaded = settings.onPreviewLoaded;
}
-
+
availableWidgets = widgetsHelper.getAvailableWidgets();
-
+
var categoryList = createWidgetCategoryList();
-
- $('li', categoryList).on('mouseover', function(){
+
+ $('li', categoryList).on('mouseover', function () {
var category = $(this).text();
var widgets = availableWidgets[category];
$('li', categoryList).removeClass(settings.choosenClass);
@@ -433,7 +423,7 @@ widgetsHelper.getEmptyWidgetHtml = function (uniqueId, widgetName)
};
}
});
-
+
/**
* Makes widgetPreview available with $().widgetPreview()
*/
diff --git a/plugins/DoNotTrack/DoNotTrack.php b/plugins/DoNotTrack/DoNotTrack.php
index e609f5e9af..16d8cbf7ed 100644
--- a/plugins/DoNotTrack/DoNotTrack.php
+++ b/plugins/DoNotTrack/DoNotTrack.php
@@ -18,58 +18,57 @@
*/
class Piwik_DoNotTrack extends Piwik_Plugin
{
- /**
- * Return information about this plugin.
- *
- * @see Piwik_Plugin
- *
- * @return array
- */
- public function getInformation()
- {
- return array(
- 'description' => Piwik_Translate('DoNotTrack_PluginDescription'),
- 'author' => 'Piwik',
- 'author_homepage' => 'http://piwik.org/',
- 'version' => Piwik_Version::VERSION,
- 'translationAvailable' => false,
- 'TrackerPlugin' => true,
- );
- }
+ /**
+ * Return information about this plugin.
+ *
+ * @see Piwik_Plugin
+ *
+ * @return array
+ */
+ public function getInformation()
+ {
+ return array(
+ 'description' => Piwik_Translate('DoNotTrack_PluginDescription'),
+ 'author' => 'Piwik',
+ 'author_homepage' => 'http://piwik.org/',
+ 'version' => Piwik_Version::VERSION,
+ 'translationAvailable' => false,
+ 'TrackerPlugin' => true,
+ );
+ }
- public function getListHooksRegistered()
- {
- return array(
- 'Tracker.Visit.isExcluded' => 'checkHeader',
- );
- }
+ public function getListHooksRegistered()
+ {
+ return array(
+ 'Tracker.Visit.isExcluded' => 'checkHeader',
+ );
+ }
- /**
- * @param Piwik_Event_Notification $notification notification object
- */
- function checkHeader($notification)
- {
- if((isset($_SERVER['HTTP_X_DO_NOT_TRACK']) && $_SERVER['HTTP_X_DO_NOT_TRACK'] === '1')
- || (isset($_SERVER['HTTP_DNT']) && substr($_SERVER['HTTP_DNT'], 0, 1) === '1'))
- {
- $ua = Piwik_Tracker_Visit::getUserAgent($_REQUEST);
- if(strpos($ua, 'MSIE 10') !== false)
- {
- printDebug("INTERNET EXPLORER 10 Enables DNT by default, so Piwik ignores DNT for all IE10 browsers...");
- return;
- }
+ /**
+ * @param Piwik_Event_Notification $notification notification object
+ */
+ function checkHeader($notification)
+ {
+ if ((isset($_SERVER['HTTP_X_DO_NOT_TRACK']) && $_SERVER['HTTP_X_DO_NOT_TRACK'] === '1')
+ || (isset($_SERVER['HTTP_DNT']) && substr($_SERVER['HTTP_DNT'], 0, 1) === '1')
+ ) {
+ $ua = Piwik_Tracker_Visit::getUserAgent($_REQUEST);
+ if (strpos($ua, 'MSIE 10') !== false) {
+ printDebug("INTERNET EXPLORER 10 Enables DNT by default, so Piwik ignores DNT for all IE10 browsers...");
+ return;
+ }
- $exclude =& $notification->getNotificationObject();
- $exclude = true;
- printDebug("DoNotTrack found.");
+ $exclude =& $notification->getNotificationObject();
+ $exclude = true;
+ printDebug("DoNotTrack found.");
- $trackingCookie = Piwik_Tracker_IgnoreCookie::getTrackingCookie();
- $trackingCookie->delete();
+ $trackingCookie = Piwik_Tracker_IgnoreCookie::getTrackingCookie();
+ $trackingCookie->delete();
- // this is an optional supplement to the site's tracking status resource at:
- // /.well-known/dnt
- // per Tracking Preference Expression (draft)
- header('Tk: 1');
- }
- }
+ // this is an optional supplement to the site's tracking status resource at:
+ // /.well-known/dnt
+ // per Tracking Preference Expression (draft)
+ header('Tk: 1');
+ }
+ }
}
diff --git a/plugins/ExampleAPI/API.php b/plugins/ExampleAPI/API.php
index 7f9e2c86e2..cbae12e22a 100644
--- a/plugins/ExampleAPI/API.php
+++ b/plugins/ExampleAPI/API.php
@@ -10,168 +10,167 @@
*/
/**
- * The ExampleAPI is useful to developers building a custom Piwik plugin.
- *
+ * The ExampleAPI is useful to developers building a custom Piwik plugin.
+ *
* Please see the <a href='http://dev.piwik.org/trac/browser/trunk/plugins/ExampleAPI/API.php#L1' target='_blank'>source code in in the file plugins/ExampleAPI/API.php</a> for more documentation.
* @package Piwik_ExampleAPI
*/
class Piwik_ExampleAPI_API
{
- /**
- * * This is an example of a basic API file. Each plugin can have one public API.
- * Each public function in this class will be available to be called via the API.
- * Protected and private members will not be callable.
- * Functions can be called internally using the PHP objects directly, or via the
- * Piwik Web APIs, using HTTP requests. For more information, check out:
- * http://piwik.org/docs/analytics-api/calling-techniques
- *
- * Parameters are passed automatically from the GET request to the API functions.
- *
- * Common API uses include:
- * - requesting stats for a given date and period, for one or several websites
- * - creating, editing, deleting entities (Goals, Websites, Users)
- * - any logic that could be useful to a larger scope than the Controller (make a setting editable for example)
- *
- * It is highly recommended that all the plugin logic is done inside API implementations, and the
- * Controller and other objects would all call the API internally using, eg.
- * Piwik_ExampleAPI_API::getInstance()->getSum(1, 2);
- *
- */
- static private $instance = null;
-
- /**
- * Singleton
- * @return Piwik_ExampleAPI_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;
- }
-
- /**
- * Get Answer to Life
- * @return integer
- */
- public function getAnswerToLife()
- {
- return 42;
- }
-
- /**
- * Returns a custom object.
- * API format conversion will fail for this custom object.
- * If used internally, the data structure can be returned untouched by using
- * the API parameter 'format=original'
- *
- * @return Piwik_MagicObject Will return a standard Piwik error when called from the Web APIs
- */
- public function getObject()
- {
- return new Piwik_MagicObject();
- }
-
- /**
- * Sums two floats and returns the result.
- * The paramaters are set automatically from the GET request
- * when the API function is called. You can also use default values
- * as shown in this example.
- *
- * @param float $a
- * @param float $b
- * @return float
- */
- public function getSum($a = 0, $b = 0)
- {
- return (float)($a + $b);
- }
-
- /**
- * Returns null value
- *
- * @return null
- */
- public function getNull()
- {
- return null;
- }
-
- /**
- * Get array of descriptive text
- * When called from the Web API, you see that simple arrays like this one
- * are automatically converted in the various formats (xml, csv, etc.)
- *
- * @return array
- */
- public function getDescriptionArray()
- {
- return array('piwik','open source','web analytics','free', 'Strong message: Свободный Тибет');
- }
-
- /**
- * Returns a custom data table.
- * This data table will be converted to all available formats
- * when requested in the API request.
- *
- * @return Piwik_DataTable
- */
- public function getCompetitionDatatable()
- {
- $dataTable = new Piwik_DataTable();
-
- $row1 = new Piwik_DataTable_Row();
- $row1->setColumns( array('name' => 'piwik', 'license' => 'GPL'));
-
- // Rows Metadata is useful to store non stats data for example (logos, urls, etc.)
- // When printed out, they are simply merged with columns
- $row1->setMetadata('logo', 'logo.png');
- $dataTable->addRow($row1);
-
- $dataTable->addRowFromSimpleArray( array('name' => 'google analytics', 'license' => 'commercial') );
-
- return $dataTable;
- }
-
- /**
- * Get more information on the Answer to Life...
- *
- * @return string
- */
- public function getMoreInformationAnswerToLife()
- {
- return "Check http://en.wikipedia.org/wiki/The_Answer_to_Life,_the_Universe,_and_Everything";
- }
-
- /**
- * Returns a Multidimensional Array
- * Only supported in JSON
- *
- * @return array
- */
- public function getMultiArray()
- {
- $return = array(
- 'Limitation' => array(
- "Multi dimensional arrays is only supported by format=JSON",
- "Known limitation"
- ),
- 'Second Dimension' => array( true, false, 1, 0, 152, 'test', array( 42 => 'end') ),
- );
- return $return;
- }
+ /**
+ * * This is an example of a basic API file. Each plugin can have one public API.
+ * Each public function in this class will be available to be called via the API.
+ * Protected and private members will not be callable.
+ * Functions can be called internally using the PHP objects directly, or via the
+ * Piwik Web APIs, using HTTP requests. For more information, check out:
+ * http://piwik.org/docs/analytics-api/calling-techniques
+ *
+ * Parameters are passed automatically from the GET request to the API functions.
+ *
+ * Common API uses include:
+ * - requesting stats for a given date and period, for one or several websites
+ * - creating, editing, deleting entities (Goals, Websites, Users)
+ * - any logic that could be useful to a larger scope than the Controller (make a setting editable for example)
+ *
+ * It is highly recommended that all the plugin logic is done inside API implementations, and the
+ * Controller and other objects would all call the API internally using, eg.
+ * Piwik_ExampleAPI_API::getInstance()->getSum(1, 2);
+ *
+ */
+ static private $instance = null;
+
+ /**
+ * Singleton
+ * @return Piwik_ExampleAPI_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;
+ }
+
+ /**
+ * Get Answer to Life
+ * @return integer
+ */
+ public function getAnswerToLife()
+ {
+ return 42;
+ }
+
+ /**
+ * Returns a custom object.
+ * API format conversion will fail for this custom object.
+ * If used internally, the data structure can be returned untouched by using
+ * the API parameter 'format=original'
+ *
+ * @return Piwik_MagicObject Will return a standard Piwik error when called from the Web APIs
+ */
+ public function getObject()
+ {
+ return new Piwik_MagicObject();
+ }
+
+ /**
+ * Sums two floats and returns the result.
+ * The paramaters are set automatically from the GET request
+ * when the API function is called. You can also use default values
+ * as shown in this example.
+ *
+ * @param float $a
+ * @param float $b
+ * @return float
+ */
+ public function getSum($a = 0, $b = 0)
+ {
+ return (float)($a + $b);
+ }
+
+ /**
+ * Returns null value
+ *
+ * @return null
+ */
+ public function getNull()
+ {
+ return null;
+ }
+
+ /**
+ * Get array of descriptive text
+ * When called from the Web API, you see that simple arrays like this one
+ * are automatically converted in the various formats (xml, csv, etc.)
+ *
+ * @return array
+ */
+ public function getDescriptionArray()
+ {
+ return array('piwik', 'open source', 'web analytics', 'free', 'Strong message: Свободный Тибет');
+ }
+
+ /**
+ * Returns a custom data table.
+ * This data table will be converted to all available formats
+ * when requested in the API request.
+ *
+ * @return Piwik_DataTable
+ */
+ public function getCompetitionDatatable()
+ {
+ $dataTable = new Piwik_DataTable();
+
+ $row1 = new Piwik_DataTable_Row();
+ $row1->setColumns(array('name' => 'piwik', 'license' => 'GPL'));
+
+ // Rows Metadata is useful to store non stats data for example (logos, urls, etc.)
+ // When printed out, they are simply merged with columns
+ $row1->setMetadata('logo', 'logo.png');
+ $dataTable->addRow($row1);
+
+ $dataTable->addRowFromSimpleArray(array('name' => 'google analytics', 'license' => 'commercial'));
+
+ return $dataTable;
+ }
+
+ /**
+ * Get more information on the Answer to Life...
+ *
+ * @return string
+ */
+ public function getMoreInformationAnswerToLife()
+ {
+ return "Check http://en.wikipedia.org/wiki/The_Answer_to_Life,_the_Universe,_and_Everything";
+ }
+
+ /**
+ * Returns a Multidimensional Array
+ * Only supported in JSON
+ *
+ * @return array
+ */
+ public function getMultiArray()
+ {
+ $return = array(
+ 'Limitation' => array(
+ "Multi dimensional arrays is only supported by format=JSON",
+ "Known limitation"
+ ),
+ 'Second Dimension' => array(true, false, 1, 0, 152, 'test', array(42 => 'end')),
+ );
+ return $return;
+ }
}
/**
@@ -181,7 +180,11 @@ class Piwik_ExampleAPI_API
*/
class Piwik_MagicObject
{
- function Incredible() { return 'Incroyable'; }
- protected $wonderful = 'magnifique';
- public $great = 'formidable';
+ function Incredible()
+ {
+ return 'Incroyable';
+ }
+
+ protected $wonderful = 'magnifique';
+ public $great = 'formidable';
}
diff --git a/plugins/ExampleAPI/ExampleAPI.php b/plugins/ExampleAPI/ExampleAPI.php
index 4e251121b1..f26630e7ee 100644
--- a/plugins/ExampleAPI/ExampleAPI.php
+++ b/plugins/ExampleAPI/ExampleAPI.php
@@ -1,35 +1,35 @@
<?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_ExampleAPI
*/
-/**
+/**
* ExampleAPI plugin
*
* @package Piwik_ExampleAPI
*/
class Piwik_ExampleAPI extends Piwik_Plugin
{
- /**
- * Return information about this plugin.
- *
- * @see Piwik_Plugin
- *
- * @return array
- */
- public function getInformation()
- {
- return array(
- 'description' => Piwik_Translate('ExampleAPI_PluginDescription'),
- 'author' => 'Piwik',
- 'author_homepage' => 'http://piwik.org/',
- 'version' => '0.1',
- );
- }
+ /**
+ * Return information about this plugin.
+ *
+ * @see Piwik_Plugin
+ *
+ * @return array
+ */
+ public function getInformation()
+ {
+ return array(
+ 'description' => Piwik_Translate('ExampleAPI_PluginDescription'),
+ 'author' => 'Piwik',
+ 'author_homepage' => 'http://piwik.org/',
+ 'version' => '0.1',
+ );
+ }
}
diff --git a/plugins/ExamplePlugin/Controller.php b/plugins/ExamplePlugin/Controller.php
index e3e56cae3b..c4d7c494ce 100644
--- a/plugins/ExamplePlugin/Controller.php
+++ b/plugins/ExamplePlugin/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_ExamplePlugin
*/
@@ -14,107 +14,107 @@
* @package Piwik_ExamplePlugin
*/
class Piwik_ExamplePlugin_Controller extends Piwik_Controller
-{
- /**
- * Go to /piwik/?module=ExamplePlugin&action=helloWorld to execute this method
- *
- */
- function helloWorld()
- {
- echo "<p>Hello world! <br />";
- echo "Happy coding with Piwik :)</p>";
- }
-
- /**
- * See the result on piwik/?module=ExamplePlugin&action=exampleWidget
- * or in the dashboard > Add a new widget
- *
- */
- function exampleWidget()
- {
- echo "<p>Hello world! <br /> You can output whatever you want in widgets, and put them on dashboard or everywhere on the web (in your blog, website, etc.).
+{
+ /**
+ * Go to /piwik/?module=ExamplePlugin&action=helloWorld to execute this method
+ *
+ */
+ function helloWorld()
+ {
+ echo "<p>Hello world! <br />";
+ echo "Happy coding with Piwik :)</p>";
+ }
+
+ /**
+ * See the result on piwik/?module=ExamplePlugin&action=exampleWidget
+ * or in the dashboard > Add a new widget
+ *
+ */
+ function exampleWidget()
+ {
+ echo "<p>Hello world! <br /> You can output whatever you want in widgets, and put them on dashboard or everywhere on the web (in your blog, website, etc.).
<br />Widgets can include graphs, tables, flash, text, images, etc.
<br />It's very easy to create a new plugin and widgets in Piwik. Have a look at this example file (/plugins/ExamplePlugin/ExamplePlugin.php).
<div id='happycoding'><i>Happy coding!</i></div>
<div id='jsenabled'>You can easily use Jquery in widgets</div>
<p>
<script type=\"text/javascript\">$('#happycoding').hide().fadeIn(5000);$('#jsenabled').hide().css({'color':'red'}).fadeIn(10000);</script>";
- }
-
- function photostreamMatt()
- {
- echo '<object width="400" height="400"> <param name="flashvars" value="offsite=true&lang=en-us&page_show_url=%2Fphotos%2Fmatthieu-aubry%2Fsets%2F72157602308487455%2Fshow%2F&page_show_back_url=%2Fphotos%2Fmatthieu-aubry%2Fsets%2F72157602308487455%2F&set_id=72157602308487455&jump_to="></param> <param name="movie" value="http://www.flickr.com/apps/slideshow/show.swf?v=109615"></param> <param name="allowFullScreen" value="true"></param><embed type="application/x-shockwave-flash" src="http://www.flickr.com/apps/slideshow/show.swf?v=109615" allowFullScreen="true" flashvars="offsite=true&lang=en-us&page_show_url=%2Fphotos%2Fmatthieu-aubry%2Fsets%2F72157602308487455%2Fshow%2F&page_show_back_url=%2Fphotos%2Fmatthieu-aubry%2Fsets%2F72157602308487455%2F&set_id=72157602308487455&jump_to=" width="400" height="400"></embed></object>';
- }
-
- /**
- * this widgets shows how to make a remote API request to piwik.org
- * you find the main JS code in templates/piwikDownloadCount.tpl
- */
- function piwikDownloads()
- {
- $view = Piwik_View::factory('piwikDownloads');
- $this->setGeneralVariablesView($view);
- echo $view->render();
- }
-
- /**
- * This method displays a text containing an help about "How to build plugins for Piwik".
- * This help is then used on http://piwik.org/docs/plugins/functions
- *
- */
- function index()
- {
- $out = '';
- $out .= '<i>This page aims to list the different functions you can use when programming plugins for Piwik.</i><br />';
- $out .= '<b>Be careful, the following APIs may change in the near future as Piwik is still in development.</b><br />';
-
- $out .= '<h2>General</h2>';
- $out .= '<h3>Accessible from your plugin controller</h3>';
-
- $out .= '<code>$this->date</code> = current selected <b>Piwik_Date</b> object (<a href="https://github.com/piwik/piwik/blob/master/core/Date.php">class</a>)<br />';
- $out .= '<code>$period = Piwik_Common::getRequestVar("period");</code> - Get the current selected period<br />';
- $out .= '<code>$idSite = Piwik_Common::getRequestVar("idSite");</code> - Get the selected idSite<br />';
- $out .= '<code>$site = new Piwik_Site($idSite);</code> - Build the Piwik_Site object (<a href="https://github.com/piwik/piwik/tree/master/core/Site.php">class</a>)<br />';
- $out .= '<code>$this->str_date</code> = current selected date in YYYY-MM-DD format<br />';
-
- $out .= '<h3>Misc</h3>';
- $out .= '<code>Piwik_AddMenu( $mainMenuName, $subMenuName, $url );</code> - Adds an entry to the menu in the Piwik interface (See the example in the <a href="https://github.com/piwik/piwik/blob/1.0/plugins/UserCountry/UserCountry.php#L76">UserCountry Plugin file</a>)<br />';
- $out .= '<code>Piwik_AddWidget( $widgetCategory, $widgetName, $controllerName, $controllerAction, $customParameters = array());</code> - Adds a widget that users can add in the dashboard, or export using the Widgets link at the top of the screen. See the example in the <a href="https://github.com/piwik/piwik/blob/1.0/plugins/UserCountry/UserCountry.php#L70">UserCountry Plugin file</a> or any other plugin)<br />';
- $out .= '<code>Piwik_Common::prefixTable("site")</code> = <b>' . Piwik_Common::prefixTable("site") . '</b><br />';
-
-
- $out .= '<h2>User access</h2>';
- $out .= '<code>Piwik::getCurrentUserLogin()</code> = <b>' . Piwik::getCurrentUserLogin() . '</b><br />';
- $out .= '<code>Piwik::isUserHasSomeAdminAccess()</code> = <b>' . self::boolToString(Piwik::isUserHasSomeAdminAccess()) . '</b><br />';
- $out .= '<code>Piwik::isUserHasAdminAccess( array $idSites = array(1,2) )</code> = <b>' . self::boolToString(Piwik::isUserHasAdminAccess(array(1,2) )) . '</b><br />';
- $out .= '<code>Piwik::isUserHasViewAccess( array $idSites = array(1) ) </code> = <b>' . self::boolToString(Piwik::isUserHasViewAccess(array(1))) . '</b><br />';
- $out .= '<code>Piwik::isUserIsSuperUser()</code> = <b>' . self::boolToString(Piwik::isUserIsSuperUser()) . '</b><br />';
-
- $out .= '<h2>Execute SQL queries</h2>';
- $txtQuery = "SELECT token_auth FROM ".Piwik_Common::prefixTable('user')." WHERE login = ?";
- $result = Piwik_FetchOne($txtQuery, array('anonymous'));
- $out .= '<code>Piwik_FetchOne("'.$txtQuery.'", array("anonymous"))</code> = <b>' . var_export($result,true) . '</b><br />';
- $out .= '<br />';
-
- $query = Piwik_Query($txtQuery, array('anonymous'));
- $fetched = $query->fetch();
- $token_auth = $fetched['token_auth'];
-
- $out .= '<code>$query = Piwik_Query("'.$txtQuery.'", array("anonymous"))</code><br />';
- $out .= '<code>$fetched = $query->fetch();</code><br />';
- $out .= 'At this point, we have: <code>$fetched[\'token_auth\'] == <b>'.var_export($token_auth,true) . '</b></code><br />';
-
- $out .= '<h2>Example Sites information API</h2>';
- $out .= '<code>Piwik_SitesManager_API::getInstance()->getSitesWithViewAccess()</code> = <b><pre>' .var_export(Piwik_SitesManager_API::getInstance()->getSitesWithViewAccess(),true) . '</pre></b><br />';
- $out .= '<code>Piwik_SitesManager_API::getInstance()->getSitesWithAdminAccess()</code> = <b><pre>' .var_export(Piwik_SitesManager_API::getInstance()->getSitesWithAdminAccess(),true) . '</pre></b><br />';
-
- $out .= '<h2>Example API Users information</h2>';
- $out .= 'View the list of API methods you can call on <a href="http://piwik.org/docs/analytics-api/reference">API reference</a><br />';
- $out .= 'For example you can try <code>Piwik_UsersManager_API::getInstance()->getUsersSitesFromAccess("view");</code> or <code>Piwik_UsersManager_API::getInstance()->deleteUser("userToDelete");</code><br />';
-
- $out .= '<h2>Javascript in Piwik</h2>';
- $out .= '<h3>i18n internationalization</h3>';
- $out .= 'In order to translate strings within Javascript code, you can use the javascript function _pk_translate( token );.
+ }
+
+ function photostreamMatt()
+ {
+ echo '<object width="400" height="400"> <param name="flashvars" value="offsite=true&lang=en-us&page_show_url=%2Fphotos%2Fmatthieu-aubry%2Fsets%2F72157602308487455%2Fshow%2F&page_show_back_url=%2Fphotos%2Fmatthieu-aubry%2Fsets%2F72157602308487455%2F&set_id=72157602308487455&jump_to="></param> <param name="movie" value="http://www.flickr.com/apps/slideshow/show.swf?v=109615"></param> <param name="allowFullScreen" value="true"></param><embed type="application/x-shockwave-flash" src="http://www.flickr.com/apps/slideshow/show.swf?v=109615" allowFullScreen="true" flashvars="offsite=true&lang=en-us&page_show_url=%2Fphotos%2Fmatthieu-aubry%2Fsets%2F72157602308487455%2Fshow%2F&page_show_back_url=%2Fphotos%2Fmatthieu-aubry%2Fsets%2F72157602308487455%2F&set_id=72157602308487455&jump_to=" width="400" height="400"></embed></object>';
+ }
+
+ /**
+ * this widgets shows how to make a remote API request to piwik.org
+ * you find the main JS code in templates/piwikDownloadCount.tpl
+ */
+ function piwikDownloads()
+ {
+ $view = Piwik_View::factory('piwikDownloads');
+ $this->setGeneralVariablesView($view);
+ echo $view->render();
+ }
+
+ /**
+ * This method displays a text containing an help about "How to build plugins for Piwik".
+ * This help is then used on http://piwik.org/docs/plugins/functions
+ *
+ */
+ function index()
+ {
+ $out = '';
+ $out .= '<i>This page aims to list the different functions you can use when programming plugins for Piwik.</i><br />';
+ $out .= '<b>Be careful, the following APIs may change in the near future as Piwik is still in development.</b><br />';
+
+ $out .= '<h2>General</h2>';
+ $out .= '<h3>Accessible from your plugin controller</h3>';
+
+ $out .= '<code>$this->date</code> = current selected <b>Piwik_Date</b> object (<a href="https://github.com/piwik/piwik/blob/master/core/Date.php">class</a>)<br />';
+ $out .= '<code>$period = Piwik_Common::getRequestVar("period");</code> - Get the current selected period<br />';
+ $out .= '<code>$idSite = Piwik_Common::getRequestVar("idSite");</code> - Get the selected idSite<br />';
+ $out .= '<code>$site = new Piwik_Site($idSite);</code> - Build the Piwik_Site object (<a href="https://github.com/piwik/piwik/tree/master/core/Site.php">class</a>)<br />';
+ $out .= '<code>$this->str_date</code> = current selected date in YYYY-MM-DD format<br />';
+
+ $out .= '<h3>Misc</h3>';
+ $out .= '<code>Piwik_AddMenu( $mainMenuName, $subMenuName, $url );</code> - Adds an entry to the menu in the Piwik interface (See the example in the <a href="https://github.com/piwik/piwik/blob/1.0/plugins/UserCountry/UserCountry.php#L76">UserCountry Plugin file</a>)<br />';
+ $out .= '<code>Piwik_AddWidget( $widgetCategory, $widgetName, $controllerName, $controllerAction, $customParameters = array());</code> - Adds a widget that users can add in the dashboard, or export using the Widgets link at the top of the screen. See the example in the <a href="https://github.com/piwik/piwik/blob/1.0/plugins/UserCountry/UserCountry.php#L70">UserCountry Plugin file</a> or any other plugin)<br />';
+ $out .= '<code>Piwik_Common::prefixTable("site")</code> = <b>' . Piwik_Common::prefixTable("site") . '</b><br />';
+
+
+ $out .= '<h2>User access</h2>';
+ $out .= '<code>Piwik::getCurrentUserLogin()</code> = <b>' . Piwik::getCurrentUserLogin() . '</b><br />';
+ $out .= '<code>Piwik::isUserHasSomeAdminAccess()</code> = <b>' . self::boolToString(Piwik::isUserHasSomeAdminAccess()) . '</b><br />';
+ $out .= '<code>Piwik::isUserHasAdminAccess( array $idSites = array(1,2) )</code> = <b>' . self::boolToString(Piwik::isUserHasAdminAccess(array(1, 2))) . '</b><br />';
+ $out .= '<code>Piwik::isUserHasViewAccess( array $idSites = array(1) ) </code> = <b>' . self::boolToString(Piwik::isUserHasViewAccess(array(1))) . '</b><br />';
+ $out .= '<code>Piwik::isUserIsSuperUser()</code> = <b>' . self::boolToString(Piwik::isUserIsSuperUser()) . '</b><br />';
+
+ $out .= '<h2>Execute SQL queries</h2>';
+ $txtQuery = "SELECT token_auth FROM " . Piwik_Common::prefixTable('user') . " WHERE login = ?";
+ $result = Piwik_FetchOne($txtQuery, array('anonymous'));
+ $out .= '<code>Piwik_FetchOne("' . $txtQuery . '", array("anonymous"))</code> = <b>' . var_export($result, true) . '</b><br />';
+ $out .= '<br />';
+
+ $query = Piwik_Query($txtQuery, array('anonymous'));
+ $fetched = $query->fetch();
+ $token_auth = $fetched['token_auth'];
+
+ $out .= '<code>$query = Piwik_Query("' . $txtQuery . '", array("anonymous"))</code><br />';
+ $out .= '<code>$fetched = $query->fetch();</code><br />';
+ $out .= 'At this point, we have: <code>$fetched[\'token_auth\'] == <b>' . var_export($token_auth, true) . '</b></code><br />';
+
+ $out .= '<h2>Example Sites information API</h2>';
+ $out .= '<code>Piwik_SitesManager_API::getInstance()->getSitesWithViewAccess()</code> = <b><pre>' . var_export(Piwik_SitesManager_API::getInstance()->getSitesWithViewAccess(), true) . '</pre></b><br />';
+ $out .= '<code>Piwik_SitesManager_API::getInstance()->getSitesWithAdminAccess()</code> = <b><pre>' . var_export(Piwik_SitesManager_API::getInstance()->getSitesWithAdminAccess(), true) . '</pre></b><br />';
+
+ $out .= '<h2>Example API Users information</h2>';
+ $out .= 'View the list of API methods you can call on <a href="http://piwik.org/docs/analytics-api/reference">API reference</a><br />';
+ $out .= 'For example you can try <code>Piwik_UsersManager_API::getInstance()->getUsersSitesFromAccess("view");</code> or <code>Piwik_UsersManager_API::getInstance()->deleteUser("userToDelete");</code><br />';
+
+ $out .= '<h2>Javascript in Piwik</h2>';
+ $out .= '<h3>i18n internationalization</h3>';
+ $out .= 'In order to translate strings within Javascript code, you can use the javascript function _pk_translate( token );.
<ul><li>The "token" parameter is the string unique key found in the translation file. For this token string to be available in Javascript, you must
suffix your token by "_js" in the language file. For example, you can add <code>\'Goals_AddGoal_js\' => \'Add Goal\',</code> in the lang/en.php file</li>
<li>You then need to instruct Piwik to load your Javascript translations for your plugin; by default, all translation strings are not loaded in Javascript for performance reasons. This can be done by calling a custom-made Smarty modifier before the Javascript code requiring translations, eg.
@@ -122,20 +122,20 @@ class Piwik_ExamplePlugin_Controller extends Piwik_Controller
</li><li>You can then print this string from your JS code by doing <code>_pk_translate(\'Goals_AddGoal_js\');</code>.
</li></ul>';
- $out .= '<h3>Reload a widget in the dashboard</h3>';
- $out .= 'It is sometimes useful to reload one widget in the dashboard (for example, every 20 seconds for a real time widget, or after a setting change).
+ $out .= '<h3>Reload a widget in the dashboard</h3>';
+ $out .= 'It is sometimes useful to reload one widget in the dashboard (for example, every 20 seconds for a real time widget, or after a setting change).
You can easily force your widget to reload in the dashboard by calling the helper function <code>$(this).parents(\'[widgetId]\').dashboardWidget(\'reload\');</code>.';
-
- $out .= '<h2>Smarty plugins</h2>';
- $out .= 'There are some builtin plugins for Smarty especially developped for Piwik. <br />
+
+ $out .= '<h2>Smarty plugins</h2>';
+ $out .= 'There are some builtin plugins for Smarty especially developped for Piwik. <br />
You can find them on the <a href="https://github.com/piwik/piwik/tree/master/core/SmartyPlugins">Git at /core/SmartyPlugins</a>. <br />
More documentation to come about smarty plugins.<br />';
-
- echo $out;
- }
-
- static private function boolToString($bool)
- {
- return $bool ? "true" : "false";
- }
+
+ echo $out;
+ }
+
+ static private function boolToString($bool)
+ {
+ return $bool ? "true" : "false";
+ }
}
diff --git a/plugins/ExamplePlugin/ExamplePlugin.php b/plugins/ExamplePlugin/ExamplePlugin.php
index 24eab7a811..1334436bf4 100644
--- a/plugins/ExamplePlugin/ExamplePlugin.php
+++ b/plugins/ExamplePlugin/ExamplePlugin.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_ExamplePlugin
*/
@@ -15,65 +15,65 @@
*/
class Piwik_ExamplePlugin extends Piwik_Plugin
{
- /**
- * Return information about this plugin.
- *
- * @see Piwik_Plugin
- *
- * @return array
- */
- public function getInformation()
- {
- return array(
- 'description' => Piwik_Translate('ExamplePlugin_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' => '0.1',
- 'translationAvailable' => true,
- );
- }
-
- public function getListHooksRegistered()
- {
- return array(
+ /**
+ * Return information about this plugin.
+ *
+ * @see Piwik_Plugin
+ *
+ * @return array
+ */
+ public function getInformation()
+ {
+ return array(
+ 'description' => Piwik_Translate('ExamplePlugin_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' => '0.1',
+ 'translationAvailable' => true,
+ );
+ }
+
+ public function getListHooksRegistered()
+ {
+ return array(
// 'Controller.renderView' => 'addUniqueVisitorsColumnToGivenReport',
'WidgetsList.add' => 'addWidgets',
- );
- }
+ );
+ }
+
+ function activate()
+ {
+ // Executed every time plugin is Enabled
+ }
+
+ function deactivate()
+ {
+ // Executed every time plugin is disabled
+ }
+
+ /**
+ * @param Piwik_Event_Notification $notification notification object
+ */
+ function addUniqueVisitorsColumnToGivenReport($notification)
+ {
+ $view = $notification->getNotificationInfo();
+ $view = $view['view'];
+ if ($view->getCurrentControllerName() == 'Referers'
+ && $view->getCurrentControllerAction() == 'getWebsites'
+ ) {
+ $view->addColumnToDisplay('nb_uniq_visitors');
+ }
+ }
- function activate()
- {
- // Executed every time plugin is Enabled
- }
-
- function deactivate()
- {
- // Executed every time plugin is disabled
- }
-
- /**
- * @param Piwik_Event_Notification $notification notification object
- */
- function addUniqueVisitorsColumnToGivenReport($notification)
- {
- $view = $notification->getNotificationInfo();
- $view = $view['view'];
- if($view->getCurrentControllerName() == 'Referers'
- && $view->getCurrentControllerAction() == 'getWebsites')
- {
- $view->addColumnToDisplay('nb_uniq_visitors');
- }
- }
-
- function addWidgets()
- {
- // we register the widgets so they appear in the "Add a new widget" window in the dashboard
- // Note that the first two parameters can be either a normal string, or an index to a translation string
- Piwik_AddWidget('ExamplePlugin_exampleWidgets', 'ExamplePlugin_exampleWidget', 'ExamplePlugin', 'exampleWidget');
- Piwik_AddWidget('ExamplePlugin_exampleWidgets', 'ExamplePlugin_photostreamMatt', 'ExamplePlugin', 'photostreamMatt');
- Piwik_AddWidget('ExamplePlugin_exampleWidgets', 'ExamplePlugin_piwikForumVisits', 'ExamplePlugin', 'piwikDownloads');
- }
+ function addWidgets()
+ {
+ // we register the widgets so they appear in the "Add a new widget" window in the dashboard
+ // Note that the first two parameters can be either a normal string, or an index to a translation string
+ Piwik_AddWidget('ExamplePlugin_exampleWidgets', 'ExamplePlugin_exampleWidget', 'ExamplePlugin', 'exampleWidget');
+ Piwik_AddWidget('ExamplePlugin_exampleWidgets', 'ExamplePlugin_photostreamMatt', 'ExamplePlugin', 'photostreamMatt');
+ Piwik_AddWidget('ExamplePlugin_exampleWidgets', 'ExamplePlugin_piwikForumVisits', 'ExamplePlugin', 'piwikDownloads');
+ }
}
diff --git a/plugins/ExamplePlugin/config/local.config.sample.php b/plugins/ExamplePlugin/config/local.config.sample.php
index 24da8c15be..fe6e62b365 100644
--- a/plugins/ExamplePlugin/config/local.config.sample.php
+++ b/plugins/ExamplePlugin/config/local.config.sample.php
@@ -1,6 +1,6 @@
<?php
return array(
- 'id' => 'Example',
- 'name' => 'ExamplePlugin',
- 'description' => 'This is an example',
+ 'id' => 'Example',
+ 'name' => 'ExamplePlugin',
+ 'description' => 'This is an example',
);
diff --git a/plugins/ExamplePlugin/lang/en.php b/plugins/ExamplePlugin/lang/en.php
index ce951d54de..253072064e 100644
--- a/plugins/ExamplePlugin/lang/en.php
+++ b/plugins/ExamplePlugin/lang/en.php
@@ -1,20 +1,20 @@
-<?php
+<?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_ExamplePlugin
*/
$translations = array(
- 'ExamplePlugin_PluginDescription' => 'Example Plugin: This plugin shows how to create a very simple plugin, that exports two widgets in the Dashboard.',
- 'ExamplePlugin_exampleWidgets' => 'Example Widgets',
- 'ExamplePlugin_exampleWidget' => 'Example Widget',
- 'ExamplePlugin_photostreamMatt' => 'Matt\'s Photos',
- 'ExamplePlugin_piwikForumVisits' => 'Piwik Forums visits',
- 'ExamplePlugin_PiwikForumReceivedVisits' => 'On %s, Piwik forums received %s visits.'
-
+ 'ExamplePlugin_PluginDescription' => 'Example Plugin: This plugin shows how to create a very simple plugin, that exports two widgets in the Dashboard.',
+ 'ExamplePlugin_exampleWidgets' => 'Example Widgets',
+ 'ExamplePlugin_exampleWidget' => 'Example Widget',
+ 'ExamplePlugin_photostreamMatt' => 'Matt\'s Photos',
+ 'ExamplePlugin_piwikForumVisits' => 'Piwik Forums visits',
+ 'ExamplePlugin_PiwikForumReceivedVisits' => 'On %s, Piwik forums received %s visits.'
+
);
diff --git a/plugins/ExamplePlugin/templates/piwikDownloads.tpl b/plugins/ExamplePlugin/templates/piwikDownloads.tpl
index 910a81f213..22b10df4b3 100644
--- a/plugins/ExamplePlugin/templates/piwikDownloads.tpl
+++ b/plugins/ExamplePlugin/templates/piwikDownloads.tpl
@@ -1,20 +1,20 @@
<div style="padding:1.5em;text-align:center">
- {"ExamplePlugin_PiwikForumReceivedVisits"|translate:$prettyDate:'<b class="piwikDownloadCount_cnt" >...</b>'}
+ {"ExamplePlugin_PiwikForumReceivedVisits"|translate:$prettyDate:'<b class="piwikDownloadCount_cnt" >...</b>'}
</div>
{*
* loading piwik download stats from demo.piwik.org
*}
<script type="text/javascript">
-{literal}
- $.ajax({
- url: "http://demo.piwik.org/?module=API&method=VisitsSummary.getVisits"
- +"&idSite=7&period="+piwik.period+"&date="+broadcast.getValueFromUrl('date')
- +"&token_auth=anonymous&format=json",
- dataType: 'jsonp',
- jsonp: 'callback',
- success: function(data) {
- $('.piwikDownloadCount_cnt').html(data.value);
- }
- });
-{/literal}
+ {literal}
+ $.ajax({
+ url: "http://demo.piwik.org/?module=API&method=VisitsSummary.getVisits"
+ + "&idSite=7&period=" + piwik.period + "&date=" + broadcast.getValueFromUrl('date')
+ + "&token_auth=anonymous&format=json",
+ dataType: 'jsonp',
+ jsonp: 'callback',
+ success: function (data) {
+ $('.piwikDownloadCount_cnt').html(data.value);
+ }
+ });
+ {/literal}
</script>
diff --git a/plugins/ExampleRssWidget/Controller.php b/plugins/ExampleRssWidget/Controller.php
index c356ee7205..41e6691f9d 100644
--- a/plugins/ExampleRssWidget/Controller.php
+++ b/plugins/ExampleRssWidget/Controller.php
@@ -21,21 +21,20 @@ class Piwik_ExampleRssWidget_Controller extends Piwik_Controller
$rss = new Piwik_ExampleRssWidget_Rss('http://feeds.feedburner.com/Piwik');
$rss->showDescription(true);
echo $rss->get();
- } catch(Exception $e) {
+ } catch (Exception $e) {
$this->error($e);
}
}
public function rssChangelog()
{
- try
- {
+ try {
$rss = new Piwik_ExampleRssWidget_Rss('http://feeds.feedburner.com/PiwikReleases');
$rss->setCountPosts(1);
$rss->showDescription(false);
$rss->showContent(true);
echo $rss->get();
- } catch(Exception $e) {
+ } catch (Exception $e) {
$this->error($e);
}
}
diff --git a/plugins/ExampleRssWidget/ExampleRssWidget.php b/plugins/ExampleRssWidget/ExampleRssWidget.php
index 0c174345ee..4fac8a6f73 100644
--- a/plugins/ExampleRssWidget/ExampleRssWidget.php
+++ b/plugins/ExampleRssWidget/ExampleRssWidget.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_ExampleRssWidget
*/
@@ -15,22 +15,22 @@
*/
class Piwik_ExampleRssWidget extends Piwik_Plugin
{
- /**
- * Return information about this plugin.
- *
- * @see Piwik_Plugin
- *
- * @return array
- */
- public function getInformation()
- {
- return array(
- 'description' => Piwik_Translate('ExampleRssWidget_PluginDescription'),
- 'author' => 'Piwik',
- 'author_homepage' => 'http://piwik.org/',
- 'version' => '0.1',
- );
- }
+ /**
+ * Return information about this plugin.
+ *
+ * @see Piwik_Plugin
+ *
+ * @return array
+ */
+ public function getInformation()
+ {
+ return array(
+ 'description' => Piwik_Translate('ExampleRssWidget_PluginDescription'),
+ 'author' => 'Piwik',
+ 'author_homepage' => 'http://piwik.org/',
+ 'version' => '0.1',
+ );
+ }
/**
* Returns a list of registered hooks.
@@ -38,22 +38,22 @@ class Piwik_ExampleRssWidget extends Piwik_Plugin
* @return array
*/
public function getListHooksRegistered()
- {
- return array(
+ {
+ return array(
'AssetManager.getCssFiles' => 'getCssFiles',
- 'WidgetsList.add' => 'addWidgets'
+ 'WidgetsList.add' => 'addWidgets'
);
- }
+ }
+
+ /**
+ * @param Piwik_Event_Notification $notification notification object
+ */
+ public function getCssFiles($notification)
+ {
+ $cssFiles = & $notification->getNotificationObject();
- /**
- * @param Piwik_Event_Notification $notification notification object
- */
- public function getCssFiles( $notification )
- {
- $cssFiles = &$notification->getNotificationObject();
-
- $cssFiles[] = "plugins/ExampleRssWidget/templates/styles.css";
- }
+ $cssFiles[] = "plugins/ExampleRssWidget/templates/styles.css";
+ }
public function addWidgets()
{
diff --git a/plugins/ExampleRssWidget/Rss.php b/plugins/ExampleRssWidget/Rss.php
index 678001da8d..ee95a8a56d 100644
--- a/plugins/ExampleRssWidget/Rss.php
+++ b/plugins/ExampleRssWidget/Rss.php
@@ -42,8 +42,7 @@ class Piwik_ExampleRssWidget_Rss
public function get()
{
- try
- {
+ try {
$rss = Zend_Feed::import($this->url);
} catch (Zend_Feed_Exception $e) {
echo "Error while importing feed: {$e->getMessage()}\n";
@@ -53,27 +52,23 @@ class Piwik_ExampleRssWidget_Rss
$output = '<div style="padding:10px 15px;"><ul class="rss">';
$i = 0;
- foreach($rss as $post)
- {
+ foreach ($rss as $post) {
$title = $post->title();
$date = @strftime("%B %e, %Y", strtotime($post->pubDate()));
$link = $post->link();
- $output .= '<li><a class="rss-title" title="" target="_blank" href="?module=Proxy&action=redirect&url='.$link.'">'.$title.'</a>'.
- '<span class="rss-date">'.$date.'</span>';
- if($this->showDescription)
- {
- $output .= '<div class="rss-description">'.$post->description().'</div>';
+ $output .= '<li><a class="rss-title" title="" target="_blank" href="?module=Proxy&action=redirect&url=' . $link . '">' . $title . '</a>' .
+ '<span class="rss-date">' . $date . '</span>';
+ if ($this->showDescription) {
+ $output .= '<div class="rss-description">' . $post->description() . '</div>';
}
- if($this->showContent)
- {
- $output .= '<div class="rss-content">'.$post->content().'</div>';
+ if ($this->showContent) {
+ $output .= '<div class="rss-content">' . $post->content() . '</div>';
}
$output .= '</li>';
- if(++$i == $this->count)
- {
+ if (++$i == $this->count) {
break;
}
}
diff --git a/plugins/ExampleRssWidget/templates/styles.css b/plugins/ExampleRssWidget/templates/styles.css
index 185e57088b..5676571a7e 100644
--- a/plugins/ExampleRssWidget/templates/styles.css
+++ b/plugins/ExampleRssWidget/templates/styles.css
@@ -1,28 +1,33 @@
.rss ul {
- list-style:none outside none;
- padding:0;
+ list-style: none outside none;
+ padding: 0;
}
+
.rss li {
- line-height:140%;
- margin:0.5em 0 1em;
+ line-height: 140%;
+ margin: 0.5em 0 1em;
}
-.rss-title, .rss-date {
- float:left;
- font-size:14px;
- line-height:140%;
+
+.rss-title, .rss-date {
+ float: left;
+ font-size: 14px;
+ line-height: 140%;
}
-.rss-title{
- color:#2583AD;
- margin:0 0.5em 0.2em 0;
- font-weight:bold;
-}
+
+.rss-title {
+ color: #2583AD;
+ margin: 0 0.5em 0.2em 0;
+ font-weight: bold;
+}
+
.rss-date {
- color:#999999;
- margin:0;
+ color: #999999;
+ margin: 0;
}
+
.rss-content, .rss-description {
- clear:both;
- line-height:1.5em;
- font-size:11px;
- color:#333333;
+ clear: both;
+ line-height: 1.5em;
+ font-size: 11px;
+ color: #333333;
}
diff --git a/plugins/ExampleUI/API.php b/plugins/ExampleUI/API.php
index c88be1d598..91e29806be 100644
--- a/plugins/ExampleUI/API.php
+++ b/plugins/ExampleUI/API.php
@@ -1,109 +1,107 @@
<?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_ExampleUI
*/
/**
* ExampleUI API is also an example API useful if you are developing a Piwik plugin.
- *
- * The functions listed in this API are returning the data used in the Controller to draw graphs and
+ *
+ * The functions listed in this API are returning the data used in the Controller to draw graphs and
* display tables. See also the ExampleAPI plugin for an introduction to Piwik APIs.
- *
+ *
* @package Piwik_ExampleUI
*/
-class Piwik_ExampleUI_API
+class Piwik_ExampleUI_API
{
- static private $instance = null;
- static public function getInstance()
- {
- if (self::$instance == null)
- {
- self::$instance = new self;
- }
- return self::$instance;
- }
-
- public function getTemperaturesEvolution($date, $period)
- {
- $period = new Piwik_Period_Range($period, 'last30');
- $dateStart = $period->getDateStart()->toString('Y-m-d'); // eg. "2009-04-01"
- $dateEnd = $period->getDateEnd()->toString('Y-m-d'); // eg. "2009-04-30"
-
- // here you could select from your custom table in the database, eg.
- $query = "SELECT AVG(temperature)
+ static private $instance = null;
+
+ static public function getInstance()
+ {
+ if (self::$instance == null) {
+ self::$instance = new self;
+ }
+ return self::$instance;
+ }
+
+ public function getTemperaturesEvolution($date, $period)
+ {
+ $period = new Piwik_Period_Range($period, 'last30');
+ $dateStart = $period->getDateStart()->toString('Y-m-d'); // eg. "2009-04-01"
+ $dateEnd = $period->getDateEnd()->toString('Y-m-d'); // eg. "2009-04-30"
+
+ // here you could select from your custom table in the database, eg.
+ $query = "SELECT AVG(temperature)
FROM server_temperatures
WHERE date > ?
AND date < ?
GROUP BY date
ORDER BY date ASC";
- //$result = Piwik_FetchAll($query, array($dateStart, $dateEnd));
- // to keep things simple, we generate the data
- foreach($period->getSubperiods() as $subPeriod)
- {
- $server1 = mt_rand(50,90);
- $server2 = mt_rand(40, 110);
- $value = array('server1' => $server1, 'server2' => $server2);
- $temperatures[$subPeriod->getLocalizedShortString()] = $value;
- }
-
- // convert this array to a DataTable object
- $dataTable = new Piwik_DataTable();
- $dataTable->addRowsFromArrayWithIndexLabel($temperatures);
- return $dataTable;
- }
-
- // we generate an array of random server temperatures
- public function getTemperatures()
- {
- $xAxis = array(
- '0h', '1h', '2h', '3h', '4h', '5h', '6h', '7h', '8h', '9h', '10h', '11h',
- '12h', '13h', '14h', '15h', '16h', '17h', '18h', '19h', '20h', '21h', '22h', '23h',
- );
- $temperatureValues = array_slice(range(50,90), 0, count($xAxis));
- shuffle($temperatureValues);
- $temperatures = array();
- foreach($xAxis as $i => $xAxisLabel) {
- $temperatures[$xAxisLabel] = $temperatureValues[$i];
- }
-
- // convert this array to a DataTable object
- $dataTable = new Piwik_DataTable();
- $dataTable->addRowsFromArrayWithIndexLabel($temperatures);
- return $dataTable;
- }
-
- public function getPlanetRatios()
- {
- $planetRatios = array(
- 'Mercury' => 0.382,
- 'Venus' => 0.949,
- 'Earth' => 1.00,
- 'Mars' => 0.532,
- 'Jupiter' => 11.209,
- 'Saturn' => 9.449,
- 'Uranus' => 4.007,
- 'Neptune' => 3.883,
- );
- // convert this array to a DataTable object
- $dataTable = new Piwik_DataTable();
- $dataTable->addRowsFromArrayWithIndexLabel($planetRatios);
- return $dataTable;
- }
-
- public function getPlanetRatiosWithLogos()
- {
- $planetsDataTable = $this->getPlanetRatios();
- foreach($planetsDataTable->getRows() as $row)
- {
- $row->addMetadata('logo', "plugins/ExampleUI/images/icons-planet/".strtolower($row->getColumn('label').".png"));
- $row->addMetadata('url', "http://en.wikipedia.org/wiki/".$row->getColumn('label'));
- }
- return $planetsDataTable;
- }
+ //$result = Piwik_FetchAll($query, array($dateStart, $dateEnd));
+ // to keep things simple, we generate the data
+ foreach ($period->getSubperiods() as $subPeriod) {
+ $server1 = mt_rand(50, 90);
+ $server2 = mt_rand(40, 110);
+ $value = array('server1' => $server1, 'server2' => $server2);
+ $temperatures[$subPeriod->getLocalizedShortString()] = $value;
+ }
+
+ // convert this array to a DataTable object
+ $dataTable = new Piwik_DataTable();
+ $dataTable->addRowsFromArrayWithIndexLabel($temperatures);
+ return $dataTable;
+ }
+
+ // we generate an array of random server temperatures
+ public function getTemperatures()
+ {
+ $xAxis = array(
+ '0h', '1h', '2h', '3h', '4h', '5h', '6h', '7h', '8h', '9h', '10h', '11h',
+ '12h', '13h', '14h', '15h', '16h', '17h', '18h', '19h', '20h', '21h', '22h', '23h',
+ );
+ $temperatureValues = array_slice(range(50, 90), 0, count($xAxis));
+ shuffle($temperatureValues);
+ $temperatures = array();
+ foreach ($xAxis as $i => $xAxisLabel) {
+ $temperatures[$xAxisLabel] = $temperatureValues[$i];
+ }
+
+ // convert this array to a DataTable object
+ $dataTable = new Piwik_DataTable();
+ $dataTable->addRowsFromArrayWithIndexLabel($temperatures);
+ return $dataTable;
+ }
+
+ public function getPlanetRatios()
+ {
+ $planetRatios = array(
+ 'Mercury' => 0.382,
+ 'Venus' => 0.949,
+ 'Earth' => 1.00,
+ 'Mars' => 0.532,
+ 'Jupiter' => 11.209,
+ 'Saturn' => 9.449,
+ 'Uranus' => 4.007,
+ 'Neptune' => 3.883,
+ );
+ // convert this array to a DataTable object
+ $dataTable = new Piwik_DataTable();
+ $dataTable->addRowsFromArrayWithIndexLabel($planetRatios);
+ return $dataTable;
+ }
+
+ public function getPlanetRatiosWithLogos()
+ {
+ $planetsDataTable = $this->getPlanetRatios();
+ foreach ($planetsDataTable->getRows() as $row) {
+ $row->addMetadata('logo', "plugins/ExampleUI/images/icons-planet/" . strtolower($row->getColumn('label') . ".png"));
+ $row->addMetadata('url', "http://en.wikipedia.org/wiki/" . $row->getColumn('label'));
+ }
+ return $planetsDataTable;
+ }
}
diff --git a/plugins/ExampleUI/Controller.php b/plugins/ExampleUI/Controller.php
index c57bbeb19e..8a997a9e92 100644
--- a/plugins/ExampleUI/Controller.php
+++ b/plugins/ExampleUI/Controller.php
@@ -14,124 +14,126 @@
*/
class Piwik_ExampleUI_Controller extends Piwik_Controller
{
- function dataTables()
- {
- $view = Piwik_ViewDataTable::factory('table');
- $view->init( $this->pluginName, __FUNCTION__, 'ExampleUI.getTemperatures' );
- $view->setColumnTranslation('value', "Temperature in °C");
- $view->setColumnTranslation('label', "Hour of day");
- $view->setSortedColumn('label', 'asc');
- $view->setGraphLimit( 24 );
- $view->setLimit( 24 );
- $view->disableExcludeLowPopulation();
- $view->disableShowAllColumns();
- $view->disableRowEvolution();
- $view->setAxisYUnit('°C'); // useful if the user requests the bar graph
- return $this->renderView($view);
- }
-
- function evolutionGraph()
- {
- echo "<h2>Evolution of server temperatures over the last few days</h2>";
- $this->echoEvolutionGraph();
- }
-
- function echoEvolutionGraph()
- {
- $view = Piwik_ViewDataTable::factory('graphEvolution');
- $view->init( $this->pluginName, __FUNCTION__, 'ExampleUI.getTemperaturesEvolution' );
- $view->setColumnTranslation('server1', "Temperature server piwik.org");
- $view->setColumnTranslation('server2', "Temperature server dev.piwik.org");
- $view->setAxisYUnit('°C'); // useful if the user requests the bar graph
- return $this->renderView($view);
- }
-
- function barGraph()
- {
- $view = Piwik_ViewDataTable::factory('graphVerticalBar');
- $view->init( $this->pluginName, __FUNCTION__, 'ExampleUI.getTemperatures' );
- $view->setColumnTranslation('value', "Temperature");
- $view->setAxisYUnit('°C');
- $view->setGraphLimit( 24 );
- $view->disableFooter();
- return $this->renderView($view);
- }
-
- function pieGraph()
- {
- $view = Piwik_ViewDataTable::factory('graphPie');
- $view->init( $this->pluginName, __FUNCTION__, 'ExampleUI.getPlanetRatios' );
- $view->setColumnsToDisplay( 'value' );
- $view->setColumnTranslation('value', "times the diameter of Earth");
- $view->setGraphLimit( 10 );
- $view->disableFooterIcons();
- return $this->renderView($view);
- }
-
- function tagClouds()
- {
- echo "<h2>Simple tag cloud</h2>";
- $this->echoSimpleTagClouds();
-
- echo "<br /><br /><h2>Advanced tag cloud: with logos and links</h2>
+ function dataTables()
+ {
+ $view = Piwik_ViewDataTable::factory('table');
+ $view->init($this->pluginName, __FUNCTION__, 'ExampleUI.getTemperatures');
+ $view->setColumnTranslation('value', "Temperature in °C");
+ $view->setColumnTranslation('label', "Hour of day");
+ $view->setSortedColumn('label', 'asc');
+ $view->setGraphLimit(24);
+ $view->setLimit(24);
+ $view->disableExcludeLowPopulation();
+ $view->disableShowAllColumns();
+ $view->disableRowEvolution();
+ $view->setAxisYUnit('°C'); // useful if the user requests the bar graph
+ return $this->renderView($view);
+ }
+
+ function evolutionGraph()
+ {
+ echo "<h2>Evolution of server temperatures over the last few days</h2>";
+ $this->echoEvolutionGraph();
+ }
+
+ function echoEvolutionGraph()
+ {
+ $view = Piwik_ViewDataTable::factory('graphEvolution');
+ $view->init($this->pluginName, __FUNCTION__, 'ExampleUI.getTemperaturesEvolution');
+ $view->setColumnTranslation('server1', "Temperature server piwik.org");
+ $view->setColumnTranslation('server2', "Temperature server dev.piwik.org");
+ $view->setAxisYUnit('°C'); // useful if the user requests the bar graph
+ return $this->renderView($view);
+ }
+
+ function barGraph()
+ {
+ $view = Piwik_ViewDataTable::factory('graphVerticalBar');
+ $view->init($this->pluginName, __FUNCTION__, 'ExampleUI.getTemperatures');
+ $view->setColumnTranslation('value', "Temperature");
+ $view->setAxisYUnit('°C');
+ $view->setGraphLimit(24);
+ $view->disableFooter();
+ return $this->renderView($view);
+ }
+
+ function pieGraph()
+ {
+ $view = Piwik_ViewDataTable::factory('graphPie');
+ $view->init($this->pluginName, __FUNCTION__, 'ExampleUI.getPlanetRatios');
+ $view->setColumnsToDisplay('value');
+ $view->setColumnTranslation('value', "times the diameter of Earth");
+ $view->setGraphLimit(10);
+ $view->disableFooterIcons();
+ return $this->renderView($view);
+ }
+
+ function tagClouds()
+ {
+ echo "<h2>Simple tag cloud</h2>";
+ $this->echoSimpleTagClouds();
+
+ echo "<br /><br /><h2>Advanced tag cloud: with logos and links</h2>
<ul style='list-style-type:disc;margin-left:50px'>
<li>The logo size is proportional to the value returned by the API</li>
<li>The logo is linked to a specific URL</li>
</ul><br /><br />";
- $this->echoAdvancedTagClouds();
- }
- function echoSimpleTagClouds()
- {
- $view = Piwik_ViewDataTable::factory('cloud');
- $view->init( $this->pluginName, __FUNCTION__, 'ExampleUI.getPlanetRatios' );
- $view->setColumnsToDisplay( array('label','value') );
- $view->setColumnTranslation('value', "times the diameter of Earth");
- $view->disableFooter();
- $this->renderView($view);
- }
- function echoAdvancedTagClouds()
- {
- $view = Piwik_ViewDataTable::factory('cloud');
- $view->init( $this->pluginName, __FUNCTION__, 'ExampleUI.getPlanetRatiosWithLogos' );
- $view->setDisplayLogoInTagCloud(true);
- $view->disableFooterExceptExportIcons();
- $view->setColumnsToDisplay( array('label','value') );
- $view->setColumnTranslation('value', "times the diameter of Earth");
- $this->renderView($view);
- }
-
- function sparklines()
- {
- require_once PIWIK_INCLUDE_PATH . '/core/SmartyPlugins/function.sparkline.php';
- $srcSparkline1 = Piwik_Url::getCurrentQueryStringWithParametersModified(array('action'=>'generateSparkline', 'server' => 'server1', 'rand'=>mt_rand()));
- $htmlSparkline1 = smarty_function_sparkline(array('src' => $srcSparkline1));
- echo "<div class='sparkline'>$htmlSparkline1 Evolution of temperature for server piwik.org</div>";
-
- $srcSparkline2 = Piwik_Url::getCurrentQueryStringWithParametersModified(array('action'=>'generateSparkline', 'server' => 'server2', 'rand'=>mt_rand()));
- $htmlSparkline2 = smarty_function_sparkline(array('src' => $srcSparkline2));
- echo "<div class='sparkline'>$htmlSparkline2 Evolution of temperature for server dev.piwik.org</div>";
- }
-
- function generateSparkline()
- {
- $serverRequested = Piwik_Common::getRequestVar('server', '');
- $view = Piwik_ViewDataTable::factory('sparkline');
- $view->init( $this->pluginName, __FUNCTION__, 'ExampleUI.getTemperaturesEvolution' );
- $view->setColumnsToDisplay($serverRequested);
- $this->renderView($view);
- }
-
- function misc()
- {
- echo "<h2>Evolution graph filtered to Google and Yahoo!</h2>";
- $this->echoDataTableSearchEnginesFiltered();
- }
-
- function echoDataTableSearchEnginesFiltered()
- {
- $view = $this->getLastUnitGraph($this->pluginName, __FUNCTION__, 'Referers.getSearchEngines');
- $view->setColumnsToDisplay( 'nb_visits' );
- $view->setSearchPattern('^(Google|Yahoo!)$', 'label');
- return $this->renderView($view);
- }
+ $this->echoAdvancedTagClouds();
+ }
+
+ function echoSimpleTagClouds()
+ {
+ $view = Piwik_ViewDataTable::factory('cloud');
+ $view->init($this->pluginName, __FUNCTION__, 'ExampleUI.getPlanetRatios');
+ $view->setColumnsToDisplay(array('label', 'value'));
+ $view->setColumnTranslation('value', "times the diameter of Earth");
+ $view->disableFooter();
+ $this->renderView($view);
+ }
+
+ function echoAdvancedTagClouds()
+ {
+ $view = Piwik_ViewDataTable::factory('cloud');
+ $view->init($this->pluginName, __FUNCTION__, 'ExampleUI.getPlanetRatiosWithLogos');
+ $view->setDisplayLogoInTagCloud(true);
+ $view->disableFooterExceptExportIcons();
+ $view->setColumnsToDisplay(array('label', 'value'));
+ $view->setColumnTranslation('value', "times the diameter of Earth");
+ $this->renderView($view);
+ }
+
+ function sparklines()
+ {
+ require_once PIWIK_INCLUDE_PATH . '/core/SmartyPlugins/function.sparkline.php';
+ $srcSparkline1 = Piwik_Url::getCurrentQueryStringWithParametersModified(array('action' => 'generateSparkline', 'server' => 'server1', 'rand' => mt_rand()));
+ $htmlSparkline1 = smarty_function_sparkline(array('src' => $srcSparkline1));
+ echo "<div class='sparkline'>$htmlSparkline1 Evolution of temperature for server piwik.org</div>";
+
+ $srcSparkline2 = Piwik_Url::getCurrentQueryStringWithParametersModified(array('action' => 'generateSparkline', 'server' => 'server2', 'rand' => mt_rand()));
+ $htmlSparkline2 = smarty_function_sparkline(array('src' => $srcSparkline2));
+ echo "<div class='sparkline'>$htmlSparkline2 Evolution of temperature for server dev.piwik.org</div>";
+ }
+
+ function generateSparkline()
+ {
+ $serverRequested = Piwik_Common::getRequestVar('server', '');
+ $view = Piwik_ViewDataTable::factory('sparkline');
+ $view->init($this->pluginName, __FUNCTION__, 'ExampleUI.getTemperaturesEvolution');
+ $view->setColumnsToDisplay($serverRequested);
+ $this->renderView($view);
+ }
+
+ function misc()
+ {
+ echo "<h2>Evolution graph filtered to Google and Yahoo!</h2>";
+ $this->echoDataTableSearchEnginesFiltered();
+ }
+
+ function echoDataTableSearchEnginesFiltered()
+ {
+ $view = $this->getLastUnitGraph($this->pluginName, __FUNCTION__, 'Referers.getSearchEngines');
+ $view->setColumnsToDisplay('nb_visits');
+ $view->setSearchPattern('^(Google|Yahoo!)$', 'label');
+ return $this->renderView($view);
+ }
}
diff --git a/plugins/ExampleUI/ExampleUI.php b/plugins/ExampleUI/ExampleUI.php
index 0937269bb3..11be32f4bd 100644
--- a/plugins/ExampleUI/ExampleUI.php
+++ b/plugins/ExampleUI/ExampleUI.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_ExampleUI
*/
@@ -25,48 +25,47 @@
*/
class Piwik_ExampleUI extends Piwik_Plugin
{
- /**
- * Return information about this plugin.
- *
- * @see Piwik_Plugin
- *
- * @return array
- */
- public function getInformation()
- {
- return array(
- 'description' => Piwik_Translate('ExampleUI_PluginDescription'),
- 'author' => 'Piwik',
- 'author_homepage' => 'http://piwik.org/',
- 'version' => '0.1',
- );
- }
-
- function getListHooksRegistered()
- {
- $hooks = array(
- 'Menu.add' => 'addMenus',
- );
- return $hooks;
- }
+ /**
+ * Return information about this plugin.
+ *
+ * @see Piwik_Plugin
+ *
+ * @return array
+ */
+ public function getInformation()
+ {
+ return array(
+ 'description' => Piwik_Translate('ExampleUI_PluginDescription'),
+ 'author' => 'Piwik',
+ 'author_homepage' => 'http://piwik.org/',
+ 'version' => '0.1',
+ );
+ }
+
+ function getListHooksRegistered()
+ {
+ $hooks = array(
+ 'Menu.add' => 'addMenus',
+ );
+ return $hooks;
+ }
- function addMenus()
- {
- $menus = array(
- 'Data tables' => 'dataTables',
- 'Evolution graph' => 'evolutionGraph',
- 'Bar graph' => 'barGraph',
- 'Pie graph' => 'pieGraph',
- 'Tag clouds' => 'tagClouds',
- 'Sparklines' => 'sparklines',
- 'Misc' => 'misc',
- );
+ function addMenus()
+ {
+ $menus = array(
+ 'Data tables' => 'dataTables',
+ 'Evolution graph' => 'evolutionGraph',
+ 'Bar graph' => 'barGraph',
+ 'Pie graph' => 'pieGraph',
+ 'Tag clouds' => 'tagClouds',
+ 'Sparklines' => 'sparklines',
+ 'Misc' => 'misc',
+ );
- Piwik_AddMenu('UI Framework', '', array('module' => 'ExampleUI', 'action' => 'dataTables'), true, 30);
- $order = 1;
- foreach($menus as $subMenu => $action)
- {
- Piwik_AddMenu('UI Framework', $subMenu, array('module' => 'ExampleUI', 'action' => $action), true, $order++);
- }
- }
+ Piwik_AddMenu('UI Framework', '', array('module' => 'ExampleUI', 'action' => 'dataTables'), true, 30);
+ $order = 1;
+ foreach ($menus as $subMenu => $action) {
+ Piwik_AddMenu('UI Framework', $subMenu, array('module' => 'ExampleUI', 'action' => $action), true, $order++);
+ }
+ }
}
diff --git a/plugins/Feedback/Controller.php b/plugins/Feedback/Controller.php
index 9eae844333..a584caefe2 100644
--- a/plugins/Feedback/Controller.php
+++ b/plugins/Feedback/Controller.php
@@ -15,66 +15,60 @@
*/
class Piwik_Feedback_Controller extends Piwik_Controller
{
- function index()
- {
- $view = Piwik_View::factory('index');
- $view->nonce = Piwik_Nonce::getNonce('Piwik_Feedback.sendFeedback', 3600);
- echo $view->render();
- }
+ function index()
+ {
+ $view = Piwik_View::factory('index');
+ $view->nonce = Piwik_Nonce::getNonce('Piwik_Feedback.sendFeedback', 3600);
+ echo $view->render();
+ }
- /**
- * send email to Piwik team and display nice thanks
- * @throws Exception
- */
- function sendFeedback()
- {
- $email = Piwik_Common::getRequestVar('email', '', 'string');
- $body = Piwik_Common::getRequestVar('body', '', 'string');
- $category = Piwik_Common::getRequestVar('category', '', 'string');
- $nonce = Piwik_Common::getRequestVar('nonce', '', 'string');
+ /**
+ * send email to Piwik team and display nice thanks
+ * @throws Exception
+ */
+ function sendFeedback()
+ {
+ $email = Piwik_Common::getRequestVar('email', '', 'string');
+ $body = Piwik_Common::getRequestVar('body', '', 'string');
+ $category = Piwik_Common::getRequestVar('category', '', 'string');
+ $nonce = Piwik_Common::getRequestVar('nonce', '', 'string');
- $view = Piwik_View::factory('sent');
- $view->feedbackEmailAddress = Piwik_Config::getInstance()->General['feedback_email_address'];
- try
- {
- $minimumBodyLength = 40;
- if(strlen($body) < $minimumBodyLength
- // Avoid those really annoying automated security test emails
- || strpos($email, 'probe@') !== false
- || strpos($body, '&lt;probe') !== false)
- {
- throw new Exception(Piwik_TranslateException('Feedback_ExceptionBodyLength', array($minimumBodyLength)));
- }
- if(!Piwik::isValidEmailString($email))
- {
- throw new Exception(Piwik_TranslateException('UsersManager_ExceptionInvalidEmail'));
- }
- if(preg_match('/https?:/i', $body))
- {
- throw new Exception(Piwik_TranslateException('Feedback_ExceptionNoUrls'));
- }
- if(!Piwik_Nonce::verifyNonce('Piwik_Feedback.sendFeedback', $nonce))
- {
- throw new Exception(Piwik_TranslateException('General_ExceptionNonceMismatch'));
- }
- Piwik_Nonce::discardNonce('Piwik_Feedback.sendFeedback');
+ $view = Piwik_View::factory('sent');
+ $view->feedbackEmailAddress = Piwik_Config::getInstance()->General['feedback_email_address'];
+ try {
+ $minimumBodyLength = 40;
+ if (strlen($body) < $minimumBodyLength
+ // Avoid those really annoying automated security test emails
+ || strpos($email, 'probe@') !== false
+ || strpos($body, '&lt;probe') !== false
+ ) {
+ throw new Exception(Piwik_TranslateException('Feedback_ExceptionBodyLength', array($minimumBodyLength)));
+ }
+ if (!Piwik::isValidEmailString($email)) {
+ throw new Exception(Piwik_TranslateException('UsersManager_ExceptionInvalidEmail'));
+ }
+ if (preg_match('/https?:/i', $body)) {
+ throw new Exception(Piwik_TranslateException('Feedback_ExceptionNoUrls'));
+ }
+ if (!Piwik_Nonce::verifyNonce('Piwik_Feedback.sendFeedback', $nonce)) {
+ throw new Exception(Piwik_TranslateException('General_ExceptionNonceMismatch'));
+ }
+ Piwik_Nonce::discardNonce('Piwik_Feedback.sendFeedback');
- $mail = new Piwik_Mail();
- $mail->setFrom(Piwik_Common::unsanitizeInputValue($email));
- $mail->addTo($view->feedbackEmailAddress, 'Piwik Team');
- $mail->setSubject('[ Feedback form - Piwik ] ' . $category);
- $mail->setBodyText(Piwik_Common::unsanitizeInputValue($body) . "\n"
- . 'Piwik ' . Piwik_Version::VERSION . "\n"
- . 'IP: ' . Piwik_IP::getIpFromHeader() . "\n"
- . 'URL: ' . Piwik_Url::getReferer() . "\n");
- @$mail->send();
- }
- catch(Exception $e)
- {
- $view->ErrorString = $e->getMessage();
- $view->message = $body;
- }
+ $mail = new Piwik_Mail();
+ $mail->setFrom(Piwik_Common::unsanitizeInputValue($email));
+ $mail->addTo($view->feedbackEmailAddress, 'Piwik Team');
+ $mail->setSubject('[ Feedback form - Piwik ] ' . $category);
+ $mail->setBodyText(Piwik_Common::unsanitizeInputValue($body) . "\n"
+ . 'Piwik ' . Piwik_Version::VERSION . "\n"
+ . 'IP: ' . Piwik_IP::getIpFromHeader() . "\n"
+ . 'URL: ' . Piwik_Url::getReferer() . "\n");
+ @$mail->send();
+ } catch (Exception $e) {
+ $view->ErrorString = $e->getMessage();
+ $view->message = $body;
+ }
- echo $view->render();
- }
+ echo $view->render();
+ }
}
diff --git a/plugins/Feedback/Feedback.php b/plugins/Feedback/Feedback.php
index 1e0098b6f1..d0094578ed 100644
--- a/plugins/Feedback/Feedback.php
+++ b/plugins/Feedback/Feedback.php
@@ -15,55 +15,55 @@
*/
class Piwik_Feedback extends Piwik_Plugin
{
- public function getInformation()
- {
- return array(
- 'description' => Piwik_Translate('Feedback_PluginDescription'),
- 'author' => 'Piwik',
- 'author_homepage' => 'http://piwik.org/',
- 'version' => Piwik_Version::VERSION,
- );
- }
+ public function getInformation()
+ {
+ return array(
+ 'description' => Piwik_Translate('Feedback_PluginDescription'),
+ 'author' => 'Piwik',
+ 'author_homepage' => 'http://piwik.org/',
+ 'version' => Piwik_Version::VERSION,
+ );
+ }
- function getListHooksRegistered()
- {
- return array(
- 'AssetManager.getCssFiles' => 'getCssFiles',
- 'AssetManager.getJsFiles' => 'getJsFiles',
- 'TopMenu.add' => 'addTopMenu',
- );
- }
+ function getListHooksRegistered()
+ {
+ return array(
+ 'AssetManager.getCssFiles' => 'getCssFiles',
+ 'AssetManager.getJsFiles' => 'getJsFiles',
+ 'TopMenu.add' => 'addTopMenu',
+ );
+ }
- public function addTopMenu()
- {
- Piwik_AddTopMenu(
- 'General_GiveUsYourFeedback',
- array('module' => 'Feedback', 'action' => 'index'),
- true,
- $order = 20,
- $isHTML = false,
- $tooltip = Piwik_Translate('Feedback_TopLinkTooltip')
- );
- }
+ public function addTopMenu()
+ {
+ Piwik_AddTopMenu(
+ 'General_GiveUsYourFeedback',
+ array('module' => 'Feedback', 'action' => 'index'),
+ true,
+ $order = 20,
+ $isHTML = false,
+ $tooltip = Piwik_Translate('Feedback_TopLinkTooltip')
+ );
+ }
- /**
- * @param Piwik_Event_Notification $notification notification object
- */
- function getCssFiles( $notification )
- {
- $cssFiles = &$notification->getNotificationObject();
-
- $cssFiles[] = "plugins/Feedback/templates/styles.css";
- }
+ /**
+ * @param Piwik_Event_Notification $notification notification object
+ */
+ function getCssFiles($notification)
+ {
+ $cssFiles = & $notification->getNotificationObject();
+
+ $cssFiles[] = "plugins/Feedback/templates/styles.css";
+ }
+
+ /**
+ * @param Piwik_Event_Notification $notification notification object
+ */
+ function getJsFiles($notification)
+ {
+ $jsFiles = & $notification->getNotificationObject();
+
+ $jsFiles[] = "plugins/Feedback/templates/feedback.js";
+ }
- /**
- * @param Piwik_Event_Notification $notification notification object
- */
- function getJsFiles( $notification )
- {
- $jsFiles = &$notification->getNotificationObject();
-
- $jsFiles[] = "plugins/Feedback/templates/feedback.js";
- }
-
}
diff --git a/plugins/Feedback/templates/feedback.js b/plugins/Feedback/templates/feedback.js
index 5fcd04d58a..cbf07b4863 100644
--- a/plugins/Feedback/templates/feedback.js
+++ b/plugins/Feedback/templates/feedback.js
@@ -5,35 +5,35 @@
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
-$(function() {
- var feedback = $('a#topmenu-feedback');
- if (feedback.size()) {
- var fbDiv = $('<div id="feedback-dialog"></div>').appendTo('body');
+$(function () {
+ var feedback = $('a#topmenu-feedback');
+ if (feedback.size()) {
+ var fbDiv = $('<div id="feedback-dialog"></div>').appendTo('body');
- $('a#topmenu-feedback').click(function() {
- if(fbDiv.html() == '') {
- fbDiv.html('<div id="feedback-loading"><img alt="" src="themes/default/images/loading-blue.gif"> '+ _pk_translate('General_Loading_js') +'</div>');
- }
- if($('#feedback-loading' ,fbDiv).length) {
- $.get(feedback.attr('href'), function(data) {
- fbDiv.html(data);
- });
+ $('a#topmenu-feedback').click(function () {
+ if (fbDiv.html() == '') {
+ fbDiv.html('<div id="feedback-loading"><img alt="" src="themes/default/images/loading-blue.gif"> ' + _pk_translate('General_Loading_js') + '</div>');
+ }
+ if ($('#feedback-loading', fbDiv).length) {
+ $.get(feedback.attr('href'), function (data) {
+ fbDiv.html(data);
+ });
+
+ fbDiv.dialog({
+ title: feedback.html(),
+ modal: true,
+ width: 650,
+ position: ['center', 40],
+ resizable: false,
+ autoOpen: false
+ });
+ }
+ $('#feedback-faq').show();
+ $('#feedback-form').hide();
+ $('#feedback-sent').hide().empty();
+ fbDiv.dialog('open');
+ return false;
+ });
+ }
- fbDiv.dialog({
- title: feedback.html(),
- modal: true,
- width: 650,
- position: ['center', 40],
- resizable: false,
- autoOpen: false
- });
- }
- $('#feedback-faq').show();
- $('#feedback-form').hide();
- $('#feedback-sent').hide().empty();
- fbDiv.dialog('open');
- return false;
- });
- }
-
});
diff --git a/plugins/Feedback/templates/index.tpl b/plugins/Feedback/templates/index.tpl
index 0794ad1e2c..adff78c214 100644
--- a/plugins/Feedback/templates/index.tpl
+++ b/plugins/Feedback/templates/index.tpl
@@ -1,67 +1,74 @@
{literal}
-<script type="text/javascript">
-$(function() {
- $('#feedback-contact').click(function() {
- $('#feedback-faq').hide();
- $('#feedback-form').show();
- return false;
- });
+ <script type="text/javascript">
+ $(function () {
+ $('#feedback-contact').click(function () {
+ $('#feedback-faq').hide();
+ $('#feedback-form').show();
+ return false;
+ });
- $('#feedback-home').click(function() {
- $('#feedback-form').hide();
- $('#feedback-faq').show();
- return false;
- });
+ $('#feedback-home').click(function () {
+ $('#feedback-form').hide();
+ $('#feedback-faq').show();
+ return false;
+ });
- $('#feedback-form-submit').click(function() {
- var feedback = $('#feedback-form form');
- $('#feedback-form').hide();
- $.post(feedback.attr('action'), feedback.serialize(), function (data) {
- $('#feedback-sent').show().html(data);
- });
- return false;
- });
-});
-</script>
+ $('#feedback-form-submit').click(function () {
+ var feedback = $('#feedback-form form');
+ $('#feedback-form').hide();
+ $.post(feedback.attr('action'), feedback.serialize(), function (data) {
+ $('#feedback-sent').show().html(data);
+ });
+ return false;
+ });
+ });
+ </script>
{/literal}
- <div id="feedback-faq">
+<div id="feedback-faq">
<p><strong>{'Feedback_DoYouHaveBugReportOrFeatureRequest'|translate}</strong></p>
+
<p> &bull; {'Feedback_ViewAnswersToFAQ'|translate:"<a target='_blank' href='?module=Proxy&action=redirect&url=http://piwik.org/faq/'>":"</a>"}.</p>
<ul>
- <li>» {'Feedback_WhyAreMyVisitsNoTracked'|translate}</li>
- <li>» {'Feedback_HowToExclude'|translate}</li>
- <li>» {'Feedback_WhyWrongCountry'|translate}</li>
- <li>» {'Feedback_HowToAnonymizeIP'|translate}</li>
+ <li>» {'Feedback_WhyAreMyVisitsNoTracked'|translate}</li>
+ <li>» {'Feedback_HowToExclude'|translate}</li>
+ <li>» {'Feedback_WhyWrongCountry'|translate}</li>
+ <li>» {'Feedback_HowToAnonymizeIP'|translate}</li>
</ul>
<p> &bull; {'Feedback_VisitTheForums'|translate:"<a target='_blank' href='?module=Proxy&action=redirect&url=http://forum.piwik.org/'>":"</a>"}.</p>
- <p> &bull; {'Feedback_LearnWaysToParticipate'|translate:"<a target='_blank' href='?module=Proxy&action=redirect&url=http://piwik.org/contribute/'>":"</a>"}.</p>
- <br />
+
+ <p> &bull; {'Feedback_LearnWaysToParticipate'|translate:"<a target='_blank' href='?module=Proxy&action=redirect&url=http://piwik.org/contribute/'>":"</a>"}
+ .</p>
+ <br/>
+
<p><strong>{'Feedback_SpecialRequest'|translate}</strong></p>
- <p> &bull; <a target='_blank' href="#" id="feedback-contact">{'Feedback_ContactThePiwikTeam'|translate}</a></p>
+
+ <p> &bull; <a target='_blank' href="#" id="feedback-contact">{'Feedback_ContactThePiwikTeam'|translate}</a></p>
<br/>
+
<p><strong>{'Feedback_WantToThankConsiderDonating'|translate}</strong></p>
<br/>
{include file="CoreHome/templates/donate.tpl" msg=""}
- </div>
- <div id="feedback-form" style="display:none;">
+</div>
+<div id="feedback-form" style="display:none;">
<form method="post" action="index.php?module=Feedback&action=sendFeedback">
- <label>{'Feedback_IWantTo'|translate}</label>
+ <label>{'Feedback_IWantTo'|translate}</label>
<select name="category">
- <option value="share">{'Feedback_CategoryShareStory'|translate}</option>
- <option value="sponsor">{'Feedback_CategorySponsor'|translate}</option>
- <option value="hire">{'Feedback_CategoryHire'|translate}</option>
- <option value="security">{'Feedback_CategorySecurity'|translate}</option>
+ <option value="share">{'Feedback_CategoryShareStory'|translate}</option>
+ <option value="sponsor">{'Feedback_CategorySponsor'|translate}</option>
+ <option value="hire">{'Feedback_CategoryHire'|translate}</option>
+ <option value="security">{'Feedback_CategorySecurity'|translate}</option>
</select>
- <br />
- <label>{'Feedback_MyEmailAddress'|translate}</label>
- <input type="text" name="email" size="59" />
- <input type="hidden" name="nonce" value="{$nonce}" /><br />
- <label>{'Feedback_MyMessage'|translate}<br /><i>{'Feedback_DetailsPlease'|translate}</i></label>
- <textarea name="body" cols="57" rows="10">Please write your message in English</textarea><br />
- <label><a href="#" id="feedback-home"><img src="plugins/Feedback/images/go-previous.png" border="0" title="{'General_Previous'|translate}" alt="[{'General_Previous'|translate}]" /></a></label>
- <input id="feedback-form-submit" type="submit" class='submit' value="{'Feedback_SendFeedback'|translate}" />
+ <br/>
+ <label>{'Feedback_MyEmailAddress'|translate}</label>
+ <input type="text" name="email" size="59"/>
+ <input type="hidden" name="nonce" value="{$nonce}"/><br/>
+ <label>{'Feedback_MyMessage'|translate}<br/><i>{'Feedback_DetailsPlease'|translate}</i></label>
+ <textarea name="body" cols="57" rows="10">Please write your message in English</textarea><br/>
+ <label><a href="#" id="feedback-home"><img src="plugins/Feedback/images/go-previous.png" border="0" title="{'General_Previous'|translate}"
+ alt="[{'General_Previous'|translate}]"/></a></label>
+ <input id="feedback-form-submit" type="submit" class='submit' value="{'Feedback_SendFeedback'|translate}"/>
</form>
- </div>
- <div id="feedback-sent" style="display:none;">
- </div>
+</div>
+<div id="feedback-sent" style="display:none;">
+</div>
diff --git a/plugins/Feedback/templates/sent.tpl b/plugins/Feedback/templates/sent.tpl
index 5b4780ce5d..74e2604878 100644
--- a/plugins/Feedback/templates/sent.tpl
+++ b/plugins/Feedback/templates/sent.tpl
@@ -1,20 +1,23 @@
{literal}
-<script type="text/javascript">
- $('#feedback-retry').click(function() {
- $('#feedback-sent').hide().empty();
- $('#feedback-form').show();
- return false;
- });
-</script>
+ <script type="text/javascript">
+ $('#feedback-retry').click(function () {
+ $('#feedback-sent').hide().empty();
+ $('#feedback-form').show();
+ return false;
+ });
+ </script>
{/literal}
{if isset($ErrorString)}
- <div id="feedback-error"><strong>{'General_Error'|translate}:</strong> {$ErrorString}</div>
- <p>{'Feedback_ManuallySendEmailTo'|translate} <a href='mailto:{$feedbackEmailAddress}?subject={'[Feedback form - Piwik]'|escape:"hex"}&body={$message|stripeol|escape:"hex"}'>{$feedbackEmailAddress}</a></p>
- <textarea cols="53" rows="10" readonly="readonly">{$message}</textarea>
- <p><a href="#" id="feedback-retry"><img src="plugins/Feedback/images/go-previous.png" border="0" title="{'General_Previous'|translate}" alt="[{'General_Previous'|translate}]" /></a></p>
+ <div id="feedback-error"><strong>{'General_Error'|translate}:</strong> {$ErrorString}</div>
+ <p>{'Feedback_ManuallySendEmailTo'|translate} <a
+ href='mailto:{$feedbackEmailAddress}?subject={'[Feedback form - Piwik]'|escape:"hex"}&body={$message|stripeol|escape:"hex"}'>{$feedbackEmailAddress}</a>
+ </p>
+ <textarea cols="53" rows="10" readonly="readonly">{$message}</textarea>
+ <p><a href="#" id="feedback-retry"><img src="plugins/Feedback/images/go-previous.png" border="0" title="{'General_Previous'|translate}"
+ alt="[{'General_Previous'|translate}]"/></a></p>
{else}
- <div id="feedback-success">{'Feedback_MessageSent'|translate}</div>
- <p><strong>{'Feedback_ThankYou'|translate}</strong></p>
- <p>-- {'Feedback_ThePiwikTeam'|translate}</p>
+ <div id="feedback-success">{'Feedback_MessageSent'|translate}</div>
+ <p><strong>{'Feedback_ThankYou'|translate}</strong></p>
+ <p>-- {'Feedback_ThePiwikTeam'|translate}</p>
{/if}
diff --git a/plugins/Feedback/templates/styles.css b/plugins/Feedback/templates/styles.css
index abc187741b..258f078ca1 100644
--- a/plugins/Feedback/templates/styles.css
+++ b/plugins/Feedback/templates/styles.css
@@ -1,47 +1,68 @@
#feedback-faq, #feedback-form, #feedback-sent {
- font-family: Arial, Helvetica, sans-serif;
- color:#5e5e5c;
- font-size:14px;
- line-height:24px;
- padding:0 20px 15px 0;
+ font-family: Arial, Helvetica, sans-serif;
+ color: #5e5e5c;
+ font-size: 14px;
+ line-height: 24px;
+ padding: 0 20px 15px 0;
}
+
#feedback-faq strong, #feedback-sent strong {
- color:#5e5e5c;
+ color: #5e5e5c;
}
#feedback-faq ul, #feedback-sent ul {
- list-style:none;
- padding:0 0 0 20px;
- font-size:12px;
- line-height:18px;
+ list-style: none;
+ padding: 0 0 0 20px;
+ font-size: 12px;
+ line-height: 18px;
}
-#feedback-faq a, #feedback-sent a{
- color:#5176a0;
- text-decoration:none;
- font-weight:bold;
+#feedback-faq a, #feedback-sent a {
+ color: #5176a0;
+ text-decoration: none;
+ font-weight: bold;
}
-
#feedback-error {
- color: red;
- text-align: center;
- border: 2px solid red;
- background-color:#FFFBFB;
- margin: 10px;
- padding: 10px;
+ color: red;
+ text-align: center;
+ border: 2px solid red;
+ background-color: #FFFBFB;
+ margin: 10px;
+ padding: 10px;
}
+
#feedback-success {
- color: #38D73B;
- text-align: center;
- border: 2px solid #38D73B;
- margin: 10px;
- padding: 10px;
+ color: #38D73B;
+ text-align: center;
+ border: 2px solid #38D73B;
+ margin: 10px;
+ padding: 10px;
+}
+
+#feedback-form {
+ font-size: 12px;
+ line-height: 18px;
}
-#feedback-form { font-size:12px; line-height:18px;}
-#feedback-form label{ float:left; width:135px; padding:4px 15px 0 0; text-align:right; position:relative; }
-#feedback-form label i{ font-style:normal; color:#cbcbcb; }
-#feedback-form br{ clear:both; }
+#feedback-form label {
+ float: left;
+ width: 135px;
+ padding: 4px 15px 0 0;
+ text-align: right;
+ position: relative;
+}
+
+#feedback-form label i {
+ font-style: normal;
+ color: #cbcbcb;
+}
+
+#feedback-form br {
+ clear: both;
+}
-#feedback-home{ float:left; margin:-7px 0 0 20px; } \ No newline at end of file
+#feedback-home {
+ float: left;
+ margin: -7px 0 0 20px;
+} \ No newline at end of file
diff --git a/plugins/Goals/API.php b/plugins/Goals/API.php
index 318705d1ab..5feba1b47e 100644
--- a/plugins/Goals/API.php
+++ b/plugins/Goals/API.php
@@ -1,575 +1,540 @@
<?php
/**
* Piwik - Open source web analytics
- *
+ *
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
- *
+ *
* @category Piwik_Plugins
* @package Piwik_Goals
*/
/**
- * Goals API lets you Manage existing goals, via "updateGoal" and "deleteGoal", create new Goals via "addGoal",
- * or list existing Goals for one or several websites via "getGoals"
- *
+ * Goals API lets you Manage existing goals, via "updateGoal" and "deleteGoal", create new Goals via "addGoal",
+ * or list existing Goals for one or several websites via "getGoals"
+ *
* If you are <a href='http://piwik.org/docs/ecommerce-analytics/' target='_blank'>tracking Ecommerce orders and products</a> on your site, the functions "getItemsSku", "getItemsName" and "getItemsCategory"
* will return the list of products purchased on your site, either grouped by Product SKU, Product Name or Product Category. For each name, SKU or category, the following
* metrics are returned: Total revenue, Total quantity, average price, average quantity, number of orders (or abandoned carts) containing this product, number of visits on the Product page,
* Conversion rate.
- *
+ *
* By default, these functions return the 'Products purchased'. These functions also accept an optional parameter &abandonedCarts=1.
- * If the parameter is set, it will instead return the metrics for products that were left in an abandoned cart therefore not purchased.
- *
+ * If the parameter is set, it will instead return the metrics for products that were left in an abandoned cart therefore not purchased.
+ *
* The API also lets you request overall Goal metrics via the method "get": Conversions, Visits with at least one conversion, Conversion rate and Revenue.
- * If you wish to request specific metrics about Ecommerce goals, you can set the parameter &idGoal=ecommerceAbandonedCart to get metrics about abandoned carts (including Lost revenue, and number of items left in the cart)
+ * If you wish to request specific metrics about Ecommerce goals, you can set the parameter &idGoal=ecommerceAbandonedCart to get metrics about abandoned carts (including Lost revenue, and number of items left in the cart)
* or &idGoal=ecommerceOrder to get metrics about Ecommerce orders (number of orders, visits with an order, subtotal, tax, shipping, discount, revenue, items ordered)
- *
+ *
* See also the documentation about <a href='http://piwik.org/docs/tracking-goals-web-analytics/' target='_blank'>Tracking Goals</a> in Piwik.
- *
+ *
* @package Piwik_Goals
*/
-class Piwik_Goals_API
+class Piwik_Goals_API
{
- static private $instance = null;
- /**
- * @return Piwik_Goals_API
- */
- static public function getInstance()
- {
- if (self::$instance == null)
- {
- self::$instance = new self;
- }
- return self::$instance;
- }
-
- /**
- * Returns all Goals for a given website, or list of websites
- *
- * @param string|array $idSite Array or Comma separated list of website IDs to request the goals for
- * @return array Array of Goal attributes
- */
- public function getGoals( $idSite )
- {
- //TODO calls to this function could be cached as static
- // would help UI at least, since some UI requests would call this 2-3 times..
- $idSite = Piwik_Site::getIdSitesFromIdSitesString($idSite);
- if(empty($idSite))
- {
- return array();
- }
- Piwik::checkUserHasViewAccess($idSite);
- $goals = Piwik_FetchAll("SELECT *
- FROM ".Piwik_Common::prefixTable('goal')."
- WHERE idsite IN (".implode(", ", $idSite).")
+ static private $instance = null;
+
+ /**
+ * @return Piwik_Goals_API
+ */
+ static public function getInstance()
+ {
+ if (self::$instance == null) {
+ self::$instance = new self;
+ }
+ return self::$instance;
+ }
+
+ /**
+ * Returns all Goals for a given website, or list of websites
+ *
+ * @param string|array $idSite Array or Comma separated list of website IDs to request the goals for
+ * @return array Array of Goal attributes
+ */
+ public function getGoals($idSite)
+ {
+ //TODO calls to this function could be cached as static
+ // would help UI at least, since some UI requests would call this 2-3 times..
+ $idSite = Piwik_Site::getIdSitesFromIdSitesString($idSite);
+ if (empty($idSite)) {
+ return array();
+ }
+ Piwik::checkUserHasViewAccess($idSite);
+ $goals = Piwik_FetchAll("SELECT *
+ FROM " . Piwik_Common::prefixTable('goal') . "
+ WHERE idsite IN (" . implode(", ", $idSite) . ")
AND deleted = 0");
- $cleanedGoals = array();
- foreach($goals as &$goal)
- {
- if($goal['match_attribute'] == 'manually') {
- unset($goal['pattern']);
- unset($goal['pattern_type']);
- unset($goal['case_sensitive']);
- }
- $cleanedGoals[$goal['idgoal']] = $goal;
- }
- return $cleanedGoals;
- }
-
- /**
- * Creates a Goal for a given website.
- *
- * @param int $idSite
- * @param string $name
- * @param string $matchAttribute 'url', 'title', 'file', 'external_website' or 'manually'
- * @param string $pattern eg. purchase-confirmation.htm
- * @param string $patternType 'regex', 'contains', 'exact'
- * @param bool $caseSensitive
- * @param bool|float $revenue If set, default revenue to assign to conversions
- * @param bool $allowMultipleConversionsPerVisit By default, multiple conversions in the same visit will only record the first conversion.
- * If set to true, multiple conversions will all be recorded within a visit (useful for Ecommerce goals)
- * @return int ID of the new goal
- */
- public function addGoal( $idSite, $name, $matchAttribute, $pattern, $patternType, $caseSensitive = false, $revenue = false, $allowMultipleConversionsPerVisit = false)
- {
- Piwik::checkUserHasAdminAccess($idSite);
- $this->checkPatternIsValid($patternType, $pattern);
- $name = $this->checkName($name);
- $pattern = $this->checkPattern($pattern);
-
- // save in db
- $db = Zend_Registry::get('db');
- $idGoal = $db->fetchOne("SELECT max(idgoal) + 1
- FROM ".Piwik_Common::prefixTable('goal')."
+ $cleanedGoals = array();
+ foreach ($goals as &$goal) {
+ if ($goal['match_attribute'] == 'manually') {
+ unset($goal['pattern']);
+ unset($goal['pattern_type']);
+ unset($goal['case_sensitive']);
+ }
+ $cleanedGoals[$goal['idgoal']] = $goal;
+ }
+ return $cleanedGoals;
+ }
+
+ /**
+ * Creates a Goal for a given website.
+ *
+ * @param int $idSite
+ * @param string $name
+ * @param string $matchAttribute 'url', 'title', 'file', 'external_website' or 'manually'
+ * @param string $pattern eg. purchase-confirmation.htm
+ * @param string $patternType 'regex', 'contains', 'exact'
+ * @param bool $caseSensitive
+ * @param bool|float $revenue If set, default revenue to assign to conversions
+ * @param bool $allowMultipleConversionsPerVisit By default, multiple conversions in the same visit will only record the first conversion.
+ * If set to true, multiple conversions will all be recorded within a visit (useful for Ecommerce goals)
+ * @return int ID of the new goal
+ */
+ public function addGoal($idSite, $name, $matchAttribute, $pattern, $patternType, $caseSensitive = false, $revenue = false, $allowMultipleConversionsPerVisit = false)
+ {
+ Piwik::checkUserHasAdminAccess($idSite);
+ $this->checkPatternIsValid($patternType, $pattern);
+ $name = $this->checkName($name);
+ $pattern = $this->checkPattern($pattern);
+
+ // save in db
+ $db = Zend_Registry::get('db');
+ $idGoal = $db->fetchOne("SELECT max(idgoal) + 1
+ FROM " . Piwik_Common::prefixTable('goal') . "
WHERE idsite = ?", $idSite);
- if($idGoal == false)
- {
- $idGoal = 1;
- }
- $db->insert(Piwik_Common::prefixTable('goal'),
- array(
- 'idsite' => $idSite,
- 'idgoal' => $idGoal,
- 'name' => $name,
- 'match_attribute' => $matchAttribute,
- 'pattern' => $pattern,
- 'pattern_type' => $patternType,
- 'case_sensitive' => (int)$caseSensitive,
- 'allow_multiple' => (int)$allowMultipleConversionsPerVisit,
- 'revenue' => (float)$revenue,
- 'deleted' => 0,
- ));
- Piwik_Tracker_Cache::regenerateCacheWebsiteAttributes($idSite);
- return $idGoal;
- }
-
- /**
- * Updates a Goal description.
- * Will not update or re-process the conversions already recorded
- *
- * @see addGoal() for parameters description
- * @param int $idSite
- * @param int $idGoal
- * @param $name
- * @param $matchAttribute
- * @param string $pattern
- * @param string $patternType
- * @param bool $caseSensitive
- * @param bool|float $revenue
- * @param bool $allowMultipleConversionsPerVisit
- * @return void
- */
- public function updateGoal( $idSite, $idGoal, $name, $matchAttribute, $pattern, $patternType, $caseSensitive = false, $revenue = false, $allowMultipleConversionsPerVisit = false)
- {
- Piwik::checkUserHasAdminAccess($idSite);
- $name = $this->checkName($name);
- $pattern = $this->checkPattern($pattern);
- $this->checkPatternIsValid($patternType, $pattern);
- Zend_Registry::get('db')->update( Piwik_Common::prefixTable('goal'),
- array(
- 'name' => $name,
- 'match_attribute' => $matchAttribute,
- 'pattern' => $pattern,
- 'pattern_type' => $patternType,
- 'case_sensitive' => (int)$caseSensitive,
- 'allow_multiple' => (int)$allowMultipleConversionsPerVisit,
- 'revenue' => (float)$revenue,
- ),
- "idsite = '$idSite' AND idgoal = '$idGoal'"
- );
- Piwik_Tracker_Cache::regenerateCacheWebsiteAttributes($idSite);
- }
-
- private function checkPatternIsValid($patternType, $pattern)
- {
- if($patternType == 'exact'
- && substr($pattern, 0, 4) != 'http')
- {
- throw new Exception(Piwik_TranslateException('Goals_ExceptionInvalidMatchingString', array("http:// or https://", "http://www.yourwebsite.com/newsletter/subscribed.html")));
- }
- }
-
- private function checkName($name)
- {
- return urldecode($name);
- }
-
- private function checkPattern($pattern)
- {
- return urldecode($pattern);
- }
-
- /**
- * Soft deletes a given Goal.
- * Stats data in the archives will still be recorded, but not displayed.
- *
- * @param int $idSite
- * @param int $idGoal
- * @return void
- */
- public function deleteGoal( $idSite, $idGoal )
- {
- Piwik::checkUserHasAdminAccess($idSite);
- Piwik_Query("UPDATE ".Piwik_Common::prefixTable('goal')."
+ if ($idGoal == false) {
+ $idGoal = 1;
+ }
+ $db->insert(Piwik_Common::prefixTable('goal'),
+ array(
+ 'idsite' => $idSite,
+ 'idgoal' => $idGoal,
+ 'name' => $name,
+ 'match_attribute' => $matchAttribute,
+ 'pattern' => $pattern,
+ 'pattern_type' => $patternType,
+ 'case_sensitive' => (int)$caseSensitive,
+ 'allow_multiple' => (int)$allowMultipleConversionsPerVisit,
+ 'revenue' => (float)$revenue,
+ 'deleted' => 0,
+ ));
+ Piwik_Tracker_Cache::regenerateCacheWebsiteAttributes($idSite);
+ return $idGoal;
+ }
+
+ /**
+ * Updates a Goal description.
+ * Will not update or re-process the conversions already recorded
+ *
+ * @see addGoal() for parameters description
+ * @param int $idSite
+ * @param int $idGoal
+ * @param $name
+ * @param $matchAttribute
+ * @param string $pattern
+ * @param string $patternType
+ * @param bool $caseSensitive
+ * @param bool|float $revenue
+ * @param bool $allowMultipleConversionsPerVisit
+ * @return void
+ */
+ public function updateGoal($idSite, $idGoal, $name, $matchAttribute, $pattern, $patternType, $caseSensitive = false, $revenue = false, $allowMultipleConversionsPerVisit = false)
+ {
+ Piwik::checkUserHasAdminAccess($idSite);
+ $name = $this->checkName($name);
+ $pattern = $this->checkPattern($pattern);
+ $this->checkPatternIsValid($patternType, $pattern);
+ Zend_Registry::get('db')->update(Piwik_Common::prefixTable('goal'),
+ array(
+ 'name' => $name,
+ 'match_attribute' => $matchAttribute,
+ 'pattern' => $pattern,
+ 'pattern_type' => $patternType,
+ 'case_sensitive' => (int)$caseSensitive,
+ 'allow_multiple' => (int)$allowMultipleConversionsPerVisit,
+ 'revenue' => (float)$revenue,
+ ),
+ "idsite = '$idSite' AND idgoal = '$idGoal'"
+ );
+ Piwik_Tracker_Cache::regenerateCacheWebsiteAttributes($idSite);
+ }
+
+ private function checkPatternIsValid($patternType, $pattern)
+ {
+ if ($patternType == 'exact'
+ && substr($pattern, 0, 4) != 'http'
+ ) {
+ throw new Exception(Piwik_TranslateException('Goals_ExceptionInvalidMatchingString', array("http:// or https://", "http://www.yourwebsite.com/newsletter/subscribed.html")));
+ }
+ }
+
+ private function checkName($name)
+ {
+ return urldecode($name);
+ }
+
+ private function checkPattern($pattern)
+ {
+ return urldecode($pattern);
+ }
+
+ /**
+ * Soft deletes a given Goal.
+ * Stats data in the archives will still be recorded, but not displayed.
+ *
+ * @param int $idSite
+ * @param int $idGoal
+ * @return void
+ */
+ public function deleteGoal($idSite, $idGoal)
+ {
+ Piwik::checkUserHasAdminAccess($idSite);
+ Piwik_Query("UPDATE " . Piwik_Common::prefixTable('goal') . "
SET deleted = 1
WHERE idsite = ?
AND idgoal = ?",
- array($idSite, $idGoal));
- Piwik_DeleteAllRows(Piwik_Common::prefixTable("log_conversion"), "WHERE idgoal = ?", 100000, array($idGoal));
- Piwik_Tracker_Cache::regenerateCacheWebsiteAttributes($idSite);
- }
-
- /**
- * Returns a datatable of Items SKU/name or categories and their metrics
- * If $abandonedCarts set to 1, will return items abandoned in carts. If set to 0, will return items ordered
- */
- protected function getItems($recordName, $idSite, $period, $date, $abandonedCarts )
- {
- Piwik::checkUserHasViewAccess( $idSite );
- $recordNameFinal = $recordName;
- if($abandonedCarts)
- {
- $recordNameFinal = Piwik_Goals::getItemRecordNameAbandonedCart($recordName);
- }
- $archive = Piwik_Archive::build($idSite, $period, $date );
- $dataTable = $archive->getDataTable($recordNameFinal);
- $dataTable->filter('Sort', array(Piwik_Archive::INDEX_ECOMMERCE_ITEM_REVENUE));
- $dataTable->queueFilter('ReplaceColumnNames');
-
- $ordersColumn = 'orders';
- if($abandonedCarts)
- {
- $ordersColumn = 'abandoned_carts';
- $dataTable->renameColumn(Piwik_Archive::INDEX_ECOMMERCE_ORDERS, $ordersColumn);
- }
-
- // Average price = sum product revenue / quantity
- $dataTable->queueFilter('ColumnCallbackAddColumnQuotient', array('avg_price', 'price', $ordersColumn, Piwik_Tracker_GoalManager::REVENUE_PRECISION));
-
- // Average quantity = sum product quantity / abandoned carts
- $dataTable->queueFilter('ColumnCallbackAddColumnQuotient', array('avg_quantity', 'quantity', $ordersColumn, $precision = 1));
- $dataTable->queueFilter('ColumnDelete', array('price'));
-
- // Enrich the datatable with Product/Categories views, and conversion rates
- $customVariables = Piwik_CustomVariables_API::getInstance()->getCustomVariables($idSite, $period, $date, $segment = false, $expanded = false, $_leavePiwikCoreVariables = true);
- $mapping = array(
- 'Goals_ItemsSku' => '_pks',
- 'Goals_ItemsName' => '_pkn',
- 'Goals_ItemsCategory' => '_pkc',
- );
- $reportToNotDefinedString = array(
- 'Goals_ItemsSku' => Piwik_Translate('General_NotDefined', Piwik_Translate('Goals_ProductSKU')), // Note: this should never happen
- 'Goals_ItemsName' => Piwik_Translate('General_NotDefined', Piwik_Translate('Goals_ProductName')),
- 'Goals_ItemsCategory' => Piwik_Translate('General_NotDefined', Piwik_Translate('Goals_ProductCategory'))
- );
- $notDefinedStringPretty = $reportToNotDefinedString[$recordName];
- $customVarNameToLookFor = $mapping[$recordName];
-
- // Handle case where date=last30&period=day
- if($customVariables instanceof Piwik_DataTable_Array)
- {
- $customVariableDatatables = $customVariables->getArray();
- $dataTables = $dataTable->getArray();
- foreach($customVariableDatatables as $key => $customVariableTableForDate)
- {
- $dataTableForDate = isset($dataTables[$key]) ? $dataTables[$key] : new Piwik_DataTable();
-
- // we do not enter the IF
- // if case idSite=1,3 AND period=day&date=datefrom,dateto,
- if(isset($customVariableTableForDate->metadata['period']))
- {
- $dateRewrite = $customVariableTableForDate->metadata['period']->getDateStart()->toString();
- $row = $customVariableTableForDate->getRowFromLabel($customVarNameToLookFor);
- if($row)
- {
- $idSubtable = $row->getIdSubDataTable();
- $this->enrichItemsDataTableWithItemsViewMetrics($dataTableForDate, $idSite, $period, $dateRewrite, $idSubtable);
- }
- $dataTable->addTable($dataTableForDate, $key);
- }
- $this->renameNotDefinedRow($dataTableForDate, $notDefinedStringPretty);
- }
- }
- elseif($customVariables instanceof Piwik_DataTable)
- {
- $row = $customVariables->getRowFromLabel($customVarNameToLookFor);
- if($row)
- {
- $idSubtable = $row->getIdSubDataTable();
- $this->enrichItemsDataTableWithItemsViewMetrics($dataTable, $idSite, $period, $date, $idSubtable);
- }
- $this->renameNotDefinedRow($dataTable, $notDefinedStringPretty);
- }
-
- // Product conversion rate = orders / visits
- $dataTable->queueFilter('ColumnCallbackAddColumnPercentage', array('conversion_rate', $ordersColumn, 'nb_visits', Piwik_Tracker_GoalManager::REVENUE_PRECISION));
-
- return $dataTable;
- }
-
- protected function renameNotDefinedRow($dataTable, $notDefinedStringPretty)
- {
- if($dataTable instanceof Piwik_DataTable_Array)
- {
- foreach($dataTable->getArray() as $table)
- {
- $this->renameNotDefinedRow($table, $notDefinedStringPretty);
- }
- return;
- }
- $rowNotDefined = $dataTable->getRowFromLabel(Piwik_CustomVariables::LABEL_CUSTOM_VALUE_NOT_DEFINED);
- if($rowNotDefined)
- {
- $rowNotDefined->setColumn('label', $notDefinedStringPretty);
- }
- }
-
-
- protected function enrichItemsDataTableWithItemsViewMetrics($dataTable, $idSite, $period, $date, $idSubtable)
- {
- $ecommerceViews = Piwik_CustomVariables_API::getInstance()->getCustomVariablesValuesFromNameId($idSite, $period, $date, $idSubtable, $segment = false, $_leavePriceViewedColumn = true);
-
- // For Product names and SKU reports, and for Category report
- // Use the Price (tracked on page views)
- // ONLY when the price sold in conversions is not found (ie. product viewed but not sold)
- foreach($ecommerceViews->getRows() as $rowView)
- {
- // If there is not already a 'sum price' for this product
- $rowFound = $dataTable->getRowFromLabel($rowView->getColumn('label'));
- $price = $rowFound
- ? $rowFound->getColumn(Piwik_Archive::INDEX_ECOMMERCE_ITEM_PRICE)
- : false;
- if(empty($price))
- {
- // If a price was tracked on the product page
- if($rowView->getColumn(Piwik_Archive::INDEX_ECOMMERCE_ITEM_PRICE_VIEWED))
- {
- $rowView->renameColumn(Piwik_Archive::INDEX_ECOMMERCE_ITEM_PRICE_VIEWED, 'avg_price');
- }
- }
- $rowView->deleteColumn(Piwik_Archive::INDEX_ECOMMERCE_ITEM_PRICE_VIEWED);
- }
-
- $dataTable->addDataTable($ecommerceViews);
- }
-
- public function getItemsSku($idSite, $period, $date, $abandonedCarts = false )
- {
- return $this->getItems('Goals_ItemsSku', $idSite, $period, $date, $abandonedCarts);
- }
-
- public function getItemsName($idSite, $period, $date, $abandonedCarts = false )
- {
- return $this->getItems('Goals_ItemsName', $idSite, $period, $date, $abandonedCarts);
- }
-
- public function getItemsCategory($idSite, $period, $date, $abandonedCarts = false )
- {
- return $this->getItems('Goals_ItemsCategory', $idSite, $period, $date, $abandonedCarts);
- }
-
- /**
- * Helper function that checks for special string goal IDs and converts them to
- * their integer equivalents.
- *
- * Checks for the following values:
- * Piwik_Archive::LABEL_ECOMMERCE_ORDER
- * Piwik_Archive::LABEL_ECOMMERCE_CART
- *
- * @param string|int $idGoal The goal id as an integer or a special string.
- * @return int The numeric goal id.
- */
- protected static function convertSpecialGoalIds( $idGoal )
- {
- if ($idGoal == Piwik_Archive::LABEL_ECOMMERCE_ORDER)
- {
- return Piwik_Tracker_GoalManager::IDGOAL_ORDER;
- }
- else if ($idGoal == Piwik_Archive::LABEL_ECOMMERCE_CART)
- {
- return Piwik_Tracker_GoalManager::IDGOAL_CART;
- }
- else
- {
- return $idGoal;
- }
- }
-
- /**
- * Returns Goals data
- *
- * @param int $idSite
- * @param string $period
- * @param string $date
- * @param bool $segment
- * @param bool|int $idGoal
- * @param array $columns Array of metrics to fetch: nb_conversions, conversion_rate, revenue
- * @return Piwik_DataTable
- */
- public function get( $idSite, $period, $date, $segment = false, $idGoal = false, $columns = array() )
- {
- Piwik::checkUserHasViewAccess( $idSite );
- $archive = Piwik_Archive::build($idSite, $period, $date, $segment );
- $columns = Piwik::getArrayFromApiParameter($columns);
-
- // Mapping string idGoal to internal ID
- $idGoal = self::convertSpecialGoalIds($idGoal);
-
- if(empty($columns))
- {
- $columns = Piwik_Goals::getGoalColumns($idGoal);
- if($idGoal == Piwik_Archive::LABEL_ECOMMERCE_ORDER)
- {
- $columns[] = 'avg_order_revenue';
- }
- }
- if(in_array('avg_order_revenue', $columns)
- && $idGoal == Piwik_Archive::LABEL_ECOMMERCE_ORDER)
- {
- $columns[] = 'nb_conversions';
- $columns[] = 'revenue';
- $columns = array_values(array_unique($columns));
- }
- $columnsToSelect = array();
- foreach($columns as &$columnName)
- {
- $columnsToSelect[] = Piwik_Goals::getRecordName($columnName, $idGoal);
- }
- $dataTable = $archive->getDataTableFromNumeric($columnsToSelect);
-
- // Rewrite column names as we expect them
- foreach($columnsToSelect as $id => $oldName)
- {
- $dataTable->renameColumn($oldName, $columns[$id]);
- }
- if($idGoal == Piwik_Archive::LABEL_ECOMMERCE_ORDER)
- {
- if($dataTable instanceof Piwik_DataTable_Array)
- {
- foreach($dataTable->getArray() as $row)
- {
- $this->enrichTable($row);
- }
- }
- else
- {
- $this->enrichTable($dataTable);
- }
- }
- return $dataTable;
- }
-
- protected function enrichTable($table)
- {
- $row = $table->getFirstRow();
- if(!$row)
- {
- return;
- }
- // AVG order per visit
- if(false !== $table->getColumn('avg_order_revenue'))
- {
- $conversions = $row->getColumn('nb_conversions');
- if($conversions)
- {
- $row->setColumn('avg_order_revenue', round($row->getColumn('revenue') / $conversions, 2));
- }
- }
- }
-
- protected function getNumeric( $idSite, $period, $date, $segment, $toFetch )
- {
- Piwik::checkUserHasViewAccess( $idSite );
- $archive = Piwik_Archive::build($idSite, $period, $date, $segment );
- $dataTable = $archive->getNumeric($toFetch);
- return $dataTable;
- }
-
- /**
- * @ignore
- */
- public function getConversions( $idSite, $period, $date, $segment = false, $idGoal = false )
- {
- return $this->getNumeric( $idSite, $period, $date, $segment, Piwik_Goals::getRecordName('nb_conversions', $idGoal));
- }
-
- /**
- * @ignore
- */
- public function getNbVisitsConverted( $idSite, $period, $date, $segment = false, $idGoal = false )
- {
- return $this->getNumeric( $idSite, $period, $date, $segment, Piwik_Goals::getRecordName('nb_visits_converted', $idGoal));
- }
-
- /**
- * @ignore
- */
- public function getConversionRate( $idSite, $period, $date, $segment = false, $idGoal = false )
- {
- return $this->getNumeric( $idSite, $period, $date, $segment, Piwik_Goals::getRecordName('conversion_rate', $idGoal));
- }
-
- /**
- * @ignore
- */
- public function getRevenue( $idSite, $period, $date, $segment = false, $idGoal = false )
- {
- return $this->getNumeric( $idSite, $period, $date, $segment, Piwik_Goals::getRecordName('revenue', $idGoal));
- }
-
- /**
- * Utility method that retrieve an archived DataTable for a specific site, date range,
- * segment and goal. If not goal is specified, this method will retrieve and sum the
- * data for every goal.
- *
- * @param string $recordName The archive entry name.
- * @param int|string $idSite The site(s) to select data for.
- * @param string $period The period type.
- * @param string $date The date type.
- * @param string $segment The segment.
- * @param int|bool $idGoal The id of the goal to get data for. If this is set to false,
- * data for every goal that belongs to $idSite is returned.
- */
- protected function getGoalSpecificDataTable($recordName, $idSite, $period, $date, $segment, $idGoal)
- {
- Piwik::checkUserHasViewAccess( $idSite );
-
- $archive = Piwik_Archive::build($idSite, $period, $date, $segment );
-
- // check for the special goal ids
- $realGoalId = $idGoal != true ? false : self::convertSpecialGoalIds($idGoal);
-
- // get the data table
- $dataTable = $archive->getDataTable(Piwik_Goals::getRecordName($recordName, $realGoalId), $idSubtable = null);
- $dataTable->queueFilter('ReplaceColumnNames');
-
- return $dataTable;
- }
-
- /**
- * Gets a DataTable that maps ranges of days to the number of conversions that occurred
- * within those ranges, for the specified site, date range, segment and goal.
- *
- * @param int $idSite The site to select data from.
- * @param string $period The period type.
- * @param string $date The date type.
- * @param string|bool $segment The segment.
- * @param int|bool $idGoal The id of the goal to get data for. If this is set to false,
- * data for every goal that belongs to $idSite is returned.
- */
- public function getDaysToConversion($idSite, $period, $date, $segment = false, $idGoal = false)
- {
- $dataTable = $this->getGoalSpecificDataTable(
- Piwik_Goals::DAYS_UNTIL_CONV_RECORD_NAME, $idSite, $period, $date, $segment, $idGoal);
-
- $dataTable->queueFilter('Sort', array('label', 'asc', true));
- $dataTable->queueFilter(
- 'BeautifyRangeLabels', array(Piwik_Translate('General_OneDay'), Piwik_Translate('General_NDays')));
-
- return $dataTable;
- }
-
- /**
- * Gets a DataTable that maps ranges of visit counts to the number of conversions that
- * occurred on those visits for the specified site, date range, segment and goal.
- *
- * @param int $idSite The site to select data from.
- * @param string $period The period type.
- * @param string $date The date type.
- * @param string|bool $segment The segment.
- * @param int|bool $idGoal The id of the goal to get data for. If this is set to false,
- * data for every goal that belongs to $idSite is returned.
- */
- public function getVisitsUntilConversion($idSite, $period, $date, $segment = false, $idGoal = false)
- {
- $dataTable = $this->getGoalSpecificDataTable(
- Piwik_Goals::VISITS_UNTIL_RECORD_NAME, $idSite, $period, $date, $segment, $idGoal);
-
- $dataTable->queueFilter('Sort', array('label', 'asc', true));
- $dataTable->queueFilter(
- 'BeautifyRangeLabels', array(Piwik_Translate('General_OneVisit'), Piwik_Translate('General_NVisits')));
-
- return $dataTable;
- }
+ array($idSite, $idGoal));
+ Piwik_DeleteAllRows(Piwik_Common::prefixTable("log_conversion"), "WHERE idgoal = ?", 100000, array($idGoal));
+ Piwik_Tracker_Cache::regenerateCacheWebsiteAttributes($idSite);
+ }
+
+ /**
+ * Returns a datatable of Items SKU/name or categories and their metrics
+ * If $abandonedCarts set to 1, will return items abandoned in carts. If set to 0, will return items ordered
+ */
+ protected function getItems($recordName, $idSite, $period, $date, $abandonedCarts)
+ {
+ Piwik::checkUserHasViewAccess($idSite);
+ $recordNameFinal = $recordName;
+ if ($abandonedCarts) {
+ $recordNameFinal = Piwik_Goals::getItemRecordNameAbandonedCart($recordName);
+ }
+ $archive = Piwik_Archive::build($idSite, $period, $date);
+ $dataTable = $archive->getDataTable($recordNameFinal);
+ $dataTable->filter('Sort', array(Piwik_Archive::INDEX_ECOMMERCE_ITEM_REVENUE));
+ $dataTable->queueFilter('ReplaceColumnNames');
+
+ $ordersColumn = 'orders';
+ if ($abandonedCarts) {
+ $ordersColumn = 'abandoned_carts';
+ $dataTable->renameColumn(Piwik_Archive::INDEX_ECOMMERCE_ORDERS, $ordersColumn);
+ }
+
+ // Average price = sum product revenue / quantity
+ $dataTable->queueFilter('ColumnCallbackAddColumnQuotient', array('avg_price', 'price', $ordersColumn, Piwik_Tracker_GoalManager::REVENUE_PRECISION));
+
+ // Average quantity = sum product quantity / abandoned carts
+ $dataTable->queueFilter('ColumnCallbackAddColumnQuotient', array('avg_quantity', 'quantity', $ordersColumn, $precision = 1));
+ $dataTable->queueFilter('ColumnDelete', array('price'));
+
+ // Enrich the datatable with Product/Categories views, and conversion rates
+ $customVariables = Piwik_CustomVariables_API::getInstance()->getCustomVariables($idSite, $period, $date, $segment = false, $expanded = false, $_leavePiwikCoreVariables = true);
+ $mapping = array(
+ 'Goals_ItemsSku' => '_pks',
+ 'Goals_ItemsName' => '_pkn',
+ 'Goals_ItemsCategory' => '_pkc',
+ );
+ $reportToNotDefinedString = array(
+ 'Goals_ItemsSku' => Piwik_Translate('General_NotDefined', Piwik_Translate('Goals_ProductSKU')), // Note: this should never happen
+ 'Goals_ItemsName' => Piwik_Translate('General_NotDefined', Piwik_Translate('Goals_ProductName')),
+ 'Goals_ItemsCategory' => Piwik_Translate('General_NotDefined', Piwik_Translate('Goals_ProductCategory'))
+ );
+ $notDefinedStringPretty = $reportToNotDefinedString[$recordName];
+ $customVarNameToLookFor = $mapping[$recordName];
+
+ // Handle case where date=last30&period=day
+ if ($customVariables instanceof Piwik_DataTable_Array) {
+ $customVariableDatatables = $customVariables->getArray();
+ $dataTables = $dataTable->getArray();
+ foreach ($customVariableDatatables as $key => $customVariableTableForDate) {
+ $dataTableForDate = isset($dataTables[$key]) ? $dataTables[$key] : new Piwik_DataTable();
+
+ // we do not enter the IF
+ // if case idSite=1,3 AND period=day&date=datefrom,dateto,
+ if (isset($customVariableTableForDate->metadata['period'])) {
+ $dateRewrite = $customVariableTableForDate->metadata['period']->getDateStart()->toString();
+ $row = $customVariableTableForDate->getRowFromLabel($customVarNameToLookFor);
+ if ($row) {
+ $idSubtable = $row->getIdSubDataTable();
+ $this->enrichItemsDataTableWithItemsViewMetrics($dataTableForDate, $idSite, $period, $dateRewrite, $idSubtable);
+ }
+ $dataTable->addTable($dataTableForDate, $key);
+ }
+ $this->renameNotDefinedRow($dataTableForDate, $notDefinedStringPretty);
+ }
+ } elseif ($customVariables instanceof Piwik_DataTable) {
+ $row = $customVariables->getRowFromLabel($customVarNameToLookFor);
+ if ($row) {
+ $idSubtable = $row->getIdSubDataTable();
+ $this->enrichItemsDataTableWithItemsViewMetrics($dataTable, $idSite, $period, $date, $idSubtable);
+ }
+ $this->renameNotDefinedRow($dataTable, $notDefinedStringPretty);
+ }
+
+ // Product conversion rate = orders / visits
+ $dataTable->queueFilter('ColumnCallbackAddColumnPercentage', array('conversion_rate', $ordersColumn, 'nb_visits', Piwik_Tracker_GoalManager::REVENUE_PRECISION));
+
+ return $dataTable;
+ }
+
+ protected function renameNotDefinedRow($dataTable, $notDefinedStringPretty)
+ {
+ if ($dataTable instanceof Piwik_DataTable_Array) {
+ foreach ($dataTable->getArray() as $table) {
+ $this->renameNotDefinedRow($table, $notDefinedStringPretty);
+ }
+ return;
+ }
+ $rowNotDefined = $dataTable->getRowFromLabel(Piwik_CustomVariables::LABEL_CUSTOM_VALUE_NOT_DEFINED);
+ if ($rowNotDefined) {
+ $rowNotDefined->setColumn('label', $notDefinedStringPretty);
+ }
+ }
+
+
+ protected function enrichItemsDataTableWithItemsViewMetrics($dataTable, $idSite, $period, $date, $idSubtable)
+ {
+ $ecommerceViews = Piwik_CustomVariables_API::getInstance()->getCustomVariablesValuesFromNameId($idSite, $period, $date, $idSubtable, $segment = false, $_leavePriceViewedColumn = true);
+
+ // For Product names and SKU reports, and for Category report
+ // Use the Price (tracked on page views)
+ // ONLY when the price sold in conversions is not found (ie. product viewed but not sold)
+ foreach ($ecommerceViews->getRows() as $rowView) {
+ // If there is not already a 'sum price' for this product
+ $rowFound = $dataTable->getRowFromLabel($rowView->getColumn('label'));
+ $price = $rowFound
+ ? $rowFound->getColumn(Piwik_Archive::INDEX_ECOMMERCE_ITEM_PRICE)
+ : false;
+ if (empty($price)) {
+ // If a price was tracked on the product page
+ if ($rowView->getColumn(Piwik_Archive::INDEX_ECOMMERCE_ITEM_PRICE_VIEWED)) {
+ $rowView->renameColumn(Piwik_Archive::INDEX_ECOMMERCE_ITEM_PRICE_VIEWED, 'avg_price');
+ }
+ }
+ $rowView->deleteColumn(Piwik_Archive::INDEX_ECOMMERCE_ITEM_PRICE_VIEWED);
+ }
+
+ $dataTable->addDataTable($ecommerceViews);
+ }
+
+ public function getItemsSku($idSite, $period, $date, $abandonedCarts = false)
+ {
+ return $this->getItems('Goals_ItemsSku', $idSite, $period, $date, $abandonedCarts);
+ }
+
+ public function getItemsName($idSite, $period, $date, $abandonedCarts = false)
+ {
+ return $this->getItems('Goals_ItemsName', $idSite, $period, $date, $abandonedCarts);
+ }
+
+ public function getItemsCategory($idSite, $period, $date, $abandonedCarts = false)
+ {
+ return $this->getItems('Goals_ItemsCategory', $idSite, $period, $date, $abandonedCarts);
+ }
+
+ /**
+ * Helper function that checks for special string goal IDs and converts them to
+ * their integer equivalents.
+ *
+ * Checks for the following values:
+ * Piwik_Archive::LABEL_ECOMMERCE_ORDER
+ * Piwik_Archive::LABEL_ECOMMERCE_CART
+ *
+ * @param string|int $idGoal The goal id as an integer or a special string.
+ * @return int The numeric goal id.
+ */
+ protected static function convertSpecialGoalIds($idGoal)
+ {
+ if ($idGoal == Piwik_Archive::LABEL_ECOMMERCE_ORDER) {
+ return Piwik_Tracker_GoalManager::IDGOAL_ORDER;
+ } else if ($idGoal == Piwik_Archive::LABEL_ECOMMERCE_CART) {
+ return Piwik_Tracker_GoalManager::IDGOAL_CART;
+ } else {
+ return $idGoal;
+ }
+ }
+
+ /**
+ * Returns Goals data
+ *
+ * @param int $idSite
+ * @param string $period
+ * @param string $date
+ * @param bool $segment
+ * @param bool|int $idGoal
+ * @param array $columns Array of metrics to fetch: nb_conversions, conversion_rate, revenue
+ * @return Piwik_DataTable
+ */
+ public function get($idSite, $period, $date, $segment = false, $idGoal = false, $columns = array())
+ {
+ Piwik::checkUserHasViewAccess($idSite);
+ $archive = Piwik_Archive::build($idSite, $period, $date, $segment);
+ $columns = Piwik::getArrayFromApiParameter($columns);
+
+ // Mapping string idGoal to internal ID
+ $idGoal = self::convertSpecialGoalIds($idGoal);
+
+ if (empty($columns)) {
+ $columns = Piwik_Goals::getGoalColumns($idGoal);
+ if ($idGoal == Piwik_Archive::LABEL_ECOMMERCE_ORDER) {
+ $columns[] = 'avg_order_revenue';
+ }
+ }
+ if (in_array('avg_order_revenue', $columns)
+ && $idGoal == Piwik_Archive::LABEL_ECOMMERCE_ORDER
+ ) {
+ $columns[] = 'nb_conversions';
+ $columns[] = 'revenue';
+ $columns = array_values(array_unique($columns));
+ }
+ $columnsToSelect = array();
+ foreach ($columns as &$columnName) {
+ $columnsToSelect[] = Piwik_Goals::getRecordName($columnName, $idGoal);
+ }
+ $dataTable = $archive->getDataTableFromNumeric($columnsToSelect);
+
+ // Rewrite column names as we expect them
+ foreach ($columnsToSelect as $id => $oldName) {
+ $dataTable->renameColumn($oldName, $columns[$id]);
+ }
+ if ($idGoal == Piwik_Archive::LABEL_ECOMMERCE_ORDER) {
+ if ($dataTable instanceof Piwik_DataTable_Array) {
+ foreach ($dataTable->getArray() as $row) {
+ $this->enrichTable($row);
+ }
+ } else {
+ $this->enrichTable($dataTable);
+ }
+ }
+ return $dataTable;
+ }
+
+ protected function enrichTable($table)
+ {
+ $row = $table->getFirstRow();
+ if (!$row) {
+ return;
+ }
+ // AVG order per visit
+ if (false !== $table->getColumn('avg_order_revenue')) {
+ $conversions = $row->getColumn('nb_conversions');
+ if ($conversions) {
+ $row->setColumn('avg_order_revenue', round($row->getColumn('revenue') / $conversions, 2));
+ }
+ }
+ }
+
+ protected function getNumeric($idSite, $period, $date, $segment, $toFetch)
+ {
+ Piwik::checkUserHasViewAccess($idSite);
+ $archive = Piwik_Archive::build($idSite, $period, $date, $segment);
+ $dataTable = $archive->getNumeric($toFetch);
+ return $dataTable;
+ }
+
+ /**
+ * @ignore
+ */
+ public function getConversions($idSite, $period, $date, $segment = false, $idGoal = false)
+ {
+ return $this->getNumeric($idSite, $period, $date, $segment, Piwik_Goals::getRecordName('nb_conversions', $idGoal));
+ }
+
+ /**
+ * @ignore
+ */
+ public function getNbVisitsConverted($idSite, $period, $date, $segment = false, $idGoal = false)
+ {
+ return $this->getNumeric($idSite, $period, $date, $segment, Piwik_Goals::getRecordName('nb_visits_converted', $idGoal));
+ }
+
+ /**
+ * @ignore
+ */
+ public function getConversionRate($idSite, $period, $date, $segment = false, $idGoal = false)
+ {
+ return $this->getNumeric($idSite, $period, $date, $segment, Piwik_Goals::getRecordName('conversion_rate', $idGoal));
+ }
+
+ /**
+ * @ignore
+ */
+ public function getRevenue($idSite, $period, $date, $segment = false, $idGoal = false)
+ {
+ return $this->getNumeric($idSite, $period, $date, $segment, Piwik_Goals::getRecordName('revenue', $idGoal));
+ }
+
+ /**
+ * Utility method that retrieve an archived DataTable for a specific site, date range,
+ * segment and goal. If not goal is specified, this method will retrieve and sum the
+ * data for every goal.
+ *
+ * @param string $recordName The archive entry name.
+ * @param int|string $idSite The site(s) to select data for.
+ * @param string $period The period type.
+ * @param string $date The date type.
+ * @param string $segment The segment.
+ * @param int|bool $idGoal The id of the goal to get data for. If this is set to false,
+ * data for every goal that belongs to $idSite is returned.
+ */
+ protected function getGoalSpecificDataTable($recordName, $idSite, $period, $date, $segment, $idGoal)
+ {
+ Piwik::checkUserHasViewAccess($idSite);
+
+ $archive = Piwik_Archive::build($idSite, $period, $date, $segment);
+
+ // check for the special goal ids
+ $realGoalId = $idGoal != true ? false : self::convertSpecialGoalIds($idGoal);
+
+ // get the data table
+ $dataTable = $archive->getDataTable(Piwik_Goals::getRecordName($recordName, $realGoalId), $idSubtable = null);
+ $dataTable->queueFilter('ReplaceColumnNames');
+
+ return $dataTable;
+ }
+
+ /**
+ * Gets a DataTable that maps ranges of days to the number of conversions that occurred
+ * within those ranges, for the specified site, date range, segment and goal.
+ *
+ * @param int $idSite The site to select data from.
+ * @param string $period The period type.
+ * @param string $date The date type.
+ * @param string|bool $segment The segment.
+ * @param int|bool $idGoal The id of the goal to get data for. If this is set to false,
+ * data for every goal that belongs to $idSite is returned.
+ */
+ public function getDaysToConversion($idSite, $period, $date, $segment = false, $idGoal = false)
+ {
+ $dataTable = $this->getGoalSpecificDataTable(
+ Piwik_Goals::DAYS_UNTIL_CONV_RECORD_NAME, $idSite, $period, $date, $segment, $idGoal);
+
+ $dataTable->queueFilter('Sort', array('label', 'asc', true));
+ $dataTable->queueFilter(
+ 'BeautifyRangeLabels', array(Piwik_Translate('General_OneDay'), Piwik_Translate('General_NDays')));
+
+ return $dataTable;
+ }
+
+ /**
+ * Gets a DataTable that maps ranges of visit counts to the number of conversions that
+ * occurred on those visits for the specified site, date range, segment and goal.
+ *
+ * @param int $idSite The site to select data from.
+ * @param string $period The period type.
+ * @param string $date The date type.
+ * @param string|bool $segment The segment.
+ * @param int|bool $idGoal The id of the goal to get data for. If this is set to false,
+ * data for every goal that belongs to $idSite is returned.
+ */
+ public function getVisitsUntilConversion($idSite, $period, $date, $segment = false, $idGoal = false)
+ {
+ $dataTable = $this->getGoalSpecificDataTable(
+ Piwik_Goals::VISITS_UNTIL_RECORD_NAME, $idSite, $period, $date, $segment, $idGoal);
+
+ $dataTable->queueFilter('Sort', array('label', 'asc', true));
+ $dataTable->queueFilter(
+ 'BeautifyRangeLabels', array(Piwik_Translate('General_OneVisit'), Piwik_Translate('General_NVisits')));
+
+ return $dataTable;
+ }
}
diff --git a/plugins/Goals/Controller.php b/plugins/Goals/Controller.php
index 2b423bf9db..35d795a23f 100644
--- a/plugins/Goals/Controller.php
+++ b/plugins/Goals/Controller.php
@@ -15,570 +15,537 @@
*/
class Piwik_Goals_Controller extends Piwik_Controller
{
- const CONVERSION_RATE_PRECISION = 1;
-
- /**
- * Number of "Your top converting keywords/etc are" to display in the per Goal overview page
- * @var int
- */
- const COUNT_TOP_ROWS_TO_DISPLAY = 3;
-
- protected $goalColumnNameToLabel = array(
- 'avg_order_revenue' => 'General_AverageOrderValue',
- 'nb_conversions' => 'Goals_ColumnConversions',
- 'conversion_rate'=> 'General_ColumnConversionRate',
- 'revenue' => 'General_TotalRevenue',
- 'items' => 'General_PurchasedProducts',
- );
-
- private function formatConversionRate($conversionRate)
- {
- return sprintf('%.' . self::CONVERSION_RATE_PRECISION . 'f%%', $conversionRate);
- }
-
- public function __construct()
- {
- parent::__construct();
- $this->idSite = Piwik_Common::getRequestVar('idSite', null, 'int');
- $this->goals = Piwik_Goals_API::getInstance()->getGoals($this->idSite);
- foreach($this->goals as &$goal)
- {
- $goal['name'] = Piwik_Common::sanitizeInputValue($goal['name']);
- if(isset($goal['pattern']))
- {
- $goal['pattern'] = Piwik_Common::sanitizeInputValue($goal['pattern']);
- }
- }
- }
-
- public function widgetGoalReport()
- {
- $view = $this->getGoalReportView($idGoal = Piwik_Common::getRequestVar('idGoal', null, 'string'));
- $view->displayFullReport = false;
- echo $view->render();
- }
-
- public function goalReport()
- {
- $view = $this->getGoalReportView($idGoal = Piwik_Common::getRequestVar('idGoal', null, 'string'));
- $view->displayFullReport = true;
- echo $view->render();
- }
-
- public function ecommerceReport()
- {
- if(!Piwik_PluginsManager::getInstance()->isPluginActivated('CustomVariables'))
- {
- throw new Exception("Ecommerce Tracking requires that the plugin Custom Variables is enabled. Please enable the plugin CustomVariables (or ask your admin).");
- }
-
- $view = $this->getGoalReportView($idGoal = Piwik_Archive::LABEL_ECOMMERCE_ORDER);
- $view->displayFullReport = true;
- echo $view->render();
- }
- protected function getItemsView($fetch, $type, $function, $api, $abandonedCart = false)
- {
- $saveGET = $_GET;
- $label = Piwik_Translate($type);
- $abandonedCart = Piwik_Common::getRequestVar('viewDataTable', 'ecommerceOrder', 'string') == 'ecommerceAbandonedCart';
-
- // Products in Ecommerce Orders
- if($abandonedCart === false)
- {
- $view = new Piwik_ViewDataTable_HtmlTable_EcommerceOrder();
- $columns = Piwik_Goals::getProductReportColumns();
- $view->setMetricDocumentation('revenue', Piwik_Translate('Goals_ColumnRevenueDocumentation', Piwik_Translate('Goals_DocumentationRevenueGeneratedByProductSales')));
- $view->setMetricDocumentation('quantity', Piwik_Translate('Goals_ColumnQuantityDocumentation', $label));
- $view->setMetricDocumentation('orders', Piwik_Translate('Goals_ColumnOrdersDocumentation', $label));
- $view->setMetricDocumentation('avg_price', Piwik_Translate('Goals_ColumnAveragePriceDocumentation', $label));
- $view->setMetricDocumentation('avg_quantity', Piwik_Translate('Goals_ColumnAverageQuantityDocumentation', $label));
- $view->setMetricDocumentation('nb_visits', Piwik_Translate('Goals_ColumnVisitsProductDocumentation', $label));
- $view->setMetricDocumentation('conversion_rate', Piwik_Translate('Goals_ColumnConversionRateProductDocumentation', $label));
- }
- // Products in Abandoned Carts
- else
- {
- $view = new Piwik_ViewDataTable_HtmlTable_EcommerceAbandonedCart();
- $columns = Piwik_Goals::getProductReportColumns();
- $columns['abandoned_carts'] = Piwik_Translate('General_AbandonedCarts');
- $columns['revenue'] = Piwik_Translate('Goals_LeftInCart', Piwik_Translate('General_ProductRevenue'));
- $columns['quantity'] = Piwik_Translate('Goals_LeftInCart', Piwik_Translate('General_Quantity'));
- $columns['avg_quantity'] = Piwik_Translate('Goals_LeftInCart', Piwik_Translate('General_AverageQuantity'));
- unset($columns['orders']);
- unset($columns['conversion_rate']);
- $_GET['abandonedCarts'] = 1;
- }
-
- $view->init( $this->pluginName, $function, $api );
- $view->enableShowEcommerce();
- $view->disableShowAllViewsIcons();
- $view->disableShowTable();
- $view->disableExcludeLowPopulation();
- $view->disableShowAllColumns();
- $this->setPeriodVariablesView($view);
- $view->setLimit( 10 );
-
- $view->setColumnsTranslations(array_merge(
- array('label' => $label),
- $columns
- ));
- $columnsToDisplay = array_merge(array('label'), array_keys($columns));
- $view->setColumnsToDisplay($columnsToDisplay);
- $view->setSortedColumn('revenue', 'desc');
- foreach(array('revenue', 'avg_price') as $column)
- {
- $view->queueFilter('ColumnCallbackReplace', array($column, array("Piwik", "getPrettyMoney"), array($this->idSite)));
- }
- $return = $this->renderView($view, $fetch);
- $_GET = $saveGET;
- return $return;
- }
-
- public function getItemsSku($fetch = false)
- {
- return $this->getItemsView($fetch, 'Goals_ProductSKU', __FUNCTION__, "Goals.getItemsSku");
- }
-
- public function getItemsName($fetch = false)
- {
- return $this->getItemsView($fetch, 'Goals_ProductName', __FUNCTION__, "Goals.getItemsName");
- }
-
- public function getItemsCategory($fetch = false)
- {
- return $this->getItemsView($fetch, 'Goals_ProductCategory', __FUNCTION__, "Goals.getItemsCategory");
- }
-
- public function getEcommerceLog($fetch = false)
- {
- $saveGET = $_GET;
- $_GET['filterEcommerce'] = Piwik_Common::getRequestVar('filterEcommerce', 1, 'int');
- $_GET['widget'] = 1;
- $_GET['segment'] = 'visitEcommerceStatus!=none';
- $output = Piwik_FrontController::getInstance()->dispatch('Live', 'getVisitorLog', array($fetch));
- $_GET = $saveGET;
- return $output;
- }
-
- protected function getGoalReportView($idGoal = false)
- {
- $view = Piwik_View::factory('single_goal');
- if($idGoal == Piwik_Archive::LABEL_ECOMMERCE_ORDER)
- {
- $goalDefinition['name'] = Piwik_Translate('Goals_Ecommerce');
- $goalDefinition['allow_multiple'] = true;
- $ecommerce = $view->ecommerce = true;
- }
- else
- {
- if(!isset($this->goals[$idGoal]))
- {
- Piwik::redirectToModule('Goals', 'index', array('idGoal' => null));
- }
- $goalDefinition = $this->goals[$idGoal];
- }
- $this->setGeneralVariablesView($view);
- $goal = $this->getMetricsForGoal($idGoal);
- foreach($goal as $name => $value)
- {
- $view->$name = $value;
- }
- if($idGoal == Piwik_Archive::LABEL_ECOMMERCE_ORDER)
- {
- $goal = $this->getMetricsForGoal(Piwik_Archive::LABEL_ECOMMERCE_CART);
- foreach($goal as $name => $value)
- {
- $name = 'cart_'.$name;
- $view->$name = $value;
- }
- }
- $view->idGoal = $idGoal;
- $view->goalName = $goalDefinition['name'];
- $view->goalAllowMultipleConversionsPerVisit = $goalDefinition['allow_multiple'];
- $view->graphEvolution = $this->getEvolutionGraph(true, array('nb_conversions'), $idGoal);
- $view->nameGraphEvolution = 'GoalsgetEvolutionGraph'.$idGoal;
- $view->topDimensions = $this->getTopDimensions($idGoal);
-
- // conversion rate for new and returning visitors
- $segment = 'visitorType==returning,visitorType==returningCustomer';
- $conversionRateReturning = Piwik_Goals_API::getInstance()->getConversionRate($this->idSite, Piwik_Common::getRequestVar('period'), Piwik_Common::getRequestVar('date'), $segment, $idGoal);
- $view->conversion_rate_returning = $this->formatConversionRate($conversionRateReturning);
- $segment = 'visitorType==new';
- $conversionRateNew = Piwik_Goals_API::getInstance()->getConversionRate($this->idSite, Piwik_Common::getRequestVar('period'), Piwik_Common::getRequestVar('date'), $segment, $idGoal);
- $view->conversion_rate_new = $this->formatConversionRate($conversionRateNew);
- $view->goalReportsByDimension = $this->getGoalReportsByDimensionTable(
- $view->nb_conversions, isset($ecommerce), !empty($view->cart_nb_conversions));
- return $view;
- }
-
- public function index()
- {
- $view = $this->getOverviewView();
- $view->goalsJSON = Piwik_Common::json_encode($this->goals);
- $view->userCanEditGoals = Piwik::isUserHasAdminAccess($this->idSite);
- $view->ecommerceEnabled = $this->site->isEcommerceEnabled();
- $view->displayFullReport = true;
- echo $view->render();
- }
-
- public function widgetGoalsOverview( )
- {
- $view = $this->getOverviewView();
- $view->displayFullReport = false;
- echo $view->render();
- }
-
- protected function getOverviewView()
- {
- $view = Piwik_View::factory('overview');
- $this->setGeneralVariablesView($view);
-
- $view->graphEvolution = $this->getEvolutionGraph(true, array('nb_conversions'));
- $view->nameGraphEvolution = 'GoalsgetEvolutionGraph';
-
- // sparkline for the historical data of the above values
- $view->urlSparklineConversions = $this->getUrlSparkline('getEvolutionGraph', array('columns' => array('nb_conversions'), 'idGoal' => ''));
- $view->urlSparklineConversionRate = $this->getUrlSparkline('getEvolutionGraph', array('columns' => array('conversion_rate'), 'idGoal' => ''));
- $view->urlSparklineRevenue = $this->getUrlSparkline('getEvolutionGraph', array('columns' => array('revenue'), 'idGoal' => ''));
-
- // Pass empty idGoal will return Goal overview
- $request = new Piwik_API_Request("method=Goals.get&format=original&idGoal=");
- $datatable = $request->process();
- $dataRow = $datatable->getFirstRow();
- $view->nb_conversions = $dataRow->getColumn('nb_conversions');
- $view->nb_visits_converted = $dataRow->getColumn('nb_visits_converted');
- $view->conversion_rate = $this->formatConversionRate($dataRow->getColumn('conversion_rate'));
- $view->revenue = $dataRow->getColumn('revenue');
-
- $goalMetrics = array();
- foreach($this->goals as $idGoal => $goal)
- {
- $goalMetrics[$idGoal] = $this->getMetricsForGoal($idGoal);
- $goalMetrics[$idGoal]['name'] = $goal['name'];
- $goalMetrics[$idGoal]['goalAllowMultipleConversionsPerVisit'] = $goal['allow_multiple'];
- }
-
- $view->goalMetrics = $goalMetrics;
- $view->goals = $this->goals;
- $view->goalReportsByDimension = $this->getGoalReportsByDimensionTable(
- $view->nb_conversions, $ecommerce = false, !empty($view->cart_nb_conversions));
- return $view;
- }
-
- public function getLastNbConversionsGraph( $fetch = false )
- {
- $view = $this->getLastUnitGraph($this->pluginName, __FUNCTION__, 'Goals.getConversions');
- return $this->renderView($view, $fetch);
- }
-
- public function getLastConversionRateGraph( $fetch = false )
- {
- $view = $this->getLastUnitGraph($this->pluginName, __FUNCTION__, 'Goals.getConversionRate');
- return $this->renderView($view, $fetch);
- }
-
- public function getLastRevenueGraph( $fetch = false )
- {
- $view = $this->getLastUnitGraph($this->pluginName, __FUNCTION__, 'Goals.getRevenue');
- return $this->renderView($view, $fetch);
- }
-
- public function addNewGoal()
- {
- $view = Piwik_View::factory('add_new_goal');
- $this->setGeneralVariablesView($view);
- $view->userCanEditGoals = Piwik::isUserHasAdminAccess($this->idSite);
- $view->onlyShowAddNewGoal = true;
- echo $view->render();
- }
-
- public function getEvolutionGraph( $fetch = false, array $columns = array(), $idGoal = false)
- {
- if(empty($columns))
- {
- $columns = Piwik_Common::getRequestVar('columns');
- $columns = Piwik::getArrayFromApiParameter($columns);
- }
-
- $columns = !is_array($columns) ? array($columns) : $columns;
-
- if(empty($idGoal))
- {
- $idGoal = Piwik_Common::getRequestVar('idGoal', false, 'string');
- }
- $view = $this->getLastUnitGraph($this->pluginName, __FUNCTION__, 'Goals.get');
- $view->setParametersToModify(array('idGoal' => $idGoal));
-
- $nameToLabel = $this->goalColumnNameToLabel;
- if($idGoal == Piwik_Archive::LABEL_ECOMMERCE_ORDER)
- {
- $nameToLabel['nb_conversions'] = 'General_EcommerceOrders';
- }
- elseif($idGoal == Piwik_Archive::LABEL_ECOMMERCE_CART)
- {
- $nameToLabel['nb_conversions'] = Piwik_Translate('General_VisitsWith', Piwik_Translate('Goals_AbandonedCart'));
- $nameToLabel['conversion_rate'] = $nameToLabel['nb_conversions'];
- $nameToLabel['revenue'] = Piwik_Translate('Goals_LeftInCart', Piwik_Translate('Goals_ColumnRevenue'));
- $nameToLabel['items'] = Piwik_Translate('Goals_LeftInCart', Piwik_Translate('Goals_Products'));
- }
-
- $selectableColumns = array('nb_conversions', 'conversion_rate', 'revenue');
- if ($this->site->isEcommerceEnabled())
- {
- $selectableColumns[] = 'items';
- $selectableColumns[] = 'avg_order_revenue';
- }
-
- foreach(array_merge($columns, $selectableColumns) as $columnName)
- {
- $columnTranslation = '';
- // find the right translation for this column, eg. find 'revenue' if column is Goal_1_revenue
- foreach($nameToLabel as $metric => $metricTranslation)
- {
- if(strpos($columnName, $metric) !== false)
- {
- $columnTranslation = Piwik_Translate($metricTranslation);
- break;
- }
- }
-
- if(!empty($idGoal) && isset($this->goals[$idGoal]))
- {
- $goalName = $this->goals[$idGoal]['name'];
- $columnTranslation = "$columnTranslation (".Piwik_Translate('Goals_GoalX', "$goalName").")";
- }
- $view->setColumnTranslation($columnName, $columnTranslation);
- }
- $view->setColumnsToDisplay($columns);
- $view->setSelectableColumns($selectableColumns);
-
- $langString = $idGoal ? 'Goals_SingleGoalOverviewDocumentation' : 'Goals_GoalsOverviewDocumentation';
- $view->setReportDocumentation(Piwik_Translate($langString, '<br />'));
-
- return $this->renderView($view, $fetch);
- }
-
-
- protected function getTopDimensions($idGoal)
- {
- $columnNbConversions = 'goal_'.$idGoal.'_nb_conversions';
- $columnConversionRate = 'goal_'.$idGoal.'_conversion_rate';
-
- $topDimensionsToLoad = array();
-
- if(Piwik_PluginsManager::getInstance()->isPluginActivated('UserCountry'))
- {
- $topDimensionsToLoad += array(
- 'country' => 'UserCountry.getCountry',
- );
- }
-
- $keywordNotDefinedString = '';
- if(Piwik_PluginsManager::getInstance()->isPluginActivated('Referers'))
- {
- $keywordNotDefinedString = Piwik_Referers::getKeywordNotDefinedString();
- $topDimensionsToLoad += array(
- 'keyword' => 'Referers.getKeywords',
- 'website' => 'Referers.getWebsites',
- );
- }
- $topDimensions = array();
- foreach($topDimensionsToLoad as $dimensionName => $apiMethod)
- {
- $request = new Piwik_API_Request("method=$apiMethod
+ const CONVERSION_RATE_PRECISION = 1;
+
+ /**
+ * Number of "Your top converting keywords/etc are" to display in the per Goal overview page
+ * @var int
+ */
+ const COUNT_TOP_ROWS_TO_DISPLAY = 3;
+
+ protected $goalColumnNameToLabel = array(
+ 'avg_order_revenue' => 'General_AverageOrderValue',
+ 'nb_conversions' => 'Goals_ColumnConversions',
+ 'conversion_rate' => 'General_ColumnConversionRate',
+ 'revenue' => 'General_TotalRevenue',
+ 'items' => 'General_PurchasedProducts',
+ );
+
+ private function formatConversionRate($conversionRate)
+ {
+ return sprintf('%.' . self::CONVERSION_RATE_PRECISION . 'f%%', $conversionRate);
+ }
+
+ public function __construct()
+ {
+ parent::__construct();
+ $this->idSite = Piwik_Common::getRequestVar('idSite', null, 'int');
+ $this->goals = Piwik_Goals_API::getInstance()->getGoals($this->idSite);
+ foreach ($this->goals as &$goal) {
+ $goal['name'] = Piwik_Common::sanitizeInputValue($goal['name']);
+ if (isset($goal['pattern'])) {
+ $goal['pattern'] = Piwik_Common::sanitizeInputValue($goal['pattern']);
+ }
+ }
+ }
+
+ public function widgetGoalReport()
+ {
+ $view = $this->getGoalReportView($idGoal = Piwik_Common::getRequestVar('idGoal', null, 'string'));
+ $view->displayFullReport = false;
+ echo $view->render();
+ }
+
+ public function goalReport()
+ {
+ $view = $this->getGoalReportView($idGoal = Piwik_Common::getRequestVar('idGoal', null, 'string'));
+ $view->displayFullReport = true;
+ echo $view->render();
+ }
+
+ public function ecommerceReport()
+ {
+ if (!Piwik_PluginsManager::getInstance()->isPluginActivated('CustomVariables')) {
+ throw new Exception("Ecommerce Tracking requires that the plugin Custom Variables is enabled. Please enable the plugin CustomVariables (or ask your admin).");
+ }
+
+ $view = $this->getGoalReportView($idGoal = Piwik_Archive::LABEL_ECOMMERCE_ORDER);
+ $view->displayFullReport = true;
+ echo $view->render();
+ }
+
+ protected function getItemsView($fetch, $type, $function, $api, $abandonedCart = false)
+ {
+ $saveGET = $_GET;
+ $label = Piwik_Translate($type);
+ $abandonedCart = Piwik_Common::getRequestVar('viewDataTable', 'ecommerceOrder', 'string') == 'ecommerceAbandonedCart';
+
+ // Products in Ecommerce Orders
+ if ($abandonedCart === false) {
+ $view = new Piwik_ViewDataTable_HtmlTable_EcommerceOrder();
+ $columns = Piwik_Goals::getProductReportColumns();
+ $view->setMetricDocumentation('revenue', Piwik_Translate('Goals_ColumnRevenueDocumentation', Piwik_Translate('Goals_DocumentationRevenueGeneratedByProductSales')));
+ $view->setMetricDocumentation('quantity', Piwik_Translate('Goals_ColumnQuantityDocumentation', $label));
+ $view->setMetricDocumentation('orders', Piwik_Translate('Goals_ColumnOrdersDocumentation', $label));
+ $view->setMetricDocumentation('avg_price', Piwik_Translate('Goals_ColumnAveragePriceDocumentation', $label));
+ $view->setMetricDocumentation('avg_quantity', Piwik_Translate('Goals_ColumnAverageQuantityDocumentation', $label));
+ $view->setMetricDocumentation('nb_visits', Piwik_Translate('Goals_ColumnVisitsProductDocumentation', $label));
+ $view->setMetricDocumentation('conversion_rate', Piwik_Translate('Goals_ColumnConversionRateProductDocumentation', $label));
+ } // Products in Abandoned Carts
+ else {
+ $view = new Piwik_ViewDataTable_HtmlTable_EcommerceAbandonedCart();
+ $columns = Piwik_Goals::getProductReportColumns();
+ $columns['abandoned_carts'] = Piwik_Translate('General_AbandonedCarts');
+ $columns['revenue'] = Piwik_Translate('Goals_LeftInCart', Piwik_Translate('General_ProductRevenue'));
+ $columns['quantity'] = Piwik_Translate('Goals_LeftInCart', Piwik_Translate('General_Quantity'));
+ $columns['avg_quantity'] = Piwik_Translate('Goals_LeftInCart', Piwik_Translate('General_AverageQuantity'));
+ unset($columns['orders']);
+ unset($columns['conversion_rate']);
+ $_GET['abandonedCarts'] = 1;
+ }
+
+ $view->init($this->pluginName, $function, $api);
+ $view->enableShowEcommerce();
+ $view->disableShowAllViewsIcons();
+ $view->disableShowTable();
+ $view->disableExcludeLowPopulation();
+ $view->disableShowAllColumns();
+ $this->setPeriodVariablesView($view);
+ $view->setLimit(10);
+
+ $view->setColumnsTranslations(array_merge(
+ array('label' => $label),
+ $columns
+ ));
+ $columnsToDisplay = array_merge(array('label'), array_keys($columns));
+ $view->setColumnsToDisplay($columnsToDisplay);
+ $view->setSortedColumn('revenue', 'desc');
+ foreach (array('revenue', 'avg_price') as $column) {
+ $view->queueFilter('ColumnCallbackReplace', array($column, array("Piwik", "getPrettyMoney"), array($this->idSite)));
+ }
+ $return = $this->renderView($view, $fetch);
+ $_GET = $saveGET;
+ return $return;
+ }
+
+ public function getItemsSku($fetch = false)
+ {
+ return $this->getItemsView($fetch, 'Goals_ProductSKU', __FUNCTION__, "Goals.getItemsSku");
+ }
+
+ public function getItemsName($fetch = false)
+ {
+ return $this->getItemsView($fetch, 'Goals_ProductName', __FUNCTION__, "Goals.getItemsName");
+ }
+
+ public function getItemsCategory($fetch = false)
+ {
+ return $this->getItemsView($fetch, 'Goals_ProductCategory', __FUNCTION__, "Goals.getItemsCategory");
+ }
+
+ public function getEcommerceLog($fetch = false)
+ {
+ $saveGET = $_GET;
+ $_GET['filterEcommerce'] = Piwik_Common::getRequestVar('filterEcommerce', 1, 'int');
+ $_GET['widget'] = 1;
+ $_GET['segment'] = 'visitEcommerceStatus!=none';
+ $output = Piwik_FrontController::getInstance()->dispatch('Live', 'getVisitorLog', array($fetch));
+ $_GET = $saveGET;
+ return $output;
+ }
+
+ protected function getGoalReportView($idGoal = false)
+ {
+ $view = Piwik_View::factory('single_goal');
+ if ($idGoal == Piwik_Archive::LABEL_ECOMMERCE_ORDER) {
+ $goalDefinition['name'] = Piwik_Translate('Goals_Ecommerce');
+ $goalDefinition['allow_multiple'] = true;
+ $ecommerce = $view->ecommerce = true;
+ } else {
+ if (!isset($this->goals[$idGoal])) {
+ Piwik::redirectToModule('Goals', 'index', array('idGoal' => null));
+ }
+ $goalDefinition = $this->goals[$idGoal];
+ }
+ $this->setGeneralVariablesView($view);
+ $goal = $this->getMetricsForGoal($idGoal);
+ foreach ($goal as $name => $value) {
+ $view->$name = $value;
+ }
+ if ($idGoal == Piwik_Archive::LABEL_ECOMMERCE_ORDER) {
+ $goal = $this->getMetricsForGoal(Piwik_Archive::LABEL_ECOMMERCE_CART);
+ foreach ($goal as $name => $value) {
+ $name = 'cart_' . $name;
+ $view->$name = $value;
+ }
+ }
+ $view->idGoal = $idGoal;
+ $view->goalName = $goalDefinition['name'];
+ $view->goalAllowMultipleConversionsPerVisit = $goalDefinition['allow_multiple'];
+ $view->graphEvolution = $this->getEvolutionGraph(true, array('nb_conversions'), $idGoal);
+ $view->nameGraphEvolution = 'GoalsgetEvolutionGraph' . $idGoal;
+ $view->topDimensions = $this->getTopDimensions($idGoal);
+
+ // conversion rate for new and returning visitors
+ $segment = 'visitorType==returning,visitorType==returningCustomer';
+ $conversionRateReturning = Piwik_Goals_API::getInstance()->getConversionRate($this->idSite, Piwik_Common::getRequestVar('period'), Piwik_Common::getRequestVar('date'), $segment, $idGoal);
+ $view->conversion_rate_returning = $this->formatConversionRate($conversionRateReturning);
+ $segment = 'visitorType==new';
+ $conversionRateNew = Piwik_Goals_API::getInstance()->getConversionRate($this->idSite, Piwik_Common::getRequestVar('period'), Piwik_Common::getRequestVar('date'), $segment, $idGoal);
+ $view->conversion_rate_new = $this->formatConversionRate($conversionRateNew);
+ $view->goalReportsByDimension = $this->getGoalReportsByDimensionTable(
+ $view->nb_conversions, isset($ecommerce), !empty($view->cart_nb_conversions));
+ return $view;
+ }
+
+ public function index()
+ {
+ $view = $this->getOverviewView();
+ $view->goalsJSON = Piwik_Common::json_encode($this->goals);
+ $view->userCanEditGoals = Piwik::isUserHasAdminAccess($this->idSite);
+ $view->ecommerceEnabled = $this->site->isEcommerceEnabled();
+ $view->displayFullReport = true;
+ echo $view->render();
+ }
+
+ public function widgetGoalsOverview()
+ {
+ $view = $this->getOverviewView();
+ $view->displayFullReport = false;
+ echo $view->render();
+ }
+
+ protected function getOverviewView()
+ {
+ $view = Piwik_View::factory('overview');
+ $this->setGeneralVariablesView($view);
+
+ $view->graphEvolution = $this->getEvolutionGraph(true, array('nb_conversions'));
+ $view->nameGraphEvolution = 'GoalsgetEvolutionGraph';
+
+ // sparkline for the historical data of the above values
+ $view->urlSparklineConversions = $this->getUrlSparkline('getEvolutionGraph', array('columns' => array('nb_conversions'), 'idGoal' => ''));
+ $view->urlSparklineConversionRate = $this->getUrlSparkline('getEvolutionGraph', array('columns' => array('conversion_rate'), 'idGoal' => ''));
+ $view->urlSparklineRevenue = $this->getUrlSparkline('getEvolutionGraph', array('columns' => array('revenue'), 'idGoal' => ''));
+
+ // Pass empty idGoal will return Goal overview
+ $request = new Piwik_API_Request("method=Goals.get&format=original&idGoal=");
+ $datatable = $request->process();
+ $dataRow = $datatable->getFirstRow();
+ $view->nb_conversions = $dataRow->getColumn('nb_conversions');
+ $view->nb_visits_converted = $dataRow->getColumn('nb_visits_converted');
+ $view->conversion_rate = $this->formatConversionRate($dataRow->getColumn('conversion_rate'));
+ $view->revenue = $dataRow->getColumn('revenue');
+
+ $goalMetrics = array();
+ foreach ($this->goals as $idGoal => $goal) {
+ $goalMetrics[$idGoal] = $this->getMetricsForGoal($idGoal);
+ $goalMetrics[$idGoal]['name'] = $goal['name'];
+ $goalMetrics[$idGoal]['goalAllowMultipleConversionsPerVisit'] = $goal['allow_multiple'];
+ }
+
+ $view->goalMetrics = $goalMetrics;
+ $view->goals = $this->goals;
+ $view->goalReportsByDimension = $this->getGoalReportsByDimensionTable(
+ $view->nb_conversions, $ecommerce = false, !empty($view->cart_nb_conversions));
+ return $view;
+ }
+
+ public function getLastNbConversionsGraph($fetch = false)
+ {
+ $view = $this->getLastUnitGraph($this->pluginName, __FUNCTION__, 'Goals.getConversions');
+ return $this->renderView($view, $fetch);
+ }
+
+ public function getLastConversionRateGraph($fetch = false)
+ {
+ $view = $this->getLastUnitGraph($this->pluginName, __FUNCTION__, 'Goals.getConversionRate');
+ return $this->renderView($view, $fetch);
+ }
+
+ public function getLastRevenueGraph($fetch = false)
+ {
+ $view = $this->getLastUnitGraph($this->pluginName, __FUNCTION__, 'Goals.getRevenue');
+ return $this->renderView($view, $fetch);
+ }
+
+ public function addNewGoal()
+ {
+ $view = Piwik_View::factory('add_new_goal');
+ $this->setGeneralVariablesView($view);
+ $view->userCanEditGoals = Piwik::isUserHasAdminAccess($this->idSite);
+ $view->onlyShowAddNewGoal = true;
+ echo $view->render();
+ }
+
+ public function getEvolutionGraph($fetch = false, array $columns = array(), $idGoal = false)
+ {
+ if (empty($columns)) {
+ $columns = Piwik_Common::getRequestVar('columns');
+ $columns = Piwik::getArrayFromApiParameter($columns);
+ }
+
+ $columns = !is_array($columns) ? array($columns) : $columns;
+
+ if (empty($idGoal)) {
+ $idGoal = Piwik_Common::getRequestVar('idGoal', false, 'string');
+ }
+ $view = $this->getLastUnitGraph($this->pluginName, __FUNCTION__, 'Goals.get');
+ $view->setParametersToModify(array('idGoal' => $idGoal));
+
+ $nameToLabel = $this->goalColumnNameToLabel;
+ if ($idGoal == Piwik_Archive::LABEL_ECOMMERCE_ORDER) {
+ $nameToLabel['nb_conversions'] = 'General_EcommerceOrders';
+ } elseif ($idGoal == Piwik_Archive::LABEL_ECOMMERCE_CART) {
+ $nameToLabel['nb_conversions'] = Piwik_Translate('General_VisitsWith', Piwik_Translate('Goals_AbandonedCart'));
+ $nameToLabel['conversion_rate'] = $nameToLabel['nb_conversions'];
+ $nameToLabel['revenue'] = Piwik_Translate('Goals_LeftInCart', Piwik_Translate('Goals_ColumnRevenue'));
+ $nameToLabel['items'] = Piwik_Translate('Goals_LeftInCart', Piwik_Translate('Goals_Products'));
+ }
+
+ $selectableColumns = array('nb_conversions', 'conversion_rate', 'revenue');
+ if ($this->site->isEcommerceEnabled()) {
+ $selectableColumns[] = 'items';
+ $selectableColumns[] = 'avg_order_revenue';
+ }
+
+ foreach (array_merge($columns, $selectableColumns) as $columnName) {
+ $columnTranslation = '';
+ // find the right translation for this column, eg. find 'revenue' if column is Goal_1_revenue
+ foreach ($nameToLabel as $metric => $metricTranslation) {
+ if (strpos($columnName, $metric) !== false) {
+ $columnTranslation = Piwik_Translate($metricTranslation);
+ break;
+ }
+ }
+
+ if (!empty($idGoal) && isset($this->goals[$idGoal])) {
+ $goalName = $this->goals[$idGoal]['name'];
+ $columnTranslation = "$columnTranslation (" . Piwik_Translate('Goals_GoalX', "$goalName") . ")";
+ }
+ $view->setColumnTranslation($columnName, $columnTranslation);
+ }
+ $view->setColumnsToDisplay($columns);
+ $view->setSelectableColumns($selectableColumns);
+
+ $langString = $idGoal ? 'Goals_SingleGoalOverviewDocumentation' : 'Goals_GoalsOverviewDocumentation';
+ $view->setReportDocumentation(Piwik_Translate($langString, '<br />'));
+
+ return $this->renderView($view, $fetch);
+ }
+
+
+ protected function getTopDimensions($idGoal)
+ {
+ $columnNbConversions = 'goal_' . $idGoal . '_nb_conversions';
+ $columnConversionRate = 'goal_' . $idGoal . '_conversion_rate';
+
+ $topDimensionsToLoad = array();
+
+ if (Piwik_PluginsManager::getInstance()->isPluginActivated('UserCountry')) {
+ $topDimensionsToLoad += array(
+ 'country' => 'UserCountry.getCountry',
+ );
+ }
+
+ $keywordNotDefinedString = '';
+ if (Piwik_PluginsManager::getInstance()->isPluginActivated('Referers')) {
+ $keywordNotDefinedString = Piwik_Referers::getKeywordNotDefinedString();
+ $topDimensionsToLoad += array(
+ 'keyword' => 'Referers.getKeywords',
+ 'website' => 'Referers.getWebsites',
+ );
+ }
+ $topDimensions = array();
+ foreach ($topDimensionsToLoad as $dimensionName => $apiMethod) {
+ $request = new Piwik_API_Request("method=$apiMethod
&format=original
&filter_update_columns_when_show_all_goals=1
- &idGoal=". Piwik_DataTable_Filter_AddColumnsProcessedMetricsGoal::GOALS_FULL_TABLE ."
+ &idGoal=" . Piwik_DataTable_Filter_AddColumnsProcessedMetricsGoal::GOALS_FULL_TABLE . "
&filter_sort_order=desc
- &filter_sort_column=$columnNbConversions".
- // select a couple more in case some are not valid (ie. conversions==0 or they are "Keyword not defined")
- "&filter_limit=". ( self::COUNT_TOP_ROWS_TO_DISPLAY + 2) );
- $datatable = $request->process();
- $topDimension = array();
- $count = 0;
- foreach($datatable->getRows() as $row)
- {
- $conversions = $row->getColumn($columnNbConversions);
- if($conversions > 0
- && $count < self::COUNT_TOP_ROWS_TO_DISPLAY
-
- // Don't put the "Keyword not defined" in the best segment since it's irritating
- && !($dimensionName == 'keyword'
- && $row->getColumn('label') == $keywordNotDefinedString)
- )
- {
- $topDimension[] = array (
- 'name' => $row->getColumn('label'),
- 'nb_conversions' => $conversions,
- 'conversion_rate' => $this->formatConversionRate($row->getColumn($columnConversionRate)),
- 'metadata' => $row->getMetadata(),
- );
- $count++;
- }
- }
- $topDimensions[$dimensionName] = $topDimension;
- }
- return $topDimensions;
- }
-
- protected function getMetricsForGoal($idGoal)
- {
- $request = new Piwik_API_Request("method=Goals.get&format=original&idGoal=$idGoal");
- $datatable = $request->process();
- $dataRow = $datatable->getFirstRow();
- $nbConversions = $dataRow->getColumn('nb_conversions');
- $nbVisitsConverted = $dataRow->getColumn('nb_visits_converted');
- // Backward compatibilty before 1.3, this value was not processed
- if(empty($nbVisitsConverted))
- {
- $nbVisitsConverted = $nbConversions;
- }
- $revenue = $dataRow->getColumn('revenue');
- $return = array (
- 'id' => $idGoal,
- 'nb_conversions' => (int)$nbConversions,
- 'nb_visits_converted' => (int)$nbVisitsConverted,
- 'conversion_rate' => $this->formatConversionRate($dataRow->getColumn('conversion_rate')),
- 'revenue' => $revenue ? $revenue : 0,
- 'urlSparklineConversions' => $this->getUrlSparkline('getEvolutionGraph', array('columns' => array('nb_conversions'), 'idGoal' => $idGoal)),
- 'urlSparklineConversionRate' => $this->getUrlSparkline('getEvolutionGraph', array('columns' => array('conversion_rate'), 'idGoal' => $idGoal)),
- 'urlSparklineRevenue' => $this->getUrlSparkline('getEvolutionGraph', array('columns' => array('revenue'), 'idGoal' => $idGoal)),
- );
- if($idGoal == Piwik_Archive::LABEL_ECOMMERCE_ORDER)
- {
- $items = $dataRow->getColumn('items');
- $aov = $dataRow->getColumn('avg_order_revenue');
- $return = array_merge($return, array(
- 'revenue_subtotal' => $dataRow->getColumn('revenue_subtotal'),
- 'revenue_tax' => $dataRow->getColumn('revenue_tax'),
- 'revenue_shipping' => $dataRow->getColumn('revenue_shipping'),
- 'revenue_discount' => $dataRow->getColumn('revenue_discount'),
-
- 'items' => $items ? $items : 0,
- 'avg_order_revenue' => $aov ? $aov : 0,
- 'urlSparklinePurchasedProducts' => $this->getUrlSparkline('getEvolutionGraph', array('columns' => array('items'), 'idGoal' => $idGoal)),
- 'urlSparklineAverageOrderValue' => $this->getUrlSparkline('getEvolutionGraph', array('columns' => array('avg_order_revenue'), 'idGoal' => $idGoal)),
- ));
- }
- return $return;
- }
-
- /**
- * Gets the 'visits to conversion' report using the requested view type.
- */
- public function getVisitsUntilConversion( $fetch = false )
- {
- $view = Piwik_ViewDataTable::factory();
- $view->init($this->pluginName, __FUNCTION__, 'Goals.getVisitsUntilConversion', 'getVisitsUntilConversion');
- $view->disableSearchBox();
- $view->disableExcludeLowPopulation();
- $view->disableSubTableWhenShowGoals();
- $view->disableShowAllColumns();
- $view->setColumnsToDisplay( array('label','nb_conversions') );
- $view->setSortedColumn('label', 'asc');
- $view->setColumnTranslation('label', Piwik_Translate('Goals_VisitsUntilConv'));
- $view->setColumnTranslation('nb_conversions', Piwik_Translate('Goals_ColumnConversions'));
- $view->setLimit(count(Piwik_Goals::$visitCountRanges));
- $view->disableOffsetInformationAndPaginationControls();
- $view->disableShowAllViewsIcons();
- return $this->renderView($view, $fetch);
- }
-
- /**
- * Gets the 'days to conversion' report using the requested view type.
- */
- public function getDaysToConversion( $fetch = false )
- {
- $view = Piwik_ViewDataTable::factory();
- $view->init($this->pluginName, __FUNCTION__, 'Goals.getDaysToConversion', 'getDaysToConversion');
- $view->disableSearchBox();
- $view->disableExcludeLowPopulation();
- $view->disableSubTableWhenShowGoals();
- $view->disableShowAllColumns();
- $view->setColumnsToDisplay( array('label','nb_conversions') );
- $view->setSortedColumn('label', 'asc');
- $view->setColumnTranslation('label', Piwik_Translate('Goals_DaysToConv'));
- $view->setColumnTranslation('nb_conversions', Piwik_Translate('Goals_ColumnConversions'));
- $view->disableShowAllViewsIcons();
- $view->setLimit(count(Piwik_Goals::$daysToConvRanges));
- $view->disableOffsetInformationAndPaginationControls();
- return $this->renderView($view, $fetch);
- }
-
- /**
- * Utility function that returns HTML that displays Goal information for reports. This
- * is the HTML that is at the bottom of every goals page.
- *
- * @param int $conversions The number of conversions for this goal (or all goals
- * in case of the overview).
- * @param bool $ecommerce Whether to show ecommerce reports or not.
- * @param bool $cartNbConversions Whether there are cart conversions or not for this
- * goal.
- */
- private function getGoalReportsByDimensionTable( $conversions, $ecommerce = false, $cartNbConversions = false )
- {
- $preloadAbandonedCart = $cartNbConversions !== false && $conversions == 0;
-
- $goalReportsByDimension = new Piwik_View_ReportsByDimension();
-
- // add ecommerce reports
- $ecommerceCustomParams = array();
- if ($ecommerce)
- {
- if ($preloadAbandonedCart)
- {
- $ecommerceCustomParams['viewDataTable'] = 'ecommerceAbandonedCart';
- $ecommerceCustomParams['filterEcommerce'] = 2;
- }
-
- $goalReportsByDimension->addReport(
- 'Goals_EcommerceReports', 'Goals_ProductSKU', 'Goals.getItemsSku', $ecommerceCustomParams);
- $goalReportsByDimension->addReport(
- 'Goals_EcommerceReports', 'Goals_ProductName', 'Goals.getItemsName', $ecommerceCustomParams);
- $goalReportsByDimension->addReport(
- 'Goals_EcommerceReports', 'Goals_ProductCategory', 'Goals.getItemsCategory', $ecommerceCustomParams);
- $goalReportsByDimension->addReport(
- 'Goals_EcommerceReports', 'Goals_EcommerceLog', 'Goals.getEcommerceLog', $ecommerceCustomParams);
- }
-
- if ($conversions > 0)
- {
- // for non-Goals reports, we show the goals table
- $customParams = $ecommerceCustomParams
- + array('viewDataTable' => 'tableGoals', 'documentationForGoalsPage' => '1');
-
- if (Piwik_Common::getRequestVar('idGoal', '') === '') // if no idGoal, use 0 for overview
- {
- $customParams['idGoal'] = '0'; // NOTE: Must be string! Otherwise Piwik_View_HtmlTable_Goals fails.
- }
-
- $allReports = Piwik_Goals::getReportsWithGoalMetrics();
- foreach ($allReports as $category => $reports)
- {
- $categoryText = Piwik_Translate('Goals_ViewGoalsBy', $category);
- foreach ($reports as $report)
- {
- $goalReportsByDimension->addReport(
- $categoryText, $report['name'], $report['module'].'.'.$report['action'], $customParams);
- }
- }
- }
-
- return $goalReportsByDimension->render();
- }
+ &filter_sort_column=$columnNbConversions" .
+ // select a couple more in case some are not valid (ie. conversions==0 or they are "Keyword not defined")
+ "&filter_limit=" . (self::COUNT_TOP_ROWS_TO_DISPLAY + 2));
+ $datatable = $request->process();
+ $topDimension = array();
+ $count = 0;
+ foreach ($datatable->getRows() as $row) {
+ $conversions = $row->getColumn($columnNbConversions);
+ if ($conversions > 0
+ && $count < self::COUNT_TOP_ROWS_TO_DISPLAY
+
+ // Don't put the "Keyword not defined" in the best segment since it's irritating
+ && !($dimensionName == 'keyword'
+ && $row->getColumn('label') == $keywordNotDefinedString)
+ ) {
+ $topDimension[] = array(
+ 'name' => $row->getColumn('label'),
+ 'nb_conversions' => $conversions,
+ 'conversion_rate' => $this->formatConversionRate($row->getColumn($columnConversionRate)),
+ 'metadata' => $row->getMetadata(),
+ );
+ $count++;
+ }
+ }
+ $topDimensions[$dimensionName] = $topDimension;
+ }
+ return $topDimensions;
+ }
+
+ protected function getMetricsForGoal($idGoal)
+ {
+ $request = new Piwik_API_Request("method=Goals.get&format=original&idGoal=$idGoal");
+ $datatable = $request->process();
+ $dataRow = $datatable->getFirstRow();
+ $nbConversions = $dataRow->getColumn('nb_conversions');
+ $nbVisitsConverted = $dataRow->getColumn('nb_visits_converted');
+ // Backward compatibilty before 1.3, this value was not processed
+ if (empty($nbVisitsConverted)) {
+ $nbVisitsConverted = $nbConversions;
+ }
+ $revenue = $dataRow->getColumn('revenue');
+ $return = array(
+ 'id' => $idGoal,
+ 'nb_conversions' => (int)$nbConversions,
+ 'nb_visits_converted' => (int)$nbVisitsConverted,
+ 'conversion_rate' => $this->formatConversionRate($dataRow->getColumn('conversion_rate')),
+ 'revenue' => $revenue ? $revenue : 0,
+ 'urlSparklineConversions' => $this->getUrlSparkline('getEvolutionGraph', array('columns' => array('nb_conversions'), 'idGoal' => $idGoal)),
+ 'urlSparklineConversionRate' => $this->getUrlSparkline('getEvolutionGraph', array('columns' => array('conversion_rate'), 'idGoal' => $idGoal)),
+ 'urlSparklineRevenue' => $this->getUrlSparkline('getEvolutionGraph', array('columns' => array('revenue'), 'idGoal' => $idGoal)),
+ );
+ if ($idGoal == Piwik_Archive::LABEL_ECOMMERCE_ORDER) {
+ $items = $dataRow->getColumn('items');
+ $aov = $dataRow->getColumn('avg_order_revenue');
+ $return = array_merge($return, array(
+ 'revenue_subtotal' => $dataRow->getColumn('revenue_subtotal'),
+ 'revenue_tax' => $dataRow->getColumn('revenue_tax'),
+ 'revenue_shipping' => $dataRow->getColumn('revenue_shipping'),
+ 'revenue_discount' => $dataRow->getColumn('revenue_discount'),
+
+ 'items' => $items ? $items : 0,
+ 'avg_order_revenue' => $aov ? $aov : 0,
+ 'urlSparklinePurchasedProducts' => $this->getUrlSparkline('getEvolutionGraph', array('columns' => array('items'), 'idGoal' => $idGoal)),
+ 'urlSparklineAverageOrderValue' => $this->getUrlSparkline('getEvolutionGraph', array('columns' => array('avg_order_revenue'), 'idGoal' => $idGoal)),
+ ));
+ }
+ return $return;
+ }
+
+ /**
+ * Gets the 'visits to conversion' report using the requested view type.
+ */
+ public function getVisitsUntilConversion($fetch = false)
+ {
+ $view = Piwik_ViewDataTable::factory();
+ $view->init($this->pluginName, __FUNCTION__, 'Goals.getVisitsUntilConversion', 'getVisitsUntilConversion');
+ $view->disableSearchBox();
+ $view->disableExcludeLowPopulation();
+ $view->disableSubTableWhenShowGoals();
+ $view->disableShowAllColumns();
+ $view->setColumnsToDisplay(array('label', 'nb_conversions'));
+ $view->setSortedColumn('label', 'asc');
+ $view->setColumnTranslation('label', Piwik_Translate('Goals_VisitsUntilConv'));
+ $view->setColumnTranslation('nb_conversions', Piwik_Translate('Goals_ColumnConversions'));
+ $view->setLimit(count(Piwik_Goals::$visitCountRanges));
+ $view->disableOffsetInformationAndPaginationControls();
+ $view->disableShowAllViewsIcons();
+ return $this->renderView($view, $fetch);
+ }
+
+ /**
+ * Gets the 'days to conversion' report using the requested view type.
+ */
+ public function getDaysToConversion($fetch = false)
+ {
+ $view = Piwik_ViewDataTable::factory();
+ $view->init($this->pluginName, __FUNCTION__, 'Goals.getDaysToConversion', 'getDaysToConversion');
+ $view->disableSearchBox();
+ $view->disableExcludeLowPopulation();
+ $view->disableSubTableWhenShowGoals();
+ $view->disableShowAllColumns();
+ $view->setColumnsToDisplay(array('label', 'nb_conversions'));
+ $view->setSortedColumn('label', 'asc');
+ $view->setColumnTranslation('label', Piwik_Translate('Goals_DaysToConv'));
+ $view->setColumnTranslation('nb_conversions', Piwik_Translate('Goals_ColumnConversions'));
+ $view->disableShowAllViewsIcons();
+ $view->setLimit(count(Piwik_Goals::$daysToConvRanges));
+ $view->disableOffsetInformationAndPaginationControls();
+ return $this->renderView($view, $fetch);
+ }
+
+ /**
+ * Utility function that returns HTML that displays Goal information for reports. This
+ * is the HTML that is at the bottom of every goals page.
+ *
+ * @param int $conversions The number of conversions for this goal (or all goals
+ * in case of the overview).
+ * @param bool $ecommerce Whether to show ecommerce reports or not.
+ * @param bool $cartNbConversions Whether there are cart conversions or not for this
+ * goal.
+ */
+ private function getGoalReportsByDimensionTable($conversions, $ecommerce = false, $cartNbConversions = false)
+ {
+ $preloadAbandonedCart = $cartNbConversions !== false && $conversions == 0;
+
+ $goalReportsByDimension = new Piwik_View_ReportsByDimension();
+
+ // add ecommerce reports
+ $ecommerceCustomParams = array();
+ if ($ecommerce) {
+ if ($preloadAbandonedCart) {
+ $ecommerceCustomParams['viewDataTable'] = 'ecommerceAbandonedCart';
+ $ecommerceCustomParams['filterEcommerce'] = 2;
+ }
+
+ $goalReportsByDimension->addReport(
+ 'Goals_EcommerceReports', 'Goals_ProductSKU', 'Goals.getItemsSku', $ecommerceCustomParams);
+ $goalReportsByDimension->addReport(
+ 'Goals_EcommerceReports', 'Goals_ProductName', 'Goals.getItemsName', $ecommerceCustomParams);
+ $goalReportsByDimension->addReport(
+ 'Goals_EcommerceReports', 'Goals_ProductCategory', 'Goals.getItemsCategory', $ecommerceCustomParams);
+ $goalReportsByDimension->addReport(
+ 'Goals_EcommerceReports', 'Goals_EcommerceLog', 'Goals.getEcommerceLog', $ecommerceCustomParams);
+ }
+
+ if ($conversions > 0) {
+ // for non-Goals reports, we show the goals table
+ $customParams = $ecommerceCustomParams
+ + array('viewDataTable' => 'tableGoals', 'documentationForGoalsPage' => '1');
+
+ if (Piwik_Common::getRequestVar('idGoal', '') === '') // if no idGoal, use 0 for overview
+ {
+ $customParams['idGoal'] = '0'; // NOTE: Must be string! Otherwise Piwik_View_HtmlTable_Goals fails.
+ }
+
+ $allReports = Piwik_Goals::getReportsWithGoalMetrics();
+ foreach ($allReports as $category => $reports) {
+ $categoryText = Piwik_Translate('Goals_ViewGoalsBy', $category);
+ foreach ($reports as $report) {
+ $goalReportsByDimension->addReport(
+ $categoryText, $report['name'], $report['module'] . '.' . $report['action'], $customParams);
+ }
+ }
+ }
+
+ return $goalReportsByDimension->render();
+ }
}
// Used so that the template knows which datatable is being currently viewed
-class Piwik_ViewDataTable_HtmlTable_EcommerceOrder extends Piwik_ViewDataTable_HtmlTable {
- protected function getViewDataTableId()
- {
- return Piwik_Archive::LABEL_ECOMMERCE_ORDER;
- }
+class Piwik_ViewDataTable_HtmlTable_EcommerceOrder extends Piwik_ViewDataTable_HtmlTable
+{
+ protected function getViewDataTableId()
+ {
+ return Piwik_Archive::LABEL_ECOMMERCE_ORDER;
+ }
}
-class Piwik_ViewDataTable_HtmlTable_EcommerceAbandonedCart extends Piwik_ViewDataTable_HtmlTable {
- protected function getViewDataTableId()
- {
- return Piwik_Archive::LABEL_ECOMMERCE_CART;
- }
+
+class Piwik_ViewDataTable_HtmlTable_EcommerceAbandonedCart extends Piwik_ViewDataTable_HtmlTable
+{
+ protected function getViewDataTableId()
+ {
+ return Piwik_Archive::LABEL_ECOMMERCE_CART;
+ }
}
diff --git a/plugins/Goals/Goals.php b/plugins/Goals/Goals.php
index ee4af3a197..5e0fe62728 100644
--- a/plugins/Goals/Goals.php
+++ b/plugins/Goals/Goals.php
@@ -15,897 +15,855 @@
*/
class Piwik_Goals extends Piwik_Plugin
{
- const VISITS_UNTIL_RECORD_NAME = 'visits_until_conv';
- const DAYS_UNTIL_CONV_RECORD_NAME = 'days_until_conv';
-
- /**
- * This array stores the ranges to use when displaying the 'visits to conversion'
- * report.
- */
- public static $visitCountRanges = array(
- array(1, 1),
- array(2, 2),
- array(3, 3),
- array(4, 4),
- array(5, 5),
- array(6, 6),
- array(7, 7),
- array(8, 8),
- array(9, 14),
- array(15, 25),
- array(26, 50),
- array(51, 100),
- array(100)
- );
-
- /**
- * This array stores the ranges to use when displaying the 'days to conversion'
- * report.
- */
- public static $daysToConvRanges = array(
- array(0, 0),
- array(1, 1),
- array(2, 2),
- array(3, 3),
- array(4, 4),
- array(5, 5),
- array(6, 6),
- array(7, 7),
- array(8, 14),
- array(15, 30),
- array(31, 60),
- array(61, 120),
- array(121, 364),
- array(364)
- );
+ const VISITS_UNTIL_RECORD_NAME = 'visits_until_conv';
+ const DAYS_UNTIL_CONV_RECORD_NAME = 'days_until_conv';
+
+ /**
+ * This array stores the ranges to use when displaying the 'visits to conversion'
+ * report.
+ */
+ public static $visitCountRanges = array(
+ array(1, 1),
+ array(2, 2),
+ array(3, 3),
+ array(4, 4),
+ array(5, 5),
+ array(6, 6),
+ array(7, 7),
+ array(8, 8),
+ array(9, 14),
+ array(15, 25),
+ array(26, 50),
+ array(51, 100),
+ array(100)
+ );
+
+ /**
+ * This array stores the ranges to use when displaying the 'days to conversion'
+ * report.
+ */
+ public static $daysToConvRanges = array(
+ array(0, 0),
+ array(1, 1),
+ array(2, 2),
+ array(3, 3),
+ array(4, 4),
+ array(5, 5),
+ array(6, 6),
+ array(7, 7),
+ array(8, 14),
+ array(15, 30),
+ array(31, 60),
+ array(61, 120),
+ array(121, 364),
+ array(364)
+ );
public function getInformation()
- {
- $info = array(
- 'description' => Piwik_Translate('Goals_PluginDescription') . ' '. Piwik_Translate('SitesManager_PiwikOffersEcommerceAnalytics', array('<a href="http://piwik.org/docs/ecommerce-analytics/" target="_blank">','</a>')),
- 'author' => 'Piwik',
- 'author_homepage' => 'http://piwik.org/',
- 'version' => Piwik_Version::VERSION,
- 'TrackerPlugin' => true, // this plugin must be loaded during the stats logging
- );
- return $info;
- }
-
- function getListHooksRegistered()
- {
- $hooks = array(
- 'AssetManager.getJsFiles' => 'getJsFiles',
- 'AssetManager.getCssFiles' => 'getCssFiles',
- 'Common.fetchWebsiteAttributes' => 'fetchGoalsFromDb',
- 'ArchiveProcessing_Day.compute' => 'archiveDay',
- 'ArchiveProcessing_Period.compute' => 'archivePeriod',
- 'API.getReportMetadata.end' => 'getReportMetadata',
- 'API.getSegmentsMetadata' => 'getSegmentsMetadata',
- 'WidgetsList.add' => 'addWidgets',
- 'Menu.add' => 'addMenus',
- 'SitesManager.deleteSite' => 'deleteSiteGoals',
- 'Goals.getReportsWithGoalMetrics' => 'getActualReportsWithGoalMetrics',
- );
- return $hooks;
- }
-
- /**
- * Delete goals recorded for this site
- *
- * @param Piwik_Event_Notification $notification notification object
- */
- function deleteSiteGoals($notification)
- {
- $idSite = &$notification->getNotificationObject();
- Piwik_Query("DELETE FROM ".Piwik_Common::prefixTable('goal') . " WHERE idsite = ? ", array($idSite));
- }
-
- /**
- * Returns the Metadata for the Goals plugin API.
- * The API returns general Goal metrics: conv, conv rate and revenue globally
- * and for each goal.
- *
- * Also, this will update metadata of all other reports that have Goal segmentation
- *
- * @param Piwik_Event_Notification $notification notification object
- */
- public function getReportMetadata($notification)
- {
- $info = $notification->getNotificationInfo();
- $idSites = $info['idSites'];
- $reports = &$notification->getNotificationObject();
-
- // Processed in AddColumnsProcessedMetricsGoal
- // These metrics will also be available for some reports, for each goal
- // Example: Conversion rate for Goal 2 for the keyword 'piwik'
- $goalProcessedMetrics = array(
- 'revenue_per_visit' => Piwik_Translate('General_ColumnValuePerVisit'),
- );
-
- $goalMetrics = array(
- 'nb_conversions' => Piwik_Translate('Goals_ColumnConversions'),
- 'nb_visits_converted' => Piwik_Translate('General_ColumnVisitsWithConversions'),
- 'conversion_rate' => Piwik_Translate('General_ColumnConversionRate'),
- 'revenue' => Piwik_Translate('Goals_ColumnRevenue')
- );
-
- $conversionReportMetrics = array(
- 'nb_conversions' => Piwik_Translate('Goals_ColumnConversions')
- );
-
- // General Goal metrics: conversions, conv rate, revenue
- $goalsCategory = Piwik_Translate('Goals_Goals');
- $reports[] = array(
- 'category' => $goalsCategory,
- 'name' => Piwik_Translate('Goals_Goals'),
- 'module' => 'Goals',
- 'action' => 'get',
- 'metrics' => $goalMetrics,
- 'processedMetrics' => array(),
- 'order' => 1
- );
-
- // If only one website is selected, we add the Goal metrics
- if(count($idSites) == 1)
- {
- $idSite = reset($idSites);
- $goals = Piwik_Goals_API::getInstance()->getGoals($idSite);
-
- // Add overall visits to conversion report
- $reports[] = array(
- 'category' => $goalsCategory,
- 'name' => Piwik_Translate('Goals_VisitsUntilConv'),
- 'module' => 'Goals',
- 'action' => 'getVisitsUntilConversion',
- 'dimension' => Piwik_Translate('Goals_VisitsUntilConv'),
- 'constantRowsCount' => true,
- 'parameters' => array(),
- 'metrics' => $conversionReportMetrics,
- 'order' => 5
- );
-
- // Add overall days to conversion report
- $reports[] = array(
- 'category' => $goalsCategory,
- 'name' => Piwik_Translate('Goals_DaysToConv'),
- 'module' => 'Goals',
- 'action' => 'getDaysToConversion',
- 'dimension' => Piwik_Translate('Goals_DaysToConv'),
- 'constantRowsCount' => true,
- 'parameters' => array(),
- 'metrics' => $conversionReportMetrics,
- 'order' => 10
- );
-
- foreach($goals as $goal)
- {
- // Add the general Goal metrics: ie. total Goal conversions,
- // Goal conv rate or Goal total revenue.
- // This API call requires a custom parameter
- $reports[] = array(
- 'category' => $goalsCategory,
- 'name' => Piwik_Translate('Goals_GoalX', $goal['name']),
- 'module' => 'Goals',
- 'action' => 'get',
- 'parameters' => array('idGoal' => $goal['idgoal']),
- 'metrics' => $goalMetrics,
- 'processedMetrics' => false,
- 'order' => 50 + $goal['idgoal'] * 3
- );
-
- // Add visits to conversion report
- $reports[] = array(
- 'category' => $goalsCategory,
- 'name' => $goal['name'] . ' - ' . Piwik_Translate('Goals_VisitsUntilConv'),
- 'module' => 'Goals',
- 'action' => 'getVisitsUntilConversion',
- 'dimension' => Piwik_Translate('Goals_VisitsUntilConv'),
- 'constantRowsCount' => true,
- 'parameters' => array('idGoal' => $goal['idgoal']),
- 'metrics' => $conversionReportMetrics,
- 'order' => 51 + $goal['idgoal'] * 3
- );
-
- // Add days to conversion report
- $reports[] = array(
- 'category' => $goalsCategory,
- 'name' => $goal['name'] . ' - ' . Piwik_Translate('Goals_DaysToConv'),
- 'module' => 'Goals',
- 'action' => 'getDaysToConversion',
- 'dimension' => Piwik_Translate('Goals_DaysToConv'),
- 'constantRowsCount' => true,
- 'parameters' => array('idGoal' => $goal['idgoal']),
- 'metrics' => $conversionReportMetrics,
- 'order' => 52 + $goal['idgoal'] * 3
- );
- }
-
- $site = new Piwik_Site($idSite);
- if($site->isEcommerceEnabled())
- {
- $category = Piwik_Translate('Goals_Ecommerce');
- $ecommerceMetrics = array_merge($goalMetrics, array(
- 'revenue_subtotal' => Piwik_Translate('General_Subtotal'),
- 'revenue_tax' => Piwik_Translate('General_Tax'),
- 'revenue_shipping' => Piwik_Translate('General_Shipping'),
- 'revenue_discount' => Piwik_Translate('General_Discount'),
- 'items' => Piwik_Translate('General_PurchasedProducts'),
- 'avg_order_revenue' => Piwik_Translate('General_AverageOrderValue')
- ));
- $ecommerceMetrics['nb_conversions'] = Piwik_Translate('General_EcommerceOrders');
-
- // General Ecommerce metrics
- $reports[] = array(
- 'category' => $category,
- 'name' => Piwik_Translate('General_EcommerceOrders'),
- 'module' => 'Goals',
- 'action' => 'get',
- 'parameters' => array('idGoal' => Piwik_Archive::LABEL_ECOMMERCE_ORDER),
- 'metrics' => $ecommerceMetrics,
- 'processedMetrics' => false,
- 'order' => 10
- );
- $reports[] = array(
- 'category' => $category,
- 'name' => Piwik_Translate('General_EcommerceOrders') . ' - ' . Piwik_Translate('Goals_VisitsUntilConv'),
- 'module' => 'Goals',
- 'action' => 'getVisitsUntilConversion',
- 'dimension' => Piwik_Translate('Goals_VisitsUntilConv'),
- 'constantRowsCount' => true,
- 'metrics' => $conversionReportMetrics,
- 'parameters' => array('idGoal' => Piwik_Archive::LABEL_ECOMMERCE_ORDER),
- 'order' => 11
- );
- $reports[] = array(
- 'category' => $category,
- 'name' => Piwik_Translate('General_EcommerceOrders') . ' - ' . Piwik_Translate('Goals_DaysToConv'),
- 'module' => 'Goals',
- 'action' => 'getDaysToConversion',
- 'dimension' => Piwik_Translate('Goals_DaysToConv'),
- 'constantRowsCount' => true,
- 'metrics' => $conversionReportMetrics,
- 'parameters' => array('idGoal' => Piwik_Archive::LABEL_ECOMMERCE_ORDER),
- 'order' => 12
- );
-
- // Abandoned cart general metrics
- $abandonedCartMetrics = $goalMetrics;
- $abandonedCartMetrics['nb_conversions'] = Piwik_Translate('General_AbandonedCarts');
- $abandonedCartMetrics['revenue'] = Piwik_Translate('Goals_LeftInCart', Piwik_Translate('Goals_ColumnRevenue'));
- $abandonedCartMetrics['items'] = Piwik_Translate('Goals_LeftInCart', Piwik_Translate('Goals_Products'));
- unset($abandonedCartMetrics['nb_visits_converted']);
-
- // Abandoned Cart metrics
- $reports[] = array(
- 'category' => $category,
- 'name' => Piwik_Translate('General_AbandonedCarts'),
- 'module' => 'Goals',
- 'action' => 'get',
- 'parameters' => array('idGoal' => Piwik_Archive::LABEL_ECOMMERCE_CART),
- 'metrics' => $abandonedCartMetrics,
- 'processedMetrics' => false,
- 'order' => 15
- );
-
- $reports[] = array(
- 'category' => $category,
- 'name' => Piwik_Translate('General_AbandonedCarts') . ' - ' . Piwik_Translate('Goals_VisitsUntilConv'),
- 'module' => 'Goals',
- 'action' => 'getVisitsUntilConversion',
- 'dimension' => Piwik_Translate('Goals_VisitsUntilConv'),
- 'constantRowsCount' => true,
- 'metrics' => $conversionReportMetrics,
- 'parameters' => array('idGoal' => Piwik_Archive::LABEL_ECOMMERCE_CART),
- 'order' => 20
- );
- $reports[] = array(
- 'category' => $category,
- 'name' => Piwik_Translate('General_AbandonedCarts') . ' - ' . Piwik_Translate('Goals_DaysToConv'),
- 'module' => 'Goals',
- 'action' => 'getDaysToConversion',
- 'dimension' => Piwik_Translate('Goals_DaysToConv'),
- 'constantRowsCount' => true,
- 'metrics' => $conversionReportMetrics,
- 'parameters' => array('idGoal' => Piwik_Archive::LABEL_ECOMMERCE_CART),
- 'order' => 25
- );
-
- // Product reports metadata
- $productColumns = self::getProductReportColumns();
- foreach($this->ecommerceReports as $i => $ecommerceReport)
- {
- $reports[] = array(
- 'category' => $category,
- 'name' => Piwik_Translate($ecommerceReport[0]),
- 'module' => 'Goals',
- 'action' => $ecommerceReport[2],
- 'dimension' => Piwik_Translate($ecommerceReport[0]),
- 'metrics' => $productColumns,
- 'processedMetrics' => false,
- 'order' => 30 + $i
- );
- }
- }
- }
-
- unset($goalMetrics['nb_visits_converted']);
-
- /*
- * Add the metricsGoal and processedMetricsGoal entry
- * to all reports that have Goal segmentation
- */
- $reportsWithGoals = array();
- Piwik_PostEvent('Goals.getReportsWithGoalMetrics', $reportsWithGoals);
- foreach($reportsWithGoals as $reportWithGoals)
- {
- // Select this report from the API metadata array
- // and add the Goal metrics to it
- foreach($reports as &$apiReportToUpdate)
- {
- if($apiReportToUpdate['module'] == $reportWithGoals['module']
- && $apiReportToUpdate['action'] == $reportWithGoals['action'])
- {
- $apiReportToUpdate['metricsGoal'] = $goalMetrics;
- $apiReportToUpdate['processedMetricsGoal'] = $goalProcessedMetrics;
- break;
- }
- }
- }
-
- }
-
- /**
- * @param Piwik_Event_Notification $notification notification object
- */
- public function getSegmentsMetadata($notification)
- {
- $segments =& $notification->getNotificationObject();
- $segments[] = array(
- 'type' => 'dimension',
- 'category' => 'Visit',
- 'name' => 'General_VisitConvertedGoalId',
- 'segment' => 'visitConvertedGoalId',
- 'sqlSegment' => 'log_conversion.idgoal',
- 'acceptedValues' => '1, 2, 3, etc.',
+ {
+ $info = array(
+ 'description' => Piwik_Translate('Goals_PluginDescription') . ' ' . Piwik_Translate('SitesManager_PiwikOffersEcommerceAnalytics', array('<a href="http://piwik.org/docs/ecommerce-analytics/" target="_blank">', '</a>')),
+ 'author' => 'Piwik',
+ 'author_homepage' => 'http://piwik.org/',
+ 'version' => Piwik_Version::VERSION,
+ 'TrackerPlugin' => true, // this plugin must be loaded during the stats logging
+ );
+ return $info;
+ }
+
+ function getListHooksRegistered()
+ {
+ $hooks = array(
+ 'AssetManager.getJsFiles' => 'getJsFiles',
+ 'AssetManager.getCssFiles' => 'getCssFiles',
+ 'Common.fetchWebsiteAttributes' => 'fetchGoalsFromDb',
+ 'ArchiveProcessing_Day.compute' => 'archiveDay',
+ 'ArchiveProcessing_Period.compute' => 'archivePeriod',
+ 'API.getReportMetadata.end' => 'getReportMetadata',
+ 'API.getSegmentsMetadata' => 'getSegmentsMetadata',
+ 'WidgetsList.add' => 'addWidgets',
+ 'Menu.add' => 'addMenus',
+ 'SitesManager.deleteSite' => 'deleteSiteGoals',
+ 'Goals.getReportsWithGoalMetrics' => 'getActualReportsWithGoalMetrics',
+ );
+ return $hooks;
+ }
+
+ /**
+ * Delete goals recorded for this site
+ *
+ * @param Piwik_Event_Notification $notification notification object
+ */
+ function deleteSiteGoals($notification)
+ {
+ $idSite = & $notification->getNotificationObject();
+ Piwik_Query("DELETE FROM " . Piwik_Common::prefixTable('goal') . " WHERE idsite = ? ", array($idSite));
+ }
+
+ /**
+ * Returns the Metadata for the Goals plugin API.
+ * The API returns general Goal metrics: conv, conv rate and revenue globally
+ * and for each goal.
+ *
+ * Also, this will update metadata of all other reports that have Goal segmentation
+ *
+ * @param Piwik_Event_Notification $notification notification object
+ */
+ public function getReportMetadata($notification)
+ {
+ $info = $notification->getNotificationInfo();
+ $idSites = $info['idSites'];
+ $reports = & $notification->getNotificationObject();
+
+ // Processed in AddColumnsProcessedMetricsGoal
+ // These metrics will also be available for some reports, for each goal
+ // Example: Conversion rate for Goal 2 for the keyword 'piwik'
+ $goalProcessedMetrics = array(
+ 'revenue_per_visit' => Piwik_Translate('General_ColumnValuePerVisit'),
+ );
+
+ $goalMetrics = array(
+ 'nb_conversions' => Piwik_Translate('Goals_ColumnConversions'),
+ 'nb_visits_converted' => Piwik_Translate('General_ColumnVisitsWithConversions'),
+ 'conversion_rate' => Piwik_Translate('General_ColumnConversionRate'),
+ 'revenue' => Piwik_Translate('Goals_ColumnRevenue')
+ );
+
+ $conversionReportMetrics = array(
+ 'nb_conversions' => Piwik_Translate('Goals_ColumnConversions')
);
- }
-
- protected $ecommerceReports = array(
- array('Goals_ProductSKU', 'Goals', 'getItemsSku'),
- array('Goals_ProductName', 'Goals', 'getItemsName'),
- array('Goals_ProductCategory', 'Goals', 'getItemsCategory')
- );
-
- static public function getProductReportColumns()
- {
- return array(
- 'revenue' => Piwik_Translate('General_ProductRevenue'),
- 'quantity' => Piwik_Translate('General_Quantity'),
- 'orders' => Piwik_Translate('General_UniquePurchases'),
- 'avg_price' => Piwik_Translate('General_AveragePrice'),
- 'avg_quantity' => Piwik_Translate('General_AverageQuantity'),
- 'nb_visits' => Piwik_Translate('General_ColumnNbVisits'),
- 'conversion_rate' => Piwik_Translate('General_ProductConversionRate'),
- );
- }
-
- static public function getReportsWithGoalMetrics()
- {
- $dimensions = array();
- Piwik_PostEvent('Goals.getReportsWithGoalMetrics', $dimensions);
- $dimensionsByGroup = array();
- foreach($dimensions as $dimension)
- {
- $group = $dimension['category'];
- unset($dimension['category']);
- $dimensionsByGroup[$group][] = $dimension;
- }
- return $dimensionsByGroup;
- }
-
- /**
- * @param Piwik_Event_Notification $notification notification object
- */
- function getJsFiles( $notification )
- {
- $jsFiles = &$notification->getNotificationObject();
- $jsFiles[] = "plugins/Goals/templates/GoalForm.js";
- }
-
- /**
- * @param Piwik_Event_Notification $notification notification object
- */
- function getCssFiles( $notification )
- {
- $cssFiles = &$notification->getNotificationObject();
- $cssFiles[] = "plugins/Goals/templates/goals.css";
- }
-
- /**
- * @param Piwik_Event_Notification $notification notification object
- */
- function fetchGoalsFromDb($notification)
- {
- $idsite = $notification->getNotificationInfo();
-
- // add the 'goal' entry in the website array
- $array =& $notification->getNotificationObject();
- $array['goals'] = Piwik_Goals_API::getInstance()->getGoals($idsite);
- }
-
- function addWidgets()
- {
- $idSite = Piwik_Common::getRequestVar('idSite', null, 'int');
-
- // Ecommerce widgets
- $site = new Piwik_Site($idSite);
- if($site->isEcommerceEnabled())
- {
- Piwik_AddWidget('Goals_Ecommerce', 'Goals_EcommerceOverview', 'Goals', 'widgetGoalReport', array('idGoal' => Piwik_Archive::LABEL_ECOMMERCE_ORDER));
- Piwik_AddWidget('Goals_Ecommerce', 'Goals_EcommerceLog', 'Goals', 'getEcommerceLog');
- foreach($this->ecommerceReports as $widget)
- {
- Piwik_AddWidget('Goals_Ecommerce', $widget[0], $widget[1], $widget[2]);
- }
- }
-
- // Goals widgets
- Piwik_AddWidget('Goals_Goals', 'Goals_GoalsOverview', 'Goals', 'widgetGoalsOverview');
- $goals = Piwik_Goals_API::getInstance()->getGoals($idSite);
- if(count($goals) > 0)
- {
- foreach($goals as $goal)
- {
- Piwik_AddWidget('Goals_Goals', Piwik_Common::sanitizeInputValue($goal['name']), 'Goals', 'widgetGoalReport', array('idGoal' => $goal['idgoal']));
- }
- }
- }
-
- protected function getGoalCategoryName($idSite)
- {
- $site = new Piwik_Site($idSite);
- return $site->isEcommerceEnabled() ? 'Goals_EcommerceAndGoalsMenu' : 'Goals_Goals';
- }
-
-
- function addMenus()
- {
- $idSite = Piwik_Common::getRequestVar('idSite', null, 'int');
- $goals = Piwik_Goals_API::getInstance()->getGoals($idSite);
- $mainGoalMenu = $this->getGoalCategoryName($idSite);
- $site = new Piwik_Site($idSite);
- if(count($goals)==0)
- {
- Piwik_AddMenu($mainGoalMenu, '', array(
- 'module' => 'Goals',
- 'action' => ($site->isEcommerceEnabled() ? 'ecommerceReport' : 'addNewGoal'),
- 'idGoal' => ($site->isEcommerceEnabled() ? Piwik_Archive::LABEL_ECOMMERCE_ORDER : null)),
- true,
- 25);
- if($site->isEcommerceEnabled())
- {
- Piwik_AddMenu($mainGoalMenu, 'Goals_Ecommerce', array('module' => 'Goals', 'action' => 'ecommerceReport', 'idGoal' => Piwik_Archive::LABEL_ECOMMERCE_ORDER), true, 1);
- }
- Piwik_AddMenu($mainGoalMenu, 'Goals_AddNewGoal', array('module' => 'Goals', 'action' => 'addNewGoal'));
- }
- else
- {
- Piwik_AddMenu($mainGoalMenu, '', array(
- 'module' => 'Goals',
- 'action' => ($site->isEcommerceEnabled() ? 'ecommerceReport' : 'index'),
- 'idGoal' => ($site->isEcommerceEnabled() ? Piwik_Archive::LABEL_ECOMMERCE_ORDER : null)),
- true,
- 25);
-
- if($site->isEcommerceEnabled())
- {
- Piwik_AddMenu($mainGoalMenu, 'Goals_Ecommerce', array('module' => 'Goals', 'action' => 'ecommerceReport', 'idGoal' => Piwik_Archive::LABEL_ECOMMERCE_ORDER), true, 1);
- }
- Piwik_AddMenu($mainGoalMenu, 'Goals_GoalsOverview', array('module' => 'Goals', 'action' => 'index'), true, 2);
- foreach($goals as $goal)
- {
- Piwik_AddMenu($mainGoalMenu, str_replace('%', '%%', Piwik_TranslationWriter::clean($goal['name'])), array('module' => 'Goals', 'action' => 'goalReport', 'idGoal' => $goal['idgoal']));
- }
- }
- }
-
- /**
- * @param string $recordName 'nb_conversions'
- * @param int|bool $idGoal idGoal to return the metrics for, or false to return overall
- * @return string Archive record name
- */
- static public function getRecordName($recordName, $idGoal = false)
- {
- $idGoalStr = '';
- if($idGoal !== false)
- {
- $idGoalStr = $idGoal . "_";
- }
- return 'Goal_' . $idGoalStr . $recordName;
- }
-
- /**
- * Hooks on Period archiving.
- * Sums up Goal conversions stats, and processes overall conversion rate
- *
- * @param Piwik_Event_Notification $notification
- * @return void
- */
- function archivePeriod($notification )
- {
- /**
- * @var Piwik_ArchiveProcessing
- */
- $archiveProcessing = $notification->getNotificationObject();
-
- if(!$archiveProcessing->shouldProcessReportsForPlugin($this->getPluginName())) return;
-
- /*
- * Archive Ecommerce Items
- */
- if($this->shouldArchiveEcommerceItems($archiveProcessing))
- {
- $dataTableToSum = $this->dimensions;
- foreach($this->dimensions as $recordName)
- {
- $dataTableToSum[] = self::getItemRecordNameAbandonedCart($recordName);
- }
- $archiveProcessing->archiveDataTable($dataTableToSum);
- }
-
- /*
- * Archive General Goal metrics
- */
- $goalIdsToSum = Piwik_Tracker_GoalManager::getGoalIds($archiveProcessing->idsite);
-
- //Ecommerce
- $goalIdsToSum[] = Piwik_Tracker_GoalManager::IDGOAL_ORDER;
- $goalIdsToSum[] = Piwik_Tracker_GoalManager::IDGOAL_CART; //bug here if idgoal=1
- // Overall goal metrics
- $goalIdsToSum[] = false;
-
- $fieldsToSum = array();
- foreach($goalIdsToSum as $goalId)
- {
- $metricsToSum = Piwik_Goals::getGoalColumns($goalId);
- unset($metricsToSum[array_search('conversion_rate', $metricsToSum)]);
- foreach($metricsToSum as $metricName)
- {
- $fieldsToSum[] = self::getRecordName($metricName, $goalId);
- }
- }
- $records = $archiveProcessing->archiveNumericValuesSum($fieldsToSum);
-
- // also recording conversion_rate for each goal
- foreach($goalIdsToSum as $goalId)
- {
- $nb_conversions = $records[self::getRecordName('nb_visits_converted', $goalId)];
- $conversion_rate = $this->getConversionRate($nb_conversions, $archiveProcessing);
- $archiveProcessing->insertNumericRecord(self::getRecordName('conversion_rate', $goalId), $conversion_rate);
-
- // sum up the visits to conversion data table & the days to conversion data table
- $archiveProcessing->archiveDataTable(array(
- self::getRecordName(self::VISITS_UNTIL_RECORD_NAME, $goalId),
- self::getRecordName(self::DAYS_UNTIL_CONV_RECORD_NAME, $goalId)));
- }
-
- // sum up goal overview reports
- $archiveProcessing->archiveDataTable(array(
- self::getRecordName(self::VISITS_UNTIL_RECORD_NAME),
- self::getRecordName(self::DAYS_UNTIL_CONV_RECORD_NAME)));
- }
-
- static public function getGoalColumns($idGoal)
- {
- $columns = array(
- 'nb_conversions',
- 'nb_visits_converted',
- 'conversion_rate',
- 'revenue',
- );
- if($idGoal === false)
- {
- return $columns;
- }
- // Orders
- if($idGoal === Piwik_Tracker_GoalManager::IDGOAL_ORDER)
- {
- $columns = array_merge($columns, array(
- 'revenue_subtotal',
- 'revenue_tax',
- 'revenue_shipping',
- 'revenue_discount',
- ));
- }
- // Abandoned carts & orders
- if($idGoal <= Piwik_Tracker_GoalManager::IDGOAL_ORDER)
- {
- $columns[] = 'items';
- }
- return $columns;
- }
-
- /**
- * Hooks on the Daily archiving.
- * Will process Goal stats overall and for each Goal.
- * Also processes the New VS Returning visitors conversion stats.
- *
- * @param Piwik_Event_Notification $notification
- * @return void
- */
- function archiveDay( $notification )
- {
- /**
- * @var Piwik_ArchiveProcessing_Day
- */
- $archiveProcessing = $notification->getNotificationObject();
-
- if(!$archiveProcessing->shouldProcessReportsForPlugin($this->getPluginName())) return;
-
- $this->archiveGeneralGoalMetrics($archiveProcessing);
- $this->archiveEcommerceItems($archiveProcessing);
- }
-
- /**
- * @param Piwik_ArchiveProcessing_Day $archiveProcessing
- */
- function archiveGeneralGoalMetrics($archiveProcessing)
- {
- // extra aggregate selects for the visits to conversion report
- $visitToConvExtraCols = Piwik_ArchiveProcessing_Day::buildReduceByRangeSelect(
- 'visitor_count_visits', self::$visitCountRanges, 'log_conversion', 'vcv');
-
- // extra aggregate selects for the days to conversion report
- $daysToConvExtraCols = Piwik_ArchiveProcessing_Day::buildReduceByRangeSelect(
- 'visitor_days_since_first', self::$daysToConvRanges, 'log_conversion', 'vdsf');
-
- $query = $archiveProcessing->queryConversionsByDimension(
- array(), '', array_merge($visitToConvExtraCols, $daysToConvExtraCols));
-
- if($query === false) { return; }
-
- $goals = array();
- $visitsToConvReport = array();
- $daysToConvReport = array();
-
- // Get a standard empty goal row
- $overall = $archiveProcessing->getNewGoalRow( $idGoal = 1);
- while($row = $query->fetch() )
- {
- $idgoal = $row['idgoal'];
-
- if(!isset($goals[$idgoal]))
- {
- $goals[$idgoal] = $archiveProcessing->getNewGoalRow($idgoal);
-
- $visitsToConvReport[$idgoal] = new Piwik_DataTable();
- $daysToConvReport[$idgoal] = new Piwik_DataTable();
- }
- $archiveProcessing->updateGoalStats($row, $goals[$idgoal]);
-
- // We don't want to sum Abandoned cart metrics in the overall revenue/conversions/converted visits
- // since it is a "negative conversion"
- if($idgoal != Piwik_Tracker_GoalManager::IDGOAL_CART)
- {
- $archiveProcessing->updateGoalStats($row, $overall);
- }
-
- // map the goal + visit number of a visitor with the # of conversions that happened on that visit
- $table = $archiveProcessing->getSimpleDataTableFromRow($row, Piwik_Archive::INDEX_NB_CONVERSIONS, 'vcv');
- $visitsToConvReport[$idgoal]->addDataTable($table);
-
- // map the goal + day number of a visit with the # of conversion that happened on that day
- $table = $archiveProcessing->getSimpleDataTableFromRow($row, Piwik_Archive::INDEX_NB_CONVERSIONS, 'vdsf');
- $daysToConvReport[$idgoal]->addDataTable($table);
- }
-
- // these data tables hold reports for every goal of a site
- $visitsToConvOverview = new Piwik_DataTable();
- $daysToConvOverview = new Piwik_DataTable();
-
- // Stats by goal, for all visitors
- foreach($goals as $idgoal => $values)
- {
- foreach($values as $metricId => $value)
- {
- $metricName = Piwik_Archive::$mappingFromIdToNameGoal[$metricId];
- $recordName = self::getRecordName($metricName, $idgoal);
- $archiveProcessing->insertNumericRecord($recordName, $value);
- }
- $conversion_rate = $this->getConversionRate($values[Piwik_Archive::INDEX_GOAL_NB_VISITS_CONVERTED], $archiveProcessing);
- $recordName = self::getRecordName('conversion_rate', $idgoal);
- $archiveProcessing->insertNumericRecord($recordName, $conversion_rate);
-
- // if the goal is not a special goal (like ecommerce) add it to the overview report
- if ($idgoal !== Piwik_Tracker_GoalManager::IDGOAL_CART &&
- $idgoal !== Piwik_Tracker_GoalManager::IDGOAL_ORDER)
- {
- $visitsToConvOverview->addDataTable($visitsToConvReport[$idgoal]);
- $daysToConvOverview->addDataTable($daysToConvReport[$idgoal]);
- }
-
- // visit count until conversion stats
- $archiveProcessing->insertBlobRecord(
- self::getRecordName(self::VISITS_UNTIL_RECORD_NAME, $idgoal),
- $visitsToConvReport[$idgoal]->getSerialized());
-
- // day count until conversion stats
- $archiveProcessing->insertBlobRecord(
- self::getRecordName(self::DAYS_UNTIL_CONV_RECORD_NAME, $idgoal),
- $daysToConvReport[$idgoal]->getSerialized());
- }
-
- // archive overview reports
- $archiveProcessing->insertBlobRecord(
- self::getRecordName(self::VISITS_UNTIL_RECORD_NAME), $visitsToConvOverview->getSerialized());
- $archiveProcessing->insertBlobRecord(
- self::getRecordName(self::DAYS_UNTIL_CONV_RECORD_NAME), $daysToConvOverview->getSerialized());
-
- // Stats for all goals
- $totalAllGoals = array(
- self::getRecordName('conversion_rate') => $this->getConversionRate($archiveProcessing->getNumberOfVisitsConverted(), $archiveProcessing),
- self::getRecordName('nb_conversions') => $overall[Piwik_Archive::INDEX_GOAL_NB_CONVERSIONS],
- self::getRecordName('nb_visits_converted') => $archiveProcessing->getNumberOfVisitsConverted(),
- self::getRecordName('revenue') => $overall[Piwik_Archive::INDEX_GOAL_REVENUE],
- );
- foreach($totalAllGoals as $recordName => $value)
- {
- $archiveProcessing->insertNumericRecord($recordName, $value);
- }
- }
-
- protected $dimensions = array(
- 'idaction_sku' => 'Goals_ItemsSku',
- 'idaction_name' => 'Goals_ItemsName',
- 'idaction_category' => 'Goals_ItemsCategory'
- );
-
- protected function shouldArchiveEcommerceItems($archiveProcessing)
- {
- // Per item doesn't support segment
- // Also, when querying Goal metrics for visitorType==returning, we wouldnt want to trigger an extra request
- // event if it did support segment
- // (if this is implented, we should have shouldProcessReportsForPlugin() support partial archiving based on which metric is requested)
- if(!$archiveProcessing->getSegment()->isEmpty())
- {
- return false;
- }
- return true;
- }
-
- /**
- * @param Piwik_ArchiveProcessing_Day $archiveProcessing
- */
- function archiveEcommerceItems($archiveProcessing)
- {
- if(!$this->shouldArchiveEcommerceItems($archiveProcessing))
- {
- return false;
- }
- $items = array();
-
- $dimensionsToQuery = $this->dimensions;
- $dimensionsToQuery['idaction_category2'] = 'AdditionalCategory';
- $dimensionsToQuery['idaction_category3'] = 'AdditionalCategory';
- $dimensionsToQuery['idaction_category4'] = 'AdditionalCategory';
- $dimensionsToQuery['idaction_category5'] = 'AdditionalCategory';
-
- foreach($dimensionsToQuery as $dimension => $recordName)
- {
- $query = $archiveProcessing->queryEcommerceItems($dimension);
- if($query == false) { continue; }
-
- while($row = $query->fetch())
- {
- $label = $row['label'];
- $ecommerceType = $row['ecommerceType'];
-
- if(empty($label))
- {
- // idaction==0 case:
- // If we are querying any optional category, we do not include idaction=0
- // Otherwise we over-report in the Product Categories report
- if($recordName == 'AdditionalCategory') {
- continue;
- }
- // Product Name/Category not defined"
- if(class_exists('Piwik_CustomVariables')) {
- $label = Piwik_CustomVariables::LABEL_CUSTOM_VALUE_NOT_DEFINED;
- } else {
- $label = "Value not defined";
- }
- }
- // For carts, idorder = 0. To count abandoned carts, we must count visits with an abandoned cart
- if($ecommerceType == Piwik_Tracker_GoalManager::IDGOAL_CART)
- {
- $row[Piwik_Archive::INDEX_ECOMMERCE_ORDERS] = $row[Piwik_Archive::INDEX_NB_VISITS];
- }
- unset($row[Piwik_Archive::INDEX_NB_VISITS]);
- unset($row['label']);
- unset($row['ecommerceType']);
-
- $columnsToRound = array(
- Piwik_Archive::INDEX_ECOMMERCE_ITEM_REVENUE,
- Piwik_Archive::INDEX_ECOMMERCE_ITEM_QUANTITY,
- Piwik_Archive::INDEX_ECOMMERCE_ITEM_PRICE,
- Piwik_Archive::INDEX_ECOMMERCE_ITEM_PRICE_VIEWED,
- );
- foreach($columnsToRound as $column)
- {
- if(isset($row[$column])
- && $row[$column] == round($row[$column]))
- {
- $row[$column] = round($row[$column]);
- }
- }
- $items[$dimension][$ecommerceType][$label] = $row;
- }
- }
-
- foreach($this->dimensions as $dimension => $recordName)
- {
- foreach(array(Piwik_Tracker_GoalManager::IDGOAL_CART, Piwik_Tracker_GoalManager::IDGOAL_ORDER) as $ecommerceType)
- {
- if(!isset($items[$dimension][$ecommerceType]))
- {
- continue;
- }
- $recordNameInsert = $recordName;
- if($ecommerceType == Piwik_Tracker_GoalManager::IDGOAL_CART)
- {
- $recordNameInsert = self::getItemRecordNameAbandonedCart($recordName);
- }
- $table = $archiveProcessing->getDataTableFromArray($items[$dimension][$ecommerceType]);
-
- // For "category" report, we aggregate all 5 category queries into one datatable
- if($dimension == 'idaction_category')
- {
- foreach(array('idaction_category2','idaction_category3','idaction_category4','idaction_category5') as $categoryToSum)
- {
- if(!empty($items[$categoryToSum][$ecommerceType]))
- {
- $tableToSum = $archiveProcessing->getDataTableFromArray($items[$categoryToSum][$ecommerceType]);
- $table->addDataTable($tableToSum);
- }
- }
- }
- $archiveProcessing->insertBlobRecord($recordNameInsert, $table->getSerialized());
- }
- }
- }
-
- static public function getItemRecordNameAbandonedCart($recordName)
- {
- return $recordName . '_Cart';
- }
-
- function getConversionRate($count, $archiveProcessing)
- {
- $visits = $archiveProcessing->getNumberOfVisits();
- return round(100 * $count / $visits, Piwik_Tracker_GoalManager::REVENUE_PRECISION);
- }
-
- /**
- * This function executes when the 'Goals.getReportsWithGoalMetrics' event fires. It
- * adds the 'visits to conversion' report metadata to the list of goal reports so
- * this report will be displayed.
- *
- * @param Piwik_Event_Notification $notification notification object
- */
- function getActualReportsWithGoalMetrics( $notification )
- {
- $dimensions =& $notification->getNotificationObject();
- $dimensions = array_merge($dimensions, array(
- array( 'category' => Piwik_Translate('General_Visit'),
- 'name' => Piwik_Translate('Goals_VisitsUntilConv'),
- 'module' => 'Goals',
- 'action' => 'getVisitsUntilConversion'
- ),
- array( 'category' => Piwik_Translate('General_Visit'),
- 'name' => Piwik_Translate('Goals_DaysToConv'),
- 'module' => 'Goals',
- 'action' => 'getDaysToConversion'
- )
- ));
- }
+
+ // General Goal metrics: conversions, conv rate, revenue
+ $goalsCategory = Piwik_Translate('Goals_Goals');
+ $reports[] = array(
+ 'category' => $goalsCategory,
+ 'name' => Piwik_Translate('Goals_Goals'),
+ 'module' => 'Goals',
+ 'action' => 'get',
+ 'metrics' => $goalMetrics,
+ 'processedMetrics' => array(),
+ 'order' => 1
+ );
+
+ // If only one website is selected, we add the Goal metrics
+ if (count($idSites) == 1) {
+ $idSite = reset($idSites);
+ $goals = Piwik_Goals_API::getInstance()->getGoals($idSite);
+
+ // Add overall visits to conversion report
+ $reports[] = array(
+ 'category' => $goalsCategory,
+ 'name' => Piwik_Translate('Goals_VisitsUntilConv'),
+ 'module' => 'Goals',
+ 'action' => 'getVisitsUntilConversion',
+ 'dimension' => Piwik_Translate('Goals_VisitsUntilConv'),
+ 'constantRowsCount' => true,
+ 'parameters' => array(),
+ 'metrics' => $conversionReportMetrics,
+ 'order' => 5
+ );
+
+ // Add overall days to conversion report
+ $reports[] = array(
+ 'category' => $goalsCategory,
+ 'name' => Piwik_Translate('Goals_DaysToConv'),
+ 'module' => 'Goals',
+ 'action' => 'getDaysToConversion',
+ 'dimension' => Piwik_Translate('Goals_DaysToConv'),
+ 'constantRowsCount' => true,
+ 'parameters' => array(),
+ 'metrics' => $conversionReportMetrics,
+ 'order' => 10
+ );
+
+ foreach ($goals as $goal) {
+ // Add the general Goal metrics: ie. total Goal conversions,
+ // Goal conv rate or Goal total revenue.
+ // This API call requires a custom parameter
+ $reports[] = array(
+ 'category' => $goalsCategory,
+ 'name' => Piwik_Translate('Goals_GoalX', $goal['name']),
+ 'module' => 'Goals',
+ 'action' => 'get',
+ 'parameters' => array('idGoal' => $goal['idgoal']),
+ 'metrics' => $goalMetrics,
+ 'processedMetrics' => false,
+ 'order' => 50 + $goal['idgoal'] * 3
+ );
+
+ // Add visits to conversion report
+ $reports[] = array(
+ 'category' => $goalsCategory,
+ 'name' => $goal['name'] . ' - ' . Piwik_Translate('Goals_VisitsUntilConv'),
+ 'module' => 'Goals',
+ 'action' => 'getVisitsUntilConversion',
+ 'dimension' => Piwik_Translate('Goals_VisitsUntilConv'),
+ 'constantRowsCount' => true,
+ 'parameters' => array('idGoal' => $goal['idgoal']),
+ 'metrics' => $conversionReportMetrics,
+ 'order' => 51 + $goal['idgoal'] * 3
+ );
+
+ // Add days to conversion report
+ $reports[] = array(
+ 'category' => $goalsCategory,
+ 'name' => $goal['name'] . ' - ' . Piwik_Translate('Goals_DaysToConv'),
+ 'module' => 'Goals',
+ 'action' => 'getDaysToConversion',
+ 'dimension' => Piwik_Translate('Goals_DaysToConv'),
+ 'constantRowsCount' => true,
+ 'parameters' => array('idGoal' => $goal['idgoal']),
+ 'metrics' => $conversionReportMetrics,
+ 'order' => 52 + $goal['idgoal'] * 3
+ );
+ }
+
+ $site = new Piwik_Site($idSite);
+ if ($site->isEcommerceEnabled()) {
+ $category = Piwik_Translate('Goals_Ecommerce');
+ $ecommerceMetrics = array_merge($goalMetrics, array(
+ 'revenue_subtotal' => Piwik_Translate('General_Subtotal'),
+ 'revenue_tax' => Piwik_Translate('General_Tax'),
+ 'revenue_shipping' => Piwik_Translate('General_Shipping'),
+ 'revenue_discount' => Piwik_Translate('General_Discount'),
+ 'items' => Piwik_Translate('General_PurchasedProducts'),
+ 'avg_order_revenue' => Piwik_Translate('General_AverageOrderValue')
+ ));
+ $ecommerceMetrics['nb_conversions'] = Piwik_Translate('General_EcommerceOrders');
+
+ // General Ecommerce metrics
+ $reports[] = array(
+ 'category' => $category,
+ 'name' => Piwik_Translate('General_EcommerceOrders'),
+ 'module' => 'Goals',
+ 'action' => 'get',
+ 'parameters' => array('idGoal' => Piwik_Archive::LABEL_ECOMMERCE_ORDER),
+ 'metrics' => $ecommerceMetrics,
+ 'processedMetrics' => false,
+ 'order' => 10
+ );
+ $reports[] = array(
+ 'category' => $category,
+ 'name' => Piwik_Translate('General_EcommerceOrders') . ' - ' . Piwik_Translate('Goals_VisitsUntilConv'),
+ 'module' => 'Goals',
+ 'action' => 'getVisitsUntilConversion',
+ 'dimension' => Piwik_Translate('Goals_VisitsUntilConv'),
+ 'constantRowsCount' => true,
+ 'metrics' => $conversionReportMetrics,
+ 'parameters' => array('idGoal' => Piwik_Archive::LABEL_ECOMMERCE_ORDER),
+ 'order' => 11
+ );
+ $reports[] = array(
+ 'category' => $category,
+ 'name' => Piwik_Translate('General_EcommerceOrders') . ' - ' . Piwik_Translate('Goals_DaysToConv'),
+ 'module' => 'Goals',
+ 'action' => 'getDaysToConversion',
+ 'dimension' => Piwik_Translate('Goals_DaysToConv'),
+ 'constantRowsCount' => true,
+ 'metrics' => $conversionReportMetrics,
+ 'parameters' => array('idGoal' => Piwik_Archive::LABEL_ECOMMERCE_ORDER),
+ 'order' => 12
+ );
+
+ // Abandoned cart general metrics
+ $abandonedCartMetrics = $goalMetrics;
+ $abandonedCartMetrics['nb_conversions'] = Piwik_Translate('General_AbandonedCarts');
+ $abandonedCartMetrics['revenue'] = Piwik_Translate('Goals_LeftInCart', Piwik_Translate('Goals_ColumnRevenue'));
+ $abandonedCartMetrics['items'] = Piwik_Translate('Goals_LeftInCart', Piwik_Translate('Goals_Products'));
+ unset($abandonedCartMetrics['nb_visits_converted']);
+
+ // Abandoned Cart metrics
+ $reports[] = array(
+ 'category' => $category,
+ 'name' => Piwik_Translate('General_AbandonedCarts'),
+ 'module' => 'Goals',
+ 'action' => 'get',
+ 'parameters' => array('idGoal' => Piwik_Archive::LABEL_ECOMMERCE_CART),
+ 'metrics' => $abandonedCartMetrics,
+ 'processedMetrics' => false,
+ 'order' => 15
+ );
+
+ $reports[] = array(
+ 'category' => $category,
+ 'name' => Piwik_Translate('General_AbandonedCarts') . ' - ' . Piwik_Translate('Goals_VisitsUntilConv'),
+ 'module' => 'Goals',
+ 'action' => 'getVisitsUntilConversion',
+ 'dimension' => Piwik_Translate('Goals_VisitsUntilConv'),
+ 'constantRowsCount' => true,
+ 'metrics' => $conversionReportMetrics,
+ 'parameters' => array('idGoal' => Piwik_Archive::LABEL_ECOMMERCE_CART),
+ 'order' => 20
+ );
+ $reports[] = array(
+ 'category' => $category,
+ 'name' => Piwik_Translate('General_AbandonedCarts') . ' - ' . Piwik_Translate('Goals_DaysToConv'),
+ 'module' => 'Goals',
+ 'action' => 'getDaysToConversion',
+ 'dimension' => Piwik_Translate('Goals_DaysToConv'),
+ 'constantRowsCount' => true,
+ 'metrics' => $conversionReportMetrics,
+ 'parameters' => array('idGoal' => Piwik_Archive::LABEL_ECOMMERCE_CART),
+ 'order' => 25
+ );
+
+ // Product reports metadata
+ $productColumns = self::getProductReportColumns();
+ foreach ($this->ecommerceReports as $i => $ecommerceReport) {
+ $reports[] = array(
+ 'category' => $category,
+ 'name' => Piwik_Translate($ecommerceReport[0]),
+ 'module' => 'Goals',
+ 'action' => $ecommerceReport[2],
+ 'dimension' => Piwik_Translate($ecommerceReport[0]),
+ 'metrics' => $productColumns,
+ 'processedMetrics' => false,
+ 'order' => 30 + $i
+ );
+ }
+ }
+ }
+
+ unset($goalMetrics['nb_visits_converted']);
+
+ /*
+ * Add the metricsGoal and processedMetricsGoal entry
+ * to all reports that have Goal segmentation
+ */
+ $reportsWithGoals = array();
+ Piwik_PostEvent('Goals.getReportsWithGoalMetrics', $reportsWithGoals);
+ foreach ($reportsWithGoals as $reportWithGoals) {
+ // Select this report from the API metadata array
+ // and add the Goal metrics to it
+ foreach ($reports as &$apiReportToUpdate) {
+ if ($apiReportToUpdate['module'] == $reportWithGoals['module']
+ && $apiReportToUpdate['action'] == $reportWithGoals['action']
+ ) {
+ $apiReportToUpdate['metricsGoal'] = $goalMetrics;
+ $apiReportToUpdate['processedMetricsGoal'] = $goalProcessedMetrics;
+ break;
+ }
+ }
+ }
+
+ }
+
+ /**
+ * @param Piwik_Event_Notification $notification notification object
+ */
+ public function getSegmentsMetadata($notification)
+ {
+ $segments =& $notification->getNotificationObject();
+ $segments[] = array(
+ 'type' => 'dimension',
+ 'category' => 'Visit',
+ 'name' => 'General_VisitConvertedGoalId',
+ 'segment' => 'visitConvertedGoalId',
+ 'sqlSegment' => 'log_conversion.idgoal',
+ 'acceptedValues' => '1, 2, 3, etc.',
+ );
+ }
+
+ protected $ecommerceReports = array(
+ array('Goals_ProductSKU', 'Goals', 'getItemsSku'),
+ array('Goals_ProductName', 'Goals', 'getItemsName'),
+ array('Goals_ProductCategory', 'Goals', 'getItemsCategory')
+ );
+
+ static public function getProductReportColumns()
+ {
+ return array(
+ 'revenue' => Piwik_Translate('General_ProductRevenue'),
+ 'quantity' => Piwik_Translate('General_Quantity'),
+ 'orders' => Piwik_Translate('General_UniquePurchases'),
+ 'avg_price' => Piwik_Translate('General_AveragePrice'),
+ 'avg_quantity' => Piwik_Translate('General_AverageQuantity'),
+ 'nb_visits' => Piwik_Translate('General_ColumnNbVisits'),
+ 'conversion_rate' => Piwik_Translate('General_ProductConversionRate'),
+ );
+ }
+
+ static public function getReportsWithGoalMetrics()
+ {
+ $dimensions = array();
+ Piwik_PostEvent('Goals.getReportsWithGoalMetrics', $dimensions);
+ $dimensionsByGroup = array();
+ foreach ($dimensions as $dimension) {
+ $group = $dimension['category'];
+ unset($dimension['category']);
+ $dimensionsByGroup[$group][] = $dimension;
+ }
+ return $dimensionsByGroup;
+ }
+
+ /**
+ * @param Piwik_Event_Notification $notification notification object
+ */
+ function getJsFiles($notification)
+ {
+ $jsFiles = & $notification->getNotificationObject();
+ $jsFiles[] = "plugins/Goals/templates/GoalForm.js";
+ }
+
+ /**
+ * @param Piwik_Event_Notification $notification notification object
+ */
+ function getCssFiles($notification)
+ {
+ $cssFiles = & $notification->getNotificationObject();
+ $cssFiles[] = "plugins/Goals/templates/goals.css";
+ }
+
+ /**
+ * @param Piwik_Event_Notification $notification notification object
+ */
+ function fetchGoalsFromDb($notification)
+ {
+ $idsite = $notification->getNotificationInfo();
+
+ // add the 'goal' entry in the website array
+ $array =& $notification->getNotificationObject();
+ $array['goals'] = Piwik_Goals_API::getInstance()->getGoals($idsite);
+ }
+
+ function addWidgets()
+ {
+ $idSite = Piwik_Common::getRequestVar('idSite', null, 'int');
+
+ // Ecommerce widgets
+ $site = new Piwik_Site($idSite);
+ if ($site->isEcommerceEnabled()) {
+ Piwik_AddWidget('Goals_Ecommerce', 'Goals_EcommerceOverview', 'Goals', 'widgetGoalReport', array('idGoal' => Piwik_Archive::LABEL_ECOMMERCE_ORDER));
+ Piwik_AddWidget('Goals_Ecommerce', 'Goals_EcommerceLog', 'Goals', 'getEcommerceLog');
+ foreach ($this->ecommerceReports as $widget) {
+ Piwik_AddWidget('Goals_Ecommerce', $widget[0], $widget[1], $widget[2]);
+ }
+ }
+
+ // Goals widgets
+ Piwik_AddWidget('Goals_Goals', 'Goals_GoalsOverview', 'Goals', 'widgetGoalsOverview');
+ $goals = Piwik_Goals_API::getInstance()->getGoals($idSite);
+ if (count($goals) > 0) {
+ foreach ($goals as $goal) {
+ Piwik_AddWidget('Goals_Goals', Piwik_Common::sanitizeInputValue($goal['name']), 'Goals', 'widgetGoalReport', array('idGoal' => $goal['idgoal']));
+ }
+ }
+ }
+
+ protected function getGoalCategoryName($idSite)
+ {
+ $site = new Piwik_Site($idSite);
+ return $site->isEcommerceEnabled() ? 'Goals_EcommerceAndGoalsMenu' : 'Goals_Goals';
+ }
+
+
+ function addMenus()
+ {
+ $idSite = Piwik_Common::getRequestVar('idSite', null, 'int');
+ $goals = Piwik_Goals_API::getInstance()->getGoals($idSite);
+ $mainGoalMenu = $this->getGoalCategoryName($idSite);
+ $site = new Piwik_Site($idSite);
+ if (count($goals) == 0) {
+ Piwik_AddMenu($mainGoalMenu, '', array(
+ 'module' => 'Goals',
+ 'action' => ($site->isEcommerceEnabled() ? 'ecommerceReport' : 'addNewGoal'),
+ 'idGoal' => ($site->isEcommerceEnabled() ? Piwik_Archive::LABEL_ECOMMERCE_ORDER : null)),
+ true,
+ 25);
+ if ($site->isEcommerceEnabled()) {
+ Piwik_AddMenu($mainGoalMenu, 'Goals_Ecommerce', array('module' => 'Goals', 'action' => 'ecommerceReport', 'idGoal' => Piwik_Archive::LABEL_ECOMMERCE_ORDER), true, 1);
+ }
+ Piwik_AddMenu($mainGoalMenu, 'Goals_AddNewGoal', array('module' => 'Goals', 'action' => 'addNewGoal'));
+ } else {
+ Piwik_AddMenu($mainGoalMenu, '', array(
+ 'module' => 'Goals',
+ 'action' => ($site->isEcommerceEnabled() ? 'ecommerceReport' : 'index'),
+ 'idGoal' => ($site->isEcommerceEnabled() ? Piwik_Archive::LABEL_ECOMMERCE_ORDER : null)),
+ true,
+ 25);
+
+ if ($site->isEcommerceEnabled()) {
+ Piwik_AddMenu($mainGoalMenu, 'Goals_Ecommerce', array('module' => 'Goals', 'action' => 'ecommerceReport', 'idGoal' => Piwik_Archive::LABEL_ECOMMERCE_ORDER), true, 1);
+ }
+ Piwik_AddMenu($mainGoalMenu, 'Goals_GoalsOverview', array('module' => 'Goals', 'action' => 'index'), true, 2);
+ foreach ($goals as $goal) {
+ Piwik_AddMenu($mainGoalMenu, str_replace('%', '%%', Piwik_TranslationWriter::clean($goal['name'])), array('module' => 'Goals', 'action' => 'goalReport', 'idGoal' => $goal['idgoal']));
+ }
+ }
+ }
+
+ /**
+ * @param string $recordName 'nb_conversions'
+ * @param int|bool $idGoal idGoal to return the metrics for, or false to return overall
+ * @return string Archive record name
+ */
+ static public function getRecordName($recordName, $idGoal = false)
+ {
+ $idGoalStr = '';
+ if ($idGoal !== false) {
+ $idGoalStr = $idGoal . "_";
+ }
+ return 'Goal_' . $idGoalStr . $recordName;
+ }
+
+ /**
+ * Hooks on Period archiving.
+ * Sums up Goal conversions stats, and processes overall conversion rate
+ *
+ * @param Piwik_Event_Notification $notification
+ * @return void
+ */
+ function archivePeriod($notification)
+ {
+ /**
+ * @var Piwik_ArchiveProcessing
+ */
+ $archiveProcessing = $notification->getNotificationObject();
+
+ if (!$archiveProcessing->shouldProcessReportsForPlugin($this->getPluginName())) return;
+
+ /*
+ * Archive Ecommerce Items
+ */
+ if ($this->shouldArchiveEcommerceItems($archiveProcessing)) {
+ $dataTableToSum = $this->dimensions;
+ foreach ($this->dimensions as $recordName) {
+ $dataTableToSum[] = self::getItemRecordNameAbandonedCart($recordName);
+ }
+ $archiveProcessing->archiveDataTable($dataTableToSum);
+ }
+
+ /*
+ * Archive General Goal metrics
+ */
+ $goalIdsToSum = Piwik_Tracker_GoalManager::getGoalIds($archiveProcessing->idsite);
+
+ //Ecommerce
+ $goalIdsToSum[] = Piwik_Tracker_GoalManager::IDGOAL_ORDER;
+ $goalIdsToSum[] = Piwik_Tracker_GoalManager::IDGOAL_CART; //bug here if idgoal=1
+ // Overall goal metrics
+ $goalIdsToSum[] = false;
+
+ $fieldsToSum = array();
+ foreach ($goalIdsToSum as $goalId) {
+ $metricsToSum = Piwik_Goals::getGoalColumns($goalId);
+ unset($metricsToSum[array_search('conversion_rate', $metricsToSum)]);
+ foreach ($metricsToSum as $metricName) {
+ $fieldsToSum[] = self::getRecordName($metricName, $goalId);
+ }
+ }
+ $records = $archiveProcessing->archiveNumericValuesSum($fieldsToSum);
+
+ // also recording conversion_rate for each goal
+ foreach ($goalIdsToSum as $goalId) {
+ $nb_conversions = $records[self::getRecordName('nb_visits_converted', $goalId)];
+ $conversion_rate = $this->getConversionRate($nb_conversions, $archiveProcessing);
+ $archiveProcessing->insertNumericRecord(self::getRecordName('conversion_rate', $goalId), $conversion_rate);
+
+ // sum up the visits to conversion data table & the days to conversion data table
+ $archiveProcessing->archiveDataTable(array(
+ self::getRecordName(self::VISITS_UNTIL_RECORD_NAME, $goalId),
+ self::getRecordName(self::DAYS_UNTIL_CONV_RECORD_NAME, $goalId)));
+ }
+
+ // sum up goal overview reports
+ $archiveProcessing->archiveDataTable(array(
+ self::getRecordName(self::VISITS_UNTIL_RECORD_NAME),
+ self::getRecordName(self::DAYS_UNTIL_CONV_RECORD_NAME)));
+ }
+
+ static public function getGoalColumns($idGoal)
+ {
+ $columns = array(
+ 'nb_conversions',
+ 'nb_visits_converted',
+ 'conversion_rate',
+ 'revenue',
+ );
+ if ($idGoal === false) {
+ return $columns;
+ }
+ // Orders
+ if ($idGoal === Piwik_Tracker_GoalManager::IDGOAL_ORDER) {
+ $columns = array_merge($columns, array(
+ 'revenue_subtotal',
+ 'revenue_tax',
+ 'revenue_shipping',
+ 'revenue_discount',
+ ));
+ }
+ // Abandoned carts & orders
+ if ($idGoal <= Piwik_Tracker_GoalManager::IDGOAL_ORDER) {
+ $columns[] = 'items';
+ }
+ return $columns;
+ }
+
+ /**
+ * Hooks on the Daily archiving.
+ * Will process Goal stats overall and for each Goal.
+ * Also processes the New VS Returning visitors conversion stats.
+ *
+ * @param Piwik_Event_Notification $notification
+ * @return void
+ */
+ function archiveDay($notification)
+ {
+ /**
+ * @var Piwik_ArchiveProcessing_Day
+ */
+ $archiveProcessing = $notification->getNotificationObject();
+
+ if (!$archiveProcessing->shouldProcessReportsForPlugin($this->getPluginName())) return;
+
+ $this->archiveGeneralGoalMetrics($archiveProcessing);
+ $this->archiveEcommerceItems($archiveProcessing);
+ }
+
+ /**
+ * @param Piwik_ArchiveProcessing_Day $archiveProcessing
+ */
+ function archiveGeneralGoalMetrics($archiveProcessing)
+ {
+ // extra aggregate selects for the visits to conversion report
+ $visitToConvExtraCols = Piwik_ArchiveProcessing_Day::buildReduceByRangeSelect(
+ 'visitor_count_visits', self::$visitCountRanges, 'log_conversion', 'vcv');
+
+ // extra aggregate selects for the days to conversion report
+ $daysToConvExtraCols = Piwik_ArchiveProcessing_Day::buildReduceByRangeSelect(
+ 'visitor_days_since_first', self::$daysToConvRanges, 'log_conversion', 'vdsf');
+
+ $query = $archiveProcessing->queryConversionsByDimension(
+ array(), '', array_merge($visitToConvExtraCols, $daysToConvExtraCols));
+
+ if ($query === false) {
+ return;
+ }
+
+ $goals = array();
+ $visitsToConvReport = array();
+ $daysToConvReport = array();
+
+ // Get a standard empty goal row
+ $overall = $archiveProcessing->getNewGoalRow($idGoal = 1);
+ while ($row = $query->fetch()) {
+ $idgoal = $row['idgoal'];
+
+ if (!isset($goals[$idgoal])) {
+ $goals[$idgoal] = $archiveProcessing->getNewGoalRow($idgoal);
+
+ $visitsToConvReport[$idgoal] = new Piwik_DataTable();
+ $daysToConvReport[$idgoal] = new Piwik_DataTable();
+ }
+ $archiveProcessing->updateGoalStats($row, $goals[$idgoal]);
+
+ // We don't want to sum Abandoned cart metrics in the overall revenue/conversions/converted visits
+ // since it is a "negative conversion"
+ if ($idgoal != Piwik_Tracker_GoalManager::IDGOAL_CART) {
+ $archiveProcessing->updateGoalStats($row, $overall);
+ }
+
+ // map the goal + visit number of a visitor with the # of conversions that happened on that visit
+ $table = $archiveProcessing->getSimpleDataTableFromRow($row, Piwik_Archive::INDEX_NB_CONVERSIONS, 'vcv');
+ $visitsToConvReport[$idgoal]->addDataTable($table);
+
+ // map the goal + day number of a visit with the # of conversion that happened on that day
+ $table = $archiveProcessing->getSimpleDataTableFromRow($row, Piwik_Archive::INDEX_NB_CONVERSIONS, 'vdsf');
+ $daysToConvReport[$idgoal]->addDataTable($table);
+ }
+
+ // these data tables hold reports for every goal of a site
+ $visitsToConvOverview = new Piwik_DataTable();
+ $daysToConvOverview = new Piwik_DataTable();
+
+ // Stats by goal, for all visitors
+ foreach ($goals as $idgoal => $values) {
+ foreach ($values as $metricId => $value) {
+ $metricName = Piwik_Archive::$mappingFromIdToNameGoal[$metricId];
+ $recordName = self::getRecordName($metricName, $idgoal);
+ $archiveProcessing->insertNumericRecord($recordName, $value);
+ }
+ $conversion_rate = $this->getConversionRate($values[Piwik_Archive::INDEX_GOAL_NB_VISITS_CONVERTED], $archiveProcessing);
+ $recordName = self::getRecordName('conversion_rate', $idgoal);
+ $archiveProcessing->insertNumericRecord($recordName, $conversion_rate);
+
+ // if the goal is not a special goal (like ecommerce) add it to the overview report
+ if ($idgoal !== Piwik_Tracker_GoalManager::IDGOAL_CART &&
+ $idgoal !== Piwik_Tracker_GoalManager::IDGOAL_ORDER
+ ) {
+ $visitsToConvOverview->addDataTable($visitsToConvReport[$idgoal]);
+ $daysToConvOverview->addDataTable($daysToConvReport[$idgoal]);
+ }
+
+ // visit count until conversion stats
+ $archiveProcessing->insertBlobRecord(
+ self::getRecordName(self::VISITS_UNTIL_RECORD_NAME, $idgoal),
+ $visitsToConvReport[$idgoal]->getSerialized());
+
+ // day count until conversion stats
+ $archiveProcessing->insertBlobRecord(
+ self::getRecordName(self::DAYS_UNTIL_CONV_RECORD_NAME, $idgoal),
+ $daysToConvReport[$idgoal]->getSerialized());
+ }
+
+ // archive overview reports
+ $archiveProcessing->insertBlobRecord(
+ self::getRecordName(self::VISITS_UNTIL_RECORD_NAME), $visitsToConvOverview->getSerialized());
+ $archiveProcessing->insertBlobRecord(
+ self::getRecordName(self::DAYS_UNTIL_CONV_RECORD_NAME), $daysToConvOverview->getSerialized());
+
+ // Stats for all goals
+ $totalAllGoals = array(
+ self::getRecordName('conversion_rate') => $this->getConversionRate($archiveProcessing->getNumberOfVisitsConverted(), $archiveProcessing),
+ self::getRecordName('nb_conversions') => $overall[Piwik_Archive::INDEX_GOAL_NB_CONVERSIONS],
+ self::getRecordName('nb_visits_converted') => $archiveProcessing->getNumberOfVisitsConverted(),
+ self::getRecordName('revenue') => $overall[Piwik_Archive::INDEX_GOAL_REVENUE],
+ );
+ foreach ($totalAllGoals as $recordName => $value) {
+ $archiveProcessing->insertNumericRecord($recordName, $value);
+ }
+ }
+
+ protected $dimensions = array(
+ 'idaction_sku' => 'Goals_ItemsSku',
+ 'idaction_name' => 'Goals_ItemsName',
+ 'idaction_category' => 'Goals_ItemsCategory'
+ );
+
+ protected function shouldArchiveEcommerceItems($archiveProcessing)
+ {
+ // Per item doesn't support segment
+ // Also, when querying Goal metrics for visitorType==returning, we wouldnt want to trigger an extra request
+ // event if it did support segment
+ // (if this is implented, we should have shouldProcessReportsForPlugin() support partial archiving based on which metric is requested)
+ if (!$archiveProcessing->getSegment()->isEmpty()) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * @param Piwik_ArchiveProcessing_Day $archiveProcessing
+ */
+ function archiveEcommerceItems($archiveProcessing)
+ {
+ if (!$this->shouldArchiveEcommerceItems($archiveProcessing)) {
+ return false;
+ }
+ $items = array();
+
+ $dimensionsToQuery = $this->dimensions;
+ $dimensionsToQuery['idaction_category2'] = 'AdditionalCategory';
+ $dimensionsToQuery['idaction_category3'] = 'AdditionalCategory';
+ $dimensionsToQuery['idaction_category4'] = 'AdditionalCategory';
+ $dimensionsToQuery['idaction_category5'] = 'AdditionalCategory';
+
+ foreach ($dimensionsToQuery as $dimension => $recordName) {
+ $query = $archiveProcessing->queryEcommerceItems($dimension);
+ if ($query == false) {
+ continue;
+ }
+
+ while ($row = $query->fetch()) {
+ $label = $row['label'];
+ $ecommerceType = $row['ecommerceType'];
+
+ if (empty($label)) {
+ // idaction==0 case:
+ // If we are querying any optional category, we do not include idaction=0
+ // Otherwise we over-report in the Product Categories report
+ if ($recordName == 'AdditionalCategory') {
+ continue;
+ }
+ // Product Name/Category not defined"
+ if (class_exists('Piwik_CustomVariables')) {
+ $label = Piwik_CustomVariables::LABEL_CUSTOM_VALUE_NOT_DEFINED;
+ } else {
+ $label = "Value not defined";
+ }
+ }
+ // For carts, idorder = 0. To count abandoned carts, we must count visits with an abandoned cart
+ if ($ecommerceType == Piwik_Tracker_GoalManager::IDGOAL_CART) {
+ $row[Piwik_Archive::INDEX_ECOMMERCE_ORDERS] = $row[Piwik_Archive::INDEX_NB_VISITS];
+ }
+ unset($row[Piwik_Archive::INDEX_NB_VISITS]);
+ unset($row['label']);
+ unset($row['ecommerceType']);
+
+ $columnsToRound = array(
+ Piwik_Archive::INDEX_ECOMMERCE_ITEM_REVENUE,
+ Piwik_Archive::INDEX_ECOMMERCE_ITEM_QUANTITY,
+ Piwik_Archive::INDEX_ECOMMERCE_ITEM_PRICE,
+ Piwik_Archive::INDEX_ECOMMERCE_ITEM_PRICE_VIEWED,
+ );
+ foreach ($columnsToRound as $column) {
+ if (isset($row[$column])
+ && $row[$column] == round($row[$column])
+ ) {
+ $row[$column] = round($row[$column]);
+ }
+ }
+ $items[$dimension][$ecommerceType][$label] = $row;
+ }
+ }
+
+ foreach ($this->dimensions as $dimension => $recordName) {
+ foreach (array(Piwik_Tracker_GoalManager::IDGOAL_CART, Piwik_Tracker_GoalManager::IDGOAL_ORDER) as $ecommerceType) {
+ if (!isset($items[$dimension][$ecommerceType])) {
+ continue;
+ }
+ $recordNameInsert = $recordName;
+ if ($ecommerceType == Piwik_Tracker_GoalManager::IDGOAL_CART) {
+ $recordNameInsert = self::getItemRecordNameAbandonedCart($recordName);
+ }
+ $table = $archiveProcessing->getDataTableFromArray($items[$dimension][$ecommerceType]);
+
+ // For "category" report, we aggregate all 5 category queries into one datatable
+ if ($dimension == 'idaction_category') {
+ foreach (array('idaction_category2', 'idaction_category3', 'idaction_category4', 'idaction_category5') as $categoryToSum) {
+ if (!empty($items[$categoryToSum][$ecommerceType])) {
+ $tableToSum = $archiveProcessing->getDataTableFromArray($items[$categoryToSum][$ecommerceType]);
+ $table->addDataTable($tableToSum);
+ }
+ }
+ }
+ $archiveProcessing->insertBlobRecord($recordNameInsert, $table->getSerialized());
+ }
+ }
+ }
+
+ static public function getItemRecordNameAbandonedCart($recordName)
+ {
+ return $recordName . '_Cart';
+ }
+
+ function getConversionRate($count, $archiveProcessing)
+ {
+ $visits = $archiveProcessing->getNumberOfVisits();
+ return round(100 * $count / $visits, Piwik_Tracker_GoalManager::REVENUE_PRECISION);
+ }
+
+ /**
+ * This function executes when the 'Goals.getReportsWithGoalMetrics' event fires. It
+ * adds the 'visits to conversion' report metadata to the list of goal reports so
+ * this report will be displayed.
+ *
+ * @param Piwik_Event_Notification $notification notification object
+ */
+ function getActualReportsWithGoalMetrics($notification)
+ {
+ $dimensions =& $notification->getNotificationObject();
+ $dimensions = array_merge($dimensions, array(
+ array('category' => Piwik_Translate('General_Visit'),
+ 'name' => Piwik_Translate('Goals_VisitsUntilConv'),
+ 'module' => 'Goals',
+ 'action' => 'getVisitsUntilConversion'
+ ),
+ array('category' => Piwik_Translate('General_Visit'),
+ 'name' => Piwik_Translate('Goals_DaysToConv'),
+ 'module' => 'Goals',
+ 'action' => 'getDaysToConversion'
+ )
+ ));
+ }
}
diff --git a/plugins/Goals/templates/GoalForm.js b/plugins/Goals/templates/GoalForm.js
index 042f8eb95f..f4574bb7c5 100644
--- a/plugins/Goals/templates/GoalForm.js
+++ b/plugins/Goals/templates/GoalForm.js
@@ -5,103 +5,96 @@
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
-function showAddNewGoal()
-{
- hideForms();
- $(".entityAddContainer").show();
- showCancel();
- piwikHelper.lazyScrollTo(".entityContainer", 400);
- return false;
+function showAddNewGoal() {
+ hideForms();
+ $(".entityAddContainer").show();
+ showCancel();
+ piwikHelper.lazyScrollTo(".entityContainer", 400);
+ return false;
}
-function showEditGoals()
-{
- hideForms();
- $("#entityEditContainer").show();
- showCancel();
- piwikHelper.lazyScrollTo(".entityContainer", 400);
- return false;
+function showEditGoals() {
+ hideForms();
+ $("#entityEditContainer").show();
+ showCancel();
+ piwikHelper.lazyScrollTo(".entityContainer", 400);
+ return false;
}
-function hideForms()
-{
- $(".entityAddContainer").hide();
- $("#entityEditContainer").hide();
+function hideForms() {
+ $(".entityAddContainer").hide();
+ $("#entityEditContainer").hide();
}
-function showCancel()
-{
- $(".entityCancel").show();
- $('.entityCancelLink').click( function(){
- hideForms();
- $(".entityCancel").hide();
- });
+function showCancel() {
+ $(".entityCancel").show();
+ $('.entityCancelLink').click(function () {
+ hideForms();
+ $(".entityCancel").hide();
+ });
}
// init the goal form with existing goal value, if any
-function initGoalForm(goalMethodAPI, submitText, goalName, matchAttribute, pattern, patternType, caseSensitive, revenue, allowMultiple, goalId)
-{
- $('#goal_name').val(goalName);
- if(matchAttribute == 'manually') {
- $('select[name=trigger_type] option[value=manually]').prop('selected', true);
- $('input[name=match_attribute]').prop('disabled', true);
- $('#match_attribute_section').hide();
- $('#manual_trigger_section').show();
- matchAttribute = 'url';
- } else {
- $('select[name=trigger_type] option[value=visitors]').prop('selected', true);
- }
- $('input[name=match_attribute][value='+matchAttribute+']').prop('checked', true);
- $('input[name=allow_multiple][value='+allowMultiple+']').prop('checked', true);
- $('#match_attribute_name').html(mappingMatchTypeName[matchAttribute]);
- $('#examples_pattern').html(mappingMatchTypeExamples[matchAttribute]);
- $('select[name=pattern_type] option[value='+patternType+']').prop('selected', true);
- $('input[name=pattern]').val(pattern);
- $('#case_sensitive').prop('checked', caseSensitive);
- $('input[name=revenue]').val(revenue);
- $('input[name=methodGoalAPI]').val(goalMethodAPI);
- $('#goal_submit').val(submitText);
- if(goalId != undefined) {
- $('input[name=goalIdUpdate]').val(goalId);
- }
+function initGoalForm(goalMethodAPI, submitText, goalName, matchAttribute, pattern, patternType, caseSensitive, revenue, allowMultiple, goalId) {
+ $('#goal_name').val(goalName);
+ if (matchAttribute == 'manually') {
+ $('select[name=trigger_type] option[value=manually]').prop('selected', true);
+ $('input[name=match_attribute]').prop('disabled', true);
+ $('#match_attribute_section').hide();
+ $('#manual_trigger_section').show();
+ matchAttribute = 'url';
+ } else {
+ $('select[name=trigger_type] option[value=visitors]').prop('selected', true);
+ }
+ $('input[name=match_attribute][value=' + matchAttribute + ']').prop('checked', true);
+ $('input[name=allow_multiple][value=' + allowMultiple + ']').prop('checked', true);
+ $('#match_attribute_name').html(mappingMatchTypeName[matchAttribute]);
+ $('#examples_pattern').html(mappingMatchTypeExamples[matchAttribute]);
+ $('select[name=pattern_type] option[value=' + patternType + ']').prop('selected', true);
+ $('input[name=pattern]').val(pattern);
+ $('#case_sensitive').prop('checked', caseSensitive);
+ $('input[name=revenue]').val(revenue);
+ $('input[name=methodGoalAPI]').val(goalMethodAPI);
+ $('#goal_submit').val(submitText);
+ if (goalId != undefined) {
+ $('input[name=goalIdUpdate]').val(goalId);
+ }
}
-function bindGoalForm()
-{
- $('select[name=trigger_type]').click( function() {
- var triggerTypeId = $(this).val();
- if(triggerTypeId == "manually") {
- $('input[name=match_attribute]').prop('disabled', true);
- $('#match_attribute_section').hide();
- $('#manual_trigger_section').show();
- } else {
- $('input[name=match_attribute]').removeProp('disabled');
- $('#match_attribute_section').show();
- $('#manual_trigger_section').hide();
- }
- });
-
- $('input[name=match_attribute]').click( function() {
- var matchTypeId = $(this).val();
- $('#match_attribute_name').html(mappingMatchTypeName[matchTypeId]);
- $('#examples_pattern').html(mappingMatchTypeExamples[matchTypeId]);
- });
-
- $('#goal_submit').click( function() {
- // prepare ajax query to API to add goal
+function bindGoalForm() {
+ $('select[name=trigger_type]').click(function () {
+ var triggerTypeId = $(this).val();
+ if (triggerTypeId == "manually") {
+ $('input[name=match_attribute]').prop('disabled', true);
+ $('#match_attribute_section').hide();
+ $('#manual_trigger_section').show();
+ } else {
+ $('input[name=match_attribute]').removeProp('disabled');
+ $('#match_attribute_section').show();
+ $('#manual_trigger_section').hide();
+ }
+ });
+
+ $('input[name=match_attribute]').click(function () {
+ var matchTypeId = $(this).val();
+ $('#match_attribute_name').html(mappingMatchTypeName[matchTypeId]);
+ $('#examples_pattern').html(mappingMatchTypeExamples[matchTypeId]);
+ });
+
+ $('#goal_submit').click(function () {
+ // prepare ajax query to API to add goal
ajaxAddGoal();
- return false;
- });
-
- $('a[name=linkAddNewGoal]').click( function(){
- initAndShowAddGoalForm();
- piwikHelper.lazyScrollTo('#goal_name');
- } );
+ return false;
+ });
+
+ $('a[name=linkAddNewGoal]').click(function () {
+ initAndShowAddGoalForm();
+ piwikHelper.lazyScrollTo('#goal_name');
+ });
}
-function ajaxDeleteGoal(idGoal)
-{
+function ajaxDeleteGoal(idGoal) {
piwikHelper.lazyScrollTo(".entityContainer", 400);
var parameters = {};
@@ -113,18 +106,17 @@ function ajaxDeleteGoal(idGoal)
var ajaxRequest = new ajaxHelper();
ajaxRequest.addParams(parameters, 'get');
ajaxRequest.setLoadingElement('#goalAjaxLoading');
- ajaxRequest.setCallback(function(){ location.reload(); });
+ ajaxRequest.setCallback(function () { location.reload(); });
ajaxRequest.send(true);
}
-function ajaxAddGoal()
-{
+function ajaxAddGoal() {
piwikHelper.lazyScrollTo(".entityContainer", 400);
var parameters = {};
- parameters.name = encodeURIComponent( $('#goal_name').val() );
+ parameters.name = encodeURIComponent($('#goal_name').val());
- if($('[name=trigger_type]').val() == 'manually') {
+ if ($('[name=trigger_type]').val() == 'manually') {
parameters.matchAttribute = 'manually';
parameters.patternType = 'regex';
parameters.pattern = '.*';
@@ -132,13 +124,13 @@ function ajaxAddGoal()
} else {
parameters.matchAttribute = $('input[name=match_attribute]:checked').val();
parameters.patternType = $('[name=pattern_type]').val();
- parameters.pattern = encodeURIComponent( $('input[name=pattern]').val() );
- parameters.caseSensitive = $('#case_sensitive').prop('checked') == true ? 1: 0;
+ parameters.pattern = encodeURIComponent($('input[name=pattern]').val());
+ parameters.caseSensitive = $('#case_sensitive').prop('checked') == true ? 1 : 0;
}
parameters.revenue = $('input[name=revenue]').val();
- parameters.allowMultipleConversionsPerVisit = $('input[name=allow_multiple]:checked').val() == true ? 1: 0;
+ parameters.allowMultipleConversionsPerVisit = $('input[name=allow_multiple]:checked').val() == true ? 1 : 0;
- parameters.idGoal = $('input[name=goalIdUpdate]').val();
+ parameters.idGoal = $('input[name=goalIdUpdate]').val();
parameters.format = 'json';
parameters.module = 'API';
parameters.method = $('input[name=methodGoalAPI]').val();
@@ -146,38 +138,36 @@ function ajaxAddGoal()
var ajaxRequest = new ajaxHelper();
ajaxRequest.addParams(parameters, 'get');
ajaxRequest.setLoadingElement('#goalAjaxLoading');
- ajaxRequest.setCallback(function(){ location.reload(); });
+ ajaxRequest.setCallback(function () { location.reload(); });
ajaxRequest.send(true);
}
-function bindListGoalEdit()
-{
- $('a[name=linkEditGoal]').click( function() {
- var goalId = $(this).attr('id');
- var goal = piwik.goals[goalId];
- initGoalForm("Goals.updateGoal", _pk_translate('Goals_UpdateGoal_js'), goal.name, goal.match_attribute, goal.pattern, goal.pattern_type, (goal.case_sensitive != '0'), goal.revenue, goal.allow_multiple, goalId);
- showAddNewGoal();
- return false;
- });
-
- $('a[name=linkDeleteGoal]').click( function() {
- var goalId = $(this).attr('id');
- var goal = piwik.goals[goalId];
-
- $('#confirm h2').text(sprintf(_pk_translate('Goals_DeleteGoalConfirm_js'), '"'+goal.name+'"'));
- piwikHelper.modalConfirm('#confirm', {yes: function(){
- ajaxDeleteGoal( goalId );
- }});
- return false;
- });
-
- $('a[name=linkEditGoals]').click( function(){
- return showEditGoals();
- } );
+function bindListGoalEdit() {
+ $('a[name=linkEditGoal]').click(function () {
+ var goalId = $(this).attr('id');
+ var goal = piwik.goals[goalId];
+ initGoalForm("Goals.updateGoal", _pk_translate('Goals_UpdateGoal_js'), goal.name, goal.match_attribute, goal.pattern, goal.pattern_type, (goal.case_sensitive != '0'), goal.revenue, goal.allow_multiple, goalId);
+ showAddNewGoal();
+ return false;
+ });
+
+ $('a[name=linkDeleteGoal]').click(function () {
+ var goalId = $(this).attr('id');
+ var goal = piwik.goals[goalId];
+
+ $('#confirm h2').text(sprintf(_pk_translate('Goals_DeleteGoalConfirm_js'), '"' + goal.name + '"'));
+ piwikHelper.modalConfirm('#confirm', {yes: function () {
+ ajaxDeleteGoal(goalId);
+ }});
+ return false;
+ });
+
+ $('a[name=linkEditGoals]').click(function () {
+ return showEditGoals();
+ });
}
-function initAndShowAddGoalForm()
-{
- initGoalForm('Goals.addGoal', _pk_translate('Goals_AddGoal_js'), '', 'url', '', 'contains', caseSensitive = false, allowMultiple = '0', '0');
- return showAddNewGoal();
+function initAndShowAddGoalForm() {
+ initGoalForm('Goals.addGoal', _pk_translate('Goals_AddGoal_js'), '', 'url', '', 'contains', caseSensitive = false, allowMultiple = '0', '0');
+ return showAddNewGoal();
}
diff --git a/plugins/Goals/templates/add_edit_goal.tpl b/plugins/Goals/templates/add_edit_goal.tpl
index 373ddc7bc8..8a5c42588f 100644
--- a/plugins/Goals/templates/add_edit_goal.tpl
+++ b/plugins/Goals/templates/add_edit_goal.tpl
@@ -1,76 +1,79 @@
{if isset($onlyShowAddNewGoal)}
<h2>{'Goals_AddNewGoal'|translate}</h2>
- <p>Goal Conversion tracking is one of the most efficient ways to measure and improve your business objectives.</p>
- <p>A Goal in Piwik is your strategy, your priority, and can entail many things: "Downloaded brochure", "Registered newsletter", "Visited page services.html", etc. What do you want your users to do on your website?
- You will be able to view and analyse your performance for each Goal, and learn how to increase conversions, conversion rates and revenue per visit.</p>
+ <p>Goal Conversion tracking is one of the most efficient ways to measure and improve your business objectives.</p>
+ <p>A Goal in Piwik is your strategy, your priority, and can entail many things: "Downloaded brochure", "Registered newsletter", "Visited page
+ services.html", etc. What do you want your users to do on your website?
+ You will be able to view and analyse your performance for each Goal, and learn how to increase conversions, conversion rates and revenue per visit.</p>
<p>{'Goals_LearnMoreAboutGoalTrackingDocumentation'|translate:"<a href='?module=Proxy&action=redirect&url=http://piwik.org/docs/tracking-goals-web-analytics/' target='_blank'>":"</a>"}
</p>
{else}
<div class="clear"></div>
- <h2>{'Goals_GoalsManagement'|translate}</h2>
- <div class="entityList">
- <ul class='listCircle'>
- <li><a onclick='' name='linkAddNewGoal'><u>{'Goals_CreateNewGOal'|translate}</u></a></li>
- <li><a onclick='' name='linkEditGoals'>{'Goals_ViewAndEditGoals'|translate}</a></li>
- <li>{'Goals_LearnMoreAboutGoalTrackingDocumentation'|translate:"<a href='?module=Proxy&action=redirect&url=http://piwik.org/docs/tracking-goals-web-analytics/' target='_blank'>":"</a>"}</li>
+ <h2>{'Goals_GoalsManagement'|translate}</h2>
+ <div class="entityList">
+ <ul class='listCircle'>
+ <li><a onclick='' name='linkAddNewGoal'><u>{'Goals_CreateNewGOal'|translate}</u></a></li>
+ <li><a onclick='' name='linkEditGoals'>{'Goals_ViewAndEditGoals'|translate}</a></li>
+ <li>{'Goals_LearnMoreAboutGoalTrackingDocumentation'|translate:"<a href='?module=Proxy&action=redirect&url=http://piwik.org/docs/tracking-goals-web-analytics/' target='_blank'>":"</a>"}</li>
- <li>{if !$ecommerceEnabled}
- {capture assign='websiteManageText'}<a href='{url module=SitesManager action=index}'>{'SitesManager_WebsitesManagement'|translate}</a>{/capture}
- {capture assign='ecommerceReportText'}<a href="http://piwik.org/docs/ecommerce-analytics/" target="_blank">{'Goals_EcommerceReports'|translate}</a>{/capture}
- {'Goals_Optional'|translate} {'Goals_Ecommerce'|translate}: {'Goals_YouCanEnableEcommerceReports'|translate:$ecommerceReportText:$websiteManageText}
- {else}
- {'SitesManager_PiwikOffersEcommerceAnalytics'|translate:'<a href="http://piwik.org/docs/ecommerce-analytics/" target="_blank">':"</a>"}
- {/if}
- </li>
- </ul>
- </div>
- <br/>
+ <li>{if !$ecommerceEnabled}
+ {capture assign='websiteManageText'}<a
+ href='{url module=SitesManager action=index}'>{'SitesManager_WebsitesManagement'|translate}</a>{/capture}
+ {capture assign='ecommerceReportText'}<a href="http://piwik.org/docs/ecommerce-analytics/"
+ target="_blank">{'Goals_EcommerceReports'|translate}</a>{/capture}
+ {'Goals_Optional'|translate} {'Goals_Ecommerce'|translate}: {'Goals_YouCanEnableEcommerceReports'|translate:$ecommerceReportText:$websiteManageText}
+ {else}
+ {'SitesManager_PiwikOffersEcommerceAnalytics'|translate:'<a href="http://piwik.org/docs/ecommerce-analytics/" target="_blank">':"</a>"}
+ {/if}
+ </li>
+ </ul>
+ </div>
+ <br/>
{/if}
{ajaxErrorDiv}
{ajaxLoadingDiv id=goalAjaxLoading}
-
+
<div class="entityContainer">
- {if !isset($onlyShowAddNewGoal)}
- {include file="Goals/templates/list_goal_edit.tpl"}
- {/if}
- {include file="Goals/templates/form_add_goal.tpl"}
- {if !isset($onlyShowAddNewGoal)}
- <div class='entityCancel' style='display:none'>
- {'General_OrCancel'|translate:"<a class='entityCancelLink'>":"</a>"}
- </div>
- {/if}
- <a id='bottom'></a>
+ {if !isset($onlyShowAddNewGoal)}
+ {include file="Goals/templates/list_goal_edit.tpl"}
+ {/if}
+ {include file="Goals/templates/form_add_goal.tpl"}
+ {if !isset($onlyShowAddNewGoal)}
+ <div class='entityCancel' style='display:none'>
+ {'General_OrCancel'|translate:"<a class='entityCancelLink'>":"</a>"}
+ </div>
+ {/if}
+ <a id='bottom'></a>
</div>
<br/><br/>
{loadJavascriptTranslations plugins='Goals'}
<script type="text/javascript">
-var mappingMatchTypeName = {ldelim}
- "url": "{'Goals_URL'|translate|escape}",
- "title": "{'Goals_PageTitle'|translate|escape}",
- "file": "{'Goals_Filename'|translate|escape}",
- "external_website": "{'Goals_ExternalWebsiteUrl'|translate|escape}"
-{rdelim};
-var mappingMatchTypeExamples = {ldelim}
- "url": "{'General_ForExampleShort'|translate} {'Goals_Contains'|translate:"'checkout/confirmation'"|escape} \
+ var mappingMatchTypeName = {ldelim}
+ "url": "{'Goals_URL'|translate|escape}",
+ "title": "{'Goals_PageTitle'|translate|escape}",
+ "file": "{'Goals_Filename'|translate|escape}",
+ "external_website": "{'Goals_ExternalWebsiteUrl'|translate|escape}"
+ {rdelim};
+ var mappingMatchTypeExamples = {ldelim}
+ "url": "{'General_ForExampleShort'|translate} {'Goals_Contains'|translate:"'checkout/confirmation'"|escape} \
<br />{'General_ForExampleShort'|translate|escape} {'Goals_IsExactly'|translate:"'http://example.com/thank-you.html'"|escape} \
- <br />{'General_ForExampleShort'|translate|escape} {'Goals_MatchesExpression'|translate:"'(.*)\\\/demo\\\/(.*)'"|escape}",
- "title": "{'General_ForExampleShort'|translate} {'Goals_Contains'|translate:"'Order confirmation'"|escape}",
- "file": "{'General_ForExampleShort'|translate|escape} {'Goals_Contains'|translate:"'files/brochure.pdf'"|escape} \
+ <br />{'General_ForExampleShort'|translate|escape} {'Goals_MatchesExpression'|translate:"'(.*)\\\/demo\\\/(.*)'"|escape}",
+ "title": "{'General_ForExampleShort'|translate} {'Goals_Contains'|translate:"'Order confirmation'"|escape}",
+ "file": "{'General_ForExampleShort'|translate|escape} {'Goals_Contains'|translate:"'files/brochure.pdf'"|escape} \
<br />{'General_ForExampleShort'|translate|escape} {'Goals_IsExactly'|translate:"'http://example.com/files/brochure.pdf'"|escape} \
- <br />{'General_ForExampleShort'|translate|escape} {'Goals_MatchesExpression'|translate:"'(.*)\\\.zip'"|escape}",
- "external_website": "{'General_ForExampleShort'|translate|escape} {'Goals_Contains'|translate:"'amazon.com'"|escape} \
+ <br />{'General_ForExampleShort'|translate|escape} {'Goals_MatchesExpression'|translate:"'(.*)\\\.zip'"|escape}",
+ "external_website": "{'General_ForExampleShort'|translate|escape} {'Goals_Contains'|translate:"'amazon.com'"|escape} \
<br />{'General_ForExampleShort'|translate|escape} {'Goals_IsExactly'|translate:"'http://mypartner.com/landing.html'"|escape} \
- <br />{'General_ForExampleShort'|translate|escape} {'Goals_MatchesExpression'|translate:"'http://www.amazon.com\\\/(.*)\\\/yourAffiliateId'"|escape}"
-{rdelim};
-bindGoalForm();
+ <br />{'General_ForExampleShort'|translate|escape} {'Goals_MatchesExpression'|translate:"'http://www.amazon.com\\\/(.*)\\\/yourAffiliateId'"|escape}"
+ {rdelim};
+ bindGoalForm();
-{if !isset($onlyShowAddNewGoal)}
-piwik.goals = {$goalsJSON};
-bindListGoalEdit();
-{else}
-initAndShowAddGoalForm();
-{/if}
+ {if !isset($onlyShowAddNewGoal)}
+ piwik.goals = {$goalsJSON};
+ bindListGoalEdit();
+ {else}
+ initAndShowAddGoalForm();
+ {/if}
</script>
diff --git a/plugins/Goals/templates/add_new_goal.tpl b/plugins/Goals/templates/add_new_goal.tpl
index fb41a3e1c3..5b7a916072 100644
--- a/plugins/Goals/templates/add_new_goal.tpl
+++ b/plugins/Goals/templates/add_new_goal.tpl
@@ -1,11 +1,10 @@
-
{if $userCanEditGoals}
- {include file="Goals/templates/add_edit_goal.tpl"}
+ {include file="Goals/templates/add_edit_goal.tpl"}
{else}
-<h2>{'Goals_CreateNewGOal'|translate}</h2>
-<p>
-{'Goals_NoGoalsNeedAccess'|translate}
-</p>
-<p>{'Goals_LearnMoreAboutGoalTrackingDocumentation'|translate:"<a href='?module=Proxy&action=redirect&url=http://piwik.org/docs/tracking-goals-web-analytics/' target='_blank'>":"</a>"}
-</p>
+ <h2>{'Goals_CreateNewGOal'|translate}</h2>
+ <p>
+ {'Goals_NoGoalsNeedAccess'|translate}
+ </p>
+ <p>{'Goals_LearnMoreAboutGoalTrackingDocumentation'|translate:"<a href='?module=Proxy&action=redirect&url=http://piwik.org/docs/tracking-goals-web-analytics/' target='_blank'>":"</a>"}
+ </p>
{/if}
diff --git a/plugins/Goals/templates/form_add_goal.tpl b/plugins/Goals/templates/form_add_goal.tpl
index 73a4f89f57..4c8517eb63 100644
--- a/plugins/Goals/templates/form_add_goal.tpl
+++ b/plugins/Goals/templates/form_add_goal.tpl
@@ -1,93 +1,97 @@
<div class='entityAddContainer' style="display:none;">
-<form>
-<table class="dataTable entityTable">
- <thead>
- <tr class="first">
- <th colspan="2">{'Goals_AddNewGoal'|translate}</th>
- <tr>
- </thead>
- <tbody>
- <tr>
- <td class="first">{'Goals_GoalName'|translate} </th>
- <td><input type="text" name="name" value="" size="28" id="goal_name" class="inp" /></td>
- </tr>
- <tr>
- <td style='width:260px;' class="first">{'Goals_GoalIsTriggered'|translate}
- <select name="trigger_type" class="inp">
- <option value="visitors">{'Goals_WhenVisitors'|translate}</option>
- <option value="manually">{'Goals_Manually'|translate}</option>
- </select>
- </td>
- <td>
- <input type="radio" id="match_attribute_url" value="url" name="match_attribute" />
- <label for="match_attribute_url">{'Goals_VisitUrl'|translate}</label>
- <br />
- <input type="radio" id="match_attribute_title" value="title" name="match_attribute" />
- <label for="match_attribute_title">{'Goals_VisitPageTitle'|translate}</label>
- <br />
- <input type="radio" id="match_attribute_file" value="file" name="match_attribute" />
- <label for="match_attribute_file">{'Goals_Download'|translate}</label>
- <br />
- <input type="radio" id="match_attribute_external_website" value="external_website" name="match_attribute" />
- <label for="match_attribute_external_website">{'Goals_ClickOutlink'|translate}</label>
- </td>
- </tr>
- </tbody>
- <tbody id="match_attribute_section">
- <tr>
- <td class="first">{'Goals_WhereThe'|translate} <span id="match_attribute_name"></span></td>
- <td>
- <select name="pattern_type" class="inp">
- <option value="contains">{'Goals_Contains'|translate:""}</option>
- <option value="exact">{'Goals_IsExactly'|translate:""}</option>
- <option value="regex">{'Goals_MatchesExpression'|translate:""}</option>
- </select>
-
- <input type="text" name="pattern" value="" size="16" class="inp" />
- <br />
- <div id="examples_pattern" class="entityInlineHelp"></div>
- <br />
+ <form>
+ <table class="dataTable entityTable">
+ <thead>
+ <tr class="first">
+ <th colspan="2">{'Goals_AddNewGoal'|translate}</th>
+ <tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td class="first">{'Goals_GoalName'|translate} </th>
+ <td><input type="text" name="name" value="" size="28" id="goal_name" class="inp"/></td>
+ </tr>
+ <tr>
+ <td style='width:260px;' class="first">{'Goals_GoalIsTriggered'|translate}
+ <select name="trigger_type" class="inp">
+ <option value="visitors">{'Goals_WhenVisitors'|translate}</option>
+ <option value="manually">{'Goals_Manually'|translate}</option>
+ </select>
+ </td>
+ <td>
+ <input type="radio" id="match_attribute_url" value="url" name="match_attribute"/>
+ <label for="match_attribute_url">{'Goals_VisitUrl'|translate}</label>
+ <br/>
+ <input type="radio" id="match_attribute_title" value="title" name="match_attribute"/>
+ <label for="match_attribute_title">{'Goals_VisitPageTitle'|translate}</label>
+ <br/>
+ <input type="radio" id="match_attribute_file" value="file" name="match_attribute"/>
+ <label for="match_attribute_file">{'Goals_Download'|translate}</label>
+ <br/>
+ <input type="radio" id="match_attribute_external_website" value="external_website" name="match_attribute"/>
+ <label for="match_attribute_external_website">{'Goals_ClickOutlink'|translate}</label>
+ </td>
+ </tr>
+ </tbody>
+ <tbody id="match_attribute_section">
+ <tr>
+ <td class="first">{'Goals_WhereThe'|translate} <span id="match_attribute_name"></span></td>
+ <td>
+ <select name="pattern_type" class="inp">
+ <option value="contains">{'Goals_Contains'|translate:""}</option>
+ <option value="exact">{'Goals_IsExactly'|translate:""}</option>
+ <option value="regex">{'Goals_MatchesExpression'|translate:""}</option>
+ </select>
+
+ <input type="text" name="pattern" value="" size="16" class="inp"/>
+ <br/>
+
+ <div id="examples_pattern" class="entityInlineHelp"></div>
+ <br/>
<span style="float:right">
- {'Goals_Optional'|translate} <input type="checkbox" id="case_sensitive" />
+ {'Goals_Optional'|translate} <input type="checkbox" id="case_sensitive"/>
<label for="case_sensitive">{'Goals_CaseSensitive'|translate}</label>
</span>
- </td>
- </tr>
- </tbody>
- <tbody id="manual_trigger_section" style="display:none">
- <tr><td colspan="2" class="first">
- {'Goals_WhereVisitedPageManuallyCallsJavascriptTrackerLearnMore'|translate:"<a target='_blank' href='?module=Proxy&action=redirect&url=http://piwik.org/docs/javascript-tracking/%23toc-manually-trigger-a-conversion-for-a-goal'>":"</a>"}
- </td></tr>
- </tbody>
- <tbody>
- <tr>
- <td class="first"> {'Goals_AllowMultipleConversionsPerVisit'|translate} </td>
- <td>
- <input type="radio" id="allow_multiple_0" value="0" name="allow_multiple" />
- <label for="allow_multiple_0">{'Goals_DefaultGoalConvertedOncePerVisit'|translate}</label>
- <div class="entityInlineHelp">
- {'Goals_HelpOneConversionPerVisit'|translate}
- </div>
- <br/>
-
- <input type="radio" id="allow_multiple_1" value="1" name="allow_multiple" />
- <label for="allow_multiple_1">{'Goals_AllowGoalConvertedMoreThanOncePerVisit'|translate}</label>
- <br /><br />
- </tr>
- <tr>
- </tbody>
- <tbody>
- <tr>
- <td class="first">{'Goals_Optional'|translate} {'Goals_DefaultRevenue'|translate}</td>
- <td>{' <input type="text" name="revenue" size="2" value="0" class="inp" /> '|money:$idSite}
- <div class="entityInlineHelp"> {'Goals_DefaultRevenueHelp'|translate} </div>
- </td>
- </tr>
- <tr>
- </tbody>
-</table>
- <input type="hidden" name="methodGoalAPI" value="" />
- <input type="hidden" name="goalIdUpdate" value="" />
- <input type="submit" value="" name="submit" id="goal_submit" class="submit" />
-</form>
+ </td>
+ </tr>
+ </tbody>
+ <tbody id="manual_trigger_section" style="display:none">
+ <tr>
+ <td colspan="2" class="first">
+ {'Goals_WhereVisitedPageManuallyCallsJavascriptTrackerLearnMore'|translate:"<a target='_blank' href='?module=Proxy&action=redirect&url=http://piwik.org/docs/javascript-tracking/%23toc-manually-trigger-a-conversion-for-a-goal'>":"</a>"}
+ </td>
+ </tr>
+ </tbody>
+ <tbody>
+ <tr>
+ <td class="first"> {'Goals_AllowMultipleConversionsPerVisit'|translate} </td>
+ <td>
+ <input type="radio" id="allow_multiple_0" value="0" name="allow_multiple"/>
+ <label for="allow_multiple_0">{'Goals_DefaultGoalConvertedOncePerVisit'|translate}</label>
+
+ <div class="entityInlineHelp">
+ {'Goals_HelpOneConversionPerVisit'|translate}
+ </div>
+ <br/>
+
+ <input type="radio" id="allow_multiple_1" value="1" name="allow_multiple"/>
+ <label for="allow_multiple_1">{'Goals_AllowGoalConvertedMoreThanOncePerVisit'|translate}</label>
+ <br/><br/>
+ </tr>
+ <tr>
+ </tbody>
+ <tbody>
+ <tr>
+ <td class="first">{'Goals_Optional'|translate} {'Goals_DefaultRevenue'|translate}</td>
+ <td>{' <input type="text" name="revenue" size="2" value="0" class="inp" /> '|money:$idSite}
+ <div class="entityInlineHelp"> {'Goals_DefaultRevenueHelp'|translate} </div>
+ </td>
+ </tr>
+ <tr>
+ </tbody>
+ </table>
+ <input type="hidden" name="methodGoalAPI" value=""/>
+ <input type="hidden" name="goalIdUpdate" value=""/>
+ <input type="submit" value="" name="submit" id="goal_submit" class="submit"/>
+ </form>
</div>
diff --git a/plugins/Goals/templates/goals.css b/plugins/Goals/templates/goals.css
index b27176beac..52602215bb 100644
--- a/plugins/Goals/templates/goals.css
+++ b/plugins/Goals/templates/goals.css
@@ -1,24 +1,27 @@
-.goalTopElement {
- border-bottom:1px dotted;
-}
-.goalEntry{
- margin:0 0 20px 0;
- padding:0 0 10px 0;
- border-bottom:1px solid #7e7363;
- width:614px;
+.goalTopElement {
+ border-bottom: 1px dotted;
+}
+
+.goalEntry {
+ margin: 0 0 20px 0;
+ padding: 0 0 10px 0;
+ border-bottom: 1px solid #7e7363;
+ width: 614px;
}
/* dimension selector */
#titleGoalsByDimension {
- padding-top:30px;
+ padding-top: 30px;
}
+
ul.ulGoalTopElements {
- list-style-type:circle;
- margin-left:30px;
+ list-style-type: circle;
+ margin-left: 30px;
}
+
.ulGoalTopElements a {
- text-decoration:none;
- color:#0033CC;
- border-bottom:1px dotted #0033CC;
- line-height:2em;
+ text-decoration: none;
+ color: #0033CC;
+ border-bottom: 1px dotted #0033CC;
+ line-height: 2em;
}
diff --git a/plugins/Goals/templates/list_goal_edit.tpl b/plugins/Goals/templates/list_goal_edit.tpl
index 4580f8baf1..1a412d5f82 100644
--- a/plugins/Goals/templates/list_goal_edit.tpl
+++ b/plugins/Goals/templates/list_goal_edit.tpl
@@ -1,50 +1,54 @@
<div id='entityEditContainer' style="display:none;">
- <table class="dataTable entityTable">
- <thead>
- <tr>
- <th class="first">Id</th>
- <th>{'Goals_GoalName'|translate}</th>
- <th>{'Goals_GoalIsTriggeredWhen'|translate}</th>
- <th>{'Goals_ColumnRevenue'|translate}</th>
- <th>{'General_Edit'|translate}</th>
- <th>{'General_Delete'|translate}</th>
- </tr>
- </thead>
- {foreach from=$goals item=goal}
- <tr>
- <td class="first">{$goal.idgoal}</td>
- <td>{$goal.name}</td>
- <td><span class='matchAttribute'>{$goal.match_attribute}</span> {if isset($goal.pattern_type)}<br />{'Goals_Pattern'|translate} {$goal.pattern_type}: {$goal.pattern}</b>{/if}</td>
- <td>{if $goal.revenue==0}-{else}{$goal.revenue|money:$idSite}{/if}</td>
- <td><a href='#' name="linkEditGoal" id="{$goal.idgoal}" class="link_but"><img src='themes/default/images/ico_edit.png' border="0" /> {'General_Edit'|translate}</a></td>
- <td><a href='#' name="linkDeleteGoal" id="{$goal.idgoal}" class="link_but"><img src='themes/default/images/ico_delete.png' border="0" /> {'General_Delete'|translate}</a></td>
- </tr>
- {/foreach}
- </table>
+ <table class="dataTable entityTable">
+ <thead>
+ <tr>
+ <th class="first">Id</th>
+ <th>{'Goals_GoalName'|translate}</th>
+ <th>{'Goals_GoalIsTriggeredWhen'|translate}</th>
+ <th>{'Goals_ColumnRevenue'|translate}</th>
+ <th>{'General_Edit'|translate}</th>
+ <th>{'General_Delete'|translate}</th>
+ </tr>
+ </thead>
+ {foreach from=$goals item=goal}
+ <tr>
+ <td class="first">{$goal.idgoal}</td>
+ <td>{$goal.name}</td>
+ <td><span class='matchAttribute'>{$goal.match_attribute}</span> {if isset($goal.pattern_type)}
+ <br/>
+ {'Goals_Pattern'|translate} {$goal.pattern_type}: {$goal.pattern}</b>{/if}</td>
+ <td>{if $goal.revenue==0}-{else}{$goal.revenue|money:$idSite}{/if}</td>
+ <td><a href='#' name="linkEditGoal" id="{$goal.idgoal}" class="link_but"><img src='themes/default/images/ico_edit.png'
+ border="0"/> {'General_Edit'|translate}</a></td>
+ <td><a href='#' name="linkDeleteGoal" id="{$goal.idgoal}" class="link_but"><img src='themes/default/images/ico_delete.png'
+ border="0"/> {'General_Delete'|translate}</a></td>
+ </tr>
+ {/foreach}
+ </table>
</div>
<div class="ui-confirm" id="confirm">
<h2></h2>
- <input role="yes" type="button" value="{'General_Yes'|translate}" />
- <input role="no" type="button" value="{'General_No'|translate}" />
-</div>
+ <input role="yes" type="button" value="{'General_Yes'|translate}"/>
+ <input role="no" type="button" value="{'General_No'|translate}"/>
+</div>
<script type="text/javascript">
-var goalTypeToTranslation = {ldelim}
- "manually" : "{'Goals_ManuallyTriggeredUsingJavascriptFunction'|translate}",
- "file" : "{'Goals_Download'|translate}",
- "url" : "{'Goals_VisitUrl'|translate}",
- "title" : "{'Goals_VisitPageTitle'|translate}",
- "external_website" : "{'Goals_ClickOutlink'|translate}"
-{rdelim}
-{literal}
-$(document).ready( function() {
- // translation of the goal "match attribute" to human readable description
- $('.matchAttribute').each( function() {
- matchAttribute = $(this).text();
- translation = goalTypeToTranslation[matchAttribute];
- $(this).text(translation);
- });
-} );
-{/literal}
+ var goalTypeToTranslation = {ldelim}
+ "manually": "{'Goals_ManuallyTriggeredUsingJavascriptFunction'|translate}",
+ "file": "{'Goals_Download'|translate}",
+ "url": "{'Goals_VisitUrl'|translate}",
+ "title": "{'Goals_VisitPageTitle'|translate}",
+ "external_website": "{'Goals_ClickOutlink'|translate}"
+ {rdelim}
+ {literal}
+ $(document).ready(function () {
+ // translation of the goal "match attribute" to human readable description
+ $('.matchAttribute').each(function () {
+ matchAttribute = $(this).text();
+ translation = goalTypeToTranslation[matchAttribute];
+ $(this).text(translation);
+ });
+ });
+ {/literal}
</script>
diff --git a/plugins/Goals/templates/list_top_dimension.tpl b/plugins/Goals/templates/list_top_dimension.tpl
index 50e86d9b4d..4bbe28adc9 100644
--- a/plugins/Goals/templates/list_top_dimension.tpl
+++ b/plugins/Goals/templates/list_top_dimension.tpl
@@ -1,10 +1,9 @@
-
{foreach from=$topDimension item=element name=topGoalElements}
- {assign var=goal_nb_conversion value=$element.nb_conversions}
- {assign var=goal_conversion_rate value=$element.conversion_rate}
- <span class='goalTopElement' title='{'Goals_Conversions'|translate:"<b>$goal_nb_conversion</b>"},
+ {assign var=goal_nb_conversion value=$element.nb_conversions}
+ {assign var=goal_conversion_rate value=$element.conversion_rate}
+ <span class='goalTopElement' title='{'Goals_Conversions'|translate:"<b>$goal_nb_conversion</b>"},
{'Goals_ConversionRate'|translate:"<b>$goal_conversion_rate</b>"}'>
{$element.name}</span>
- {logoHtml metadata=$element.metadata alt=$element.name}
- {if $smarty.foreach.topGoalElements.iteration == $smarty.foreach.topGoalElements.total-1} and {elseif $smarty.foreach.topGoalElements.iteration < $smarty.foreach.topGoalElements.total-1}, {else}{/if}
+ {logoHtml metadata=$element.metadata alt=$element.name}
+ {if $smarty.foreach.topGoalElements.iteration == $smarty.foreach.topGoalElements.total-1} and {elseif $smarty.foreach.topGoalElements.iteration < $smarty.foreach.topGoalElements.total-1}, {else}{/if}
{/foreach} \ No newline at end of file
diff --git a/plugins/Goals/templates/overview.tpl b/plugins/Goals/templates/overview.tpl
index 200260f36b..0e971602ce 100644
--- a/plugins/Goals/templates/overview.tpl
+++ b/plugins/Goals/templates/overview.tpl
@@ -1,48 +1,47 @@
-<link rel="stylesheet" type="text/css" href="plugins/Goals/templates/goals.css" />
+<link rel="stylesheet" type="text/css" href="plugins/Goals/templates/goals.css"/>
{include file="Goals/templates/title_and_evolution_graph.tpl"}
{assign var=sum_nb_conversions value=$nb_conversions}
{foreach from=$goalMetrics item=goal}
- {assign var=nb_conversions value=$goal.nb_conversions}
- {assign var=nb_visits_converted value=$goal.nb_visits_converted}
- {assign var=conversion_rate value=$goal.conversion_rate}
- {assign var=name value=$goal.name}
+ {assign var=nb_conversions value=$goal.nb_conversions}
+ {assign var=nb_visits_converted value=$goal.nb_visits_converted}
+ {assign var=conversion_rate value=$goal.conversion_rate}
+ {assign var=name value=$goal.name}
+ <div class="goalEntry">
+ <h2>
+ <a href="javascript:broadcast.propagateAjax('module=Goals&action=goalReport&idGoal={$goal.id}')">
+ {'Goals_GoalX'|translate:"'$name'"}
+ </a>
+ </h2>
-<div class="goalEntry">
- <h2>
- <a href="javascript:broadcast.propagateAjax('module=Goals&action=goalReport&idGoal={$goal.id}')">
- {'Goals_GoalX'|translate:"'$name'"}
- </a>
- </h2>
- <div id='leftcolumn'>
- <div class="sparkline">{sparkline src=$goal.urlSparklineConversions}
- {'Goals_Conversions'|translate:"<strong>$nb_conversions</strong>"}
- {if $goal.goalAllowMultipleConversionsPerVisit}
- ({'VisitsSummary_NbVisits'|translate:"<strong>$nb_visits_converted</strong>"})
- {/if}
- </div>
- </div>
- <div id='rightcolumn'>
- <div class="sparkline">{sparkline src=$goal.urlSparklineConversionRate}
- {'Goals_ConversionRate'|translate:"<strong>$conversion_rate</strong>"}</div>
- </div>
- <br class="clear" />
-</div>
+ <div id='leftcolumn'>
+ <div class="sparkline">{sparkline src=$goal.urlSparklineConversions}
+ {'Goals_Conversions'|translate:"<strong>$nb_conversions</strong>"}
+ {if $goal.goalAllowMultipleConversionsPerVisit}
+ ({'VisitsSummary_NbVisits'|translate:"<strong>$nb_visits_converted</strong>"})
+ {/if}
+ </div>
+ </div>
+ <div id='rightcolumn'>
+ <div class="sparkline">{sparkline src=$goal.urlSparklineConversionRate}
+ {'Goals_ConversionRate'|translate:"<strong>$conversion_rate</strong>"}</div>
+ </div>
+ <br class="clear"/>
+ </div>
{/foreach}
{if $displayFullReport}
-<h2 id='titleGoalsByDimension'>
-{if isset($idGoal)}
- {'Goals_GoalConversionsBy'|translate:$goalName}
-{else}
- {'Goals_ConversionsOverviewBy'|translate}
-{/if}
-</h2>
-
-{$goalReportsByDimension}
+ <h2 id='titleGoalsByDimension'>
+ {if isset($idGoal)}
+ {'Goals_GoalConversionsBy'|translate:$goalName}
+ {else}
+ {'Goals_ConversionsOverviewBy'|translate}
+ {/if}
+ </h2>
+ {$goalReportsByDimension}
- {if $userCanEditGoals}
- {include file="Goals/templates/add_edit_goal.tpl"}
- {/if}
+ {if $userCanEditGoals}
+ {include file="Goals/templates/add_edit_goal.tpl"}
+ {/if}
{/if}
diff --git a/plugins/Goals/templates/single_goal.tpl b/plugins/Goals/templates/single_goal.tpl
index a65d0a0cdd..741a310f6c 100644
--- a/plugins/Goals/templates/single_goal.tpl
+++ b/plugins/Goals/templates/single_goal.tpl
@@ -1,44 +1,47 @@
-<link rel="stylesheet" type="text/css" href="plugins/Goals/templates/goals.css" />
+<link rel="stylesheet" type="text/css" href="plugins/Goals/templates/goals.css"/>
{include file="Goals/templates/title_and_evolution_graph.tpl"}
- <div class="clear"></div>
- {if $nb_conversions > 0}
- <h2>{'Goals_ConversionsOverview'|translate}</h2>
- <ul class="ulGoalTopElements">
-{if !isset($ecommerce)}
- {if isset($topDimensions.country)}<li>{'Goals_BestCountries'|translate} {include file='Goals/templates/list_top_dimension.tpl' topDimension=$topDimensions.country}</li>{/if}
- {if isset($topDimensions.keyword) && count($topDimensions.keyword)>0}<li>{'Goals_BestKeywords'|translate} {include file='Goals/templates/list_top_dimension.tpl' topDimension=$topDimensions.keyword}</li>{/if}
- {if isset($topDimensions.website) && count($topDimensions.website)>0}<li>{'Goals_BestReferers'|translate} {include file='Goals/templates/list_top_dimension.tpl' topDimension=$topDimensions.website}</li>{/if}
- <li>{'Goals_ReturningVisitorsConversionRateIs'|translate:"<b>$conversion_rate_returning</b>"}, {'Goals_NewVisitorsConversionRateIs'|translate:"<b>$conversion_rate_new</b>"}</li>
-{else}
- <li>{'Live_GoalRevenue'|translate}: {$revenue|money:$idSite}{if !empty($revenue_subtotal)},
- {'General_Subtotal'|translate}: {$revenue_subtotal|money:$idSite}{/if}{if !empty($revenue_tax)},
- {'General_Tax'|translate}: {$revenue_tax|money:$idSite}{/if}{if !empty($revenue_shipping)},
- {'General_Shipping'|translate}: {$revenue_shipping|money:$idSite}{/if}{if !empty($revenue_discount)},
- {'General_Discount'|translate}: {$revenue_discount|money:$idSite}{/if}
- </li>
+<div class="clear"></div>
+{if $nb_conversions > 0}
+ <h2>{'Goals_ConversionsOverview'|translate}</h2>
+ <ul class="ulGoalTopElements">
+ {if !isset($ecommerce)}
+ {if isset($topDimensions.country)}
+ <li>{'Goals_BestCountries'|translate} {include file='Goals/templates/list_top_dimension.tpl' topDimension=$topDimensions.country}</li>{/if}
+ {if isset($topDimensions.keyword) && count($topDimensions.keyword)>0}
+ <li>{'Goals_BestKeywords'|translate} {include file='Goals/templates/list_top_dimension.tpl' topDimension=$topDimensions.keyword}</li>{/if}
+ {if isset($topDimensions.website) && count($topDimensions.website)>0}
+ <li>{'Goals_BestReferers'|translate} {include file='Goals/templates/list_top_dimension.tpl' topDimension=$topDimensions.website}</li>{/if}
+ <li>{'Goals_ReturningVisitorsConversionRateIs'|translate:"<b>$conversion_rate_returning</b>"}
+ , {'Goals_NewVisitorsConversionRateIs'|translate:"<b>$conversion_rate_new</b>"}</li>
+ {else}
+ <li>{'Live_GoalRevenue'|translate}: {$revenue|money:$idSite}{if !empty($revenue_subtotal)},
+ {'General_Subtotal'|translate}: {$revenue_subtotal|money:$idSite}{/if}{if !empty($revenue_tax)},
+ {'General_Tax'|translate}: {$revenue_tax|money:$idSite}{/if}{if !empty($revenue_shipping)},
+ {'General_Shipping'|translate}: {$revenue_shipping|money:$idSite}{/if}{if !empty($revenue_discount)},
+ {'General_Discount'|translate}: {$revenue_discount|money:$idSite}{/if}
+ </li>
+ {/if}
+ </ul>
{/if}
- </ul>
- {/if}
{literal}
-<script type="text/javascript">
-$(document).ready( function() {
- $('.goalTopElement').tooltip();
-});
-</script>
+ <script type="text/javascript">
+ $(document).ready(function () {
+ $('.goalTopElement').tooltip();
+ });
+ </script>
{/literal}
{if $displayFullReport}
- {if $nb_conversions > 0 || !empty($cart_nb_conversions)}
- <h2 id='titleGoalsByDimension'>
- {if isset($idGoal)}
- {'Goals_GoalConversionsBy'|translate:$goalName}
- {else}
- {'Goals_ConversionsOverviewBy'|translate}
- {/if}
- </h2>
-
- {$goalReportsByDimension}
- {/if}
+ {if $nb_conversions > 0 || !empty($cart_nb_conversions)}
+ <h2 id='titleGoalsByDimension'>
+ {if isset($idGoal)}
+ {'Goals_GoalConversionsBy'|translate:$goalName}
+ {else}
+ {'Goals_ConversionsOverviewBy'|translate}
+ {/if}
+ </h2>
+ {$goalReportsByDimension}
+ {/if}
{/if}
diff --git a/plugins/Goals/templates/title_and_evolution_graph.tpl b/plugins/Goals/templates/title_and_evolution_graph.tpl
index 6065ef5bfc..d45d149295 100644
--- a/plugins/Goals/templates/title_and_evolution_graph.tpl
+++ b/plugins/Goals/templates/title_and_evolution_graph.tpl
@@ -1,67 +1,70 @@
<a name="evolutionGraph" graphId="{$nameGraphEvolution}"></a>
{if $displayFullReport}
- <h2>{if isset($goalName)}{'Goals_GoalX'|translate:$goalName}{else}{'Goals_GoalsOverview'|translate}{/if}</h2>
+ <h2>{if isset($goalName)}{'Goals_GoalX'|translate:$goalName}{else}{'Goals_GoalsOverview'|translate}{/if}</h2>
{/if}
{$graphEvolution}
<div id='leftcolumn' {if !$isWidget}style='width:33%'{/if}>
- <div class="sparkline">{sparkline src=$urlSparklineConversions}
- {if isset($ecommerce)} <strong>{$nb_conversions}</strong> {'General_EcommerceOrders'|translate} <img src='themes/default/images/ecommerceOrder.gif'>
- {else}{'Goals_Conversions'|translate:"<strong>$nb_conversions</strong>"}
- {/if}
- {if isset($goalAllowMultipleConversionsPerVisit) && $goalAllowMultipleConversionsPerVisit}
- ({'VisitsSummary_NbVisits'|translate:"<strong>$nb_visits_converted</strong>"})
- {/if}
- </div>
- {if $revenue != 0 || isset($ecommerce)}
- <div class="sparkline">{sparkline src=$urlSparklineRevenue}
- {assign var=revenue value=$revenue|money:$idSite}
- {if isset($ecommerce)}<strong>{$revenue}</strong> {'General_TotalRevenue'|translate}
- {else}{'Goals_OverallRevenue'|translate:"<strong>$revenue</strong>"}
- {/if}
- </div>
- {/if}
- {if isset($ecommerce)}
- <div class="sparkline">{sparkline src=$urlSparklineAverageOrderValue}
- <strong>{$avg_order_revenue|money:$idSite}</strong> {'General_AverageOrderValue'|translate}</div>
- {/if}
-
+ <div class="sparkline">{sparkline src=$urlSparklineConversions}
+ {if isset($ecommerce)}
+ <strong>{$nb_conversions}</strong>
+ {'General_EcommerceOrders'|translate}
+ <img src='themes/default/images/ecommerceOrder.gif'>
+ {else}{'Goals_Conversions'|translate:"<strong>$nb_conversions</strong>"}
+ {/if}
+ {if isset($goalAllowMultipleConversionsPerVisit) && $goalAllowMultipleConversionsPerVisit}
+ ({'VisitsSummary_NbVisits'|translate:"<strong>$nb_visits_converted</strong>"})
+ {/if}
+ </div>
+ {if $revenue != 0 || isset($ecommerce)}
+ <div class="sparkline">{sparkline src=$urlSparklineRevenue}
+ {assign var=revenue value=$revenue|money:$idSite}
+ {if isset($ecommerce)}<strong>{$revenue}</strong> {'General_TotalRevenue'|translate}
+ {else}{'Goals_OverallRevenue'|translate:"<strong>$revenue</strong>"}
+ {/if}
+ </div>
+ {/if}
+ {if isset($ecommerce)}
+ <div class="sparkline">{sparkline src=$urlSparklineAverageOrderValue}
+ <strong>{$avg_order_revenue|money:$idSite}</strong> {'General_AverageOrderValue'|translate}</div>
+ {/if}
+
</div>
<div id='leftcolumn' {if !$isWidget}style='width:33%'{/if}>
- <div class="sparkline">{sparkline src=$urlSparklineConversionRate}
- {if isset($ecommerce)}{capture assign='ecommerceOrdersText'}{'General_EcommerceOrders'|translate}{/capture}
- {'Goals_ConversionRate'|translate:"<strong>$conversion_rate</strong> $ecommerceOrdersText"}
- {else}
- {'Goals_OverallConversionRate'|translate:"<strong>$conversion_rate</strong>"}
- {/if}
- </div>
- {if isset($ecommerce)}
- <div class="sparkline">{sparkline src=$urlSparklinePurchasedProducts}
- <strong>{$items}</strong> {'General_PurchasedProducts'|translate}</div>
- {/if}
+ <div class="sparkline">{sparkline src=$urlSparklineConversionRate}
+ {if isset($ecommerce)}{capture assign='ecommerceOrdersText'}{'General_EcommerceOrders'|translate}{/capture}
+ {'Goals_ConversionRate'|translate:"<strong>$conversion_rate</strong> $ecommerceOrdersText"}
+ {else}
+ {'Goals_OverallConversionRate'|translate:"<strong>$conversion_rate</strong>"}
+ {/if}
+ </div>
+ {if isset($ecommerce)}
+ <div class="sparkline">{sparkline src=$urlSparklinePurchasedProducts}
+ <strong>{$items}</strong> {'General_PurchasedProducts'|translate}</div>
+ {/if}
</div>
{if isset($ecommerce)}
-<div id='rightcolumn' {if !$isWidget}style='width:30%'{/if}>
- <div>
- <img src='themes/default/images/ecommerceAbandonedCart.gif'> <i>{'General_AbandonedCarts'|translate}</i>
- </div>
-
- <div class="sparkline">{sparkline src=$cart_urlSparklineConversions}
- {capture assign='ecommerceAbandonedCartsText'}{'Goals_AbandonedCart'|translate}{/capture}
- <strong>{$cart_nb_conversions}</strong> {'General_VisitsWith'|translate:$ecommerceAbandonedCartsText}
- </div>
-
- <div class="sparkline">{sparkline src=$cart_urlSparklineRevenue}
- {capture assign=revenue}{$cart_revenue|money:$idSite}{/capture}
- {capture assign=revenueText}{'Live_GoalRevenue'|translate}{/capture}
- <strong>{$revenue}</strong> {'Goals_LeftInCart'|translate:$revenueText}
- </div>
-
- <div class="sparkline">{sparkline src=$cart_urlSparklineConversionRate}
- <strong>{$cart_conversion_rate}</strong> {'General_VisitsWith'|translate:$ecommerceAbandonedCartsText}
- </div>
-</div>
+ <div id='rightcolumn' {if !$isWidget}style='width:30%'{/if}>
+ <div>
+ <img src='themes/default/images/ecommerceAbandonedCart.gif'> <i>{'General_AbandonedCarts'|translate}</i>
+ </div>
+
+ <div class="sparkline">{sparkline src=$cart_urlSparklineConversions}
+ {capture assign='ecommerceAbandonedCartsText'}{'Goals_AbandonedCart'|translate}{/capture}
+ <strong>{$cart_nb_conversions}</strong> {'General_VisitsWith'|translate:$ecommerceAbandonedCartsText}
+ </div>
+
+ <div class="sparkline">{sparkline src=$cart_urlSparklineRevenue}
+ {capture assign=revenue}{$cart_revenue|money:$idSite}{/capture}
+ {capture assign=revenueText}{'Live_GoalRevenue'|translate}{/capture}
+ <strong>{$revenue}</strong> {'Goals_LeftInCart'|translate:$revenueText}
+ </div>
+
+ <div class="sparkline">{sparkline src=$cart_urlSparklineConversionRate}
+ <strong>{$cart_conversion_rate}</strong> {'General_VisitsWith'|translate:$ecommerceAbandonedCartsText}
+ </div>
+ </div>
{/if}
{include file="CoreHome/templates/sparkline_footer.tpl"}
diff --git a/plugins/ImageGraph/API.php b/plugins/ImageGraph/API.php
index d1d5bace51..c4a16297b4 100644
--- a/plugins/ImageGraph/API.php
+++ b/plugins/ImageGraph/API.php
@@ -1,7 +1,7 @@
<?php
/**
* Piwik - Open source web analytics
- *
+ *
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*
@@ -24,562 +24,497 @@
*/
class Piwik_ImageGraph_API
{
- const FILENAME_KEY = 'filename';
- const TRUNCATE_KEY = 'truncate';
- const WIDTH_KEY = 'width';
- const HEIGHT_KEY = 'height';
- const MAX_WIDTH = 2048;
- const MAX_HEIGHT = 2048;
-
- static private $DEFAULT_PARAMETERS = array(
- Piwik_ImageGraph_StaticGraph::GRAPH_TYPE_BASIC_LINE => array(
- self::FILENAME_KEY => 'BasicLine',
- self::TRUNCATE_KEY => 6,
- self::WIDTH_KEY => 1044,
- self::HEIGHT_KEY => 290,
- ),
- Piwik_ImageGraph_StaticGraph::GRAPH_TYPE_VERTICAL_BAR => array(
- self::FILENAME_KEY => 'BasicBar',
- self::TRUNCATE_KEY => 6,
- self::WIDTH_KEY => 1044,
- self::HEIGHT_KEY => 290,
- ),
- Piwik_ImageGraph_StaticGraph::GRAPH_TYPE_HORIZONTAL_BAR => array(
- self::FILENAME_KEY => 'HorizontalBar',
- self::TRUNCATE_KEY => null, // horizontal bar graphs are dynamically truncated
- self::WIDTH_KEY => 800,
- self::HEIGHT_KEY => 290,
- ),
- Piwik_ImageGraph_StaticGraph::GRAPH_TYPE_3D_PIE => array(
- self::FILENAME_KEY => '3DPie',
- self::TRUNCATE_KEY => 5,
- self::WIDTH_KEY => 1044,
- self::HEIGHT_KEY => 290,
- ),
- Piwik_ImageGraph_StaticGraph::GRAPH_TYPE_BASIC_PIE => array(
- self::FILENAME_KEY => 'BasicPie',
- self::TRUNCATE_KEY => 5,
- self::WIDTH_KEY => 1044,
- self::HEIGHT_KEY => 290,
- ),
- );
-
- static private $DEFAULT_GRAPH_TYPE_OVERRIDE = array(
- 'UserSettings_getPlugin' => array(
- false // override if !$isMultiplePeriod
- => Piwik_ImageGraph_StaticGraph::GRAPH_TYPE_HORIZONTAL_BAR,
- ),
- 'Referers_getRefererType' => array(
- false // override if !$isMultiplePeriod
- => Piwik_ImageGraph_StaticGraph::GRAPH_TYPE_HORIZONTAL_BAR,
- ),
- );
-
- const GRAPH_OUTPUT_INLINE = 0;
- const GRAPH_OUTPUT_FILE = 1;
- const GRAPH_OUTPUT_PHP = 2;
-
- const DEFAULT_ORDINATE_METRIC = 'nb_visits';
- const FONT_DIR = '/plugins/ImageGraph/fonts/';
- const DEFAULT_FONT = 'tahoma.ttf';
- const UNICODE_FONT = 'unifont.ttf';
- const DEFAULT_FONT_SIZE = 9;
- const DEFAULT_LEGEND_FONT_SIZE_OFFSET = 2;
-
- // number of row evolutions to plot when no labels are specified, can be overridden using &filter_limit
- const DEFAULT_NB_ROW_EVOLUTIONS = 5;
- const MAX_NB_ROW_LABELS = 10;
-
- static private $instance = null;
-
- /**
- * @return Piwik_ImageGraph_API
- */
- static public function getInstance()
- {
- if (self::$instance == null)
- {
- $c = __CLASS__;
- self::$instance = new $c();
- }
- return self::$instance;
- }
-
- public function get($idSite, $period, $date, $apiModule, $apiAction, $graphType = false,
- $outputType = Piwik_ImageGraph_API::GRAPH_OUTPUT_INLINE, $columns = false, $labels = false, $showLegend = true,
- $width = false, $height = false, $fontSize = Piwik_ImageGraph_API::DEFAULT_FONT_SIZE, $legendFontSize = false,
- $aliasedGraph = true, $idGoal = false, $colors = false, $idSubtable = false, $legendAppendMetric = true)
- {
- Piwik::checkUserHasViewAccess($idSite);
-
- // Health check - should we also test for GD2 only?
- if(!Piwik::isGdExtensionEnabled())
- {
- throw new Exception('Error: To create graphs in Piwik, please enable GD php extension (with Freetype support) in php.ini, and restart your web server.');
- }
-
- $useUnicodeFont = array(
- 'am', 'ar', 'el', 'fa' , 'fi', 'he', 'ja', 'ka', 'ko', 'te', 'th', 'zh-cn', 'zh-tw',
- );
- $languageLoaded = Piwik_Translate::getInstance()->getLanguageLoaded();
- $font = self::getFontPath(self::DEFAULT_FONT);
- if(in_array($languageLoaded, $useUnicodeFont))
- {
- $unicodeFontPath = self::getFontPath(self::UNICODE_FONT);
- $font = file_exists($unicodeFontPath) ? $unicodeFontPath : $font;
- }
-
- // save original GET to reset after processing. Important for API-in-API-call
- $savedGET = $_GET;
-
- try
- {
- $apiParameters = array();
- if(!empty($idGoal)) {
- $apiParameters = array( 'idGoal' => $idGoal);
- }
- // Fetch the metadata for given api-action
- $metadata = Piwik_API_API::getInstance()->getMetadata(
- $idSite, $apiModule, $apiAction, $apiParameters, $languageLoaded, $period, $date,
- $hideMetricsDoc = false, $showSubtableReports = true);
- if(!$metadata)
- {
- throw new Exception('Invalid API Module and/or API Action');
- }
-
- $metadata = $metadata[0];
- $reportHasDimension = !empty($metadata['dimension']);
- $constantRowsCount = !empty($metadata['constantRowsCount']);
-
- $isMultiplePeriod = Piwik_Archive::isMultiplePeriod($date, $period);
- if(!$reportHasDimension && !$isMultiplePeriod)
- {
- throw new Exception('The graph cannot be drawn for this combination of \'date\' and \'period\' parameters.');
- }
-
- if(empty($legendFontSize))
- {
- $legendFontSize = (int)$fontSize + self::DEFAULT_LEGEND_FONT_SIZE_OFFSET;
- }
-
- if(empty($graphType))
- {
- if($isMultiplePeriod)
- {
- $graphType = Piwik_ImageGraph_StaticGraph::GRAPH_TYPE_BASIC_LINE;
- }
- else
- {
- if($constantRowsCount)
- {
- $graphType = Piwik_ImageGraph_StaticGraph::GRAPH_TYPE_VERTICAL_BAR;
- }
- else
- {
- $graphType = Piwik_ImageGraph_StaticGraph::GRAPH_TYPE_HORIZONTAL_BAR;
- }
- }
-
- $reportUniqueId = $metadata['uniqueId'];
- if(isset(self::$DEFAULT_GRAPH_TYPE_OVERRIDE[$reportUniqueId][$isMultiplePeriod]))
- {
- $graphType = self::$DEFAULT_GRAPH_TYPE_OVERRIDE[$reportUniqueId][$isMultiplePeriod];
- }
- }
- else
- {
- $availableGraphTypes = Piwik_ImageGraph_StaticGraph::getAvailableStaticGraphTypes();
- if (!in_array($graphType, $availableGraphTypes))
- {
- throw new Exception(
- Piwik_TranslateException(
- 'General_ExceptionInvalidStaticGraphType',
- array($graphType, implode(', ', $availableGraphTypes))
- )
- );
- }
- }
-
- $width = (int)$width;
- $height = (int)$height;
- if(empty($width))
- {
- $width = self::$DEFAULT_PARAMETERS[$graphType][self::WIDTH_KEY];
- }
- if(empty($height))
- {
- $height = self::$DEFAULT_PARAMETERS[$graphType][self::HEIGHT_KEY];
- }
-
- // Cap width and height to a safe amount
- $width = min($width, self::MAX_WIDTH);
- $height = min($height, self::MAX_HEIGHT);
-
- $reportColumns = array_merge(
- !empty($metadata['metrics']) ? $metadata['metrics'] : array(),
- !empty($metadata['processedMetrics']) ? $metadata['processedMetrics'] : array(),
- !empty($metadata['metricsGoal']) ? $metadata['metricsGoal'] : array(),
- !empty($metadata['processedMetricsGoal']) ? $metadata['processedMetricsGoal'] : array()
- );
-
- $ordinateColumns = array();
- if(empty($columns))
- {
- $ordinateColumns[] =
- empty($reportColumns[self::DEFAULT_ORDINATE_METRIC]) ? key($metadata['metrics']) : self::DEFAULT_ORDINATE_METRIC;
- }
- else
- {
- $ordinateColumns = explode(',', $columns);
- foreach($ordinateColumns as $column)
- {
- if(empty($reportColumns[$column]))
- {
- throw new Exception(
- Piwik_Translate(
- 'ImageGraph_ColumnOrdinateMissing',
- array($column, implode(',',array_keys($reportColumns)))
- )
- );
- }
- }
- }
-
- $ordinateLabels = array();
- foreach($ordinateColumns as $column)
- {
- $ordinateLabels[$column] = $reportColumns[$column];
- }
-
- // sort and truncate filters
- $defaultFilterTruncate = self::$DEFAULT_PARAMETERS[$graphType][self::TRUNCATE_KEY];
- switch($graphType)
- {
- case Piwik_ImageGraph_StaticGraph::GRAPH_TYPE_3D_PIE:
- case Piwik_ImageGraph_StaticGraph::GRAPH_TYPE_BASIC_PIE:
-
- if(count($ordinateColumns) > 1)
- {
- // pChart doesn't support multiple series on pie charts
- throw new Exception("Pie charts do not currently support multiple series");
- }
-
- $_GET['filter_sort_column'] = reset($ordinateColumns);
- $this->setFilterTruncate($defaultFilterTruncate);
- break;
-
- case Piwik_ImageGraph_StaticGraph::GRAPH_TYPE_VERTICAL_BAR:
- case Piwik_ImageGraph_StaticGraph::GRAPH_TYPE_BASIC_LINE:
-
- if(!$isMultiplePeriod && !$constantRowsCount)
- {
- $this->setFilterTruncate($defaultFilterTruncate);
- }
- break;
- }
-
- $ordinateLogos = array();
-
- // row evolutions
- if($isMultiplePeriod && $reportHasDimension)
- {
- $plottedMetric = reset($ordinateColumns);
-
- // when no labels are specified, getRowEvolution returns the top N=filter_limit row evolutions
- // rows are sorted using filter_sort_column (see Piwik_API_DataTableGenericFilter for more info)
- if(!$labels)
- {
- $savedFilterSortColumnValue = Piwik_Common::getRequestVar('filter_sort_column', '');
- $_GET['filter_sort_column'] = $plottedMetric;
-
- $savedFilterLimitValue = Piwik_Common::getRequestVar('filter_limit', -1, 'int');
- if($savedFilterLimitValue == -1 || $savedFilterLimitValue > self::MAX_NB_ROW_LABELS)
- {
- $_GET['filter_limit'] = self::DEFAULT_NB_ROW_EVOLUTIONS;
- }
- }
-
- $processedReport = Piwik_API_API::getInstance()->getRowEvolution(
- $idSite,
- $period,
- $date,
- $apiModule,
- $apiAction,
- $labels,
- $segment = false,
- $plottedMetric,
- $languageLoaded,
- $idGoal,
- $legendAppendMetric,
- $labelUseAbsoluteUrl = false
- );
-
- //@review this test will need to be updated after evaluating the @review comment in API/API.php
- if(!$processedReport)
- {
- throw new Exception(Piwik_Translate('General_NoDataForGraph_js'));
- }
-
- // restoring generic filter parameters
- if(!$labels)
- {
- $_GET['filter_sort_column'] = $savedFilterSortColumnValue;
- if($savedFilterLimitValue != -1)
- {
- $_GET['filter_limit'] = $savedFilterLimitValue;
- }
- }
-
- // retrieve metric names & labels
- $metrics = $processedReport['metadata']['metrics'];
- $ordinateLabels = array();
-
- // getRowEvolution returned more than one label
- if(!array_key_exists($plottedMetric, $metrics))
- {
- $ordinateColumns = array();
- $i = 0;
- foreach($metrics as $metric => $info)
- {
- $ordinateColumn = $plottedMetric . '_' . $i++;
- $ordinateColumns[] = $metric;
- $ordinateLabels[$ordinateColumn] = $info['name'];
-
- if(isset($info['logo']))
- {
- $ordinateLogo = $info['logo'];
-
- // @review pChart does not support gifs in graph legends, would it be possible to convert all plugin pictures (cookie.gif, flash.gif, ..) to png files?
- if(!strstr($ordinateLogo, '.gif'))
- {
- $absoluteLogoPath = self::getAbsoluteLogoPath($ordinateLogo);
- if(file_exists($absoluteLogoPath))
- {
- $ordinateLogos[$ordinateColumn] = $absoluteLogoPath;
- }
- }
- }
- }
- }
- else
- {
- $ordinateLabels[$plottedMetric] = $processedReport['label'] . ' (' . $metrics[$plottedMetric]['name'] . ')';
- }
- }
- else
- {
- $processedReport = Piwik_API_API::getInstance()->getProcessedReport(
- $idSite,
- $period,
- $date,
- $apiModule,
- $apiAction,
- $segment = false,
- $apiParameters = false,
- $idGoal,
- $languageLoaded,
- $showTimer = true,
- $hideMetricsDoc = false,
- $idSubtable,
- $showRawMetrics = false
- );
- }
- // prepare abscissa and ordinate series
- $abscissaSeries = array();
- $abscissaLogos = array();
- $ordinateSeries = array();
- $reportData = $processedReport['reportData'];
- $hasData = false;
- $hasNonZeroValue = false;
-
- if(!$isMultiplePeriod)
- {
- $reportMetadata = $processedReport['reportMetadata']->getRows();
-
- $i = 0;
- // $reportData instanceof Piwik_DataTable
- foreach($reportData->getRows() as $row) // Piwik_DataTable_Row[]
- {
- // $row instanceof Piwik_DataTable_Row
- $rowData = $row->getColumns(); // Associative Array
- $abscissaSeries[] = Piwik_Common::unsanitizeInputValue($rowData['label']);
-
- foreach($ordinateColumns as $column)
- {
- $parsedOrdinateValue = $this->parseOrdinateValue($rowData[$column]);
- $hasData = true;
-
- if($parsedOrdinateValue != 0)
- {
- $hasNonZeroValue = true;
- }
- $ordinateSeries[$column][] = $parsedOrdinateValue;
- }
-
- if(isset($reportMetadata[$i]))
- {
- $rowMetadata = $reportMetadata[$i]->getColumns();
- if(isset($rowMetadata['logo']))
- {
- $absoluteLogoPath = self::getAbsoluteLogoPath($rowMetadata['logo']);
- if(file_exists($absoluteLogoPath))
- {
- $abscissaLogos[$i] = $absoluteLogoPath;
- }
- }
- }
- $i++;
- }
- }
- else // if the report has no dimension we have multiple reports each with only one row within the reportData
- {
- // $periodsData instanceof Piwik_DataTable_Simple[]
- $periodsData = array_values($reportData->getArray());
- $periodsCount = count($periodsData);
-
- for ($i = 0 ; $i < $periodsCount ; $i++)
- {
- // $periodsData[$i] instanceof Piwik_DataTable_Simple
- // $rows instanceof Piwik_DataTable_Row[]
- if(empty($periodsData[$i]))
- {
- continue;
- }
- $rows = $periodsData[$i]->getRows();
-
- if(array_key_exists(0, $rows))
- {
- $rowData = $rows[0]->getColumns(); // associative Array
-
- foreach($ordinateColumns as $column)
- {
- $ordinateValue = $rowData[$column];
- $parsedOrdinateValue = $this->parseOrdinateValue($ordinateValue);
-
- $hasData = true;
-
- if(!empty($parsedOrdinateValue))
- {
- $hasNonZeroValue = true;
- }
-
- $ordinateSeries[$column][] = $parsedOrdinateValue;
- }
-
- }
- else
- {
- foreach($ordinateColumns as $column)
- {
- $ordinateSeries[$column][] = 0;
- }
- }
-
- $rowId = $periodsData[$i]->metadata['period']->getLocalizedShortString();
- $abscissaSeries[] = Piwik_Common::unsanitizeInputValue($rowId);
- }
- }
-
- if(!$hasData || !$hasNonZeroValue)
- {
- throw new Exception(Piwik_Translate('General_NoDataForGraph_js'));
- }
-
- //Setup the graph
- $graph = Piwik_ImageGraph_StaticGraph::factory($graphType);
- $graph->setWidth($width);
- $graph->setHeight($height);
- $graph->setFont($font);
- $graph->setFontSize($fontSize);
- $graph->setLegendFontSize($legendFontSize);
- $graph->setOrdinateLabels($ordinateLabels);
- $graph->setShowLegend($showLegend);
- $graph->setAliasedGraph($aliasedGraph);
- $graph->setAbscissaSeries($abscissaSeries);
- $graph->setAbscissaLogos($abscissaLogos);
- $graph->setOrdinateSeries($ordinateSeries);
- $graph->setOrdinateLogos($ordinateLogos);
- $graph->setColors(!empty($colors) ? explode(',', $colors) : array());
- if($period == 'day')
- {
- $graph->setForceSkippedLabels(6);
- }
-
- // render graph
- $graph->renderGraph();
-
- } catch (Exception $e) {
-
- $graph = new Piwik_ImageGraph_StaticGraph_Exception();
- $graph->setWidth($width);
- $graph->setHeight($height);
- $graph->setFont($font);
- $graph->setFontSize($fontSize);
- $graph->setException($e);
- $graph->renderGraph();
- }
-
- // restoring get parameters
- $_GET = $savedGET;
-
- switch($outputType)
- {
- case self::GRAPH_OUTPUT_FILE:
- if($idGoal != '')
- {
- $idGoal = '_' . $idGoal;
- }
- $fileName = self::$DEFAULT_PARAMETERS[$graphType][self::FILENAME_KEY] . '_' . $apiModule . '_' . $apiAction . $idGoal . ' ' . str_replace(',', '-', $date) . ' ' . $idSite . '.png';
- $fileName = str_replace(array(' ','/'), '_', $fileName);
-
- if(!Piwik_Common::isValidFilename($fileName))
- {
- throw new Exception('Error: Image graph filename ' . $fileName . ' is not valid.');
- }
-
- return $graph->sendToDisk($fileName);
-
- case self::GRAPH_OUTPUT_PHP:
- return $graph->getRenderedImage();
-
- case self::GRAPH_OUTPUT_INLINE:
- default:
- $graph->sendToBrowser();
- exit;
- }
- }
-
- private function setFilterTruncate($default)
- {
- $_GET['filter_truncate'] = Piwik_Common::getRequestVar('filter_truncate', $default, 'int');
- }
-
- private static function parseOrdinateValue($ordinateValue)
- {
- $ordinateValue = @str_replace(',', '.', $ordinateValue);
-
- // convert hh:mm:ss formatted time values to number of seconds
- if(preg_match('/([0-9]{1,2}):([0-9]{1,2}):([0-9]{1,2})/', $ordinateValue, $matches))
- {
- $hour = $matches[1];
- $min = $matches[2];
- $sec = $matches[3];
-
- $ordinateValue = ($hour * 3600) + ($min * 60) + $sec;
- }
-
- // OK, only numbers from here please (strip out currency sign)
- $ordinateValue = preg_replace('/[^0-9.]/', '', $ordinateValue);
- return $ordinateValue;
- }
-
- private static function getFontPath($font)
- {
- return PIWIK_INCLUDE_PATH . self::FONT_DIR . $font;
- }
-
- protected static function getAbsoluteLogoPath($relativeLogoPath)
- {
- return PIWIK_INCLUDE_PATH . '/' . $relativeLogoPath;
- }
+ const FILENAME_KEY = 'filename';
+ const TRUNCATE_KEY = 'truncate';
+ const WIDTH_KEY = 'width';
+ const HEIGHT_KEY = 'height';
+ const MAX_WIDTH = 2048;
+ const MAX_HEIGHT = 2048;
+
+ static private $DEFAULT_PARAMETERS = array(
+ Piwik_ImageGraph_StaticGraph::GRAPH_TYPE_BASIC_LINE => array(
+ self::FILENAME_KEY => 'BasicLine',
+ self::TRUNCATE_KEY => 6,
+ self::WIDTH_KEY => 1044,
+ self::HEIGHT_KEY => 290,
+ ),
+ Piwik_ImageGraph_StaticGraph::GRAPH_TYPE_VERTICAL_BAR => array(
+ self::FILENAME_KEY => 'BasicBar',
+ self::TRUNCATE_KEY => 6,
+ self::WIDTH_KEY => 1044,
+ self::HEIGHT_KEY => 290,
+ ),
+ Piwik_ImageGraph_StaticGraph::GRAPH_TYPE_HORIZONTAL_BAR => array(
+ self::FILENAME_KEY => 'HorizontalBar',
+ self::TRUNCATE_KEY => null, // horizontal bar graphs are dynamically truncated
+ self::WIDTH_KEY => 800,
+ self::HEIGHT_KEY => 290,
+ ),
+ Piwik_ImageGraph_StaticGraph::GRAPH_TYPE_3D_PIE => array(
+ self::FILENAME_KEY => '3DPie',
+ self::TRUNCATE_KEY => 5,
+ self::WIDTH_KEY => 1044,
+ self::HEIGHT_KEY => 290,
+ ),
+ Piwik_ImageGraph_StaticGraph::GRAPH_TYPE_BASIC_PIE => array(
+ self::FILENAME_KEY => 'BasicPie',
+ self::TRUNCATE_KEY => 5,
+ self::WIDTH_KEY => 1044,
+ self::HEIGHT_KEY => 290,
+ ),
+ );
+
+ static private $DEFAULT_GRAPH_TYPE_OVERRIDE = array(
+ 'UserSettings_getPlugin' => array(
+ false // override if !$isMultiplePeriod
+ => Piwik_ImageGraph_StaticGraph::GRAPH_TYPE_HORIZONTAL_BAR,
+ ),
+ 'Referers_getRefererType' => array(
+ false // override if !$isMultiplePeriod
+ => Piwik_ImageGraph_StaticGraph::GRAPH_TYPE_HORIZONTAL_BAR,
+ ),
+ );
+
+ const GRAPH_OUTPUT_INLINE = 0;
+ const GRAPH_OUTPUT_FILE = 1;
+ const GRAPH_OUTPUT_PHP = 2;
+
+ const DEFAULT_ORDINATE_METRIC = 'nb_visits';
+ const FONT_DIR = '/plugins/ImageGraph/fonts/';
+ const DEFAULT_FONT = 'tahoma.ttf';
+ const UNICODE_FONT = 'unifont.ttf';
+ const DEFAULT_FONT_SIZE = 9;
+ const DEFAULT_LEGEND_FONT_SIZE_OFFSET = 2;
+
+ // number of row evolutions to plot when no labels are specified, can be overridden using &filter_limit
+ const DEFAULT_NB_ROW_EVOLUTIONS = 5;
+ const MAX_NB_ROW_LABELS = 10;
+
+ static private $instance = null;
+
+ /**
+ * @return Piwik_ImageGraph_API
+ */
+ static public function getInstance()
+ {
+ if (self::$instance == null) {
+ $c = __CLASS__;
+ self::$instance = new $c();
+ }
+ return self::$instance;
+ }
+
+ public function get($idSite, $period, $date, $apiModule, $apiAction, $graphType = false,
+ $outputType = Piwik_ImageGraph_API::GRAPH_OUTPUT_INLINE, $columns = false, $labels = false, $showLegend = true,
+ $width = false, $height = false, $fontSize = Piwik_ImageGraph_API::DEFAULT_FONT_SIZE, $legendFontSize = false,
+ $aliasedGraph = true, $idGoal = false, $colors = false, $idSubtable = false, $legendAppendMetric = true)
+ {
+ Piwik::checkUserHasViewAccess($idSite);
+
+ // Health check - should we also test for GD2 only?
+ if (!Piwik::isGdExtensionEnabled()) {
+ throw new Exception('Error: To create graphs in Piwik, please enable GD php extension (with Freetype support) in php.ini, and restart your web server.');
+ }
+
+ $useUnicodeFont = array(
+ 'am', 'ar', 'el', 'fa', 'fi', 'he', 'ja', 'ka', 'ko', 'te', 'th', 'zh-cn', 'zh-tw',
+ );
+ $languageLoaded = Piwik_Translate::getInstance()->getLanguageLoaded();
+ $font = self::getFontPath(self::DEFAULT_FONT);
+ if (in_array($languageLoaded, $useUnicodeFont)) {
+ $unicodeFontPath = self::getFontPath(self::UNICODE_FONT);
+ $font = file_exists($unicodeFontPath) ? $unicodeFontPath : $font;
+ }
+
+ // save original GET to reset after processing. Important for API-in-API-call
+ $savedGET = $_GET;
+
+ try {
+ $apiParameters = array();
+ if (!empty($idGoal)) {
+ $apiParameters = array('idGoal' => $idGoal);
+ }
+ // Fetch the metadata for given api-action
+ $metadata = Piwik_API_API::getInstance()->getMetadata(
+ $idSite, $apiModule, $apiAction, $apiParameters, $languageLoaded, $period, $date,
+ $hideMetricsDoc = false, $showSubtableReports = true);
+ if (!$metadata) {
+ throw new Exception('Invalid API Module and/or API Action');
+ }
+
+ $metadata = $metadata[0];
+ $reportHasDimension = !empty($metadata['dimension']);
+ $constantRowsCount = !empty($metadata['constantRowsCount']);
+
+ $isMultiplePeriod = Piwik_Archive::isMultiplePeriod($date, $period);
+ if (!$reportHasDimension && !$isMultiplePeriod) {
+ throw new Exception('The graph cannot be drawn for this combination of \'date\' and \'period\' parameters.');
+ }
+
+ if (empty($legendFontSize)) {
+ $legendFontSize = (int)$fontSize + self::DEFAULT_LEGEND_FONT_SIZE_OFFSET;
+ }
+
+ if (empty($graphType)) {
+ if ($isMultiplePeriod) {
+ $graphType = Piwik_ImageGraph_StaticGraph::GRAPH_TYPE_BASIC_LINE;
+ } else {
+ if ($constantRowsCount) {
+ $graphType = Piwik_ImageGraph_StaticGraph::GRAPH_TYPE_VERTICAL_BAR;
+ } else {
+ $graphType = Piwik_ImageGraph_StaticGraph::GRAPH_TYPE_HORIZONTAL_BAR;
+ }
+ }
+
+ $reportUniqueId = $metadata['uniqueId'];
+ if (isset(self::$DEFAULT_GRAPH_TYPE_OVERRIDE[$reportUniqueId][$isMultiplePeriod])) {
+ $graphType = self::$DEFAULT_GRAPH_TYPE_OVERRIDE[$reportUniqueId][$isMultiplePeriod];
+ }
+ } else {
+ $availableGraphTypes = Piwik_ImageGraph_StaticGraph::getAvailableStaticGraphTypes();
+ if (!in_array($graphType, $availableGraphTypes)) {
+ throw new Exception(
+ Piwik_TranslateException(
+ 'General_ExceptionInvalidStaticGraphType',
+ array($graphType, implode(', ', $availableGraphTypes))
+ )
+ );
+ }
+ }
+
+ $width = (int)$width;
+ $height = (int)$height;
+ if (empty($width)) {
+ $width = self::$DEFAULT_PARAMETERS[$graphType][self::WIDTH_KEY];
+ }
+ if (empty($height)) {
+ $height = self::$DEFAULT_PARAMETERS[$graphType][self::HEIGHT_KEY];
+ }
+
+ // Cap width and height to a safe amount
+ $width = min($width, self::MAX_WIDTH);
+ $height = min($height, self::MAX_HEIGHT);
+
+ $reportColumns = array_merge(
+ !empty($metadata['metrics']) ? $metadata['metrics'] : array(),
+ !empty($metadata['processedMetrics']) ? $metadata['processedMetrics'] : array(),
+ !empty($metadata['metricsGoal']) ? $metadata['metricsGoal'] : array(),
+ !empty($metadata['processedMetricsGoal']) ? $metadata['processedMetricsGoal'] : array()
+ );
+
+ $ordinateColumns = array();
+ if (empty($columns)) {
+ $ordinateColumns[] =
+ empty($reportColumns[self::DEFAULT_ORDINATE_METRIC]) ? key($metadata['metrics']) : self::DEFAULT_ORDINATE_METRIC;
+ } else {
+ $ordinateColumns = explode(',', $columns);
+ foreach ($ordinateColumns as $column) {
+ if (empty($reportColumns[$column])) {
+ throw new Exception(
+ Piwik_Translate(
+ 'ImageGraph_ColumnOrdinateMissing',
+ array($column, implode(',', array_keys($reportColumns)))
+ )
+ );
+ }
+ }
+ }
+
+ $ordinateLabels = array();
+ foreach ($ordinateColumns as $column) {
+ $ordinateLabels[$column] = $reportColumns[$column];
+ }
+
+ // sort and truncate filters
+ $defaultFilterTruncate = self::$DEFAULT_PARAMETERS[$graphType][self::TRUNCATE_KEY];
+ switch ($graphType) {
+ case Piwik_ImageGraph_StaticGraph::GRAPH_TYPE_3D_PIE:
+ case Piwik_ImageGraph_StaticGraph::GRAPH_TYPE_BASIC_PIE:
+
+ if (count($ordinateColumns) > 1) {
+ // pChart doesn't support multiple series on pie charts
+ throw new Exception("Pie charts do not currently support multiple series");
+ }
+
+ $_GET['filter_sort_column'] = reset($ordinateColumns);
+ $this->setFilterTruncate($defaultFilterTruncate);
+ break;
+
+ case Piwik_ImageGraph_StaticGraph::GRAPH_TYPE_VERTICAL_BAR:
+ case Piwik_ImageGraph_StaticGraph::GRAPH_TYPE_BASIC_LINE:
+
+ if (!$isMultiplePeriod && !$constantRowsCount) {
+ $this->setFilterTruncate($defaultFilterTruncate);
+ }
+ break;
+ }
+
+ $ordinateLogos = array();
+
+ // row evolutions
+ if ($isMultiplePeriod && $reportHasDimension) {
+ $plottedMetric = reset($ordinateColumns);
+
+ // when no labels are specified, getRowEvolution returns the top N=filter_limit row evolutions
+ // rows are sorted using filter_sort_column (see Piwik_API_DataTableGenericFilter for more info)
+ if (!$labels) {
+ $savedFilterSortColumnValue = Piwik_Common::getRequestVar('filter_sort_column', '');
+ $_GET['filter_sort_column'] = $plottedMetric;
+
+ $savedFilterLimitValue = Piwik_Common::getRequestVar('filter_limit', -1, 'int');
+ if ($savedFilterLimitValue == -1 || $savedFilterLimitValue > self::MAX_NB_ROW_LABELS) {
+ $_GET['filter_limit'] = self::DEFAULT_NB_ROW_EVOLUTIONS;
+ }
+ }
+
+ $processedReport = Piwik_API_API::getInstance()->getRowEvolution(
+ $idSite,
+ $period,
+ $date,
+ $apiModule,
+ $apiAction,
+ $labels,
+ $segment = false,
+ $plottedMetric,
+ $languageLoaded,
+ $idGoal,
+ $legendAppendMetric,
+ $labelUseAbsoluteUrl = false
+ );
+
+ //@review this test will need to be updated after evaluating the @review comment in API/API.php
+ if (!$processedReport) {
+ throw new Exception(Piwik_Translate('General_NoDataForGraph_js'));
+ }
+
+ // restoring generic filter parameters
+ if (!$labels) {
+ $_GET['filter_sort_column'] = $savedFilterSortColumnValue;
+ if ($savedFilterLimitValue != -1) {
+ $_GET['filter_limit'] = $savedFilterLimitValue;
+ }
+ }
+
+ // retrieve metric names & labels
+ $metrics = $processedReport['metadata']['metrics'];
+ $ordinateLabels = array();
+
+ // getRowEvolution returned more than one label
+ if (!array_key_exists($plottedMetric, $metrics)) {
+ $ordinateColumns = array();
+ $i = 0;
+ foreach ($metrics as $metric => $info) {
+ $ordinateColumn = $plottedMetric . '_' . $i++;
+ $ordinateColumns[] = $metric;
+ $ordinateLabels[$ordinateColumn] = $info['name'];
+
+ if (isset($info['logo'])) {
+ $ordinateLogo = $info['logo'];
+
+ // @review pChart does not support gifs in graph legends, would it be possible to convert all plugin pictures (cookie.gif, flash.gif, ..) to png files?
+ if (!strstr($ordinateLogo, '.gif')) {
+ $absoluteLogoPath = self::getAbsoluteLogoPath($ordinateLogo);
+ if (file_exists($absoluteLogoPath)) {
+ $ordinateLogos[$ordinateColumn] = $absoluteLogoPath;
+ }
+ }
+ }
+ }
+ } else {
+ $ordinateLabels[$plottedMetric] = $processedReport['label'] . ' (' . $metrics[$plottedMetric]['name'] . ')';
+ }
+ } else {
+ $processedReport = Piwik_API_API::getInstance()->getProcessedReport(
+ $idSite,
+ $period,
+ $date,
+ $apiModule,
+ $apiAction,
+ $segment = false,
+ $apiParameters = false,
+ $idGoal,
+ $languageLoaded,
+ $showTimer = true,
+ $hideMetricsDoc = false,
+ $idSubtable,
+ $showRawMetrics = false
+ );
+ }
+ // prepare abscissa and ordinate series
+ $abscissaSeries = array();
+ $abscissaLogos = array();
+ $ordinateSeries = array();
+ $reportData = $processedReport['reportData'];
+ $hasData = false;
+ $hasNonZeroValue = false;
+
+ if (!$isMultiplePeriod) {
+ $reportMetadata = $processedReport['reportMetadata']->getRows();
+
+ $i = 0;
+ // $reportData instanceof Piwik_DataTable
+ foreach ($reportData->getRows() as $row) // Piwik_DataTable_Row[]
+ {
+ // $row instanceof Piwik_DataTable_Row
+ $rowData = $row->getColumns(); // Associative Array
+ $abscissaSeries[] = Piwik_Common::unsanitizeInputValue($rowData['label']);
+
+ foreach ($ordinateColumns as $column) {
+ $parsedOrdinateValue = $this->parseOrdinateValue($rowData[$column]);
+ $hasData = true;
+
+ if ($parsedOrdinateValue != 0) {
+ $hasNonZeroValue = true;
+ }
+ $ordinateSeries[$column][] = $parsedOrdinateValue;
+ }
+
+ if (isset($reportMetadata[$i])) {
+ $rowMetadata = $reportMetadata[$i]->getColumns();
+ if (isset($rowMetadata['logo'])) {
+ $absoluteLogoPath = self::getAbsoluteLogoPath($rowMetadata['logo']);
+ if (file_exists($absoluteLogoPath)) {
+ $abscissaLogos[$i] = $absoluteLogoPath;
+ }
+ }
+ }
+ $i++;
+ }
+ } else // if the report has no dimension we have multiple reports each with only one row within the reportData
+ {
+ // $periodsData instanceof Piwik_DataTable_Simple[]
+ $periodsData = array_values($reportData->getArray());
+ $periodsCount = count($periodsData);
+
+ for ($i = 0; $i < $periodsCount; $i++) {
+ // $periodsData[$i] instanceof Piwik_DataTable_Simple
+ // $rows instanceof Piwik_DataTable_Row[]
+ if (empty($periodsData[$i])) {
+ continue;
+ }
+ $rows = $periodsData[$i]->getRows();
+
+ if (array_key_exists(0, $rows)) {
+ $rowData = $rows[0]->getColumns(); // associative Array
+
+ foreach ($ordinateColumns as $column) {
+ $ordinateValue = $rowData[$column];
+ $parsedOrdinateValue = $this->parseOrdinateValue($ordinateValue);
+
+ $hasData = true;
+
+ if (!empty($parsedOrdinateValue)) {
+ $hasNonZeroValue = true;
+ }
+
+ $ordinateSeries[$column][] = $parsedOrdinateValue;
+ }
+
+ } else {
+ foreach ($ordinateColumns as $column) {
+ $ordinateSeries[$column][] = 0;
+ }
+ }
+
+ $rowId = $periodsData[$i]->metadata['period']->getLocalizedShortString();
+ $abscissaSeries[] = Piwik_Common::unsanitizeInputValue($rowId);
+ }
+ }
+
+ if (!$hasData || !$hasNonZeroValue) {
+ throw new Exception(Piwik_Translate('General_NoDataForGraph_js'));
+ }
+
+ //Setup the graph
+ $graph = Piwik_ImageGraph_StaticGraph::factory($graphType);
+ $graph->setWidth($width);
+ $graph->setHeight($height);
+ $graph->setFont($font);
+ $graph->setFontSize($fontSize);
+ $graph->setLegendFontSize($legendFontSize);
+ $graph->setOrdinateLabels($ordinateLabels);
+ $graph->setShowLegend($showLegend);
+ $graph->setAliasedGraph($aliasedGraph);
+ $graph->setAbscissaSeries($abscissaSeries);
+ $graph->setAbscissaLogos($abscissaLogos);
+ $graph->setOrdinateSeries($ordinateSeries);
+ $graph->setOrdinateLogos($ordinateLogos);
+ $graph->setColors(!empty($colors) ? explode(',', $colors) : array());
+ if ($period == 'day') {
+ $graph->setForceSkippedLabels(6);
+ }
+
+ // render graph
+ $graph->renderGraph();
+
+ } catch (Exception $e) {
+
+ $graph = new Piwik_ImageGraph_StaticGraph_Exception();
+ $graph->setWidth($width);
+ $graph->setHeight($height);
+ $graph->setFont($font);
+ $graph->setFontSize($fontSize);
+ $graph->setException($e);
+ $graph->renderGraph();
+ }
+
+ // restoring get parameters
+ $_GET = $savedGET;
+
+ switch ($outputType) {
+ case self::GRAPH_OUTPUT_FILE:
+ if ($idGoal != '') {
+ $idGoal = '_' . $idGoal;
+ }
+ $fileName = self::$DEFAULT_PARAMETERS[$graphType][self::FILENAME_KEY] . '_' . $apiModule . '_' . $apiAction . $idGoal . ' ' . str_replace(',', '-', $date) . ' ' . $idSite . '.png';
+ $fileName = str_replace(array(' ', '/'), '_', $fileName);
+
+ if (!Piwik_Common::isValidFilename($fileName)) {
+ throw new Exception('Error: Image graph filename ' . $fileName . ' is not valid.');
+ }
+
+ return $graph->sendToDisk($fileName);
+
+ case self::GRAPH_OUTPUT_PHP:
+ return $graph->getRenderedImage();
+
+ case self::GRAPH_OUTPUT_INLINE:
+ default:
+ $graph->sendToBrowser();
+ exit;
+ }
+ }
+
+ private function setFilterTruncate($default)
+ {
+ $_GET['filter_truncate'] = Piwik_Common::getRequestVar('filter_truncate', $default, 'int');
+ }
+
+ private static function parseOrdinateValue($ordinateValue)
+ {
+ $ordinateValue = @str_replace(',', '.', $ordinateValue);
+
+ // convert hh:mm:ss formatted time values to number of seconds
+ if (preg_match('/([0-9]{1,2}):([0-9]{1,2}):([0-9]{1,2})/', $ordinateValue, $matches)) {
+ $hour = $matches[1];
+ $min = $matches[2];
+ $sec = $matches[3];
+
+ $ordinateValue = ($hour * 3600) + ($min * 60) + $sec;
+ }
+
+ // OK, only numbers from here please (strip out currency sign)
+ $ordinateValue = preg_replace('/[^0-9.]/', '', $ordinateValue);
+ return $ordinateValue;
+ }
+
+ private static function getFontPath($font)
+ {
+ return PIWIK_INCLUDE_PATH . self::FONT_DIR . $font;
+ }
+
+ protected static function getAbsoluteLogoPath($relativeLogoPath)
+ {
+ return PIWIK_INCLUDE_PATH . '/' . $relativeLogoPath;
+ }
}
diff --git a/plugins/ImageGraph/Controller.php b/plugins/ImageGraph/Controller.php
index 91689fa3ed..1b76c6cec7 100644
--- a/plugins/ImageGraph/Controller.php
+++ b/plugins/ImageGraph/Controller.php
@@ -1,75 +1,73 @@
<?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_ImageGraph
*/
class Piwik_ImageGraph_Controller extends Piwik_Controller
{
- // Call metadata reports, and draw the default graph for each report.
- public function index()
- {
- Piwik::checkUserHasSomeAdminAccess();
- $idSite = Piwik_Common::getRequestVar('idSite', 1, 'int');
- $period = Piwik_Common::getRequestVar('period', 'day', 'string');
- $date = Piwik_Common::getRequestVar('date', 'today', 'string');
- $_GET['token_auth'] = Piwik::getCurrentUserTokenAuth();
- $reports = Piwik_API_API::getInstance()->getReportMetadata($idSite, $period, $date);
- $plot = array();
- foreach($reports as $report)
- {
- if(!empty($report['imageGraphUrl']))
- {
- $plot[] = array(
- // Title
- $report['category'] . ' › ' . $report['name'],
- //URL
- Piwik::getPiwikUrl() . $report['imageGraphUrl']
- );
- }
- }
- $view = Piwik_View::factory('index');
- $view->titleAndUrls = $plot;
- echo $view->render();
- }
-
- // Draw graphs for all sizes (DEBUG)
+ // Call metadata reports, and draw the default graph for each report.
+ public function index()
+ {
+ Piwik::checkUserHasSomeAdminAccess();
+ $idSite = Piwik_Common::getRequestVar('idSite', 1, 'int');
+ $period = Piwik_Common::getRequestVar('period', 'day', 'string');
+ $date = Piwik_Common::getRequestVar('date', 'today', 'string');
+ $_GET['token_auth'] = Piwik::getCurrentUserTokenAuth();
+ $reports = Piwik_API_API::getInstance()->getReportMetadata($idSite, $period, $date);
+ $plot = array();
+ foreach ($reports as $report) {
+ if (!empty($report['imageGraphUrl'])) {
+ $plot[] = array(
+ // Title
+ $report['category'] . ' › ' . $report['name'],
+ //URL
+ Piwik::getPiwikUrl() . $report['imageGraphUrl']
+ );
+ }
+ }
+ $view = Piwik_View::factory('index');
+ $view->titleAndUrls = $plot;
+ echo $view->render();
+ }
+
+ // Draw graphs for all sizes (DEBUG)
public function testAllSizes()
- {
- Piwik::checkUserIsSuperUser();
-
- $view = Piwik_View::factory('debug_graphs_all_sizes');
- $this->setGeneralVariablesView($view);
+ {
+ Piwik::checkUserIsSuperUser();
+
+ $view = Piwik_View::factory('debug_graphs_all_sizes');
+ $this->setGeneralVariablesView($view);
- $period = Piwik_Common::getRequestVar('period', 'day', 'string');
- $date = Piwik_Common::getRequestVar('date', 'today', 'string');
+ $period = Piwik_Common::getRequestVar('period', 'day', 'string');
+ $date = Piwik_Common::getRequestVar('date', 'today', 'string');
- $_GET['token_auth'] = Piwik::getCurrentUserTokenAuth();
- $availableReports = Piwik_API_API::getInstance()->getReportMetadata($this->idSite, $period, $date);
- $view->availableReports = $availableReports;
- $view->graphTypes = array(
- '', // default graph type
+ $_GET['token_auth'] = Piwik::getCurrentUserTokenAuth();
+ $availableReports = Piwik_API_API::getInstance()->getReportMetadata($this->idSite, $period, $date);
+ $view->availableReports = $availableReports;
+ $view->graphTypes = array(
+ '', // default graph type
// 'evolution',
// 'verticalBar',
// 'horizontalBar',
// 'pie',
// '3dPie',
- );
- $view->graphSizes = array(
- array(null, null), // default graph size
- array(Piwik_ReportRenderer::IMAGE_GRAPH_WIDTH, Piwik_ReportRenderer::IMAGE_GRAPH_HEIGHT), // PDF/HTML reports
- array(460, 150), // standard phone
- array(300, 150), // standard phone 2
- array(240, 150), // smallest mobile display
- array(800, 150), // landscape mode
- array(600, 300, $fontSize = 18, 300, 150), // iphone requires bigger font, then it will be scaled down by ios
- );
- echo $view->render();
- }
+ );
+ $view->graphSizes = array(
+ array(null, null), // default graph size
+ array(Piwik_ReportRenderer::IMAGE_GRAPH_WIDTH, Piwik_ReportRenderer::IMAGE_GRAPH_HEIGHT), // PDF/HTML reports
+ array(460, 150), // standard phone
+ array(300, 150), // standard phone 2
+ array(240, 150), // smallest mobile display
+ array(800, 150), // landscape mode
+ array(600, 300, $fontSize = 18, 300, 150), // iphone requires bigger font, then it will be scaled down by ios
+ );
+ echo $view->render();
+ }
}
diff --git a/plugins/ImageGraph/ImageGraph.php b/plugins/ImageGraph/ImageGraph.php
index 1494dc79e6..f149150d23 100644
--- a/plugins/ImageGraph/ImageGraph.php
+++ b/plugins/ImageGraph/ImageGraph.php
@@ -1,157 +1,140 @@
<?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_ImageGraph
*/
class Piwik_ImageGraph extends Piwik_Plugin
{
- static private $CONSTANT_ROW_COUNT_REPORT_EXCEPTIONS = array(
- 'Referers_getRefererType',
- );
-
- public function getInformation()
- {
- return array(
- 'description' => Piwik_Translate('ImageGraph_PluginDescription')
- . ' Debug: <a href="'.Piwik_Url::getCurrentQueryStringWithParametersModified(
- array('module'=> 'ImageGraph', 'action' => 'index'))
- . '">All images</a>',
- 'author' => 'Piwik',
- 'author_homepage' => 'http://piwik.org/',
- 'version' => Piwik_Version::VERSION
- );
- }
-
- function getListHooksRegistered()
- {
- $hooks = array(
- 'API.getReportMetadata.end.end' => 'getReportMetadata',
- );
- return $hooks;
- }
-
- // Number of periods to plot on an evolution graph
- const GRAPH_EVOLUTION_LAST_PERIODS = 30;
-
- /**
- * @param Piwik_Event_Notification $notification notification object
- * @return mixed
- */
- public function getReportMetadata($notification)
- {
- $info = $notification->getNotificationInfo();
- $reports = &$notification->getNotificationObject();
- $idSites = $info['idSites'];
-
- // If only one website is selected, we add the Graph URL
- if(count($idSites) != 1)
- {
- return;
- }
- $idSite = reset($idSites);
-
- // in case API.getReportMetadata was not called with date/period we use sane defaults
- if(empty($info['period']))
- {
- $info['period'] = 'day';
- }
- if(empty($info['date']))
- {
- $info['date'] = 'today';
- }
-
- // need two sets of period & date, one for single period graphs, one for multiple periods graphs
- if(Piwik_Archive::isMultiplePeriod($info['date'], $info['period']))
- {
- $periodForMultiplePeriodGraph = $info['period'];
- $dateForMultiplePeriodGraph = $info['date'];
-
- $periodForSinglePeriodGraph = 'range';
- $dateForSinglePeriodGraph = $info['date'];
- }
- else
- {
- $periodForSinglePeriodGraph = $info['period'];
- $dateForSinglePeriodGraph = $info['date'];
-
- $piwikSite = new Piwik_Site($idSite);
- if($periodForSinglePeriodGraph == 'range')
- {
- $periodForMultiplePeriodGraph = Piwik_Config::getInstance()->General['graphs_default_period_to_plot_when_period_range'];
- $dateForMultiplePeriodGraph = $dateForSinglePeriodGraph;
- }
- else
- {
- $periodForMultiplePeriodGraph = $periodForSinglePeriodGraph;
- $dateForMultiplePeriodGraph = Piwik_Controller::getDateRangeRelativeToEndDate(
- $periodForSinglePeriodGraph,
- 'last' . self::GRAPH_EVOLUTION_LAST_PERIODS,
- $dateForSinglePeriodGraph,
- $piwikSite
- );
- }
- }
-
- $token_auth = Piwik_Common::getRequestVar('token_auth', false);
-
- $urlPrefix = "index.php?";
- foreach($reports as &$report)
- {
- $reportModule = $report['module'];
- $reportAction = $report['action'];
- $reportUniqueId = $reportModule.'_'.$reportAction;
-
- $parameters = array();
- $parameters['module'] = 'API';
- $parameters['method'] = 'ImageGraph.get';
- $parameters['idSite'] = $idSite;
- $parameters['apiModule'] = $reportModule;
- $parameters['apiAction'] = $reportAction;
- if(!empty($token_auth))
- {
- $parameters['token_auth'] = $token_auth;
- }
-
- // Forward custom Report parameters to the graph URL
- if(!empty($report['parameters']))
- {
- $parameters = array_merge($parameters, $report['parameters']);
- }
- if(empty($report['dimension']))
- {
- $parameters['period'] = $periodForMultiplePeriodGraph;
- $parameters['date'] = $dateForMultiplePeriodGraph;
- }
- else
- {
- $parameters['period'] = $periodForSinglePeriodGraph;
- $parameters['date'] = $dateForSinglePeriodGraph;
- }
-
- // add the idSubtable if it exists
- $idSubtable = Piwik_Common::getRequestVar('idSubtable', false);
- if ($idSubtable !== false)
- {
- $parameters['idSubtable'] = $idSubtable;
- }
-
- $report['imageGraphUrl'] = $urlPrefix . Piwik_Url::getQueryStringFromParameters($parameters);
-
- // thanks to API.getRowEvolution, reports with dimensions can now be plotted using an evolution graph
- // however, most reports with a fixed set of dimension values are excluded
- // this is done so Piwik Mobile and Scheduled Reports do not display them
- if(empty($report['constantRowsCount']) || in_array($reportUniqueId,self::$CONSTANT_ROW_COUNT_REPORT_EXCEPTIONS))
- {
- $parameters['period'] = $periodForMultiplePeriodGraph;
- $parameters['date'] = $dateForMultiplePeriodGraph;
- $report['imageGraphEvolutionUrl'] = $urlPrefix . Piwik_Url::getQueryStringFromParameters($parameters);
- }
- }
- }
+ static private $CONSTANT_ROW_COUNT_REPORT_EXCEPTIONS = array(
+ 'Referers_getRefererType',
+ );
+
+ public function getInformation()
+ {
+ return array(
+ 'description' => Piwik_Translate('ImageGraph_PluginDescription')
+ . ' Debug: <a href="' . Piwik_Url::getCurrentQueryStringWithParametersModified(
+ array('module' => 'ImageGraph', 'action' => 'index'))
+ . '">All images</a>',
+ 'author' => 'Piwik',
+ 'author_homepage' => 'http://piwik.org/',
+ 'version' => Piwik_Version::VERSION
+ );
+ }
+
+ function getListHooksRegistered()
+ {
+ $hooks = array(
+ 'API.getReportMetadata.end.end' => 'getReportMetadata',
+ );
+ return $hooks;
+ }
+
+ // Number of periods to plot on an evolution graph
+ const GRAPH_EVOLUTION_LAST_PERIODS = 30;
+
+ /**
+ * @param Piwik_Event_Notification $notification notification object
+ * @return mixed
+ */
+ public function getReportMetadata($notification)
+ {
+ $info = $notification->getNotificationInfo();
+ $reports = & $notification->getNotificationObject();
+ $idSites = $info['idSites'];
+
+ // If only one website is selected, we add the Graph URL
+ if (count($idSites) != 1) {
+ return;
+ }
+ $idSite = reset($idSites);
+
+ // in case API.getReportMetadata was not called with date/period we use sane defaults
+ if (empty($info['period'])) {
+ $info['period'] = 'day';
+ }
+ if (empty($info['date'])) {
+ $info['date'] = 'today';
+ }
+
+ // need two sets of period & date, one for single period graphs, one for multiple periods graphs
+ if (Piwik_Archive::isMultiplePeriod($info['date'], $info['period'])) {
+ $periodForMultiplePeriodGraph = $info['period'];
+ $dateForMultiplePeriodGraph = $info['date'];
+
+ $periodForSinglePeriodGraph = 'range';
+ $dateForSinglePeriodGraph = $info['date'];
+ } else {
+ $periodForSinglePeriodGraph = $info['period'];
+ $dateForSinglePeriodGraph = $info['date'];
+
+ $piwikSite = new Piwik_Site($idSite);
+ if ($periodForSinglePeriodGraph == 'range') {
+ $periodForMultiplePeriodGraph = Piwik_Config::getInstance()->General['graphs_default_period_to_plot_when_period_range'];
+ $dateForMultiplePeriodGraph = $dateForSinglePeriodGraph;
+ } else {
+ $periodForMultiplePeriodGraph = $periodForSinglePeriodGraph;
+ $dateForMultiplePeriodGraph = Piwik_Controller::getDateRangeRelativeToEndDate(
+ $periodForSinglePeriodGraph,
+ 'last' . self::GRAPH_EVOLUTION_LAST_PERIODS,
+ $dateForSinglePeriodGraph,
+ $piwikSite
+ );
+ }
+ }
+
+ $token_auth = Piwik_Common::getRequestVar('token_auth', false);
+
+ $urlPrefix = "index.php?";
+ foreach ($reports as &$report) {
+ $reportModule = $report['module'];
+ $reportAction = $report['action'];
+ $reportUniqueId = $reportModule . '_' . $reportAction;
+
+ $parameters = array();
+ $parameters['module'] = 'API';
+ $parameters['method'] = 'ImageGraph.get';
+ $parameters['idSite'] = $idSite;
+ $parameters['apiModule'] = $reportModule;
+ $parameters['apiAction'] = $reportAction;
+ if (!empty($token_auth)) {
+ $parameters['token_auth'] = $token_auth;
+ }
+
+ // Forward custom Report parameters to the graph URL
+ if (!empty($report['parameters'])) {
+ $parameters = array_merge($parameters, $report['parameters']);
+ }
+ if (empty($report['dimension'])) {
+ $parameters['period'] = $periodForMultiplePeriodGraph;
+ $parameters['date'] = $dateForMultiplePeriodGraph;
+ } else {
+ $parameters['period'] = $periodForSinglePeriodGraph;
+ $parameters['date'] = $dateForSinglePeriodGraph;
+ }
+
+ // add the idSubtable if it exists
+ $idSubtable = Piwik_Common::getRequestVar('idSubtable', false);
+ if ($idSubtable !== false) {
+ $parameters['idSubtable'] = $idSubtable;
+ }
+
+ $report['imageGraphUrl'] = $urlPrefix . Piwik_Url::getQueryStringFromParameters($parameters);
+
+ // thanks to API.getRowEvolution, reports with dimensions can now be plotted using an evolution graph
+ // however, most reports with a fixed set of dimension values are excluded
+ // this is done so Piwik Mobile and Scheduled Reports do not display them
+ if (empty($report['constantRowsCount']) || in_array($reportUniqueId, self::$CONSTANT_ROW_COUNT_REPORT_EXCEPTIONS)) {
+ $parameters['period'] = $periodForMultiplePeriodGraph;
+ $parameters['date'] = $dateForMultiplePeriodGraph;
+ $report['imageGraphEvolutionUrl'] = $urlPrefix . Piwik_Url::getQueryStringFromParameters($parameters);
+ }
+ }
+ }
}
diff --git a/plugins/ImageGraph/StaticGraph.php b/plugins/ImageGraph/StaticGraph.php
index afb8993db8..2d3ee04685 100644
--- a/plugins/ImageGraph/StaticGraph.php
+++ b/plugins/ImageGraph/StaticGraph.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_ImageGraph
*/
@@ -21,311 +21,291 @@ require_once PIWIK_INCLUDE_PATH . "/libs/pChart2.1.3/class/pData.class.php";
*/
abstract class Piwik_ImageGraph_StaticGraph
{
- const GRAPH_TYPE_BASIC_LINE = "evolution";
- const GRAPH_TYPE_VERTICAL_BAR = "verticalBar";
- const GRAPH_TYPE_HORIZONTAL_BAR = "horizontalBar";
- const GRAPH_TYPE_3D_PIE = "3dPie";
- const GRAPH_TYPE_BASIC_PIE = "pie";
-
- static private $availableStaticGraphTypes = array(
- self::GRAPH_TYPE_BASIC_LINE => 'Piwik_ImageGraph_StaticGraph_Evolution',
- self::GRAPH_TYPE_VERTICAL_BAR => 'Piwik_ImageGraph_StaticGraph_VerticalBar',
- self::GRAPH_TYPE_HORIZONTAL_BAR => 'Piwik_ImageGraph_StaticGraph_HorizontalBar',
- self::GRAPH_TYPE_BASIC_PIE => 'Piwik_ImageGraph_StaticGraph_Pie',
- self::GRAPH_TYPE_3D_PIE => 'Piwik_ImageGraph_StaticGraph_3DPie',
- );
-
- const ABSCISSA_SERIE_NAME = 'ABSCISSA';
-
- private $aliasedGraph;
-
- /**
- * @var pImage
- */
- protected $pImage;
- protected $pData;
- protected $ordinateLabels;
- protected $showLegend;
- protected $abscissaSeries;
- protected $abscissaLogos;
- protected $ordinateSeries;
- protected $ordinateLogos;
- protected $colors;
- protected $font;
- protected $fontSize;
- protected $legendFontSize;
- protected $width;
- protected $height;
- protected $forceSkippedLabels = false;
-
- abstract protected function getDefaultColors();
-
- abstract public function renderGraph();
-
- /**
- * Return the StaticGraph according to the static graph type $graphType
- *
- * @throws exception If the static graph type is unknown
- * @param string $graphType
- * @return Piwik_ImageGraph_StaticGraph
- */
- public static function factory($graphType)
- {
- if (isset(self::$availableStaticGraphTypes[$graphType]))
- {
-
- $className = self::$availableStaticGraphTypes[$graphType];
- Piwik_Loader::loadClass($className);
- return new $className;
- }
- else
- {
- throw new Exception(
- Piwik_TranslateException(
- 'General_ExceptionInvalidStaticGraphType',
- array($graphType, implode(', ', self::getAvailableStaticGraphTypes()))
- )
- );
- }
- }
-
- public static function getAvailableStaticGraphTypes()
- {
- return array_keys(self::$availableStaticGraphTypes);
- }
-
- /**
- * Save rendering to disk
- *
- * @param string $filename without path
- * @return string path of file
- */
- public function sendToDisk($filename)
- {
- $filePath = self::getOutputPath($filename);
- $this->pImage->Render($filePath);
- return $filePath;
- }
-
- /**
- * @return rendered static graph
- */
- public function getRenderedImage()
- {
- return $this->pImage->Picture;
- }
-
- /**
- * Output rendering to browser
- */
- public function sendToBrowser()
- {
- $this->pImage->stroke();
- }
-
- public function setWidth($width)
- {
- $this->width = $width;
- }
-
- public function setHeight($height)
- {
- $this->height = $height;
- }
-
- public function setFontSize($fontSize)
- {
- if(!is_numeric($fontSize))
- {
- $fontSize = Piwik_ImageGraph_API::DEFAULT_FONT_SIZE;
- }
- $this->fontSize = $fontSize;
- }
-
- public function setLegendFontSize($legendFontSize)
- {
- $this->legendFontSize = $legendFontSize;
- }
-
- public function setFont($font)
- {
- $this->font = $font;
- }
-
- public function setOrdinateSeries($ordinateSeries)
- {
- $this->ordinateSeries = $ordinateSeries;
- }
-
- public function setOrdinateLogos($ordinateLogos)
- {
- $this->ordinateLogos = $ordinateLogos;
- }
-
- public function setAbscissaLogos($abscissaLogos)
- {
- $this->abscissaLogos = $abscissaLogos;
- }
-
- public function setAbscissaSeries($abscissaSeries)
- {
- $this->abscissaSeries = $abscissaSeries;
- }
-
- public function setShowLegend($showLegend)
- {
- $this->showLegend = $showLegend;
- }
-
- public function setForceSkippedLabels($forceSkippedLabels)
- {
- $this->forceSkippedLabels = $forceSkippedLabels;
- }
-
- public function setOrdinateLabels($ordinateLabels)
- {
- $this->ordinateLabels = $ordinateLabels;
- }
-
- public function setAliasedGraph($aliasedGraph)
- {
- $this->aliasedGraph = $aliasedGraph;
- }
-
- public function setColors($colors)
- {
- $i = 0;
- foreach($this->getDefaultColors() as $colorKey => $defaultColor)
- {
- if(isset($colors[$i]) && $this->hex2rgb($colors[$i]))
- {
- $hexColor = $colors[$i];
- }
- else
- {
- $hexColor = $defaultColor;
- }
-
- $this->colors[$colorKey] = $this->hex2rgb($hexColor);
- $i++;
- }
- }
-
- /**
- * Return $filename with temp directory and delete file
- *
- * @static
- * @param $filename
- * @return string path of file in temp directory
- */
- protected static function getOutputPath($filename)
- {
- $outputFilename = PIWIK_USER_PATH . '/tmp/assets/' . $filename;
- @chmod($outputFilename, 0600);
- @unlink($outputFilename);
- return $outputFilename;
- }
-
- protected function initpData()
- {
- $this->pData = new pData();
-
- foreach($this->ordinateSeries as $column => $data)
- {
- $this->pData->addPoints($data, $column);
- $this->pData->setSerieDescription($column,$this->ordinateLabels[$column]);
- if(isset($this->ordinateLogos[$column]))
- {
- $ordinateLogo = $this->ordinateLogos[$column];
- $this->pData->setSeriePicture($column, $ordinateLogo);
- }
- }
-
- $this->pData->addPoints($this->abscissaSeries, self::ABSCISSA_SERIE_NAME);
- $this->pData->setAbscissa(self::ABSCISSA_SERIE_NAME);
- }
-
- protected function initpImage()
- {
- $this->pImage = new pImage($this->width, $this->height, $this->pData);
- $this->pImage->Antialias = $this->aliasedGraph;
-
- $this->pImage->setFontProperties(
- array(
- "FontName" => $this->font,
- "FontSize" => $this->fontSize
- )
- );
- }
-
- protected function getTextWidthHeight($text, $fontSize = false)
- {
- if(!$fontSize)
- {
- $fontSize = $this->fontSize;
- }
-
- if(!$this->pImage)
- {
- $this->initpImage();
- }
-
- // could not find a way to get pixel perfect width & height info using imageftbbox
- $textInfo = $this->pImage->drawText(
- 0, 0, $text,
- array(
- 'Alpha'=>0,
- 'FontSize'=>$fontSize,
- 'FontName' => $this->font
- )
- );
-
- return array($textInfo[1]["X"] + 1, $textInfo[0]["Y"]-$textInfo[2]["Y"]);
- }
-
- protected function getMaximumTextWidthHeight($values)
- {
- if(array_values($values) === $values)
- {
- $values = array('' => $values);
- }
-
- $maxWidth = 0;
- $maxHeight = 0;
- foreach($values as $column => $data)
- {
- foreach($data as $value)
- {
- list($valueWidth, $valueHeight) = $this->getTextWidthHeight($value);
-
- if($valueWidth > $maxWidth)
- {
- $maxWidth = $valueWidth;
- }
-
- if($valueHeight > $maxHeight)
- {
- $maxHeight = $valueHeight;
- }
- }
- }
-
- return array($maxWidth, $maxHeight);
- }
-
- private static function hex2rgb($hexColor)
- {
- if(preg_match('/([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/', $hexColor, $matches))
- {
- return array(
- 'R' => hexdec($matches[1]),
- 'G' => hexdec($matches[2]),
- 'B' => hexdec($matches[3])
- );
- }
- else
- {
- return false;
- }
- }
+ const GRAPH_TYPE_BASIC_LINE = "evolution";
+ const GRAPH_TYPE_VERTICAL_BAR = "verticalBar";
+ const GRAPH_TYPE_HORIZONTAL_BAR = "horizontalBar";
+ const GRAPH_TYPE_3D_PIE = "3dPie";
+ const GRAPH_TYPE_BASIC_PIE = "pie";
+
+ static private $availableStaticGraphTypes = array(
+ self::GRAPH_TYPE_BASIC_LINE => 'Piwik_ImageGraph_StaticGraph_Evolution',
+ self::GRAPH_TYPE_VERTICAL_BAR => 'Piwik_ImageGraph_StaticGraph_VerticalBar',
+ self::GRAPH_TYPE_HORIZONTAL_BAR => 'Piwik_ImageGraph_StaticGraph_HorizontalBar',
+ self::GRAPH_TYPE_BASIC_PIE => 'Piwik_ImageGraph_StaticGraph_Pie',
+ self::GRAPH_TYPE_3D_PIE => 'Piwik_ImageGraph_StaticGraph_3DPie',
+ );
+
+ const ABSCISSA_SERIE_NAME = 'ABSCISSA';
+
+ private $aliasedGraph;
+
+ /**
+ * @var pImage
+ */
+ protected $pImage;
+ protected $pData;
+ protected $ordinateLabels;
+ protected $showLegend;
+ protected $abscissaSeries;
+ protected $abscissaLogos;
+ protected $ordinateSeries;
+ protected $ordinateLogos;
+ protected $colors;
+ protected $font;
+ protected $fontSize;
+ protected $legendFontSize;
+ protected $width;
+ protected $height;
+ protected $forceSkippedLabels = false;
+
+ abstract protected function getDefaultColors();
+
+ abstract public function renderGraph();
+
+ /**
+ * Return the StaticGraph according to the static graph type $graphType
+ *
+ * @throws exception If the static graph type is unknown
+ * @param string $graphType
+ * @return Piwik_ImageGraph_StaticGraph
+ */
+ public static function factory($graphType)
+ {
+ if (isset(self::$availableStaticGraphTypes[$graphType])) {
+
+ $className = self::$availableStaticGraphTypes[$graphType];
+ Piwik_Loader::loadClass($className);
+ return new $className;
+ } else {
+ throw new Exception(
+ Piwik_TranslateException(
+ 'General_ExceptionInvalidStaticGraphType',
+ array($graphType, implode(', ', self::getAvailableStaticGraphTypes()))
+ )
+ );
+ }
+ }
+
+ public static function getAvailableStaticGraphTypes()
+ {
+ return array_keys(self::$availableStaticGraphTypes);
+ }
+
+ /**
+ * Save rendering to disk
+ *
+ * @param string $filename without path
+ * @return string path of file
+ */
+ public function sendToDisk($filename)
+ {
+ $filePath = self::getOutputPath($filename);
+ $this->pImage->Render($filePath);
+ return $filePath;
+ }
+
+ /**
+ * @return rendered static graph
+ */
+ public function getRenderedImage()
+ {
+ return $this->pImage->Picture;
+ }
+
+ /**
+ * Output rendering to browser
+ */
+ public function sendToBrowser()
+ {
+ $this->pImage->stroke();
+ }
+
+ public function setWidth($width)
+ {
+ $this->width = $width;
+ }
+
+ public function setHeight($height)
+ {
+ $this->height = $height;
+ }
+
+ public function setFontSize($fontSize)
+ {
+ if (!is_numeric($fontSize)) {
+ $fontSize = Piwik_ImageGraph_API::DEFAULT_FONT_SIZE;
+ }
+ $this->fontSize = $fontSize;
+ }
+
+ public function setLegendFontSize($legendFontSize)
+ {
+ $this->legendFontSize = $legendFontSize;
+ }
+
+ public function setFont($font)
+ {
+ $this->font = $font;
+ }
+
+ public function setOrdinateSeries($ordinateSeries)
+ {
+ $this->ordinateSeries = $ordinateSeries;
+ }
+
+ public function setOrdinateLogos($ordinateLogos)
+ {
+ $this->ordinateLogos = $ordinateLogos;
+ }
+
+ public function setAbscissaLogos($abscissaLogos)
+ {
+ $this->abscissaLogos = $abscissaLogos;
+ }
+
+ public function setAbscissaSeries($abscissaSeries)
+ {
+ $this->abscissaSeries = $abscissaSeries;
+ }
+
+ public function setShowLegend($showLegend)
+ {
+ $this->showLegend = $showLegend;
+ }
+
+ public function setForceSkippedLabels($forceSkippedLabels)
+ {
+ $this->forceSkippedLabels = $forceSkippedLabels;
+ }
+
+ public function setOrdinateLabels($ordinateLabels)
+ {
+ $this->ordinateLabels = $ordinateLabels;
+ }
+
+ public function setAliasedGraph($aliasedGraph)
+ {
+ $this->aliasedGraph = $aliasedGraph;
+ }
+
+ public function setColors($colors)
+ {
+ $i = 0;
+ foreach ($this->getDefaultColors() as $colorKey => $defaultColor) {
+ if (isset($colors[$i]) && $this->hex2rgb($colors[$i])) {
+ $hexColor = $colors[$i];
+ } else {
+ $hexColor = $defaultColor;
+ }
+
+ $this->colors[$colorKey] = $this->hex2rgb($hexColor);
+ $i++;
+ }
+ }
+
+ /**
+ * Return $filename with temp directory and delete file
+ *
+ * @static
+ * @param $filename
+ * @return string path of file in temp directory
+ */
+ protected static function getOutputPath($filename)
+ {
+ $outputFilename = PIWIK_USER_PATH . '/tmp/assets/' . $filename;
+ @chmod($outputFilename, 0600);
+ @unlink($outputFilename);
+ return $outputFilename;
+ }
+
+ protected function initpData()
+ {
+ $this->pData = new pData();
+
+ foreach ($this->ordinateSeries as $column => $data) {
+ $this->pData->addPoints($data, $column);
+ $this->pData->setSerieDescription($column, $this->ordinateLabels[$column]);
+ if (isset($this->ordinateLogos[$column])) {
+ $ordinateLogo = $this->ordinateLogos[$column];
+ $this->pData->setSeriePicture($column, $ordinateLogo);
+ }
+ }
+
+ $this->pData->addPoints($this->abscissaSeries, self::ABSCISSA_SERIE_NAME);
+ $this->pData->setAbscissa(self::ABSCISSA_SERIE_NAME);
+ }
+
+ protected function initpImage()
+ {
+ $this->pImage = new pImage($this->width, $this->height, $this->pData);
+ $this->pImage->Antialias = $this->aliasedGraph;
+
+ $this->pImage->setFontProperties(
+ array(
+ "FontName" => $this->font,
+ "FontSize" => $this->fontSize
+ )
+ );
+ }
+
+ protected function getTextWidthHeight($text, $fontSize = false)
+ {
+ if (!$fontSize) {
+ $fontSize = $this->fontSize;
+ }
+
+ if (!$this->pImage) {
+ $this->initpImage();
+ }
+
+ // could not find a way to get pixel perfect width & height info using imageftbbox
+ $textInfo = $this->pImage->drawText(
+ 0, 0, $text,
+ array(
+ 'Alpha' => 0,
+ 'FontSize' => $fontSize,
+ 'FontName' => $this->font
+ )
+ );
+
+ return array($textInfo[1]["X"] + 1, $textInfo[0]["Y"] - $textInfo[2]["Y"]);
+ }
+
+ protected function getMaximumTextWidthHeight($values)
+ {
+ if (array_values($values) === $values) {
+ $values = array('' => $values);
+ }
+
+ $maxWidth = 0;
+ $maxHeight = 0;
+ foreach ($values as $column => $data) {
+ foreach ($data as $value) {
+ list($valueWidth, $valueHeight) = $this->getTextWidthHeight($value);
+
+ if ($valueWidth > $maxWidth) {
+ $maxWidth = $valueWidth;
+ }
+
+ if ($valueHeight > $maxHeight) {
+ $maxHeight = $valueHeight;
+ }
+ }
+ }
+
+ return array($maxWidth, $maxHeight);
+ }
+
+ private static function hex2rgb($hexColor)
+ {
+ if (preg_match('/([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/', $hexColor, $matches)) {
+ return array(
+ 'R' => hexdec($matches[1]),
+ 'G' => hexdec($matches[2]),
+ 'B' => hexdec($matches[3])
+ );
+ } else {
+ return false;
+ }
+ }
} \ No newline at end of file
diff --git a/plugins/ImageGraph/StaticGraph/3DPie.php b/plugins/ImageGraph/StaticGraph/3DPie.php
index 966800b454..62252c0059 100644
--- a/plugins/ImageGraph/StaticGraph/3DPie.php
+++ b/plugins/ImageGraph/StaticGraph/3DPie.php
@@ -15,14 +15,14 @@
*/
class Piwik_ImageGraph_StaticGraph_3DPie extends Piwik_ImageGraph_StaticGraph_PieGraph
{
- public function renderGraph()
- {
- $this->initPieGraph(true);
+ public function renderGraph()
+ {
+ $this->initPieGraph(true);
- $this->pieChart->draw3DPie(
- $this->xPosition,
- $this->yPosition,
- $this->pieConfig
- );
- }
+ $this->pieChart->draw3DPie(
+ $this->xPosition,
+ $this->yPosition,
+ $this->pieConfig
+ );
+ }
}
diff --git a/plugins/ImageGraph/StaticGraph/Evolution.php b/plugins/ImageGraph/StaticGraph/Evolution.php
index 69f87ea2f3..a4851afaa4 100644
--- a/plugins/ImageGraph/StaticGraph/Evolution.php
+++ b/plugins/ImageGraph/StaticGraph/Evolution.php
@@ -17,16 +17,16 @@
class Piwik_ImageGraph_StaticGraph_Evolution extends Piwik_ImageGraph_StaticGraph_GridGraph
{
- public function renderGraph()
- {
- $this->initGridChart(
- $displayVerticalGridLines = true,
- $bulletType = LEGEND_FAMILY_LINE,
- $horizontalGraph = false,
- $showTicks = true,
- $verticalLegend = true
- );
+ public function renderGraph()
+ {
+ $this->initGridChart(
+ $displayVerticalGridLines = true,
+ $bulletType = LEGEND_FAMILY_LINE,
+ $horizontalGraph = false,
+ $showTicks = true,
+ $verticalLegend = true
+ );
- $this->pImage->drawLineChart();
- }
+ $this->pImage->drawLineChart();
+ }
}
diff --git a/plugins/ImageGraph/StaticGraph/Exception.php b/plugins/ImageGraph/StaticGraph/Exception.php
index 9d93c84dc2..5847ee10e1 100644
--- a/plugins/ImageGraph/StaticGraph/Exception.php
+++ b/plugins/ImageGraph/StaticGraph/Exception.php
@@ -16,60 +16,58 @@
*/
class Piwik_ImageGraph_StaticGraph_Exception extends Piwik_ImageGraph_StaticGraph
{
- const MESSAGE_RIGHT_MARGIN = 5;
+ const MESSAGE_RIGHT_MARGIN = 5;
- private $exception;
+ private $exception;
- public function setException($exception)
- {
- $this->exception = $exception;
- }
+ public function setException($exception)
+ {
+ $this->exception = $exception;
+ }
- protected function getDefaultColors()
- {
- return array();
- }
+ protected function getDefaultColors()
+ {
+ return array();
+ }
- public function setWidth($width)
- {
- if(empty($width)) {
- $width = 450;
- }
- parent::setWidth($width);
- }
+ public function setWidth($width)
+ {
+ if (empty($width)) {
+ $width = 450;
+ }
+ parent::setWidth($width);
+ }
- public function setHeight($height)
- {
- if(empty($height)) {
- $height = 300;
- }
- parent::setHeight($height);
- }
+ public function setHeight($height)
+ {
+ if (empty($height)) {
+ $height = 300;
+ }
+ parent::setHeight($height);
+ }
- public function renderGraph()
- {
- $this->pData = new pData();
+ public function renderGraph()
+ {
+ $this->pData = new pData();
- $message = $this->exception->getMessage();
- list($textWidth, $textHeight) = $this->getTextWidthHeight($message);
+ $message = $this->exception->getMessage();
+ list($textWidth, $textHeight) = $this->getTextWidthHeight($message);
- if($this->width == null)
- {
- $this->width = $textWidth + self::MESSAGE_RIGHT_MARGIN;
- }
+ if ($this->width == null) {
+ $this->width = $textWidth + self::MESSAGE_RIGHT_MARGIN;
+ }
- if($this->height == null)
- {
- $this->height = $textHeight;
- }
+ if ($this->height == null) {
+ $this->height = $textHeight;
+ }
- $this->initpImage();
+ $this->initpImage();
- $this->pImage->drawText(
- 0,
- $textHeight,
- $message
- );
- }
+ $this->pImage->drawText(
+ 0,
+ $textHeight,
+ $message
+ );
+ }
}
diff --git a/plugins/ImageGraph/StaticGraph/GridGraph.php b/plugins/ImageGraph/StaticGraph/GridGraph.php
index 8e150054c5..dd70094106 100644
--- a/plugins/ImageGraph/StaticGraph/GridGraph.php
+++ b/plugins/ImageGraph/StaticGraph/GridGraph.php
@@ -16,452 +16,411 @@
*/
abstract class Piwik_ImageGraph_StaticGraph_GridGraph extends Piwik_ImageGraph_StaticGraph
{
- const GRAPHIC_COLOR_KEY = 'GRAPHIC_COLOR';
- const VALUE_COLOR_KEY = 'VALUE_COLOR';
- const GRID_COLOR_KEY = 'GRID_COLOR';
-
- const TRUNCATION_TEXT = '...';
-
- const DEFAULT_TICK_ALPHA = 20;
- const DEFAULT_SERIE_WEIGHT = 0.5;
- const LEFT_GRID_MARGIN = 4;
- const BOTTOM_GRID_MARGIN = 10;
- const TOP_GRID_MARGIN_HORIZONTAL_GRAPH = 1;
- const RIGHT_GRID_MARGIN_HORIZONTAL_GRAPH = 4;
- const OUTER_TICK_WIDTH = 5;
- const INNER_TICK_WIDTH = 0;
- const LABEL_SPACE_VERTICAL_GRAPH = 7;
-
- const HORIZONTAL_LEGEND_TOP_MARGIN = 5;
- const HORIZONTAL_LEGEND_LEFT_MARGIN = 10;
- const HORIZONTAL_LEGEND_BOTTOM_MARGIN = 10;
- const VERTICAL_LEGEND_TOP_MARGIN = 8;
- const VERTICAL_LEGEND_LEFT_MARGIN = 6;
- const VERTICAL_LEGEND_MAX_WIDTH_PCT = 0.70;
- const LEGEND_LINE_BULLET_WIDTH = 14;
- const LEGEND_BOX_BULLET_WIDTH = 5;
- const LEGEND_BULLET_RIGHT_PADDING = 5;
- const LEGEND_ITEM_HORIZONTAL_INTERSTICE = 6;
- const LEGEND_ITEM_VERTICAL_INTERSTICE_OFFSET = 4;
- const LEGEND_SHADOW_OPACITY = 25;
- const LEGEND_VERTICAL_SHADOW_PADDING = 3;
- const LEGEND_HORIZONTAL_SHADOW_PADDING = 2;
- const PCHART_HARD_CODED_VERTICAL_LEGEND_INTERSTICE = 5;
-
- protected function getDefaultColors()
- {
- return array(
- self::VALUE_COLOR_KEY => '444444',
- self::GRID_COLOR_KEY => 'CCCCCC',
- self::GRAPHIC_COLOR_KEY . '1' => '5170AE',
- self::GRAPHIC_COLOR_KEY . '2' => 'F29007',
- self::GRAPHIC_COLOR_KEY . '3' => 'CC3399',
- self::GRAPHIC_COLOR_KEY . '4' => '9933CC',
- self::GRAPHIC_COLOR_KEY . '5' => '80A033',
- self::GRAPHIC_COLOR_KEY . '6' => '246AD2'
- );
- }
-
- protected function initGridChart(
- $displayVerticalGridLines,
- $bulletType,
- $horizontalGraph,
- $showTicks,
- $verticalLegend
- )
- {
- $this->initpData();
-
- $colorIndex = 1;
- foreach($this->ordinateSeries as $column => $data)
- {
- $this->pData->setSerieWeight($column, self::DEFAULT_SERIE_WEIGHT);
- $graphicColor = $this->colors[self::GRAPHIC_COLOR_KEY . $colorIndex++];
- $this->pData->setPalette($column, $graphicColor);
- }
-
- $this->initpImage();
-
- // graph area coordinates
- $topLeftXValue = $this->getGridLeftMargin($horizontalGraph, $withLabel = true);
- $topLeftYValue = $this->getGridTopMargin($horizontalGraph, $verticalLegend);
- $bottomRightXValue = $this->width - $this->getGridRightMargin($horizontalGraph);
- $bottomRightYValue = $this->getGraphBottom($horizontalGraph);
-
- $this->pImage->setGraphArea(
- $topLeftXValue,
- $topLeftYValue,
- $bottomRightXValue,
- $bottomRightYValue
- );
-
- // determine how many labels need to be skipped
- $skippedLabels = 0;
- if(!$horizontalGraph)
- {
- list($abscissaMaxWidth, $abscissaMaxHeight) = $this->getMaximumTextWidthHeight($this->abscissaSeries);
- $graphWidth = $bottomRightXValue - $topLeftXValue;
- $maxNumOfLabels = floor($graphWidth / ($abscissaMaxWidth + self::LABEL_SPACE_VERTICAL_GRAPH));
-
- $abscissaSeriesCount = count($this->abscissaSeries);
- if($maxNumOfLabels < $abscissaSeriesCount)
- {
- for($candidateSkippedLabels = 1 ; $candidateSkippedLabels < $abscissaSeriesCount; $candidateSkippedLabels++)
- {
- $numberOfSegments = $abscissaSeriesCount / ($candidateSkippedLabels + 1);
- $numberOfCompleteSegments = floor($numberOfSegments);
-
- $numberOfLabels = $numberOfCompleteSegments;
- if($numberOfSegments > $numberOfCompleteSegments)
- {
- $numberOfLabels++;
- }
-
- if($numberOfLabels <= $maxNumOfLabels )
- {
- $skippedLabels = $candidateSkippedLabels;
- break;
- }
- }
- }
-
- if($this->forceSkippedLabels
- && $skippedLabels
- && $skippedLabels < $this->forceSkippedLabels
- && $abscissaSeriesCount > $this->forceSkippedLabels + 1
- )
- {
- $skippedLabels = $this->forceSkippedLabels;
- }
- }
-
- $ordinateAxisLength =
- $horizontalGraph ? $bottomRightXValue - $topLeftXValue : $this->getGraphHeight($horizontalGraph, $verticalLegend);
-
- $maxOrdinateValue = 0;
- foreach($this->ordinateSeries as $column => $data)
- {
- $currentMax = $this->pData->getMax($column);
-
- if($currentMax > $maxOrdinateValue)
- {
- $maxOrdinateValue = $currentMax;
- }
- }
-
- // rounding top scale value to the next multiple of 10
- if($maxOrdinateValue > 10)
- {
- $modTen = $maxOrdinateValue % 10;
- if($modTen) $maxOrdinateValue += 10 - $modTen;
- }
-
- $gridColor = $this->colors[self::GRID_COLOR_KEY];
- $this->pImage->drawScale(
- array(
- 'Mode' => SCALE_MODE_MANUAL,
- 'GridTicks' => 0,
- 'LabelSkip' => $skippedLabels,
- 'DrawXLines' => $displayVerticalGridLines,
- 'Factors' => array(ceil($maxOrdinateValue / 2)),
- 'MinDivHeight' => $ordinateAxisLength / 2,
- 'AxisAlpha' => 0,
- 'SkippedAxisAlpha' => 0,
- 'TickAlpha' => $showTicks ? self::DEFAULT_TICK_ALPHA : 0,
- 'InnerTickWidth' => self::INNER_TICK_WIDTH,
- 'OuterTickWidth' => self::OUTER_TICK_WIDTH,
- 'GridR' => $gridColor['R'],
- 'GridG' => $gridColor['G'],
- 'GridB' => $gridColor['B'],
- 'GridAlpha' => 100,
- 'ManualScale' => array(
- 0 => array(
- 'Min' => 0,
- 'Max' => $maxOrdinateValue
- )
- ),
- 'Pos' => $horizontalGraph ? SCALE_POS_TOPBOTTOM : SCALE_POS_LEFTRIGHT,
- )
- );
-
- if($this->showLegend)
- {
- switch($bulletType)
- {
- case LEGEND_FAMILY_LINE:
- $bulletWidth = self::LEGEND_LINE_BULLET_WIDTH;
-
- // measured using a picture editing software
- $iconOffsetAboveLabelSymmetryAxis = -2;
- break;
-
- case LEGEND_FAMILY_BOX:
- $bulletWidth = self::LEGEND_BOX_BULLET_WIDTH;
-
- // measured using a picture editing software
- $iconOffsetAboveLabelSymmetryAxis = 3;
- break;
- }
-
- // pChart requires two coordinates to draw the legend $legendTopLeftXValue & $legendTopLeftYValue
- // $legendTopLeftXValue = legend's left padding
- $legendTopLeftXValue = $topLeftXValue + ($verticalLegend ? self::VERTICAL_LEGEND_LEFT_MARGIN : self::HORIZONTAL_LEGEND_LEFT_MARGIN);
-
- // $legendTopLeftYValue = y coordinate of the top edge of the legend's icons
- // Caution :
- // - pChart will silently add some value (see $paddingAddedByPChart) to $legendTopLeftYValue depending on multiple criterias
- // - pChart will not take into account the size of the text. Setting $legendTopLeftYValue = 0 will crop the legend's labels
- // The following section of code determines the value of $legendTopLeftYValue while taking into account the following paremeters :
- // - whether legend items have icons
- // - whether icons are bigger than the legend's labels
- // - how much colored shadow padding is required
- list($maxLogoWidth, $maxLogoHeight) = self::getMaxLogoSize(array_values($this->ordinateLogos));
- if($maxLogoHeight >= $this->legendFontSize)
- {
- $heightOfTextAboveBulletTop = 0;
- $paddingCreatedByLogo = $maxLogoHeight - $this->legendFontSize;
- $effectiveShadowPadding = $paddingCreatedByLogo < self::LEGEND_VERTICAL_SHADOW_PADDING * 2 ? self::LEGEND_VERTICAL_SHADOW_PADDING - ($paddingCreatedByLogo / 2) : 0;
- }
- else
- {
- if($maxLogoHeight)
- {
- // measured using a picture editing software
- $iconOffsetAboveLabelSymmetryAxis = 5;
- }
- $heightOfTextAboveBulletTop = $this->legendFontSize / 2 - $iconOffsetAboveLabelSymmetryAxis;
- $effectiveShadowPadding = self::LEGEND_VERTICAL_SHADOW_PADDING;
- }
-
- $effectiveLegendItemVerticalInterstice = $this->legendFontSize + self::LEGEND_ITEM_VERTICAL_INTERSTICE_OFFSET;
- $effectiveLegendItemHorizontalInterstice = self::LEGEND_ITEM_HORIZONTAL_INTERSTICE + self::LEGEND_HORIZONTAL_SHADOW_PADDING;
-
- $legendTopMargin = $verticalLegend ? self::VERTICAL_LEGEND_TOP_MARGIN : self::HORIZONTAL_LEGEND_TOP_MARGIN;
- $requiredPaddingAboveItemBullet = $legendTopMargin + $heightOfTextAboveBulletTop + $effectiveShadowPadding;
-
- $paddingAddedByPChart = 0;
- if($verticalLegend)
- {
- if($maxLogoHeight)
- {
- // see line 1691 of pDraw.class.php
- if($maxLogoHeight < $effectiveLegendItemVerticalInterstice)
- {
- $paddingAddedByPChart = ($effectiveLegendItemVerticalInterstice / 2) - ($maxLogoHeight / 2);
- }
- }
- else
- {
- // see line 1711 of pDraw.class.php ($Y+$IconAreaHeight/2)
- $paddingAddedByPChart = $effectiveLegendItemVerticalInterstice / 2;
- }
- }
-
- $legendTopLeftYValue = $paddingAddedByPChart < $requiredPaddingAboveItemBullet ? $requiredPaddingAboveItemBullet - $paddingAddedByPChart : 0;
-
- // add colored background to each legend item
- if(count($this->ordinateLabels) > 1)
- {
- $currentPosition = $verticalLegend ? $legendTopMargin : $legendTopLeftXValue;
- $colorIndex = 1;
- foreach($this->ordinateLabels as $metricCode => &$label)
- {
- $color = $this->colors[self::GRAPHIC_COLOR_KEY . $colorIndex++];
-
- $paddedBulletWidth = $bulletWidth;
- if(isset($this->ordinateLogos[$metricCode]))
- {
- $paddedBulletWidth = $maxLogoWidth;
- }
- $paddedBulletWidth += self::LEGEND_BULLET_RIGHT_PADDING;
-
- // truncate labels if required
- if($verticalLegend)
- {
- $label = $this->truncateLabel($label, ($this->width * self::VERTICAL_LEGEND_MAX_WIDTH_PCT) - $legendTopLeftXValue - $paddedBulletWidth, $this->legendFontSize);
- $this->pData->setSerieDescription($metricCode, $label);
- }
-
- $rectangleTopLeftXValue = ($verticalLegend ? $legendTopLeftXValue : $currentPosition) + $paddedBulletWidth - self::LEGEND_HORIZONTAL_SHADOW_PADDING;
- $rectangleTopLeftYValue = $verticalLegend ? $currentPosition : $legendTopMargin;
-
- list($labelWidth, $labelHeight) = $this->getTextWidthHeight($label, $this->legendFontSize);
- $legendItemWidth = $paddedBulletWidth + $labelWidth + $effectiveLegendItemHorizontalInterstice;
- $rectangleBottomRightXValue = $rectangleTopLeftXValue + $labelWidth + (self::LEGEND_HORIZONTAL_SHADOW_PADDING * 2);
-
- $legendItemHeight = max($maxLogoHeight, $this->legendFontSize) + ($effectiveShadowPadding * 2);
- $rectangleBottomRightYValue = $rectangleTopLeftYValue + $legendItemHeight;
-
- $this->pImage->drawFilledRectangle(
- $rectangleTopLeftXValue,
- $rectangleTopLeftYValue,
- $rectangleBottomRightXValue,
- $rectangleBottomRightYValue,
- array(
- 'Alpha' => self::LEGEND_SHADOW_OPACITY,
- 'R' => $color['R'],
- 'G' => $color['G'],
- 'B' => $color['B'],
- )
- );
-
- if($verticalLegend)
- {
- $currentPositionIncrement = max($maxLogoHeight, $effectiveLegendItemVerticalInterstice, $this->legendFontSize) + self::PCHART_HARD_CODED_VERTICAL_LEGEND_INTERSTICE;
- }
- else
- {
- $currentPositionIncrement = $legendItemWidth;
- }
-
- $currentPosition += $currentPositionIncrement;
- }
- }
-
- // draw legend
- $legendColor = $this->colors[self::VALUE_COLOR_KEY];
- $this->pImage->drawLegend(
- $legendTopLeftXValue,
- $legendTopLeftYValue,
- array(
- 'Style' => LEGEND_NOBORDER,
- 'FontSize' => $this->legendFontSize,
- 'BoxWidth' => $bulletWidth,
- 'XSpacing' => $effectiveLegendItemHorizontalInterstice, // not effective when vertical
- 'Mode' => $verticalLegend ? LEGEND_VERTICAL : LEGEND_HORIZONTAL,
- 'BoxHeight' => $verticalLegend ? $effectiveLegendItemVerticalInterstice : null,
- 'Family' => $bulletType,
- 'FontR' => $legendColor['R'],
- 'FontG' => $legendColor['G'],
- 'FontB' => $legendColor['B'],
- )
- );
- }
- }
-
- protected static function getMaxLogoSize($logoPaths)
- {
- $maxLogoWidth = 0;
- $maxLogoHeight = 0;
- foreach($logoPaths as $logoPath)
- {
- list($logoWidth, $logoHeight) = self::getLogoSize($logoPath);
-
- if($logoWidth > $maxLogoWidth)
- {
- $maxLogoWidth = $logoWidth;
- }
- if($logoHeight > $maxLogoHeight)
- {
- $maxLogoHeight = $logoHeight;
- }
- }
-
- return array($maxLogoWidth, $maxLogoHeight);
- }
-
- protected static function getLogoSize($logoPath)
- {
- $pathInfo = getimagesize($logoPath);
- return array($pathInfo[0], $pathInfo[1]);
- }
-
- protected function getGridLeftMargin($horizontalGraph, $withLabel)
- {
- $gridLeftMargin = self::LEFT_GRID_MARGIN + self::OUTER_TICK_WIDTH;
-
- if($withLabel)
- {
- list($maxTextWidth, $maxTextHeight) = $this->getMaximumTextWidthHeight($horizontalGraph ? $this->abscissaSeries : $this->ordinateSeries);
- $gridLeftMargin += $maxTextWidth;
- }
-
- return $gridLeftMargin;
- }
-
- protected function getGridTopMargin($horizontalGraph, $verticalLegend)
- {
- list($ordinateMaxWidth, $ordinateMaxHeight) = $this->getMaximumTextWidthHeight($this->ordinateSeries);
-
- if($horizontalGraph)
- {
- $topMargin = $ordinateMaxHeight + self::TOP_GRID_MARGIN_HORIZONTAL_GRAPH + self::OUTER_TICK_WIDTH;
- }
- else
- {
- $topMargin = $ordinateMaxHeight / 2;
- }
-
- if($this->showLegend && !$verticalLegend)
- {
- $topMargin += $this->getHorizontalLegendHeight();
- }
-
- return $topMargin;
- }
-
- private function getHorizontalLegendHeight()
- {
- list($maxMetricLegendWidth, $maxMetricLegendHeight) =
- $this->getMaximumTextWidthHeight(array_values($this->ordinateLabels), $this->legendFontSize);
-
- return $maxMetricLegendHeight + self::HORIZONTAL_LEGEND_BOTTOM_MARGIN + self::HORIZONTAL_LEGEND_TOP_MARGIN;
- }
-
- protected function getGraphHeight($horizontalGraph, $verticalLegend)
- {
- return $this->getGraphBottom($horizontalGraph) - $this->getGridTopMargin($horizontalGraph, $verticalLegend);
- }
-
- private function getGridBottomMargin($horizontalGraph)
- {
- $gridBottomMargin = self::BOTTOM_GRID_MARGIN;
- if(!$horizontalGraph)
- {
- list($abscissaMaxWidth, $abscissaMaxHeight) = $this->getMaximumTextWidthHeight($this->abscissaSeries);
- $gridBottomMargin += $abscissaMaxHeight;
- }
- return $gridBottomMargin;
- }
-
- protected function getGridRightMargin($horizontalGraph)
- {
- if($horizontalGraph)
- {
- // in horizontal graphs, metric values are displayed on the far right of the bar
- list($ordinateMaxWidth, $ordinateMaxHeight) = $this->getMaximumTextWidthHeight($this->ordinateSeries);
- return self::RIGHT_GRID_MARGIN_HORIZONTAL_GRAPH + $ordinateMaxWidth;
- }
- else
- {
- return 0;
- }
- }
-
- protected function getGraphBottom($horizontalGraph)
- {
- return $this->height - $this->getGridBottomMargin($horizontalGraph);
- }
-
- protected function truncateLabel($label, $labelWidthLimit, $fontSize = false)
- {
- list($truncationTextWidth, $truncationTextHeight) = $this->getTextWidthHeight(self::TRUNCATION_TEXT, $fontSize);
- list($labelWidth, $labelHeight) = $this->getTextWidthHeight($label, $fontSize);
-
- if($labelWidth > $labelWidthLimit)
- {
- $averageCharWidth = $labelWidth / strlen($label);
- $charsToKeep = floor(($labelWidthLimit - $truncationTextWidth) / $averageCharWidth);
- $label = substr($label, 0, $charsToKeep) . self::TRUNCATION_TEXT;
- }
- return $label;
- }
-
- // display min & max values
- // can not currently be used because pChart's label design is not flexible enough
- // e.g: it is not possible to remove the box border & the square icon
- // it would require modifying pChart code base which we try to avoid
- // see http://dev.piwik.org/trac/ticket/3396
+ const GRAPHIC_COLOR_KEY = 'GRAPHIC_COLOR';
+ const VALUE_COLOR_KEY = 'VALUE_COLOR';
+ const GRID_COLOR_KEY = 'GRID_COLOR';
+
+ const TRUNCATION_TEXT = '...';
+
+ const DEFAULT_TICK_ALPHA = 20;
+ const DEFAULT_SERIE_WEIGHT = 0.5;
+ const LEFT_GRID_MARGIN = 4;
+ const BOTTOM_GRID_MARGIN = 10;
+ const TOP_GRID_MARGIN_HORIZONTAL_GRAPH = 1;
+ const RIGHT_GRID_MARGIN_HORIZONTAL_GRAPH = 4;
+ const OUTER_TICK_WIDTH = 5;
+ const INNER_TICK_WIDTH = 0;
+ const LABEL_SPACE_VERTICAL_GRAPH = 7;
+
+ const HORIZONTAL_LEGEND_TOP_MARGIN = 5;
+ const HORIZONTAL_LEGEND_LEFT_MARGIN = 10;
+ const HORIZONTAL_LEGEND_BOTTOM_MARGIN = 10;
+ const VERTICAL_LEGEND_TOP_MARGIN = 8;
+ const VERTICAL_LEGEND_LEFT_MARGIN = 6;
+ const VERTICAL_LEGEND_MAX_WIDTH_PCT = 0.70;
+ const LEGEND_LINE_BULLET_WIDTH = 14;
+ const LEGEND_BOX_BULLET_WIDTH = 5;
+ const LEGEND_BULLET_RIGHT_PADDING = 5;
+ const LEGEND_ITEM_HORIZONTAL_INTERSTICE = 6;
+ const LEGEND_ITEM_VERTICAL_INTERSTICE_OFFSET = 4;
+ const LEGEND_SHADOW_OPACITY = 25;
+ const LEGEND_VERTICAL_SHADOW_PADDING = 3;
+ const LEGEND_HORIZONTAL_SHADOW_PADDING = 2;
+ const PCHART_HARD_CODED_VERTICAL_LEGEND_INTERSTICE = 5;
+
+ protected function getDefaultColors()
+ {
+ return array(
+ self::VALUE_COLOR_KEY => '444444',
+ self::GRID_COLOR_KEY => 'CCCCCC',
+ self::GRAPHIC_COLOR_KEY . '1' => '5170AE',
+ self::GRAPHIC_COLOR_KEY . '2' => 'F29007',
+ self::GRAPHIC_COLOR_KEY . '3' => 'CC3399',
+ self::GRAPHIC_COLOR_KEY . '4' => '9933CC',
+ self::GRAPHIC_COLOR_KEY . '5' => '80A033',
+ self::GRAPHIC_COLOR_KEY . '6' => '246AD2'
+ );
+ }
+
+ protected function initGridChart(
+ $displayVerticalGridLines,
+ $bulletType,
+ $horizontalGraph,
+ $showTicks,
+ $verticalLegend
+ )
+ {
+ $this->initpData();
+
+ $colorIndex = 1;
+ foreach ($this->ordinateSeries as $column => $data) {
+ $this->pData->setSerieWeight($column, self::DEFAULT_SERIE_WEIGHT);
+ $graphicColor = $this->colors[self::GRAPHIC_COLOR_KEY . $colorIndex++];
+ $this->pData->setPalette($column, $graphicColor);
+ }
+
+ $this->initpImage();
+
+ // graph area coordinates
+ $topLeftXValue = $this->getGridLeftMargin($horizontalGraph, $withLabel = true);
+ $topLeftYValue = $this->getGridTopMargin($horizontalGraph, $verticalLegend);
+ $bottomRightXValue = $this->width - $this->getGridRightMargin($horizontalGraph);
+ $bottomRightYValue = $this->getGraphBottom($horizontalGraph);
+
+ $this->pImage->setGraphArea(
+ $topLeftXValue,
+ $topLeftYValue,
+ $bottomRightXValue,
+ $bottomRightYValue
+ );
+
+ // determine how many labels need to be skipped
+ $skippedLabels = 0;
+ if (!$horizontalGraph) {
+ list($abscissaMaxWidth, $abscissaMaxHeight) = $this->getMaximumTextWidthHeight($this->abscissaSeries);
+ $graphWidth = $bottomRightXValue - $topLeftXValue;
+ $maxNumOfLabels = floor($graphWidth / ($abscissaMaxWidth + self::LABEL_SPACE_VERTICAL_GRAPH));
+
+ $abscissaSeriesCount = count($this->abscissaSeries);
+ if ($maxNumOfLabels < $abscissaSeriesCount) {
+ for ($candidateSkippedLabels = 1; $candidateSkippedLabels < $abscissaSeriesCount; $candidateSkippedLabels++) {
+ $numberOfSegments = $abscissaSeriesCount / ($candidateSkippedLabels + 1);
+ $numberOfCompleteSegments = floor($numberOfSegments);
+
+ $numberOfLabels = $numberOfCompleteSegments;
+ if ($numberOfSegments > $numberOfCompleteSegments) {
+ $numberOfLabels++;
+ }
+
+ if ($numberOfLabels <= $maxNumOfLabels) {
+ $skippedLabels = $candidateSkippedLabels;
+ break;
+ }
+ }
+ }
+
+ if ($this->forceSkippedLabels
+ && $skippedLabels
+ && $skippedLabels < $this->forceSkippedLabels
+ && $abscissaSeriesCount > $this->forceSkippedLabels + 1
+ ) {
+ $skippedLabels = $this->forceSkippedLabels;
+ }
+ }
+
+ $ordinateAxisLength =
+ $horizontalGraph ? $bottomRightXValue - $topLeftXValue : $this->getGraphHeight($horizontalGraph, $verticalLegend);
+
+ $maxOrdinateValue = 0;
+ foreach ($this->ordinateSeries as $column => $data) {
+ $currentMax = $this->pData->getMax($column);
+
+ if ($currentMax > $maxOrdinateValue) {
+ $maxOrdinateValue = $currentMax;
+ }
+ }
+
+ // rounding top scale value to the next multiple of 10
+ if ($maxOrdinateValue > 10) {
+ $modTen = $maxOrdinateValue % 10;
+ if ($modTen) $maxOrdinateValue += 10 - $modTen;
+ }
+
+ $gridColor = $this->colors[self::GRID_COLOR_KEY];
+ $this->pImage->drawScale(
+ array(
+ 'Mode' => SCALE_MODE_MANUAL,
+ 'GridTicks' => 0,
+ 'LabelSkip' => $skippedLabels,
+ 'DrawXLines' => $displayVerticalGridLines,
+ 'Factors' => array(ceil($maxOrdinateValue / 2)),
+ 'MinDivHeight' => $ordinateAxisLength / 2,
+ 'AxisAlpha' => 0,
+ 'SkippedAxisAlpha' => 0,
+ 'TickAlpha' => $showTicks ? self::DEFAULT_TICK_ALPHA : 0,
+ 'InnerTickWidth' => self::INNER_TICK_WIDTH,
+ 'OuterTickWidth' => self::OUTER_TICK_WIDTH,
+ 'GridR' => $gridColor['R'],
+ 'GridG' => $gridColor['G'],
+ 'GridB' => $gridColor['B'],
+ 'GridAlpha' => 100,
+ 'ManualScale' => array(
+ 0 => array(
+ 'Min' => 0,
+ 'Max' => $maxOrdinateValue
+ )
+ ),
+ 'Pos' => $horizontalGraph ? SCALE_POS_TOPBOTTOM : SCALE_POS_LEFTRIGHT,
+ )
+ );
+
+ if ($this->showLegend) {
+ switch ($bulletType) {
+ case LEGEND_FAMILY_LINE:
+ $bulletWidth = self::LEGEND_LINE_BULLET_WIDTH;
+
+ // measured using a picture editing software
+ $iconOffsetAboveLabelSymmetryAxis = -2;
+ break;
+
+ case LEGEND_FAMILY_BOX:
+ $bulletWidth = self::LEGEND_BOX_BULLET_WIDTH;
+
+ // measured using a picture editing software
+ $iconOffsetAboveLabelSymmetryAxis = 3;
+ break;
+ }
+
+ // pChart requires two coordinates to draw the legend $legendTopLeftXValue & $legendTopLeftYValue
+ // $legendTopLeftXValue = legend's left padding
+ $legendTopLeftXValue = $topLeftXValue + ($verticalLegend ? self::VERTICAL_LEGEND_LEFT_MARGIN : self::HORIZONTAL_LEGEND_LEFT_MARGIN);
+
+ // $legendTopLeftYValue = y coordinate of the top edge of the legend's icons
+ // Caution :
+ // - pChart will silently add some value (see $paddingAddedByPChart) to $legendTopLeftYValue depending on multiple criterias
+ // - pChart will not take into account the size of the text. Setting $legendTopLeftYValue = 0 will crop the legend's labels
+ // The following section of code determines the value of $legendTopLeftYValue while taking into account the following paremeters :
+ // - whether legend items have icons
+ // - whether icons are bigger than the legend's labels
+ // - how much colored shadow padding is required
+ list($maxLogoWidth, $maxLogoHeight) = self::getMaxLogoSize(array_values($this->ordinateLogos));
+ if ($maxLogoHeight >= $this->legendFontSize) {
+ $heightOfTextAboveBulletTop = 0;
+ $paddingCreatedByLogo = $maxLogoHeight - $this->legendFontSize;
+ $effectiveShadowPadding = $paddingCreatedByLogo < self::LEGEND_VERTICAL_SHADOW_PADDING * 2 ? self::LEGEND_VERTICAL_SHADOW_PADDING - ($paddingCreatedByLogo / 2) : 0;
+ } else {
+ if ($maxLogoHeight) {
+ // measured using a picture editing software
+ $iconOffsetAboveLabelSymmetryAxis = 5;
+ }
+ $heightOfTextAboveBulletTop = $this->legendFontSize / 2 - $iconOffsetAboveLabelSymmetryAxis;
+ $effectiveShadowPadding = self::LEGEND_VERTICAL_SHADOW_PADDING;
+ }
+
+ $effectiveLegendItemVerticalInterstice = $this->legendFontSize + self::LEGEND_ITEM_VERTICAL_INTERSTICE_OFFSET;
+ $effectiveLegendItemHorizontalInterstice = self::LEGEND_ITEM_HORIZONTAL_INTERSTICE + self::LEGEND_HORIZONTAL_SHADOW_PADDING;
+
+ $legendTopMargin = $verticalLegend ? self::VERTICAL_LEGEND_TOP_MARGIN : self::HORIZONTAL_LEGEND_TOP_MARGIN;
+ $requiredPaddingAboveItemBullet = $legendTopMargin + $heightOfTextAboveBulletTop + $effectiveShadowPadding;
+
+ $paddingAddedByPChart = 0;
+ if ($verticalLegend) {
+ if ($maxLogoHeight) {
+ // see line 1691 of pDraw.class.php
+ if ($maxLogoHeight < $effectiveLegendItemVerticalInterstice) {
+ $paddingAddedByPChart = ($effectiveLegendItemVerticalInterstice / 2) - ($maxLogoHeight / 2);
+ }
+ } else {
+ // see line 1711 of pDraw.class.php ($Y+$IconAreaHeight/2)
+ $paddingAddedByPChart = $effectiveLegendItemVerticalInterstice / 2;
+ }
+ }
+
+ $legendTopLeftYValue = $paddingAddedByPChart < $requiredPaddingAboveItemBullet ? $requiredPaddingAboveItemBullet - $paddingAddedByPChart : 0;
+
+ // add colored background to each legend item
+ if (count($this->ordinateLabels) > 1) {
+ $currentPosition = $verticalLegend ? $legendTopMargin : $legendTopLeftXValue;
+ $colorIndex = 1;
+ foreach ($this->ordinateLabels as $metricCode => &$label) {
+ $color = $this->colors[self::GRAPHIC_COLOR_KEY . $colorIndex++];
+
+ $paddedBulletWidth = $bulletWidth;
+ if (isset($this->ordinateLogos[$metricCode])) {
+ $paddedBulletWidth = $maxLogoWidth;
+ }
+ $paddedBulletWidth += self::LEGEND_BULLET_RIGHT_PADDING;
+
+ // truncate labels if required
+ if ($verticalLegend) {
+ $label = $this->truncateLabel($label, ($this->width * self::VERTICAL_LEGEND_MAX_WIDTH_PCT) - $legendTopLeftXValue - $paddedBulletWidth, $this->legendFontSize);
+ $this->pData->setSerieDescription($metricCode, $label);
+ }
+
+ $rectangleTopLeftXValue = ($verticalLegend ? $legendTopLeftXValue : $currentPosition) + $paddedBulletWidth - self::LEGEND_HORIZONTAL_SHADOW_PADDING;
+ $rectangleTopLeftYValue = $verticalLegend ? $currentPosition : $legendTopMargin;
+
+ list($labelWidth, $labelHeight) = $this->getTextWidthHeight($label, $this->legendFontSize);
+ $legendItemWidth = $paddedBulletWidth + $labelWidth + $effectiveLegendItemHorizontalInterstice;
+ $rectangleBottomRightXValue = $rectangleTopLeftXValue + $labelWidth + (self::LEGEND_HORIZONTAL_SHADOW_PADDING * 2);
+
+ $legendItemHeight = max($maxLogoHeight, $this->legendFontSize) + ($effectiveShadowPadding * 2);
+ $rectangleBottomRightYValue = $rectangleTopLeftYValue + $legendItemHeight;
+
+ $this->pImage->drawFilledRectangle(
+ $rectangleTopLeftXValue,
+ $rectangleTopLeftYValue,
+ $rectangleBottomRightXValue,
+ $rectangleBottomRightYValue,
+ array(
+ 'Alpha' => self::LEGEND_SHADOW_OPACITY,
+ 'R' => $color['R'],
+ 'G' => $color['G'],
+ 'B' => $color['B'],
+ )
+ );
+
+ if ($verticalLegend) {
+ $currentPositionIncrement = max($maxLogoHeight, $effectiveLegendItemVerticalInterstice, $this->legendFontSize) + self::PCHART_HARD_CODED_VERTICAL_LEGEND_INTERSTICE;
+ } else {
+ $currentPositionIncrement = $legendItemWidth;
+ }
+
+ $currentPosition += $currentPositionIncrement;
+ }
+ }
+
+ // draw legend
+ $legendColor = $this->colors[self::VALUE_COLOR_KEY];
+ $this->pImage->drawLegend(
+ $legendTopLeftXValue,
+ $legendTopLeftYValue,
+ array(
+ 'Style' => LEGEND_NOBORDER,
+ 'FontSize' => $this->legendFontSize,
+ 'BoxWidth' => $bulletWidth,
+ 'XSpacing' => $effectiveLegendItemHorizontalInterstice, // not effective when vertical
+ 'Mode' => $verticalLegend ? LEGEND_VERTICAL : LEGEND_HORIZONTAL,
+ 'BoxHeight' => $verticalLegend ? $effectiveLegendItemVerticalInterstice : null,
+ 'Family' => $bulletType,
+ 'FontR' => $legendColor['R'],
+ 'FontG' => $legendColor['G'],
+ 'FontB' => $legendColor['B'],
+ )
+ );
+ }
+ }
+
+ protected static function getMaxLogoSize($logoPaths)
+ {
+ $maxLogoWidth = 0;
+ $maxLogoHeight = 0;
+ foreach ($logoPaths as $logoPath) {
+ list($logoWidth, $logoHeight) = self::getLogoSize($logoPath);
+
+ if ($logoWidth > $maxLogoWidth) {
+ $maxLogoWidth = $logoWidth;
+ }
+ if ($logoHeight > $maxLogoHeight) {
+ $maxLogoHeight = $logoHeight;
+ }
+ }
+
+ return array($maxLogoWidth, $maxLogoHeight);
+ }
+
+ protected static function getLogoSize($logoPath)
+ {
+ $pathInfo = getimagesize($logoPath);
+ return array($pathInfo[0], $pathInfo[1]);
+ }
+
+ protected function getGridLeftMargin($horizontalGraph, $withLabel)
+ {
+ $gridLeftMargin = self::LEFT_GRID_MARGIN + self::OUTER_TICK_WIDTH;
+
+ if ($withLabel) {
+ list($maxTextWidth, $maxTextHeight) = $this->getMaximumTextWidthHeight($horizontalGraph ? $this->abscissaSeries : $this->ordinateSeries);
+ $gridLeftMargin += $maxTextWidth;
+ }
+
+ return $gridLeftMargin;
+ }
+
+ protected function getGridTopMargin($horizontalGraph, $verticalLegend)
+ {
+ list($ordinateMaxWidth, $ordinateMaxHeight) = $this->getMaximumTextWidthHeight($this->ordinateSeries);
+
+ if ($horizontalGraph) {
+ $topMargin = $ordinateMaxHeight + self::TOP_GRID_MARGIN_HORIZONTAL_GRAPH + self::OUTER_TICK_WIDTH;
+ } else {
+ $topMargin = $ordinateMaxHeight / 2;
+ }
+
+ if ($this->showLegend && !$verticalLegend) {
+ $topMargin += $this->getHorizontalLegendHeight();
+ }
+
+ return $topMargin;
+ }
+
+ private function getHorizontalLegendHeight()
+ {
+ list($maxMetricLegendWidth, $maxMetricLegendHeight) =
+ $this->getMaximumTextWidthHeight(array_values($this->ordinateLabels), $this->legendFontSize);
+
+ return $maxMetricLegendHeight + self::HORIZONTAL_LEGEND_BOTTOM_MARGIN + self::HORIZONTAL_LEGEND_TOP_MARGIN;
+ }
+
+ protected function getGraphHeight($horizontalGraph, $verticalLegend)
+ {
+ return $this->getGraphBottom($horizontalGraph) - $this->getGridTopMargin($horizontalGraph, $verticalLegend);
+ }
+
+ private function getGridBottomMargin($horizontalGraph)
+ {
+ $gridBottomMargin = self::BOTTOM_GRID_MARGIN;
+ if (!$horizontalGraph) {
+ list($abscissaMaxWidth, $abscissaMaxHeight) = $this->getMaximumTextWidthHeight($this->abscissaSeries);
+ $gridBottomMargin += $abscissaMaxHeight;
+ }
+ return $gridBottomMargin;
+ }
+
+ protected function getGridRightMargin($horizontalGraph)
+ {
+ if ($horizontalGraph) {
+ // in horizontal graphs, metric values are displayed on the far right of the bar
+ list($ordinateMaxWidth, $ordinateMaxHeight) = $this->getMaximumTextWidthHeight($this->ordinateSeries);
+ return self::RIGHT_GRID_MARGIN_HORIZONTAL_GRAPH + $ordinateMaxWidth;
+ } else {
+ return 0;
+ }
+ }
+
+ protected function getGraphBottom($horizontalGraph)
+ {
+ return $this->height - $this->getGridBottomMargin($horizontalGraph);
+ }
+
+ protected function truncateLabel($label, $labelWidthLimit, $fontSize = false)
+ {
+ list($truncationTextWidth, $truncationTextHeight) = $this->getTextWidthHeight(self::TRUNCATION_TEXT, $fontSize);
+ list($labelWidth, $labelHeight) = $this->getTextWidthHeight($label, $fontSize);
+
+ if ($labelWidth > $labelWidthLimit) {
+ $averageCharWidth = $labelWidth / strlen($label);
+ $charsToKeep = floor(($labelWidthLimit - $truncationTextWidth) / $averageCharWidth);
+ $label = substr($label, 0, $charsToKeep) . self::TRUNCATION_TEXT;
+ }
+ return $label;
+ }
+
+ // display min & max values
+ // can not currently be used because pChart's label design is not flexible enough
+ // e.g: it is not possible to remove the box border & the square icon
+ // it would require modifying pChart code base which we try to avoid
+ // see http://dev.piwik.org/trac/ticket/3396
// protected function displayMinMaxValues()
// {
// if($displayMinMax)
diff --git a/plugins/ImageGraph/StaticGraph/HorizontalBar.php b/plugins/ImageGraph/StaticGraph/HorizontalBar.php
index e531239522..0fd8d46890 100644
--- a/plugins/ImageGraph/StaticGraph/HorizontalBar.php
+++ b/plugins/ImageGraph/StaticGraph/HorizontalBar.php
@@ -16,188 +16,170 @@
*/
class Piwik_ImageGraph_StaticGraph_HorizontalBar extends Piwik_ImageGraph_StaticGraph_GridGraph
{
- const INTERLEAVE = 0.30;
- const PADDING_CHARS = ' ';
- const LEGEND_SQUARE_WIDTH = 11;
- const MIN_SPACE_BETWEEN_HORIZONTAL_VALUES = 5;
- const LOGO_MIN_RIGHT_MARGIN = 3;
-
- public function renderGraph()
- {
- $verticalLegend = false;
-
- // determine the maximum logo width & height
- list($maxLogoWidth, $maxLogoHeight) = self::getMaxLogoSize($this->abscissaLogos);
-
- foreach($this->abscissaLogos as $logoPath)
- {
- list($logoWidth, $logoHeight) = self::getLogoSize($logoPath);
- $logoPathToHeight[$logoPath] = $logoHeight;
- }
-
- // truncate report
- $graphHeight = $this->getGraphBottom($horizontalGraph = true) - $this->getGridTopMargin($horizontalGraph = true, $verticalLegend);
-
- list($abscissaMaxWidth, $abscissaMaxHeight) = $this->getMaximumTextWidthHeight($this->abscissaSeries);
- list($ordinateMaxWidth, $ordinateMaxHeight) = $this->getMaximumTextWidthHeight($this->ordinateSeries);
-
- $numberOfSeries = count($this->ordinateSeries);
- $ordinateMaxHeight = $ordinateMaxHeight * $numberOfSeries;
-
- $textMaxHeight = $abscissaMaxHeight > $ordinateMaxHeight ? $abscissaMaxHeight : $ordinateMaxHeight;
-
- $minLineWidth = ($textMaxHeight > $maxLogoHeight ? $textMaxHeight : $maxLogoHeight) + (self::MIN_SPACE_BETWEEN_HORIZONTAL_VALUES * $numberOfSeries);
- $maxNumOfValues = floor($graphHeight / $minLineWidth);
- $abscissaSeriesCount = count($this->abscissaSeries);
-
- if($maxNumOfValues < $abscissaSeriesCount - 1)
- {
- $sumOfOthers = array();
- $truncatedOrdinateSeries = array();
- $truncatedAbscissaLogos = array();
- $truncatedAbscissaSeries = array();
- foreach($this->ordinateSeries as $column => $data)
- {
- $truncatedOrdinateSeries[$column] = array();
- $sumOfOthers[$column] = 0;
- }
-
- $i = 0;
- for(; $i < $maxNumOfValues; $i++)
- {
- foreach($this->ordinateSeries as $column => $data)
- {
- $truncatedOrdinateSeries[$column][] = $data[$i];
- }
-
- $truncatedAbscissaLogos[] = isset($this->abscissaLogos[$i]) ? $this->abscissaLogos[$i] : null;
- $truncatedAbscissaSeries[] = $this->abscissaSeries[$i];
- }
-
- for(; $i < $abscissaSeriesCount; $i++)
- {
- foreach($this->ordinateSeries as $column => $data)
- {
- $sumOfOthers[$column] += $data[$i];
- }
- }
-
- foreach($this->ordinateSeries as $column => $data)
- {
- $truncatedOrdinateSeries[$column][] = $sumOfOthers[$column];
- }
-
- $truncatedAbscissaSeries[] = Piwik_Translate('General_Others');
- $this->abscissaSeries = $truncatedAbscissaSeries;
- $this->ordinateSeries = $truncatedOrdinateSeries;
- $this->abscissaLogos = $truncatedAbscissaLogos;
- }
-
- // blank characters are used to pad labels so the logo can be displayed
- $paddingText = '';
- $paddingWidth = 0;
- if($maxLogoWidth > 0)
- {
- while($paddingWidth < $maxLogoWidth + self::LOGO_MIN_RIGHT_MARGIN)
- {
- $paddingText .= self::PADDING_CHARS;
- list($paddingWidth, $paddingHeight) = $this->getTextWidthHeight($paddingText);
- }
- }
-
- // determine the maximum label width according to the minimum comfortable graph size
- $gridRightMargin = $this->getGridRightMargin($horizontalGraph = true);
- $minGraphSize = ($this->width - $gridRightMargin) / 2;
-
- $metricLegendWidth = 0;
- foreach($this->ordinateLabels as $column => $label)
- {
- list($textWidth, $textHeight) = $this->getTextWidthHeight($label);
- $metricLegendWidth += $textWidth;
- }
-
- $legendWidth = $metricLegendWidth + ((self::HORIZONTAL_LEGEND_LEFT_MARGIN + self::LEGEND_SQUARE_WIDTH) * $numberOfSeries);
- if($this->showLegend)
- {
- if($legendWidth > $minGraphSize)
- {
- $minGraphSize = $legendWidth;
- }
- }
-
- $gridLeftMarginWithoutLabels = $this->getGridLeftMargin($horizontalGraph = true, $withLabel = false);
- $labelWidthLimit =
- $this->width
- - $gridLeftMarginWithoutLabels
- - $gridRightMargin
- - $paddingWidth
- - $minGraphSize;
-
- // truncate labels if needed
- foreach($this->abscissaSeries as &$label)
- {
- $label = $this->truncateLabel($label, $labelWidthLimit);
- }
-
- $gridLeftMarginBeforePadding = $this->getGridLeftMargin($horizontalGraph = true, $withLabel = true);
-
- // pad labels for logo space
- foreach($this->abscissaSeries as &$label)
- {
- $label .= $paddingText;
- }
-
- $this->initGridChart(
- $displayVerticalGridLines = false,
- $bulletType = LEGEND_FAMILY_BOX,
- $horizontalGraph = true,
- $showTicks = false,
- $verticalLegend
- );
-
- $valueColor = $this->colors[self::VALUE_COLOR_KEY];
- $this->pImage->drawBarChart(
- array(
- 'DisplayValues' => true,
- 'Interleave' => self::INTERLEAVE,
- 'DisplayR' => $valueColor['R'],
- 'DisplayG' => $valueColor['G'],
- 'DisplayB' => $valueColor['B'],
- )
- );
+ const INTERLEAVE = 0.30;
+ const PADDING_CHARS = ' ';
+ const LEGEND_SQUARE_WIDTH = 11;
+ const MIN_SPACE_BETWEEN_HORIZONTAL_VALUES = 5;
+ const LOGO_MIN_RIGHT_MARGIN = 3;
+
+ public function renderGraph()
+ {
+ $verticalLegend = false;
+
+ // determine the maximum logo width & height
+ list($maxLogoWidth, $maxLogoHeight) = self::getMaxLogoSize($this->abscissaLogos);
+
+ foreach ($this->abscissaLogos as $logoPath) {
+ list($logoWidth, $logoHeight) = self::getLogoSize($logoPath);
+ $logoPathToHeight[$logoPath] = $logoHeight;
+ }
+
+ // truncate report
+ $graphHeight = $this->getGraphBottom($horizontalGraph = true) - $this->getGridTopMargin($horizontalGraph = true, $verticalLegend);
+
+ list($abscissaMaxWidth, $abscissaMaxHeight) = $this->getMaximumTextWidthHeight($this->abscissaSeries);
+ list($ordinateMaxWidth, $ordinateMaxHeight) = $this->getMaximumTextWidthHeight($this->ordinateSeries);
+
+ $numberOfSeries = count($this->ordinateSeries);
+ $ordinateMaxHeight = $ordinateMaxHeight * $numberOfSeries;
+
+ $textMaxHeight = $abscissaMaxHeight > $ordinateMaxHeight ? $abscissaMaxHeight : $ordinateMaxHeight;
+
+ $minLineWidth = ($textMaxHeight > $maxLogoHeight ? $textMaxHeight : $maxLogoHeight) + (self::MIN_SPACE_BETWEEN_HORIZONTAL_VALUES * $numberOfSeries);
+ $maxNumOfValues = floor($graphHeight / $minLineWidth);
+ $abscissaSeriesCount = count($this->abscissaSeries);
+
+ if ($maxNumOfValues < $abscissaSeriesCount - 1) {
+ $sumOfOthers = array();
+ $truncatedOrdinateSeries = array();
+ $truncatedAbscissaLogos = array();
+ $truncatedAbscissaSeries = array();
+ foreach ($this->ordinateSeries as $column => $data) {
+ $truncatedOrdinateSeries[$column] = array();
+ $sumOfOthers[$column] = 0;
+ }
+
+ $i = 0;
+ for (; $i < $maxNumOfValues; $i++) {
+ foreach ($this->ordinateSeries as $column => $data) {
+ $truncatedOrdinateSeries[$column][] = $data[$i];
+ }
+
+ $truncatedAbscissaLogos[] = isset($this->abscissaLogos[$i]) ? $this->abscissaLogos[$i] : null;
+ $truncatedAbscissaSeries[] = $this->abscissaSeries[$i];
+ }
+
+ for (; $i < $abscissaSeriesCount; $i++) {
+ foreach ($this->ordinateSeries as $column => $data) {
+ $sumOfOthers[$column] += $data[$i];
+ }
+ }
+
+ foreach ($this->ordinateSeries as $column => $data) {
+ $truncatedOrdinateSeries[$column][] = $sumOfOthers[$column];
+ }
+
+ $truncatedAbscissaSeries[] = Piwik_Translate('General_Others');
+ $this->abscissaSeries = $truncatedAbscissaSeries;
+ $this->ordinateSeries = $truncatedOrdinateSeries;
+ $this->abscissaLogos = $truncatedAbscissaLogos;
+ }
+
+ // blank characters are used to pad labels so the logo can be displayed
+ $paddingText = '';
+ $paddingWidth = 0;
+ if ($maxLogoWidth > 0) {
+ while ($paddingWidth < $maxLogoWidth + self::LOGO_MIN_RIGHT_MARGIN) {
+ $paddingText .= self::PADDING_CHARS;
+ list($paddingWidth, $paddingHeight) = $this->getTextWidthHeight($paddingText);
+ }
+ }
+
+ // determine the maximum label width according to the minimum comfortable graph size
+ $gridRightMargin = $this->getGridRightMargin($horizontalGraph = true);
+ $minGraphSize = ($this->width - $gridRightMargin) / 2;
+
+ $metricLegendWidth = 0;
+ foreach ($this->ordinateLabels as $column => $label) {
+ list($textWidth, $textHeight) = $this->getTextWidthHeight($label);
+ $metricLegendWidth += $textWidth;
+ }
+
+ $legendWidth = $metricLegendWidth + ((self::HORIZONTAL_LEGEND_LEFT_MARGIN + self::LEGEND_SQUARE_WIDTH) * $numberOfSeries);
+ if ($this->showLegend) {
+ if ($legendWidth > $minGraphSize) {
+ $minGraphSize = $legendWidth;
+ }
+ }
+
+ $gridLeftMarginWithoutLabels = $this->getGridLeftMargin($horizontalGraph = true, $withLabel = false);
+ $labelWidthLimit =
+ $this->width
+ - $gridLeftMarginWithoutLabels
+ - $gridRightMargin
+ - $paddingWidth
+ - $minGraphSize;
+
+ // truncate labels if needed
+ foreach ($this->abscissaSeries as &$label) {
+ $label = $this->truncateLabel($label, $labelWidthLimit);
+ }
+
+ $gridLeftMarginBeforePadding = $this->getGridLeftMargin($horizontalGraph = true, $withLabel = true);
+
+ // pad labels for logo space
+ foreach ($this->abscissaSeries as &$label) {
+ $label .= $paddingText;
+ }
+
+ $this->initGridChart(
+ $displayVerticalGridLines = false,
+ $bulletType = LEGEND_FAMILY_BOX,
+ $horizontalGraph = true,
+ $showTicks = false,
+ $verticalLegend
+ );
+
+ $valueColor = $this->colors[self::VALUE_COLOR_KEY];
+ $this->pImage->drawBarChart(
+ array(
+ 'DisplayValues' => true,
+ 'Interleave' => self::INTERLEAVE,
+ 'DisplayR' => $valueColor['R'],
+ 'DisplayG' => $valueColor['G'],
+ 'DisplayB' => $valueColor['B'],
+ )
+ );
// // display icons
- $graphData = $this->pData->getData();
- $numberOfRows = count($this->abscissaSeries);
- $logoInterleave = $this->getGraphHeight(true, $verticalLegend) / $numberOfRows;
- for($i = 0; $i < $numberOfRows; $i++)
- {
- if(isset($this->abscissaLogos[$i]))
- {
- $logoPath = $this->abscissaLogos[$i];
-
- if(isset($logoPathToHeight[$logoPath]))
- {
- $logoHeight = $logoPathToHeight[$logoPath];
-
- $pathInfo = pathinfo($logoPath);
- $logoExtension = strtoupper($pathInfo['extension']);
- $drawingFunction = 'drawFrom' . $logoExtension;
-
- $logoYPosition =
- ($logoInterleave * $i)
- + $this->getGridTopMargin(true, $verticalLegend)
- + $graphData['Axis'][1]['Margin']
- - $logoHeight / 2
- + 1;
-
- $this->pImage->$drawingFunction(
- $gridLeftMarginBeforePadding,
- $logoYPosition,
- $logoPath
- );
- }
- }
- }
- }
+ $graphData = $this->pData->getData();
+ $numberOfRows = count($this->abscissaSeries);
+ $logoInterleave = $this->getGraphHeight(true, $verticalLegend) / $numberOfRows;
+ for ($i = 0; $i < $numberOfRows; $i++) {
+ if (isset($this->abscissaLogos[$i])) {
+ $logoPath = $this->abscissaLogos[$i];
+
+ if (isset($logoPathToHeight[$logoPath])) {
+ $logoHeight = $logoPathToHeight[$logoPath];
+
+ $pathInfo = pathinfo($logoPath);
+ $logoExtension = strtoupper($pathInfo['extension']);
+ $drawingFunction = 'drawFrom' . $logoExtension;
+
+ $logoYPosition =
+ ($logoInterleave * $i)
+ + $this->getGridTopMargin(true, $verticalLegend)
+ + $graphData['Axis'][1]['Margin']
+ - $logoHeight / 2
+ + 1;
+
+ $this->pImage->$drawingFunction(
+ $gridLeftMarginBeforePadding,
+ $logoYPosition,
+ $logoPath
+ );
+ }
+ }
+ }
+ }
}
diff --git a/plugins/ImageGraph/StaticGraph/Pie.php b/plugins/ImageGraph/StaticGraph/Pie.php
index 5c01745edd..bdfbdef9ab 100644
--- a/plugins/ImageGraph/StaticGraph/Pie.php
+++ b/plugins/ImageGraph/StaticGraph/Pie.php
@@ -15,14 +15,14 @@
*/
class Piwik_ImageGraph_StaticGraph_Pie extends Piwik_ImageGraph_StaticGraph_PieGraph
{
- public function renderGraph()
- {
- $this->initPieGraph(false);
+ public function renderGraph()
+ {
+ $this->initPieGraph(false);
- $this->pieChart->draw2DPie(
- $this->xPosition,
- $this->yPosition,
- $this->pieConfig
- );
- }
+ $this->pieChart->draw2DPie(
+ $this->xPosition,
+ $this->yPosition,
+ $this->pieConfig
+ );
+ }
}
diff --git a/plugins/ImageGraph/StaticGraph/PieGraph.php b/plugins/ImageGraph/StaticGraph/PieGraph.php
index 383f605432..d2b8df51c5 100644
--- a/plugins/ImageGraph/StaticGraph/PieGraph.php
+++ b/plugins/ImageGraph/StaticGraph/PieGraph.php
@@ -17,118 +17,107 @@ require_once PIWIK_INCLUDE_PATH . "/libs/pChart2.1.3/class/pPie.class.php";
*/
abstract class Piwik_ImageGraph_StaticGraph_PieGraph extends Piwik_ImageGraph_StaticGraph
{
- const RADIUS_MARGIN = 40;
- const PIE_RIGHT_MARGIN = 20;
- const SECTOR_GAP = 2.5;
-
- const SLICE_COLOR_KEY = "SLICE_COLOR";
-
- protected $pieChart;
- protected $xPosition;
- protected $yPosition;
- protected $pieConfig;
-
- protected function getDefaultColors()
- {
- return array(
- self::SLICE_COLOR_KEY . '1' => '3C5A69',
- self::SLICE_COLOR_KEY . '2' => '679BB5',
- self::SLICE_COLOR_KEY . '3' => '695A3C',
- self::SLICE_COLOR_KEY . '4' => 'B58E67',
- self::SLICE_COLOR_KEY . '5' => '8AA68A',
- self::SLICE_COLOR_KEY . '6' => 'A4D2A6',
- );
- }
-
- protected function initPieGraph($showLegend)
- {
- $this->truncateSmallValues();
- $this->initpData();
- $this->initpImage();
-
- if ($this->height > $this->width)
- {
- $radius = ($this->width / 2) - self::RADIUS_MARGIN;
- }
- else
- {
- $radius = ($this->height / 2) - self::RADIUS_MARGIN;
- }
-
- $this->pieChart = new pPie($this->pImage, $this->pData);
-
- $numberOfSlices = count($this->abscissaSeries);
- $numberOfAvailableColors = count($this->colors);
- for($i = 0; $i < $numberOfSlices; $i++)
- {
- $this->pieChart->setSliceColor($i, $this->colors[self::SLICE_COLOR_KEY . (($i % $numberOfAvailableColors) + 1)]);
- }
-
- // max abscissa label width is used to set the pie right margin
- list($abscissaMaxWidth, $abscissaMaxHeight) = $this->getMaximumTextWidthHeight($this->abscissaSeries);
-
- $this->xPosition = $this->width - $radius - $abscissaMaxWidth - self::PIE_RIGHT_MARGIN;
- $this->yPosition = $this->height / 2;
-
- if ($showLegend)
- {
- $this->pieChart->drawPieLegend(15, 40, array("Alpha" => 20));
- }
-
- $this->pieConfig =
- array(
- 'Radius' => $radius,
- 'DrawLabels' => true,
- 'DataGapAngle' => self::SECTOR_GAP,
- 'DataGapRadius' => self::SECTOR_GAP,
- );
- }
-
- /**
- * this method logic is close to Piwik's core filter_truncate.
- * it uses a threshold to determine if an abscissa value should be drawn on the PIE
- * discarded abscissa values are summed in the 'other' abscissa value
- *
- * if this process is not perform, pChart will draw pie slices that are too small to see
- */
- private function truncateSmallValues()
- {
- $metricColumns = array_keys($this->ordinateSeries);
- $metricColumn = $metricColumns[0];
-
- $ordinateValuesSum = 0;
- foreach($this->ordinateSeries[$metricColumn] as $ordinateValue)
- {
- $ordinateValuesSum += $ordinateValue;
- }
-
- $truncatedOrdinateSeries[$metricColumn] = array();
- $truncatedAbscissaSeries = array();
- $smallValuesSum = 0;
-
- $ordinateValuesCount = count($this->ordinateSeries[$metricColumn]);
- for($i = 0; $i < $ordinateValuesCount - 1 ; $i++)
- {
- $ordinateValue = $this->ordinateSeries[$metricColumn][$i];
- if($ordinateValue / $ordinateValuesSum > 0.01)
- {
- $truncatedOrdinateSeries[$metricColumn][] = $ordinateValue;
- $truncatedAbscissaSeries[] = $this->abscissaSeries[$i];
- }
- else
- {
- $smallValuesSum += $ordinateValue;
- }
- }
-
- $smallValuesSum += $this->ordinateSeries[$metricColumn][$ordinateValuesCount - 1];
- if(($smallValuesSum / $ordinateValuesSum) > 0.01)
- {
- $truncatedOrdinateSeries[$metricColumn][] = $smallValuesSum;
- $truncatedAbscissaSeries[] = Piwik_Translate('General_Others');
- }
-
- $this->ordinateSeries = $truncatedOrdinateSeries;
- $this->abscissaSeries = $truncatedAbscissaSeries;
- }
+ const RADIUS_MARGIN = 40;
+ const PIE_RIGHT_MARGIN = 20;
+ const SECTOR_GAP = 2.5;
+
+ const SLICE_COLOR_KEY = "SLICE_COLOR";
+
+ protected $pieChart;
+ protected $xPosition;
+ protected $yPosition;
+ protected $pieConfig;
+
+ protected function getDefaultColors()
+ {
+ return array(
+ self::SLICE_COLOR_KEY . '1' => '3C5A69',
+ self::SLICE_COLOR_KEY . '2' => '679BB5',
+ self::SLICE_COLOR_KEY . '3' => '695A3C',
+ self::SLICE_COLOR_KEY . '4' => 'B58E67',
+ self::SLICE_COLOR_KEY . '5' => '8AA68A',
+ self::SLICE_COLOR_KEY . '6' => 'A4D2A6',
+ );
+ }
+
+ protected function initPieGraph($showLegend)
+ {
+ $this->truncateSmallValues();
+ $this->initpData();
+ $this->initpImage();
+
+ if ($this->height > $this->width) {
+ $radius = ($this->width / 2) - self::RADIUS_MARGIN;
+ } else {
+ $radius = ($this->height / 2) - self::RADIUS_MARGIN;
+ }
+
+ $this->pieChart = new pPie($this->pImage, $this->pData);
+
+ $numberOfSlices = count($this->abscissaSeries);
+ $numberOfAvailableColors = count($this->colors);
+ for ($i = 0; $i < $numberOfSlices; $i++) {
+ $this->pieChart->setSliceColor($i, $this->colors[self::SLICE_COLOR_KEY . (($i % $numberOfAvailableColors) + 1)]);
+ }
+
+ // max abscissa label width is used to set the pie right margin
+ list($abscissaMaxWidth, $abscissaMaxHeight) = $this->getMaximumTextWidthHeight($this->abscissaSeries);
+
+ $this->xPosition = $this->width - $radius - $abscissaMaxWidth - self::PIE_RIGHT_MARGIN;
+ $this->yPosition = $this->height / 2;
+
+ if ($showLegend) {
+ $this->pieChart->drawPieLegend(15, 40, array("Alpha" => 20));
+ }
+
+ $this->pieConfig =
+ array(
+ 'Radius' => $radius,
+ 'DrawLabels' => true,
+ 'DataGapAngle' => self::SECTOR_GAP,
+ 'DataGapRadius' => self::SECTOR_GAP,
+ );
+ }
+
+ /**
+ * this method logic is close to Piwik's core filter_truncate.
+ * it uses a threshold to determine if an abscissa value should be drawn on the PIE
+ * discarded abscissa values are summed in the 'other' abscissa value
+ *
+ * if this process is not perform, pChart will draw pie slices that are too small to see
+ */
+ private function truncateSmallValues()
+ {
+ $metricColumns = array_keys($this->ordinateSeries);
+ $metricColumn = $metricColumns[0];
+
+ $ordinateValuesSum = 0;
+ foreach ($this->ordinateSeries[$metricColumn] as $ordinateValue) {
+ $ordinateValuesSum += $ordinateValue;
+ }
+
+ $truncatedOrdinateSeries[$metricColumn] = array();
+ $truncatedAbscissaSeries = array();
+ $smallValuesSum = 0;
+
+ $ordinateValuesCount = count($this->ordinateSeries[$metricColumn]);
+ for ($i = 0; $i < $ordinateValuesCount - 1; $i++) {
+ $ordinateValue = $this->ordinateSeries[$metricColumn][$i];
+ if ($ordinateValue / $ordinateValuesSum > 0.01) {
+ $truncatedOrdinateSeries[$metricColumn][] = $ordinateValue;
+ $truncatedAbscissaSeries[] = $this->abscissaSeries[$i];
+ } else {
+ $smallValuesSum += $ordinateValue;
+ }
+ }
+
+ $smallValuesSum += $this->ordinateSeries[$metricColumn][$ordinateValuesCount - 1];
+ if (($smallValuesSum / $ordinateValuesSum) > 0.01) {
+ $truncatedOrdinateSeries[$metricColumn][] = $smallValuesSum;
+ $truncatedAbscissaSeries[] = Piwik_Translate('General_Others');
+ }
+
+ $this->ordinateSeries = $truncatedOrdinateSeries;
+ $this->abscissaSeries = $truncatedAbscissaSeries;
+ }
}
diff --git a/plugins/ImageGraph/StaticGraph/VerticalBar.php b/plugins/ImageGraph/StaticGraph/VerticalBar.php
index cac1b219c6..5bf40df7f8 100644
--- a/plugins/ImageGraph/StaticGraph/VerticalBar.php
+++ b/plugins/ImageGraph/StaticGraph/VerticalBar.php
@@ -16,22 +16,22 @@
*/
class Piwik_ImageGraph_StaticGraph_VerticalBar extends Piwik_ImageGraph_StaticGraph_GridGraph
{
- const INTERLEAVE = 0.10;
+ const INTERLEAVE = 0.10;
- public function renderGraph()
- {
- $this->initGridChart(
- $displayVerticalGridLines = false,
- $bulletType = LEGEND_FAMILY_BOX,
- $horizontalGraph = false,
- $showTicks = true,
- $verticalLegend = false
- );
+ public function renderGraph()
+ {
+ $this->initGridChart(
+ $displayVerticalGridLines = false,
+ $bulletType = LEGEND_FAMILY_BOX,
+ $horizontalGraph = false,
+ $showTicks = true,
+ $verticalLegend = false
+ );
- $this->pImage->drawBarChart(
- array(
- 'Interleave' => self::INTERLEAVE,
- )
- );
- }
+ $this->pImage->drawBarChart(
+ array(
+ 'Interleave' => self::INTERLEAVE,
+ )
+ );
+ }
}
diff --git a/plugins/ImageGraph/templates/debug_graphs_all_sizes.tpl b/plugins/ImageGraph/templates/debug_graphs_all_sizes.tpl
index 67e04750f1..9214293f95 100644
--- a/plugins/ImageGraph/templates/debug_graphs_all_sizes.tpl
+++ b/plugins/ImageGraph/templates/debug_graphs_all_sizes.tpl
@@ -3,49 +3,50 @@
{include file="CoreHome/templates/header.tpl"}
<div class="top_controls_inner"></div>
<div>
- <h2>{"ImageGraph_ImageGraph"|translate} ::: {$siteName}</h2>
- <div class="top_controls_inner">
- {include file="CoreHome/templates/period_select.tpl"}
- </div>
-
- <div class="entityContainer" style="width:100%;">
- <div class="entityAddContainer">
- <table class="dataTable entityTable">
- <thead>
- </thead>
- <tbody>
- <tr class="first">
- <th style="white-space:normal;">Category</th>
- <th style="white-space:normal;">Name</th>
-
- {foreach from=$graphTypes item=type}
- <th style="white-space:normal;">{$type}</th>
- {/foreach}
- </tr>
- {foreach from=$availableReports item=report name=i}
- {if isset($report.imageGraphUrl)}
- <tr>
- <td>{$report.category|escape:"html"}</td>
- <td>{$report.name|escape:"html"}</td>
- {foreach from=$graphTypes item=type}
- <td>
- <h2>Graph {$type} for all supported sizes</h2>
- {foreach from=$graphSizes item=sizes}
- <p>{$sizes.0} x {$sizes.1} {if !empty($sizes.2)} (scaled down to {$sizes.3} x {$sizes.4}){/if}</p>
- <img
- src="{$report.imageGraphUrl}&graphType={$type}&width={$sizes.0}&height={$sizes.1}{if !empty($sizes.2)}&fontSize={$sizes.2}{/if}"
- {if !empty($sizes.3)}width={$sizes.3}{/if}
- {if !empty($sizes.4)}height={$sizes.4}{/if}
- />
- {/foreach}
- </td>
- {/foreach}
- </tr>
- {/if}
- {/foreach}
- </tbody>
- </table>
- </div>
- <a id="bottom"></a>
- </div>
+ <h2>{"ImageGraph_ImageGraph"|translate} ::: {$siteName}</h2>
+
+ <div class="top_controls_inner">
+ {include file="CoreHome/templates/period_select.tpl"}
+ </div>
+
+ <div class="entityContainer" style="width:100%;">
+ <div class="entityAddContainer">
+ <table class="dataTable entityTable">
+ <thead>
+ </thead>
+ <tbody>
+ <tr class="first">
+ <th style="white-space:normal;">Category</th>
+ <th style="white-space:normal;">Name</th>
+
+ {foreach from=$graphTypes item=type}
+ <th style="white-space:normal;">{$type}</th>
+ {/foreach}
+ </tr>
+ {foreach from=$availableReports item=report name=i}
+ {if isset($report.imageGraphUrl)}
+ <tr>
+ <td>{$report.category|escape:"html"}</td>
+ <td>{$report.name|escape:"html"}</td>
+ {foreach from=$graphTypes item=type}
+ <td>
+ <h2>Graph {$type} for all supported sizes</h2>
+ {foreach from=$graphSizes item=sizes}
+ <p>{$sizes.0} x {$sizes.1} {if !empty($sizes.2)} (scaled down to {$sizes.3} x {$sizes.4}){/if}</p>
+ <img
+ src="{$report.imageGraphUrl}&graphType={$type}&width={$sizes.0}&height={$sizes.1}{if !empty($sizes.2)}&fontSize={$sizes.2}{/if}"
+ {if !empty($sizes.3)}width={$sizes.3}{/if}
+ {if !empty($sizes.4)}height={$sizes.4}{/if}
+ />
+ {/foreach}
+ </td>
+ {/foreach}
+ </tr>
+ {/if}
+ {/foreach}
+ </tbody>
+ </table>
+ </div>
+ <a id="bottom"></a>
+ </div>
</div> \ No newline at end of file
diff --git a/plugins/ImageGraph/templates/index.tpl b/plugins/ImageGraph/templates/index.tpl
index f292e8271a..8dae3a1901 100644
--- a/plugins/ImageGraph/templates/index.tpl
+++ b/plugins/ImageGraph/templates/index.tpl
@@ -1,4 +1,4 @@
{foreach from=$titleAndUrls item=plot}
- <h2>{$plot.0|escape}</h2>
- <a href='{$plot.1}'><img border=0 src="{$plot.1}"></a>
+ <h2>{$plot.0|escape}</h2>
+ <a href='{$plot.1}'><img border=0 src="{$plot.1}"></a>
{/foreach} \ No newline at end of file
diff --git a/plugins/Installation/Controller.php b/plugins/Installation/Controller.php
index 1cdc760c73..153991a5d3 100644
--- a/plugins/Installation/Controller.php
+++ b/plugins/Installation/Controller.php
@@ -16,381 +16,365 @@
*/
class Piwik_Installation_Controller extends Piwik_Controller_Admin
{
- // public so plugins can add/delete installation steps
- public $steps = array(
- 'welcome' => 'Installation_Welcome',
- 'systemCheck' => 'Installation_SystemCheck',
- 'databaseSetup' => 'Installation_DatabaseSetup',
- 'databaseCheck' => 'Installation_DatabaseCheck',
- 'tablesCreation' => 'Installation_Tables',
- 'generalSetup' => 'Installation_SuperUser',
- 'firstWebsiteSetup' => 'Installation_SetupWebsite',
- 'displayJavascriptCode' => 'Installation_JsTag',
- 'finished' => 'Installation_Congratulations',
- );
-
- protected $pathView = 'Installation/templates/';
-
- protected $session;
-
- public function __construct()
- {
- $this->session = new Piwik_Session_Namespace('Piwik_Installation');
- if(!isset($this->session->currentStepDone))
- {
- $this->session->currentStepDone = '';
- $this->session->skipThisStep = array();
- }
-
- Piwik_PostEvent('InstallationController.construct', $this);
- }
-
- /**
- * Get installation steps
- *
- * @return array installation steps
- */
- public function getInstallationSteps()
- {
- return $this->steps;
- }
-
- /**
- * Get default action (first installation step)
- *
- * @return string function name
- */
- function getDefaultAction()
- {
- $steps = array_keys($this->steps);
- return $steps[0];
- }
-
- /**
- * Installation Step 1: Welcome
- */
- function welcome($message = false)
- {
- // Delete merged js/css files to force regenerations based on updated activated plugin list
- Piwik::deleteAllCacheOnUpdate();
-
- $view = new Piwik_Installation_View(
- $this->pathView . 'welcome.tpl',
- $this->getInstallationSteps(),
- __FUNCTION__
- );
- $view->newInstall = !file_exists(Piwik_Config::getLocalConfigPath());
- $view->errorMessage = $message;
- $this->skipThisStep( __FUNCTION__ );
- $view->showNextStep = $view->newInstall;
- $this->session->currentStepDone = __FUNCTION__;
- echo $view->render();
- }
-
- /**
- * Installation Step 2: System Check
- */
- function systemCheck()
- {
- $this->checkPreviousStepIsValid( __FUNCTION__ );
-
- $view = new Piwik_Installation_View(
- $this->pathView . 'systemCheck.tpl',
- $this->getInstallationSteps(),
- __FUNCTION__
- );
- $this->skipThisStep( __FUNCTION__ );
-
- $view->duringInstall = true;
-
- $this->setupSystemCheckView($view);
- $this->session->general_infos = $view->infos['general_infos'];
-
- // make sure DB sessions are used if the filesystem is NFS
- if ($view->infos['is_nfs'])
- {
- $this->session->general_infos['session_save_handler'] = 'dbtable';
- }
-
- $view->showNextStep = !$view->problemWithSomeDirectories
- && $view->infos['phpVersion_ok']
- && count($view->infos['adapters'])
- && !count($view->infos['missing_extensions'])
- && !count($view->infos['missing_functions'])
- ;
- // On the system check page, if all is green, display Next link at the top
- $view->showNextStepAtTop = $view->showNextStep;
-
- $this->session->currentStepDone = __FUNCTION__;
-
- echo $view->render();
- }
-
- /**
- * Installation Step 3: Database Set-up
- * @throws Exception|Zend_Db_Adapter_Exception
- */
- function databaseSetup()
- {
- $this->checkPreviousStepIsValid( __FUNCTION__ );
-
- // case the user hits the back button
- $this->session->skipThisStep = array(
- 'firstWebsiteSetup' => false,
- 'displayJavascriptCode' => false,
- );
-
- $view = new Piwik_Installation_View(
- $this->pathView . 'databaseSetup.tpl',
- $this->getInstallationSteps(),
- __FUNCTION__
- );
- $this->skipThisStep( __FUNCTION__ );
-
- $view->showNextStep = false;
-
- $form = new Piwik_Installation_FormDatabaseSetup();
-
- if ($form->validate())
- {
- try
- {
- $dbInfos = $form->createDatabaseObject();
- $this->session->databaseCreated = true;
-
- Piwik::checkDatabaseVersion();
- $this->session->databaseVersionOk = true;
-
- $this->session->db_infos = $dbInfos;
- $this->redirectToNextStep( __FUNCTION__ );
- }
- catch (Exception $e)
- {
- $view->errorMessage = Piwik_Common::sanitizeInputValue($e->getMessage());
- }
- }
- $view->addForm($form);
-
- echo $view->render();
- }
-
- /**
- * Installation Step 4: Database Check
- */
- function databaseCheck()
- {
- $this->checkPreviousStepIsValid( __FUNCTION__ );
- $view = new Piwik_Installation_View(
- $this->pathView . 'databaseCheck.tpl',
- $this->getInstallationSteps(),
- __FUNCTION__
- );
-
- $error = false;
- $this->skipThisStep( __FUNCTION__ );
-
- if(isset($this->session->databaseVersionOk)
- && $this->session->databaseVersionOk === true)
- {
- $view->databaseVersionOk = true;
- }
- else
- {
- $error = true;
- }
-
- if(isset($this->session->databaseCreated)
- && $this->session->databaseCreated === true)
- {
- $dbInfos = $this->session->db_infos;
- $view->databaseName = $dbInfos['dbname'];
- $view->databaseCreated = true;
- }
- else
- {
- $error = true;
- }
-
- $this->createDbFromSessionInformation();
- $db = Zend_Registry::get('db');
-
- try {
- $db->checkClientVersion();
- } catch(Exception $e) {
- $view->clientVersionWarning = $e->getMessage();
- $error = true;
- }
-
- if(!Piwik::isDatabaseConnectionUTF8())
- {
- $dbInfos = $this->session->db_infos;
- $dbInfos['charset'] = 'utf8';
- $this->session->db_infos = $dbInfos;
- }
-
- $view->showNextStep = true;
- $this->session->currentStepDone = __FUNCTION__;
-
- if($error === false)
- {
- $this->redirectToNextStep(__FUNCTION__);
- }
- echo $view->render();
- }
-
- /**
- * Installation Step 5: Table Creation
- */
- function tablesCreation()
- {
- $this->checkPreviousStepIsValid( __FUNCTION__ );
-
- $view = new Piwik_Installation_View(
- $this->pathView . 'tablesCreation.tpl',
- $this->getInstallationSteps(),
- __FUNCTION__
- );
- $this->skipThisStep( __FUNCTION__ );
- $this->createDbFromSessionInformation();
-
- if(Piwik_Common::getRequestVar('deleteTables', 0, 'int') == 1)
- {
- Piwik::dropTables();
- $view->existingTablesDeleted = true;
-
- // when the user decides to drop the tables then we dont skip the next steps anymore
- // workaround ZF-1743
- $tmp = $this->session->skipThisStep;
- $tmp['firstWebsiteSetup'] = false;
- $tmp['displayJavascriptCode'] = false;
- $this->session->skipThisStep = $tmp;
- }
-
- $tablesInstalled = Piwik::getTablesInstalled();
- $tablesToInstall = Piwik::getTablesNames();
- $view->tablesInstalled = '';
- if(count($tablesInstalled) > 0)
- {
- // we have existing tables
- $view->tablesInstalled = implode(', ', $tablesInstalled);
- $view->someTablesInstalled = true;
-
- $minimumCountPiwikTables = 17;
- $baseTablesInstalled = preg_grep('/archive_numeric|archive_blob/', $tablesInstalled, PREG_GREP_INVERT);
-
- Piwik::createAccessObject();
- Piwik::setUserIsSuperUser();
- if(count($baseTablesInstalled) >= $minimumCountPiwikTables &&
- count(Piwik_SitesManager_API::getInstance()->getAllSitesId()) > 0 &&
- count(Piwik_UsersManager_API::getInstance()->getUsers()) > 0)
- {
- $view->showReuseExistingTables = true;
- // when the user reuses the same tables we skip the website creation step
- // workaround ZF-1743
- $tmp = $this->session->skipThisStep;
- $tmp['firstWebsiteSetup'] = true;
- $tmp['displayJavascriptCode'] = true;
- $this->session->skipThisStep = $tmp;
- }
- }
- else
- {
- Piwik::createTables();
- Piwik::createAnonymousUser();
-
- $updater = new Piwik_Updater();
- $updater->recordComponentSuccessfullyUpdated('core', Piwik_Version::VERSION);
- $view->tablesCreated = true;
- $view->showNextStep = true;
- }
-
- $this->session->currentStepDone = __FUNCTION__;
- echo $view->render();
- }
-
- /**
- * Installation Step 6: General Set-up (superuser login/password/email and subscriptions)
- */
- function generalSetup()
- {
- $this->checkPreviousStepIsValid( __FUNCTION__ );
-
- $view = new Piwik_Installation_View(
- $this->pathView . 'generalSetup.tpl',
- $this->getInstallationSteps(),
- __FUNCTION__
- );
- $this->skipThisStep( __FUNCTION__ );
-
- $form = new Piwik_Installation_FormGeneralSetup();
-
- if($form->validate())
- {
- $superUserInfos = array(
- 'login' => $form->getSubmitValue('login'),
- 'password' => md5( $form->getSubmitValue('password') ),
- 'email' => $form->getSubmitValue('email'),
- 'salt' => Piwik_Common::generateUniqId(),
- );
-
- $this->session->superuser_infos = $superUserInfos;
-
- $url = Piwik_Config::getInstance()->General['api_service_url'];
- $url .= '/1.0/subscribeNewsletter/';
- $params = array(
- 'email' => $form->getSubmitValue('email'),
- 'security' => $form->getSubmitValue('subscribe_newsletter_security'),
- 'community' => $form->getSubmitValue('subscribe_newsletter_community'),
- 'url' => Piwik_Url::getCurrentUrlWithoutQueryString(),
- );
- if($params['security'] == '1'
- || $params['community'] == '1')
- {
- if( !isset($params['security'])) { $params['security'] = '0'; }
- if( !isset($params['community'])) { $params['community'] = '0'; }
- $url .= '?' . http_build_query($params, '', '&');
- try {
- Piwik_Http::sendHttpRequest($url, $timeout = 2);
- } catch(Exception $e) {
- // e.g., disable_functions = fsockopen; allow_url_open = Off
- }
- }
- $this->redirectToNextStep( __FUNCTION__ );
- }
- $view->addForm($form);
-
- echo $view->render();
- }
-
- /**
- * Installation Step 7: Configure first web-site
- */
- public function firstWebsiteSetup()
- {
- $this->checkPreviousStepIsValid( __FUNCTION__ );
-
- $view = new Piwik_Installation_View(
- $this->pathView . 'firstWebsiteSetup.tpl',
- $this->getInstallationSteps(),
- __FUNCTION__
- );
- $this->skipThisStep( __FUNCTION__ );
-
- $form = new Piwik_Installation_FormFirstWebsiteSetup();
- if( !isset($this->session->generalSetupSuccessMessage))
- {
- $view->displayGeneralSetupSuccess = true;
- $this->session->generalSetupSuccessMessage = true;
- }
-
- $this->initObjectsToCallAPI();
- if($form->validate())
- {
- $name = urlencode($form->getSubmitValue('siteName'));
- $url = urlencode($form->getSubmitValue('url'));
- $ecommerce = (int)$form->getSubmitValue('ecommerce');
-
- $request = new Piwik_API_Request("
+ // public so plugins can add/delete installation steps
+ public $steps = array(
+ 'welcome' => 'Installation_Welcome',
+ 'systemCheck' => 'Installation_SystemCheck',
+ 'databaseSetup' => 'Installation_DatabaseSetup',
+ 'databaseCheck' => 'Installation_DatabaseCheck',
+ 'tablesCreation' => 'Installation_Tables',
+ 'generalSetup' => 'Installation_SuperUser',
+ 'firstWebsiteSetup' => 'Installation_SetupWebsite',
+ 'displayJavascriptCode' => 'Installation_JsTag',
+ 'finished' => 'Installation_Congratulations',
+ );
+
+ protected $pathView = 'Installation/templates/';
+
+ protected $session;
+
+ public function __construct()
+ {
+ $this->session = new Piwik_Session_Namespace('Piwik_Installation');
+ if (!isset($this->session->currentStepDone)) {
+ $this->session->currentStepDone = '';
+ $this->session->skipThisStep = array();
+ }
+
+ Piwik_PostEvent('InstallationController.construct', $this);
+ }
+
+ /**
+ * Get installation steps
+ *
+ * @return array installation steps
+ */
+ public function getInstallationSteps()
+ {
+ return $this->steps;
+ }
+
+ /**
+ * Get default action (first installation step)
+ *
+ * @return string function name
+ */
+ function getDefaultAction()
+ {
+ $steps = array_keys($this->steps);
+ return $steps[0];
+ }
+
+ /**
+ * Installation Step 1: Welcome
+ */
+ function welcome($message = false)
+ {
+ // Delete merged js/css files to force regenerations based on updated activated plugin list
+ Piwik::deleteAllCacheOnUpdate();
+
+ $view = new Piwik_Installation_View(
+ $this->pathView . 'welcome.tpl',
+ $this->getInstallationSteps(),
+ __FUNCTION__
+ );
+ $view->newInstall = !file_exists(Piwik_Config::getLocalConfigPath());
+ $view->errorMessage = $message;
+ $this->skipThisStep(__FUNCTION__);
+ $view->showNextStep = $view->newInstall;
+ $this->session->currentStepDone = __FUNCTION__;
+ echo $view->render();
+ }
+
+ /**
+ * Installation Step 2: System Check
+ */
+ function systemCheck()
+ {
+ $this->checkPreviousStepIsValid(__FUNCTION__);
+
+ $view = new Piwik_Installation_View(
+ $this->pathView . 'systemCheck.tpl',
+ $this->getInstallationSteps(),
+ __FUNCTION__
+ );
+ $this->skipThisStep(__FUNCTION__);
+
+ $view->duringInstall = true;
+
+ $this->setupSystemCheckView($view);
+ $this->session->general_infos = $view->infos['general_infos'];
+
+ // make sure DB sessions are used if the filesystem is NFS
+ if ($view->infos['is_nfs']) {
+ $this->session->general_infos['session_save_handler'] = 'dbtable';
+ }
+
+ $view->showNextStep = !$view->problemWithSomeDirectories
+ && $view->infos['phpVersion_ok']
+ && count($view->infos['adapters'])
+ && !count($view->infos['missing_extensions'])
+ && !count($view->infos['missing_functions']);
+ // On the system check page, if all is green, display Next link at the top
+ $view->showNextStepAtTop = $view->showNextStep;
+
+ $this->session->currentStepDone = __FUNCTION__;
+
+ echo $view->render();
+ }
+
+ /**
+ * Installation Step 3: Database Set-up
+ * @throws Exception|Zend_Db_Adapter_Exception
+ */
+ function databaseSetup()
+ {
+ $this->checkPreviousStepIsValid(__FUNCTION__);
+
+ // case the user hits the back button
+ $this->session->skipThisStep = array(
+ 'firstWebsiteSetup' => false,
+ 'displayJavascriptCode' => false,
+ );
+
+ $view = new Piwik_Installation_View(
+ $this->pathView . 'databaseSetup.tpl',
+ $this->getInstallationSteps(),
+ __FUNCTION__
+ );
+ $this->skipThisStep(__FUNCTION__);
+
+ $view->showNextStep = false;
+
+ $form = new Piwik_Installation_FormDatabaseSetup();
+
+ if ($form->validate()) {
+ try {
+ $dbInfos = $form->createDatabaseObject();
+ $this->session->databaseCreated = true;
+
+ Piwik::checkDatabaseVersion();
+ $this->session->databaseVersionOk = true;
+
+ $this->session->db_infos = $dbInfos;
+ $this->redirectToNextStep(__FUNCTION__);
+ } catch (Exception $e) {
+ $view->errorMessage = Piwik_Common::sanitizeInputValue($e->getMessage());
+ }
+ }
+ $view->addForm($form);
+
+ echo $view->render();
+ }
+
+ /**
+ * Installation Step 4: Database Check
+ */
+ function databaseCheck()
+ {
+ $this->checkPreviousStepIsValid(__FUNCTION__);
+ $view = new Piwik_Installation_View(
+ $this->pathView . 'databaseCheck.tpl',
+ $this->getInstallationSteps(),
+ __FUNCTION__
+ );
+
+ $error = false;
+ $this->skipThisStep(__FUNCTION__);
+
+ if (isset($this->session->databaseVersionOk)
+ && $this->session->databaseVersionOk === true
+ ) {
+ $view->databaseVersionOk = true;
+ } else {
+ $error = true;
+ }
+
+ if (isset($this->session->databaseCreated)
+ && $this->session->databaseCreated === true
+ ) {
+ $dbInfos = $this->session->db_infos;
+ $view->databaseName = $dbInfos['dbname'];
+ $view->databaseCreated = true;
+ } else {
+ $error = true;
+ }
+
+ $this->createDbFromSessionInformation();
+ $db = Zend_Registry::get('db');
+
+ try {
+ $db->checkClientVersion();
+ } catch (Exception $e) {
+ $view->clientVersionWarning = $e->getMessage();
+ $error = true;
+ }
+
+ if (!Piwik::isDatabaseConnectionUTF8()) {
+ $dbInfos = $this->session->db_infos;
+ $dbInfos['charset'] = 'utf8';
+ $this->session->db_infos = $dbInfos;
+ }
+
+ $view->showNextStep = true;
+ $this->session->currentStepDone = __FUNCTION__;
+
+ if ($error === false) {
+ $this->redirectToNextStep(__FUNCTION__);
+ }
+ echo $view->render();
+ }
+
+ /**
+ * Installation Step 5: Table Creation
+ */
+ function tablesCreation()
+ {
+ $this->checkPreviousStepIsValid(__FUNCTION__);
+
+ $view = new Piwik_Installation_View(
+ $this->pathView . 'tablesCreation.tpl',
+ $this->getInstallationSteps(),
+ __FUNCTION__
+ );
+ $this->skipThisStep(__FUNCTION__);
+ $this->createDbFromSessionInformation();
+
+ if (Piwik_Common::getRequestVar('deleteTables', 0, 'int') == 1) {
+ Piwik::dropTables();
+ $view->existingTablesDeleted = true;
+
+ // when the user decides to drop the tables then we dont skip the next steps anymore
+ // workaround ZF-1743
+ $tmp = $this->session->skipThisStep;
+ $tmp['firstWebsiteSetup'] = false;
+ $tmp['displayJavascriptCode'] = false;
+ $this->session->skipThisStep = $tmp;
+ }
+
+ $tablesInstalled = Piwik::getTablesInstalled();
+ $tablesToInstall = Piwik::getTablesNames();
+ $view->tablesInstalled = '';
+ if (count($tablesInstalled) > 0) {
+ // we have existing tables
+ $view->tablesInstalled = implode(', ', $tablesInstalled);
+ $view->someTablesInstalled = true;
+
+ $minimumCountPiwikTables = 17;
+ $baseTablesInstalled = preg_grep('/archive_numeric|archive_blob/', $tablesInstalled, PREG_GREP_INVERT);
+
+ Piwik::createAccessObject();
+ Piwik::setUserIsSuperUser();
+ if (count($baseTablesInstalled) >= $minimumCountPiwikTables &&
+ count(Piwik_SitesManager_API::getInstance()->getAllSitesId()) > 0 &&
+ count(Piwik_UsersManager_API::getInstance()->getUsers()) > 0
+ ) {
+ $view->showReuseExistingTables = true;
+ // when the user reuses the same tables we skip the website creation step
+ // workaround ZF-1743
+ $tmp = $this->session->skipThisStep;
+ $tmp['firstWebsiteSetup'] = true;
+ $tmp['displayJavascriptCode'] = true;
+ $this->session->skipThisStep = $tmp;
+ }
+ } else {
+ Piwik::createTables();
+ Piwik::createAnonymousUser();
+
+ $updater = new Piwik_Updater();
+ $updater->recordComponentSuccessfullyUpdated('core', Piwik_Version::VERSION);
+ $view->tablesCreated = true;
+ $view->showNextStep = true;
+ }
+
+ $this->session->currentStepDone = __FUNCTION__;
+ echo $view->render();
+ }
+
+ /**
+ * Installation Step 6: General Set-up (superuser login/password/email and subscriptions)
+ */
+ function generalSetup()
+ {
+ $this->checkPreviousStepIsValid(__FUNCTION__);
+
+ $view = new Piwik_Installation_View(
+ $this->pathView . 'generalSetup.tpl',
+ $this->getInstallationSteps(),
+ __FUNCTION__
+ );
+ $this->skipThisStep(__FUNCTION__);
+
+ $form = new Piwik_Installation_FormGeneralSetup();
+
+ if ($form->validate()) {
+ $superUserInfos = array(
+ 'login' => $form->getSubmitValue('login'),
+ 'password' => md5($form->getSubmitValue('password')),
+ 'email' => $form->getSubmitValue('email'),
+ 'salt' => Piwik_Common::generateUniqId(),
+ );
+
+ $this->session->superuser_infos = $superUserInfos;
+
+ $url = Piwik_Config::getInstance()->General['api_service_url'];
+ $url .= '/1.0/subscribeNewsletter/';
+ $params = array(
+ 'email' => $form->getSubmitValue('email'),
+ 'security' => $form->getSubmitValue('subscribe_newsletter_security'),
+ 'community' => $form->getSubmitValue('subscribe_newsletter_community'),
+ 'url' => Piwik_Url::getCurrentUrlWithoutQueryString(),
+ );
+ if ($params['security'] == '1'
+ || $params['community'] == '1'
+ ) {
+ if (!isset($params['security'])) {
+ $params['security'] = '0';
+ }
+ if (!isset($params['community'])) {
+ $params['community'] = '0';
+ }
+ $url .= '?' . http_build_query($params, '', '&');
+ try {
+ Piwik_Http::sendHttpRequest($url, $timeout = 2);
+ } catch (Exception $e) {
+ // e.g., disable_functions = fsockopen; allow_url_open = Off
+ }
+ }
+ $this->redirectToNextStep(__FUNCTION__);
+ }
+ $view->addForm($form);
+
+ echo $view->render();
+ }
+
+ /**
+ * Installation Step 7: Configure first web-site
+ */
+ public function firstWebsiteSetup()
+ {
+ $this->checkPreviousStepIsValid(__FUNCTION__);
+
+ $view = new Piwik_Installation_View(
+ $this->pathView . 'firstWebsiteSetup.tpl',
+ $this->getInstallationSteps(),
+ __FUNCTION__
+ );
+ $this->skipThisStep(__FUNCTION__);
+
+ $form = new Piwik_Installation_FormFirstWebsiteSetup();
+ if (!isset($this->session->generalSetupSuccessMessage)) {
+ $view->displayGeneralSetupSuccess = true;
+ $this->session->generalSetupSuccessMessage = true;
+ }
+
+ $this->initObjectsToCallAPI();
+ if ($form->validate()) {
+ $name = urlencode($form->getSubmitValue('siteName'));
+ $url = urlencode($form->getSubmitValue('url'));
+ $ecommerce = (int)$form->getSubmitValue('ecommerce');
+
+ $request = new Piwik_API_Request("
method=SitesManager.addSite
&siteName=$name
&urls=$url
@@ -398,616 +382,573 @@ class Piwik_Installation_Controller extends Piwik_Controller_Admin
&format=original
");
- try {
- $result = $request->process();
- $this->session->site_idSite = $result;
- $this->session->site_name = $name;
- $this->session->site_url = $url;
-
- $this->redirectToNextStep( __FUNCTION__ );
- } catch(Exception $e) {
- $view->errorMessage = $e->getMessage();
- }
-
- }
- $view->addForm($form);
- echo $view->render();
- }
-
- /**
- * Installation Step 8: Display JavaScript tracking code
- */
- public function displayJavascriptCode()
- {
- $this->checkPreviousStepIsValid( __FUNCTION__ );
-
- $view = new Piwik_Installation_View(
- $this->pathView . 'displayJavascriptCode.tpl',
- $this->getInstallationSteps(),
- __FUNCTION__
- );
- $this->skipThisStep( __FUNCTION__ );
-
- if( !isset($this->session->firstWebsiteSetupSuccessMessage))
- {
- $view->displayfirstWebsiteSetupSuccess = true;
- $this->session->firstWebsiteSetupSuccessMessage = true;
- }
- $siteName = $this->session->site_name;
- $siteName = Piwik_Common::sanitizeInputValue( urldecode($siteName) );
- $idSite = $this->session->site_idSite;
-
- // Load the Tracking code and help text from the SitesManager
- $viewTrackingHelp = new Piwik_View('SitesManager/templates/DisplayJavascriptCode.tpl');
- $viewTrackingHelp->displaySiteName = $siteName;
- $viewTrackingHelp->jsTag = Piwik::getJavascriptCode($idSite, Piwik_Url::getCurrentUrlWithoutFileName());
- $viewTrackingHelp->idSite = $idSite;
- $viewTrackingHelp->piwikUrl = Piwik_Url::getCurrentUrlWithoutFileName();
-
- // Assign the html output to a smarty variable
- $view->trackingHelp = $viewTrackingHelp->render();
- $view->displaySiteName = $siteName;
-
- $view->showNextStep = true;
-
- $this->session->currentStepDone = __FUNCTION__;
- echo $view->render();
- }
-
- /**
- * Installation Step 9: Finished!
- */
- public function finished()
- {
- $this->checkPreviousStepIsValid( __FUNCTION__ );
-
- $view = new Piwik_Installation_View(
- $this->pathView . 'finished.tpl',
- $this->getInstallationSteps(),
- __FUNCTION__
- );
- $this->skipThisStep( __FUNCTION__ );
-
- if(!file_exists(Piwik_Config::getLocalConfigPath()))
- {
+ try {
+ $result = $request->process();
+ $this->session->site_idSite = $result;
+ $this->session->site_name = $name;
+ $this->session->site_url = $url;
+
+ $this->redirectToNextStep(__FUNCTION__);
+ } catch (Exception $e) {
+ $view->errorMessage = $e->getMessage();
+ }
+
+ }
+ $view->addForm($form);
+ echo $view->render();
+ }
+
+ /**
+ * Installation Step 8: Display JavaScript tracking code
+ */
+ public function displayJavascriptCode()
+ {
+ $this->checkPreviousStepIsValid(__FUNCTION__);
+
+ $view = new Piwik_Installation_View(
+ $this->pathView . 'displayJavascriptCode.tpl',
+ $this->getInstallationSteps(),
+ __FUNCTION__
+ );
+ $this->skipThisStep(__FUNCTION__);
+
+ if (!isset($this->session->firstWebsiteSetupSuccessMessage)) {
+ $view->displayfirstWebsiteSetupSuccess = true;
+ $this->session->firstWebsiteSetupSuccessMessage = true;
+ }
+ $siteName = $this->session->site_name;
+ $siteName = Piwik_Common::sanitizeInputValue(urldecode($siteName));
+ $idSite = $this->session->site_idSite;
+
+ // Load the Tracking code and help text from the SitesManager
+ $viewTrackingHelp = new Piwik_View('SitesManager/templates/DisplayJavascriptCode.tpl');
+ $viewTrackingHelp->displaySiteName = $siteName;
+ $viewTrackingHelp->jsTag = Piwik::getJavascriptCode($idSite, Piwik_Url::getCurrentUrlWithoutFileName());
+ $viewTrackingHelp->idSite = $idSite;
+ $viewTrackingHelp->piwikUrl = Piwik_Url::getCurrentUrlWithoutFileName();
+
+ // Assign the html output to a smarty variable
+ $view->trackingHelp = $viewTrackingHelp->render();
+ $view->displaySiteName = $siteName;
+
+ $view->showNextStep = true;
+
+ $this->session->currentStepDone = __FUNCTION__;
+ echo $view->render();
+ }
+
+ /**
+ * Installation Step 9: Finished!
+ */
+ public function finished()
+ {
+ $this->checkPreviousStepIsValid(__FUNCTION__);
+
+ $view = new Piwik_Installation_View(
+ $this->pathView . 'finished.tpl',
+ $this->getInstallationSteps(),
+ __FUNCTION__
+ );
+ $this->skipThisStep(__FUNCTION__);
+
+ if (!file_exists(Piwik_Config::getLocalConfigPath())) {
// $this->addTrustedHosts();
- $this->writeConfigFileFromSession();
- }
-
- $view->showNextStep = false;
-
- $this->session->currentStepDone = __FUNCTION__;
- echo $view->render();
-
- $this->session->unsetAll();
- }
-
- /**
- * This controller action renders an admin tab that runs the installation
- * system check, so people can see if there are any issues w/ their running
- * Piwik installation.
- *
- * This admin tab is only viewable by the super user.
- */
- public function systemCheckPage()
- {
- Piwik::checkUserIsSuperUser();
-
- $view = Piwik_View::factory('systemCheckPage');
- $this->setBasicVariablesView($view);
- $view->menu = Piwik_GetAdminMenu();
-
- $view->duringInstall = false;
-
- $this->setupSystemCheckView($view);
-
- $infos = $view->infos;
- $infos['extra'] = self::performAdminPageOnlySystemCheck();
- $view->infos = $infos;
-
- echo $view->render();
- }
-
- /**
- * Instantiate access and log objects
- */
- protected function initObjectsToCallAPI()
- {
- // connect to the database using the DB infos currently in the session
- $this->createDbFromSessionInformation();
-
- Piwik::createAccessObject();
- Piwik::setUserIsSuperUser();
- Piwik::createLogObject();
- }
-
- /**
- * Create database connection from session-store
- */
- protected function createDbFromSessionInformation()
- {
- $dbInfos = $this->session->db_infos;
- Piwik_Config::getInstance()->database = $dbInfos;
- Piwik::createDatabaseObject($dbInfos);
- }
-
- /**
- * Write configuration file from session-store
- */
- protected function writeConfigFileFromSession()
- {
- if(!isset($this->session->superuser_infos)
- || !isset($this->session->db_infos))
- {
- return;
- }
-
- $config = Piwik_Config::getInstance();
- try {
- // expect exception since config.ini.php doesn't exist yet
- $config->init();
- } catch(Exception $e) {
- $config->superuser = $this->session->superuser_infos;
- $config->database = $this->session->db_infos;
-
- if(!empty($this->session->general_infos))
- {
- $config->General = $this->session->general_infos;
- }
-
- $config->forceSave();
- }
-
- unset($this->session->superuser_infos);
- unset($this->session->db_infos);
- unset($this->session->general_infos);
- }
-
- /**
- * Save language selection in session-store
- */
- public function saveLanguage()
- {
- $language = Piwik_Common::getRequestVar('language');
- Piwik_LanguagesManager::setLanguageForSession($language);
- Piwik_Url::redirectToReferer();
- }
-
- /**
- * The previous step is valid if it is either
- * - any step before (OK to go back)
- * - the current step (case when validating a form)
- * If step is invalid, then exit.
- *
- * @param string $currentStep Current step
- */
- protected function checkPreviousStepIsValid( $currentStep )
- {
- $error = false;
-
- if(empty($this->session->currentStepDone))
- {
- $error = true;
- }
- else if($currentStep == 'finished' && $this->session->currentStepDone == 'finished')
- {
- // ok to refresh this page or use language selector
- }
- else
- {
- if(file_exists(Piwik_Config::getLocalConfigPath()))
- {
- $error = true;
- }
-
- $steps = array_keys($this->steps);
-
- // the currentStep
- $currentStepId = array_search($currentStep, $steps);
-
- // the step before
- $previousStepId = array_search($this->session->currentStepDone, $steps);
-
- // not OK if currentStepId > previous+1
- if( $currentStepId > $previousStepId + 1 )
- {
- $error = true;
- }
- }
- if($error)
- {
- Piwik_Login_Controller::clearSession();
- $message = Piwik_Translate('Installation_ErrorInvalidState',
- array( '<br /><b>',
- '</b>',
- '<a href=\''.Piwik_Common::sanitizeInputValue(Piwik_Url::getCurrentUrlWithoutFileName()).'\'>',
- '</a>')
- );
- Piwik::exitWithErrorMessage( $message );
- }
- }
-
- /**
- * Redirect to next step
- *
- * @param string Current step
- * @return none
- */
- protected function redirectToNextStep($currentStep)
- {
- $steps = array_keys($this->steps);
- $this->session->currentStepDone = $currentStep;
- $nextStep = $steps[1 + array_search($currentStep, $steps)];
- Piwik::redirectToModule('Installation' , $nextStep);
- }
-
- /**
- * Skip this step (typically to mark the current function as completed)
- *
- * @param string function name
- */
- protected function skipThisStep( $step )
- {
- $skipThisStep = $this->session->skipThisStep;
- if(isset($skipThisStep[$step]) && $skipThisStep[$step])
- {
- $this->redirectToNextStep($step);
- }
- }
-
- /**
- * Extract host from URL
- *
- * @param string $url URL
- *
- * @return string|false
- */
- protected function extractHost($url)
- {
- $urlParts = parse_url($url);
- if (isset($urlParts['host']) && strlen($host = $urlParts['host']))
- {
- return $host;
- }
-
- return false;
- }
-
- /**
- * Add trusted hosts
- */
- protected function addTrustedHosts()
- {
- $trustedHosts = array();
-
- // extract host from the request header
- if (($host = $this->extractHost('http://'.Piwik_Url::getHost())) !== false)
- {
- $trustedHosts[] = $host;
- }
-
- // extract host from first web site
- if (($host = $this->extractHost(urldecode($this->session->site_url))) !== false)
- {
- $trustedHosts[] = $host;
- }
-
- $trustedHosts = array_unique($trustedHosts);
- if (count($trustedHosts))
- {
- $this->session->general_infos['trusted_hosts'] = $trustedHosts;
- }
- }
-
- /**
- * Get system information
- */
- public static function getSystemInformation()
- {
- global $piwik_minimumPHPVersion;
- $minimumMemoryLimit = Piwik_Config::getInstance()->General['minimum_memory_limit'];
-
- $infos = array();
-
- $infos['general_infos'] = array();
- $infos['directories'] = Piwik::checkDirectoriesWritable();
- $infos['can_auto_update'] = Piwik::canAutoUpdate();
-
- if(Piwik_Common::isIIS())
- {
- Piwik::createWebConfigFiles();
- }
- else
- {
- Piwik::createHtAccessFiles();
- }
- Piwik::createWebRootFiles();
-
- $infos['phpVersion_minimum'] = $piwik_minimumPHPVersion;
- $infos['phpVersion'] = PHP_VERSION;
- $infos['phpVersion_ok'] = version_compare( $piwik_minimumPHPVersion, $infos['phpVersion']) === -1;
-
- // critical errors
- $extensions = @get_loaded_extensions();
- $needed_extensions = array(
- 'zlib',
- 'SPL',
- 'iconv',
- 'Reflection',
- );
- $infos['needed_extensions'] = $needed_extensions;
- $infos['missing_extensions'] = array();
- foreach($needed_extensions as $needed_extension)
- {
- if(!in_array($needed_extension, $extensions))
- {
- $infos['missing_extensions'][] = $needed_extension;
- }
- }
-
- $infos['pdo_ok'] = false;
- if(in_array('PDO', $extensions))
- {
- $infos['pdo_ok'] = true;
- }
-
- $infos['adapters'] = Piwik_Db_Adapter::getAdapters();
-
- $needed_functions = array(
- 'debug_backtrace',
- 'create_function',
- 'eval',
- 'gzcompress',
- 'gzuncompress',
- 'pack',
- );
- $infos['needed_functions'] = $needed_functions;
- $infos['missing_functions'] = array();
- foreach($needed_functions as $needed_function)
- {
- if(!self::functionExists($needed_function))
- {
- $infos['missing_functions'][] = $needed_function;
- }
- }
-
- // warnings
- $desired_extensions = array(
- 'json',
- 'libxml',
- 'dom',
- 'SimpleXML',
- );
- $infos['desired_extensions'] = $desired_extensions;
- $infos['missing_desired_extensions'] = array();
- foreach($desired_extensions as $desired_extension)
- {
- if(!in_array($desired_extension, $extensions))
- {
- $infos['missing_desired_extensions'][] = $desired_extension;
- }
- }
-
- $desired_functions = array(
- 'set_time_limit',
- 'mail',
- 'parse_ini_file',
- 'glob',
- );
- $infos['desired_functions'] = $desired_functions;
- $infos['missing_desired_functions'] = array();
- foreach($desired_functions as $desired_function)
- {
- if(!self::functionExists($desired_function))
- {
- $infos['missing_desired_functions'][] = $desired_function;
- }
- }
-
- $infos['openurl'] = Piwik_Http::getTransportMethod();
-
- $infos['gd_ok'] = Piwik::isGdExtensionEnabled();
-
- $infos['hasMbstring'] = false;
- $infos['multibyte_ok'] = true;
- if(function_exists('mb_internal_encoding'))
- {
- $infos['hasMbstring'] = true;
- if (((int) ini_get('mbstring.func_overload')) != 0)
- {
- $infos['multibyte_ok'] = false;
- }
- }
-
- $serverSoftware = isset($_SERVER['SERVER_SOFTWARE']) ? $_SERVER['SERVER_SOFTWARE'] : '';
- $infos['serverVersion'] = addslashes($serverSoftware);
- $infos['serverOs'] = @php_uname();
- $infos['serverTime'] = date('H:i:s');
-
- $infos['registerGlobals_ok'] = ini_get('register_globals') == 0;
- $infos['memoryMinimum'] = $minimumMemoryLimit;
-
- $infos['memory_ok'] = true;
- $infos['memoryCurrent'] = '';
-
- $raised = Piwik::raiseMemoryLimitIfNecessary();
- if(($memoryValue = Piwik::getMemoryLimitValue()) > 0)
- {
- $infos['memoryCurrent'] = $memoryValue.'M';
- $infos['memory_ok'] = $memoryValue >= $minimumMemoryLimit;
- }
-
- $infos['isWindows'] = Piwik_Common::isWindows();
-
- $integrityInfo = Piwik::getFileIntegrityInformation();
- $infos['integrity'] = $integrityInfo[0];
-
- $infos['integrityErrorMessages'] = array();
- if(isset($integrityInfo[1]))
- {
- if($infos['integrity'] == false)
- {
- $infos['integrityErrorMessages'][] = '<b>'.Piwik_Translate('General_FileIntegrityWarningExplanation').'</b>';
- }
- $infos['integrityErrorMessages'] = array_merge($infos['integrityErrorMessages'], array_slice($integrityInfo, 1));
- }
-
- $infos['timezone'] = Piwik::isTimezoneSupportEnabled();
-
- $infos['tracker_status'] = Piwik_Common::getRequestVar('trackerStatus', 0, 'int');
-
- $infos['protocol'] = Piwik_ProxyHeaders::getProtocolInformation();
- if(!Piwik::isHttps() && $infos['protocol'] !== null)
- {
- $infos['general_infos']['assume_secure_protocol'] = '1';
- }
- if(count($headers = Piwik_ProxyHeaders::getProxyClientHeaders()) > 0)
- {
- $infos['general_infos']['proxy_client_headers'] = $headers;
- }
- if(count($headers = Piwik_ProxyHeaders::getProxyHostHeaders()) > 0)
- {
- $infos['general_infos']['proxy_host_headers'] = $headers;
- }
-
- // check if filesystem is NFS, if it is file based sessions won't work properly
- $infos['is_nfs'] = Piwik::checkIfFileSystemIsNFS();
-
- // determine whether there are any errors/warnings from the checks done above
- $infos['has_errors'] = false;
- $infos['has_warnings'] = false;
- if (in_array(0, $infos['directories']) // if a directory is not writable
- || !$infos['phpVersion_ok']
- || !empty($infos['missing_extensions'])
- || empty($infos['adapters'])
- || !empty($infos['missing_functions']))
- {
- $infos['has_errors'] = true;
- }
-
- if (!$infos['can_auto_update']
- || !empty($infos['missing_desired_extensions'])
- || !$infos['gd_ok']
- || !$infos['multibyte_ok']
- || !$infos['registerGlobals_ok']
- || !$infos['memory_ok']
- || !empty($infos['integrityErrorMessages'])
- || !$infos['timezone'] // if timezone support isn't available
- || $infos['tracker_status'] != 0
- || $infos['is_nfs'])
- {
- $infos['has_warnings'] = true;
- }
-
- return $infos;
- }
-
- /**
- * Test if function exists. Also handles case where function is disabled via Suhosin.
- *
- * @param string $functionName Function name
- * @return bool True if function exists (not disabled); False otherwise.
- */
- public static function functionExists($functionName)
- {
- // eval() is a language construct
- if($functionName == 'eval')
- {
- // does not check suhosin.executor.eval.whitelist (or blacklist)
- if(extension_loaded('suhosin'))
- {
- return @ini_get("suhosin.executor.disable_eval") != "1";
- }
- return true;
- }
-
- $exists = function_exists($functionName);
- if(extension_loaded('suhosin'))
- {
- $blacklist = @ini_get("suhosin.executor.func.blacklist");
- if(!empty($blacklist))
- {
- $blacklistFunctions = array_map('strtolower', array_map('trim', explode(',', $blacklist)));
- return $exists && !in_array($functionName, $blacklistFunctions);
- }
-
- }
- return $exists;
- }
-
- /**
- * Utility function, sets up a view that will display system check info.
- *
- * @param Piwik_View $view
- */
- private function setupSystemCheckView( $view )
- {
- $view->infos = self::getSystemInformation();
-
- $view->helpMessages = array(
- 'zlib' => 'Installation_SystemCheckZlibHelp',
- 'SPL' => 'Installation_SystemCheckSplHelp',
- 'iconv' => 'Installation_SystemCheckIconvHelp',
- 'Reflection' => 'Required extension that is built in PHP, see http://www.php.net/manual/en/book.reflection.php',
- 'json' => 'Installation_SystemCheckWarnJsonHelp',
- 'libxml' => 'Installation_SystemCheckWarnLibXmlHelp',
- 'dom' => 'Installation_SystemCheckWarnDomHelp',
- 'SimpleXML' => 'Installation_SystemCheckWarnSimpleXMLHelp',
- 'set_time_limit' => 'Installation_SystemCheckTimeLimitHelp',
- 'mail' => 'Installation_SystemCheckMailHelp',
- 'parse_ini_file' => 'Installation_SystemCheckParseIniFileHelp',
- 'glob' => 'Installation_SystemCheckGlobHelp',
- 'debug_backtrace' => 'Installation_SystemCheckDebugBacktraceHelp',
- 'create_function' => 'Installation_SystemCheckCreateFunctionHelp',
- 'eval' => 'Installation_SystemCheckEvalHelp',
- 'gzcompress' => 'Installation_SystemCheckGzcompressHelp',
- 'gzuncompress' => 'Installation_SystemCheckGzuncompressHelp',
- 'pack' => 'Installation_SystemCheckPackHelp',
- );
-
- $view->problemWithSomeDirectories = (false !== array_search(false, $view->infos['directories']));
- }
-
- /**
- * Performs extra system checks for the 'System Check' admin page. These
- * checks are not performed during Installation.
- *
- * The following checks are performed:
- * - Check for whether LOAD DATA INFILE can be used. The result of the check
- * is stored in $result['load_data_infile_available']. The error message is
- * stored in $result['load_data_infile_error'].
- *
- * @return array
- */
- public static function performAdminPageOnlySystemCheck()
- {
- $result = array();
-
- // check if LOAD DATA INFILE works
- $optionTable = Piwik_Common::prefixTable('option');
- $testOptionNames = array('test_system_check1', 'test_system_check2');
-
- $result['load_data_infile_available'] = false;
- try
- {
- $result['load_data_infile_available'] = Piwik::tableInsertBatch(
- $optionTable,
- array('option_name', 'option_value'),
- array(
- array($testOptionNames[0], '1'),
- array($testOptionNames[1], '2'),
- ),
- $throwException = true
- );
- }
- catch (Exception $ex)
- {
- $result['load_data_infile_error'] = str_replace("\n","<br/>", $ex->getMessage());
- }
-
- // delete the temporary rows that were created
- Piwik_Exec("DELETE FROM `$optionTable` WHERE option_name IN ('".implode("','", $testOptionNames)."')");
-
- return $result;
- }
+ $this->writeConfigFileFromSession();
+ }
+
+ $view->showNextStep = false;
+
+ $this->session->currentStepDone = __FUNCTION__;
+ echo $view->render();
+
+ $this->session->unsetAll();
+ }
+
+ /**
+ * This controller action renders an admin tab that runs the installation
+ * system check, so people can see if there are any issues w/ their running
+ * Piwik installation.
+ *
+ * This admin tab is only viewable by the super user.
+ */
+ public function systemCheckPage()
+ {
+ Piwik::checkUserIsSuperUser();
+
+ $view = Piwik_View::factory('systemCheckPage');
+ $this->setBasicVariablesView($view);
+ $view->menu = Piwik_GetAdminMenu();
+
+ $view->duringInstall = false;
+
+ $this->setupSystemCheckView($view);
+
+ $infos = $view->infos;
+ $infos['extra'] = self::performAdminPageOnlySystemCheck();
+ $view->infos = $infos;
+
+ echo $view->render();
+ }
+
+ /**
+ * Instantiate access and log objects
+ */
+ protected function initObjectsToCallAPI()
+ {
+ // connect to the database using the DB infos currently in the session
+ $this->createDbFromSessionInformation();
+
+ Piwik::createAccessObject();
+ Piwik::setUserIsSuperUser();
+ Piwik::createLogObject();
+ }
+
+ /**
+ * Create database connection from session-store
+ */
+ protected function createDbFromSessionInformation()
+ {
+ $dbInfos = $this->session->db_infos;
+ Piwik_Config::getInstance()->database = $dbInfos;
+ Piwik::createDatabaseObject($dbInfos);
+ }
+
+ /**
+ * Write configuration file from session-store
+ */
+ protected function writeConfigFileFromSession()
+ {
+ if (!isset($this->session->superuser_infos)
+ || !isset($this->session->db_infos)
+ ) {
+ return;
+ }
+
+ $config = Piwik_Config::getInstance();
+ try {
+ // expect exception since config.ini.php doesn't exist yet
+ $config->init();
+ } catch (Exception $e) {
+ $config->superuser = $this->session->superuser_infos;
+ $config->database = $this->session->db_infos;
+
+ if (!empty($this->session->general_infos)) {
+ $config->General = $this->session->general_infos;
+ }
+
+ $config->forceSave();
+ }
+
+ unset($this->session->superuser_infos);
+ unset($this->session->db_infos);
+ unset($this->session->general_infos);
+ }
+
+ /**
+ * Save language selection in session-store
+ */
+ public function saveLanguage()
+ {
+ $language = Piwik_Common::getRequestVar('language');
+ Piwik_LanguagesManager::setLanguageForSession($language);
+ Piwik_Url::redirectToReferer();
+ }
+
+ /**
+ * The previous step is valid if it is either
+ * - any step before (OK to go back)
+ * - the current step (case when validating a form)
+ * If step is invalid, then exit.
+ *
+ * @param string $currentStep Current step
+ */
+ protected function checkPreviousStepIsValid($currentStep)
+ {
+ $error = false;
+
+ if (empty($this->session->currentStepDone)) {
+ $error = true;
+ } else if ($currentStep == 'finished' && $this->session->currentStepDone == 'finished') {
+ // ok to refresh this page or use language selector
+ } else {
+ if (file_exists(Piwik_Config::getLocalConfigPath())) {
+ $error = true;
+ }
+
+ $steps = array_keys($this->steps);
+
+ // the currentStep
+ $currentStepId = array_search($currentStep, $steps);
+
+ // the step before
+ $previousStepId = array_search($this->session->currentStepDone, $steps);
+
+ // not OK if currentStepId > previous+1
+ if ($currentStepId > $previousStepId + 1) {
+ $error = true;
+ }
+ }
+ if ($error) {
+ Piwik_Login_Controller::clearSession();
+ $message = Piwik_Translate('Installation_ErrorInvalidState',
+ array('<br /><b>',
+ '</b>',
+ '<a href=\'' . Piwik_Common::sanitizeInputValue(Piwik_Url::getCurrentUrlWithoutFileName()) . '\'>',
+ '</a>')
+ );
+ Piwik::exitWithErrorMessage($message);
+ }
+ }
+
+ /**
+ * Redirect to next step
+ *
+ * @param string Current step
+ * @return none
+ */
+ protected function redirectToNextStep($currentStep)
+ {
+ $steps = array_keys($this->steps);
+ $this->session->currentStepDone = $currentStep;
+ $nextStep = $steps[1 + array_search($currentStep, $steps)];
+ Piwik::redirectToModule('Installation', $nextStep);
+ }
+
+ /**
+ * Skip this step (typically to mark the current function as completed)
+ *
+ * @param string function name
+ */
+ protected function skipThisStep($step)
+ {
+ $skipThisStep = $this->session->skipThisStep;
+ if (isset($skipThisStep[$step]) && $skipThisStep[$step]) {
+ $this->redirectToNextStep($step);
+ }
+ }
+
+ /**
+ * Extract host from URL
+ *
+ * @param string $url URL
+ *
+ * @return string|false
+ */
+ protected function extractHost($url)
+ {
+ $urlParts = parse_url($url);
+ if (isset($urlParts['host']) && strlen($host = $urlParts['host'])) {
+ return $host;
+ }
+
+ return false;
+ }
+
+ /**
+ * Add trusted hosts
+ */
+ protected function addTrustedHosts()
+ {
+ $trustedHosts = array();
+
+ // extract host from the request header
+ if (($host = $this->extractHost('http://' . Piwik_Url::getHost())) !== false) {
+ $trustedHosts[] = $host;
+ }
+
+ // extract host from first web site
+ if (($host = $this->extractHost(urldecode($this->session->site_url))) !== false) {
+ $trustedHosts[] = $host;
+ }
+
+ $trustedHosts = array_unique($trustedHosts);
+ if (count($trustedHosts)) {
+ $this->session->general_infos['trusted_hosts'] = $trustedHosts;
+ }
+ }
+
+ /**
+ * Get system information
+ */
+ public static function getSystemInformation()
+ {
+ global $piwik_minimumPHPVersion;
+ $minimumMemoryLimit = Piwik_Config::getInstance()->General['minimum_memory_limit'];
+
+ $infos = array();
+
+ $infos['general_infos'] = array();
+ $infos['directories'] = Piwik::checkDirectoriesWritable();
+ $infos['can_auto_update'] = Piwik::canAutoUpdate();
+
+ if (Piwik_Common::isIIS()) {
+ Piwik::createWebConfigFiles();
+ } else {
+ Piwik::createHtAccessFiles();
+ }
+ Piwik::createWebRootFiles();
+
+ $infos['phpVersion_minimum'] = $piwik_minimumPHPVersion;
+ $infos['phpVersion'] = PHP_VERSION;
+ $infos['phpVersion_ok'] = version_compare($piwik_minimumPHPVersion, $infos['phpVersion']) === -1;
+
+ // critical errors
+ $extensions = @get_loaded_extensions();
+ $needed_extensions = array(
+ 'zlib',
+ 'SPL',
+ 'iconv',
+ 'Reflection',
+ );
+ $infos['needed_extensions'] = $needed_extensions;
+ $infos['missing_extensions'] = array();
+ foreach ($needed_extensions as $needed_extension) {
+ if (!in_array($needed_extension, $extensions)) {
+ $infos['missing_extensions'][] = $needed_extension;
+ }
+ }
+
+ $infos['pdo_ok'] = false;
+ if (in_array('PDO', $extensions)) {
+ $infos['pdo_ok'] = true;
+ }
+
+ $infos['adapters'] = Piwik_Db_Adapter::getAdapters();
+
+ $needed_functions = array(
+ 'debug_backtrace',
+ 'create_function',
+ 'eval',
+ 'gzcompress',
+ 'gzuncompress',
+ 'pack',
+ );
+ $infos['needed_functions'] = $needed_functions;
+ $infos['missing_functions'] = array();
+ foreach ($needed_functions as $needed_function) {
+ if (!self::functionExists($needed_function)) {
+ $infos['missing_functions'][] = $needed_function;
+ }
+ }
+
+ // warnings
+ $desired_extensions = array(
+ 'json',
+ 'libxml',
+ 'dom',
+ 'SimpleXML',
+ );
+ $infos['desired_extensions'] = $desired_extensions;
+ $infos['missing_desired_extensions'] = array();
+ foreach ($desired_extensions as $desired_extension) {
+ if (!in_array($desired_extension, $extensions)) {
+ $infos['missing_desired_extensions'][] = $desired_extension;
+ }
+ }
+
+ $desired_functions = array(
+ 'set_time_limit',
+ 'mail',
+ 'parse_ini_file',
+ 'glob',
+ );
+ $infos['desired_functions'] = $desired_functions;
+ $infos['missing_desired_functions'] = array();
+ foreach ($desired_functions as $desired_function) {
+ if (!self::functionExists($desired_function)) {
+ $infos['missing_desired_functions'][] = $desired_function;
+ }
+ }
+
+ $infos['openurl'] = Piwik_Http::getTransportMethod();
+
+ $infos['gd_ok'] = Piwik::isGdExtensionEnabled();
+
+ $infos['hasMbstring'] = false;
+ $infos['multibyte_ok'] = true;
+ if (function_exists('mb_internal_encoding')) {
+ $infos['hasMbstring'] = true;
+ if (((int)ini_get('mbstring.func_overload')) != 0) {
+ $infos['multibyte_ok'] = false;
+ }
+ }
+
+ $serverSoftware = isset($_SERVER['SERVER_SOFTWARE']) ? $_SERVER['SERVER_SOFTWARE'] : '';
+ $infos['serverVersion'] = addslashes($serverSoftware);
+ $infos['serverOs'] = @php_uname();
+ $infos['serverTime'] = date('H:i:s');
+
+ $infos['registerGlobals_ok'] = ini_get('register_globals') == 0;
+ $infos['memoryMinimum'] = $minimumMemoryLimit;
+
+ $infos['memory_ok'] = true;
+ $infos['memoryCurrent'] = '';
+
+ $raised = Piwik::raiseMemoryLimitIfNecessary();
+ if (($memoryValue = Piwik::getMemoryLimitValue()) > 0) {
+ $infos['memoryCurrent'] = $memoryValue . 'M';
+ $infos['memory_ok'] = $memoryValue >= $minimumMemoryLimit;
+ }
+
+ $infos['isWindows'] = Piwik_Common::isWindows();
+
+ $integrityInfo = Piwik::getFileIntegrityInformation();
+ $infos['integrity'] = $integrityInfo[0];
+
+ $infos['integrityErrorMessages'] = array();
+ if (isset($integrityInfo[1])) {
+ if ($infos['integrity'] == false) {
+ $infos['integrityErrorMessages'][] = '<b>' . Piwik_Translate('General_FileIntegrityWarningExplanation') . '</b>';
+ }
+ $infos['integrityErrorMessages'] = array_merge($infos['integrityErrorMessages'], array_slice($integrityInfo, 1));
+ }
+
+ $infos['timezone'] = Piwik::isTimezoneSupportEnabled();
+
+ $infos['tracker_status'] = Piwik_Common::getRequestVar('trackerStatus', 0, 'int');
+
+ $infos['protocol'] = Piwik_ProxyHeaders::getProtocolInformation();
+ if (!Piwik::isHttps() && $infos['protocol'] !== null) {
+ $infos['general_infos']['assume_secure_protocol'] = '1';
+ }
+ if (count($headers = Piwik_ProxyHeaders::getProxyClientHeaders()) > 0) {
+ $infos['general_infos']['proxy_client_headers'] = $headers;
+ }
+ if (count($headers = Piwik_ProxyHeaders::getProxyHostHeaders()) > 0) {
+ $infos['general_infos']['proxy_host_headers'] = $headers;
+ }
+
+ // check if filesystem is NFS, if it is file based sessions won't work properly
+ $infos['is_nfs'] = Piwik::checkIfFileSystemIsNFS();
+
+ // determine whether there are any errors/warnings from the checks done above
+ $infos['has_errors'] = false;
+ $infos['has_warnings'] = false;
+ if (in_array(0, $infos['directories']) // if a directory is not writable
+ || !$infos['phpVersion_ok']
+ || !empty($infos['missing_extensions'])
+ || empty($infos['adapters'])
+ || !empty($infos['missing_functions'])
+ ) {
+ $infos['has_errors'] = true;
+ }
+
+ if (!$infos['can_auto_update']
+ || !empty($infos['missing_desired_extensions'])
+ || !$infos['gd_ok']
+ || !$infos['multibyte_ok']
+ || !$infos['registerGlobals_ok']
+ || !$infos['memory_ok']
+ || !empty($infos['integrityErrorMessages'])
+ || !$infos['timezone'] // if timezone support isn't available
+ || $infos['tracker_status'] != 0
+ || $infos['is_nfs']
+ ) {
+ $infos['has_warnings'] = true;
+ }
+
+ return $infos;
+ }
+
+ /**
+ * Test if function exists. Also handles case where function is disabled via Suhosin.
+ *
+ * @param string $functionName Function name
+ * @return bool True if function exists (not disabled); False otherwise.
+ */
+ public static function functionExists($functionName)
+ {
+ // eval() is a language construct
+ if ($functionName == 'eval') {
+ // does not check suhosin.executor.eval.whitelist (or blacklist)
+ if (extension_loaded('suhosin')) {
+ return @ini_get("suhosin.executor.disable_eval") != "1";
+ }
+ return true;
+ }
+
+ $exists = function_exists($functionName);
+ if (extension_loaded('suhosin')) {
+ $blacklist = @ini_get("suhosin.executor.func.blacklist");
+ if (!empty($blacklist)) {
+ $blacklistFunctions = array_map('strtolower', array_map('trim', explode(',', $blacklist)));
+ return $exists && !in_array($functionName, $blacklistFunctions);
+ }
+
+ }
+ return $exists;
+ }
+
+ /**
+ * Utility function, sets up a view that will display system check info.
+ *
+ * @param Piwik_View $view
+ */
+ private function setupSystemCheckView($view)
+ {
+ $view->infos = self::getSystemInformation();
+
+ $view->helpMessages = array(
+ 'zlib' => 'Installation_SystemCheckZlibHelp',
+ 'SPL' => 'Installation_SystemCheckSplHelp',
+ 'iconv' => 'Installation_SystemCheckIconvHelp',
+ 'Reflection' => 'Required extension that is built in PHP, see http://www.php.net/manual/en/book.reflection.php',
+ 'json' => 'Installation_SystemCheckWarnJsonHelp',
+ 'libxml' => 'Installation_SystemCheckWarnLibXmlHelp',
+ 'dom' => 'Installation_SystemCheckWarnDomHelp',
+ 'SimpleXML' => 'Installation_SystemCheckWarnSimpleXMLHelp',
+ 'set_time_limit' => 'Installation_SystemCheckTimeLimitHelp',
+ 'mail' => 'Installation_SystemCheckMailHelp',
+ 'parse_ini_file' => 'Installation_SystemCheckParseIniFileHelp',
+ 'glob' => 'Installation_SystemCheckGlobHelp',
+ 'debug_backtrace' => 'Installation_SystemCheckDebugBacktraceHelp',
+ 'create_function' => 'Installation_SystemCheckCreateFunctionHelp',
+ 'eval' => 'Installation_SystemCheckEvalHelp',
+ 'gzcompress' => 'Installation_SystemCheckGzcompressHelp',
+ 'gzuncompress' => 'Installation_SystemCheckGzuncompressHelp',
+ 'pack' => 'Installation_SystemCheckPackHelp',
+ );
+
+ $view->problemWithSomeDirectories = (false !== array_search(false, $view->infos['directories']));
+ }
+
+ /**
+ * Performs extra system checks for the 'System Check' admin page. These
+ * checks are not performed during Installation.
+ *
+ * The following checks are performed:
+ * - Check for whether LOAD DATA INFILE can be used. The result of the check
+ * is stored in $result['load_data_infile_available']. The error message is
+ * stored in $result['load_data_infile_error'].
+ *
+ * @return array
+ */
+ public static function performAdminPageOnlySystemCheck()
+ {
+ $result = array();
+
+ // check if LOAD DATA INFILE works
+ $optionTable = Piwik_Common::prefixTable('option');
+ $testOptionNames = array('test_system_check1', 'test_system_check2');
+
+ $result['load_data_infile_available'] = false;
+ try {
+ $result['load_data_infile_available'] = Piwik::tableInsertBatch(
+ $optionTable,
+ array('option_name', 'option_value'),
+ array(
+ array($testOptionNames[0], '1'),
+ array($testOptionNames[1], '2'),
+ ),
+ $throwException = true
+ );
+ } catch (Exception $ex) {
+ $result['load_data_infile_error'] = str_replace("\n", "<br/>", $ex->getMessage());
+ }
+
+ // delete the temporary rows that were created
+ Piwik_Exec("DELETE FROM `$optionTable` WHERE option_name IN ('" . implode("','", $testOptionNames) . "')");
+
+ return $result;
+ }
}
diff --git a/plugins/Installation/FormDatabaseSetup.php b/plugins/Installation/FormDatabaseSetup.php
index 866025be67..809c19c20c 100644
--- a/plugins/Installation/FormDatabaseSetup.php
+++ b/plugins/Installation/FormDatabaseSetup.php
@@ -1,145 +1,138 @@
<?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_Installation
*/
/**
- *
+ *
* @package Piwik_Installation
*/
class Piwik_Installation_FormDatabaseSetup extends Piwik_QuickForm2
{
- function __construct( $id = 'databasesetupform', $method = 'post', $attributes = null, $trackSubmit = false)
- {
- parent::__construct($id, $method, $attributes = array('autocomplete' => 'off'), $trackSubmit);
- }
-
- function init()
- {
- HTML_QuickForm2_Factory::registerRule('checkValidFilename', 'Piwik_Installation_FormDatabaseSetup_Rule_checkValidFilename');
-
- $checkUserPrivilegesClass = 'Piwik_Installation_FormDatabaseSetup_Rule_checkUserPrivileges';
- HTML_QuickForm2_Factory::registerRule('checkUserPrivileges', $checkUserPrivilegesClass);
-
- $availableAdapters = Piwik_Db_Adapter::getAdapters();
- $adapters = array();
- foreach($availableAdapters as $adapter => $port)
- {
- $adapters[$adapter] = $adapter;
- }
-
- $this->addElement('text', 'host')
- ->setLabel(Piwik_Translate('Installation_DatabaseSetupServer'))
- ->addRule('required', Piwik_Translate('General_Required', Piwik_Translate('Installation_DatabaseSetupServer')));
-
- $user = $this->addElement('text', 'username')
- ->setLabel(Piwik_Translate('Installation_DatabaseSetupLogin'));
- $user->addRule('required', Piwik_Translate('General_Required', Piwik_Translate('Installation_DatabaseSetupLogin')));
- $requiredPrivileges = Piwik_Installation_FormDatabaseSetup_Rule_checkUserPrivileges::getRequiredPrivilegesPretty();
- $user->addRule('checkUserPrivileges',
- Piwik_Translate('Installation_InsufficientPrivileges', $requiredPrivileges . '<br/><br/>').
- Piwik_Translate('Installation_InsufficientPrivilegesHelp'));
-
- $this->addElement('password', 'password')
- ->setLabel(Piwik_Translate('Installation_DatabaseSetupPassword'));
-
- $item = $this->addElement('text', 'dbname')
- ->setLabel(Piwik_Translate('Installation_DatabaseSetupDatabaseName'));
- $item->addRule('required', Piwik_Translate('General_Required', Piwik_Translate('Installation_DatabaseSetupDatabaseName')));
- $item->addRule('checkValidFilename', Piwik_Translate('General_NotValid', Piwik_Translate('Installation_DatabaseSetupDatabaseName')));
-
- $this->addElement('text', 'tables_prefix')
- ->setLabel(Piwik_Translate('Installation_DatabaseSetupTablePrefix'))
- ->addRule('checkValidFilename', Piwik_Translate('General_NotValid', Piwik_Translate('Installation_DatabaseSetupTablePrefix')));
-
- $this->addElement('select', 'adapter')
- ->setLabel(Piwik_Translate('Installation_DatabaseSetupAdapter'))
- ->loadOptions($adapters)
- ->addRule('required', Piwik_Translate('General_Required', Piwik_Translate('Installation_DatabaseSetupAdapter')));
-
- $this->addElement('submit', 'submit', array('value' => Piwik_Translate('General_Next') .' »', 'class' => 'submit'));
-
- // default values
- $this->addDataSource(new HTML_QuickForm2_DataSource_Array(array(
- 'host' => '127.0.0.1',
- 'tables_prefix' => 'piwik_',
- )));
- }
-
- /**
- * Creates database object based on form data.
- *
- * @return array The database connection info. Can be passed into Piwik::createDatabaseObject.
- */
- public function createDatabaseObject()
- {
- $dbname = $this->getSubmitValue('dbname');
- if (empty($dbname)) // disallow database object creation w/ no selected database
- {
- throw new Exception("No database name");
- }
-
- $adapter = $this->getSubmitValue('adapter');
- $port = Piwik_Db_Adapter::getDefaultPortForAdapter($adapter);
-
- $dbInfos = array(
- 'host' => $this->getSubmitValue('host'),
- 'username' => $this->getSubmitValue('username'),
- 'password' => $this->getSubmitValue('password'),
- 'dbname' => $dbname,
- 'tables_prefix' => $this->getSubmitValue('tables_prefix'),
- 'adapter' => $adapter,
- 'port' => $port,
- );
-
- if(($portIndex = strpos($dbInfos['host'], '/')) !== false)
- {
- // unix_socket=/path/sock.n
- $dbInfos['port'] = substr($dbInfos['host'], $portIndex);
- $dbInfos['host'] = '';
- }
- else if(($portIndex = strpos($dbInfos['host'], ':')) !== false)
- {
- // host:port
- $dbInfos['port'] = substr($dbInfos['host'], $portIndex + 1 );
- $dbInfos['host'] = substr($dbInfos['host'], 0, $portIndex);
- }
-
- try {
- @Piwik::createDatabaseObject($dbInfos);
- } catch (Zend_Db_Adapter_Exception $e) {
- $db = Piwik_Db_Adapter::factory($adapter, $dbInfos, $connect = false);
-
- // database not found, we try to create it
- if($db->isErrNo($e, '1049'))
- {
- $dbInfosConnectOnly = $dbInfos;
- $dbInfosConnectOnly['dbname'] = null;
- @Piwik::createDatabaseObject($dbInfosConnectOnly);
- @Piwik::createDatabase($dbInfos['dbname']);
-
- // select the newly created database
- @Piwik::createDatabaseObject($dbInfos);
- }
- else
- {
- throw $e;
- }
- }
-
- return $dbInfos;
- }
+ function __construct($id = 'databasesetupform', $method = 'post', $attributes = null, $trackSubmit = false)
+ {
+ parent::__construct($id, $method, $attributes = array('autocomplete' => 'off'), $trackSubmit);
+ }
+
+ function init()
+ {
+ HTML_QuickForm2_Factory::registerRule('checkValidFilename', 'Piwik_Installation_FormDatabaseSetup_Rule_checkValidFilename');
+
+ $checkUserPrivilegesClass = 'Piwik_Installation_FormDatabaseSetup_Rule_checkUserPrivileges';
+ HTML_QuickForm2_Factory::registerRule('checkUserPrivileges', $checkUserPrivilegesClass);
+
+ $availableAdapters = Piwik_Db_Adapter::getAdapters();
+ $adapters = array();
+ foreach ($availableAdapters as $adapter => $port) {
+ $adapters[$adapter] = $adapter;
+ }
+
+ $this->addElement('text', 'host')
+ ->setLabel(Piwik_Translate('Installation_DatabaseSetupServer'))
+ ->addRule('required', Piwik_Translate('General_Required', Piwik_Translate('Installation_DatabaseSetupServer')));
+
+ $user = $this->addElement('text', 'username')
+ ->setLabel(Piwik_Translate('Installation_DatabaseSetupLogin'));
+ $user->addRule('required', Piwik_Translate('General_Required', Piwik_Translate('Installation_DatabaseSetupLogin')));
+ $requiredPrivileges = Piwik_Installation_FormDatabaseSetup_Rule_checkUserPrivileges::getRequiredPrivilegesPretty();
+ $user->addRule('checkUserPrivileges',
+ Piwik_Translate('Installation_InsufficientPrivileges', $requiredPrivileges . '<br/><br/>') .
+ Piwik_Translate('Installation_InsufficientPrivilegesHelp'));
+
+ $this->addElement('password', 'password')
+ ->setLabel(Piwik_Translate('Installation_DatabaseSetupPassword'));
+
+ $item = $this->addElement('text', 'dbname')
+ ->setLabel(Piwik_Translate('Installation_DatabaseSetupDatabaseName'));
+ $item->addRule('required', Piwik_Translate('General_Required', Piwik_Translate('Installation_DatabaseSetupDatabaseName')));
+ $item->addRule('checkValidFilename', Piwik_Translate('General_NotValid', Piwik_Translate('Installation_DatabaseSetupDatabaseName')));
+
+ $this->addElement('text', 'tables_prefix')
+ ->setLabel(Piwik_Translate('Installation_DatabaseSetupTablePrefix'))
+ ->addRule('checkValidFilename', Piwik_Translate('General_NotValid', Piwik_Translate('Installation_DatabaseSetupTablePrefix')));
+
+ $this->addElement('select', 'adapter')
+ ->setLabel(Piwik_Translate('Installation_DatabaseSetupAdapter'))
+ ->loadOptions($adapters)
+ ->addRule('required', Piwik_Translate('General_Required', Piwik_Translate('Installation_DatabaseSetupAdapter')));
+
+ $this->addElement('submit', 'submit', array('value' => Piwik_Translate('General_Next') . ' »', 'class' => 'submit'));
+
+ // default values
+ $this->addDataSource(new HTML_QuickForm2_DataSource_Array(array(
+ 'host' => '127.0.0.1',
+ 'tables_prefix' => 'piwik_',
+ )));
+ }
+
+ /**
+ * Creates database object based on form data.
+ *
+ * @return array The database connection info. Can be passed into Piwik::createDatabaseObject.
+ */
+ public function createDatabaseObject()
+ {
+ $dbname = $this->getSubmitValue('dbname');
+ if (empty($dbname)) // disallow database object creation w/ no selected database
+ {
+ throw new Exception("No database name");
+ }
+
+ $adapter = $this->getSubmitValue('adapter');
+ $port = Piwik_Db_Adapter::getDefaultPortForAdapter($adapter);
+
+ $dbInfos = array(
+ 'host' => $this->getSubmitValue('host'),
+ 'username' => $this->getSubmitValue('username'),
+ 'password' => $this->getSubmitValue('password'),
+ 'dbname' => $dbname,
+ 'tables_prefix' => $this->getSubmitValue('tables_prefix'),
+ 'adapter' => $adapter,
+ 'port' => $port,
+ );
+
+ if (($portIndex = strpos($dbInfos['host'], '/')) !== false) {
+ // unix_socket=/path/sock.n
+ $dbInfos['port'] = substr($dbInfos['host'], $portIndex);
+ $dbInfos['host'] = '';
+ } else if (($portIndex = strpos($dbInfos['host'], ':')) !== false) {
+ // host:port
+ $dbInfos['port'] = substr($dbInfos['host'], $portIndex + 1);
+ $dbInfos['host'] = substr($dbInfos['host'], 0, $portIndex);
+ }
+
+ try {
+ @Piwik::createDatabaseObject($dbInfos);
+ } catch (Zend_Db_Adapter_Exception $e) {
+ $db = Piwik_Db_Adapter::factory($adapter, $dbInfos, $connect = false);
+
+ // database not found, we try to create it
+ if ($db->isErrNo($e, '1049')) {
+ $dbInfosConnectOnly = $dbInfos;
+ $dbInfosConnectOnly['dbname'] = null;
+ @Piwik::createDatabaseObject($dbInfosConnectOnly);
+ @Piwik::createDatabase($dbInfos['dbname']);
+
+ // select the newly created database
+ @Piwik::createDatabaseObject($dbInfos);
+ } else {
+ throw $e;
+ }
+ }
+
+ return $dbInfos;
+ }
}
/**
* Validation rule that checks that the supplied DB user has enough privileges.
- *
+ *
* The following privileges are required for Piwik to run:
* - CREATE
* - ALTER
@@ -149,169 +142,148 @@ class Piwik_Installation_FormDatabaseSetup extends Piwik_QuickForm2
* - DELETE
* - DROP
* - CREATE TEMPORARY TABLES
- *
+ *
* @package Piwik_Installation
*/
class Piwik_Installation_FormDatabaseSetup_Rule_checkUserPrivileges extends HTML_QuickForm2_Rule
{
- const TEST_TABLE_NAME = 'piwik_test_table';
- const TEST_TEMP_TABLE_NAME = 'piwik_test_table_temp';
-
- /**
- * Checks that the DB user entered in the form has the necessary privileges for Piwik
- * to run.
- */
- public function validateOwner()
- {
- // try and create the database object
- try
- {
- $this->createDatabaseObject();
- }
- catch (Exception $ex)
- {
- if ($this->isAccessDenied($ex))
- {
- return false;
- }
- else
- {
- return true; // if we can't create the database object, skip this validation
- }
- }
-
- $db = Zend_Registry::get('db');
-
- try
- {
- // try to drop tables before running privilege tests
- $this->dropExtraTables($db);
- }
- catch (Exception $ex)
- {
- if ($this->isAccessDenied($ex))
- {
- return false;
- }
- else
- {
- throw $ex;
- }
- }
-
- // check each required privilege by running a query that uses it
- foreach (self::getRequiredPrivileges() as $privilegeType => $queries)
- {
- if (!is_array($queries))
- {
- $queries = array($queries);
- }
-
- foreach ($queries as $sql)
- {
- try
- {
- if( in_array($privilegeType, array('SELECT'))) {
- $db->fetchAll($sql);
- } else {
- $db->exec($sql);
- }
- }
- catch (Exception $ex)
- {
- if ($this->isAccessDenied($ex))
- {
- return false;
- }
- else
- {
- throw new Exception("Test SQL failed to execute: $sql\nError: ".$ex->getMessage());
- }
- }
- }
- }
-
- // remove extra tables that were created
- $this->dropExtraTables($db);
-
- return true;
- }
-
- /**
- * Returns an array describing the database privileges required for Piwik to run. The
- * array maps privilege names with one or more SQL queries that can be used to test
- * if the current user has the privilege.
- *
- * NOTE: LOAD DATA INFILE & LOCK TABLES privileges are not **required** so they're
- * not checked.
- *
- * @return array
- */
- public static function getRequiredPrivileges()
- {
- return array(
- 'CREATE' => 'CREATE TABLE '.self::TEST_TABLE_NAME.' (
+ const TEST_TABLE_NAME = 'piwik_test_table';
+ const TEST_TEMP_TABLE_NAME = 'piwik_test_table_temp';
+
+ /**
+ * Checks that the DB user entered in the form has the necessary privileges for Piwik
+ * to run.
+ */
+ public function validateOwner()
+ {
+ // try and create the database object
+ try {
+ $this->createDatabaseObject();
+ } catch (Exception $ex) {
+ if ($this->isAccessDenied($ex)) {
+ return false;
+ } else {
+ return true; // if we can't create the database object, skip this validation
+ }
+ }
+
+ $db = Zend_Registry::get('db');
+
+ try {
+ // try to drop tables before running privilege tests
+ $this->dropExtraTables($db);
+ } catch (Exception $ex) {
+ if ($this->isAccessDenied($ex)) {
+ return false;
+ } else {
+ throw $ex;
+ }
+ }
+
+ // check each required privilege by running a query that uses it
+ foreach (self::getRequiredPrivileges() as $privilegeType => $queries) {
+ if (!is_array($queries)) {
+ $queries = array($queries);
+ }
+
+ foreach ($queries as $sql) {
+ try {
+ if (in_array($privilegeType, array('SELECT'))) {
+ $db->fetchAll($sql);
+ } else {
+ $db->exec($sql);
+ }
+ } catch (Exception $ex) {
+ if ($this->isAccessDenied($ex)) {
+ return false;
+ } else {
+ throw new Exception("Test SQL failed to execute: $sql\nError: " . $ex->getMessage());
+ }
+ }
+ }
+ }
+
+ // remove extra tables that were created
+ $this->dropExtraTables($db);
+
+ return true;
+ }
+
+ /**
+ * Returns an array describing the database privileges required for Piwik to run. The
+ * array maps privilege names with one or more SQL queries that can be used to test
+ * if the current user has the privilege.
+ *
+ * NOTE: LOAD DATA INFILE & LOCK TABLES privileges are not **required** so they're
+ * not checked.
+ *
+ * @return array
+ */
+ public static function getRequiredPrivileges()
+ {
+ return array(
+ 'CREATE' => 'CREATE TABLE ' . self::TEST_TABLE_NAME . ' (
id INT AUTO_INCREMENT,
value INT,
PRIMARY KEY (id),
KEY index_value (value)
)',
- 'ALTER' => 'ALTER TABLE '.self::TEST_TABLE_NAME.'
+ 'ALTER' => 'ALTER TABLE ' . self::TEST_TABLE_NAME . '
ADD COLUMN other_value INT DEFAULT 0',
- 'SELECT' => 'SELECT * FROM '.self::TEST_TABLE_NAME,
- 'INSERT' => 'INSERT INTO '.self::TEST_TABLE_NAME.' (value) VALUES (123)',
- 'UPDATE' => 'UPDATE '.self::TEST_TABLE_NAME.' SET value = 456 WHERE id = 1',
- 'DELETE' => 'DELETE FROM '.self::TEST_TABLE_NAME.' WHERE id = 1',
- 'DROP' => 'DROP TABLE '.self::TEST_TABLE_NAME,
- 'CREATE TEMPORARY TABLES' => 'CREATE TEMPORARY TABLE '.self::TEST_TEMP_TABLE_NAME.' (
+ 'SELECT' => 'SELECT * FROM ' . self::TEST_TABLE_NAME,
+ 'INSERT' => 'INSERT INTO ' . self::TEST_TABLE_NAME . ' (value) VALUES (123)',
+ 'UPDATE' => 'UPDATE ' . self::TEST_TABLE_NAME . ' SET value = 456 WHERE id = 1',
+ 'DELETE' => 'DELETE FROM ' . self::TEST_TABLE_NAME . ' WHERE id = 1',
+ 'DROP' => 'DROP TABLE ' . self::TEST_TABLE_NAME,
+ 'CREATE TEMPORARY TABLES' => 'CREATE TEMPORARY TABLE ' . self::TEST_TEMP_TABLE_NAME . ' (
id INT AUTO_INCREMENT,
PRIMARY KEY (id)
)',
- );
- }
-
- /**
- * Returns a string description of the database privileges required for Piwik to run.
- *
- * @return string
- */
- public static function getRequiredPrivilegesPretty()
- {
- return implode('<br/>', array_keys(self::getRequiredPrivileges()));
- }
-
- /**
- * Checks if an exception that was thrown after running a query represents an 'access denied'
- * error.
- *
- * @param Exception $ex The exception to check.
- * @return bool
- */
- private function isAccessDenied( $ex )
- {
- //NOte: this code is duplicated in Tracker.php error handler
- return $ex->getCode() == 1044 || $ex->getCode() == 42000;
- }
-
- /**
- * Creates a database object using the connection information entered in the form.
- *
- * @return array
- */
- private function createDatabaseObject()
- {
- return $this->owner->getContainer()->createDatabaseObject();
- }
-
- /**
- * Drops the tables created by the privilege checking queries, if they exist.
- *
- * @param $db The database object to use.
- */
- private function dropExtraTables( $db )
- {
- $db->query('DROP TABLE IF EXISTS '.self::TEST_TABLE_NAME.', '.self::TEST_TEMP_TABLE_NAME);
- }
+ );
+ }
+
+ /**
+ * Returns a string description of the database privileges required for Piwik to run.
+ *
+ * @return string
+ */
+ public static function getRequiredPrivilegesPretty()
+ {
+ return implode('<br/>', array_keys(self::getRequiredPrivileges()));
+ }
+
+ /**
+ * Checks if an exception that was thrown after running a query represents an 'access denied'
+ * error.
+ *
+ * @param Exception $ex The exception to check.
+ * @return bool
+ */
+ private function isAccessDenied($ex)
+ {
+ //NOte: this code is duplicated in Tracker.php error handler
+ return $ex->getCode() == 1044 || $ex->getCode() == 42000;
+ }
+
+ /**
+ * Creates a database object using the connection information entered in the form.
+ *
+ * @return array
+ */
+ private function createDatabaseObject()
+ {
+ return $this->owner->getContainer()->createDatabaseObject();
+ }
+
+ /**
+ * Drops the tables created by the privilege checking queries, if they exist.
+ *
+ * @param $db The database object to use.
+ */
+ private function dropExtraTables($db)
+ {
+ $db->query('DROP TABLE IF EXISTS ' . self::TEST_TABLE_NAME . ', ' . self::TEST_TEMP_TABLE_NAME);
+ }
}
/**
@@ -321,9 +293,9 @@ class Piwik_Installation_FormDatabaseSetup_Rule_checkUserPrivileges extends HTML
*/
class Piwik_Installation_FormDatabaseSetup_Rule_checkValidFilename extends HTML_QuickForm2_Rule
{
- function validateOwner()
- {
- return Piwik_Common::isValidFilename($this->owner->getValue());
- }
+ function validateOwner()
+ {
+ return Piwik_Common::isValidFilename($this->owner->getValue());
+ }
}
diff --git a/plugins/Installation/FormFirstWebsiteSetup.php b/plugins/Installation/FormFirstWebsiteSetup.php
index 8629d12021..d44151f1b5 100644
--- a/plugins/Installation/FormFirstWebsiteSetup.php
+++ b/plugins/Installation/FormFirstWebsiteSetup.php
@@ -1,65 +1,65 @@
<?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_Installation
*/
/**
- *
+ *
* @package Piwik_Installation
*/
class Piwik_Installation_FormFirstWebsiteSetup extends Piwik_QuickForm2
{
- function __construct( $id = 'websitesetupform', $method = 'post', $attributes = null, $trackSubmit = false)
- {
- parent::__construct($id, $method, $attributes, $trackSubmit);
- }
+ function __construct($id = 'websitesetupform', $method = 'post', $attributes = null, $trackSubmit = false)
+ {
+ parent::__construct($id, $method, $attributes, $trackSubmit);
+ }
- function init()
- {
- HTML_QuickForm2_Factory::registerRule('checkTimezone', 'Piwik_Installation_FormFirstWebsiteSetup_Rule_isValidTimezone');
+ function init()
+ {
+ HTML_QuickForm2_Factory::registerRule('checkTimezone', 'Piwik_Installation_FormFirstWebsiteSetup_Rule_isValidTimezone');
- $urlExample = 'http://example.org';
- $javascriptOnClickUrlExample = "javascript:if(this.value=='$urlExample'){this.value='http://';} this.style.color='black';";
+ $urlExample = 'http://example.org';
+ $javascriptOnClickUrlExample = "javascript:if(this.value=='$urlExample'){this.value='http://';} this.style.color='black';";
- $timezones = Piwik_SitesManager_API::getInstance()->getTimezonesList();
- $timezones = array_merge(array('No timezone' => Piwik_Translate('SitesManager_SelectACity')), $timezones);
+ $timezones = Piwik_SitesManager_API::getInstance()->getTimezonesList();
+ $timezones = array_merge(array('No timezone' => Piwik_Translate('SitesManager_SelectACity')), $timezones);
- $this->addElement('text', 'siteName')
- ->setLabel(Piwik_Translate('Installation_SetupWebSiteName'))
- ->addRule('required', Piwik_Translate('General_Required', Piwik_Translate('Installation_SetupWebSiteName')));
+ $this->addElement('text', 'siteName')
+ ->setLabel(Piwik_Translate('Installation_SetupWebSiteName'))
+ ->addRule('required', Piwik_Translate('General_Required', Piwik_Translate('Installation_SetupWebSiteName')));
- $url = $this->addElement('text', 'url')
- ->setLabel(Piwik_Translate('Installation_SetupWebSiteURL'));
- $url->setAttribute('style', 'color:rgb(153, 153, 153);');
- $url->setAttribute('onfocus', $javascriptOnClickUrlExample);
- $url->setAttribute('onclick', $javascriptOnClickUrlExample);
- $url->addRule('required', Piwik_Translate('General_Required', Piwik_Translate('Installation_SetupWebSiteURL')));
+ $url = $this->addElement('text', 'url')
+ ->setLabel(Piwik_Translate('Installation_SetupWebSiteURL'));
+ $url->setAttribute('style', 'color:rgb(153, 153, 153);');
+ $url->setAttribute('onfocus', $javascriptOnClickUrlExample);
+ $url->setAttribute('onclick', $javascriptOnClickUrlExample);
+ $url->addRule('required', Piwik_Translate('General_Required', Piwik_Translate('Installation_SetupWebSiteURL')));
- $tz = $this->addElement('select', 'timezone')
- ->setLabel(Piwik_Translate('Installation_Timezone'))
- ->loadOptions($timezones);
- $tz->addRule('required', Piwik_Translate('General_Required', Piwik_Translate('Installation_Timezone')));
- $tz->addRule('checkTimezone', Piwik_Translate('General_NotValid', Piwik_Translate('Installation_Timezone')));
- $tz = $this->addElement('select', 'ecommerce')
- ->setLabel(Piwik_Translate('Goals_Ecommerce'))
- ->loadOptions(array(
- 0 => Piwik_Translate('SitesManager_NotAnEcommerceSite'),
- 1 => Piwik_Translate('SitesManager_EnableEcommerce'),
- ));
+ $tz = $this->addElement('select', 'timezone')
+ ->setLabel(Piwik_Translate('Installation_Timezone'))
+ ->loadOptions($timezones);
+ $tz->addRule('required', Piwik_Translate('General_Required', Piwik_Translate('Installation_Timezone')));
+ $tz->addRule('checkTimezone', Piwik_Translate('General_NotValid', Piwik_Translate('Installation_Timezone')));
+ $tz = $this->addElement('select', 'ecommerce')
+ ->setLabel(Piwik_Translate('Goals_Ecommerce'))
+ ->loadOptions(array(
+ 0 => Piwik_Translate('SitesManager_NotAnEcommerceSite'),
+ 1 => Piwik_Translate('SitesManager_EnableEcommerce'),
+ ));
- $this->addElement('submit', 'submit', array('value' => Piwik_Translate('General_Next').' »', 'class' => 'submit'));
+ $this->addElement('submit', 'submit', array('value' => Piwik_Translate('General_Next') . ' »', 'class' => 'submit'));
- // default values
- $this->addDataSource(new HTML_QuickForm2_DataSource_Array(array(
- 'url' => $urlExample,
- )));
- }
+ // default values
+ $this->addDataSource(new HTML_QuickForm2_DataSource_Array(array(
+ 'url' => $urlExample,
+ )));
+ }
}
/**
@@ -69,17 +69,16 @@ class Piwik_Installation_FormFirstWebsiteSetup extends Piwik_QuickForm2
*/
class Piwik_Installation_FormFirstWebsiteSetup_Rule_isValidTimezone extends HTML_QuickForm2_Rule
{
- function validateOwner()
- {
- try {
- $timezone = $this->owner->getValue();
- if(!empty($timezone))
- {
- Piwik_SitesManager_API::getInstance()->setDefaultTimezone($timezone);
- }
- } catch(Exception $e) {
- return false;
- }
- return true;
- }
+ function validateOwner()
+ {
+ try {
+ $timezone = $this->owner->getValue();
+ if (!empty($timezone)) {
+ Piwik_SitesManager_API::getInstance()->setDefaultTimezone($timezone);
+ }
+ } catch (Exception $e) {
+ return false;
+ }
+ return true;
+ }
}
diff --git a/plugins/Installation/FormGeneralSetup.php b/plugins/Installation/FormGeneralSetup.php
index 8d1f23a5c1..345b027298 100644
--- a/plugins/Installation/FormGeneralSetup.php
+++ b/plugins/Installation/FormGeneralSetup.php
@@ -1,65 +1,65 @@
<?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_Installation
*/
/**
- *
+ *
* @package Piwik_Installation
*/
class Piwik_Installation_FormGeneralSetup extends Piwik_QuickForm2
{
- function __construct( $id = 'generalsetupform', $method = 'post', $attributes = null, $trackSubmit = false)
- {
- parent::__construct($id, $method, $attributes = array('autocomplete' => 'off'), $trackSubmit);
- }
+ function __construct($id = 'generalsetupform', $method = 'post', $attributes = null, $trackSubmit = false)
+ {
+ parent::__construct($id, $method, $attributes = array('autocomplete' => 'off'), $trackSubmit);
+ }
- function init()
- {
- HTML_QuickForm2_Factory::registerRule('checkLogin', 'Piwik_Installation_FormGeneralSetup_Rule_isValidLoginString');
- HTML_QuickForm2_Factory::registerRule('checkEmail', 'Piwik_Installation_FormGeneralSetup_Rule_isValidEmailString');
+ function init()
+ {
+ HTML_QuickForm2_Factory::registerRule('checkLogin', 'Piwik_Installation_FormGeneralSetup_Rule_isValidLoginString');
+ HTML_QuickForm2_Factory::registerRule('checkEmail', 'Piwik_Installation_FormGeneralSetup_Rule_isValidEmailString');
- $login = $this->addElement('text', 'login')
- ->setLabel(Piwik_Translate('Installation_SuperUserLogin'));
- $login->addRule('required', Piwik_Translate('General_Required', Piwik_Translate('Installation_SuperUserLogin')));
- $login->addRule('checkLogin');
+ $login = $this->addElement('text', 'login')
+ ->setLabel(Piwik_Translate('Installation_SuperUserLogin'));
+ $login->addRule('required', Piwik_Translate('General_Required', Piwik_Translate('Installation_SuperUserLogin')));
+ $login->addRule('checkLogin');
- $password = $this->addElement('password', 'password')
- ->setLabel(Piwik_Translate('Installation_Password'));
- $password->addRule('required', Piwik_Translate('General_Required', Piwik_Translate('Installation_Password')));
+ $password = $this->addElement('password', 'password')
+ ->setLabel(Piwik_Translate('Installation_Password'));
+ $password->addRule('required', Piwik_Translate('General_Required', Piwik_Translate('Installation_Password')));
- $passwordBis = $this->addElement('password', 'password_bis')
- ->setLabel(Piwik_Translate('Installation_PasswordRepeat'));
- $passwordBis->addRule('required', Piwik_Translate('General_Required', Piwik_Translate('Installation_PasswordRepeat')));
- $passwordBis->addRule('eq', Piwik_Translate( 'Installation_PasswordDoNotMatch'), $password);
+ $passwordBis = $this->addElement('password', 'password_bis')
+ ->setLabel(Piwik_Translate('Installation_PasswordRepeat'));
+ $passwordBis->addRule('required', Piwik_Translate('General_Required', Piwik_Translate('Installation_PasswordRepeat')));
+ $passwordBis->addRule('eq', Piwik_Translate('Installation_PasswordDoNotMatch'), $password);
- $email = $this->addElement('text', 'email')
- ->setLabel(Piwik_Translate('Installation_Email'));
- $email->addRule('required', Piwik_Translate('General_Required', Piwik_Translate('Installation_Email')));
- $email->addRule('checkEmail', Piwik_Translate( 'UsersManager_ExceptionInvalidEmail'));
+ $email = $this->addElement('text', 'email')
+ ->setLabel(Piwik_Translate('Installation_Email'));
+ $email->addRule('required', Piwik_Translate('General_Required', Piwik_Translate('Installation_Email')));
+ $email->addRule('checkEmail', Piwik_Translate('UsersManager_ExceptionInvalidEmail'));
- $this->addElement('checkbox', 'subscribe_newsletter_security', null, array(
- 'content' => '&nbsp;&nbsp;' . Piwik_Translate('Installation_SecurityNewsletter'),
- ));
+ $this->addElement('checkbox', 'subscribe_newsletter_security', null, array(
+ 'content' => '&nbsp;&nbsp;' . Piwik_Translate('Installation_SecurityNewsletter'),
+ ));
- $this->addElement('checkbox', 'subscribe_newsletter_community', null, array(
- 'content' => '&nbsp;&nbsp;' . Piwik_Translate('Installation_CommunityNewsletter'),
- ));
+ $this->addElement('checkbox', 'subscribe_newsletter_community', null, array(
+ 'content' => '&nbsp;&nbsp;' . Piwik_Translate('Installation_CommunityNewsletter'),
+ ));
- $this->addElement('submit', 'submit', array('value' => Piwik_Translate('General_Next').' »', 'class' => 'submit'));
+ $this->addElement('submit', 'submit', array('value' => Piwik_Translate('General_Next') . ' »', 'class' => 'submit'));
- // default values
- $this->addDataSource(new HTML_QuickForm2_DataSource_Array(array(
- 'subscribe_newsletter_community' => 1,
- 'subscribe_newsletter_security' => 1,
- )));
- }
+ // default values
+ $this->addDataSource(new HTML_QuickForm2_DataSource_Array(array(
+ 'subscribe_newsletter_community' => 1,
+ 'subscribe_newsletter_security' => 1,
+ )));
+ }
}
/**
@@ -69,20 +69,19 @@ class Piwik_Installation_FormGeneralSetup extends Piwik_QuickForm2
*/
class Piwik_Installation_FormGeneralSetup_Rule_isValidLoginString extends HTML_QuickForm2_Rule
{
- function validateOwner()
- {
- try {
- $login = $this->owner->getValue();
- if(!empty($login))
- {
- Piwik::checkValidLoginString($login);
- }
- } catch(Exception $e) {
- $this->setMessage($e->getMessage());
- return false;
- }
- return true;
- }
+ function validateOwner()
+ {
+ try {
+ $login = $this->owner->getValue();
+ if (!empty($login)) {
+ Piwik::checkValidLoginString($login);
+ }
+ } catch (Exception $e) {
+ $this->setMessage($e->getMessage());
+ return false;
+ }
+ return true;
+ }
}
/**
@@ -92,8 +91,8 @@ class Piwik_Installation_FormGeneralSetup_Rule_isValidLoginString extends HTML_Q
*/
class Piwik_Installation_FormGeneralSetup_Rule_isValidEmailString extends HTML_QuickForm2_Rule
{
- function validateOwner()
- {
- return Piwik::isValidEmailString($this->owner->getValue());
- }
+ function validateOwner()
+ {
+ return Piwik::isValidEmailString($this->owner->getValue());
+ }
}
diff --git a/plugins/Installation/Installation.php b/plugins/Installation/Installation.php
index 793ae38210..ede139e2aa 100644
--- a/plugins/Installation/Installation.php
+++ b/plugins/Installation/Installation.php
@@ -1,105 +1,99 @@
<?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_Installation
*/
/**
- *
+ *
* @package Piwik_Installation
*/
class Piwik_Installation extends Piwik_Plugin
-{
- protected $installationControllerName = 'Piwik_Installation_Controller';
+{
+ protected $installationControllerName = 'Piwik_Installation_Controller';
- public function getInformation()
- {
- $info = array(
- 'description' => Piwik_Translate('Installation_PluginDescription'),
- 'author' => 'Piwik',
- 'author_homepage' => 'http://piwik.org/',
- 'version' => Piwik_Version::VERSION,
- );
-
- return $info;
- }
+ public function getInformation()
+ {
+ $info = array(
+ 'description' => Piwik_Translate('Installation_PluginDescription'),
+ 'author' => 'Piwik',
+ 'author_homepage' => 'http://piwik.org/',
+ 'version' => Piwik_Version::VERSION,
+ );
- function getListHooksRegistered()
- {
- $hooks = array(
- 'FrontController.NoConfigurationFile' => 'dispatch',
- 'FrontController.badConfigurationFile' => 'dispatch',
- 'AdminMenu.add' => 'addMenu',
- 'AssetManager.getCssFiles' => 'getCss',
- );
- return $hooks;
- }
+ return $info;
+ }
- public function setControllerToLoad( $newControllerName )
- {
- $this->installationControllerName = $newControllerName;
- }
+ function getListHooksRegistered()
+ {
+ $hooks = array(
+ 'FrontController.NoConfigurationFile' => 'dispatch',
+ 'FrontController.badConfigurationFile' => 'dispatch',
+ 'AdminMenu.add' => 'addMenu',
+ 'AssetManager.getCssFiles' => 'getCss',
+ );
+ return $hooks;
+ }
- protected function getInstallationController()
- {
- return new $this->installationControllerName();
- }
+ public function setControllerToLoad($newControllerName)
+ {
+ $this->installationControllerName = $newControllerName;
+ }
- /**
- * @param Piwik_Event_Notification|null $notification notification object
- */
- function dispatch($notification = null)
- {
- if($notification)
- {
- $exception = $notification->getNotificationObject();
- $message = $exception->getMessage();
- }
- else
- {
- $message = '';
- }
+ protected function getInstallationController()
+ {
+ return new $this->installationControllerName();
+ }
- Piwik_Translate::getInstance()->loadCoreTranslation();
+ /**
+ * @param Piwik_Event_Notification|null $notification notification object
+ */
+ function dispatch($notification = null)
+ {
+ if ($notification) {
+ $exception = $notification->getNotificationObject();
+ $message = $exception->getMessage();
+ } else {
+ $message = '';
+ }
- Piwik_PostEvent('Installation.startInstallation', $this);
+ Piwik_Translate::getInstance()->loadCoreTranslation();
- $step = Piwik_Common::getRequestVar('action', 'welcome', 'string');
- $controller = $this->getInstallationController();
- if(in_array($step, array_keys($controller->getInstallationSteps())) || $step == 'saveLanguage')
- {
- $controller->$step($message);
- }
- else
- {
- Piwik::exitWithErrorMessage(Piwik_Translate('Installation_NoConfigFound'));
- }
+ Piwik_PostEvent('Installation.startInstallation', $this);
- exit;
- }
+ $step = Piwik_Common::getRequestVar('action', 'welcome', 'string');
+ $controller = $this->getInstallationController();
+ if (in_array($step, array_keys($controller->getInstallationSteps())) || $step == 'saveLanguage') {
+ $controller->$step($message);
+ } else {
+ Piwik::exitWithErrorMessage(Piwik_Translate('Installation_NoConfigFound'));
+ }
+
+ exit;
+ }
- /**
- * Adds the 'System Check' admin page if the user is the super user.
- */
- public function addMenu()
- {
- Piwik_AddAdminSubMenu('CoreAdminHome_MenuDiagnostic', 'Installation_SystemCheck',
- array('module' => 'Installation', 'action' => 'systemCheckPage'),
- $addIf = Piwik::isUserIsSuperUser(),
- $order = 15);
+ /**
+ * Adds the 'System Check' admin page if the user is the super user.
+ */
+ public function addMenu()
+ {
+ Piwik_AddAdminSubMenu('CoreAdminHome_MenuDiagnostic', 'Installation_SystemCheck',
+ array('module' => 'Installation', 'action' => 'systemCheckPage'),
+ $addIf = Piwik::isUserIsSuperUser(),
+ $order = 15);
}
-
+
/**
* Adds CSS files to list of CSS files for asset manager.
*/
- public function getCss( $notification )
+ public function getCss($notification)
{
- $cssFiles = &$notification->getNotificationObject();
+ $cssFiles = & $notification->getNotificationObject();
$cssFiles[] = "plugins/Installation/templates/systemCheckPage.css";
}
diff --git a/plugins/Installation/View.php b/plugins/Installation/View.php
index c55fcb4ac2..1bd4866ab0 100644
--- a/plugins/Installation/View.php
+++ b/plugins/Installation/View.php
@@ -1,58 +1,55 @@
<?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_Installation
*/
/**
- *
+ *
* @package Piwik_Installation
*/
class Piwik_Installation_View extends Piwik_View
{
- protected $mainTemplate = 'Installation/templates/structure.tpl';
-
- function __construct($subtemplatePath, $installationSteps, $currentStepName)
- {
- parent::__construct($this->mainTemplate);
+ protected $mainTemplate = 'Installation/templates/structure.tpl';
- $this->subTemplateToLoad = $subtemplatePath;
- $this->steps = array_keys($installationSteps);
- $this->allStepsTitle = array_values($installationSteps);
- $this->currentStepName = $currentStepName;
- $this->showNextStep = false;
- }
-
- function render()
- {
- // prepare the all steps templates
- $this->currentStepId = array_search($this->currentStepName, $this->steps);
- $this->totalNumberOfSteps = count($this->steps);
-
- $this->percentDone = round(($this->currentStepId) * 100 / ($this->totalNumberOfSteps-1));
- $this->percentToDo = 100 - $this->percentDone;
-
- $this->nextModuleName = '';
- if(isset($this->steps[$this->currentStepId + 1]))
- {
- $this->nextModuleName = $this->steps[$this->currentStepId + 1];
- }
- $this->previousModuleName = '';
- if(isset($this->steps[$this->currentStepId - 1]))
- {
- $this->previousModuleName = $this->steps[$this->currentStepId - 1];
- }
- $this->previousPreviousModuleName = '';
- if(isset($this->steps[$this->currentStepId - 2]))
- {
- $this->previousPreviousModuleName = $this->steps[$this->currentStepId - 2];
- }
+ function __construct($subtemplatePath, $installationSteps, $currentStepName)
+ {
+ parent::__construct($this->mainTemplate);
- return parent::render();
- }
+ $this->subTemplateToLoad = $subtemplatePath;
+ $this->steps = array_keys($installationSteps);
+ $this->allStepsTitle = array_values($installationSteps);
+ $this->currentStepName = $currentStepName;
+ $this->showNextStep = false;
+ }
+
+ function render()
+ {
+ // prepare the all steps templates
+ $this->currentStepId = array_search($this->currentStepName, $this->steps);
+ $this->totalNumberOfSteps = count($this->steps);
+
+ $this->percentDone = round(($this->currentStepId) * 100 / ($this->totalNumberOfSteps - 1));
+ $this->percentToDo = 100 - $this->percentDone;
+
+ $this->nextModuleName = '';
+ if (isset($this->steps[$this->currentStepId + 1])) {
+ $this->nextModuleName = $this->steps[$this->currentStepId + 1];
+ }
+ $this->previousModuleName = '';
+ if (isset($this->steps[$this->currentStepId - 1])) {
+ $this->previousModuleName = $this->steps[$this->currentStepId - 1];
+ }
+ $this->previousPreviousModuleName = '';
+ if (isset($this->steps[$this->currentStepId - 2])) {
+ $this->previousPreviousModuleName = $this->steps[$this->currentStepId - 2];
+ }
+
+ return parent::render();
+ }
}
diff --git a/plugins/Installation/templates/allSteps.tpl b/plugins/Installation/templates/allSteps.tpl
index 5cba61e4d5..fa4bcf0063 100644
--- a/plugins/Installation/templates/allSteps.tpl
+++ b/plugins/Installation/templates/allSteps.tpl
@@ -1,11 +1,11 @@
<ul>
-{foreach from=$allStepsTitle key=stepId item=stepName}
- {if $currentStepId > $stepId}
- <li class="pastStep">{$stepName|translate}</li>
- {elseif $currentStepId == $stepId}
- <li class="actualStep">{$stepName|translate}</li>
- {else}
- <li class="futureStep">{$stepName|translate}</li>
- {/if}
-{/foreach}
+ {foreach from=$allStepsTitle key=stepId item=stepName}
+ {if $currentStepId > $stepId}
+ <li class="pastStep">{$stepName|translate}</li>
+ {elseif $currentStepId == $stepId}
+ <li class="actualStep">{$stepName|translate}</li>
+ {else}
+ <li class="futureStep">{$stepName|translate}</li>
+ {/if}
+ {/foreach}
</ul>
diff --git a/plugins/Installation/templates/databaseCheck.tpl b/plugins/Installation/templates/databaseCheck.tpl
index 07916d11b4..3f0309f1ee 100644
--- a/plugins/Installation/templates/databaseCheck.tpl
+++ b/plugins/Installation/templates/databaseCheck.tpl
@@ -6,29 +6,29 @@
<h2>{'Installation_DatabaseCheck'|translate}</h2>
<table class="infosServer">
- <tr>
- <td class="label">{'Installation_DatabaseServerVersion'|translate}</td>
- <td>{if isset($databaseVersionOk)}{$ok}{else}{$error}{/if}</td>
- </tr>
- <tr>
- <td class="label">{'Installation_DatabaseClientVersion'|translate}</td>
- <td>{if isset($clientVersionWarning)}{$warning}{else}{$ok}{/if}</td>
- </tr>
-{if isset($clientVersionWarning)}
- <tr>
- <td colspan="2">
- <small>
- <span style="color:#FF7F00">{$clientVersionWarning}</span>
- </small>
- </td>
- </tr>
-{/if}
- <tr>
- <td class="label">{'Installation_DatabaseCreation'|translate}</td>
- <td>{if isset($databaseCreated)}{$ok}{else}{$error}{/if}</td>
- </tr>
+ <tr>
+ <td class="label">{'Installation_DatabaseServerVersion'|translate}</td>
+ <td>{if isset($databaseVersionOk)}{$ok}{else}{$error}{/if}</td>
+ </tr>
+ <tr>
+ <td class="label">{'Installation_DatabaseClientVersion'|translate}</td>
+ <td>{if isset($clientVersionWarning)}{$warning}{else}{$ok}{/if}</td>
+ </tr>
+ {if isset($clientVersionWarning)}
+ <tr>
+ <td colspan="2">
+ <small>
+ <span style="color:#FF7F00">{$clientVersionWarning}</span>
+ </small>
+ </td>
+ </tr>
+ {/if}
+ <tr>
+ <td class="label">{'Installation_DatabaseCreation'|translate}</td>
+ <td>{if isset($databaseCreated)}{$ok}{else}{$error}{/if}</td>
+ </tr>
</table>
<p>
-{$link} <a href="?module=Proxy&action=redirect&url=http://piwik.org/docs/requirements/" target="_blank">{'Installation_Requirements'|translate}</a>
+ {$link} <a href="?module=Proxy&action=redirect&url=http://piwik.org/docs/requirements/" target="_blank">{'Installation_Requirements'|translate}</a>
</p>
diff --git a/plugins/Installation/templates/databaseSetup.tpl b/plugins/Installation/templates/databaseSetup.tpl
index 22cd8e0469..d5546fb28d 100644
--- a/plugins/Installation/templates/databaseSetup.tpl
+++ b/plugins/Installation/templates/databaseSetup.tpl
@@ -1,14 +1,14 @@
<h2>{'Installation_DatabaseSetup'|translate}</h2>
{if isset($errorMessage)}
- <div class="error">
- <img src="themes/default/images/error_medium.png" />
- {'Installation_DatabaseErrorConnect'|translate}:
- <br />{$errorMessage}
-
- </div>
+ <div class="error">
+ <img src="themes/default/images/error_medium.png"/>
+ {'Installation_DatabaseErrorConnect'|translate}:
+ <br/>{$errorMessage}
+
+ </div>
{/if}
{if isset($form_data)}
- {include file="default/genericForm.tpl"}
+ {include file="default/genericForm.tpl"}
{/if}
diff --git a/plugins/Installation/templates/displayJavascriptCode.tpl b/plugins/Installation/templates/displayJavascriptCode.tpl
index 1b0c00e0c5..78abfca64c 100644
--- a/plugins/Installation/templates/displayJavascriptCode.tpl
+++ b/plugins/Installation/templates/displayJavascriptCode.tpl
@@ -1,10 +1,7 @@
-
-
{if isset($displayfirstWebsiteSetupSuccess)}
-
-<span id="toFade" class="success">
+ <span id="toFade" class="success">
{'Installation_SetupWebsiteSetupSuccess'|translate:$displaySiteName}
- <img src="themes/default/images/success_medium.png" />
+ <img src="themes/default/images/success_medium.png"/>
</span>
{/if}
@@ -14,15 +11,14 @@
{'Installation_JsTagArchivingHelp1'|translate:'<a target="_blank" href="http://piwik.org/docs/setup-auto-archiving/">':'</a>'} {'General_ReadThisToLearnMore'|translate:'<a target="_blank" href="http://piwik.org/docs/optimize/">':'</a>'}
{literal}
-<style type="text/css">
-code {
- font-size:80%;
-}
-</style>
-<script>
-$(document).ready( function(){
- $('code').click( function(){ $(this).select(); });
-});
-</script>
-
+ <style type="text/css">
+ code {
+ font-size: 80%;
+ }
+ </style>
+ <script>
+ $(document).ready(function () {
+ $('code').click(function () { $(this).select(); });
+ });
+ </script>
{/literal}
diff --git a/plugins/Installation/templates/finished.tpl b/plugins/Installation/templates/finished.tpl
index e1137c510e..ebf32507f6 100644
--- a/plugins/Installation/templates/finished.tpl
+++ b/plugins/Installation/templates/finished.tpl
@@ -4,5 +4,5 @@
<p class="nextStep">
- <a class="submit" href="index.php">{'Installation_ContinueToPiwik'|translate} &raquo;</a>
+ <a class="submit" href="index.php">{'Installation_ContinueToPiwik'|translate} &raquo;</a>
</p>
diff --git a/plugins/Installation/templates/firstWebsiteSetup.tpl b/plugins/Installation/templates/firstWebsiteSetup.tpl
index 41360cdd2d..1db08d0674 100644
--- a/plugins/Installation/templates/firstWebsiteSetup.tpl
+++ b/plugins/Installation/templates/firstWebsiteSetup.tpl
@@ -1,25 +1,23 @@
-
-
{if isset($displayGeneralSetupSuccess)}
-<span id="toFade" class="success">
+ <span id="toFade" class="success">
{'Installation_SuperUserSetupSuccess'|translate}
- <img src="themes/default/images/success_medium.png" />
+ <img src="themes/default/images/success_medium.png"/>
</span>
{/if}
<h2>{'Installation_SetupWebsite'|translate}</h2>
<p>{'Installation_SiteSetup'|translate}</p>
{if isset($errorMessage)}
- <div class="error">
- <img src="themes/default/images/error_medium.png" />
- {'Installation_SetupWebsiteError'|translate}:
- <br />- {$errorMessage}
-
- </div>
+ <div class="error">
+ <img src="themes/default/images/error_medium.png"/>
+ {'Installation_SetupWebsiteError'|translate}:
+ <br/>- {$errorMessage}
+
+ </div>
{/if}
{if isset($form_data)}
- {include file="default/genericForm.tpl"}
+ {include file="default/genericForm.tpl"}
{/if}
<br/>
<p><i>{'Installation_SiteSetupFootnote'|translate}</i></p> \ No newline at end of file
diff --git a/plugins/Installation/templates/generalSetup.tpl b/plugins/Installation/templates/generalSetup.tpl
index 6fc4f246f3..29406ec657 100644
--- a/plugins/Installation/templates/generalSetup.tpl
+++ b/plugins/Installation/templates/generalSetup.tpl
@@ -1,5 +1,5 @@
<h2>{'Installation_SuperUser'|translate}</h2>
{if isset($form_data)}
- {include file="default/genericForm.tpl"}
+ {include file="default/genericForm.tpl"}
{/if}
diff --git a/plugins/Installation/templates/install.css b/plugins/Installation/templates/install.css
index 3f605feee9..ba67fc2531 100644
--- a/plugins/Installation/templates/install.css
+++ b/plugins/Installation/templates/install.css
@@ -1,195 +1,215 @@
-
div.both {
- clear: both;
+ clear: both;
}
body {
- background-color: #FFFBF9;
- text-align: center;
- font-family:Arial,Georgia,"Times New Roman",Times,serif;
- font-size:17px;
+ background-color: #FFFBF9;
+ text-align: center;
+ font-family: Arial, Georgia, "Times New Roman", Times, serif;
+ font-size: 17px;
}
+
p {
- margin-bottom:5px;
- margin-top:10px;
+ margin-bottom: 5px;
+ margin-top: 10px;
}
-#title{
- font-size:50px;
- color:#284F92;
- vertical-align: text-bottom;
+
+#title {
+ font-size: 50px;
+ color: #284F92;
+ vertical-align: text-bottom;
}
-#subtitle{
- font-size:30px;
- color: #6B320B;
- font-size: 27px;
+#subtitle {
+ font-size: 30px;
+ color: #6B320B;
+ font-size: 27px;
}
#logo {
- padding: 20px 30px 40px;
+ padding: 20px 30px 40px;
}
h2 {
- font-size:20px;
- color:#666666;
- border-bottom:1px solid #DADADA;
- padding:0 0 7px;
+ font-size: 20px;
+ color: #666666;
+ border-bottom: 1px solid #DADADA;
+ padding: 0 0 7px;
}
h3 {
- margin-top:10px;
- font-size:17px;
- color:#3F5163;
+ margin-top: 10px;
+ font-size: 17px;
+ color: #3F5163;
}
.topBarElem {
- font-family:arial,sans-serif !important;
- font-size:13px;
- line-height:1.33;
+ font-family: arial, sans-serif !important;
+ font-size: 13px;
+ line-height: 1.33;
}
#topRightBar {
- float:right;top:-60px; right:10px;position:relative;
+ float: right;
+ top: -60px;
+ right: 10px;
+ position: relative;
}
+
.error {
- color:red;
- font-size:100%;
- font-weight:bold;
- border: 2px solid red;
- width: 550px;
- padding:20px;
- margin-bottom:10px;
+ color: red;
+ font-size: 100%;
+ font-weight: bold;
+ border: 2px solid red;
+ width: 550px;
+ padding: 20px;
+ margin-bottom: 10px;
}
.error img {
- border:0;
- float:right;
- margin:10px;
+ border: 0;
+ float: right;
+ margin: 10px;
}
+
.success {
- color:#26981C;
- font-size:130%;
- font-weight:bold;
- padding:10px;
+ color: #26981C;
+ font-size: 130%;
+ font-weight: bold;
+ padding: 10px;
}
+
.warn {
- color:#D7A006;
- font-weight:bold;
+ color: #D7A006;
+ font-weight: bold;
}
+
.warning {
- margin:10px;
- color:#ff5502;
- font-size:130%;
- font-weight:bold;
- padding:10px 20px 10px 30px;
- border: 1px solid #ff5502;
+ margin: 10px;
+ color: #ff5502;
+ font-size: 130%;
+ font-weight: bold;
+ padding: 10px 20px 10px 30px;
+ border: 1px solid #ff5502;
}
.warning ul {
- list-style:disc;
+ list-style: disc;
}
.success img, .warning img {
- border:0;
- vertical-align:bottom;
+ border: 0;
+ vertical-align: bottom;
}
-
/* Cadre general */
#main {
- margin: 30px 5px 5px;
- text-align: left;
+ margin: 30px 5px 5px;
+ text-align: left;
}
#content {
- font-size: 90%;
- line-height: 1.4em;
- width: 860px;
- border: 1px solid #7A5A3F;
- text-align: $rightouleft;
- margin: auto;
- background: #FFFFFF;
- padding: 0.2em 2em 2em 2em;
- border-radius: 8px;
- -moz-border-radius: 8px;
- -webkit-border-radius: 8px;
+ font-size: 90%;
+ line-height: 1.4em;
+ width: 860px;
+ border: 1px solid #7A5A3F;
+ text-align: $ rightouleft;
+ margin: auto;
+ background: #FFFFFF;
+ padding: 0.2em 2em 2em 2em;
+ border-radius: 8px;
+ -moz-border-radius: 8px;
+ -webkit-border-radius: 8px;
}
+
/* form errors */
#adminErrors {
- color:#FF6E46;
- font-size:120%;
+ color: #FF6E46;
+ font-size: 120%;
}
+
/* listing all the steps */
#generalInstall {
- float: left;
- margin-left:20px;
- width:19%;
+ float: left;
+ margin-left: 20px;
+ width: 19%;
}
#detailInstall {
- width:75%;
- float: right;
+ width: 75%;
+ float: right;
}
#generalInstall ul {
- list-style-type: decimal;
+ list-style-type: decimal;
}
+
li.futureStep {
- color: #d3d3d3;
+ color: #d3d3d3;
}
+
li.actualStep {
- font-weight: bold;
+ font-weight: bold;
}
+
li.pastStep {
- color: #008000;
+ color: #008000;
}
p.nextStep a {
- text-decoration: none;
+ text-decoration: none;
}
td {
- border: 1px solid rgb(198, 205, 216);
- border-top-color: #FFF;
- color:#203276;
- padding:0.5em 0.5em 0.5em 0.8em;
+ border: 1px solid rgb(198, 205, 216);
+ border-top-color: #FFF;
+ color: #203276;
+ padding: 0.5em 0.5em 0.5em 0.8em;
}
.submit {
- text-align:center;
- cursor: pointer;
+ text-align: center;
+ cursor: pointer;
}
+
.submit input {
- margin-top:15px;
- background:transparent url(./themes/default/images/background-submit.png) repeat scroll 0;
- font-size:1.4em;
- border-color:#CCCCCC rgb(153, 153, 153) rgb(153, 153, 153) rgb(204, 204, 204);
- border-style:double;
- border-width:3px;
- color:#333333;
- padding:0.15em;
-}
-
-input {
- font-size:18px;
- border-color:#CCCCCC rgb(153, 153, 153) rgb(153, 153, 153) rgb(204, 204, 204);
- border-width:1px;
- color:#3A2B16;
- padding:0.15em;
+ margin-top: 15px;
+ background: transparent url(./themes/default/images/background-submit.png) repeat scroll 0;
+ font-size: 1.4em;
+ border-color: #CCCCCC rgb(153, 153, 153) rgb(153, 153, 153) rgb(204, 204, 204);
+ border-style: double;
+ border-width: 3px;
+ color: #333333;
+ padding: 0.15em;
}
+
+input {
+ font-size: 18px;
+ border-color: #CCCCCC rgb(153, 153, 153) rgb(153, 153, 153) rgb(204, 204, 204);
+ border-width: 1px;
+ color: #3A2B16;
+ padding: 0.15em;
+}
+
#systemCheckLegend {
- border:1px solid #A5A5A5;
- padding:20px;
- color:#727272;
- margin-top:30px;
+ border: 1px solid #A5A5A5;
+ padding: 20px;
+ color: #727272;
+ margin-top: 30px;
}
+
#systemCheckLegend img {
- padding-right:10px;
+ padding-right: 10px;
vertical-align: middle;
}
-.infos img , .infosServer img {
- padding-right:10px;
- vertical-align:middle;
+
+.infos img, .infosServer img {
+ padding-right: 10px;
+ vertical-align: middle;
+}
+
+.err {
+ color: red;
+ font-weight: bold;
}
-.err { color:red;font-weight:bold;}
diff --git a/plugins/Installation/templates/integrityDetails.tpl b/plugins/Installation/templates/integrityDetails.tpl
index 074f93faca..66882fa3d1 100644
--- a/plugins/Installation/templates/integrityDetails.tpl
+++ b/plugins/Installation/templates/integrityDetails.tpl
@@ -1,42 +1,45 @@
{if !isset($warningMessages)}
-{assign var=warningMessages value=$infos.integrityErrorMessages}
+ {assign var=warningMessages value=$infos.integrityErrorMessages}
{/if}
<div id="integrity-results" title="{'Installation_SystemCheckFileIntegrity'|translate}" style="display:none; font-size: 62.5%;">
- <table>
- {foreach from=$warningMessages item=msg}
- <tr><td>{$msg}</td></tr>
- {/foreach}
- </table>
+ <table>
+ {foreach from=$warningMessages item=msg}
+ <tr>
+ <td>{$msg}</td>
+ </tr>
+ {/foreach}
+ </table>
</div>
<script type="text/javascript">
-{literal}<!--
-$(function() {
- $("#integrity-results").dialog({
- modal: true,
- autoOpen: false,
- width: 600,
- buttons: {
- Ok: function() {
- $(this).dialog('close');
- }
- }
- });
-});
-$('#more-results').click(function() {
- $('#integrity-results').dialog('open');
-})
-.hover(
- function(){
- $(this).addClass("ui-state-hover");
- },
- function(){
- $(this).removeClass("ui-state-hover");
- }
-).mousedown(function(){
- $(this).addClass("ui-state-active");
-})
-.mouseup(function(){
- $(this).removeClass("ui-state-active");
-});
-//-->{/literal}
+ {literal}<!--
+ $(function () {
+ $("#integrity-results").dialog({
+ modal: true,
+ autoOpen: false,
+ width: 600,
+ buttons: {
+ Ok: function () {
+ $(this).dialog('close');
+ }
+ }
+ });
+ });
+ $('#more-results').click(function () {
+ $('#integrity-results').dialog('open');
+ })
+ .hover(
+ function () {
+ $(this).addClass("ui-state-hover");
+ },
+ function () {
+ $(this).removeClass("ui-state-hover");
+ }
+ ).mousedown(function () {
+ $(this).addClass("ui-state-active");
+ })
+ .mouseup(function () {
+ $(this).removeClass("ui-state-active");
+ });
+ //-->
+ {/literal}
</script>
diff --git a/plugins/Installation/templates/structure.tpl b/plugins/Installation/templates/structure.tpl
index 5e5fd11893..6760c60c12 100644
--- a/plugins/Installation/templates/structure.tpl
+++ b/plugins/Installation/templates/structure.tpl
@@ -1,75 +1,77 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
- "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
-<title>Piwik &rsaquo; {'Installation_Installation'|translate}</title>
-<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+ <title>Piwik &rsaquo; {'Installation_Installation'|translate}</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
-<link rel="stylesheet" type="text/css" href="themes/default/common.css" />
-<link rel="stylesheet" type="text/css" href="libs/jquery/themes/base/jquery-ui.css" />
-<link rel="stylesheet" type="text/css" href="themes/default/styles.css" />
-<link rel="shortcut icon" href="plugins/CoreHome/templates/images/favicon.ico" />
-<script type="text/javascript" src="libs/jquery/jquery.js"></script>
-<script type="text/javascript" src="libs/jquery/jquery-ui.js"></script>
+ <link rel="stylesheet" type="text/css" href="themes/default/common.css"/>
+ <link rel="stylesheet" type="text/css" href="libs/jquery/themes/base/jquery-ui.css"/>
+ <link rel="stylesheet" type="text/css" href="themes/default/styles.css"/>
+ <link rel="shortcut icon" href="plugins/CoreHome/templates/images/favicon.ico"/>
+ <script type="text/javascript" src="libs/jquery/jquery.js"></script>
+ <script type="text/javascript" src="libs/jquery/jquery-ui.js"></script>
-{literal}
-<script type="text/javascript">
-$(document).ready( function(){
- $('#toFade').fadeOut(4000, function(){ $(this).show().css({visibility:'hidden'}); } );
- $('input:first').focus();
- $('#progressbar').progressbar({
-{/literal}
- value: {$percentDone}
-{literal}
- });
-});
-</script>
-{/literal}
+ {literal}
+ <script type="text/javascript">
+ $(document).ready(function () {
+ $('#toFade').fadeOut(4000, function () { $(this).show().css({visibility: 'hidden'}); });
+ $('input:first').focus();
+ $('#progressbar').progressbar({
+ {/literal}
+ value: {$percentDone}
+ {literal}
+ });
+ });
+ </script>
+ {/literal}
-<link rel="stylesheet" type="text/css" href="plugins/Installation/templates/install.css" />
-{if 'General_LayoutDirection'|translate =='rtl'}
-<link rel="stylesheet" type="text/css" href="themes/default/rtl.css" />
-{/if}
+ <link rel="stylesheet" type="text/css" href="plugins/Installation/templates/install.css"/>
+ {if 'General_LayoutDirection'|translate =='rtl'}
+ <link rel="stylesheet" type="text/css" href="themes/default/rtl.css"/>
+ {/if}
</head>
<body>
<div id="main">
- <div id="content">
- <div id="logo">
- <img id="title" width='160' src="themes/default/images/logo.png"/> &nbsp;&nbsp;&nbsp;<span id="subtitle"># {'General_OpenSourceWebAnalytics'|translate}</span>
- </div>
- <div style="float:right" id="topRightBar">
- <br />
- {postEvent name="template_topBar"}
- </div>
- <div class="both"></div>
+ <div id="content">
+ <div id="logo">
+ <img id="title" width='160' src="themes/default/images/logo.png"/> &nbsp;&nbsp;&nbsp;<span
+ id="subtitle"># {'General_OpenSourceWebAnalytics'|translate}</span>
+ </div>
+ <div style="float:right" id="topRightBar">
+ <br/>
+ {postEvent name="template_topBar"}
+ </div>
+ <div class="both"></div>
- <div id="generalInstall">
- {include file="Installation/templates/allSteps.tpl"}
- </div>
-
- <div id="detailInstall">
- {if isset($showNextStepAtTop) && $showNextStepAtTop}
- <p class="nextStep">
- <a class="submit" href="{url action=$nextModuleName}">{'General_Next'|translate} &raquo;</a>
- </p>
- {/if}
- {include file="$subTemplateToLoad"}
- {if $showNextStep}
- <p class="nextStep">
- <a class="submit" href="{url action=$nextModuleName}">{'General_Next'|translate} &raquo;</a>
- </p>
- {/if}
- </div>
-
- <div class="both"></div>
-
- <br />
- <br />
- <h3>{'Installation_InstallationStatus'|translate}</h3>
-
- <div id="progressbar"></div>
- {'Installation_PercentDone'|translate:$percentDone}
- </div>
+ <div id="generalInstall">
+ {include file="Installation/templates/allSteps.tpl"}
+ </div>
+
+ <div id="detailInstall">
+ {if isset($showNextStepAtTop) && $showNextStepAtTop}
+ <p class="nextStep">
+ <a class="submit" href="{url action=$nextModuleName}">{'General_Next'|translate} &raquo;</a>
+ </p>
+ {/if}
+ {include file="$subTemplateToLoad"}
+ {if $showNextStep}
+ <p class="nextStep">
+ <a class="submit" href="{url action=$nextModuleName}">{'General_Next'|translate} &raquo;</a>
+ </p>
+ {/if}
+ </div>
+
+ <div class="both"></div>
+
+ <br/>
+ <br/>
+
+ <h3>{'Installation_InstallationStatus'|translate}</h3>
+
+ <div id="progressbar"></div>
+ {'Installation_PercentDone'|translate:$percentDone}
+ </div>
</div>
</body>
</html>
diff --git a/plugins/Installation/templates/systemCheck.tpl b/plugins/Installation/templates/systemCheck.tpl
index 0f8cb0e309..50278ab34a 100644
--- a/plugins/Installation/templates/systemCheck.tpl
+++ b/plugins/Installation/templates/systemCheck.tpl
@@ -1,6 +1,6 @@
{if !$showNextStep}
-{include file="Installation/templates/systemCheck_legend.tpl"}
-<br style="clear:both">
+ {include file="Installation/templates/systemCheck_legend.tpl"}
+ <br style="clear:both">
{/if}
<h3>{'Installation_SystemCheck'|translate}</h3>
@@ -8,8 +8,10 @@
{include file="Installation/templates/systemCheckSection.tpl"}
{if !$showNextStep}
-<br/><p>
-<img src='themes/default/images/link.gif' /> &nbsp;<a href="?module=Proxy&action=redirect&url=http://piwik.org/docs/requirements/" target="_blank">{'Installation_Requirements'|translate}</a>
-</p>
-{include file="Installation/templates/systemCheck_legend.tpl"}
+ <br/>
+ <p>
+ <img src='themes/default/images/link.gif'/> &nbsp;<a href="?module=Proxy&action=redirect&url=http://piwik.org/docs/requirements/"
+ target="_blank">{'Installation_Requirements'|translate}</a>
+ </p>
+ {include file="Installation/templates/systemCheck_legend.tpl"}
{/if}
diff --git a/plugins/Installation/templates/systemCheckPage.css b/plugins/Installation/templates/systemCheckPage.css
index e9175cdabe..49444ce275 100755
--- a/plugins/Installation/templates/systemCheckPage.css
+++ b/plugins/Installation/templates/systemCheckPage.css
@@ -1,42 +1,42 @@
-#systemCheckOptional,#systemCheckRequired {
- border: 1px solid #dadada;
- width:100%;
- max-width: 900px;
+#systemCheckOptional, #systemCheckRequired {
+ border: 1px solid #dadada;
+ width: 100%;
+ max-width: 900px;
}
#systemCheckOptional {
- margin-bottom:2em;
+ margin-bottom: 2em;
}
-#systemCheckOptional td,#systemCheckRequired td {
- /*border: 1px solid #222;*/
- padding: 1em .5em 1em 2em;
- vertical-align:middle;
- font-size:1.2em;
- margin:0;
+#systemCheckOptional td, #systemCheckRequired td {
+ /*border: 1px solid #222;*/
+ padding: 1em .5em 1em 2em;
+ vertical-align: middle;
+ font-size: 1.2em;
+ margin: 0;
}
#systemCheckOptional tr:nth-child(even), #systemCheckRequired tr:nth-child(even) {
- background-color: #EFEEEC;
+ background-color: #EFEEEC;
}
#systemCheckOptional tr:nth-child(odd), #systemCheckRequired tr:nth-child(odd) {
- background-color: #F6F5F3;
+ background-color: #F6F5F3;
}
.error {
- color:red;
- font-size:100%;
- font-weight:bold;
- border: 2px solid red;
- width: 550px;
- padding:20px;
- margin-bottom:10px;
+ color: red;
+ font-size: 100%;
+ font-weight: bold;
+ border: 2px solid red;
+ width: 550px;
+ padding: 20px;
+ margin-bottom: 10px;
}
.error img {
- border:0;
- float:right;
- margin:10px;
+ border: 0;
+ float: right;
+ margin: 10px;
}
diff --git a/plugins/Installation/templates/systemCheckPage.tpl b/plugins/Installation/templates/systemCheckPage.tpl
index b70b4ef94c..133a4b0adb 100755
--- a/plugins/Installation/templates/systemCheckPage.tpl
+++ b/plugins/Installation/templates/systemCheckPage.tpl
@@ -1,18 +1,18 @@
{include file="CoreAdminHome/templates/header.tpl"}
{if $isSuperUser}
-
-<h2>{'Installation_SystemCheck'|translate}</h2>
-
-<p style="margin-left:1em">{if $infos.has_errors}
-<img src='themes/default/images/error.png' /> {'Installation_SystemCheckSummaryThereWereErrors'|translate:'<strong>':'</strong>':'<strong><em>':'</em></strong>'} {'Installation_SeeBelowForMoreInfo'|translate}
-{elseif $infos.has_warnings}
-<img src='themes/default/images/warning.png' /> {'Installation_SystemCheckSummaryThereWereWarnings'|translate} {'Installation_SeeBelowForMoreInfo'|translate}
-{else}
-<img src='themes/default/images/ok.png' /> {'Installation_SystemCheckSummaryNoProblems'|translate}
-{/if}</p>
-
-{include file="Installation/templates/systemCheckSection.tpl"}
+ <h2>{'Installation_SystemCheck'|translate}</h2>
+ <p style="margin-left:1em">{if $infos.has_errors}
+ <img src='themes/default/images/error.png'/>
+ {'Installation_SystemCheckSummaryThereWereErrors'|translate:'<strong>':'</strong>':'<strong><em>':'</em></strong>'} {'Installation_SeeBelowForMoreInfo'|translate}
+ {elseif $infos.has_warnings}
+ <img src='themes/default/images/warning.png'/>
+ {'Installation_SystemCheckSummaryThereWereWarnings'|translate} {'Installation_SeeBelowForMoreInfo'|translate}
+ {else}
+ <img src='themes/default/images/ok.png'/>
+ {'Installation_SystemCheckSummaryNoProblems'|translate}
+ {/if}</p>
+ {include file="Installation/templates/systemCheckSection.tpl"}
{/if}
diff --git a/plugins/Installation/templates/systemCheckSection.tpl b/plugins/Installation/templates/systemCheckSection.tpl
index 8788e396cd..e659684ba4 100755
--- a/plugins/Installation/templates/systemCheckSection.tpl
+++ b/plugins/Installation/templates/systemCheckSection.tpl
@@ -4,275 +4,303 @@
{assign var=link value="<img src='themes/default/images/link.gif' />"}
<table class="infosServer" id="systemCheckRequired">
- <tr>
- {capture assign="MinPHP"}{'Installation_SystemCheckPhp'|translate} &gt; {$infos.phpVersion_minimum}{/capture}
- <td class="label">{$MinPHP}</td>
+ <tr>
+ {capture assign="MinPHP"}{'Installation_SystemCheckPhp'|translate} &gt; {$infos.phpVersion_minimum}{/capture}
+ <td class="label">{$MinPHP}</td>
- <td>{if $infos.phpVersion_ok}{$ok}
- {else}{$error} <span class="err">{'General_Error'|translate}: {'General_Required'|translate:$MinPHP}</span>{/if}</td>
- </tr>
- <tr>
- <td class="label">PDO {'Installation_Extension'|translate}</td>
- <td>{if $infos.pdo_ok}{$ok}
- {else}-{/if}
- </td>
- </tr>
- {foreach from=$infos.adapters key=adapter item=port}
- <tr>
- <td class="label">{$adapter} {'Installation_Extension'|translate}</td>
- <td>{$ok}</td>
- </tr>
- {/foreach}
- {if !count($infos.adapters)}
- <tr>
- <td colspan="2" class="error">
- <small>
- {'Installation_SystemCheckDatabaseHelp'|translate}
- <p>
- {if $infos.isWindows}
- {'Installation_SystemCheckWinPdoAndMysqliHelp'|translate:"<br /><br /><code>extension=php_mysqli.dll</code><br /><code>extension=php_pdo.dll</code><br /><code>extension=php_pdo_mysql.dll</code><br />"|nl2br}
- {else}
- {'Installation_SystemCheckPdoAndMysqliHelp'|translate:"<br /><br /><code>--with-mysqli</code><br /><code>--with-pdo-mysql</code><br /><br />":"<br /><br /><code>extension=mysqli.so</code><br /><code>extension=pdo.so</code><br /><code>extension=pdo_mysql.so</code><br />"}
- {/if}
- {'Installation_RestartWebServer'|translate}
- <br />
- <br />
- {'Installation_SystemCheckPhpPdoAndMysqliSite'|translate}
- </p>
- </small>
- </td>
- </tr>
- {/if}
- </tr>
- <tr>
- <td class="label">{'Installation_SystemCheckExtensions'|translate}</td>
- <td>{foreach from=$infos.needed_extensions item=needed_extension}
- {if in_array($needed_extension, $infos.missing_extensions)}
- {$error}
- {capture assign="hasError"}1{/capture}
- {else}
- {$ok}
- {/if}
- {$needed_extension}
- <br />
- {/foreach}
- <br/>{if isset($hasError)}{'Installation_RestartWebServer'|translate}{/if}
- </td>
- </tr>
- {if count($infos.missing_extensions) gt 0}
- <tr>
- <td colspan="2" class="error">
- <small>
- {foreach from=$infos.missing_extensions item=missing_extension}
- <p>
- <i>{$helpMessages[$missing_extension]|translate}</i>
- </p>
- {/foreach}
- </small>
- </td>
- </tr>
- {/if}
- <tr>
- <td class="label">{'Installation_SystemCheckFunctions'|translate}</td>
-
- <td>{foreach from=$infos.needed_functions item=needed_function}
- {if in_array($needed_function, $infos.missing_functions)}
- {$error} <span class='err'>{$needed_function}</span>
- {capture assign="hasError"}1{/capture}
- <p>
- <i>{$helpMessages[$needed_function]|translate}</i>
- </p>
- {else}
- {$ok} {$needed_function}<br />
- {/if}
- {/foreach}
- <br/>{if isset($hasError)}{'Installation_RestartWebServer'|translate}{/if}
- </td>
- </tr>
- <tr>
- <td valign="top">
- {'Installation_SystemCheckWriteDirs'|translate}
- </td>
- <td>
- <small>
- {foreach from=$infos.directories key=dir item=bool}
- {if $bool}{$ok}{else}
- <span style="color:red">{$error}</span>{/if}
- {$dir}
- <br />
- {/foreach}
- </small>
- </td>
- </tr>
- {if $problemWithSomeDirectories}
- <tr>
- <td colspan="2" class="error">
- {'Installation_SystemCheckWriteDirsHelp'|translate}:
- {foreach from=$infos.directories key=dir item=bool}
- <ul>{if !$bool}
- <li><pre>chmod a+w {$dir}</pre></li>
- {/if}
- </ul>
- {/foreach}
- </td>
- </tr>
- {/if}
+ <td>{if $infos.phpVersion_ok}{$ok}
+ {else}{$error} <span class="err">{'General_Error'|translate}: {'General_Required'|translate:$MinPHP}</span>{/if}</td>
+ </tr>
+ <tr>
+ <td class="label">PDO {'Installation_Extension'|translate}</td>
+ <td>{if $infos.pdo_ok}{$ok}
+ {else}-{/if}
+ </td>
+ </tr>
+ {foreach from=$infos.adapters key=adapter item=port}
+ <tr>
+ <td class="label">{$adapter} {'Installation_Extension'|translate}</td>
+ <td>{$ok}</td>
+ </tr>
+ {/foreach}
+ {if !count($infos.adapters)}
+ <tr>
+ <td colspan="2" class="error">
+ <small>
+ {'Installation_SystemCheckDatabaseHelp'|translate}
+ <p>
+ {if $infos.isWindows}
+ {'Installation_SystemCheckWinPdoAndMysqliHelp'|translate:"<br /><br /><code>extension=php_mysqli.dll</code><br /><code>extension=php_pdo.dll</code><br /><code>extension=php_pdo_mysql.dll</code><br />"|nl2br}
+ {else}
+ {'Installation_SystemCheckPdoAndMysqliHelp'|translate:"<br /><br /><code>--with-mysqli</code><br /><code>--with-pdo-mysql</code><br /><br />":"<br /><br /><code>extension=mysqli.so</code><br /><code>extension=pdo.so</code><br /><code>extension=pdo_mysql.so</code><br />"}
+ {/if}
+ {'Installation_RestartWebServer'|translate}
+ <br/>
+ <br/>
+ {'Installation_SystemCheckPhpPdoAndMysqliSite'|translate}
+ </p>
+ </small>
+ </td>
+ </tr>
+ {/if}
+ </tr>
+ <tr>
+ <td class="label">{'Installation_SystemCheckExtensions'|translate}</td>
+ <td>{foreach from=$infos.needed_extensions item=needed_extension}
+ {if in_array($needed_extension, $infos.missing_extensions)}
+ {$error}
+ {capture assign="hasError"}1{/capture}
+ {else}
+ {$ok}
+ {/if}
+ {$needed_extension}
+ <br/>
+ {/foreach}
+ <br/>{if isset($hasError)}{'Installation_RestartWebServer'|translate}{/if}
+ </td>
+ </tr>
+ {if count($infos.missing_extensions) gt 0}
+ <tr>
+ <td colspan="2" class="error">
+ <small>
+ {foreach from=$infos.missing_extensions item=missing_extension}
+ <p>
+ <i>{$helpMessages[$missing_extension]|translate}</i>
+ </p>
+ {/foreach}
+ </small>
+ </td>
+ </tr>
+ {/if}
+ <tr>
+ <td class="label">{'Installation_SystemCheckFunctions'|translate}</td>
+
+ <td>{foreach from=$infos.needed_functions item=needed_function}
+ {if in_array($needed_function, $infos.missing_functions)}
+ {$error}
+ <span class='err'>{$needed_function}</span>
+ {capture assign="hasError"}1{/capture}
+ <p>
+ <i>{$helpMessages[$needed_function]|translate}</i>
+ </p>
+ {else}
+ {$ok} {$needed_function}
+ <br/>
+ {/if}
+ {/foreach}
+ <br/>{if isset($hasError)}{'Installation_RestartWebServer'|translate}{/if}
+ </td>
+ </tr>
+ <tr>
+ <td valign="top">
+ {'Installation_SystemCheckWriteDirs'|translate}
+ </td>
+ <td>
+ <small>
+ {foreach from=$infos.directories key=dir item=bool}
+ {if $bool}{$ok}{else}
+ <span style="color:red">{$error}</span>{/if}
+ {$dir}
+ <br/>
+ {/foreach}
+ </small>
+ </td>
+ </tr>
+ {if $problemWithSomeDirectories}
+ <tr>
+ <td colspan="2" class="error">
+ {'Installation_SystemCheckWriteDirsHelp'|translate}:
+ {foreach from=$infos.directories key=dir item=bool}
+ <ul>{if !$bool}
+ <li>
+ <pre>chmod a+w {$dir}</pre>
+ </li>
+ {/if}
+ </ul>
+ {/foreach}
+ </td>
+ </tr>
+ {/if}
</table>
-<br />
-
+<br/>
+
<h2>{'Optional'|translate}</h2>
<table class="infos" id="systemCheckOptional">
- <tr>
- <td class="label">{'Installation_SystemCheckFileIntegrity'|translate}</td>
- <td>
- {if empty($infos.integrityErrorMessages)}
- {$ok}
- {else}
- {if $infos.integrity}
- {$warning} <i>{$infos.integrityErrorMessages[0]}</i>
- {else}
- {$error} <i>{$infos.integrityErrorMessages[0]}</i>
- {/if}
- {if count($infos.integrityErrorMessages) > 1}
- <button id="more-results" class="ui-button ui-state-default ui-corner-all">{'General_Details'|translate}</button>
- {/if}
- {/if}
- </td>
- </tr>
- <tr>
- <td class="label">{'Installation_SystemCheckTracker'|translate}</td>
- <td>
- {if $infos.tracker_status == 0}
- {$ok}
- {else}
- {$warning} <span class="warn">{$infos.tracker_status}
- <br />{'Installation_SystemCheckTrackerHelp'|translate} </span>
- <br/>{'Installation_RestartWebServer'|translate}
- {/if}
- </td>
- </tr>
- <tr>
- <td class="label">{'Installation_SystemCheckMemoryLimit'|translate}</td>
- <td>
- {if $infos.memory_ok}
- {$ok} {$infos.memoryCurrent}
- {else}
- {$warning} <span class="warn">{$infos.memoryCurrent}</span>
- <br />{'Installation_SystemCheckMemoryLimitHelp'|translate}
- {'Installation_RestartWebServer'|translate}
- {/if}
- </td>
- </tr>
- <tr>
- <td class="label">{'SitesManager_Timezone'|translate}</td>
- <td>
- {if $infos.timezone}{$ok}
- {else}{$warning}
- <span class="warn">{'SitesManager_AdvancedTimezoneSupportNotFound'|translate} </span>
- <br/><a href="http://php.net/manual/en/datetime.installation.php" target="_blank">Timezone PHP documentation</a>.
- {/if}
- </td>
- </tr>
- <tr>
- <td class="label">{'Installation_SystemCheckOpenURL'|translate}</td>
- <td>
- {if $infos.openurl}{$ok} {$infos.openurl}
- {else}{$warning} <span class="warn">{'Installation_SystemCheckOpenURLHelp'|translate}</span>
- {/if}
- {if !$infos.can_auto_update}
- <br />{$warning} <span class="warn">{'Installation_SystemCheckAutoUpdateHelp'|translate}</span>{/if}
- </td>
- </tr>
- <tr>
- <td class="label">{'Installation_SystemCheckGD'|translate}</td>
- <td>
- {if $infos.gd_ok}{$ok}
- {else}{$warning} <span class="warn">{'Installation_SystemCheckGD'|translate}
- <br /> {'Installation_SystemCheckGDHelp'|translate} </span>{/if}
- </td>
- </tr>
- <tr>
- <td class="label">{'Installation_SystemCheckMbstring'|translate}</td>
- <td>
- {if $infos.hasMbstring}
- {if $infos.multibyte_ok}{$ok}
- {else}
- {$warning} <span class="warn">{'Installation_SystemCheckMbstring'|translate}
- <br/> {'Installation_SystemCheckMbstringFuncOverloadHelp'|translate}</span>
- {/if}
- {else}
- {$warning} <span class="warn">{'Installation_SystemCheckMbstringExtensionHelp'|translate}&nbsp;{'Installation_SystemCheckMbstringExtensionGeoIpHelp'|translate}</span>
- {/if}
- </td>
- </tr>
- <tr>
- <td class="label">{'Installation_SystemCheckOtherExtensions'|translate}</td>
- <td>{foreach from=$infos.desired_extensions item=desired_extension}
- {if in_array($desired_extension, $infos.missing_desired_extensions)}
- {$warning}<span class="warn">{$desired_extension}</span>
- <p>{$helpMessages[$desired_extension]|translate}</p>
- {else}
- {$ok} {$desired_extension}<br />
- {/if}
- {/foreach}
- </td>
- </tr>
- <tr>
- <td class="label">{'Installation_SystemCheckOtherFunctions'|translate}</td>
- <td>{foreach from=$infos.desired_functions item=desired_function}
- {if in_array($desired_function, $infos.missing_desired_functions)}
- {$warning} <span class="warn">{$desired_function}</span>
- <p>{$helpMessages[$desired_function]|translate}</p>
- {else}
- {$ok} {$desired_function}<br />
- {/if}
- {/foreach}
- </td>
- </tr>
- {if isset($infos.general_infos.assume_secure_protocol)}
- <tr>
- <td class="label">{'Installation_SystemCheckSecureProtocol'|translate}</td>
- <td>
- {$warning} <span class="warn">{$infos.protocol} </span><br/>
- {'Installation_SystemCheckSecureProtocolHelp'|translate}
- <br /><br />
- <code>[General]<br/>
-assume_secure_protocol = 1</code><br />
- </td>
- </tr>
- {/if}
- {if isset($infos.extra.load_data_infile_available)}
- <tr>
- <td class="label">{'Installation_DatabaseAbilities'|translate}</td>
- <td>
- {if $infos.extra.load_data_infile_available}
- {$ok} LOAD DATA INFILE<br/>
- {else}
- {$warning} <span class="warn">LOAD DATA INFILE</span><br/><br/>
- <p>{'Installation_LoadDataInfileUnavailableHelp'|translate:"LOAD DATA INFILE":"FILE"}</p>
- <p>{'Installation_LoadDataInfileRecommended'|translate}</p>
- {if isset($infos.extra.load_data_infile_error)}
- <em><strong>{'General_Error'|translate}:</strong></em> {$infos.extra.load_data_infile_error}
- {/if}
- {/if}
- </td>
- </tr>
- {/if}
- <tr>
- <td class="label">{'Installation_Filesystem'|translate}</td>
- <td>
- {if !$infos.is_nfs}
- {$ok} {'General_Ok'|translate}<br/>
- {else}
- {$warning} <span class="warn">{'Installation_NfsFilesystemWarning'|translate}</span>
- {if !empty($duringInstall)}
- <p>{'Installation_NfsFilesystemWarningSuffixInstall'|translate}</p>
- {else}
- <p>{'Installation_NfsFilesystemWarningSuffixAdmin'|translate}</p>
- {/if}
- {/if}
- </td>
- </tr>
+ <tr>
+ <td class="label">{'Installation_SystemCheckFileIntegrity'|translate}</td>
+ <td>
+ {if empty($infos.integrityErrorMessages)}
+ {$ok}
+ {else}
+ {if $infos.integrity}
+ {$warning}
+ <i>{$infos.integrityErrorMessages[0]}</i>
+ {else}
+ {$error}
+ <i>{$infos.integrityErrorMessages[0]}</i>
+ {/if}
+ {if count($infos.integrityErrorMessages) > 1}
+ <button id="more-results" class="ui-button ui-state-default ui-corner-all">{'General_Details'|translate}</button>
+ {/if}
+ {/if}
+ </td>
+ </tr>
+ <tr>
+ <td class="label">{'Installation_SystemCheckTracker'|translate}</td>
+ <td>
+ {if $infos.tracker_status == 0}
+ {$ok}
+ {else}
+ {$warning}
+ <span class="warn">{$infos.tracker_status}
+ <br/>{'Installation_SystemCheckTrackerHelp'|translate} </span>
+ <br/>
+ {'Installation_RestartWebServer'|translate}
+ {/if}
+ </td>
+ </tr>
+ <tr>
+ <td class="label">{'Installation_SystemCheckMemoryLimit'|translate}</td>
+ <td>
+ {if $infos.memory_ok}
+ {$ok} {$infos.memoryCurrent}
+ {else}
+ {$warning}
+ <span class="warn">{$infos.memoryCurrent}</span>
+ <br/>
+ {'Installation_SystemCheckMemoryLimitHelp'|translate}
+ {'Installation_RestartWebServer'|translate}
+ {/if}
+ </td>
+ </tr>
+ <tr>
+ <td class="label">{'SitesManager_Timezone'|translate}</td>
+ <td>
+ {if $infos.timezone}{$ok}
+ {else}{$warning}
+ <span class="warn">{'SitesManager_AdvancedTimezoneSupportNotFound'|translate} </span>
+ <br/>
+ <a href="http://php.net/manual/en/datetime.installation.php" target="_blank">Timezone PHP documentation</a>
+ .
+ {/if}
+ </td>
+ </tr>
+ <tr>
+ <td class="label">{'Installation_SystemCheckOpenURL'|translate}</td>
+ <td>
+ {if $infos.openurl}{$ok} {$infos.openurl}
+ {else}{$warning}
+ <span class="warn">{'Installation_SystemCheckOpenURLHelp'|translate}</span>
+ {/if}
+ {if !$infos.can_auto_update}
+ <br/>
+ {$warning} <span class="warn">{'Installation_SystemCheckAutoUpdateHelp'|translate}</span>{/if}
+ </td>
+ </tr>
+ <tr>
+ <td class="label">{'Installation_SystemCheckGD'|translate}</td>
+ <td>
+ {if $infos.gd_ok}{$ok}
+ {else}{$warning} <span class="warn">{'Installation_SystemCheckGD'|translate}
+ <br/>
+ {'Installation_SystemCheckGDHelp'|translate} </span>{/if}
+ </td>
+ </tr>
+ <tr>
+ <td class="label">{'Installation_SystemCheckMbstring'|translate}</td>
+ <td>
+ {if $infos.hasMbstring}
+ {if $infos.multibyte_ok}{$ok}
+ {else}
+ {$warning}
+ <span class="warn">{'Installation_SystemCheckMbstring'|translate}
+ <br/> {'Installation_SystemCheckMbstringFuncOverloadHelp'|translate}</span>
+ {/if}
+ {else}
+ {$warning}
+ <span class="warn">{'Installation_SystemCheckMbstringExtensionHelp'|translate}
+ &nbsp;{'Installation_SystemCheckMbstringExtensionGeoIpHelp'|translate}</span>
+ {/if}
+ </td>
+ </tr>
+ <tr>
+ <td class="label">{'Installation_SystemCheckOtherExtensions'|translate}</td>
+ <td>{foreach from=$infos.desired_extensions item=desired_extension}
+ {if in_array($desired_extension, $infos.missing_desired_extensions)}
+ {$warning}<span class="warn">{$desired_extension}</span>
+ <p>{$helpMessages[$desired_extension]|translate}</p>
+ {else}
+ {$ok} {$desired_extension}
+ <br/>
+ {/if}
+ {/foreach}
+ </td>
+ </tr>
+ <tr>
+ <td class="label">{'Installation_SystemCheckOtherFunctions'|translate}</td>
+ <td>{foreach from=$infos.desired_functions item=desired_function}
+ {if in_array($desired_function, $infos.missing_desired_functions)}
+ {$warning}
+ <span class="warn">{$desired_function}</span>
+ <p>{$helpMessages[$desired_function]|translate}</p>
+ {else}
+ {$ok} {$desired_function}
+ <br/>
+ {/if}
+ {/foreach}
+ </td>
+ </tr>
+ {if isset($infos.general_infos.assume_secure_protocol)}
+ <tr>
+ <td class="label">{'Installation_SystemCheckSecureProtocol'|translate}</td>
+ <td>
+ {$warning} <span class="warn">{$infos.protocol} </span><br/>
+ {'Installation_SystemCheckSecureProtocolHelp'|translate}
+ <br/><br/>
+ <code>[General]<br/>
+ assume_secure_protocol = 1</code><br/>
+ </td>
+ </tr>
+ {/if}
+ {if isset($infos.extra.load_data_infile_available)}
+ <tr>
+ <td class="label">{'Installation_DatabaseAbilities'|translate}</td>
+ <td>
+ {if $infos.extra.load_data_infile_available}
+ {$ok} LOAD DATA INFILE
+ <br/>
+ {else}
+ {$warning}
+ <span class="warn">LOAD DATA INFILE</span>
+ <br/>
+ <br/>
+ <p>{'Installation_LoadDataInfileUnavailableHelp'|translate:"LOAD DATA INFILE":"FILE"}</p>
+ <p>{'Installation_LoadDataInfileRecommended'|translate}</p>
+ {if isset($infos.extra.load_data_infile_error)}
+ <em><strong>{'General_Error'|translate}:</strong></em>
+ {$infos.extra.load_data_infile_error}
+ {/if}
+ {/if}
+ </td>
+ </tr>
+ {/if}
+ <tr>
+ <td class="label">{'Installation_Filesystem'|translate}</td>
+ <td>
+ {if !$infos.is_nfs}
+ {$ok} {'General_Ok'|translate}
+ <br/>
+ {else}
+ {$warning}
+ <span class="warn">{'Installation_NfsFilesystemWarning'|translate}</span>
+ {if !empty($duringInstall)}
+ <p>{'Installation_NfsFilesystemWarningSuffixInstall'|translate}</p>
+ {else}
+ <p>{'Installation_NfsFilesystemWarningSuffixAdmin'|translate}</p>
+ {/if}
+ {/if}
+ </td>
+ </tr>
</table>
{include file="Installation/templates/integrityDetails.tpl"}
diff --git a/plugins/Installation/templates/systemCheck_legend.tpl b/plugins/Installation/templates/systemCheck_legend.tpl
index d07cf00fba..0e57aa11e1 100644
--- a/plugins/Installation/templates/systemCheck_legend.tpl
+++ b/plugins/Installation/templates/systemCheck_legend.tpl
@@ -1,11 +1,15 @@
-<div id="systemCheckLegend"><small>
-<h2>{'Installation_Legend'|translate}</h2>
-<br />
-<img src='themes/default/images/warning.png' /> <span class="warn">{'General_Warning'|translate}: {'Installation_SystemCheckWarning'|translate}</span> <br />
-<img src='themes/default/images/error.png' /> <span style="color:red;font-weight:bold">{'General_Error'|translate}: {'Installation_SystemCheckError'|translate} </span><br />
-<img src='themes/default/images/ok.png' /> <span style="color:#26981C;font-weight:bold">{'General_Ok'|translate}</span><br />
-</small></div>
+<div id="systemCheckLegend">
+ <small>
+ <h2>{'Installation_Legend'|translate}</h2>
+ <br/>
+ <img src='themes/default/images/warning.png'/> <span class="warn">{'General_Warning'|translate}: {'Installation_SystemCheckWarning'|translate}</span>
+ <br/>
+ <img src='themes/default/images/error.png'/> <span style="color:red;font-weight:bold">{'General_Error'|translate}
+ : {'Installation_SystemCheckError'|translate} </span><br/>
+ <img src='themes/default/images/ok.png'/> <span style="color:#26981C;font-weight:bold">{'General_Ok'|translate}</span><br/>
+ </small>
+</div>
<p class="nextStep">
- <a href="{url}">{'General_Refresh'|translate} &raquo;</a>
+ <a href="{url}">{'General_Refresh'|translate} &raquo;</a>
</p>
diff --git a/plugins/Installation/templates/tablesCreation.tpl b/plugins/Installation/templates/tablesCreation.tpl
index 8614cb3153..fc03008d2c 100644
--- a/plugins/Installation/templates/tablesCreation.tpl
+++ b/plugins/Installation/templates/tablesCreation.tpl
@@ -1,63 +1,63 @@
<h2>{'Installation_Tables'|translate}</h2>
{if isset($someTablesInstalled)}
- <div class="warning">{'Installation_TablesWithSameNamesFound'|translate:"<span id='linkToggle'>":"</span>"}
- <img src="themes/default/images/warning_medium.png" />
- </div>
- <div id="toggle" style="display:none;color:#4F2410"><small><i>{'Installation_TablesFound'|translate}:
- <br />{$tablesInstalled} </i></small></div>
-
- {if isset($showReuseExistingTables)}
- <p>{'Installation_TablesWarningHelp'|translate}</p>
- <p class="nextStep"><a href="{url action=$nextModuleName}">{'Installation_TablesReuse'|translate} &raquo;</a></p>
- {else}
- <p class="nextStep"><a href="{url action=$previousPreviousModuleName}">&laquo; {'Installation_GoBackAndDefinePrefix'|translate}</a></p>
- {/if}
-
- <p class="nextStep"><a href="{url deleteTables=1}" id="eraseAllTables">{'Installation_TablesDelete'|translate} &raquo;</a></p>
+ <div class="warning">{'Installation_TablesWithSameNamesFound'|translate:"<span id='linkToggle'>":"</span>"}
+ <img src="themes/default/images/warning_medium.png"/>
+ </div>
+ <div id="toggle" style="display:none;color:#4F2410">
+ <small><i>{'Installation_TablesFound'|translate}:
+ <br/>{$tablesInstalled} </i></small>
+ </div>
+ {if isset($showReuseExistingTables)}
+ <p>{'Installation_TablesWarningHelp'|translate}</p>
+ <p class="nextStep"><a href="{url action=$nextModuleName}">{'Installation_TablesReuse'|translate} &raquo;</a></p>
+ {else}
+ <p class="nextStep"><a href="{url action=$previousPreviousModuleName}">&laquo; {'Installation_GoBackAndDefinePrefix'|translate}</a></p>
+ {/if}
+ <p class="nextStep"><a href="{url deleteTables=1}" id="eraseAllTables">{'Installation_TablesDelete'|translate} &raquo;</a></p>
{/if}
{if isset($existingTablesDeleted)}
- <div class="success"> {'Installation_TablesDeletedSuccess'|translate}
- <img src="themes/default/images/success_medium.png" /></div>
+ <div class="success"> {'Installation_TablesDeletedSuccess'|translate}
+ <img src="themes/default/images/success_medium.png"/></div>
{/if}
{if isset($tablesCreated)}
- <div class="success"> {'Installation_TablesCreatedSuccess'|translate}
- <img src="themes/default/images/success_medium.png" /></div>
+ <div class="success"> {'Installation_TablesCreatedSuccess'|translate}
+ <img src="themes/default/images/success_medium.png"/></div>
{/if}
{literal}
<script>
-$(document).ready( function(){
- {/literal}
- var strConfirmEraseTables = "{'Installation_ConfirmDeleteExistingTables'|translate:"[$tablesInstalled]":"<br />"} ";
- {literal}
-
- // toggle the display of the tables detected during the installation when clicking
- // on the span "linkToggle"
- $("#linkToggle")
- .css("border-bottom","thin dotted #ff5502")
-
- .hover( function() {
- $(this).css({ cursor: "pointer"});
- },
- function() {
- $(this).css({ cursor: "auto"});
- })
- .css("border-bottom","thin dotted #ff5502")
- .click(function(){
- $("#toggle").toggle();} );
-
- $("#eraseAllTables")
- .click( function(){
- if(!confirm( strConfirmEraseTables ) )
- {
- return false;
- }
- });
-
- ;
-});
+ $(document).ready(function () {
+ {/literal}
+ var strConfirmEraseTables = "{'Installation_ConfirmDeleteExistingTables'|translate:"[$tablesInstalled]":"<br />"} ";
+ {literal}
+
+ // toggle the display of the tables detected during the installation when clicking
+ // on the span "linkToggle"
+ $("#linkToggle")
+ .css("border-bottom", "thin dotted #ff5502")
+
+ .hover(function () {
+ $(this).css({ cursor: "pointer"});
+ },
+ function () {
+ $(this).css({ cursor: "auto"});
+ })
+ .css("border-bottom", "thin dotted #ff5502")
+ .click(function () {
+ $("#toggle").toggle();
+ });
+
+ $("#eraseAllTables")
+ .click(function () {
+ if (!confirm(strConfirmEraseTables)) {
+ return false;
+ }
+ });
+
+ ;
+ });
</script>
{/literal}
diff --git a/plugins/Installation/templates/welcome.tpl b/plugins/Installation/templates/welcome.tpl
index 526f04452f..b1fecd42dc 100644
--- a/plugins/Installation/templates/welcome.tpl
+++ b/plugins/Installation/templates/welcome.tpl
@@ -1,43 +1,43 @@
<h2>{'Installation_Welcome'|translate}</h2>
{if $newInstall}
-{'Installation_WelcomeHelp'|translate:$totalNumberOfSteps}
+ {'Installation_WelcomeHelp'|translate:$totalNumberOfSteps}
{else}
-<p>{'Installation_ConfigurationHelp'|translate}</p>
-<br />
-<div class="error">
-{$errorMessage}
-</div>
+ <p>{'Installation_ConfigurationHelp'|translate}</p>
+ <br/>
+ <div class="error">
+ {$errorMessage}
+ </div>
{/if}
{literal}
-<script type="text/javascript">
-<!--
-$(function() {
- // client-side test for https to handle the case where the server is behind a reverse proxy
- if (document.location.protocol === 'https:') {
- $('p.nextStep a').attr('href', $('p.nextStep a').attr('href') + '&clientProtocol=https');
- }
+ <script type="text/javascript">
+ <!--
+ $(function () {
+ // client-side test for https to handle the case where the server is behind a reverse proxy
+ if (document.location.protocol === 'https:') {
+ $('p.nextStep a').attr('href', $('p.nextStep a').attr('href') + '&clientProtocol=https');
+ }
- // client-side test for broken tracker (e.g., mod_security rule)
- $('p.nextStep').hide();
- $.ajax({
- url: 'piwik.php',
- data: 'url=http://example.com',
- complete: function() {
- $('p.nextStep').show();
- },
- error: function(req) {
- $('p.nextStep a').attr('href', $('p.nextStep a').attr('href') + '&trackerStatus=' + req.status);
- }
- });
-});
-//-->
-</script>
+ // client-side test for broken tracker (e.g., mod_security rule)
+ $('p.nextStep').hide();
+ $.ajax({
+ url: 'piwik.php',
+ data: 'url=http://example.com',
+ complete: function () {
+ $('p.nextStep').show();
+ },
+ error: function (req) {
+ $('p.nextStep a').attr('href', $('p.nextStep a').attr('href') + '&trackerStatus=' + req.status);
+ }
+ });
+ });
+ //-->
+ </script>
{/literal}
{if !$showNextStep}
-<p class="nextStep">
- <a href="{url}">{'General_Refresh'|translate} &raquo;</a>
-</p>
+ <p class="nextStep">
+ <a href="{url}">{'General_Refresh'|translate} &raquo;</a>
+ </p>
{/if}
diff --git a/plugins/LanguagesManager/API.php b/plugins/LanguagesManager/API.php
index f8e7a4effb..484065f4e1 100644
--- a/plugins/LanguagesManager/API.php
+++ b/plugins/LanguagesManager/API.php
@@ -24,173 +24,163 @@
*/
class Piwik_LanguagesManager_API
{
- static private $instance = null;
-
- /**
- * @return Piwik_LanguagesManager_API
- */
- static public function getInstance()
- {
- if (self::$instance == null)
- {
- self::$instance = new self;
- }
- return self::$instance;
- }
+ static private $instance = null;
- protected $availableLanguageNames = null;
- protected $languageNames = null;
-
- /**
- * Returns true if specified language is available
- *
- * @param string $languageCode
- * @return bool true if language available; false otherwise
- */
- public function isLanguageAvailable($languageCode)
- {
- return $languageCode !== false
- && Piwik_Common::isValidFilename($languageCode)
- && in_array($languageCode, $this->getAvailableLanguages());
- }
-
- /**
- * Return array of available languages
- *
- * @return array Arry of strings, each containing its ISO language code
- */
- public function getAvailableLanguages()
- {
- if(!is_null($this->languageNames))
- {
- return $this->languageNames;
- }
- $path = PIWIK_INCLUDE_PATH . "/lang/";
- $languages = _glob($path . "*.php");
- $pathLength = strlen($path);
- $languageNames = array();
- if($languages)
- {
- foreach($languages as $language)
- {
- $languageNames[] = substr($language, $pathLength, -strlen('.php'));
- }
- }
- $this->languageNames = $languageNames;
- return $languageNames;
- }
+ /**
+ * @return Piwik_LanguagesManager_API
+ */
+ static public function getInstance()
+ {
+ if (self::$instance == null) {
+ self::$instance = new self;
+ }
+ return self::$instance;
+ }
- /**
- * Return information on translations (code, language, % translated, etc)
- *
- * @return array Array of arrays
- */
- public function getAvailableLanguagesInfo()
- {
- require PIWIK_INCLUDE_PATH . '/lang/en.php';
- $englishTranslation = $translations;
- $filenames = $this->getAvailableLanguages();
- $languagesInfo = array();
- foreach($filenames as $filename)
- {
- require PIWIK_INCLUDE_PATH . "/lang/$filename.php";
- $translationStringsDone = array_intersect_key($englishTranslation, array_filter($translations, 'strlen'));
- $percentageComplete = count($translationStringsDone) / count($englishTranslation);
- $percentageComplete = round(100 * $percentageComplete, 0);
- $languageInfo = array( 'code' => $filename,
- 'name' => $translations['General_OriginalLanguageName'],
- 'english_name' => $translations['General_EnglishLanguageName'],
- 'translators' => $translations['General_TranslatorName'],
- 'translators_email' => $translations['General_TranslatorEmail'],
- 'percentage_complete' => $percentageComplete . '%',
- );
- $languagesInfo[] = $languageInfo;
- }
- return $languagesInfo;
- }
-
- /**
- * Return array of available languages
- *
- * @return array Arry of array, each containing its ISO language code and name of the language
- */
- public function getAvailableLanguageNames()
- {
- if(!is_null($this->availableLanguageNames))
- {
- return $this->availableLanguageNames;
- }
-
- $filenames = $this->getAvailableLanguages();
- $languagesInfo = array();
- foreach($filenames as $filename)
- {
- require PIWIK_INCLUDE_PATH . "/lang/$filename.php";
- $languagesInfo[] = array(
- 'code' => $filename,
- 'name' => $translations['General_OriginalLanguageName'],
- 'english_name' => $translations['General_EnglishLanguageName']
- );
- }
- $this->availableLanguageNames = $languagesInfo;
- return $this->availableLanguageNames;
- }
-
- /**
- * Returns translation strings by language
- *
- * @param string $languageCode ISO language code
- * @return array|false Array of arrays, each containing 'label' (translation index) and 'value' (translated string); false if language unavailable
- */
- public function getTranslationsForLanguage($languageCode)
- {
- if(!$this->isLanguageAvailable($languageCode))
- {
- return false;
- }
- require PIWIK_INCLUDE_PATH . "/lang/$languageCode.php";
- $languageInfo = array();
- foreach($translations as $key => $value)
- {
- $languageInfo[] = array('label' => $key, 'value' => $value);
- }
- return $languageInfo;
- }
+ protected $availableLanguageNames = null;
+ protected $languageNames = null;
- /**
- * Returns the language for the user
- *
- * @param string $login
- * @return string
- */
- public function getLanguageForUser( $login )
- {
- Piwik::checkUserIsSuperUserOrTheUser($login);
- Piwik::checkUserIsNotAnonymous();
- return Piwik_FetchOne('SELECT language FROM '.Piwik_Common::prefixTable('user_language') .
- ' WHERE login = ? ', array($login ));
- }
-
- /**
- * Sets the language for the user
- *
- * @param string $login
- * @param string $languageCode
- * @return bool
- */
- public function setLanguageForUser($login, $languageCode)
- {
- Piwik::checkUserIsSuperUserOrTheUser($login);
- Piwik::checkUserIsNotAnonymous();
- if(!$this->isLanguageAvailable($languageCode))
- {
- return false;
- }
- $paramsBind = array($login, $languageCode, $languageCode);
- Piwik_Query('INSERT INTO '.Piwik_Common::prefixTable('user_language') .
- ' (login, language)
- VALUES (?,?)
- ON DUPLICATE KEY UPDATE language=?',
- $paramsBind);
- }
+ /**
+ * Returns true if specified language is available
+ *
+ * @param string $languageCode
+ * @return bool true if language available; false otherwise
+ */
+ public function isLanguageAvailable($languageCode)
+ {
+ return $languageCode !== false
+ && Piwik_Common::isValidFilename($languageCode)
+ && in_array($languageCode, $this->getAvailableLanguages());
+ }
+
+ /**
+ * Return array of available languages
+ *
+ * @return array Arry of strings, each containing its ISO language code
+ */
+ public function getAvailableLanguages()
+ {
+ if (!is_null($this->languageNames)) {
+ return $this->languageNames;
+ }
+ $path = PIWIK_INCLUDE_PATH . "/lang/";
+ $languages = _glob($path . "*.php");
+ $pathLength = strlen($path);
+ $languageNames = array();
+ if ($languages) {
+ foreach ($languages as $language) {
+ $languageNames[] = substr($language, $pathLength, -strlen('.php'));
+ }
+ }
+ $this->languageNames = $languageNames;
+ return $languageNames;
+ }
+
+ /**
+ * Return information on translations (code, language, % translated, etc)
+ *
+ * @return array Array of arrays
+ */
+ public function getAvailableLanguagesInfo()
+ {
+ require PIWIK_INCLUDE_PATH . '/lang/en.php';
+ $englishTranslation = $translations;
+ $filenames = $this->getAvailableLanguages();
+ $languagesInfo = array();
+ foreach ($filenames as $filename) {
+ require PIWIK_INCLUDE_PATH . "/lang/$filename.php";
+ $translationStringsDone = array_intersect_key($englishTranslation, array_filter($translations, 'strlen'));
+ $percentageComplete = count($translationStringsDone) / count($englishTranslation);
+ $percentageComplete = round(100 * $percentageComplete, 0);
+ $languageInfo = array('code' => $filename,
+ 'name' => $translations['General_OriginalLanguageName'],
+ 'english_name' => $translations['General_EnglishLanguageName'],
+ 'translators' => $translations['General_TranslatorName'],
+ 'translators_email' => $translations['General_TranslatorEmail'],
+ 'percentage_complete' => $percentageComplete . '%',
+ );
+ $languagesInfo[] = $languageInfo;
+ }
+ return $languagesInfo;
+ }
+
+ /**
+ * Return array of available languages
+ *
+ * @return array Arry of array, each containing its ISO language code and name of the language
+ */
+ public function getAvailableLanguageNames()
+ {
+ if (!is_null($this->availableLanguageNames)) {
+ return $this->availableLanguageNames;
+ }
+
+ $filenames = $this->getAvailableLanguages();
+ $languagesInfo = array();
+ foreach ($filenames as $filename) {
+ require PIWIK_INCLUDE_PATH . "/lang/$filename.php";
+ $languagesInfo[] = array(
+ 'code' => $filename,
+ 'name' => $translations['General_OriginalLanguageName'],
+ 'english_name' => $translations['General_EnglishLanguageName']
+ );
+ }
+ $this->availableLanguageNames = $languagesInfo;
+ return $this->availableLanguageNames;
+ }
+
+ /**
+ * Returns translation strings by language
+ *
+ * @param string $languageCode ISO language code
+ * @return array|false Array of arrays, each containing 'label' (translation index) and 'value' (translated string); false if language unavailable
+ */
+ public function getTranslationsForLanguage($languageCode)
+ {
+ if (!$this->isLanguageAvailable($languageCode)) {
+ return false;
+ }
+ require PIWIK_INCLUDE_PATH . "/lang/$languageCode.php";
+ $languageInfo = array();
+ foreach ($translations as $key => $value) {
+ $languageInfo[] = array('label' => $key, 'value' => $value);
+ }
+ return $languageInfo;
+ }
+
+ /**
+ * Returns the language for the user
+ *
+ * @param string $login
+ * @return string
+ */
+ public function getLanguageForUser($login)
+ {
+ Piwik::checkUserIsSuperUserOrTheUser($login);
+ Piwik::checkUserIsNotAnonymous();
+ return Piwik_FetchOne('SELECT language FROM ' . Piwik_Common::prefixTable('user_language') .
+ ' WHERE login = ? ', array($login));
+ }
+
+ /**
+ * Sets the language for the user
+ *
+ * @param string $login
+ * @param string $languageCode
+ * @return bool
+ */
+ public function setLanguageForUser($login, $languageCode)
+ {
+ Piwik::checkUserIsSuperUserOrTheUser($login);
+ Piwik::checkUserIsNotAnonymous();
+ if (!$this->isLanguageAvailable($languageCode)) {
+ return false;
+ }
+ $paramsBind = array($login, $languageCode, $languageCode);
+ Piwik_Query('INSERT INTO ' . Piwik_Common::prefixTable('user_language') .
+ ' (login, language)
+ VALUES (?,?)
+ ON DUPLICATE KEY UPDATE language=?',
+ $paramsBind);
+ }
}
diff --git a/plugins/LanguagesManager/Controller.php b/plugins/LanguagesManager/Controller.php
index 4553143783..3162d209ef 100644
--- a/plugins/LanguagesManager/Controller.php
+++ b/plugins/LanguagesManager/Controller.php
@@ -1,13 +1,13 @@
<?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_LanguagesManager
- *
+ *
*/
/**
@@ -15,26 +15,25 @@
*/
class Piwik_LanguagesManager_Controller extends Piwik_Controller
{
- /**
- * anonymous = in the session
- * authenticated user = in the session and in DB
- */
- public function saveLanguage()
- {
- $language = Piwik_Common::getRequestVar('language');
+ /**
+ * anonymous = in the session
+ * authenticated user = in the session and in DB
+ */
+ public function saveLanguage()
+ {
+ $language = Piwik_Common::getRequestVar('language');
- // Prevent CSRF only when piwik is not installed yet (During install user can change language)
- if(Piwik::isInstalled()) {
- $this->checkTokenInUrl();
- }
- Piwik_LanguagesManager::setLanguageForSession($language);
- if(Zend_Registry::isRegistered('access')) {
- $currentUser = Piwik::getCurrentUserLogin();
- if($currentUser && $currentUser !== 'anonymous')
- {
- Piwik_LanguagesManager_API::getInstance()->setLanguageForUser($currentUser, $language);
- }
- }
- Piwik_Url::redirectToReferer();
- }
+ // Prevent CSRF only when piwik is not installed yet (During install user can change language)
+ if (Piwik::isInstalled()) {
+ $this->checkTokenInUrl();
+ }
+ Piwik_LanguagesManager::setLanguageForSession($language);
+ if (Zend_Registry::isRegistered('access')) {
+ $currentUser = Piwik::getCurrentUserLogin();
+ if ($currentUser && $currentUser !== 'anonymous') {
+ Piwik_LanguagesManager_API::getInstance()->setLanguageForUser($currentUser, $language);
+ }
+ }
+ Piwik_Url::redirectToReferer();
+ }
}
diff --git a/plugins/LanguagesManager/LanguagesManager.php b/plugins/LanguagesManager/LanguagesManager.php
index d6162ecf01..29c900a8eb 100644
--- a/plugins/LanguagesManager/LanguagesManager.php
+++ b/plugins/LanguagesManager/LanguagesManager.php
@@ -1,13 +1,13 @@
<?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_LanguagesManager
- *
+ *
*/
/**
@@ -16,229 +16,219 @@
*/
class Piwik_LanguagesManager extends Piwik_Plugin
{
- public function getInformation()
- {
- return array(
- 'description' => Piwik_Translate('LanguagesManager_PluginDescription'),
- 'author' => 'Piwik',
- 'author_homepage' => 'http://piwik.org/',
- 'version' => Piwik_Version::VERSION,
- );
- }
-
- public function getListHooksRegistered()
- {
- return array(
- 'AssetManager.getCssFiles' => 'getCssFiles',
- 'AssetManager.getJsFiles' => 'getJsFiles',
- 'TopMenu.add' => 'showLanguagesSelector',
- 'Translate.getLanguageToLoad' => 'getLanguageToLoad',
- 'UsersManager.deleteUser' => 'deleteUserLanguage',
- 'template_topBar' => 'addLanguagesManagerToOtherTopBar',
- );
- }
-
- /**
- * @param Piwik_Event_Notification $notification notification object
- */
- function getCssFiles( $notification )
- {
- $cssFiles = &$notification->getNotificationObject();
-
- $cssFiles[] = "themes/default/styles.css";
- }
-
- /**
- * @param Piwik_Event_Notification $notification notification object
- */
- function getJsFiles( $notification )
- {
- $jsFiles = &$notification->getNotificationObject();
-
- $jsFiles[] = "plugins/LanguagesManager/templates/languageSelector.js";
- }
-
- /**
- * Show styled language selection drop-down list
- *
- * @param string $url The form action. Default is to save language.
- */
- function showLanguagesSelector()
- {
- Piwik_AddTopMenu('LanguageSelector', $this->getLanguagesSelector(), true, $order = 30, true);
- }
-
- /**
- * Adds the languages drop-down list to topbars other than the main one rendered
- * in CoreHome/templates/top_bar.tpl. The 'other' topbars are on the Installation
- * and CoreUpdater screens.
- *
- * @param Piwik_Event_Notification $notification notification object
- */
- public function addLanguagesManagerToOtherTopBar( $notification )
- {
- $str =& $notification->getNotificationObject();
- // piwik object & scripts aren't loaded in 'other' topbars
- $str .= "<script type='text/javascript'>if (!window.piwik) window.piwik={};</script>";
- $str .= "<script type='text/javascript' src='plugins/LanguagesManager/templates/languageSelector.js'></script>";
- $str .= $this->getLanguagesSelector();
- }
-
- /**
- * Renders and returns the language selector HTML.
- *
- * @return string
- */
- private function getLanguagesSelector()
- {
- // don't use Piwik_View::factory() here
- $view = new Piwik_View("LanguagesManager/templates/languages.tpl");
- $view->languages = Piwik_LanguagesManager_API::getInstance()->getAvailableLanguageNames();
- $view->currentLanguageCode = self::getLanguageCodeForCurrentUser();
- $view->currentLanguageName = self::getLanguageNameForCurrentUser();
- return $view->render();
- }
-
- /**
- * @param Piwik_Event_Notification $notification notification object
- */
- function getLanguageToLoad($notification)
- {
- $language =& $notification->getNotificationObject();
- if (empty($language))
- {
- $language = self::getLanguageCodeForCurrentUser();
- }
- if(!Piwik_LanguagesManager_API::getInstance()->isLanguageAvailable($language))
- {
- $language = Piwik_Translate::getInstance()->getLanguageDefault();
- }
- }
-
- /**
- * @param Piwik_Event_Notification $notification notification object
- */
- function deleteUserLanguage($notification)
- {
- $userLogin = $notification->getNotificationObject();
- Piwik_Query('DELETE FROM ' . Piwik_Common::prefixTable('user_language') . ' WHERE login = ?', $userLogin);
- }
-
- /**
- * @throws Exception if non-recoverable error
- */
- public function install()
- {
- // we catch the exception
- try{
- $sql = "CREATE TABLE ". Piwik_Common::prefixTable('user_language')." (
+ public function getInformation()
+ {
+ return array(
+ 'description' => Piwik_Translate('LanguagesManager_PluginDescription'),
+ 'author' => 'Piwik',
+ 'author_homepage' => 'http://piwik.org/',
+ 'version' => Piwik_Version::VERSION,
+ );
+ }
+
+ public function getListHooksRegistered()
+ {
+ return array(
+ 'AssetManager.getCssFiles' => 'getCssFiles',
+ 'AssetManager.getJsFiles' => 'getJsFiles',
+ 'TopMenu.add' => 'showLanguagesSelector',
+ 'Translate.getLanguageToLoad' => 'getLanguageToLoad',
+ 'UsersManager.deleteUser' => 'deleteUserLanguage',
+ 'template_topBar' => 'addLanguagesManagerToOtherTopBar',
+ );
+ }
+
+ /**
+ * @param Piwik_Event_Notification $notification notification object
+ */
+ function getCssFiles($notification)
+ {
+ $cssFiles = & $notification->getNotificationObject();
+
+ $cssFiles[] = "themes/default/styles.css";
+ }
+
+ /**
+ * @param Piwik_Event_Notification $notification notification object
+ */
+ function getJsFiles($notification)
+ {
+ $jsFiles = & $notification->getNotificationObject();
+
+ $jsFiles[] = "plugins/LanguagesManager/templates/languageSelector.js";
+ }
+
+ /**
+ * Show styled language selection drop-down list
+ *
+ * @param string $url The form action. Default is to save language.
+ */
+ function showLanguagesSelector()
+ {
+ Piwik_AddTopMenu('LanguageSelector', $this->getLanguagesSelector(), true, $order = 30, true);
+ }
+
+ /**
+ * Adds the languages drop-down list to topbars other than the main one rendered
+ * in CoreHome/templates/top_bar.tpl. The 'other' topbars are on the Installation
+ * and CoreUpdater screens.
+ *
+ * @param Piwik_Event_Notification $notification notification object
+ */
+ public function addLanguagesManagerToOtherTopBar($notification)
+ {
+ $str =& $notification->getNotificationObject();
+ // piwik object & scripts aren't loaded in 'other' topbars
+ $str .= "<script type='text/javascript'>if (!window.piwik) window.piwik={};</script>";
+ $str .= "<script type='text/javascript' src='plugins/LanguagesManager/templates/languageSelector.js'></script>";
+ $str .= $this->getLanguagesSelector();
+ }
+
+ /**
+ * Renders and returns the language selector HTML.
+ *
+ * @return string
+ */
+ private function getLanguagesSelector()
+ {
+ // don't use Piwik_View::factory() here
+ $view = new Piwik_View("LanguagesManager/templates/languages.tpl");
+ $view->languages = Piwik_LanguagesManager_API::getInstance()->getAvailableLanguageNames();
+ $view->currentLanguageCode = self::getLanguageCodeForCurrentUser();
+ $view->currentLanguageName = self::getLanguageNameForCurrentUser();
+ return $view->render();
+ }
+
+ /**
+ * @param Piwik_Event_Notification $notification notification object
+ */
+ function getLanguageToLoad($notification)
+ {
+ $language =& $notification->getNotificationObject();
+ if (empty($language)) {
+ $language = self::getLanguageCodeForCurrentUser();
+ }
+ if (!Piwik_LanguagesManager_API::getInstance()->isLanguageAvailable($language)) {
+ $language = Piwik_Translate::getInstance()->getLanguageDefault();
+ }
+ }
+
+ /**
+ * @param Piwik_Event_Notification $notification notification object
+ */
+ function deleteUserLanguage($notification)
+ {
+ $userLogin = $notification->getNotificationObject();
+ Piwik_Query('DELETE FROM ' . Piwik_Common::prefixTable('user_language') . ' WHERE login = ?', $userLogin);
+ }
+
+ /**
+ * @throws Exception if non-recoverable error
+ */
+ public function install()
+ {
+ // we catch the exception
+ try {
+ $sql = "CREATE TABLE " . Piwik_Common::prefixTable('user_language') . " (
login VARCHAR( 100 ) NOT NULL ,
language VARCHAR( 10 ) NOT NULL ,
PRIMARY KEY ( login )
- ) DEFAULT CHARSET=utf8 " ;
- Piwik_Exec($sql);
- } catch(Exception $e){
- // mysql code error 1050:table already exists
- // see bug #153 http://dev.piwik.org/trac/ticket/153
- if(!Zend_Registry::get('db')->isErrNo($e, '1050'))
- {
- throw $e;
- }
- }
- }
-
- /**
- * @throws Exception if non-recoverable error
- */
- public function uninstall()
- {
- Piwik_DropTables(Piwik_Common::prefixTable('user_language'));
- }
-
- /**
- * @return string Two letters language code, eg. "fr"
- */
- static public function getLanguageCodeForCurrentUser()
- {
- $languageCode = self::getLanguageFromPreferences();
- if(!Piwik_LanguagesManager_API::getInstance()->isLanguageAvailable($languageCode))
- {
- $languageCode = Piwik_Common::extractLanguageCodeFromBrowserLanguage(Piwik_Common::getBrowserLanguage(), Piwik_LanguagesManager_API::getInstance()->getAvailableLanguages());
- }
- if(!Piwik_LanguagesManager_API::getInstance()->isLanguageAvailable($languageCode))
- {
- $languageCode = Piwik_Translate::getInstance()->getLanguageDefault();
- }
- return $languageCode;
- }
-
- /**
- * @return string Full english language string, eg. "French"
- */
- static public function getLanguageNameForCurrentUser()
- {
- $languageCode = self::getLanguageCodeForCurrentUser();
- $languages = Piwik_LanguagesManager_API::getInstance()->getAvailableLanguageNames();
- foreach($languages as $language)
- {
- if($language['code'] === $languageCode)
- {
- return $language['name'];
- }
- }
- }
-
- /**
- * @return string|false if language preference could not be loaded
- */
- static protected function getLanguageFromPreferences()
- {
- if(($language = self::getLanguageForSession()) != null)
- {
- return $language;
- }
-
- try {
- $currentUser = Piwik::getCurrentUserLogin();
- return Piwik_LanguagesManager_API::getInstance()->getLanguageForUser($currentUser);
- } catch(Exception $e) {
- return false;
- }
- }
-
-
- /**
- * Returns the langage for the session
- *
- * @return string|null
- */
- static public function getLanguageForSession()
- {
- $cookieName = Piwik_Config::getInstance()->General['language_cookie_name'];
- $cookie = new Piwik_Cookie($cookieName);
- if($cookie->isCookieFound())
- {
- return $cookie->get('language');
- }
- return null;
- }
-
- /**
- * Set the language for the session
- *
- * @param string $languageCode ISO language code
- * @return bool
- */
- static public function setLanguageForSession($languageCode)
- {
- if(!Piwik_LanguagesManager_API::getInstance()->isLanguageAvailable($languageCode))
- {
- return false;
- }
-
- $cookieName = Piwik_Config::getInstance()->General['language_cookie_name'];
- $cookie = new Piwik_Cookie($cookieName, 0);
- $cookie->set('language', $languageCode);
- $cookie->save();
- }
+ ) DEFAULT CHARSET=utf8 ";
+ Piwik_Exec($sql);
+ } catch (Exception $e) {
+ // mysql code error 1050:table already exists
+ // see bug #153 http://dev.piwik.org/trac/ticket/153
+ if (!Zend_Registry::get('db')->isErrNo($e, '1050')) {
+ throw $e;
+ }
+ }
+ }
+
+ /**
+ * @throws Exception if non-recoverable error
+ */
+ public function uninstall()
+ {
+ Piwik_DropTables(Piwik_Common::prefixTable('user_language'));
+ }
+
+ /**
+ * @return string Two letters language code, eg. "fr"
+ */
+ static public function getLanguageCodeForCurrentUser()
+ {
+ $languageCode = self::getLanguageFromPreferences();
+ if (!Piwik_LanguagesManager_API::getInstance()->isLanguageAvailable($languageCode)) {
+ $languageCode = Piwik_Common::extractLanguageCodeFromBrowserLanguage(Piwik_Common::getBrowserLanguage(), Piwik_LanguagesManager_API::getInstance()->getAvailableLanguages());
+ }
+ if (!Piwik_LanguagesManager_API::getInstance()->isLanguageAvailable($languageCode)) {
+ $languageCode = Piwik_Translate::getInstance()->getLanguageDefault();
+ }
+ return $languageCode;
+ }
+
+ /**
+ * @return string Full english language string, eg. "French"
+ */
+ static public function getLanguageNameForCurrentUser()
+ {
+ $languageCode = self::getLanguageCodeForCurrentUser();
+ $languages = Piwik_LanguagesManager_API::getInstance()->getAvailableLanguageNames();
+ foreach ($languages as $language) {
+ if ($language['code'] === $languageCode) {
+ return $language['name'];
+ }
+ }
+ }
+
+ /**
+ * @return string|false if language preference could not be loaded
+ */
+ static protected function getLanguageFromPreferences()
+ {
+ if (($language = self::getLanguageForSession()) != null) {
+ return $language;
+ }
+
+ try {
+ $currentUser = Piwik::getCurrentUserLogin();
+ return Piwik_LanguagesManager_API::getInstance()->getLanguageForUser($currentUser);
+ } catch (Exception $e) {
+ return false;
+ }
+ }
+
+
+ /**
+ * Returns the langage for the session
+ *
+ * @return string|null
+ */
+ static public function getLanguageForSession()
+ {
+ $cookieName = Piwik_Config::getInstance()->General['language_cookie_name'];
+ $cookie = new Piwik_Cookie($cookieName);
+ if ($cookie->isCookieFound()) {
+ return $cookie->get('language');
+ }
+ return null;
+ }
+
+ /**
+ * Set the language for the session
+ *
+ * @param string $languageCode ISO language code
+ * @return bool
+ */
+ static public function setLanguageForSession($languageCode)
+ {
+ if (!Piwik_LanguagesManager_API::getInstance()->isLanguageAvailable($languageCode)) {
+ return false;
+ }
+
+ $cookieName = Piwik_Config::getInstance()->General['language_cookie_name'];
+ $cookie = new Piwik_Cookie($cookieName, 0);
+ $cookie->set('language', $languageCode);
+ $cookie->save();
+ }
}
diff --git a/plugins/LanguagesManager/templates/languageSelector.js b/plugins/LanguagesManager/templates/languageSelector.js
index 4343d998cc..cebd8d54a5 100644
--- a/plugins/LanguagesManager/templates/languageSelector.js
+++ b/plugins/LanguagesManager/templates/languageSelector.js
@@ -5,22 +5,22 @@
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
-$(document).ready(function() {
- // no Language sector on the page
- if($("#languageSelection").size() == 0) return false;
-
+$(document).ready(function () {
+ // no Language sector on the page
+ if ($("#languageSelection").size() == 0) return false;
+
$("#languageSelection input").hide();
var select = $("#language").hide();
- var langSelect = $( "<a>" )
- .insertAfter( select )
- .text( select.children(':selected').text() )
- .autocomplete({
- delay: 0,
- minLength: 0,
- appendTo: '#languageSelection',
- source: function( request, response ) {
- response( select.children( "option" ).map(function() {
- var text = $( this ).text();
+ var langSelect = $("<a>")
+ .insertAfter(select)
+ .text(select.children(':selected').text())
+ .autocomplete({
+ delay: 0,
+ minLength: 0,
+ appendTo: '#languageSelection',
+ source: function (request, response) {
+ response(select.children("option").map(function () {
+ var text = $(this).text();
return {
label: text,
value: this.value,
@@ -28,39 +28,39 @@ $(document).ready(function() {
href: $(this).attr('href'),
option: this
};
- }) );
- },
- select: function( event, ui ) {
- ui.item.option.selected = true;
- if(ui.item.value) {
- langSelect.text(ui.item.label);
- $('#languageSelection form').submit();
- } else if(ui.item.href) {
- window.open(ui.item.href);
+ }));
+ },
+ select: function (event, ui) {
+ ui.item.option.selected = true;
+ if (ui.item.value) {
+ langSelect.text(ui.item.label);
+ $('#languageSelection form').submit();
+ } else if (ui.item.href) {
+ window.open(ui.item.href);
+ }
+ }
+ })
+ .click(function () {
+ // close if already visible
+ if ($(this).autocomplete("widget").is(":visible")) {
+ $(this).autocomplete("close");
+ return;
}
- }
- })
- .click(function() {
- // close if already visible
- if ( $(this).autocomplete( "widget" ).is(":visible") ) {
- $(this).autocomplete("close");
- return;
- }
- // pass empty string as value to search for, displaying all results
- $(this).autocomplete( "search", "" );
- });
+ // pass empty string as value to search for, displaying all results
+ $(this).autocomplete("search", "");
+ });
- langSelect.data( "autocomplete" )._renderItem = function( ul, item ) {
+ langSelect.data("autocomplete")._renderItem = function (ul, item) {
$(ul).attr('id', 'languageSelect');
- return $( "<li></li>" )
- .data( "item.autocomplete", item )
- .append( "<a title=\"" + item.title + "\" href=\"" + $('#languageSelection form').attr('action') + "&language=" + item.value + "\">" + item.label + "</a>" )
- .appendTo( ul );
+ return $("<li></li>")
+ .data("item.autocomplete", item)
+ .append("<a title=\"" + item.title + "\" href=\"" + $('#languageSelection form').attr('action') + "&language=" + item.value + "\">" + item.label + "</a>")
+ .appendTo(ul);
};
- $('body').on('mouseup',function(e){
- if(!$(e.target).parents('#languageSelection').length && !$(e.target).is('#languageSelection') && !$(e.target).parents('#languageSelect').length) {
+ $('body').on('mouseup', function (e) {
+ if (!$(e.target).parents('#languageSelection').length && !$(e.target).is('#languageSelection') && !$(e.target).parents('#languageSelect').length) {
langSelect.autocomplete("close");
}
});
diff --git a/plugins/LanguagesManager/templates/languages.tpl b/plugins/LanguagesManager/templates/languages.tpl
index c1c84e8ffa..9da7b2ff93 100644
--- a/plugins/LanguagesManager/templates/languages.tpl
+++ b/plugins/LanguagesManager/templates/languages.tpl
@@ -1,19 +1,21 @@
<span class="topBarElem" style="padding-right:70px">
<span id="languageSelection" style="position:absolute">
<form action="index.php?module=LanguagesManager&amp;action=saveLanguage" method="post">
- <select name="language" id="language">
- <option title="" value="" href="?module=Proxy&amp;action=redirect&amp;url=http://piwik.org/translations/">{'LanguagesManager_AboutPiwikTranslations'|translate}</option>
- {foreach from=$languages item=language}
- <option value="{$language.code}" {if $language.code == $currentLanguageCode}selected="selected"{/if} title="{$language.name} ({$language.english_name})">{$language.name}</option>
- {/foreach}
- </select>
- {* During installation token_auth is not set *}
- {if !empty($token_auth)}<input type="hidden" name="token_auth" value="{$token_auth}"/>{/if}
- <input type="submit" value="go" />
- </form>
+ <select name="language" id="language">
+ <option title="" value=""
+ href="?module=Proxy&amp;action=redirect&amp;url=http://piwik.org/translations/">{'LanguagesManager_AboutPiwikTranslations'|translate}</option>
+ {foreach from=$languages item=language}
+ <option value="{$language.code}" {if $language.code == $currentLanguageCode}selected="selected"{/if}
+ title="{$language.name} ({$language.english_name})">{$language.name}</option>
+ {/foreach}
+ </select>
+ {* During installation token_auth is not set *}
+ {if !empty($token_auth)}<input type="hidden" name="token_auth" value="{$token_auth}"/>{/if}
+ <input type="submit" value="go"/>
+ </form>
</span>
<script type="text/javascript">
- piwik.languageName = "{$currentLanguageName}";
- </script>
+ piwik.languageName = "{$currentLanguageName}";
+ </script>
</span>
diff --git a/plugins/Live/API.php b/plugins/Live/API.php
index 64fe37c444..e1b2525c1f 100644
--- a/plugins/Live/API.php
+++ b/plugins/Live/API.php
@@ -15,176 +15,173 @@
require_once PIWIK_INCLUDE_PATH . '/plugins/Live/Visitor.php';
/**
- * The Live! API lets you access complete visit level information about your visitors. Combined with the power of <a href='http://piwik.org/docs/analytics-api/segmentation/' target='_blank'>Segmentation</a>,
+ * The Live! API lets you access complete visit level information about your visitors. Combined with the power of <a href='http://piwik.org/docs/analytics-api/segmentation/' target='_blank'>Segmentation</a>,
* you will be able to request visits filtered by any criteria.
- *
- * The method "getLastVisitsDetails" will return extensive data for each visit, which includes: server time, visitId, visitorId,
- * visitorType (new or returning), number of pages, list of all pages (and events, file downloaded and outlinks clicked),
- * custom variables names and values set to this visit, number of goal conversions (and list of all Goal conversions for this visit,
- * with time of conversion, revenue, URL, etc.), but also other attributes such as: days since last visit, days since first visit,
+ *
+ * The method "getLastVisitsDetails" will return extensive data for each visit, which includes: server time, visitId, visitorId,
+ * visitorType (new or returning), number of pages, list of all pages (and events, file downloaded and outlinks clicked),
+ * custom variables names and values set to this visit, number of goal conversions (and list of all Goal conversions for this visit,
+ * with time of conversion, revenue, URL, etc.), but also other attributes such as: days since last visit, days since first visit,
* country, continent, visitor IP,
- * provider, referrer used (referrer name, keyword if it was a search engine, full URL), campaign name and keyword, operating system,
+ * provider, referrer used (referrer name, keyword if it was a search engine, full URL), campaign name and keyword, operating system,
* browser, type of screen, resolution, supported browser plugins (flash, java, silverlight, pdf, etc.), various dates & times format to make
* it easier for API users... and more!
- *
+ *
* With the parameter <a href='http://piwik.org/docs/analytics-api/segmentation/' target='_blank'>'&segment='</a> you can filter the
* returned visits by any criteria (visitor IP, visitor ID, country, keyword used, time of day, etc.).
- *
+ *
* The method "getCounters" is used to return a simple counter: visits, number of actions, number of converted visits, in the last N minutes.
- *
+ *
* See also the documentation about <a href='http://piwik.org/docs/real-time/' target='_blank'>Real time widget and visitor level reports</a> in Piwik.
* @package Piwik_Live
*/
class Piwik_Live_API
{
- static private $instance = null;
- /**
- * @return Piwik_Live_API
- */
- static public function getInstance()
- {
- if (self::$instance == null)
- {
- self::$instance = new self;
- }
- return self::$instance;
- }
-
- /**
- * This will return simple counters, for a given website ID, for visits over the last N minutes
- *
- * @param int Id Site
- * @param int Number of minutes to look back at
- *
- * @return array( visits => N, actions => M, visitsConverted => P )
- */
- public function getCounters($idSite, $lastMinutes, $segment = false)
- {
- Piwik::checkUserHasViewAccess($idSite);
- $lastMinutes = (int)$lastMinutes;
-
- $select = "count(*) as visits,
+ static private $instance = null;
+
+ /**
+ * @return Piwik_Live_API
+ */
+ static public function getInstance()
+ {
+ if (self::$instance == null) {
+ self::$instance = new self;
+ }
+ return self::$instance;
+ }
+
+ /**
+ * This will return simple counters, for a given website ID, for visits over the last N minutes
+ *
+ * @param int Id Site
+ * @param int Number of minutes to look back at
+ *
+ * @return array( visits => N, actions => M, visitsConverted => P )
+ */
+ public function getCounters($idSite, $lastMinutes, $segment = false)
+ {
+ Piwik::checkUserHasViewAccess($idSite);
+ $lastMinutes = (int)$lastMinutes;
+
+ $select = "count(*) as visits,
SUM(log_visit.visit_total_actions) as actions,
SUM(log_visit.visit_goal_converted) as visitsConverted,
COUNT(DISTINCT log_visit.idvisitor) as visitors";
-
- $from = "log_visit";
-
- $where = "log_visit.idsite = ?
+
+ $from = "log_visit";
+
+ $where = "log_visit.idsite = ?
AND log_visit.visit_last_action_time >= ?";
-
- $bind = array(
- $idSite,
- Piwik_Date::factory(time() - $lastMinutes * 60)->toString('Y-m-d H:i:s')
- );
-
- $segment = new Piwik_Segment($segment, $idSite);
- $query = $segment->getSelectQuery($select, $from, $where, $bind);
-
- $data = Piwik_FetchAll($query['sql'], $query['bind']);
-
- // These could be unset for some reasons, ensure they are set to 0
- empty($data[0]['actions']) ? $data[0]['actions'] = 0 : '';
- empty($data[0]['visitsConverted']) ? $data[0]['visitsConverted'] = 0 : '';
- return $data;
- }
-
- /**
- * The same functionnality can be obtained using segment=visitorId==$visitorId with getLastVisitsDetails
- *
- * @deprecated
- * @ignore
- * @param int $visitorId
- * @param int $idSite
- * @param int $filter_limit
- * @return Piwik_DataTable
- */
- public function getLastVisitsForVisitor( $visitorId, $idSite, $filter_limit = 10 )
- {
- $visitorDetails = $this->loadLastVisitorDetailsFromDatabase($idSite, $period = false, $date = false, $segment = false, $filter_limit, $filter_offset = false, $visitorId);
- $table = $this->getCleanedVisitorsFromDetails($visitorDetails, $idSite);
- return $table;
- }
-
- /**
- * Returns the last visits tracked in the specified website
- * You can define any number of filters: none, one, many or all parameters can be defined
- *
- * @param int $idSite Site ID
- * @param bool|string $period Period to restrict to when looking at the logs
- * @param bool|string $date Date to restrict to
- * @param bool|int $segment (optional) Number of visits rows to return
- * @param bool|int $filter_limit (optional) Only return X visits
- * @param bool|int $filter_offset (optional) Skip the first X visits (useful when paginating)
- * @param bool|int $minTimestamp (optional) Minimum timestamp to restrict the query to (useful when paginating or refreshing visits)
- *
- * @return Piwik_DataTable
- */
- public function getLastVisitsDetails( $idSite, $period, $date, $segment = false, $filter_limit = false, $filter_offset = false, $minTimestamp = false )
- {
- if(empty($filter_limit))
- {
- $filter_limit = 10;
- }
- Piwik::checkUserHasViewAccess($idSite);
- $visitorDetails = $this->loadLastVisitorDetailsFromDatabase($idSite, $period, $date, $segment, $filter_limit, $filter_offset, $visitorId = false, $minTimestamp);
- $dataTable = $this->getCleanedVisitorsFromDetails($visitorDetails, $idSite);
-
- return $dataTable;
- }
-
- /**
- * @deprecated
- */
-
- public function getLastVisits( $idSite, $filter_limit = 10, $minTimestamp = false )
- {
- return $this->getLastVisitsDetails($idSite, $period = false, $date = false, $segment = false, $filter_limit, $filter_offset = false, $minTimestamp );
- }
-
- /**
- * For an array of visits, query the list of pages for this visit
- * as well as make the data human readable
- * @param array $visitorDetails
- * @param int $idSite
- * @return Piwik_DataTable
- */
- private function getCleanedVisitorsFromDetails($visitorDetails, $idSite)
- {
- $actionsLimit = Piwik_Config::getInstance()->General['visitor_log_maximum_actions_per_visit'];
-
- $table = new Piwik_DataTable();
-
- $site = new Piwik_Site($idSite);
- $timezone = $site->getTimezone();
- $currencies = Piwik_SitesManager_API::getInstance()->getCurrencySymbols();
- foreach($visitorDetails as $visitorDetail)
- {
- $this->cleanVisitorDetails($visitorDetail, $idSite);
- $visitor = new Piwik_Live_Visitor($visitorDetail);
- $visitorDetailsArray = $visitor->getAllVisitorDetails();
-
- $visitorDetailsArray['siteCurrency'] = $site->getCurrency();
- $visitorDetailsArray['siteCurrencySymbol'] = @$currencies[$site->getCurrency()];
- $visitorDetailsArray['serverTimestamp'] = $visitorDetailsArray['lastActionTimestamp'];
- $dateTimeVisit = Piwik_Date::factory($visitorDetailsArray['lastActionTimestamp'], $timezone);
- $visitorDetailsArray['serverTimePretty'] = $dateTimeVisit->getLocalized('%time%');
- $visitorDetailsArray['serverDatePretty'] = $dateTimeVisit->getLocalized(Piwik_Translate('CoreHome_ShortDateFormat'));
-
- $dateTimeVisitFirstAction = Piwik_Date::factory($visitorDetailsArray['firstActionTimestamp'], $timezone);
- $visitorDetailsArray['serverDatePrettyFirstAction'] = $dateTimeVisitFirstAction->getLocalized(Piwik_Translate('CoreHome_ShortDateFormat'));
- $visitorDetailsArray['serverTimePrettyFirstAction'] = $dateTimeVisitFirstAction->getLocalized('%time%');
-
- $idvisit = $visitorDetailsArray['idVisit'];
-
- $sqlCustomVariables = '';
- for($i = 1; $i <= Piwik_Tracker::MAX_CUSTOM_VARIABLES; $i++)
- {
- $sqlCustomVariables .= ', custom_var_k' . $i . ', custom_var_v' . $i;
- }
- // The second join is a LEFT join to allow returning records that don't have a matching page title
- // eg. Downloads, Outlinks. For these, idaction_name is set to 0
- $sql = "
+
+ $bind = array(
+ $idSite,
+ Piwik_Date::factory(time() - $lastMinutes * 60)->toString('Y-m-d H:i:s')
+ );
+
+ $segment = new Piwik_Segment($segment, $idSite);
+ $query = $segment->getSelectQuery($select, $from, $where, $bind);
+
+ $data = Piwik_FetchAll($query['sql'], $query['bind']);
+
+ // These could be unset for some reasons, ensure they are set to 0
+ empty($data[0]['actions']) ? $data[0]['actions'] = 0 : '';
+ empty($data[0]['visitsConverted']) ? $data[0]['visitsConverted'] = 0 : '';
+ return $data;
+ }
+
+ /**
+ * The same functionnality can be obtained using segment=visitorId==$visitorId with getLastVisitsDetails
+ *
+ * @deprecated
+ * @ignore
+ * @param int $visitorId
+ * @param int $idSite
+ * @param int $filter_limit
+ * @return Piwik_DataTable
+ */
+ public function getLastVisitsForVisitor($visitorId, $idSite, $filter_limit = 10)
+ {
+ $visitorDetails = $this->loadLastVisitorDetailsFromDatabase($idSite, $period = false, $date = false, $segment = false, $filter_limit, $filter_offset = false, $visitorId);
+ $table = $this->getCleanedVisitorsFromDetails($visitorDetails, $idSite);
+ return $table;
+ }
+
+ /**
+ * Returns the last visits tracked in the specified website
+ * You can define any number of filters: none, one, many or all parameters can be defined
+ *
+ * @param int $idSite Site ID
+ * @param bool|string $period Period to restrict to when looking at the logs
+ * @param bool|string $date Date to restrict to
+ * @param bool|int $segment (optional) Number of visits rows to return
+ * @param bool|int $filter_limit (optional) Only return X visits
+ * @param bool|int $filter_offset (optional) Skip the first X visits (useful when paginating)
+ * @param bool|int $minTimestamp (optional) Minimum timestamp to restrict the query to (useful when paginating or refreshing visits)
+ *
+ * @return Piwik_DataTable
+ */
+ public function getLastVisitsDetails($idSite, $period, $date, $segment = false, $filter_limit = false, $filter_offset = false, $minTimestamp = false)
+ {
+ if (empty($filter_limit)) {
+ $filter_limit = 10;
+ }
+ Piwik::checkUserHasViewAccess($idSite);
+ $visitorDetails = $this->loadLastVisitorDetailsFromDatabase($idSite, $period, $date, $segment, $filter_limit, $filter_offset, $visitorId = false, $minTimestamp);
+ $dataTable = $this->getCleanedVisitorsFromDetails($visitorDetails, $idSite);
+
+ return $dataTable;
+ }
+
+ /**
+ * @deprecated
+ */
+
+ public function getLastVisits($idSite, $filter_limit = 10, $minTimestamp = false)
+ {
+ return $this->getLastVisitsDetails($idSite, $period = false, $date = false, $segment = false, $filter_limit, $filter_offset = false, $minTimestamp);
+ }
+
+ /**
+ * For an array of visits, query the list of pages for this visit
+ * as well as make the data human readable
+ * @param array $visitorDetails
+ * @param int $idSite
+ * @return Piwik_DataTable
+ */
+ private function getCleanedVisitorsFromDetails($visitorDetails, $idSite)
+ {
+ $actionsLimit = Piwik_Config::getInstance()->General['visitor_log_maximum_actions_per_visit'];
+
+ $table = new Piwik_DataTable();
+
+ $site = new Piwik_Site($idSite);
+ $timezone = $site->getTimezone();
+ $currencies = Piwik_SitesManager_API::getInstance()->getCurrencySymbols();
+ foreach ($visitorDetails as $visitorDetail) {
+ $this->cleanVisitorDetails($visitorDetail, $idSite);
+ $visitor = new Piwik_Live_Visitor($visitorDetail);
+ $visitorDetailsArray = $visitor->getAllVisitorDetails();
+
+ $visitorDetailsArray['siteCurrency'] = $site->getCurrency();
+ $visitorDetailsArray['siteCurrencySymbol'] = @$currencies[$site->getCurrency()];
+ $visitorDetailsArray['serverTimestamp'] = $visitorDetailsArray['lastActionTimestamp'];
+ $dateTimeVisit = Piwik_Date::factory($visitorDetailsArray['lastActionTimestamp'], $timezone);
+ $visitorDetailsArray['serverTimePretty'] = $dateTimeVisit->getLocalized('%time%');
+ $visitorDetailsArray['serverDatePretty'] = $dateTimeVisit->getLocalized(Piwik_Translate('CoreHome_ShortDateFormat'));
+
+ $dateTimeVisitFirstAction = Piwik_Date::factory($visitorDetailsArray['firstActionTimestamp'], $timezone);
+ $visitorDetailsArray['serverDatePrettyFirstAction'] = $dateTimeVisitFirstAction->getLocalized(Piwik_Translate('CoreHome_ShortDateFormat'));
+ $visitorDetailsArray['serverTimePrettyFirstAction'] = $dateTimeVisitFirstAction->getLocalized('%time%');
+
+ $idvisit = $visitorDetailsArray['idVisit'];
+
+ $sqlCustomVariables = '';
+ for ($i = 1; $i <= Piwik_Tracker::MAX_CUSTOM_VARIABLES; $i++) {
+ $sqlCustomVariables .= ', custom_var_k' . $i . ', custom_var_v' . $i;
+ }
+ // The second join is a LEFT join to allow returning records that don't have a matching page title
+ // eg. Downloads, Outlinks. For these, idaction_name is set to 0
+ $sql = "
SELECT
COALESCE(log_action.type,log_action_title.type) AS type,
log_action.name AS url,
@@ -195,53 +192,48 @@ class Piwik_Live_API
log_link_visit_action.server_time as serverTimePretty,
log_link_visit_action.time_spent_ref_action as timeSpentRef
$sqlCustomVariables
- FROM " .Piwik_Common::prefixTable('log_link_visit_action')." AS log_link_visit_action
- LEFT JOIN " .Piwik_Common::prefixTable('log_action')." AS log_action
+ FROM " . Piwik_Common::prefixTable('log_link_visit_action') . " AS log_link_visit_action
+ LEFT JOIN " . Piwik_Common::prefixTable('log_action') . " AS log_action
ON log_link_visit_action.idaction_url = log_action.idaction
- LEFT JOIN " .Piwik_Common::prefixTable('log_action')." AS log_action_title
+ LEFT JOIN " . Piwik_Common::prefixTable('log_action') . " AS log_action_title
ON log_link_visit_action.idaction_name = log_action_title.idaction
WHERE log_link_visit_action.idvisit = ?
LIMIT 0, $actionsLimit
";
- $actionDetails = Piwik_FetchAll($sql, array($idvisit));
-
- foreach($actionDetails as $actionIdx => &$actionDetail)
- {
- $actionDetail =& $actionDetails[$actionIdx];
- $customVariablesPage = array();
- for($i = 1; $i <= Piwik_Tracker::MAX_CUSTOM_VARIABLES; $i++)
- {
- if(!empty($actionDetail['custom_var_k'.$i]))
- {
- $cvarKey = $actionDetail['custom_var_k'.$i];
- $cvarKey = $this->getCustomVariablePrettyKey($cvarKey);
- $customVariablesPage[$i] = array(
- 'customVariableName'.$i => $cvarKey,
- 'customVariableValue'.$i => $actionDetail['custom_var_v'.$i],
- );
- }
- unset($actionDetail['custom_var_k'.$i]);
- unset($actionDetail['custom_var_v'.$i]);
- }
- if(!empty($customVariablesPage))
- {
- $actionDetail['customVariables'] = $customVariablesPage;
- }
- // reconstruct url from prefix
- $actionDetail['url'] = Piwik_Tracker_Action::reconstructNormalizedUrl($actionDetail['url'], $actionDetail['url_prefix']);
- unset($actionDetail['url_prefix']);
- // set the time spent for this action (which is the timeSpentRef of the next action)
- if (isset($actionDetails[$actionIdx + 1]))
- {
- $actionDetail['timeSpent'] = $actionDetails[$actionIdx + 1]['timeSpentRef'];
- $actionDetail['timeSpentPretty'] = Piwik::getPrettyTimeFromSeconds($actionDetail['timeSpent']);
-
- }
- unset($actionDetails[$actionIdx]['timeSpentRef']); // not needed after timeSpent is added
- }
-
- // If the visitor converted a goal, we shall select all Goals
- $sql = "
+ $actionDetails = Piwik_FetchAll($sql, array($idvisit));
+
+ foreach ($actionDetails as $actionIdx => &$actionDetail) {
+ $actionDetail =& $actionDetails[$actionIdx];
+ $customVariablesPage = array();
+ for ($i = 1; $i <= Piwik_Tracker::MAX_CUSTOM_VARIABLES; $i++) {
+ if (!empty($actionDetail['custom_var_k' . $i])) {
+ $cvarKey = $actionDetail['custom_var_k' . $i];
+ $cvarKey = $this->getCustomVariablePrettyKey($cvarKey);
+ $customVariablesPage[$i] = array(
+ 'customVariableName' . $i => $cvarKey,
+ 'customVariableValue' . $i => $actionDetail['custom_var_v' . $i],
+ );
+ }
+ unset($actionDetail['custom_var_k' . $i]);
+ unset($actionDetail['custom_var_v' . $i]);
+ }
+ if (!empty($customVariablesPage)) {
+ $actionDetail['customVariables'] = $customVariablesPage;
+ }
+ // reconstruct url from prefix
+ $actionDetail['url'] = Piwik_Tracker_Action::reconstructNormalizedUrl($actionDetail['url'], $actionDetail['url_prefix']);
+ unset($actionDetail['url_prefix']);
+ // set the time spent for this action (which is the timeSpentRef of the next action)
+ if (isset($actionDetails[$actionIdx + 1])) {
+ $actionDetail['timeSpent'] = $actionDetails[$actionIdx + 1]['timeSpentRef'];
+ $actionDetail['timeSpentPretty'] = Piwik::getPrettyTimeFromSeconds($actionDetail['timeSpent']);
+
+ }
+ unset($actionDetails[$actionIdx]['timeSpentRef']); // not needed after timeSpent is added
+ }
+
+ // If the visitor converted a goal, we shall select all Goals
+ $sql = "
SELECT
'goal' as type,
goal.name as goalName,
@@ -249,8 +241,8 @@ class Piwik_Live_API
log_conversion.idlink_va as goalPageId,
log_conversion.server_time as serverTimePretty,
log_conversion.url as url
- FROM ".Piwik_Common::prefixTable('log_conversion')." AS log_conversion
- LEFT JOIN ".Piwik_Common::prefixTable('goal')." AS goal
+ FROM " . Piwik_Common::prefixTable('log_conversion') . " AS log_conversion
+ LEFT JOIN " . Piwik_Common::prefixTable('goal') . " AS goal
ON (goal.idsite = log_conversion.idsite
AND
goal.idgoal = log_conversion.idgoal)
@@ -259,293 +251,269 @@ class Piwik_Live_API
AND log_conversion.idgoal > 0
LIMIT 0, $actionsLimit
";
- $goalDetails = Piwik_FetchAll($sql, array($idvisit));
+ $goalDetails = Piwik_FetchAll($sql, array($idvisit));
- $sql = "SELECT
- case idgoal when ".Piwik_Tracker_GoalManager::IDGOAL_CART." then '".Piwik_Archive::LABEL_ECOMMERCE_CART."' else '".Piwik_Archive::LABEL_ECOMMERCE_ORDER."' end as type,
+ $sql = "SELECT
+ case idgoal when " . Piwik_Tracker_GoalManager::IDGOAL_CART . " then '" . Piwik_Archive::LABEL_ECOMMERCE_CART . "' else '" . Piwik_Archive::LABEL_ECOMMERCE_ORDER . "' end as type,
idorder as orderId,
- ".Piwik_ArchiveProcessing_Day::getSqlRevenue('revenue')." as revenue,
- ".Piwik_ArchiveProcessing_Day::getSqlRevenue('revenue_subtotal')." as revenueSubTotal,
- ".Piwik_ArchiveProcessing_Day::getSqlRevenue('revenue_tax')." as revenueTax,
- ".Piwik_ArchiveProcessing_Day::getSqlRevenue('revenue_shipping')." as revenueShipping,
- ".Piwik_ArchiveProcessing_Day::getSqlRevenue('revenue_discount')." as revenueDiscount,
+ " . Piwik_ArchiveProcessing_Day::getSqlRevenue('revenue') . " as revenue,
+ " . Piwik_ArchiveProcessing_Day::getSqlRevenue('revenue_subtotal') . " as revenueSubTotal,
+ " . Piwik_ArchiveProcessing_Day::getSqlRevenue('revenue_tax') . " as revenueTax,
+ " . Piwik_ArchiveProcessing_Day::getSqlRevenue('revenue_shipping') . " as revenueShipping,
+ " . Piwik_ArchiveProcessing_Day::getSqlRevenue('revenue_discount') . " as revenueDiscount,
items as items,
log_conversion.server_time as serverTimePretty
- FROM ".Piwik_Common::prefixTable('log_conversion')." AS log_conversion
+ FROM " . Piwik_Common::prefixTable('log_conversion') . " AS log_conversion
WHERE idvisit = ?
- AND idgoal <= ".Piwik_Tracker_GoalManager::IDGOAL_ORDER."
+ AND idgoal <= " . Piwik_Tracker_GoalManager::IDGOAL_ORDER . "
LIMIT 0, $actionsLimit";
- $ecommerceDetails = Piwik_FetchAll($sql, array($idvisit));
-
- foreach($ecommerceDetails as &$ecommerceDetail)
- {
- if($ecommerceDetail['type'] == Piwik_Archive::LABEL_ECOMMERCE_CART)
- {
- unset($ecommerceDetail['orderId']);
- unset($ecommerceDetail['revenueSubTotal']);
- unset($ecommerceDetail['revenueTax']);
- unset($ecommerceDetail['revenueShipping']);
- unset($ecommerceDetail['revenueDiscount']);
- }
-
- // 25.00 => 25
- foreach($ecommerceDetail as $column => $value)
- {
- if(strpos($column, 'revenue') !== false)
- {
- if($value == round($value))
- {
- $ecommerceDetail[$column] = round($value);
- }
- }
- }
- }
-
- // Enrich ecommerce carts/orders with the list of products
- usort($ecommerceDetails, array($this, 'sortByServerTime'));
- foreach($ecommerceDetails as $key => &$ecommerceConversion)
- {
- $sql = "SELECT
+ $ecommerceDetails = Piwik_FetchAll($sql, array($idvisit));
+
+ foreach ($ecommerceDetails as &$ecommerceDetail) {
+ if ($ecommerceDetail['type'] == Piwik_Archive::LABEL_ECOMMERCE_CART) {
+ unset($ecommerceDetail['orderId']);
+ unset($ecommerceDetail['revenueSubTotal']);
+ unset($ecommerceDetail['revenueTax']);
+ unset($ecommerceDetail['revenueShipping']);
+ unset($ecommerceDetail['revenueDiscount']);
+ }
+
+ // 25.00 => 25
+ foreach ($ecommerceDetail as $column => $value) {
+ if (strpos($column, 'revenue') !== false) {
+ if ($value == round($value)) {
+ $ecommerceDetail[$column] = round($value);
+ }
+ }
+ }
+ }
+
+ // Enrich ecommerce carts/orders with the list of products
+ usort($ecommerceDetails, array($this, 'sortByServerTime'));
+ foreach ($ecommerceDetails as $key => &$ecommerceConversion) {
+ $sql = "SELECT
log_action_sku.name as itemSKU,
log_action_name.name as itemName,
log_action_category.name as itemCategory,
- ".Piwik_ArchiveProcessing_Day::getSqlRevenue('price')." as price,
+ " . Piwik_ArchiveProcessing_Day::getSqlRevenue('price') . " as price,
quantity as quantity
- FROM ".Piwik_Common::prefixTable('log_conversion_item')."
- INNER JOIN " .Piwik_Common::prefixTable('log_action')." AS log_action_sku
+ FROM " . Piwik_Common::prefixTable('log_conversion_item') . "
+ INNER JOIN " . Piwik_Common::prefixTable('log_action') . " AS log_action_sku
ON idaction_sku = log_action_sku.idaction
- LEFT JOIN " .Piwik_Common::prefixTable('log_action')." AS log_action_name
+ LEFT JOIN " . Piwik_Common::prefixTable('log_action') . " AS log_action_name
ON idaction_name = log_action_name.idaction
- LEFT JOIN " .Piwik_Common::prefixTable('log_action')." AS log_action_category
+ LEFT JOIN " . Piwik_Common::prefixTable('log_action') . " AS log_action_category
ON idaction_category = log_action_category.idaction
WHERE idvisit = ?
AND idorder = ?
AND deleted = 0
LIMIT 0, $actionsLimit
";
- $bind = array($idvisit, isset($ecommerceConversion['orderId'])
- ? $ecommerceConversion['orderId']
- : Piwik_Tracker_GoalManager::ITEM_IDORDER_ABANDONED_CART
- );
-
- $itemsDetails = Piwik_FetchAll($sql, $bind);
- foreach($itemsDetails as &$detail)
- {
- if($detail['price'] == round($detail['price']))
- {
- $detail['price'] = round($detail['price']);
- }
- }
- $ecommerceConversion['itemDetails'] = $itemsDetails;
- }
-
- $actions = array_merge($actionDetails, $goalDetails, $ecommerceDetails);
-
- usort($actions, array($this, 'sortByServerTime'));
-
- $visitorDetailsArray['actionDetails'] = $actions;
- // Convert datetimes to the site timezone
- foreach($visitorDetailsArray['actionDetails'] as &$details)
- {
- switch($details['type'])
- {
- case 'goal':
- $details['icon'] = 'themes/default/images/goal.png';
- break;
- case Piwik_Archive::LABEL_ECOMMERCE_ORDER:
- case Piwik_Archive::LABEL_ECOMMERCE_CART:
- $details['icon'] = 'themes/default/images/'.$details['type'].'.gif';
- break;
- case Piwik_Tracker_Action_Interface::TYPE_DOWNLOAD:
- $details['type'] = 'download';
- $details['icon'] = 'themes/default/images/download.png';
- break;
- case Piwik_Tracker_Action_Interface::TYPE_OUTLINK:
- $details['type'] = 'outlink';
- $details['icon'] = 'themes/default/images/link.gif';
- break;
- case Piwik_Tracker_Action::TYPE_SITE_SEARCH:
- $details['type'] = 'search';
- $details['icon'] = 'themes/default/images/search_ico.png';
- break;
- default:
- $details['type'] = 'action';
- $details['icon'] = null;
- break;
- }
- $dateTimeVisit = Piwik_Date::factory($details['serverTimePretty'], $timezone);
- $details['serverTimePretty'] = $dateTimeVisit->getLocalized(Piwik_Translate('CoreHome_ShortDateFormat') .' %time%');
- }
- $visitorDetailsArray['goalConversions'] = count($goalDetails);
-
- $table->addRowFromArray( array(Piwik_DataTable_Row::COLUMNS => $visitorDetailsArray));
- }
- return $table;
- }
-
- private function getCustomVariablePrettyKey($key)
- {
- $rename = array(
- Piwik_Tracker_Action::CVAR_KEY_SEARCH_CATEGORY => Piwik_Translate('Actions_ColumnSearchCategory'),
- Piwik_Tracker_Action::CVAR_KEY_SEARCH_COUNT => Piwik_Translate('Actions_ColumnSearchResultsCount'),
- );
- if(isset($rename[$key])) {
- return $rename[$key];
- }
- return $key;
- }
-
- private function sortByServerTime($a, $b)
- {
- $ta = strtotime($a['serverTimePretty']);
- $tb = strtotime($b['serverTimePretty']);
- return $ta < $tb
- ? -1
- : ($ta == $tb
- ? 0
- : 1 );
- }
-
- private function loadLastVisitorDetailsFromDatabase($idSite, $period = false, $date = false, $segment = false, $filter_limit = false, $filter_offset = false, $visitorId = false, $minTimestamp = false)
- {
- if(empty($filter_limit))
- {
- $filter_limit = 100;
- }
- if(empty($filter_offset))
- {
- $filter_offset = 0;
- }
-
- $where = $whereBind = array();
- $where[] = "log_visit.idsite = ? ";
- $whereBind[] = $idSite;
- $orderBy = "idsite, visit_last_action_time DESC";
- $orderByParent = "sub.visit_last_action_time DESC";
- if(!empty($visitorId))
- {
- $where[] = "log_visit.idvisitor = ? ";
- $whereBind[] = @Piwik_Common::hex2bin($visitorId);
- }
-
- if(!empty($minTimestamp))
- {
- $where[] = "log_visit.visit_last_action_time > ? ";
- $whereBind[] = date("Y-m-d H:i:s", $minTimestamp);
- }
-
- // If no other filter, only look at the last 24 hours of stats
- if(empty($visitorId)
- && empty($filter_offset)
- && empty($period)
- && empty($date))
- {
- $period = 'day';
- $date = 'yesterdaySameTime';
- }
-
- // SQL Filter with provided period
- if (!empty($period) && !empty($date))
- {
- $currentSite = new Piwik_Site($idSite);
- $currentTimezone = $currentSite->getTimezone();
-
- $dateString = $date;
- if($period == 'range')
- {
- $processedPeriod = new Piwik_Period_Range('range', $date);
- if($parsedDate = Piwik_Period_Range::parseDateRange($date))
- {
- $dateString = $parsedDate[2];
- }
- }
- else
- {
- $processedDate = Piwik_Date::factory($date);
- if($date == 'today'
- || $date == 'now'
- || $processedDate->toString() == Piwik_Date::factory('now', $currentTimezone)->toString())
- {
- $processedDate = $processedDate->subDay(1);
- }
- $processedPeriod = Piwik_Period::factory($period, $processedDate);
- }
- $dateStart = $processedPeriod->getDateStart()->setTimezone($currentTimezone);
- $where[] = "log_visit.visit_last_action_time >= ?";
- $whereBind[] = $dateStart->toString('Y-m-d H:i:s');
-
- if(!in_array($date, array('now', 'today', 'yesterdaySameTime'))
- && strpos($date, 'last') === false
- && strpos($date, 'previous') === false
- && Piwik_Date::factory($dateString)->toString('Y-m-d') != Piwik_Date::factory('now', $currentTimezone)->toString())
- {
- $dateEnd = $processedPeriod->getDateEnd()->setTimezone($currentTimezone);
- $where[] = " log_visit.visit_last_action_time <= ?";
- $dateEndString = $dateEnd->addDay(1)->toString('Y-m-d H:i:s');
- $whereBind[] = $dateEndString;
- }
- }
-
- if(count($where) > 0)
- {
- $where = join("
+ $bind = array($idvisit, isset($ecommerceConversion['orderId'])
+ ? $ecommerceConversion['orderId']
+ : Piwik_Tracker_GoalManager::ITEM_IDORDER_ABANDONED_CART
+ );
+
+ $itemsDetails = Piwik_FetchAll($sql, $bind);
+ foreach ($itemsDetails as &$detail) {
+ if ($detail['price'] == round($detail['price'])) {
+ $detail['price'] = round($detail['price']);
+ }
+ }
+ $ecommerceConversion['itemDetails'] = $itemsDetails;
+ }
+
+ $actions = array_merge($actionDetails, $goalDetails, $ecommerceDetails);
+
+ usort($actions, array($this, 'sortByServerTime'));
+
+ $visitorDetailsArray['actionDetails'] = $actions;
+ // Convert datetimes to the site timezone
+ foreach ($visitorDetailsArray['actionDetails'] as &$details) {
+ switch ($details['type']) {
+ case 'goal':
+ $details['icon'] = 'themes/default/images/goal.png';
+ break;
+ case Piwik_Archive::LABEL_ECOMMERCE_ORDER:
+ case Piwik_Archive::LABEL_ECOMMERCE_CART:
+ $details['icon'] = 'themes/default/images/' . $details['type'] . '.gif';
+ break;
+ case Piwik_Tracker_Action_Interface::TYPE_DOWNLOAD:
+ $details['type'] = 'download';
+ $details['icon'] = 'themes/default/images/download.png';
+ break;
+ case Piwik_Tracker_Action_Interface::TYPE_OUTLINK:
+ $details['type'] = 'outlink';
+ $details['icon'] = 'themes/default/images/link.gif';
+ break;
+ case Piwik_Tracker_Action::TYPE_SITE_SEARCH:
+ $details['type'] = 'search';
+ $details['icon'] = 'themes/default/images/search_ico.png';
+ break;
+ default:
+ $details['type'] = 'action';
+ $details['icon'] = null;
+ break;
+ }
+ $dateTimeVisit = Piwik_Date::factory($details['serverTimePretty'], $timezone);
+ $details['serverTimePretty'] = $dateTimeVisit->getLocalized(Piwik_Translate('CoreHome_ShortDateFormat') . ' %time%');
+ }
+ $visitorDetailsArray['goalConversions'] = count($goalDetails);
+
+ $table->addRowFromArray(array(Piwik_DataTable_Row::COLUMNS => $visitorDetailsArray));
+ }
+ return $table;
+ }
+
+ private function getCustomVariablePrettyKey($key)
+ {
+ $rename = array(
+ Piwik_Tracker_Action::CVAR_KEY_SEARCH_CATEGORY => Piwik_Translate('Actions_ColumnSearchCategory'),
+ Piwik_Tracker_Action::CVAR_KEY_SEARCH_COUNT => Piwik_Translate('Actions_ColumnSearchResultsCount'),
+ );
+ if (isset($rename[$key])) {
+ return $rename[$key];
+ }
+ return $key;
+ }
+
+ private function sortByServerTime($a, $b)
+ {
+ $ta = strtotime($a['serverTimePretty']);
+ $tb = strtotime($b['serverTimePretty']);
+ return $ta < $tb
+ ? -1
+ : ($ta == $tb
+ ? 0
+ : 1);
+ }
+
+ private function loadLastVisitorDetailsFromDatabase($idSite, $period = false, $date = false, $segment = false, $filter_limit = false, $filter_offset = false, $visitorId = false, $minTimestamp = false)
+ {
+ if (empty($filter_limit)) {
+ $filter_limit = 100;
+ }
+ if (empty($filter_offset)) {
+ $filter_offset = 0;
+ }
+
+ $where = $whereBind = array();
+ $where[] = "log_visit.idsite = ? ";
+ $whereBind[] = $idSite;
+ $orderBy = "idsite, visit_last_action_time DESC";
+ $orderByParent = "sub.visit_last_action_time DESC";
+ if (!empty($visitorId)) {
+ $where[] = "log_visit.idvisitor = ? ";
+ $whereBind[] = @Piwik_Common::hex2bin($visitorId);
+ }
+
+ if (!empty($minTimestamp)) {
+ $where[] = "log_visit.visit_last_action_time > ? ";
+ $whereBind[] = date("Y-m-d H:i:s", $minTimestamp);
+ }
+
+ // If no other filter, only look at the last 24 hours of stats
+ if (empty($visitorId)
+ && empty($filter_offset)
+ && empty($period)
+ && empty($date)
+ ) {
+ $period = 'day';
+ $date = 'yesterdaySameTime';
+ }
+
+ // SQL Filter with provided period
+ if (!empty($period) && !empty($date)) {
+ $currentSite = new Piwik_Site($idSite);
+ $currentTimezone = $currentSite->getTimezone();
+
+ $dateString = $date;
+ if ($period == 'range') {
+ $processedPeriod = new Piwik_Period_Range('range', $date);
+ if ($parsedDate = Piwik_Period_Range::parseDateRange($date)) {
+ $dateString = $parsedDate[2];
+ }
+ } else {
+ $processedDate = Piwik_Date::factory($date);
+ if ($date == 'today'
+ || $date == 'now'
+ || $processedDate->toString() == Piwik_Date::factory('now', $currentTimezone)->toString()
+ ) {
+ $processedDate = $processedDate->subDay(1);
+ }
+ $processedPeriod = Piwik_Period::factory($period, $processedDate);
+ }
+ $dateStart = $processedPeriod->getDateStart()->setTimezone($currentTimezone);
+ $where[] = "log_visit.visit_last_action_time >= ?";
+ $whereBind[] = $dateStart->toString('Y-m-d H:i:s');
+
+ if (!in_array($date, array('now', 'today', 'yesterdaySameTime'))
+ && strpos($date, 'last') === false
+ && strpos($date, 'previous') === false
+ && Piwik_Date::factory($dateString)->toString('Y-m-d') != Piwik_Date::factory('now', $currentTimezone)->toString()
+ ) {
+ $dateEnd = $processedPeriod->getDateEnd()->setTimezone($currentTimezone);
+ $where[] = " log_visit.visit_last_action_time <= ?";
+ $dateEndString = $dateEnd->addDay(1)->toString('Y-m-d H:i:s');
+ $whereBind[] = $dateEndString;
+ }
+ }
+
+ if (count($where) > 0) {
+ $where = join("
AND ", $where);
- }
- else
- {
- $where = false;
- }
-
- $segment = new Piwik_Segment($segment, $idSite);
-
- // Subquery to use the indexes for ORDER BY
- $select = "log_visit.*";
- $from = "log_visit";
- $subQuery = $segment->getSelectQuery($select, $from, $where, $whereBind, $orderBy);
-
- $sqlLimit = $filter_limit >= 1 ? " LIMIT ".$filter_offset.", ".(int)$filter_limit : "";
-
- // Group by idvisit so that a visitor converting 2 goals only appears once
- $sql = "
+ } else {
+ $where = false;
+ }
+
+ $segment = new Piwik_Segment($segment, $idSite);
+
+ // Subquery to use the indexes for ORDER BY
+ $select = "log_visit.*";
+ $from = "log_visit";
+ $subQuery = $segment->getSelectQuery($select, $from, $where, $whereBind, $orderBy);
+
+ $sqlLimit = $filter_limit >= 1 ? " LIMIT " . $filter_offset . ", " . (int)$filter_limit : "";
+
+ // Group by idvisit so that a visitor converting 2 goals only appears once
+ $sql = "
SELECT sub.*
FROM (
- ".$subQuery['sql']."
+ " . $subQuery['sql'] . "
$sqlLimit
) AS sub
GROUP BY sub.idvisit
ORDER BY $orderByParent
";
-
- try {
- $data = Piwik_FetchAll($sql, $subQuery['bind']);
- } catch(Exception $e) {
- echo $e->getMessage();exit;
- }
-
- return $data;
- }
-
- /**
- * Removes fields that are not meant to be displayed (md5 config hash)
- * Or that the user should only access if he is super user or admin (cookie, IP)
- *
- * @return void
- */
- private function cleanVisitorDetails( &$visitorDetails, $idSite )
- {
- $toUnset = array('config_id');
- if(Piwik::isUserIsAnonymous())
- {
- $toUnset[] = 'idvisitor';
- $toUnset[] = 'location_ip';
- }
- foreach($toUnset as $keyName)
- {
- if(isset($visitorDetails[$keyName]))
- {
- unset($visitorDetails[$keyName]);
- }
- }
- }
+
+ try {
+ $data = Piwik_FetchAll($sql, $subQuery['bind']);
+ } catch (Exception $e) {
+ echo $e->getMessage();
+ exit;
+ }
+
+ return $data;
+ }
+
+ /**
+ * Removes fields that are not meant to be displayed (md5 config hash)
+ * Or that the user should only access if he is super user or admin (cookie, IP)
+ *
+ * @return void
+ */
+ private function cleanVisitorDetails(&$visitorDetails, $idSite)
+ {
+ $toUnset = array('config_id');
+ if (Piwik::isUserIsAnonymous()) {
+ $toUnset[] = 'idvisitor';
+ $toUnset[] = 'location_ip';
+ }
+ foreach ($toUnset as $keyName) {
+ if (isset($visitorDetails[$keyName])) {
+ unset($visitorDetails[$keyName]);
+ }
+ }
+ }
}
diff --git a/plugins/Live/Controller.php b/plugins/Live/Controller.php
index 815d2701a4..40f2300678 100644
--- a/plugins/Live/Controller.php
+++ b/plugins/Live/Controller.php
@@ -14,133 +14,132 @@
*/
class Piwik_Live_Controller extends Piwik_Controller
{
- const SIMPLE_VISIT_COUNT_WIDGET_LAST_MINUTES_CONFIG_KEY = 'live_widget_visitor_count_last_minutes';
-
- function index($fetch = false)
- {
- return $this->widget($fetch);
- }
-
- public function widget($fetch = false)
- {
- $view = Piwik_View::factory('index');
- $view->idSite = $this->idSite;
- $view = $this->setCounters($view);
- $view->liveRefreshAfterMs = (int)Piwik_Config::getInstance()->General['live_widget_refresh_after_seconds'] * 1000;
- $view->visitors = $this->getLastVisitsStart($fetchPlease = true);
- $view->liveTokenAuth = Piwik::getCurrentUserTokenAuth();
- return $this->render($view, $fetch);
- }
-
- public function getSimpleLastVisitCount( $fetch = false )
- {
- $lastMinutes = Piwik_Config::getInstance()->General[self::SIMPLE_VISIT_COUNT_WIDGET_LAST_MINUTES_CONFIG_KEY];
-
- $lastNData = Piwik_API_Request::processRequest('Live.getCounters', array('lastMinutes' => $lastMinutes));
-
- $view = Piwik_View::factory('simpleLastVisitCount');
- $view->lastMinutes = $lastMinutes;
- $view->visitors = Piwik::getPrettyNumber($lastNData[0]['visitors']);
- $view->visits = Piwik::getPrettyNumber($lastNData[0]['visits']);
- $view->actions = Piwik::getPrettyNumber($lastNData[0]['actions']);
- $view->refreshAfterXSecs = Piwik_Config::getInstance()->General['live_widget_refresh_after_seconds'];
- $view->translations = array(
- 'one_visitor' => Piwik_Translate('Live_NbVisitor'),
- 'visitors' => Piwik_Translate('Live_NbVisitors'),
- 'one_visit' => Piwik_Translate('General_OneVisit'),
- 'visits' => Piwik_Translate('General_NVisits'),
- 'one_action' => Piwik_Translate('General_OneAction'),
- 'actions' => Piwik_Translate('VisitsSummary_NbActionsDescription'),
- 'one_minute' => Piwik_Translate('General_OneMinute'),
- 'minutes' => Piwik_Translate('General_NMinutes')
- );
- return $this->render($view, $fetch);
- }
-
- public function ajaxTotalVisitors($fetch = false)
- {
- $view = Piwik_View::factory('totalVisits');
- $view = $this->setCounters($view);
- $view->idSite = $this->idSite;
- return $this->render($view, $fetch);
- }
-
- private function render($view, $fetch)
- {
- $rendered = $view->render();
- if($fetch) {
- return $rendered;
- }
- echo $rendered;
- }
-
- public function getVisitorLog($fetch = false)
- {
- $view = Piwik_ViewDataTable::factory();
- $view->init( $this->pluginName,
- __FUNCTION__,
- 'Live.getLastVisitsDetails'
- );
- $view->disableGenericFilters();
- $view->disableSort();
- $view->setTemplate("Live/templates/visitorLog.tpl");
- $view->setSortedColumn('idVisit', 'ASC');
- $view->disableSearchBox();
- $view->setLimit(20);
- $view->disableOffsetInformation();
- $view->disableExcludeLowPopulation();
-
- // disable the tag cloud, pie charts, bar chart icons
- $view->disableShowAllViewsIcons();
- // disable the button "show more datas"
- $view->disableShowAllColumns();
- // disable the RSS feed
- $view->disableShowExportAsRssFeed();
-
- // disable all row actions
- if ($view instanceof Piwik_ViewDataTable_HtmlTable)
- {
- $view->disableRowActions();
- }
-
- $view->setReportDocumentation(Piwik_Translate('Live_VisitorLogDocumentation', array('<br />', '<br />')));
-
- // set a very high row count so that the next link in the footer of the data table is always shown
- $view->setCustomParameter('totalRows', 10000000);
-
- $view->setCustomParameter('filterEcommerce', Piwik_Common::getRequestVar('filterEcommerce', 0, 'int'));
- $view->setCustomParameter('pageUrlNotDefined', Piwik_Translate('General_NotDefined', Piwik_Translate('Actions_ColumnPageURL')));
-
- return $this->renderView($view, $fetch);
- }
-
- public function getLastVisitsStart($fetch = false)
- {
- // hack, ensure we load today's visits by default
- $_GET['date'] = 'today';
- $_GET['period'] = 'day';
- $view = Piwik_View::factory('lastVisits');
- $view->idSite = $this->idSite;
-
- $api = new Piwik_API_Request("method=Live.getLastVisitsDetails&idSite=$this->idSite&filter_limit=10&format=php&serialize=0&disable_generic_filters=1");
- $visitors = $api->process();
- $view->visitors = $visitors;
-
- return $this->render($view, $fetch);
- }
-
- private function setCounters($view)
- {
- $segment = Piwik_Common::getRequestVar('segment', false, 'string');
- $last30min = Piwik_Live_API::getInstance()->getCounters($this->idSite, $lastMinutes = 30, $segment);
- $last30min = $last30min[0];
- $today = Piwik_Live_API::getInstance()->getCounters($this->idSite, $lastMinutes = 24*60, $segment);
- $today = $today[0];
- $view->visitorsCountHalfHour = $last30min['visits'];
- $view->visitorsCountToday = $today['visits'];
- $view->pisHalfhour = $last30min['actions'];
- $view->pisToday = $today['actions'];
- return $view;
- }
+ const SIMPLE_VISIT_COUNT_WIDGET_LAST_MINUTES_CONFIG_KEY = 'live_widget_visitor_count_last_minutes';
+
+ function index($fetch = false)
+ {
+ return $this->widget($fetch);
+ }
+
+ public function widget($fetch = false)
+ {
+ $view = Piwik_View::factory('index');
+ $view->idSite = $this->idSite;
+ $view = $this->setCounters($view);
+ $view->liveRefreshAfterMs = (int)Piwik_Config::getInstance()->General['live_widget_refresh_after_seconds'] * 1000;
+ $view->visitors = $this->getLastVisitsStart($fetchPlease = true);
+ $view->liveTokenAuth = Piwik::getCurrentUserTokenAuth();
+ return $this->render($view, $fetch);
+ }
+
+ public function getSimpleLastVisitCount($fetch = false)
+ {
+ $lastMinutes = Piwik_Config::getInstance()->General[self::SIMPLE_VISIT_COUNT_WIDGET_LAST_MINUTES_CONFIG_KEY];
+
+ $lastNData = Piwik_API_Request::processRequest('Live.getCounters', array('lastMinutes' => $lastMinutes));
+
+ $view = Piwik_View::factory('simpleLastVisitCount');
+ $view->lastMinutes = $lastMinutes;
+ $view->visitors = Piwik::getPrettyNumber($lastNData[0]['visitors']);
+ $view->visits = Piwik::getPrettyNumber($lastNData[0]['visits']);
+ $view->actions = Piwik::getPrettyNumber($lastNData[0]['actions']);
+ $view->refreshAfterXSecs = Piwik_Config::getInstance()->General['live_widget_refresh_after_seconds'];
+ $view->translations = array(
+ 'one_visitor' => Piwik_Translate('Live_NbVisitor'),
+ 'visitors' => Piwik_Translate('Live_NbVisitors'),
+ 'one_visit' => Piwik_Translate('General_OneVisit'),
+ 'visits' => Piwik_Translate('General_NVisits'),
+ 'one_action' => Piwik_Translate('General_OneAction'),
+ 'actions' => Piwik_Translate('VisitsSummary_NbActionsDescription'),
+ 'one_minute' => Piwik_Translate('General_OneMinute'),
+ 'minutes' => Piwik_Translate('General_NMinutes')
+ );
+ return $this->render($view, $fetch);
+ }
+
+ public function ajaxTotalVisitors($fetch = false)
+ {
+ $view = Piwik_View::factory('totalVisits');
+ $view = $this->setCounters($view);
+ $view->idSite = $this->idSite;
+ return $this->render($view, $fetch);
+ }
+
+ private function render($view, $fetch)
+ {
+ $rendered = $view->render();
+ if ($fetch) {
+ return $rendered;
+ }
+ echo $rendered;
+ }
+
+ public function getVisitorLog($fetch = false)
+ {
+ $view = Piwik_ViewDataTable::factory();
+ $view->init($this->pluginName,
+ __FUNCTION__,
+ 'Live.getLastVisitsDetails'
+ );
+ $view->disableGenericFilters();
+ $view->disableSort();
+ $view->setTemplate("Live/templates/visitorLog.tpl");
+ $view->setSortedColumn('idVisit', 'ASC');
+ $view->disableSearchBox();
+ $view->setLimit(20);
+ $view->disableOffsetInformation();
+ $view->disableExcludeLowPopulation();
+
+ // disable the tag cloud, pie charts, bar chart icons
+ $view->disableShowAllViewsIcons();
+ // disable the button "show more datas"
+ $view->disableShowAllColumns();
+ // disable the RSS feed
+ $view->disableShowExportAsRssFeed();
+
+ // disable all row actions
+ if ($view instanceof Piwik_ViewDataTable_HtmlTable) {
+ $view->disableRowActions();
+ }
+
+ $view->setReportDocumentation(Piwik_Translate('Live_VisitorLogDocumentation', array('<br />', '<br />')));
+
+ // set a very high row count so that the next link in the footer of the data table is always shown
+ $view->setCustomParameter('totalRows', 10000000);
+
+ $view->setCustomParameter('filterEcommerce', Piwik_Common::getRequestVar('filterEcommerce', 0, 'int'));
+ $view->setCustomParameter('pageUrlNotDefined', Piwik_Translate('General_NotDefined', Piwik_Translate('Actions_ColumnPageURL')));
+
+ return $this->renderView($view, $fetch);
+ }
+
+ public function getLastVisitsStart($fetch = false)
+ {
+ // hack, ensure we load today's visits by default
+ $_GET['date'] = 'today';
+ $_GET['period'] = 'day';
+ $view = Piwik_View::factory('lastVisits');
+ $view->idSite = $this->idSite;
+
+ $api = new Piwik_API_Request("method=Live.getLastVisitsDetails&idSite=$this->idSite&filter_limit=10&format=php&serialize=0&disable_generic_filters=1");
+ $visitors = $api->process();
+ $view->visitors = $visitors;
+
+ return $this->render($view, $fetch);
+ }
+
+ private function setCounters($view)
+ {
+ $segment = Piwik_Common::getRequestVar('segment', false, 'string');
+ $last30min = Piwik_Live_API::getInstance()->getCounters($this->idSite, $lastMinutes = 30, $segment);
+ $last30min = $last30min[0];
+ $today = Piwik_Live_API::getInstance()->getCounters($this->idSite, $lastMinutes = 24 * 60, $segment);
+ $today = $today[0];
+ $view->visitorsCountHalfHour = $last30min['visits'];
+ $view->visitorsCountToday = $today['visits'];
+ $view->pisHalfhour = $last30min['actions'];
+ $view->pisToday = $today['actions'];
+ return $view;
+ }
}
diff --git a/plugins/Live/Live.php b/plugins/Live/Live.php
index 4f51435854..4a505d0ad2 100644
--- a/plugins/Live/Live.php
+++ b/plugins/Live/Live.php
@@ -15,56 +15,56 @@
*/
class Piwik_Live extends Piwik_Plugin
{
- public function getInformation()
- {
- return array(
- 'description' => Piwik_Translate('Live_PluginDescription'),
- 'author' => 'Piwik',
- 'author_homepage' => 'http://piwik.org/',
- 'version' => Piwik_Version::VERSION,
- );
- }
+ public function getInformation()
+ {
+ return array(
+ 'description' => Piwik_Translate('Live_PluginDescription'),
+ 'author' => 'Piwik',
+ 'author_homepage' => 'http://piwik.org/',
+ 'version' => Piwik_Version::VERSION,
+ );
+ }
- function getListHooksRegistered()
- {
- return array(
- 'AssetManager.getJsFiles' => 'getJsFiles',
- 'AssetManager.getCssFiles' => 'getCssFiles',
- 'WidgetsList.add' => 'addWidget',
- 'Menu.add' => 'addMenu',
- );
- }
+ function getListHooksRegistered()
+ {
+ return array(
+ 'AssetManager.getJsFiles' => 'getJsFiles',
+ 'AssetManager.getCssFiles' => 'getCssFiles',
+ 'WidgetsList.add' => 'addWidget',
+ 'Menu.add' => 'addMenu',
+ );
+ }
- /**
- * @param Piwik_Event_Notification $notification notification object
- */
- function getCssFiles( $notification )
- {
- $cssFiles = &$notification->getNotificationObject();
-
- $cssFiles[] = "plugins/Live/templates/live.css";
- }
+ /**
+ * @param Piwik_Event_Notification $notification notification object
+ */
+ function getCssFiles($notification)
+ {
+ $cssFiles = & $notification->getNotificationObject();
- /**
- * @param Piwik_Event_Notification $notification notification object
- */
- function getJsFiles( $notification )
- {
- $jsFiles = &$notification->getNotificationObject();
-
- $jsFiles[] = "plugins/Live/templates/scripts/live.js";
- }
+ $cssFiles[] = "plugins/Live/templates/live.css";
+ }
- function addMenu()
- {
- Piwik_AddMenu('General_Visitors', 'Live_VisitorLog', array('module' => 'Live', 'action' => 'getVisitorLog'), true, $order = 5);
- }
+ /**
+ * @param Piwik_Event_Notification $notification notification object
+ */
+ function getJsFiles($notification)
+ {
+ $jsFiles = & $notification->getNotificationObject();
- public function addWidget()
- {
- Piwik_AddWidget('Live!', 'Live_VisitorsInRealTime', 'Live', 'widget');
- Piwik_AddWidget('Live!', 'Live_VisitorLog', 'Live', 'getVisitorLog');
- Piwik_AddWidget('Live!', 'Live_RealTimeVisitorCount', 'Live', 'getSimpleLastVisitCount');
- }
+ $jsFiles[] = "plugins/Live/templates/scripts/live.js";
+ }
+
+ function addMenu()
+ {
+ Piwik_AddMenu('General_Visitors', 'Live_VisitorLog', array('module' => 'Live', 'action' => 'getVisitorLog'), true, $order = 5);
+ }
+
+ public function addWidget()
+ {
+ Piwik_AddWidget('Live!', 'Live_VisitorsInRealTime', 'Live', 'widget');
+ Piwik_AddWidget('Live!', 'Live_VisitorLog', 'Live', 'getVisitorLog');
+ Piwik_AddWidget('Live!', 'Live_RealTimeVisitorCount', 'Live', 'getSimpleLastVisitCount');
+ }
}
diff --git a/plugins/Live/Visitor.php b/plugins/Live/Visitor.php
index c42ff66233..249a98349d 100644
--- a/plugins/Live/Visitor.php
+++ b/plugins/Live/Visitor.php
@@ -27,518 +27,500 @@ require_once PIWIK_INCLUDE_PATH . '/plugins/Provider/functions.php';
*/
class Piwik_Live_Visitor
{
- const DELIMITER_PLUGIN_NAME = ", ";
-
- function __construct($visitorRawData)
- {
- $this->details = $visitorRawData;
- }
-
- function getAllVisitorDetails()
- {
- return array(
- 'idSite' => $this->getIdSite(),
- 'idVisit' => $this->getIdVisit(),
- 'visitIp' => $this->getIp(),
- 'visitorId' => $this->getVisitorId(),
- 'visitorType' => $this->getVisitorReturning(),
- 'visitorTypeIcon' => $this->getVisitorReturningIcon(),
- 'visitConverted' => $this->isVisitorGoalConverted(),
- 'visitConvertedIcon' => $this->getVisitorGoalConvertedIcon(),
- 'visitEcommerceStatus' => $this->getVisitEcommerceStatus(),
- 'visitEcommerceStatusIcon' => $this->getVisitEcommerceStatusIcon(),
-
- 'searches' => $this->getNumberOfSearches(),
- 'actions' => $this->getNumberOfActions(),
- // => false are placeholders to be filled in API later
- 'actionDetails' => false,
- 'customVariables' => $this->getCustomVariables(),
- 'goalConversions' => false,
- 'siteCurrency' => false,
- 'siteCurrencySymbol' => false,
-
- // all time entries
- 'serverDate' => $this->getServerDate(),
- 'visitLocalTime' => $this->getVisitLocalTime(),
- 'firstActionTimestamp' => $this->getTimestampFirstAction(),
- 'lastActionTimestamp' => $this->getTimestampLastAction(),
- 'lastActionDateTime' => $this->getDateTimeLastAction(),
-
- // standard attributes
- 'visitDuration' => $this->getVisitLength(),
- 'visitDurationPretty' => $this->getVisitLengthPretty(),
- 'visitCount' => $this->getVisitCount(),
- 'daysSinceLastVisit' => $this->getDaysSinceLastVisit(),
- 'daysSinceFirstVisit' => $this->getDaysSinceFirstVisit(),
- 'daysSinceLastEcommerceOrder' => $this->getDaysSinceLastEcommerceOrder(),
- 'continent' => $this->getContinent(),
- 'continentCode' => $this->getContinentCode(),
- 'country' => $this->getCountryName(),
- 'countryCode' => $this->getCountryCode(),
- 'countryFlag' => $this->getCountryFlag(),
- 'region' => $this->getRegionName(),
- 'city' => $this->getCityName(),
- 'location' => $this->getPrettyLocation(),
- 'latitude' => $this->getLatitude(),
- 'longitude' => $this->getLongitude(),
- 'provider' => $this->getProvider(),
- 'providerUrl' => $this->getProviderUrl(),
-
- 'referrerType' => $this->getRefererType(),
- 'referrerTypeName' => $this->getRefererTypeName(),
- 'referrerName' => $this->getRefererName(),
- 'referrerKeyword' => $this->getKeyword(),
- 'referrerKeywordPosition' => $this->getKeywordPosition(),
- 'referrerUrl' => $this->getRefererUrl(),
- 'referrerSearchEngineUrl' => $this->getSearchEngineUrl(),
- 'referrerSearchEngineIcon' => $this->getSearchEngineIcon(),
- 'operatingSystem' => $this->getOperatingSystem(),
- 'operatingSystemShortName' => $this->getOperatingSystemShortName(),
- 'operatingSystemIcon' => $this->getOperatingSystemIcon(),
- 'browserFamily' => $this->getBrowserFamily(),
- 'browserFamilyDescription' => $this->getBrowserFamilyDescription(),
- 'browserName' => $this->getBrowser(),
- 'browserIcon' => $this->getBrowserIcon(),
- 'screenType' => $this->getScreenType(),
- 'resolution' => $this->getResolution(),
- 'screenTypeIcon' => $this->getScreenTypeIcon(),
- 'plugins' => $this->getPlugins(),
- 'pluginsIcons' => $this->getPluginIcons(),
- );
- }
-
- function getVisitorId()
- {
- if(isset($this->details['idvisitor']))
- {
- return bin2hex($this->details['idvisitor']);
- }
- return false;
- }
-
- function getVisitLocalTime()
- {
- return $this->details['visitor_localtime'];
- }
-
- function getVisitCount()
- {
- return $this->details['visitor_count_visits'];
- }
-
- function getDaysSinceLastVisit()
- {
- return $this->details['visitor_days_since_last'];
- }
-
- function getDaysSinceLastEcommerceOrder()
- {
- return $this->details['visitor_days_since_order'];
- }
- function getDaysSinceFirstVisit()
- {
- return $this->details['visitor_days_since_first'];
- }
-
- function getServerDate()
- {
- return date('Y-m-d', strtotime($this->details['visit_last_action_time']));
- }
-
- function getIp()
- {
- if(isset($this->details['location_ip']))
- {
- return Piwik_IP::N2P($this->details['location_ip']);
- }
- return false;
- }
-
- function getIdVisit()
- {
- return $this->details['idvisit'];
- }
-
- function getIdSite()
- {
- return $this->details['idsite'];
- }
-
- function getNumberOfActions()
- {
- return $this->details['visit_total_actions'];
- }
-
- function getNumberOfSearches()
- {
- return $this->details['visit_total_searches'];
- }
-
- function getVisitLength()
- {
- return $this->details['visit_total_time'];
- }
-
- function getVisitLengthPretty()
- {
- return Piwik::getPrettyTimeFromSeconds($this->details['visit_total_time']);
- }
-
- function getVisitorReturning()
- {
- $type = $this->details['visitor_returning'];
- return $type == 2
- ? 'returningCustomer'
- : ($type == 1
- ? 'returning'
- : 'new');
- }
-
- function getVisitorReturningIcon()
- {
- $type = $this->getVisitorReturning();
- if($type == 'returning'
- || $type =='returningCustomer')
- {
- return "plugins/Live/templates/images/returningVisitor.gif";
- }
- return null;
- }
-
- function getTimestampFirstAction()
- {
- return strtotime($this->details['visit_first_action_time']);
- }
-
- function getTimestampLastAction()
- {
- return strtotime($this->details['visit_last_action_time']);
- }
-
- function getCountryCode()
- {
- return $this->details['location_country'];
- }
-
- function getCountryName()
- {
- return Piwik_CountryTranslate($this->getCountryCode());
- }
-
- function getCountryFlag()
- {
- return Piwik_getFlagFromCode($this->getCountryCode());
- }
-
- function getContinent()
- {
- return Piwik_ContinentTranslate($this->getContinentCode());
- }
-
- function getContinentCode()
- {
- return Piwik_Common::getContinent($this->details['location_country']);
- }
-
- function getCityName()
- {
- if (!empty($this->details['location_city']))
- {
- return $this->details['location_city'];
- }
- return null;
- }
-
- public function getRegionName()
- {
- $region = $this->details['location_region'];
- if ($region != '' && $region != Piwik_Tracker_Visit::UNKNOWN_CODE)
- {
- return Piwik_UserCountry_LocationProvider_GeoIp::getRegionNameFromCodes(
- $this->details['location_country'], $region);
- }
- return null;
- }
-
- function getPrettyLocation()
- {
- $parts = array();
-
- $city = $this->getCityName();
- if(!empty($city)) {
- $parts[] = $city;
- }
- $region = $this->getRegionName();
- if(!empty($region)) {
- $parts[] = $region;
- }
-
- // add country & return concatenated result
- $parts[] = $this->getCountryName();
- return implode(', ', $parts);
- }
-
- function getLatitude()
- {
- if(!empty($this->details['location_latitude'])) {
- return $this->details['location_latitude'];
- }
- return null;
- }
-
- function getLongitude()
- {
- if(!empty($this->details['location_longitude'])) {
- return $this->details['location_longitude'];
- }
- return null;
- }
-
- function getCustomVariables()
- {
- $customVariables = array();
- for($i = 1; $i <= Piwik_Tracker::MAX_CUSTOM_VARIABLES; $i++)
- {
- if(!empty($this->details['custom_var_k'.$i]))
- {
- $customVariables[$i] = array(
- 'customVariableName'.$i => $this->details['custom_var_k'.$i],
- 'customVariableValue'.$i => $this->details['custom_var_v'.$i],
- );
- }
- }
- return $customVariables;
- }
-
- function getRefererType()
- {
- return Piwik_getRefererTypeFromShortName($this->details['referer_type']);
- }
-
- function getRefererTypeName()
- {
- return Piwik_getRefererTypeLabel($this->details['referer_type']);
- }
-
- function getKeyword()
- {
- $keyword = $this->details['referer_keyword'];
- if(Piwik_PluginsManager::getInstance()->isPluginActivated('Referers')
- && $this->getRefererType() == 'search')
- {
- $keyword = Piwik_Referers::getCleanKeyword($keyword);
- }
- return urldecode($keyword);
- }
-
- function getRefererUrl()
- {
- if($this->getRefererType() == 'search')
- {
- if(Piwik_PluginsManager::getInstance()->isPluginActivated('Referers')
- && $this->details['referer_keyword'] == Piwik_Referers::LABEL_KEYWORD_NOT_DEFINED)
- {
- return 'http://piwik.org/faq/general/#faq_144';
- }
- // Case URL is google.XX/url.... then we rewrite to the search result page url
- elseif($this->getRefererName() == 'Google'
- && strpos($this->details['referer_url'], '/url'))
- {
- $refUrl = @parse_url($this->details['referer_url']);
- if(isset($refUrl['host']))
- {
- $url = Piwik_getSearchEngineUrlFromUrlAndKeyword('http://google.com', $this->getKeyword());
- $url = str_replace('google.com', $refUrl['host'], $url);
- return $url;
- }
- }
- }
- if(Piwik_Common::isLookLikeUrl($this->details['referer_url']))
- {
- return $this->details['referer_url'];
- }
- return null;
- }
-
- function getKeywordPosition()
- {
- if($this->getRefererType() == 'search'
- && strpos($this->getRefererName(), 'Google') !== false)
- {
- $url = @parse_url($this->details['referer_url']);
- if(empty($url['query']))
- {
- return null;
- }
- $position = Piwik_Common::getParameterFromQueryString($url['query'], 'cd');
- if(!empty($position))
- {
- return $position;
- }
- }
- return null;
- }
-
- function getRefererName()
- {
- return urldecode($this->details['referer_name']);
- }
-
- function getSearchEngineUrl()
- {
- if($this->getRefererType() == 'search'
- && !empty($this->details['referer_name']))
- {
- return Piwik_getSearchEngineUrlFromName($this->details['referer_name']);
- }
- return null;
- }
-
- function getSearchEngineIcon()
- {
- $searchEngineUrl = $this->getSearchEngineUrl();
- if( !is_null($searchEngineUrl) )
- {
- return Piwik_getSearchEngineLogoFromUrl($searchEngineUrl);
- }
- return null;
- }
-
- function getPlugins()
- {
- $plugins = array(
- 'config_pdf',
- 'config_flash',
- 'config_java',
- 'config_director',
- 'config_quicktime',
- 'config_realplayer',
- 'config_windowsmedia',
- 'config_gears',
- 'config_silverlight',
- );
- $pluginShortNames = array();
- foreach($plugins as $plugin)
- {
- if($this->details[$plugin] == 1)
- {
- $pluginShortName = substr($plugin, 7);
- $pluginShortNames[] = $pluginShortName;
- }
- }
- return implode(self::DELIMITER_PLUGIN_NAME, $pluginShortNames);
- }
-
- function getPluginIcons()
- {
- $pluginNames = $this->getPlugins();
- if( !empty($pluginNames) )
- {
- $pluginNames = explode(self::DELIMITER_PLUGIN_NAME, $pluginNames);
- $pluginIcons = array();
-
- foreach($pluginNames as $plugin) {
- $pluginIcons[] = array("pluginIcon" =>Piwik_getPluginsLogo($plugin), "pluginName" =>$plugin);
- }
- return $pluginIcons;
- }
- return null;
- }
-
- function getOperatingSystem()
- {
- return Piwik_getOSLabel($this->details['config_os']);
- }
-
- function getOperatingSystemShortName()
- {
- return Piwik_getOSShortLabel($this->details['config_os']);
- }
-
- function getOperatingSystemIcon()
- {
- return Piwik_getOSLogo($this->details['config_os']);
- }
-
- function getBrowserFamilyDescription()
- {
- return Piwik_getBrowserTypeLabel($this->getBrowserFamily());
- }
-
- function getBrowserFamily()
- {
- return Piwik_getBrowserFamily($this->details['config_browser_name']);
- }
-
- function getBrowser()
- {
- return Piwik_getBrowserLabel($this->details['config_browser_name'] . ";" . $this->details['config_browser_version']);
- }
-
- function getBrowserIcon()
- {
- return Piwik_getBrowsersLogo($this->details['config_browser_name'] . ";" . $this->details['config_browser_version']);
- }
-
- function getScreenType()
- {
- return Piwik_getScreenTypeFromResolution($this->details['config_resolution']);
- }
-
- function getResolution()
- {
- return $this->details['config_resolution'];
- }
-
- function getScreenTypeIcon()
- {
- return Piwik_getScreensLogo($this->getScreenType());
- }
-
- function getProvider()
- {
- return Piwik_getHostnameName( @$this->details['location_provider']);
- }
-
- function getProviderUrl()
- {
- return Piwik_getHostnameUrl( @$this->details['location_provider']);
- }
-
- function getDateTimeLastAction()
- {
- return date('Y-m-d H:i:s', strtotime($this->details['visit_last_action_time']));
- }
-
- function getVisitEcommerceStatusIcon()
- {
- $status = $this->getVisitEcommerceStatus();
-
- if(in_array($status, array('ordered', 'orderedThenAbandonedCart')))
- {
- return "themes/default/images/ecommerceOrder.gif";
- }
- elseif($status == 'abandonedCart')
- {
- return "themes/default/images/ecommerceAbandonedCart.gif";
- }
- return null;
- }
-
- function getVisitEcommerceStatus()
- {
- return Piwik_API_API::getVisitEcommerceStatusFromId($this->details['visit_goal_buyer']);
- }
-
- function getVisitorGoalConvertedIcon()
- {
- return $this->isVisitorGoalConverted()
- ? "themes/default/images/goal.png"
- : null;
- }
-
- function isVisitorGoalConverted()
- {
- return $this->details['visit_goal_converted'];
- }
+ const DELIMITER_PLUGIN_NAME = ", ";
+
+ function __construct($visitorRawData)
+ {
+ $this->details = $visitorRawData;
+ }
+
+ function getAllVisitorDetails()
+ {
+ return array(
+ 'idSite' => $this->getIdSite(),
+ 'idVisit' => $this->getIdVisit(),
+ 'visitIp' => $this->getIp(),
+ 'visitorId' => $this->getVisitorId(),
+ 'visitorType' => $this->getVisitorReturning(),
+ 'visitorTypeIcon' => $this->getVisitorReturningIcon(),
+ 'visitConverted' => $this->isVisitorGoalConverted(),
+ 'visitConvertedIcon' => $this->getVisitorGoalConvertedIcon(),
+ 'visitEcommerceStatus' => $this->getVisitEcommerceStatus(),
+ 'visitEcommerceStatusIcon' => $this->getVisitEcommerceStatusIcon(),
+
+ 'searches' => $this->getNumberOfSearches(),
+ 'actions' => $this->getNumberOfActions(),
+ // => false are placeholders to be filled in API later
+ 'actionDetails' => false,
+ 'customVariables' => $this->getCustomVariables(),
+ 'goalConversions' => false,
+ 'siteCurrency' => false,
+ 'siteCurrencySymbol' => false,
+
+ // all time entries
+ 'serverDate' => $this->getServerDate(),
+ 'visitLocalTime' => $this->getVisitLocalTime(),
+ 'firstActionTimestamp' => $this->getTimestampFirstAction(),
+ 'lastActionTimestamp' => $this->getTimestampLastAction(),
+ 'lastActionDateTime' => $this->getDateTimeLastAction(),
+
+ // standard attributes
+ 'visitDuration' => $this->getVisitLength(),
+ 'visitDurationPretty' => $this->getVisitLengthPretty(),
+ 'visitCount' => $this->getVisitCount(),
+ 'daysSinceLastVisit' => $this->getDaysSinceLastVisit(),
+ 'daysSinceFirstVisit' => $this->getDaysSinceFirstVisit(),
+ 'daysSinceLastEcommerceOrder' => $this->getDaysSinceLastEcommerceOrder(),
+ 'continent' => $this->getContinent(),
+ 'continentCode' => $this->getContinentCode(),
+ 'country' => $this->getCountryName(),
+ 'countryCode' => $this->getCountryCode(),
+ 'countryFlag' => $this->getCountryFlag(),
+ 'region' => $this->getRegionName(),
+ 'city' => $this->getCityName(),
+ 'location' => $this->getPrettyLocation(),
+ 'latitude' => $this->getLatitude(),
+ 'longitude' => $this->getLongitude(),
+ 'provider' => $this->getProvider(),
+ 'providerUrl' => $this->getProviderUrl(),
+
+ 'referrerType' => $this->getRefererType(),
+ 'referrerTypeName' => $this->getRefererTypeName(),
+ 'referrerName' => $this->getRefererName(),
+ 'referrerKeyword' => $this->getKeyword(),
+ 'referrerKeywordPosition' => $this->getKeywordPosition(),
+ 'referrerUrl' => $this->getRefererUrl(),
+ 'referrerSearchEngineUrl' => $this->getSearchEngineUrl(),
+ 'referrerSearchEngineIcon' => $this->getSearchEngineIcon(),
+ 'operatingSystem' => $this->getOperatingSystem(),
+ 'operatingSystemShortName' => $this->getOperatingSystemShortName(),
+ 'operatingSystemIcon' => $this->getOperatingSystemIcon(),
+ 'browserFamily' => $this->getBrowserFamily(),
+ 'browserFamilyDescription' => $this->getBrowserFamilyDescription(),
+ 'browserName' => $this->getBrowser(),
+ 'browserIcon' => $this->getBrowserIcon(),
+ 'screenType' => $this->getScreenType(),
+ 'resolution' => $this->getResolution(),
+ 'screenTypeIcon' => $this->getScreenTypeIcon(),
+ 'plugins' => $this->getPlugins(),
+ 'pluginsIcons' => $this->getPluginIcons(),
+ );
+ }
+
+ function getVisitorId()
+ {
+ if (isset($this->details['idvisitor'])) {
+ return bin2hex($this->details['idvisitor']);
+ }
+ return false;
+ }
+
+ function getVisitLocalTime()
+ {
+ return $this->details['visitor_localtime'];
+ }
+
+ function getVisitCount()
+ {
+ return $this->details['visitor_count_visits'];
+ }
+
+ function getDaysSinceLastVisit()
+ {
+ return $this->details['visitor_days_since_last'];
+ }
+
+ function getDaysSinceLastEcommerceOrder()
+ {
+ return $this->details['visitor_days_since_order'];
+ }
+
+ function getDaysSinceFirstVisit()
+ {
+ return $this->details['visitor_days_since_first'];
+ }
+
+ function getServerDate()
+ {
+ return date('Y-m-d', strtotime($this->details['visit_last_action_time']));
+ }
+
+ function getIp()
+ {
+ if (isset($this->details['location_ip'])) {
+ return Piwik_IP::N2P($this->details['location_ip']);
+ }
+ return false;
+ }
+
+ function getIdVisit()
+ {
+ return $this->details['idvisit'];
+ }
+
+ function getIdSite()
+ {
+ return $this->details['idsite'];
+ }
+
+ function getNumberOfActions()
+ {
+ return $this->details['visit_total_actions'];
+ }
+
+ function getNumberOfSearches()
+ {
+ return $this->details['visit_total_searches'];
+ }
+
+ function getVisitLength()
+ {
+ return $this->details['visit_total_time'];
+ }
+
+ function getVisitLengthPretty()
+ {
+ return Piwik::getPrettyTimeFromSeconds($this->details['visit_total_time']);
+ }
+
+ function getVisitorReturning()
+ {
+ $type = $this->details['visitor_returning'];
+ return $type == 2
+ ? 'returningCustomer'
+ : ($type == 1
+ ? 'returning'
+ : 'new');
+ }
+
+ function getVisitorReturningIcon()
+ {
+ $type = $this->getVisitorReturning();
+ if ($type == 'returning'
+ || $type == 'returningCustomer'
+ ) {
+ return "plugins/Live/templates/images/returningVisitor.gif";
+ }
+ return null;
+ }
+
+ function getTimestampFirstAction()
+ {
+ return strtotime($this->details['visit_first_action_time']);
+ }
+
+ function getTimestampLastAction()
+ {
+ return strtotime($this->details['visit_last_action_time']);
+ }
+
+ function getCountryCode()
+ {
+ return $this->details['location_country'];
+ }
+
+ function getCountryName()
+ {
+ return Piwik_CountryTranslate($this->getCountryCode());
+ }
+
+ function getCountryFlag()
+ {
+ return Piwik_getFlagFromCode($this->getCountryCode());
+ }
+
+ function getContinent()
+ {
+ return Piwik_ContinentTranslate($this->getContinentCode());
+ }
+
+ function getContinentCode()
+ {
+ return Piwik_Common::getContinent($this->details['location_country']);
+ }
+
+ function getCityName()
+ {
+ if (!empty($this->details['location_city'])) {
+ return $this->details['location_city'];
+ }
+ return null;
+ }
+
+ public function getRegionName()
+ {
+ $region = $this->details['location_region'];
+ if ($region != '' && $region != Piwik_Tracker_Visit::UNKNOWN_CODE) {
+ return Piwik_UserCountry_LocationProvider_GeoIp::getRegionNameFromCodes(
+ $this->details['location_country'], $region);
+ }
+ return null;
+ }
+
+ function getPrettyLocation()
+ {
+ $parts = array();
+
+ $city = $this->getCityName();
+ if (!empty($city)) {
+ $parts[] = $city;
+ }
+ $region = $this->getRegionName();
+ if (!empty($region)) {
+ $parts[] = $region;
+ }
+
+ // add country & return concatenated result
+ $parts[] = $this->getCountryName();
+ return implode(', ', $parts);
+ }
+
+ function getLatitude()
+ {
+ if (!empty($this->details['location_latitude'])) {
+ return $this->details['location_latitude'];
+ }
+ return null;
+ }
+
+ function getLongitude()
+ {
+ if (!empty($this->details['location_longitude'])) {
+ return $this->details['location_longitude'];
+ }
+ return null;
+ }
+
+ function getCustomVariables()
+ {
+ $customVariables = array();
+ for ($i = 1; $i <= Piwik_Tracker::MAX_CUSTOM_VARIABLES; $i++) {
+ if (!empty($this->details['custom_var_k' . $i])) {
+ $customVariables[$i] = array(
+ 'customVariableName' . $i => $this->details['custom_var_k' . $i],
+ 'customVariableValue' . $i => $this->details['custom_var_v' . $i],
+ );
+ }
+ }
+ return $customVariables;
+ }
+
+ function getRefererType()
+ {
+ return Piwik_getRefererTypeFromShortName($this->details['referer_type']);
+ }
+
+ function getRefererTypeName()
+ {
+ return Piwik_getRefererTypeLabel($this->details['referer_type']);
+ }
+
+ function getKeyword()
+ {
+ $keyword = $this->details['referer_keyword'];
+ if (Piwik_PluginsManager::getInstance()->isPluginActivated('Referers')
+ && $this->getRefererType() == 'search'
+ ) {
+ $keyword = Piwik_Referers::getCleanKeyword($keyword);
+ }
+ return urldecode($keyword);
+ }
+
+ function getRefererUrl()
+ {
+ if ($this->getRefererType() == 'search') {
+ if (Piwik_PluginsManager::getInstance()->isPluginActivated('Referers')
+ && $this->details['referer_keyword'] == Piwik_Referers::LABEL_KEYWORD_NOT_DEFINED
+ ) {
+ return 'http://piwik.org/faq/general/#faq_144';
+ } // Case URL is google.XX/url.... then we rewrite to the search result page url
+ elseif ($this->getRefererName() == 'Google'
+ && strpos($this->details['referer_url'], '/url')
+ ) {
+ $refUrl = @parse_url($this->details['referer_url']);
+ if (isset($refUrl['host'])) {
+ $url = Piwik_getSearchEngineUrlFromUrlAndKeyword('http://google.com', $this->getKeyword());
+ $url = str_replace('google.com', $refUrl['host'], $url);
+ return $url;
+ }
+ }
+ }
+ if (Piwik_Common::isLookLikeUrl($this->details['referer_url'])) {
+ return $this->details['referer_url'];
+ }
+ return null;
+ }
+
+ function getKeywordPosition()
+ {
+ if ($this->getRefererType() == 'search'
+ && strpos($this->getRefererName(), 'Google') !== false
+ ) {
+ $url = @parse_url($this->details['referer_url']);
+ if (empty($url['query'])) {
+ return null;
+ }
+ $position = Piwik_Common::getParameterFromQueryString($url['query'], 'cd');
+ if (!empty($position)) {
+ return $position;
+ }
+ }
+ return null;
+ }
+
+ function getRefererName()
+ {
+ return urldecode($this->details['referer_name']);
+ }
+
+ function getSearchEngineUrl()
+ {
+ if ($this->getRefererType() == 'search'
+ && !empty($this->details['referer_name'])
+ ) {
+ return Piwik_getSearchEngineUrlFromName($this->details['referer_name']);
+ }
+ return null;
+ }
+
+ function getSearchEngineIcon()
+ {
+ $searchEngineUrl = $this->getSearchEngineUrl();
+ if (!is_null($searchEngineUrl)) {
+ return Piwik_getSearchEngineLogoFromUrl($searchEngineUrl);
+ }
+ return null;
+ }
+
+ function getPlugins()
+ {
+ $plugins = array(
+ 'config_pdf',
+ 'config_flash',
+ 'config_java',
+ 'config_director',
+ 'config_quicktime',
+ 'config_realplayer',
+ 'config_windowsmedia',
+ 'config_gears',
+ 'config_silverlight',
+ );
+ $pluginShortNames = array();
+ foreach ($plugins as $plugin) {
+ if ($this->details[$plugin] == 1) {
+ $pluginShortName = substr($plugin, 7);
+ $pluginShortNames[] = $pluginShortName;
+ }
+ }
+ return implode(self::DELIMITER_PLUGIN_NAME, $pluginShortNames);
+ }
+
+ function getPluginIcons()
+ {
+ $pluginNames = $this->getPlugins();
+ if (!empty($pluginNames)) {
+ $pluginNames = explode(self::DELIMITER_PLUGIN_NAME, $pluginNames);
+ $pluginIcons = array();
+
+ foreach ($pluginNames as $plugin) {
+ $pluginIcons[] = array("pluginIcon" => Piwik_getPluginsLogo($plugin), "pluginName" => $plugin);
+ }
+ return $pluginIcons;
+ }
+ return null;
+ }
+
+ function getOperatingSystem()
+ {
+ return Piwik_getOSLabel($this->details['config_os']);
+ }
+
+ function getOperatingSystemShortName()
+ {
+ return Piwik_getOSShortLabel($this->details['config_os']);
+ }
+
+ function getOperatingSystemIcon()
+ {
+ return Piwik_getOSLogo($this->details['config_os']);
+ }
+
+ function getBrowserFamilyDescription()
+ {
+ return Piwik_getBrowserTypeLabel($this->getBrowserFamily());
+ }
+
+ function getBrowserFamily()
+ {
+ return Piwik_getBrowserFamily($this->details['config_browser_name']);
+ }
+
+ function getBrowser()
+ {
+ return Piwik_getBrowserLabel($this->details['config_browser_name'] . ";" . $this->details['config_browser_version']);
+ }
+
+ function getBrowserIcon()
+ {
+ return Piwik_getBrowsersLogo($this->details['config_browser_name'] . ";" . $this->details['config_browser_version']);
+ }
+
+ function getScreenType()
+ {
+ return Piwik_getScreenTypeFromResolution($this->details['config_resolution']);
+ }
+
+ function getResolution()
+ {
+ return $this->details['config_resolution'];
+ }
+
+ function getScreenTypeIcon()
+ {
+ return Piwik_getScreensLogo($this->getScreenType());
+ }
+
+ function getProvider()
+ {
+ return Piwik_getHostnameName(@$this->details['location_provider']);
+ }
+
+ function getProviderUrl()
+ {
+ return Piwik_getHostnameUrl(@$this->details['location_provider']);
+ }
+
+ function getDateTimeLastAction()
+ {
+ return date('Y-m-d H:i:s', strtotime($this->details['visit_last_action_time']));
+ }
+
+ function getVisitEcommerceStatusIcon()
+ {
+ $status = $this->getVisitEcommerceStatus();
+
+ if (in_array($status, array('ordered', 'orderedThenAbandonedCart'))) {
+ return "themes/default/images/ecommerceOrder.gif";
+ } elseif ($status == 'abandonedCart') {
+ return "themes/default/images/ecommerceAbandonedCart.gif";
+ }
+ return null;
+ }
+
+ function getVisitEcommerceStatus()
+ {
+ return Piwik_API_API::getVisitEcommerceStatusFromId($this->details['visit_goal_buyer']);
+ }
+
+ function getVisitorGoalConvertedIcon()
+ {
+ return $this->isVisitorGoalConverted()
+ ? "themes/default/images/goal.png"
+ : null;
+ }
+
+ function isVisitorGoalConverted()
+ {
+ return $this->details['visit_goal_converted'];
+ }
}
diff --git a/plugins/Live/templates/index.tpl b/plugins/Live/templates/index.tpl
index d839c6bf33..13e8eb8d92 100644
--- a/plugins/Live/templates/index.tpl
+++ b/plugins/Live/templates/index.tpl
@@ -1,29 +1,29 @@
{literal}
<script type="text/javascript" charset="utf-8">
-$(document).ready(function() {
- $('#visitsLive').liveWidget({
- interval: {/literal}{$liveRefreshAfterMs}{literal},
- onUpdate: function(){
- //updates the numbers of total visits in startbox
- var ajaxRequest = new ajaxHelper();
- ajaxRequest.setFormat('html');
- ajaxRequest.addParams({
+ $(document).ready(function () {
+ $('#visitsLive').liveWidget({
+ interval: {/literal}{$liveRefreshAfterMs}{literal},
+ onUpdate: function () {
+ //updates the numbers of total visits in startbox
+ var ajaxRequest = new ajaxHelper();
+ ajaxRequest.setFormat('html');
+ ajaxRequest.addParams({
+ module: 'Live',
+ action: 'ajaxTotalVisitors'
+ }, 'GET');
+ ajaxRequest.setCallback(function (r) {
+ $("#visitsTotal").html(r);
+ });
+ ajaxRequest.send(false);
+ },
+ maxRows: 10,
+ fadeInSpeed: 600,
+ dataUrlParams: {
module: 'Live',
- action: 'ajaxTotalVisitors'
- }, 'GET');
- ajaxRequest.setCallback(function (r) {
- $("#visitsTotal").html(r);
- });
- ajaxRequest.send(false);
- },
- maxRows: 10,
- fadeInSpeed: 600,
- dataUrlParams: {
- module: 'Live',
- action: 'getLastVisitsStart'
- }
+ action: 'getLastVisitsStart'
+ }
+ });
});
-});
</script>
{/literal}
@@ -32,9 +32,11 @@ $(document).ready(function() {
{$visitors}
<div class="visitsLiveFooter">
- <a title="Pause Live!" href="javascript:void(0);" onclick="onClickPause();"><img id="pauseImage" border="0" src="plugins/Live/templates/images/pause_disabled.gif" /></a>
- <a title="Start Live!" href="javascript:void(0);" onclick="onClickPlay();"><img id="playImage" border="0" src="plugins/Live/templates/images/play.gif" /></a>
- {if !$disableLink}
- &nbsp; <a class="rightLink" href="javascript:broadcast.propagateAjax('module=Live&action=getVisitorLog')">{'Live_LinkVisitorLog'|translate}</a>
- {/if}
+ <a title="Pause Live!" href="javascript:void(0);" onclick="onClickPause();"><img id="pauseImage" border="0"
+ src="plugins/Live/templates/images/pause_disabled.gif"/></a>
+ <a title="Start Live!" href="javascript:void(0);" onclick="onClickPlay();"><img id="playImage" border="0" src="plugins/Live/templates/images/play.gif"/></a>
+ {if !$disableLink}
+ &nbsp;
+ <a class="rightLink" href="javascript:broadcast.propagateAjax('module=Live&action=getVisitorLog')">{'Live_LinkVisitorLog'|translate}</a>
+ {/if}
</div>
diff --git a/plugins/Live/templates/lastVisits.tpl b/plugins/Live/templates/lastVisits.tpl
index 5fb028978a..63a26afe6b 100644
--- a/plugins/Live/templates/lastVisits.tpl
+++ b/plugins/Live/templates/lastVisits.tpl
@@ -2,77 +2,90 @@
{assign var=maxPagesDisplayedByVisitor value=100}
<ul id='visitsLive'>
-{foreach from=$visitors item=visitor}
- <li id="{$visitor.idVisit}" class="visit">
- <div style="display:none" class="idvisit">{$visitor.idVisit}</div>
- <div title="{$visitor.actionDetails|@count} {'Live_Actions'|translate}" class="datetime">
- <span style='display:none' class='serverTimestamp'>{$visitor.serverTimestamp}</span>
- {$visitor.serverDatePretty} - {$visitor.serverTimePretty} {if $visitor.visitDuration > 0}<i>({$visitor.visitDurationPretty})</i>{/if}
- &nbsp;<img src="{$visitor.countryFlag}" title="{$visitor.location|escape:'html'}, {'Provider_ColumnProvider'|translate} {$visitor.provider}" />
- &nbsp;<img src="{$visitor.browserIcon}" title="{$visitor.browserName}, {'UserSettings_Plugins'|translate}: {$visitor.plugins}" />
- &nbsp;<img src="{$visitor.operatingSystemIcon}" title="{$visitor.operatingSystem}, {$visitor.resolution}" />
- &nbsp;
- {if $visitor.visitConverted}
- <span title="{'General_VisitConvertedNGoals'|translate:$visitor.goalConversions}" class='visitorRank'>
- <img src="{$visitor.visitConvertedIcon}" />
- <span class='hash'>#</span>{$visitor.goalConversions}
- {if $visitor.visitEcommerceStatusIcon}
- &nbsp;- <img src="{$visitor.visitEcommerceStatusIcon}" title="{$visitor.visitEcommerceStatus}"/>
- {/if}
- </span>{/if}
- {if $visitor.visitorTypeIcon}
- <a class="rightLink" href="javascript:broadcast.propagateAjax('module=Live&action=getVisitorLog&period=month&segment=visitorId=={$visitor.visitorId}')">
- &nbsp;- <img src="{$visitor.visitorTypeIcon}" title="{'General_ReturningVisitor'|translate} - {'General_ReturningVisitorAllVisits'|translate}" />
- </a>
- {/if}
- {if $visitor.visitIp}- <span title="{if !empty($visitor.visitorId)}{'General_VisitorID'|translate}: {$visitor.visitorId}{/if}">IP: {$visitor.visitIp}</span>{/if}
- </div>
- <!--<div class="settings"></div>-->
- <div class="referer">
- {if $visitor.referrerType != 'direct'}{'General_FromReferrer'|translate} {if !empty($visitor.referrerUrl)}<a href="{$visitor.referrerUrl|escape:'html'}" target="_blank">{/if}{if !empty($visitor.searchEngineIcon)}<img src="{$visitor.searchEngineIcon}" /> {/if}{$visitor.referrerName|escape:'html'}{if !empty($visitor.referrerUrl)}</a>{/if}
- {if !empty($visitor.referrerKeyword)} - "{$visitor.referrerKeyword|escape:'html'}"{/if}
- {capture assign='keyword'}{$visitor.referrerKeyword|escape:'html'}{/capture}
- {capture assign='searchName'}{$visitor.referrerName|escape:"html"}{/capture}
- {capture assign='position'}#{$visitor.referrerKeywordPosition}{/capture}
- {if !empty($visitor.referrerKeywordPosition)}<span title='{'Live_KeywordRankedOnSearchResultForThisVisitor'|translate:$keyword:$position:$searchName}' class='visitorRank'><span class='hash'>#</span>{$visitor.referrerKeywordPosition}</span>{/if}
- {else}{'Referers_DirectEntry'|translate}{/if}
- </div>
- <div id="{$visitor.idVisit}_actions" class="settings">
- <span class="pagesTitle" title="{$visitor.actionDetails|@count} {'Live_Actions'|translate}" >{'Actions_SubmenuPages'|translate}:</span>&nbsp;
- {php} $col = 0; {/php}
- {foreach from=$visitor.actionDetails item=action name=visitorPages}
- {if $smarty.foreach.visitorPages.iteration <= $maxPagesDisplayedByVisitor}
- {if $action.type == 'ecommerceOrder' || $action.type == 'ecommerceAbandonedCart'}
- <span title="
+ {foreach from=$visitors item=visitor}
+ <li id="{$visitor.idVisit}" class="visit">
+ <div style="display:none" class="idvisit">{$visitor.idVisit}</div>
+ <div title="{$visitor.actionDetails|@count} {'Live_Actions'|translate}" class="datetime">
+ <span style='display:none' class='serverTimestamp'>{$visitor.serverTimestamp}</span>
+ {$visitor.serverDatePretty} - {$visitor.serverTimePretty} {if $visitor.visitDuration > 0}<i>({$visitor.visitDurationPretty})</i>{/if}
+ &nbsp;<img src="{$visitor.countryFlag}" title="{$visitor.location|escape:'html'}, {'Provider_ColumnProvider'|translate} {$visitor.provider}"/>
+ &nbsp;<img src="{$visitor.browserIcon}" title="{$visitor.browserName}, {'UserSettings_Plugins'|translate}: {$visitor.plugins}"/>
+ &nbsp;<img src="{$visitor.operatingSystemIcon}" title="{$visitor.operatingSystem}, {$visitor.resolution}"/>
+ &nbsp;
+ {if $visitor.visitConverted}
+ <span title="{'General_VisitConvertedNGoals'|translate:$visitor.goalConversions}" class='visitorRank'>
+ <img src="{$visitor.visitConvertedIcon}"/>
+ <span class='hash'>#</span>
+ {$visitor.goalConversions}
+ {if $visitor.visitEcommerceStatusIcon}
+ &nbsp;-
+ <img src="{$visitor.visitEcommerceStatusIcon}" title="{$visitor.visitEcommerceStatus}"/>
+ {/if}
+ </span>{/if}
+ {if $visitor.visitorTypeIcon}
+ <a class="rightLink"
+ href="javascript:broadcast.propagateAjax('module=Live&action=getVisitorLog&period=month&segment=visitorId=={$visitor.visitorId}')">
+ &nbsp;- <img src="{$visitor.visitorTypeIcon}"
+ title="{'General_ReturningVisitor'|translate} - {'General_ReturningVisitorAllVisits'|translate}"/>
+ </a>
+ {/if}
+ {if $visitor.visitIp}- <span title="{if !empty($visitor.visitorId)}{'General_VisitorID'|translate}: {$visitor.visitorId}{/if}">
+ IP: {$visitor.visitIp}</span>{/if}
+ </div>
+ <!--<div class="settings"></div>-->
+ <div class="referer">
+ {if $visitor.referrerType != 'direct'}{'General_FromReferrer'|translate} {if !empty($visitor.referrerUrl)}<a href="{$visitor.referrerUrl|escape:'html'}" target="_blank">{/if}{if !empty($visitor.searchEngineIcon)}
+ <img src="{$visitor.searchEngineIcon}" /> {/if}{$visitor.referrerName|escape:'html'}{if !empty($visitor.referrerUrl)}</a>{/if}
+ {if !empty($visitor.referrerKeyword)} - "{$visitor.referrerKeyword|escape:'html'}"{/if}
+ {capture assign='keyword'}{$visitor.referrerKeyword|escape:'html'}{/capture}
+ {capture assign='searchName'}{$visitor.referrerName|escape:"html"}{/capture}
+ {capture assign='position'}#{$visitor.referrerKeywordPosition}{/capture}
+ {if !empty($visitor.referrerKeywordPosition)}<span
+ title='{'Live_KeywordRankedOnSearchResultForThisVisitor'|translate:$keyword:$position:$searchName}' class='visitorRank'>
+ <span class='hash'>#</span>
+ {$visitor.referrerKeywordPosition}</span>{/if}
+ {else}{'Referers_DirectEntry'|translate}{/if}
+ </div>
+ <div id="{$visitor.idVisit}_actions" class="settings">
+ <span class="pagesTitle" title="{$visitor.actionDetails|@count} {'Live_Actions'|translate}">{'Actions_SubmenuPages'|translate}:</span>&nbsp;
+ {php} $col = 0; {/php}
+ {foreach from=$visitor.actionDetails item=action name=visitorPages}
+ {if $smarty.foreach.visitorPages.iteration <= $maxPagesDisplayedByVisitor}
+ {if $action.type == 'ecommerceOrder' || $action.type == 'ecommerceAbandonedCart'}
+ <span title="
{if $action.type == 'ecommerceOrder'}{'Goals_EcommerceOrder'|translate}{else}{'Goals_AbandonedCart'|translate}{/if}
- {if $action.type == 'ecommerceOrder'}{'Live_GoalRevenue'|translate}: {else}{capture assign='revenueLeft'}{'Live_GoalRevenue'|translate}{/capture}{'Goals_LeftInCart'|translate:$revenueLeft}: {/if}{$action.revenue|money:$idSite}
- {$action.serverTimePretty|escape:'html'}
{if !empty($action.itemDetails)}{foreach from=$action.itemDetails item=product}
# {$product.itemSKU}{if !empty($product.itemName)}: {$product.itemName}{/if}{if !empty($product.itemCategory)} ({$product.itemCategory}){/if}, {'General_Quantity'|translate}: {$product.quantity}, {'General_Price'|translate}: {$product.price|money:$idSite}
{/foreach}{/if}">
- <img class='iconPadding' src="{$action.icon }" />
- {if $action.type == 'ecommerceOrder'}{'Live_GoalRevenue'|translate}: {$action.revenue|money:$idSite} {/if}
+ <img class='iconPadding' src="{$action.icon }"/>
+ {if $action.type == 'ecommerceOrder'}{'Live_GoalRevenue'|translate}: {$action.revenue|money:$idSite} {/if}
</span>
- {else}
- {php}$col++; if ($col>=9) { $col=0; }{/php}
- <a href="{$action.url|escape:'html'}" target="_blank">
- {if $action.type == 'action'}
- <img src="plugins/Live/templates/images/file{php} echo $col; {/php}.png" title="{if !empty($action.pageTitle)}{$action.pageTitle}{/if} - {$action.serverTimePretty|escape:'html'}{if isset($action.timeSpentPretty)} - {'General_TimeOnPage'|translate}: {$action.timeSpentPretty}{/if}" />
- {elseif $action.type == 'outlink' || $action.type == 'download'}
- <img class='iconPadding' src="{$action.icon}" title="{$action.url|escape:'html'} - {$action.serverTimePretty|escape:'html'}" />
- {elseif $action.type == 'search'}
- <img class='iconPadding' src="{$action.icon}" title="{'Actions_SubmenuSitesearch'|translate|escape:'html'}: {$action.pageTitle|escape:'html'} - {$action.serverTimePretty|escape:'html'}" />
- {else}
- <img class='iconPadding' src="{$action.icon}" title="{$action.goalName|escape:'html'} - {if $action.revenue > 0}{'Live_GoalRevenue'|translate}: {$action.revenue|money:$idSite} - {/if} {$action.serverTimePretty|escape:'html'}" />
- {/if}
- </a>
- {/if}
- {/if}
- {/foreach}
- {if $smarty.foreach.visitorPages.iteration > $maxPagesDisplayedByVisitor}
- <i>({'Live_MorePagesNotDisplayed'|translate})</i>
- {/if}
- </div>
- </li>
-{/foreach}
+ {else}
+ {php}$col++; if ($col>=9) { $col=0; }{/php}
+ <a href="{$action.url|escape:'html'}" target="_blank">
+ {if $action.type == 'action'}
+ <img src="plugins/Live/templates/images/file{php} echo $col; {/php}.png"
+ title="{if !empty($action.pageTitle)}{$action.pageTitle}{/if} - {$action.serverTimePretty|escape:'html'}{if isset($action.timeSpentPretty)} - {'General_TimeOnPage'|translate}: {$action.timeSpentPretty}{/if}"/>
+ {elseif $action.type == 'outlink' || $action.type == 'download'}
+ <img class='iconPadding' src="{$action.icon}"
+ title="{$action.url|escape:'html'} - {$action.serverTimePretty|escape:'html'}"/>
+ {elseif $action.type == 'search'}
+ <img class='iconPadding' src="{$action.icon}"
+ title="{'Actions_SubmenuSitesearch'|translate|escape:'html'}: {$action.pageTitle|escape:'html'} - {$action.serverTimePretty|escape:'html'}"/>
+ {else}
+ <img class='iconPadding' src="{$action.icon}"
+ title="{$action.goalName|escape:'html'} - {if $action.revenue > 0}{'Live_GoalRevenue'|translate}: {$action.revenue|money:$idSite} - {/if} {$action.serverTimePretty|escape:'html'}"/>
+ {/if}
+ </a>
+ {/if}
+ {/if}
+ {/foreach}
+ {if $smarty.foreach.visitorPages.iteration > $maxPagesDisplayedByVisitor}
+ <i>({'Live_MorePagesNotDisplayed'|translate})</i>
+ {/if}
+ </div>
+ </li>
+ {/foreach}
</ul>
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}
<script type="text/javascript">
-$(document).ready(function() {
- var refreshWidget = function (element, refreshAfterXSecs)
- {
- // if the widget has been removed from the DOM, abort
- if ($(element).parent().length == 0)
- {
- return;
- }
-
- var lastMinutes = $(element).attr('data-last-minutes') || 3,
- translations = JSON.parse($(element).attr('data-translations'));
-
- var ajaxRequest = new ajaxHelper();
- ajaxRequest.addParams({
- module: 'API',
- method: 'Live.getCounters',
- format: 'json',
- lastMinutes: lastMinutes
- }, 'get');
- ajaxRequest.setFormat('json');
- ajaxRequest.setCallback(function (data) {
- data = data[0];
-
- // set text and tooltip of visitors count metric
- var visitors = data['visitors'];
- if (visitors == 1)
- {
- var visitorsCountMessage = translations['one_visitor'];
- }
- else
- {
- var visitorsCountMessage = translations['visitors'].replace('%s', visitors);
- }
- $('.simple-realtime-visitor-counter', element)
- .attr('title', visitorsCountMessage)
- .find('div').text(visitors);
-
- // set text of individual metrics spans
- var metrics = $('.simple-realtime-metric', element);
-
- var visitsText = data['visits'] == 1
- ? translations['one_visit'] : translations['visits'].replace('%s', data['visits']);
- $(metrics[0]).text(visitsText);
-
- var actionsText = data['actions'] == 1
- ? translations['one_action'] : translations['actions'].replace('%s', data['actions']);
- $(metrics[1]).text(actionsText);
-
- var lastMinutesText = lastMinutes == 1
- ? translations['one_minute'] : translations['minutes'].replace('%s', lastMinutes);
- $(metrics[2]).text(lastMinutesText);
-
- // schedule another request
- setTimeout(function() { refreshWidget(element, refreshAfterXSecs); }, refreshAfterXSecs * 1000);
- });
- ajaxRequest.send(true);
- };
-
- var initSimpleRealtimeVisitorWidget = function (refreshAfterXSecs) {
- $('.simple-realtime-visitor-widget').each(function() {
- var self = this;
- if ($(self).attr('data-inited'))
- {
- return;
- }
-
- $(self).attr('data-inited', 1);
-
- setTimeout(function() { refreshWidget(self, refreshAfterXSecs); }, refreshAfterXSecs * 1000);
- });
- };
-
- initSimpleRealtimeVisitorWidget({/literal}{$refreshAfterXSecs}{literal});
-});
+ $(document).ready(function () {
+ var refreshWidget = function (element, refreshAfterXSecs) {
+ // if the widget has been removed from the DOM, abort
+ if ($(element).parent().length == 0) {
+ return;
+ }
+
+ var lastMinutes = $(element).attr('data-last-minutes') || 3,
+ translations = JSON.parse($(element).attr('data-translations'));
+
+ var ajaxRequest = new ajaxHelper();
+ ajaxRequest.addParams({
+ module: 'API',
+ method: 'Live.getCounters',
+ format: 'json',
+ lastMinutes: lastMinutes
+ }, 'get');
+ ajaxRequest.setFormat('json');
+ ajaxRequest.setCallback(function (data) {
+ data = data[0];
+
+ // set text and tooltip of visitors count metric
+ var visitors = data['visitors'];
+ if (visitors == 1) {
+ var visitorsCountMessage = translations['one_visitor'];
+ }
+ else {
+ var visitorsCountMessage = translations['visitors'].replace('%s', visitors);
+ }
+ $('.simple-realtime-visitor-counter', element)
+ .attr('title', visitorsCountMessage)
+ .find('div').text(visitors);
+
+ // set text of individual metrics spans
+ var metrics = $('.simple-realtime-metric', element);
+
+ var visitsText = data['visits'] == 1
+ ? translations['one_visit'] : translations['visits'].replace('%s', data['visits']);
+ $(metrics[0]).text(visitsText);
+
+ var actionsText = data['actions'] == 1
+ ? translations['one_action'] : translations['actions'].replace('%s', data['actions']);
+ $(metrics[1]).text(actionsText);
+
+ var lastMinutesText = lastMinutes == 1
+ ? translations['one_minute'] : translations['minutes'].replace('%s', lastMinutes);
+ $(metrics[2]).text(lastMinutesText);
+
+ // schedule another request
+ setTimeout(function () { refreshWidget(element, refreshAfterXSecs); }, refreshAfterXSecs * 1000);
+ });
+ ajaxRequest.send(true);
+ };
+
+ var initSimpleRealtimeVisitorWidget = function (refreshAfterXSecs) {
+ $('.simple-realtime-visitor-widget').each(function () {
+ var self = this;
+ if ($(self).attr('data-inited')) {
+ return;
+ }
+
+ $(self).attr('data-inited', 1);
+
+ setTimeout(function () { refreshWidget(self, refreshAfterXSecs); }, refreshAfterXSecs * 1000);
+ });
+ };
+
+ initSimpleRealtimeVisitorWidget({/literal}{$refreshAfterXSecs}{literal});
+ });
</script>
-<style>
-.simple-realtime-visitor-widget {
- text-align:center;
-}
-.simple-realtime-visitor-counter {
- background-color: #F1F0EB;
-
- -moz-border-radius: 10px;
- -webkit-border-radius: 10px;
- border-radius: 10px;
- display:inline-block;
- margin: 2em 0 1em 0;
-}
-.simple-realtime-visitor-counter > div {
- font-size: 4.0em;
- padding: .25em .5em .25em .5em;
- color:#444;
-}
-.simple-realtime-metric {
- font-style:italic;
- font-weight:bold;
- color:#333;
-}
-
-.simple-realtime-elaboration {
- margin: 1em 2em 1em 2em;
- color:#666;
- display:inline-block;
-}
-</style>
+ <style>
+ .simple-realtime-visitor-widget {
+ text-align: center;
+ }
+
+ .simple-realtime-visitor-counter {
+ background-color: #F1F0EB;
+
+ -moz-border-radius: 10px;
+ -webkit-border-radius: 10px;
+ border-radius: 10px;
+ display: inline-block;
+ margin: 2em 0 1em 0;
+ }
+
+ .simple-realtime-visitor-counter > div {
+ font-size: 4.0em;
+ padding: .25em .5em .25em .5em;
+ color: #444;
+ }
+
+ .simple-realtime-metric {
+ font-style: italic;
+ font-weight: bold;
+ color: #333;
+ }
+
+ .simple-realtime-elaboration {
+ margin: 1em 2em 1em 2em;
+ color: #666;
+ display: inline-block;
+ }
+ </style>
{/literal}
<div class='simple-realtime-visitor-widget' data-last-minutes="{$lastMinutes}" data-translations="{$translations|@json_encode|escape:'html'}">
- <div class='simple-realtime-visitor-counter' title="{if $visitors eq 1}{'Live_NbVisitor'|translate}{else}{'Live_NbVisitors'|translate:$visitors}{/if}">
- <div>{$visitors}</div>
- </div>
- <br/>
- <div class='simple-realtime-elaboration'>
- {capture assign="visitsMessage"}<span class="simple-realtime-metric" data-metric="visits">{if $visits eq 1}{'General_OneVisit'|translate}{else}{'General_NVisits'|translate:$visits}{/if}</span>{/capture}
- {capture assign="actionsMessage"}<span class="simple-realtime-metric" data-metric="actions">{if $actions eq 1}{'General_OneAction'|translate}{else}{'VisitsSummary_NbActionsDescription'|translate:$actions}{/if}</span>{/capture}
- {capture assign="minutesMessage"}<span class="simple-realtime-metric" data-metric="minutes">{if $lastMinutes eq 1}{'General_OneMinute'|translate}{else}{'General_NMinutes'|translate:$lastMinutes}{/if}</span>{/capture}
-
- {'Live_SimpleRealTimeWidget_Message'|translate:$visitsMessage:$actionsMessage:$minutesMessage}
- </div>
+ <div class='simple-realtime-visitor-counter' title="{if $visitors eq 1}{'Live_NbVisitor'|translate}{else}{'Live_NbVisitors'|translate:$visitors}{/if}">
+ <div>{$visitors}</div>
+ </div>
+ <br/>
+
+ <div class='simple-realtime-elaboration'>
+ {capture assign="visitsMessage"}<span class="simple-realtime-metric"
+ data-metric="visits">{if $visits eq 1}{'General_OneVisit'|translate}{else}{'General_NVisits'|translate:$visits}{/if}</span>{/capture}
+ {capture assign="actionsMessage"}<span class="simple-realtime-metric"
+ data-metric="actions">{if $actions eq 1}{'General_OneAction'|translate}{else}{'VisitsSummary_NbActionsDescription'|translate:$actions}{/if}</span>{/capture}
+ {capture assign="minutesMessage"}<span class="simple-realtime-metric"
+ data-metric="minutes">{if $lastMinutes eq 1}{'General_OneMinute'|translate}{else}{'General_NMinutes'|translate:$lastMinutes}{/if}</span>{/capture}
+
+ {'Live_SimpleRealTimeWidget_Message'|translate:$visitsMessage:$actionsMessage:$minutesMessage}
+ </div>
</div>
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 @@
<div id="visitsTotal">
- <table class="dataTable" cellspacing="0">
- <thead>
- <tr>
- <th id="label" class="sortable label" style="cursor: auto;">
- <div id="thDIV">{'General_Date'|translate}</div></th>
- <th id="label" class="sortable label" style="cursor: auto;">
- <div id="thDIV">{'General_ColumnNbVisits'|translate}</div></th>
- <th id="label" class="sortable label" style="cursor: auto;">
- <div id="thDIV">{'General_ColumnPageviews'|translate}</div></th>
- </tr>
- </thead>
- <tbody>
- <tr class="">
- <td class="columnodd">{'Live_LastHours'|translate:24}</td>
- <td class="columnodd">{$visitorsCountToday}</td>
- <td class="columnodd">{$pisToday}</td>
- </tr>
- <tr class="">
- <td class="columnodd">{'Live_LastMinutes'|translate:30}</td>
- <td class="columnodd">{$visitorsCountHalfHour}</td>
- <td class="columnodd">{$pisHalfhour}</td>
- </tr>
- </tbody>
- </table>
+ <table class="dataTable" cellspacing="0">
+ <thead>
+ <tr>
+ <th id="label" class="sortable label" style="cursor: auto;">
+ <div id="thDIV">{'General_Date'|translate}</div>
+ </th>
+ <th id="label" class="sortable label" style="cursor: auto;">
+ <div id="thDIV">{'General_ColumnNbVisits'|translate}</div>
+ </th>
+ <th id="label" class="sortable label" style="cursor: auto;">
+ <div id="thDIV">{'General_ColumnPageviews'|translate}</div>
+ </th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr class="">
+ <td class="columnodd">{'Live_LastHours'|translate:24}</td>
+ <td class="columnodd">{$visitorsCountToday}</td>
+ <td class="columnodd">{$pisToday}</td>
+ </tr>
+ <tr class="">
+ <td class="columnodd">{'Live_LastMinutes'|translate:30}</td>
+ <td class="columnodd">{$visitorsCountHalfHour}</td>
+ <td class="columnodd">{$pisHalfhour}</td>
+ </tr>
+ </tbody>
+ </table>
</div>
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 @@
<div class="visitorLog dataTable" data-report="{$properties.uniqueId}" data-params="{$javascriptVariablesToSet|@json_encode|escape:'html'}">
{if !$isWidget}
- <h2>{if $javascriptVariablesToSet.filterEcommerce}{'Goals_EcommerceLog'|translate}{else}{'Live_VisitorLog'|translate}{/if}</h2>
-
- {if !empty($reportDocumentation)}
- <div class="reportDocumentation"><p>{$reportDocumentation}</p></div>
- {/if}
+ <h2>{if $javascriptVariablesToSet.filterEcommerce}{'Goals_EcommerceLog'|translate}{else}{'Live_VisitorLog'|translate}{/if}</h2>
+ {if !empty($reportDocumentation)}
+ <div class="reportDocumentation"><p>{$reportDocumentation}</p></div>
+ {/if}
{/if}
{capture assign='displayVisitorsInOwnColumn'}{if $isWidget}0{else}1{/if}{/capture}
<a graphid="VisitsSummarygetEvolutionGraph" name="evolutionGraph"></a>
{if isset($arrayDataTable.result) and $arrayDataTable.result == 'error'}
- {$arrayDataTable.message}
- {else}
- {if count($arrayDataTable) == 0}
- <a name="{$properties.uniqueId}"></a>
- <div class="pk-emptyDataTable">{'CoreHome_ThereIsNoDataForThisReport'|translate}</div>
- {else}
- <a name="{$properties.uniqueId}"></a>
+ {$arrayDataTable.message}
+{else}
+{if count($arrayDataTable) == 0}
+ <a name="{$properties.uniqueId}"></a>
+ <div class="pk-emptyDataTable">{'CoreHome_ThereIsNoDataForThisReport'|translate}</div>
+{else}
+ <a name="{$properties.uniqueId}"></a>
+ <table class="dataTable" cellspacing="0" width="100%" style="width:100%;">
+ <thead>
+ <tr>
+ <th style="display:none"></th>
+ <th id="label" class="sortable label" style="cursor: auto;width:12%" width="12%">
+ <div id="thDIV">{'General_Date'|translate}
+ <div>
+ </th>
+ {if $displayVisitorsInOwnColumn}
+ <th id="label" class="sortable label" style="cursor: auto;width:13%" width="13%">
+ <div id="thDIV">{'General_Visitors'|translate}
+ <div>
+ </th>
+ {/if}
+ <th id="label" class="sortable label" style="cursor: auto;width:15%" width="15%">
+ <div id="thDIV">{'Live_Referrer_URL'|translate}
+ <div>
+ </th>
+ <th id="label" class="sortable label" style="cursor: auto;width:62%" width="62%">
+ <div id="thDIV">{'General_ColumnNbActions'|translate}
+ <div>
+ </th>
+ </tr>
+ </thead>
+ <tbody>
- <table class="dataTable" cellspacing="0" width="100%" style="width:100%;">
- <thead>
- <tr>
- <th style="display:none"></th>
- <th id="label" class="sortable label" style="cursor: auto;width:12%" width="12%">
- <div id="thDIV">{'General_Date'|translate}<div></th>
- {if $displayVisitorsInOwnColumn}
- <th id="label" class="sortable label" style="cursor: auto;width:13%" width="13%">
- <div id="thDIV">{'General_Visitors'|translate}<div></th>
- {/if}
- <th id="label" class="sortable label" style="cursor: auto;width:15%" width="15%">
- <div id="thDIV">{'Live_Referrer_URL'|translate}<div></th>
- <th id="label" class="sortable label" style="cursor: auto;width:62%" width="62%">
- <div id="thDIV">{'General_ColumnNbActions'|translate}<div></th>
- </tr>
- </thead>
- <tbody>
+ {foreach from=$arrayDataTable item=visitor}
-{foreach from=$arrayDataTable item=visitor}
+ {capture assign='visitorColumnContent'}
+ &nbsp;
+ <img src="{$visitor.columns.countryFlag}" title="{$visitor.columns.location|escape:'html'}, Provider {$visitor.columns.provider|escape:'html'}"/>
+ &nbsp;
+ <img src="{$visitor.columns.browserIcon}" title="{$visitor.columns.browserName} with plugins {$visitor.columns.plugins} enabled"/>
+ &nbsp;
+ <img src="{$visitor.columns.operatingSystemIcon}"
+ title="{$visitor.columns.operatingSystem}, {$visitor.columns.resolution} ({$visitor.columns.screenType})"/>
+ {if $visitor.columns.visitorTypeIcon}
+ {if !empty($visitor.columns.visitorId)}
+ <a class="rightLink" href="javascript:Piwik_Live_LoadVisitorPopover('{$visitor.columns.visitorId}')">
+ {/if}
+ &nbsp;-
+ <img src="{$visitor.columns.visitorTypeIcon}"
+ title="{'General_ReturningVisitor'|translate}{if !empty($visitor.columns.visitorId)} - {'General_ReturningVisitorAllVisits'|translate}{/if}"/>
+ {if !empty($visitor.columns.visitorId)}</a>{/if}
+ {/if}
+
+ {if !$displayVisitorsInOwnColumn}<br/><br/>{/if}
- {capture assign='visitorColumnContent'}
- &nbsp;<img src="{$visitor.columns.countryFlag}" title="{$visitor.columns.location|escape:'html'}, Provider {$visitor.columns.provider|escape:'html'}" />
- &nbsp;<img src="{$visitor.columns.browserIcon}" title="{$visitor.columns.browserName} with plugins {$visitor.columns.plugins} enabled" />
- &nbsp;<img src="{$visitor.columns.operatingSystemIcon}" title="{$visitor.columns.operatingSystem}, {$visitor.columns.resolution} ({$visitor.columns.screenType})" />
- {if $visitor.columns.visitorTypeIcon}
- {if !empty($visitor.columns.visitorId)}
- <a class="rightLink" href="javascript:Piwik_Live_LoadVisitorPopover('{$visitor.columns.visitorId}')">
+ &nbsp;{if $visitor.columns.visitConverted}
+ <span title="{'General_VisitConvertedNGoals'|translate:$visitor.columns.goalConversions}" class='visitorRank'
+ {if !$displayVisitorsInOwnColumn}style='margin-left:0'{/if}>
+ <img src="{$visitor.columns.visitConvertedIcon}"/>
+ <span class='hash'>#</span>
+ {$visitor.columns.goalConversions}
+ {if $visitor.columns.visitEcommerceStatusIcon}
+ &nbsp;-
+ <img src="{$visitor.columns.visitEcommerceStatusIcon}" title="{$visitor.columns.visitEcommerceStatus}"/>
{/if}
- &nbsp;- <img src="{$visitor.columns.visitorTypeIcon}" title="{'General_ReturningVisitor'|translate}{if !empty($visitor.columns.visitorId)} - {'General_ReturningVisitorAllVisits'|translate}{/if}" />
- {if !empty($visitor.columns.visitorId)}</a>{/if}
- {/if}
-
- {if !$displayVisitorsInOwnColumn} <br/> <br/> {/if}
-
- &nbsp;{if $visitor.columns.visitConverted}
- <span title="{'General_VisitConvertedNGoals'|translate:$visitor.columns.goalConversions}" class='visitorRank' {if !$displayVisitorsInOwnColumn}style='margin-left:0'{/if}>
- <img src="{$visitor.columns.visitConvertedIcon}" />
- <span class='hash'>#</span>{$visitor.columns.goalConversions}
- {if $visitor.columns.visitEcommerceStatusIcon}
- &nbsp;- <img src="{$visitor.columns.visitEcommerceStatusIcon}" title="{$visitor.columns.visitEcommerceStatus}"/>
- {/if}
- </span>{/if}
- <br/>
- {if $displayVisitorsInOwnColumn}
- {if count($visitor.columns.pluginsIcons) > 0}
- <hr/>
- {'UserSettings_Plugins'|translate}:
- {foreach from=$visitor.columns.pluginsIcons item=pluginIcon name=plugins}
- <img src="{$pluginIcon.pluginIcon}" title="{$pluginIcon.pluginName|capitalize:true}" alt="{$pluginIcon.pluginName|capitalize:true}" />
- {/foreach}
- {/if}
- {/if}
- {/capture}
-
- {capture assign='visitorRow'}
- <tr class="label{cycle values='odd,even'}">
- <td style="display:none;"></td>
- <td class="label" style="width:12%" width="12%">
- <strong title="{if $visitor.columns.visitorType=='new'}{'General_NewVisitor'|translate}{else}{'Live_VisitorsLastVisit'|translate:$visitor.columns.daysSinceLastVisit}{/if}">
- {$visitor.columns.serverDatePrettyFirstAction}
- {if $isWidget}<br/>{else}-{/if} {$visitor.columns.serverTimePrettyFirstAction}</strong>
- {if !empty($visitor.columns.visitIp)} <br/><span title="{if !empty($visitor.columns.visitorId)}{'General_VisitorID'|translate}: {$visitor.columns.visitorId}{/if}{if $visitor.columns.latitude || $visitor.columns.longitude}
+ </span>{/if}
+ <br/>
+ {if $displayVisitorsInOwnColumn}
+ {if count($visitor.columns.pluginsIcons) > 0}
+ <hr/>
+ {'UserSettings_Plugins'|translate}:
+ {foreach from=$visitor.columns.pluginsIcons item=pluginIcon name=plugins}
+ <img src="{$pluginIcon.pluginIcon}" title="{$pluginIcon.pluginName|capitalize:true}" alt="{$pluginIcon.pluginName|capitalize:true}"/>
+ {/foreach}
+ {/if}
+ {/if}
+ {/capture}
+
+ {capture assign='visitorRow'}
+ <tr class="label{cycle values='odd,even'}">
+ <td style="display:none;"></td>
+ <td class="label" style="width:12%" width="12%">
+ <strong title="{if $visitor.columns.visitorType=='new'}{'General_NewVisitor'|translate}{else}{'Live_VisitorsLastVisit'|translate:$visitor.columns.daysSinceLastVisit}{/if}">
+ {$visitor.columns.serverDatePrettyFirstAction}
+ {if $isWidget}<br/>{else}-{/if} {$visitor.columns.serverTimePrettyFirstAction}</strong>
+ {if !empty($visitor.columns.visitIp)}
+ <br/>
+ <span title="{if !empty($visitor.columns.visitorId)}{'General_VisitorID'|translate}: {$visitor.columns.visitorId}{/if}{if $visitor.columns.latitude || $visitor.columns.longitude}
+
+ GPS (lat/long): {$visitor.columns.latitude|escape:'html'},{$visitor.columns.longitude|escape:'html'}{/if}">
+ IP: {$visitor.columns.visitIp}</span>{/if}
+
+ {if (isset($visitor.columns.provider)&&$visitor.columns.provider!='IP')}
+ <br/>
+ {'Provider_ColumnProvider'|translate}:
+ <a href="{$visitor.columns.providerUrl}" target="_blank" title="{$visitor.columns.providerUrl}" style="text-decoration:underline;">
+ {$visitor.columns.provider}
+ </a>
+ {/if}
+ {if !empty($visitor.columns.customVariables)}
+ <br/>
+ {foreach from=$visitor.columns.customVariables item=customVariable key=id}
+ {capture assign=name}customVariableName{$id}{/capture}
+ {capture assign=value}customVariableValue{$id}{/capture}
+ <br/>
+ <acronym
+ title="{'CustomVariables_CustomVariables'|translate} (index {$id})">{$customVariable.$name|truncate:30:"...":true|escape:'html'}</acronym>{if strlen($customVariable.$value)>0}: {$customVariable.$value|truncate:50:"...":true|escape:'html'}{/if}
+ {/foreach}
+ {/if}
+ {if !$displayVisitorsInOwnColumn}
+ <br/>
+ {$visitorColumnContent}
+ {/if}
+ </td>
+
+ {if $displayVisitorsInOwnColumn}
+ <td class="label" style="width:13%" width="13%">
+ {$visitorColumnContent}
+ </td>
+ {/if}
- GPS (lat/long): {$visitor.columns.latitude|escape:'html'},{$visitor.columns.longitude|escape:'html'}{/if}">IP: {$visitor.columns.visitIp}</span>{/if}
-
- {if (isset($visitor.columns.provider)&&$visitor.columns.provider!='IP')}
- <br />
- {'Provider_ColumnProvider'|translate}:
- <a href="{$visitor.columns.providerUrl}" target="_blank" title="{$visitor.columns.providerUrl}" style="text-decoration:underline;">
- {$visitor.columns.provider}
- </a>
- {/if}
- {if !empty($visitor.columns.customVariables)}
- <br/>
- {foreach from=$visitor.columns.customVariables item=customVariable key=id}
- {capture assign=name}customVariableName{$id}{/capture}
- {capture assign=value}customVariableValue{$id}{/capture}
- <br/><acronym title="{'CustomVariables_CustomVariables'|translate} (index {$id})">{$customVariable.$name|truncate:30:"...":true|escape:'html'}</acronym>{if strlen($customVariable.$value)>0}: {$customVariable.$value|truncate:50:"...":true|escape:'html'}{/if}
- {/foreach}
- {/if}
- {if !$displayVisitorsInOwnColumn}
- <br/>
- {$visitorColumnContent}
- {/if}
- </td>
-
- {if $displayVisitorsInOwnColumn}
- <td class="label" style="width:13%" width="13%">
- {$visitorColumnContent}
- </td>
- {/if}
-
- <td class="column" style="width:20%" width="20%">
- <div class="referer">
- {if $visitor.columns.referrerType == 'website'}
- {'Referers_ColumnWebsite'|translate}:
- <a href="{$visitor.columns.referrerUrl|escape:'html'}" target="_blank" title="{$visitor.columns.referrerUrl|escape:'html'}" style="text-decoration:underline;">
- {$visitor.columns.referrerName|escape:'html'}
- </a>
- {/if}
- {if $visitor.columns.referrerType == 'campaign'}
- {'Referers_ColumnCampaign'|translate}
- <br />
- {$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)}
- <img src="{$visitor.columns.searchEngineIcon}" alt="{$visitor.columns.referrerName|escape:'html'}" />
- {/if}
- {$visitor.columns.referrerName|escape:'html'}
- {if !empty($visitor.columns.referrerKeyword)}{'Referers_Keywords'|translate}:
- <br />
- <a href="{$visitor.columns.referrerUrl|escape:'html'}" target="_blank" style="text-decoration:underline;">
- "{$visitor.columns.referrerKeyword|escape:'html'}"</a>
- {/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)}<span title='{'Live_KeywordRankedOnSearchResultForThisVisitor'|translate:$keyword:$position:$searchName}' class='visitorRank'><span class='hash'>#</span>{$visitor.columns.referrerKeywordPosition}</span>{/if}
- {/if}
- {if $visitor.columns.referrerType == 'direct'}{'Referers_DirectEntry'|translate}{/if}
- </div>
- </td>
- <td class="column {if $visitor.columns.visitConverted && !$isWidget}highlightField{/if}" style="width:55%" width="55%">
- <strong>
- {$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}
- </strong>
- <br />
- <ol class='visitorLog'>
- {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}
+ <td class="column" style="width:20%" width="20%">
+ <div class="referer">
+ {if $visitor.columns.referrerType == 'website'}
+ {'Referers_ColumnWebsite'|translate}:
+ <a href="{$visitor.columns.referrerUrl|escape:'html'}" target="_blank" title="{$visitor.columns.referrerUrl|escape:'html'}"
+ style="text-decoration:underline;">
+ {$visitor.columns.referrerName|escape:'html'}
+ </a>
+ {/if}
+ {if $visitor.columns.referrerType == 'campaign'}
+ {'Referers_ColumnCampaign'|translate}
+ <br/>
+ {$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)}
+ <img src="{$visitor.columns.searchEngineIcon}" alt="{$visitor.columns.referrerName|escape:'html'}"/>
+ {/if}
+ {$visitor.columns.referrerName|escape:'html'}
+ {if !empty($visitor.columns.referrerKeyword)}{'Referers_Keywords'|translate}:
+ <br/>
+ <a href="{$visitor.columns.referrerUrl|escape:'html'}" target="_blank" style="text-decoration:underline;">
+ "{$visitor.columns.referrerKeyword|escape:'html'}"</a>
+ {/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)}<span
+ title='{'Live_KeywordRankedOnSearchResultForThisVisitor'|translate:$keyword:$position:$searchName}' class='visitorRank'>
+ <span class='hash'>#</span>
+ {$visitor.columns.referrerKeywordPosition}</span>{/if}
+ {/if}
+ {if $visitor.columns.referrerType == 'direct'}{'Referers_DirectEntry'|translate}{/if}
+ </div>
+ </td>
+ <td class="column {if $visitor.columns.visitConverted && !$isWidget}highlightField{/if}" style="width:55%" width="55%">
+ <strong>
+ {$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}
+ </strong>
+ <br/>
+ <ol class='visitorLog'>
+ {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'}
- <li class="{if !empty($action.goalName)}goal{else}action{/if}" title="{$action.serverTimePretty|escape:'html'}{if !empty($action.url) && strlen(trim($action.url))} - {$action.url|escape:'html'}{/if} {if strlen(trim($customVariablesTooltip))}
+ - {$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'}
+ <li class="{if !empty($action.goalName)}goal{else}action{/if}"
+ title="{$action.serverTimePretty|escape:'html'}{if !empty($action.url) && strlen(trim($action.url))} - {$action.url|escape:'html'}{/if} {if strlen(trim($customVariablesTooltip))}
{$customVariablesTooltip|trim}{/if}{if isset($action.timeSpentPretty)}
{'General_TimeOnPage'|translate}: {$action.timeSpentPretty}{/if}">
- {if $action.type == 'ecommerceOrder' || $action.type == 'ecommerceAbandonedCart'}
- {* Ecommerce Abandoned Cart / Ecommerce Order *}
-
- <img src="{$action.icon}" />
- {if $action.type == 'ecommerceOrder'}
- {capture assign='visitorHasSomeEcommerceActivity'}1{/capture}
- <strong>{'Goals_EcommerceOrder'|translate}</strong> <span style='color:#666666'>({$action.orderId})</span>
- {else}<strong>{'Goals_AbandonedCart'|translate}</strong>
-
- {* 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} <br/>
- <span {if !$isWidget}style='margin-left:20px'{/if}>
+ {if $action.type == 'ecommerceOrder' || $action.type == 'ecommerceAbandonedCart'}
+ {* Ecommerce Abandoned Cart / Ecommerce Order *}
+ <img src="{$action.icon}"/>
+ {if $action.type == 'ecommerceOrder'}
+ {capture assign='visitorHasSomeEcommerceActivity'}1{/capture}
+ <strong>{'Goals_EcommerceOrder'|translate}</strong>
+ <span style='color:#666666'>({$action.orderId})</span>
+ {else}<strong>{'Goals_AbandonedCart'|translate}</strong>
+
+ {* 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}
+ <br/>
+ <span {if !$isWidget}style='margin-left:20px'{/if}>
{if $action.type == 'ecommerceOrder'}
- <abbr title="
+ <abbr title="
{'Live_GoalRevenue'|translate}: {$action.revenue|money:$javascriptVariablesToSet.idSite}
{if !empty($action.revenueSubTotal)} - {'General_Subtotal'|translate}: {$action.revenueSubTotal|money:$javascriptVariablesToSet.idSite}{/if}
{if !empty($action.revenueTax)} - {'General_Tax'|translate}: {$action.revenueTax|money:$javascriptVariablesToSet.idSite}{/if}
{if !empty($action.revenueShipping)} - {'General_Shipping'|translate}: {$action.revenueShipping|money:$javascriptVariablesToSet.idSite}{/if}
{if !empty($action.revenueDiscount)} - {'General_Discount'|translate}: {$action.revenueDiscount|money:$javascriptVariablesToSet.idSite}{/if}
">{'Live_GoalRevenue'|translate}:
- {else}
- {capture assign='revenueLeft'}{'Live_GoalRevenue'|translate}{/capture}{'Goals_LeftInCart'|translate:$revenueLeft}:
- {/if}
- <strong>{$action.revenue|money:$javascriptVariablesToSet.idSite}</strong>{if $action.type == 'ecommerceOrder'}</abbr>{/if},
- {'General_Quantity'|translate}: {$action.items}
-
- {* Ecommerce items in Cart/Order *}
- {if !empty($action.itemDetails)}
- <ul style='list-style:square;margin-left:{if $isWidget}15{else}50{/if}px'>
- {foreach from=$action.itemDetails item=product}
- <li>{$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}
- </li>
- {/foreach}
- </ul>
- {/if}
+ {else}
+ {capture assign='revenueLeft'}{'Live_GoalRevenue'|translate}{/capture}{'Goals_LeftInCart'|translate:$revenueLeft}
+ :
+ {/if}
+ <strong>{$action.revenue|money:$javascriptVariablesToSet.idSite}</strong>{if $action.type == 'ecommerceOrder'}
+ </abbr>{/if},
+ {'General_Quantity'|translate}: {$action.items}
+
+ {* Ecommerce items in Cart/Order *}
+ {if !empty($action.itemDetails)}
+ <ul style='list-style:square;margin-left:{if $isWidget}15{else}50{/if}px'>
+ {foreach from=$action.itemDetails item=product}
+ <li>{$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}
+ </li>
+ {/foreach}
+ </ul>
+ {/if}
</span>
-
- {elseif empty($action.goalName)}
- {* Page view / Download / Outlink *}
- {if !empty($action.pageTitle)}
- {if $action.type == 'search'}<img src='{$action.icon}' title='{'Actions_SubmenuSitesearch'|translate|escape:'html'}'>{/if}
- {$action.pageTitle|unescape|urldecode|escape:'html'|truncate:80:"...":true}
- {/if}
- {if !empty($action.url)}
- {if $action.type == 'action' && !empty($action.pageTitle)}<br/>{/if}
- {if $action.type == 'download'
- || $action.type == 'outlink'}
- <img src='{$action.icon}'>
- {/if}
- <a href="{$action.url|escape:'html'}" target="_blank" style="{if $action.type=='action' && !empty($action.pageTitle)}margin-left: 25px;{/if}text-decoration:underline;">{$action.url|escape:'html'|truncate:80:"...":true}</a>
- {elseif $action.type!='search'}
- <br/>
- <span style="margin-left: 25px;">{$javascriptVariablesToSet.pageUrlNotDefined}</span>
- {/if}
- {else}
- {* Goal conversion *}
- <img src="{$action.icon}" />
- <strong>{$action.goalName|escape:'html'}</strong>
- {if $action.revenue > 0}, {'Live_GoalRevenue'|translate}: <strong>{$action.revenue|money:$javascriptVariablesToSet.idSite}</strong>{/if}
- {/if}
- </li>
- {/if}
- {/foreach}
- </ol>
- </td>
- </tr>
- {/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'}<img src='{$action.icon}'
+ title='{'Actions_SubmenuSitesearch'|translate|escape:'html'}'>{/if}
+ {$action.pageTitle|unescape|urldecode|escape:'html'|truncate:80:"...":true}
+ {/if}
+ {if !empty($action.url)}
+ {if $action.type == 'action' && !empty($action.pageTitle)}<br/>{/if}
+ {if $action.type == 'download'
+ || $action.type == 'outlink'}
+ <img src='{$action.icon}'>
+ {/if}
+ <a href="{$action.url|escape:'html'}" target="_blank"
+ style="{if $action.type=='action' && !empty($action.pageTitle)}margin-left: 25px;{/if}text-decoration:underline;">{$action.url|escape:'html'|truncate:80:"...":true}</a>
+ {elseif $action.type!='search'}
+ <br/>
+ <span style="margin-left: 25px;">{$javascriptVariablesToSet.pageUrlNotDefined}</span>
+ {/if}
+ {else}
+ {* Goal conversion *}
+ <img src="{$action.icon}"/>
+ <strong>{$action.goalName|escape:'html'}</strong>
+ {if $action.revenue > 0}, {'Live_GoalRevenue'|translate}:
+ <strong>{$action.revenue|money:$javascriptVariablesToSet.idSite}</strong>{/if}
+ {/if}
+ </li>
+ {/if}
+ {/foreach}
+ </ol>
+ </td>
+ </tr>
+ {/capture}
-</tbody>
-</table>
-{/if}
+ {if !$javascriptVariablesToSet.filterEcommerce
+ || !empty($visitorHasSomeEcommerceActivity)}
+ {$visitorRow}
+ {/if}
+ {/foreach}
-{if $properties.show_footer}
- {include file="CoreHome/templates/datatable_footer.tpl"}
+ </tbody>
+ </table>
{/if}
-{include file="CoreHome/templates/datatable_js.tpl"}
-<script type="text/javascript" defer="defer">
+ {if $properties.show_footer}
+ {include file="CoreHome/templates/datatable_footer.tpl"}
+ {/if}
+
+ {include file="CoreHome/templates/datatable_js.tpl"}
+ <script type="text/javascript" defer="defer">
-var visitorLogTitle = '{'Live_VisitorLog'|translate|escape:'javascript'}';
-function Piwik_Live_LoadVisitorPopover(visitorId)
-{ldelim}
- var startingDate = piwik.minDateYear +'-01-01';
- var url = 'module=Live&action=getVisitorLog&period=range&date='+ startingDate +',today&show_footer=0&segment=visitorId'+encodeURIComponent('==')+visitorId;
- return Piwik_Popover.createPopupAndLoadUrl(url,visitorLogTitle);
-{rdelim}
+ var visitorLogTitle = '{'Live_VisitorLog'|translate|escape:'javascript'}';
+ function Piwik_Live_LoadVisitorPopover(visitorId) {ldelim}
+ var startingDate = piwik.minDateYear + '-01-01';
+ var url = 'module=Live&action=getVisitorLog&period=range&date=' + startingDate + ',today&show_footer=0&segment=visitorId' + encodeURIComponent('==') + visitorId;
+ return Piwik_Popover.createPopupAndLoadUrl(url, visitorLogTitle);
+ {rdelim
+ }
-$(document).ready(function(){ldelim}
- {literal}
- // Replace duplicated page views by a NX count instead of using too much vertical space
- $("ol.visitorLog").each(function () {
- var prevelement;
- var prevhtml;
- var counter = 0;
- $(this).find("li").each(function () {
- counter++;
- $(this).val(counter);
- var current = $(this).html();
- if (current == prevhtml) {
- var repeat = prevelement.find(".repeat")
- if (repeat.length) {
- repeat.html( (parseInt(repeat.html()) + 1) + "x" );
- } else {
- prevelement.append($("<em title='{/literal}{'Live_PageRefreshed'|translate|escape:'js'}{literal}' class='repeat'>2x</em>"));
- }
- $(this).hide();
- } else {
- prevhtml = current;
- prevelement = $(this);
- }
+ $(document).ready(function () {ldelim}
+ {literal}
+ // Replace duplicated page views by a NX count instead of using too much vertical space
+ $("ol.visitorLog").each(function () {
+ var prevelement;
+ var prevhtml;
+ var counter = 0;
+ $(this).find("li").each(function () {
+ counter++;
+ $(this).val(counter);
+ var current = $(this).html();
+ if (current == prevhtml) {
+ var repeat = prevelement.find(".repeat")
+ if (repeat.length) {
+ repeat.html((parseInt(repeat.html()) + 1) + "x");
+ } else {
+ prevelement.append($("<em title='{/literal}{'Live_PageRefreshed'|translate|escape:'js'}{literal}' class='repeat'>2x</em>"));
+ }
+ $(this).hide();
+ } else {
+ prevhtml = current;
+ prevelement = $(this);
+ }
+ });
+ });
});
- });
-});
-{/literal}
-</script>
+ {/literal}
+ </script>
{/if}
{literal}
-<style type="text/css">
-hr {
- background:none repeat scroll 0 0 transparent;
- border: 0 none #000;
- border-bottom: 1px solid #ccc;
- color:#eee;
- margin:0 2em 0.5em;
- padding:0 0 0.5em;
-}
+ <style type="text/css">
+ hr {
+ background: none repeat scroll 0 0 transparent;
+ border: 0 none #000;
+ border-bottom: 1px solid #ccc;
+ color: #eee;
+ margin: 0 2em 0.5em;
+ padding: 0 0 0.5em;
+ }
-</style>
+ </style>
{/literal}
</div>
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('<a href="?module=Proxy&action=redirect&url='.urlencode('http://piwik.org/faq/how-to-install/#faq_98').'" target="_blank">', '</a>'));
- // 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().'<br/>'.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)","<br/>")));
- }
-
- $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('<a href="?module=Proxy&action=redirect&url=' . urlencode('http://piwik.org/faq/how-to-install/#faq_98') . '" target="_blank">', '</a>'));
+ // 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() . '<br/>' . 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)", "<br/>")));
+ }
+
+ $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 @@
<!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 &rsaquo; {/if}{'Login_LogIn'|translate}</title>
- <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
- <link rel="shortcut icon" href="plugins/CoreHome/templates/images/favicon.ico" />
- <link rel="stylesheet" type="text/css" href="plugins/Login/templates/login.css" />
- <meta name="description" content="{'General_OpenSourceWebAnalytics'|translate|escape}" />
- <!--[if lt IE 9]>
- <script src="libs/html5shiv/html5shiv.js"></script>
- <![endif]-->
- <script type="text/javascript" src="libs/jquery/jquery.js"></script>
- <script type="text/javascript" src="libs/jquery/jquery.placeholder.js"></script>
-{if isset($forceSslLogin) && $forceSslLogin}
-{literal}
- <script type="text/javascript">
- if(window.location.protocol !== 'https:') {
- var url = window.location.toString();
- url = url.replace(/^http:/, 'https:');
- window.location.replace(url);
- }
- </script>
-{/literal}
-{/if}
-{literal}
- <script type="text/javascript">
- $(function() {
- $('#form_login').focus();
- $('input').placeholder();
- });
- </script>
-{/literal}
- <script type="text/javascript" src="plugins/Login/templates/login.js"></script>
-{if 'General_LayoutDirection'|translate =='rtl'}
-<link rel="stylesheet" type="text/css" href="themes/default/rtl.css" />
-{/if}
-{include file="CoreHome/templates/iframe_buster_header.tpl"}
+ <title>{if !$isCustomLogo}Piwik &rsaquo; {/if}{'Login_LogIn'|translate}</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
+ <link rel="shortcut icon" href="plugins/CoreHome/templates/images/favicon.ico"/>
+ <link rel="stylesheet" type="text/css" href="plugins/Login/templates/login.css"/>
+ <meta name="description" content="{'General_OpenSourceWebAnalytics'|translate|escape}"/>
+ <!--[if lt IE 9]>
+ <script src="libs/html5shiv/html5shiv.js"></script>
+ <![endif]-->
+ <script type="text/javascript" src="libs/jquery/jquery.js"></script>
+ <script type="text/javascript" src="libs/jquery/jquery.placeholder.js"></script>
+ {if isset($forceSslLogin) && $forceSslLogin}
+ {literal}
+ <script type="text/javascript">
+ if (window.location.protocol !== 'https:') {
+ var url = window.location.toString();
+ url = url.replace(/^http:/, 'https:');
+ window.location.replace(url);
+ }
+ </script>
+ {/literal}
+ {/if}
+ {literal}
+ <script type="text/javascript">
+ $(function () {
+ $('#form_login').focus();
+ $('input').placeholder();
+ });
+ </script>
+ {/literal}
+ <script type="text/javascript" src="plugins/Login/templates/login.js"></script>
+ {if 'General_LayoutDirection'|translate =='rtl'}
+ <link rel="stylesheet" type="text/css" href="themes/default/rtl.css"/>
+ {/if}
+ {include file="CoreHome/templates/iframe_buster_header.tpl"}
</head>
<body class="login">
{include file="CoreHome/templates/iframe_buster_body.tpl"}
- <div id="logo">
- {if !$isCustomLogo}<a href="http://piwik.org" title="{$linkTitle}">{/if}
- {if $hasSVGLogo}
- <img src='{$logoSVG}' title="{$linkTitle}" alt="Piwik" width="240" style='margin-right: 20px' class="ie-hide" />
- <!--[if lt IE 9]>
- {/if}
- <img src='{$logoLarge}' title="{$linkTitle}" alt="Piwik" width="240" style='margin-right:20px' />
- {if $hasSVGLogo}<![endif]-->{/if}
- {if $isCustomLogo}
- {capture name='poweredByPiwik'}
- <i><a href="http://piwik.org/" target="_blank">{$linkTitle}</a></i>
- {/capture}
- {/if}
- {if !$isCustomLogo}</a>
- <div class="description"><a href="http://piwik.org" title="{$linkTitle}">{$linkTitle}</a>
- <div class="arrow"> </div>
- </div>
- {/if}
- </div>
+<div id="logo">
+ {if !$isCustomLogo}<a href="http://piwik.org" title="{$linkTitle}">{/if}
+ {if $hasSVGLogo}
+ <img src='{$logoSVG}' title="{$linkTitle}" alt="Piwik" width="240" style='margin-right: 20px' class="ie-hide"/>
+ <!--[if lt IE 9]>
+ {/if}
+ <img src='{$logoLarge}' title="{$linkTitle}" alt="Piwik" width="240" style='margin-right:20px'/>
+ {if $hasSVGLogo}<![endif]-->{/if}
+ {if $isCustomLogo}
+ {capture name='poweredByPiwik'}
+ <i><a href="http://piwik.org/" target="_blank">{$linkTitle}</a></i>
+ {/capture}
+ {/if}
+ {if !$isCustomLogo}</a>
+
+ <div class="description"><a href="http://piwik.org" title="{$linkTitle}">{$linkTitle}</a>
+
+ <div class="arrow"></div>
+ </div>
+ {/if}
+</div>
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('<div id="login_error"><strong>HTTP Error</strong></div>'); },
- 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('<div id="login_error"><strong>HTTP Error</strong></div>'); },
+ 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 @@
<section id="login">
-{* untrusted host warning *}
-{if isset($isValidHost) && isset($invalidHostMessage) && !$isValidHost}
-<div id="login_error">
- <strong>{'General_Warning'|translate}:&nbsp;</strong>{$invalidHostMessage}
+ {* untrusted host warning *}
+ {if isset($isValidHost) && isset($invalidHostMessage) && !$isValidHost}
+ <div id="login_error">
+ <strong>{'General_Warning'|translate}:&nbsp;</strong>{$invalidHostMessage}
- <br><br>{$invalidHostMessageHowToFix}
- <br/><br/><a style="float:right" href="http://piwik.org/faq/troubleshooting/#faq_171" target="_blank">{'General_Help'|translate} <img style='vertical-align: bottom' src="themes/default/images/help_grey.png" /></a><br/>
+ <br><br>{$invalidHostMessageHowToFix}
+ <br/><br/><a style="float:right" href="http://piwik.org/faq/troubleshooting/#faq_171" target="_blank">{'General_Help'|translate} <img
+ style='vertical-align: bottom' src="themes/default/images/help_grey.png"/></a><br/>
-</div>
-{else}
-<div id="message_container">
- {if $form_data.errors}
- <div id="login_error">
- {foreach from=$form_data.errors item=data}
- <strong>{'General_Error'|translate}</strong>: {$data}<br />
- {/foreach}
- </div>
- {/if}
+ </div>
+ {else}
+ <div id="message_container">
+ {if $form_data.errors}
+ <div id="login_error">
+ {foreach from=$form_data.errors item=data}
+ <strong>{'General_Error'|translate}</strong>
+ : {$data}
+ <br/>
+ {/foreach}
+ </div>
+ {/if}
- {if $AccessErrorString}
- <div id="login_error"><strong>{'General_Error'|translate}</strong>: {$AccessErrorString}<br /></div>
- {/if}
+ {if $AccessErrorString}
+ <div id="login_error"><strong>{'General_Error'|translate}</strong>: {$AccessErrorString}<br/></div>
+ {/if}
- {if $infoMessage}
- <p class="message">{$infoMessage}</p>
- {/if}
-</div>
+ {if $infoMessage}
+ <p class="message">{$infoMessage}</p>
+ {/if}
+ </div>
+ <form {$form_data.attributes}>
+ <h1>{'Login_LogIn'|translate}</h1>
+ <fieldset class="inputs">
+ <input type="text" name="form_login" id="login_form_login" class="input" value="" size="20" tabindex="10"
+ placeholder="{'General_Username'|translate}" autofocus="autofocus"/>
+ <input type="password" name="form_password" id="login_form_password" class="input" value="" size="20" tabindex="20"
+ placeholder="{'Login_Password'|translate}"/>
+ <input type="hidden" name="form_nonce" id="login_form_nonce" value="{$nonce}"/>
+ </fieldset>
-<form {$form_data.attributes}>
- <h1>{'Login_LogIn'|translate}</h1>
- <fieldset class="inputs">
- <input type="text" name="form_login" id="login_form_login" class="input" value="" size="20" tabindex="10" placeholder="{'General_Username'|translate}" autofocus="autofocus" />
- <input type="password" name="form_password" id="login_form_password" class="input" value="" size="20" tabindex="20" placeholder="{'Login_Password'|translate}" />
- <input type="hidden" name="form_nonce" id="login_form_nonce" value="{$nonce}" />
- </fieldset>
+ <fieldset class="actions">
+ <input name="form_rememberme" type="checkbox" id="login_form_rememberme" value="1" tabindex="90"
+ {if $form_data.form_rememberme.value}checked="checked" {/if}/>
+ <label for="login_form_rememberme">{'Login_RememberMe'|translate}</label>
+ <input class="submit" id='login_form_submit' type="submit" value="{'Login_LogIn'|translate}" tabindex="100"/>
+ </fieldset>
+ </form>
+ <form id="reset_form" style="display:none;">
+ <fieldset class="inputs">
+ <input type="text" name="form_login" id="reset_form_login" class="input" value="" size="20" tabindex="10"
+ placeholder="{'Login_LoginOrEmail'|translate}"/>
+ <input type="hidden" name="form_nonce" id="reset_form_nonce" value="{$nonce}"/>
- <fieldset class="actions">
- <input name="form_rememberme" type="checkbox" id="login_form_rememberme" value="1" tabindex="90" {if $form_data.form_rememberme.value}checked="checked" {/if}/>
- <label for="login_form_rememberme">{'Login_RememberMe'|translate}</label>
- <input class="submit" id='login_form_submit' type="submit" value="{'Login_LogIn'|translate}" tabindex="100" />
- </fieldset>
-</form>
+ <input type="password" name="form_password" id="reset_form_password" class="input" value="" size="20" tabindex="20"
+ placeholder="{'Login_Password'|translate}"/>
-<form id="reset_form" style="display:none;">
- <fieldset class="inputs">
- <input type="text" name="form_login" id="reset_form_login" class="input" value="" size="20" tabindex="10" placeholder="{'Login_LoginOrEmail'|translate}" />
- <input type="hidden" name="form_nonce" id="reset_form_nonce" value="{$nonce}" />
+ <input type="password" name="form_password_bis" id="reset_form_password_bis" class="input" value="" size="20" tabindex="30"
+ placeholder="{'Login_PasswordRepeat'|translate}"/>
+ </fieldset>
- <input type="password" name="form_password" id="reset_form_password" class="input" value="" size="20" tabindex="20" placeholder="{'Login_Password'|translate}" />
+ <fieldset class="actions">
+ <span class="loadingPiwik" style="display:none;"><img alt="Loading" src="themes/default/images/loading-blue.gif"/></span>
+ <input class="submit" id='reset_form_submit' type="submit" value="{'Login_ChangePassword'|translate}" tabindex="100"/>
+ </fieldset>
- <input type="password" name="form_password_bis" id="reset_form_password_bis" class="input" value="" size="20" tabindex="30" placeholder="{'Login_PasswordRepeat'|translate}" />
- </fieldset>
-
- <fieldset class="actions">
- <span class="loadingPiwik" style="display:none;"><img alt="Loading" src="themes/default/images/loading-blue.gif" /></span>
- <input class="submit" id='reset_form_submit' type="submit" value="{'Login_ChangePassword'|translate}" tabindex="100"/>
- </fieldset>
-
- <input type="hidden" name="module" value="Login"/>
- <input type="hidden" name="action" value="resetPassword"/>
-</form>
-
-<p id="nav">
-<a id="login_form_nav" href="#" title="{'Login_LostYourPassword'|translate}">{'Login_LostYourPassword'|translate}</a>
-<a id="alternate_reset_nav" href="#" style="display:none;" title="{'Login_LogIn'|translate}">{'Login_LogIn'|translate}</a>
-<a id="reset_form_nav" href="#" style="display:none;" title="{'Mobile_NavigationBack'|translate}">{'General_Cancel'|translate}</a>
-</p>
-{if isset($smarty.capture.poweredByPiwik)}
- <p id="piwik">
- {$smarty.capture.poweredByPiwik}
- </p>
-{/if}
-
-<div id="lost_password_instructions" style="display:none;">
- <p class="message">{'Login_ResetPasswordInstructions'|translate}</p>
-</div>
-{/if}
+ <input type="hidden" name="module" value="Login"/>
+ <input type="hidden" name="action" value="resetPassword"/>
+ </form>
+ <p id="nav">
+ <a id="login_form_nav" href="#" title="{'Login_LostYourPassword'|translate}">{'Login_LostYourPassword'|translate}</a>
+ <a id="alternate_reset_nav" href="#" style="display:none;" title="{'Login_LogIn'|translate}">{'Login_LogIn'|translate}</a>
+ <a id="reset_form_nav" href="#" style="display:none;" title="{'Mobile_NavigationBack'|translate}">{'General_Cancel'|translate}</a>
+ </p>
+ {if isset($smarty.capture.poweredByPiwik)}
+ <p id="piwik">
+ {$smarty.capture.poweredByPiwik}
+ </p>
+ {/if}
+ <div id="lost_password_instructions" style="display:none;">
+ <p class="message">{'Login_ResetPasswordInstructions'|translate}</p>
+ </div>
+ {/if}
</section>
</body>
</html>
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)}
-<p class="message">{$infoMessage}</p>
+ <p class="message">{$infoMessage}</p>
{/if}
{if isset($formErrors)}
-<p id="login_error">
- {foreach from=$formErrors item=data}
- <strong>{'General_Error'|translate}</strong>: {$data}<br />
- {/foreach}
-</p>
+ <p id="login_error">
+ {foreach from=$formErrors item=data}
+ <strong>{'General_Error'|translate}</strong>
+ : {$data}
+ <br/>
+ {/foreach}
+ </p>
{/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 @@
<?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_MobileMessaging
*/
@@ -15,324 +15,301 @@
*/
class Piwik_MobileMessaging extends Piwik_Plugin
{
- 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;
- }
+ 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 <a target="_blank" href="?module=Proxy&action=redirect&url=http://www.clockworksms.com/platforms/piwik/"><img src="plugins/MobileMessaging/images/Clockwork.png"/></a> to send SMS Reports from Piwik.<br/>
+ static public $availableSMSProviders = array(
+ 'Clockwork' => 'You can use <a target="_blank" href="?module=Proxy&action=redirect&url=http://www.clockworksms.com/platforms/piwik/"><img src="plugins/MobileMessaging/images/Clockwork.png"/></a> to send SMS Reports from Piwik.<br/>
<ul>
<li> First, <a target="_blank" href="?module=Proxy&action=redirect&url=http://www.clockworksms.com/platforms/piwik/">get an API Key from Clockwork</a> (Signup is free!)
</li><li> Enter your Clockwork API Key on this page. </li>
@@ -35,141 +35,138 @@ abstract class Piwik_MobileMessaging_SMSProvider
</li>
</ul>
',
- );
-
- /**
- * 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 @@
<script>
- $(function() {ldelim}
- resetReportParametersFunctions ['{$reportType}'] =
- function () {ldelim}
+ $(function () {ldelim}
+ resetReportParametersFunctions ['{$reportType}'] =
+ function () {ldelim}
- var reportParameters = {ldelim}
- 'phoneNumbers' : [],
- {rdelim};
+ var reportParameters = {ldelim}
+ 'phoneNumbers': [],
+ {rdelim};
- updateReportParametersFunctions['{$reportType}'](reportParameters);
- {rdelim};
+ updateReportParametersFunctions['{$reportType}'](reportParameters);
+ {rdelim
+ };
- updateReportParametersFunctions['{$reportType}'] =
- function (reportParameters) {ldelim}
+ updateReportParametersFunctions['{$reportType}'] =
+ function (reportParameters) {ldelim}
- if(reportParameters == null) return;
+ if (reportParameters == null) return;
- $('[name=phoneNumbers]').removeProp('checked');
- $(reportParameters.phoneNumbers).each(function(index, phoneNumber) {ldelim}
- $('#\\'+phoneNumber).prop('checked','checked');
- {rdelim});
- {rdelim};
+ $('[name=phoneNumbers]').removeProp('checked');
+ $(reportParameters.phoneNumbers).each(function (index, phoneNumber) {ldelim}
+ $('#\\' + phoneNumber).prop('checked', 'checked');
+ {rdelim
+ });
+ {rdelim
+ };
- getReportParametersFunctions['{$reportType}'] =
- function () {ldelim}
+ getReportParametersFunctions['{$reportType}'] =
+ function () {ldelim}
- var parameters = Object();
+ var parameters = Object();
- var selectedPhoneNumbers =
- $.map(
- $('[name=phoneNumbers]:checked'),
- function (phoneNumber) {ldelim}
- return $(phoneNumber).attr('id');
- {rdelim}
- );
+ var selectedPhoneNumbers =
+ $.map(
+ $('[name=phoneNumbers]:checked'),
+ function (phoneNumber) {ldelim}
+ return $(phoneNumber).attr('id');
+ {rdelim
+ }
+ );
- // returning [''] when no phone numbers are selected avoids the "please provide a value for 'parameters'" error message
- parameters.phoneNumbers =
- selectedPhoneNumbers.length > 0 ? selectedPhoneNumbers : [''];
+ // returning [''] when no phone numbers are selected avoids the "please provide a value for 'parameters'" error message
+ parameters.phoneNumbers =
+ selectedPhoneNumbers.length > 0 ? selectedPhoneNumbers : [''];
- return parameters;
- {rdelim};
- {rdelim});
+ return parameters;
+ {rdelim
+ };
+ {rdelim
+ });
</script>
<tr class='{$reportType}'>
- <td class="first">
- {'MobileMessaging_MobileReport_PhoneNumbers'|translate}
- </td>
- <td>
- {if $phoneNumbers|@count eq 0}
- <div class="entityInlineHelp">
- {'MobileMessaging_MobileReport_NoPhoneNumbers'|translate}
- {else}
- {foreach from=$phoneNumbers item=phoneNumber}
- <label><input name='phoneNumbers' type='checkbox' id='{$phoneNumber}'/>{$phoneNumber}</label><br/>
- {/foreach}
- <div class="entityInlineHelp">
- {'MobileMessaging_MobileReport_AdditionalPhoneNumbers'|translate}
- {/if}
- <a href='{url module="MobileMessaging" updated=null}'>{'MobileMessaging_MobileReport_MobileMessagingSettingsLink'|translate}</a>
- </div>
- </td>
+ <td class="first">
+ {'MobileMessaging_MobileReport_PhoneNumbers'|translate}
+ </td>
+ <td>
+ {if $phoneNumbers|@count eq 0}
+ <div class="entityInlineHelp">
+ {'MobileMessaging_MobileReport_NoPhoneNumbers'|translate}
+ {else}
+ {foreach from=$phoneNumbers item=phoneNumber}
+ <label><input name='phoneNumbers' type='checkbox' id='{$phoneNumber}'/>{$phoneNumber}</label>
+ <br/>
+ {/foreach}
+ <div class="entityInlineHelp">
+ {'MobileMessaging_MobileReport_AdditionalPhoneNumbers'|translate}
+ {/if}
+ <a href='{url module="MobileMessaging" updated=null}'>{'MobileMessaging_MobileReport_MobileMessagingSettingsLink'|translate}</a>
+ </div>
+ </td>
</tr>
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}
-<style>#accountForm ul {
- list-style: circle;
- margin-left: 17px;
- line-height: 1.5em;
-}
-.providerDescription {
- border: 2px dashed #C5BDAD;
- border-radius: 16px 16px 16px 16px;
- margin-left: 24px;
- padding: 11px;
- width: 600px;
-}
-</style>
+ <style>#accountForm ul {
+ list-style: circle;
+ margin-left: 17px;
+ line-height: 1.5em;
+ }
+
+ .providerDescription {
+ border: 2px dashed #C5BDAD;
+ border-radius: 16px 16px 16px 16px;
+ margin-left: 24px;
+ padding: 11px;
+ width: 600px;
+ }
+ </style>
{/literal}
{if $accountManagedByCurrentUser}
-<h2>{'MobileMessaging_Settings_SMSAPIAccount'|translate}</h2>
- {if $credentialSupplied}
- {'MobileMessaging_Settings_CredentialProvided'|translate:$provider}
- {$creditLeft}
- <br/>
- {'MobileMessaging_Settings_UpdateOrDeleteAccount'|translate:"<a id='displayAccountForm'>":"</a>":"<a id='deleteAccount'>":"</a>"}
- {else}
- {'MobileMessaging_Settings_PleaseSignUp'|translate}
- {/if}
-
- <div id='accountForm' {if $credentialSupplied}style='display: none;'{/if}>
- <br/>
- {'MobileMessaging_Settings_SMSProvider'|translate}
- <select id='smsProviders'>
- {foreach from=$smsProviders key=smsProvider item=description}
- <option value='{$smsProvider}'>
- {$smsProvider}
- </option>
- {/foreach}
- </select>
-
- {'MobileMessaging_Settings_APIKey'|translate}
- <input size='25' id='apiKey'/>
-
- <input type='submit' value='{'General_Save'|translate}' id='apiAccountSubmit' class='submit' />
-
- {foreach from=$smsProviders key=smsProvider item=description}
- <div class='providerDescription' id='{$smsProvider}'>
- {$description}
- </div>
- {/foreach}
-
- </div>
+ <h2>{'MobileMessaging_Settings_SMSAPIAccount'|translate}</h2>
+ {if $credentialSupplied}
+ {'MobileMessaging_Settings_CredentialProvided'|translate:$provider}
+ {$creditLeft}
+ <br/>
+ {'MobileMessaging_Settings_UpdateOrDeleteAccount'|translate:"<a id='displayAccountForm'>":"</a>":"<a id='deleteAccount'>":"</a>"}
+ {else}
+ {'MobileMessaging_Settings_PleaseSignUp'|translate}
+ {/if}
+ <div id='accountForm' {if $credentialSupplied}style='display: none;'{/if}>
+ <br/>
+ {'MobileMessaging_Settings_SMSProvider'|translate}
+ <select id='smsProviders'>
+ {foreach from=$smsProviders key=smsProvider item=description}
+ <option value='{$smsProvider}'>
+ {$smsProvider}
+ </option>
+ {/foreach}
+ </select>
+
+ {'MobileMessaging_Settings_APIKey'|translate}
+ <input size='25' id='apiKey'/>
+
+ <input type='submit' value='{'General_Save'|translate}' id='apiAccountSubmit' class='submit'/>
+
+ {foreach from=$smsProviders key=smsProvider item=description}
+ <div class='providerDescription' id='{$smsProvider}'>
+ {$description}
+ </div>
+ {/foreach}
+
+ </div>
{/if}
{ajaxErrorDiv id=ajaxErrorMobileMessagingSettings}
<h2>{'MobileMessaging_Settings_PhoneNumbers'|translate}</h2>
{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}<br/><br/>
-
- <table style="width:900px;" class="adminTable">
- <tbody><tr>
- <td style="width:480px">
- <strong>{'MobileMessaging_Settings_PhoneNumbers_Add'|translate}</strong><br/><br/>
+ {'MobileMessaging_Settings_PhoneNumbers_Help'|translate}
+ <br/>
+ <br/>
+ <table style="width:900px;" class="adminTable">
+ <tbody>
+ <tr>
+ <td style="width:480px">
+ <strong>{'MobileMessaging_Settings_PhoneNumbers_Add'|translate}</strong><br/><br/>
<span id='suspiciousPhoneNumber' style='display:none;'>
{'MobileMessaging_Settings_SuspiciousPhoneNumber'|translate:'54184032'}<br/><br/>
</span>
-
- + <input id='countryCallingCode' size='4' maxlength='4'/>&nbsp;
- <input id='newPhoneNumber'/>
- <input
- type='submit'
- value='{'MobileMessaging_Settings_AddPhoneNumber'|translate}'
- id='addPhoneNumberSubmit'
- />
-
- <br/>
+
+ + <input id='countryCallingCode' size='4' maxlength='4'/>&nbsp;
+ <input id='newPhoneNumber'/>
+ <input
+ type='submit'
+ value='{'MobileMessaging_Settings_AddPhoneNumber'|translate}'
+ id='addPhoneNumberSubmit'
+ />
+
+ <br/>
<span style=' font-size: 11px;'><span class="form-description">{'MobileMessaging_Settings_CountryCode'|translate}</span>
<span class="form-description" style="margin-left:50px">{'MobileMessaging_Settings_PhoneNumber'|translate}</span></span>
- <br/><br/>
-
- {'MobileMessaging_Settings_PhoneNumbers_CountryCode_Help'|translate}
-
- <select id='countries'>
- <option value=''>&nbsp;</option> {* this is a trick to avoid selecting the first country when no default could be found *}
- {foreach from=$countries key=countryCode item=country}
- <option
- value='{$country.countryCallingCode}'
- {if $defaultCountry==$countryCode} selected='selected' {/if}
- >
- {$country.countryName|truncate:15:'...'}
- </option>
- {/foreach}
- </select>
-
- </td>
- <td style="width:220px">
- {$strHelpAddPhone|inlineHelp}
-
- </td></tr>
- <tr><td colspan="2">
-
- {if $phoneNumbers|@count gt 0}
- <br/>
- <br/>
- <strong>{'MobileMessaging_Settings_ManagePhoneNumbers'|translate}</strong><br/><br/>
- {/if}
-
- {ajaxErrorDiv id=invalidVerificationCodeAjaxError}
-
- <div id='phoneNumberActivated' class="ajaxSuccess" style="display:none;">
- {'MobileMessaging_Settings_PhoneActivated'|translate}
- </div>
-
- <div id='invalidActivationCode' style="display:none;">
- {'MobileMessaging_Settings_InvalidActivationCode'|translate}
- </div>
-
- <ul>
- {foreach from=$phoneNumbers key=phoneNumber item=validated}
- <li>
- <span class='phoneNumber'>{$phoneNumber}</span>
- {if !$validated}
- <input class='verificationCode'/>
- <input
- type='submit'
- value='{'MobileMessaging_Settings_ValidatePhoneNumber'|translate}'
- class='validatePhoneNumberSubmit'
- />
- {/if}
- <input
- type='submit'
- value='{'MobileMessaging_Settings_RemovePhoneNumber'|translate}'
- class='removePhoneNumberSubmit'
- />
- {if !$validated}
- <br/>
- <span class='form-description'>{'MobileMessaging_Settings_VerificationCodeJustSent'|translate}</span>
- {/if}
- <br/>
- <br/>
- </li>
- {/foreach}
- </ul>
-
- </td>
- </tr>
- </tbody></table>
+ <br/><br/>
+
+ {'MobileMessaging_Settings_PhoneNumbers_CountryCode_Help'|translate}
+
+ <select id='countries'>
+ <option value=''>&nbsp;</option> {* this is a trick to avoid selecting the first country when no default could be found *}
+ {foreach from=$countries key=countryCode item=country}
+ <option
+ value='{$country.countryCallingCode}'
+ {if $defaultCountry==$countryCode} selected='selected' {/if}
+ >
+ {$country.countryName|truncate:15:'...'}
+ </option>
+ {/foreach}
+ </select>
+
+ </td>
+ <td style="width:220px">
+ {$strHelpAddPhone|inlineHelp}
+
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+
+ {if $phoneNumbers|@count gt 0}
+ <br/>
+ <br/>
+ <strong>{'MobileMessaging_Settings_ManagePhoneNumbers'|translate}</strong>
+ <br/>
+ <br/>
+ {/if}
+
+ {ajaxErrorDiv id=invalidVerificationCodeAjaxError}
+
+ <div id='phoneNumberActivated' class="ajaxSuccess" style="display:none;">
+ {'MobileMessaging_Settings_PhoneActivated'|translate}
+ </div>
+
+ <div id='invalidActivationCode' style="display:none;">
+ {'MobileMessaging_Settings_InvalidActivationCode'|translate}
+ </div>
+
+ <ul>
+ {foreach from=$phoneNumbers key=phoneNumber item=validated}
+ <li>
+ <span class='phoneNumber'>{$phoneNumber}</span>
+ {if !$validated}
+ <input class='verificationCode'/>
+ <input
+ type='submit'
+ value='{'MobileMessaging_Settings_ValidatePhoneNumber'|translate}'
+ class='validatePhoneNumberSubmit'
+ />
+ {/if}
+ <input
+ type='submit'
+ value='{'MobileMessaging_Settings_RemovePhoneNumber'|translate}'
+ class='removePhoneNumberSubmit'
+ />
+ {if !$validated}
+ <br/>
+ <span class='form-description'>{'MobileMessaging_Settings_VerificationCodeJustSent'|translate}</span>
+ {/if}
+ <br/>
+ <br/>
+ </li>
+ {/foreach}
+ </ul>
+
+ </td>
+ </tr>
+ </tbody>
+ </table>
{/if}
{if $isSuperUser}
- <h2>{'MobileMessaging_Settings_SuperAdmin'|translate}</h2>
-
- <table class='adminTable' style='width:650px;'>
- <tr>
- <td style='width:400px'>{'MobileMessaging_Settings_LetUsersManageAPICredential'|translate}</td>
- <td style='width:250px'>
- <fieldset>
- <label>
- <input
- type='radio'
- value='false'
- name='delegatedManagement' {if !$delegatedManagement} checked='checked'{/if} />
- {'General_No'|translate}
- <br/>
- <span class='form-description'>({'General_Default'|translate}) {'MobileMessaging_Settings_LetUsersManageAPICredential_No_Help'|translate}</span>
- </label>
- <br/>
- <br/>
- <label>
- <input
- type='radio'
- value='true'
- name='delegatedManagement' {if $delegatedManagement} checked='checked'{/if} />
- {'General_Yes'|translate}
- <br/>
- <span class='form-description'>{'MobileMessaging_Settings_LetUsersManageAPICredential_Yes_Help'|translate}</span>
- </label>
-
- </fieldset>
- </tr>
- </table>
+ <h2>{'MobileMessaging_Settings_SuperAdmin'|translate}</h2>
+ <table class='adminTable' style='width:650px;'>
+ <tr>
+ <td style='width:400px'>{'MobileMessaging_Settings_LetUsersManageAPICredential'|translate}</td>
+ <td style='width:250px'>
+ <fieldset>
+ <label>
+ <input
+ type='radio'
+ value='false'
+ name='delegatedManagement' {if !$delegatedManagement} checked='checked'{/if} />
+ {'General_No'|translate}
+ <br/>
+ <span class='form-description'>({'General_Default'|translate}
+ ) {'MobileMessaging_Settings_LetUsersManageAPICredential_No_Help'|translate}</span>
+ </label>
+ <br/>
+ <br/>
+ <label>
+ <input
+ type='radio'
+ value='true'
+ name='delegatedManagement' {if $delegatedManagement} checked='checked'{/if} />
+ {'General_Yes'|translate}
+ <br/>
+ <span class='form-description'>{'MobileMessaging_Settings_LetUsersManageAPICredential_Yes_Help'|translate}</span>
+ </label>
+
+ </fieldset>
+ </tr>
+ </table>
{/if}
{ajaxLoadingDiv id=ajaxLoadingMobileMessagingSettings}
@@ -197,8 +204,8 @@
{include file='CoreAdminHome/templates/footer.tpl'}
<div class='ui-confirm' id='confirmDeleteAccount'>
- <h2>{'MobileMessaging_Settings_DeleteAccountConfirm'|translate}</h2>
- <input role='yes' type='button' value='{'General_Yes'|translate}' />
- <input role='no' type='button' value='{'General_No'|translate}' />
+ <h2>{'MobileMessaging_Settings_DeleteAccountConfirm'|translate}</h2>
+ <input role='yes' type='button' value='{'General_Yes'|translate}'/>
+ <input role='no' type='button' value='{'General_No'|translate}'/>
</div>
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 @@
<?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_MultiSites
*/
@@ -14,487 +14,447 @@
*/
class Piwik_MultiSites_API
{
- 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;
- }
+ 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 @@
<?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_MultiSites
*/
@@ -15,227 +15,208 @@
*/
class Piwik_MultiSites_Controller extends Piwik_Controller
{
- 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);
- }
+ 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('<tr class="tables_row" id="row_'+ allSites[i].idsite+'">' + str + '</tr>');
- }
-
- $(".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('<tr class="tables_row" id="row_' + allSites[i].idsite + '">' + str + '</tr>');
+ }
+
+ $(".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 '<img class="sparkline" alt="" src="?module=MultiSites&action=getEvolutionGraph&period=' + params['period'] + '&date=' + params['dateSparkline'] + '&evolutionBy=' + params['evolutionBy'] + '&columns=' + column + '&idSite=' + id + '&idsite=' + id + '&viewDataTable=sparkline'+ append +'" width="100" height="25" />';
+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 '<img class="sparkline" alt="" src="?module=MultiSites&action=getEvolutionGraph&period=' + params['period'] + '&date=' + params['dateSparkline'] + '&evolutionBy=' + params['evolutionBy'] + '&columns=' + column + '&idSite=' + id + '&idsite=' + id + '&viewDataTable=sparkline' + append + '" width="100" height="25" />';
}
-function showPagination(allSites, params)
-{
- if ((params['page'] * params['limit']) < allSites.length)
- {
- var html = '<span style="cursor:pointer;" class="pointer" onClick="changePage(allSites, params, \'next\');">' + params['next'] + ' &#187;</span>';
- $("#next").html(html);
- }
- if(params['page'] > 1)
- {
- html = '<span style="cursor:pointer;" onClick="changePage(allSites, params, \'prev\');">&#171; ' + params['prev'] + '</span>'
- $("#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 = '<span>' + (start ) + ' - ' + end + ' of ' + count + '</span>';
- $("#counter").html(html);
+function showPagination(allSites, params) {
+ if ((params['page'] * params['limit']) < allSites.length) {
+ var html = '<span style="cursor:pointer;" class="pointer" onClick="changePage(allSites, params, \'next\');">' + params['next'] + ' &#187;</span>';
+ $("#next").html(html);
+ }
+ if (params['page'] > 1) {
+ html = '<span style="cursor:pointer;" onClick="changePage(allSites, params, \'prev\');">&#171; ' + params['prev'] + '</span>'
+ $("#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 = '<span>' + (start ) + ' - ' + end + ' of ' + count + '</span>';
+ $("#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 '<img src="plugins/MultiSites/images/arrow_up.png" alt="" /> <b style="color: green;">' + value + '&nbsp;%</b>';
- }
- else if(value == 0)
- {
- return '<img src="plugins/MultiSites/images/stop.png" alt="" /> <b>' + value + '%</b>';
- }
- else
- {
- return '<img src="plugins/MultiSites/images/arrow_down.png" alt="" /> <b style="color: red;">' + value +'&nbsp;%</b>';
- }
+function getImageForSummary(value) {
+ if (value > 0) {
+ return '<img src="plugins/MultiSites/images/arrow_up.png" alt="" /> <b style="color: green;">' + value + '&nbsp;%</b>';
+ }
+ else if (value == 0) {
+ return '<img src="plugins/MultiSites/images/stop.png" alt="" /> <b>' + value + '%</b>';
+ }
+ else {
+ return '<img src="plugins/MultiSites/images/arrow_down.png" alt="" /> <b style="color: red;">' + value + '&nbsp;%</b>';
+ }
}
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}
<div id="multisites">
-<div id="main">
-{include file="MultiSites/templates/row.tpl" assign="row"}
-<script type="text/javascript">
- var allSites = new Array();
- var params = new Array();
- {foreach from=$sitesData key=i item=site}
- allSites[{$i}] = new setRowData({$site.idsite}, {$site.visits}, {$site.pageviews}, {if empty($site.revenue)}0{else}{$site.revenue}{/if}, '{$site.name|escape:"javascript"}', '{$site.main_url|escape:"javascript"}', '{if isset($site.visits_evolution)}{$site.visits_evolution|replace:",":"."}{/if}', '{if isset($site.pageviews_evolution)}{$site.pageviews_evolution|replace:",":"."}{/if}', '{if isset($site.revenue_evolution)}{$site.revenue_evolution|replace:",":"."}{/if}');
- {/foreach}
- params['period'] = '{$period}';
- params['date'] = '{$date}';
- params['evolutionBy'] = '{$evolutionBy}';
- params['mOrderBy'] = '{$orderBy}';
- params['order'] = '{$order}';
- params['limit'] = '{$limit}';
- params['page'] = 1;
- params['prev'] = "{'General_Previous'|translate|escape:"javascript"}";
- params['next'] = "{'General_Next'|translate|escape:"javascript"}";
- params['row'] = '{$row|escape:"javascript"}';
- params['dateSparkline'] = '{$dateSparkline}';
-</script>
+ <div id="main">
+ {include file="MultiSites/templates/row.tpl" assign="row"}
+ <script type="text/javascript">
+ var allSites = new Array();
+ var params = new Array();
+ {foreach from=$sitesData key=i item=site}
+ allSites[{$i}] = new setRowData({$site.idsite}, {$site.visits}, {$site.pageviews}, {if empty($site.revenue)}0{else}{$site.revenue}{/if}, '{$site.name|escape:"javascript"}', '{$site.main_url|escape:"javascript"}', '{if isset($site.visits_evolution)}{$site.visits_evolution|replace:",":"."}{/if}', '{if isset($site.pageviews_evolution)}{$site.pageviews_evolution|replace:",":"."}{/if}', '{if isset($site.revenue_evolution)}{$site.revenue_evolution|replace:",":"."}{/if}');
+ {/foreach}
+ params['period'] = '{$period}';
+ params['date'] = '{$date}';
+ params['evolutionBy'] = '{$evolutionBy}';
+ params['mOrderBy'] = '{$orderBy}';
+ params['order'] = '{$order}';
+ params['limit'] = '{$limit}';
+ params['page'] = 1;
+ params['prev'] = "{'General_Previous'|translate|escape:"javascript"}";
+ params['next'] = "{'General_Next'|translate|escape:"javascript"}";
+ params['row'] = '{$row|escape:"javascript"}';
+ params['dateSparkline'] = '{$dateSparkline}';
+ </script>
-{postEvent name="template_headerMultiSites"}
+ {postEvent name="template_headerMultiSites"}
-{if !$isWidgetized}
-<div class="top_controls_inner">
- {include file="CoreHome/templates/period_select.tpl"}
- {include file="CoreHome/templates/header_message.tpl"}
-</div>
-{/if}
+ {if !$isWidgetized}
+ <div class="top_controls_inner">
+ {include file="CoreHome/templates/period_select.tpl"}
+ {include file="CoreHome/templates/header_message.tpl"}
+ </div>
+ {/if}
-<div class="centerLargeDiv">
+ <div class="centerLargeDiv">
-<h2>{'General_AllWebsitesDashboard'|translate}
- {capture assign=nVisits}{'General_NVisits'|translate:$totalVisits}{/capture}
- {capture assign=nVisitsLast}{'General_NVisits'|translate:$pastTotalVisits}{/capture}
- <span class='smallTitle' {if $totalVisitsEvolution}title="{'General_EvolutionSummaryGeneric'|translate:$nVisits:$prettyDate:$nVisitsLast:$pastPeriodPretty:$totalVisitsEvolution}"{/if}>
+ <h2>{'General_AllWebsitesDashboard'|translate}
+ {capture assign=nVisits}{'General_NVisits'|translate:$totalVisits}{/capture}
+ {capture assign=nVisitsLast}{'General_NVisits'|translate:$pastTotalVisits}{/capture}
+ <span class='smallTitle'
+ {if $totalVisitsEvolution}title="{'General_EvolutionSummaryGeneric'|translate:$nVisits:$prettyDate:$nVisitsLast:$pastPeriodPretty:$totalVisitsEvolution}"{/if}>
{'General_TotalVisitsPageviewsRevenue'|translate:"<strong>$totalVisits</strong>":"<strong>$totalPageviews</strong>":"<strong>$totalRevenue</strong>"}
</span>
-</h2>
+ </h2>
-<table id="mt" class="dataTable" cellspacing="0">
- <thead>
- <tr>
- <th id="names" class="label" onClick="params = setOrderBy(this,allSites, params, 'names');">
- <span>{'General_Website'|translate}</span>
- <span class="arrow {if $evolutionBy=='names'}multisites_{$order}{/if}"></span>
- </th>
- <th id="visits" class="multisites-column" style="width: 100px" onClick="params = setOrderBy(this,allSites, params, 'visits');">
- <span>{'General_ColumnNbVisits'|translate}</span>
- <span class="arrow {if $evolutionBy=='visits'}multisites_{$order}{/if}"></span>
- </th>
- <th id="pageviews" class="multisites-column" style="width: 110px" onClick="params = setOrderBy(this,allSites, params, 'pageviews');">
- <span>{'General_ColumnPageviews'|translate}</span>
- <span class="arrow {if $evolutionBy=='pageviews'}multisites_{$order}{/if}"></span>
- </th>
- {if $displayRevenueColumn}
- <th id="revenue" class="multisites-column" style="width: 110px" onClick="params = setOrderBy(this,allSites, params, 'revenue');">
- <span>{'Goals_ColumnRevenue'|translate}</span>
- <span class="arrow {if $evolutionBy=='revenue'}multisites_{$order}{/if}"></span>
- </th>
- {/if}
- <th id="evolution" style=" width:350px" colspan="{if $show_sparklines}2{else}1{/if}">
- <span class="arrow "></span>
- <span class="evolution" style="cursor:pointer;" onClick="params = setOrderBy(this,allSites, params, $('#evolution_selector').val() + 'Summary');"> {'MultiSites_Evolution'|translate}</span>
- <select class="selector" id="evolution_selector" onchange="params['evolutionBy'] = $('#evolution_selector').val(); switchEvolution(params);">
- <option value="visits" {if $evolutionBy eq 'visits'} selected {/if}>{'General_ColumnNbVisits'|translate}</option>
- <option value="pageviews" {if $evolutionBy eq 'pageviews'} selected {/if}>{'General_ColumnPageviews'|translate}</option>
- {if $displayRevenueColumn}<option value="revenue" {if $evolutionBy eq 'revenue'} selected {/if}>{'Goals_ColumnRevenue'|translate}</option>{/if}
- </select>
- </th>
- </tr>
- </thead>
+ <table id="mt" class="dataTable" cellspacing="0">
+ <thead>
+ <tr>
+ <th id="names" class="label" onClick="params = setOrderBy(this,allSites, params, 'names');">
+ <span>{'General_Website'|translate}</span>
+ <span class="arrow {if $evolutionBy=='names'}multisites_{$order}{/if}"></span>
+ </th>
+ <th id="visits" class="multisites-column" style="width: 100px" onClick="params = setOrderBy(this,allSites, params, 'visits');">
+ <span>{'General_ColumnNbVisits'|translate}</span>
+ <span class="arrow {if $evolutionBy=='visits'}multisites_{$order}{/if}"></span>
+ </th>
+ <th id="pageviews" class="multisites-column" style="width: 110px" onClick="params = setOrderBy(this,allSites, params, 'pageviews');">
+ <span>{'General_ColumnPageviews'|translate}</span>
+ <span class="arrow {if $evolutionBy=='pageviews'}multisites_{$order}{/if}"></span>
+ </th>
+ {if $displayRevenueColumn}
+ <th id="revenue" class="multisites-column" style="width: 110px" onClick="params = setOrderBy(this,allSites, params, 'revenue');">
+ <span>{'Goals_ColumnRevenue'|translate}</span>
+ <span class="arrow {if $evolutionBy=='revenue'}multisites_{$order}{/if}"></span>
+ </th>
+ {/if}
+ <th id="evolution" style=" width:350px" colspan="{if $show_sparklines}2{else}1{/if}">
+ <span class="arrow "></span>
+ <span class="evolution" style="cursor:pointer;"
+ onClick="params = setOrderBy(this,allSites, params, $('#evolution_selector').val() + 'Summary');"> {'MultiSites_Evolution'|translate}</span>
+ <select class="selector" id="evolution_selector"
+ onchange="params['evolutionBy'] = $('#evolution_selector').val(); switchEvolution(params);">
+ <option value="visits" {if $evolutionBy eq 'visits'} selected {/if}>{'General_ColumnNbVisits'|translate}</option>
+ <option value="pageviews" {if $evolutionBy eq 'pageviews'} selected {/if}>{'General_ColumnPageviews'|translate}</option>
+ {if $displayRevenueColumn}
+ <option value="revenue" {if $evolutionBy eq 'revenue'} selected {/if}>{'Goals_ColumnRevenue'|translate}</option>{/if}
+ </select>
+ </th>
+ </tr>
+ </thead>
- <tbody id="tb">
- </tbody>
+ <tbody id="tb">
+ </tbody>
- <tfoot>
- {if $isSuperUser}
- <tr>
- <td colspan="8" class="clean" style="text-align: right; padding-top: 15px;padding-right:10px">
- <a href="{url}&module=SitesManager&action=index&showaddsite=1"><img src='plugins/UsersManager/images/add.png' alt="" style="margin: 0;" /> {'SitesManager_AddSite'|translate}</a>
- </td>
- </tr>
- {/if}
- <tr row_id="last" >
- <td colspan="8" class="clean" style="padding: 20px">
- <span id="prev" class="pager" style="padding-right: 20px;"></span>
+ <tfoot>
+ {if $isSuperUser}
+ <tr>
+ <td colspan="8" class="clean" style="text-align: right; padding-top: 15px;padding-right:10px">
+ <a href="{url}&module=SitesManager&action=index&showaddsite=1"><img src='plugins/UsersManager/images/add.png' alt=""
+ style="margin: 0;"/> {'SitesManager_AddSite'|translate}</a>
+ </td>
+ </tr>
+ {/if}
+ <tr row_id="last">
+ <td colspan="8" class="clean" style="padding: 20px">
+ <span id="prev" class="pager" style="padding-right: 20px;"></span>
<span class="dataTablePages">
<span id="counter">
</span>
</span>
- <span id="next" class="clean" style="padding-left: 20px;"></span>
- </td>
- </tr>
- </tfoot>
-</table>
-</div>
-<script type="text/javascript">
-prepareRows(allSites, params, '{$orderBy}');
+ <span id="next" class="clean" style="padding-left: 20px;"></span>
+ </td>
+ </tr>
+ </tfoot>
+ </table>
+ </div>
+ <script type="text/javascript">
+ prepareRows(allSites, params, '{$orderBy}');
-{if $autoRefreshTodayReport}
-piwikHelper.refreshAfter({$autoRefreshTodayReport} *1000);
-{/if}
-</script>
-</div>
+ {if $autoRefreshTodayReport}
+ piwikHelper.refreshAfter({$autoRefreshTodayReport} * 1000
+ )
+ ;
+ {/if}
+ </script>
+ </div>
</div>
{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 @@
-<td class="multisites-label label" >
+<td class="multisites-label label">
<a title="View reports" href="index.php?module=CoreHome&action=index&date=%date%&period=%period%&idSite=%idsite%">%name%</a>
<span style="width: 10px; margin-left:3px">
- <a target="_blank" title="{'General_GoTo'|translate:"%main_url%"}" href="%main_url%"><img src="plugins/MultiSites/images/link.gif" /></a>
+ <a target="_blank" title="{'General_GoTo'|translate:"%main_url%"}" href="%main_url%"><img src="plugins/MultiSites/images/link.gif"/></a>
</span>
</td>
<td class="multisites-column">
@@ -12,22 +12,23 @@
%pageviews%
</td>
{if $displayRevenueColumn}
-<td class="multisites-column">
- %revenue%
-</td>
+ <td class="multisites-column">
+ %revenue%
+ </td>
{/if}
{if $period!='range'}
- <td style="width:170px">
- <div class="visits" style="display:none">%visitsSummary%</div>
- <div class="pageviews"style="display:none">%pageviewsSummary%</div>
- {if $displayRevenueColumn}
- <div class="revenue"style="display:none">%revenueSummary%</div>
- {/if}
-{/if}
-{if $show_sparklines}
-<td style="width:180px">
- <div id="sparkline_%idsite%" style="width: 100px; margin: auto">
- <a target="_blank" href="index.php?module=CoreHome&action=index&date=%date%&period=%period%&idSite=%idsite%" title="{capture assign=dashboardName}{'Dashboard_DashboardOf'|translate:'%name%'}{/capture} {'General_GoTo'|translate:$dashboardName}">%sparkline%</a>
- </div>
-</td>
+<td style="width:170px">
+ <div class="visits" style="display:none">%visitsSummary%</div>
+ <div class="pageviews" style="display:none">%pageviewsSummary%</div>
+ {if $displayRevenueColumn}
+ <div class="revenue" style="display:none">%revenueSummary%</div>
+ {/if}
+ {/if}
+ {if $show_sparklines}
+ <td style="width:180px">
+ <div id="sparkline_%idsite%" style="width: 100px; margin: auto">
+ <a target="_blank" href="index.php?module=CoreHome&action=index&date=%date%&period=%period%&idSite=%idsite%"
+ title="{capture assign=dashboardName}{'Dashboard_DashboardOf'|translate:'%name%'}{/capture} {'General_GoTo'|translate:$dashboardName}">%sparkline%</a>
+ </div>
+ </td>
{/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 = '<script type="text/javascript" src="plugins/Overlay/templates/index.js"></script>'
- . '<link rel="stylesheet" type="text/css" href="plugins/Overlay/templates/index.css" />';
-
- $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 = '<script type="text/javascript" src="plugins/Overlay/templates/index.js"></script>'
+ . '<link rel="stylesheet" type="text/css" href="plugins/Overlay/templates/index.css" />';
+
+ $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 '
<html><head><title></title></head><body>
<script type="text/javascript">
function handleProtocol(url) {
@@ -154,7 +148,7 @@ class Piwik_Overlay_Controller extends Piwik_Controller
var urlToRedirect = window.location.hash.substr(1);
var urlToRedirectWithoutPrefix = removeUrlPrefix(urlToRedirect);
- var knownUrls = '.Piwik_Common::json_encode($urls).';
+ var knownUrls = ' . Piwik_Common::json_encode($urls) . ';
for (var i = 0; i < knownUrls.length; i++) {
var testUrl = removeUrlPrefix(knownUrls[i]);
if (urlToRedirectWithoutPrefix.substr(0, testUrl.length) == testUrl) {
@@ -180,57 +174,57 @@ class Piwik_Overlay_Controller extends Piwik_Controller
}
}
else {
- window.location.href = handleProtocol("'.$site['main_url'].'");
+ window.location.href = handleProtocol("' . $site['main_url'] . '");
};
</script>
</body></html>
';
- }
-
- /**
- * 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, '<a href="' . $url . '" target="_top">', '</a>');
- $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, '<a href="' . $url . '" target="_top">', '</a>');
+ $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 <a> tag contains only an <img>, 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 <a> 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 = '&nbsp;&nbsp;';
- 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 <a> tag contains only an <img>, 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 <a> 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 = '&nbsp;&nbsp;';
+ 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 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
- <title></title>
- <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
- <meta name="generator" content="Piwik - Open Source Web Analytics" />
- <link rel="shortcut icon" href="plugins/CoreHome/templates/images/favicon.ico" />
-
- <style type="text/css">
- {literal}
- body {
- font-family: Arial, Helvetica, sans-serif;
- font-size: 14px;
- color: black;
- background: white;
- margin: 15px;
- padding: 0;
+ <title></title>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
+ <meta name="generator" content="Piwik - Open Source Web Analytics"/>
+ <link rel="shortcut icon" href="plugins/CoreHome/templates/images/favicon.ico"/>
+
+ <style type="text/css">
+ {literal}
+ body {
+ font-family: Arial, Helvetica, sans-serif;
+ font-size: 14px;
+ color: black;
+ background: white;
+ margin: 15px;
+ padding: 0;
}
-
- p {
- margin: 0 0 10px 0;
- padding: 0;
+
+ p {
+ margin: 0 0 10px 0;
+ padding: 0;
}
-
- a {
- color: #255792;
+
+ a {
+ color: #255792;
}
- {/literal}
- </style>
-
+
+ {/literal}
+ </style>
+
</head>
<body>
- <p><b>{$message}</b></p>
- <p>{$troubleshoot}</p>
+<p><b>{$message}</b></p>
+
+<p>{$troubleshoot}</p>
</body>
</html> \ 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('&nbsp;').unbind('mouseenter').unbind('mouseleave');
+ $location.html('&nbsp;').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('<b>' + Piwik_Overlay_Translations.domain + ':</b> ' +
@@ -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('<option selected="selected">');
- }
-
- // handle transitions link
- $transitionsLink.click(function() {
- DataTable_RowActions_Transitions.launchForUrl(iframeCurrentPageNormalized);
- return false;
- });
-
- // handle row evolution link
- $rowEvolutionLink.click(function() {
- DataTable_RowActions_RowEvolution.launch('Actions.getPageUrls', iframeCurrentActionLabel);
- return false;
- });
-
- // handle full screen link
- $fullScreenLink.click(function() {
- var href = iframeSrcBase;
- if (iframeCurrentPage) {
- href += '#' + iframeCurrentPage.replace(/#/g, '%23');
- }
- window.location.href = href;
- return false;
- });
- },
-
- /** This callback is used from within the iframe */
- setCurrentUrl: function(currentUrl) {
- showLoading();
-
- var locationParts = location.href.split('#');
- var currentLocation = '';
- if (locationParts.length > 1) {
- currentLocation = broadcast.getParamValue('l', locationParts[1]);
- }
-
- var newLocation = Overlay_Helper.encodeFrameUrl(currentUrl);
-
- if (newLocation != currentLocation) {
- updateComesFromInsideFrame = true;
- // put the current iframe url in the main url to enable refresh and deep linking.
- // use disableHistory=true to make sure that the back and forward buttons can be
- // used on the iframe (which in turn notifies the parent about the location change)
- broadcast.propagateAjax('l=' + newLocation, true);
- } else {
- // happens when the url is changed by hand or when the l parameter is there on page load
- loadSidebar(currentUrl);
- }
- }
-
- };
+ }
+
+ /** 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('<option selected="selected">');
+ }
+
+ // handle transitions link
+ $transitionsLink.click(function () {
+ DataTable_RowActions_Transitions.launchForUrl(iframeCurrentPageNormalized);
+ return false;
+ });
+
+ // handle row evolution link
+ $rowEvolutionLink.click(function () {
+ DataTable_RowActions_RowEvolution.launch('Actions.getPageUrls', iframeCurrentActionLabel);
+ return false;
+ });
+
+ // handle full screen link
+ $fullScreenLink.click(function () {
+ var href = iframeSrcBase;
+ if (iframeCurrentPage) {
+ href += '#' + iframeCurrentPage.replace(/#/g, '%23');
+ }
+ window.location.href = href;
+ return false;
+ });
+ },
+
+ /** This callback is used from within the iframe */
+ setCurrentUrl: function (currentUrl) {
+ showLoading();
+
+ var locationParts = location.href.split('#');
+ var currentLocation = '';
+ if (locationParts.length > 1) {
+ currentLocation = broadcast.getParamValue('l', locationParts[1]);
+ }
+
+ var newLocation = Overlay_Helper.encodeFrameUrl(currentUrl);
+
+ if (newLocation != currentLocation) {
+ updateComesFromInsideFrame = true;
+ // put the current iframe url in the main url to enable refresh and deep linking.
+ // use disableHistory=true to make sure that the back and forward buttons can be
+ // used on the iframe (which in turn notifies the parent about the location change)
+ broadcast.propagateAjax('l=' + newLocation, true);
+ } else {
+ // happens when the url is changed by hand or when the l parameter is there on page load
+ loadSidebar(currentUrl);
+ }
+ }
+
+ };
})(); \ No newline at end of file
diff --git a/plugins/Overlay/templates/index.tpl b/plugins/Overlay/templates/index.tpl
index d2fe954194..e858aefa29 100644
--- a/plugins/Overlay/templates/index.tpl
+++ b/plugins/Overlay/templates/index.tpl
@@ -2,31 +2,33 @@
<h1>{'Overlay_Overlay'|translate|escape:'html'}</h1>
<div id="Overlay_DateRangeSelection">
- <select id="Overlay_DateRangeSelect" name="Overlay_DateRangeSelect">
- <option value="day;today">{'General_Today'|translate|escape:'html'}</option>
- <option value="day;yesterday">{'General_Yesterday'|translate|escape:'html'}</option>
- <option value="week;today">{'General_CurrentWeek'|translate|escape:'html'}</option>
- <option value="month;today">{'General_CurrentMonth'|translate|escape:'html'}</option>
- <option value="year;today">{'General_CurrentYear'|translate|escape:'html'}</option>
- </select>
+ <select id="Overlay_DateRangeSelect" name="Overlay_DateRangeSelect">
+ <option value="day;today">{'General_Today'|translate|escape:'html'}</option>
+ <option value="day;yesterday">{'General_Yesterday'|translate|escape:'html'}</option>
+ <option value="week;today">{'General_CurrentWeek'|translate|escape:'html'}</option>
+ <option value="month;today">{'General_CurrentMonth'|translate|escape:'html'}</option>
+ <option value="year;today">{'General_CurrentYear'|translate|escape:'html'}</option>
+ </select>
</div>
<div id="Overlay_Error_NotLoading">
- <p>
- <span>{'Overlay_ErrorNotLoading'|translate|escape:'html'}</span>
- </p>
- <p>
- {if $ssl}
- {'Overlay_ErrorNotLoadingDetailsSSL'|translate|escape:'html'}
- {else}
- {'Overlay_ErrorNotLoadingDetails'|translate|escape:'html'}
- {/if}
- </p>
- <p>
- <a href="http://piwik.org/docs/page-overlay/#toc-page-overlay-troubleshooting" target="_blank">
- {'Overlay_ErrorNotLoadingLink'|translate|escape:'html'}
- </a>
- </p>
+ <p>
+ <span>{'Overlay_ErrorNotLoading'|translate|escape:'html'}</span>
+ </p>
+
+ <p>
+ {if $ssl}
+ {'Overlay_ErrorNotLoadingDetailsSSL'|translate|escape:'html'}
+ {else}
+ {'Overlay_ErrorNotLoadingDetails'|translate|escape:'html'}
+ {/if}
+ </p>
+
+ <p>
+ <a href="http://piwik.org/docs/page-overlay/#toc-page-overlay-troubleshooting" target="_blank">
+ {'Overlay_ErrorNotLoadingLink'|translate|escape:'html'}
+ </a>
+ </p>
</div>
<div id="Overlay_Location">&nbsp;</div>
@@ -45,17 +47,17 @@
<div id="Overlay_Main">
- <iframe id="Overlay_Iframe" src="" frameborder="0"></iframe>
+ <iframe id="Overlay_Iframe" src="" frameborder="0"></iframe>
</div>
<script type="text/javascript">
- var iframeSrc = 'index.php?module=Overlay&action=startOverlaySession&idsite={$idSite}&period={$period}&date={$date}';
- Piwik_Overlay.init(iframeSrc, '{$idSite}', '{$period}', '{$date}');
-
- Piwik_Overlay_Translations = {literal}{{/literal}
- domain: "{'Overlay_Domain'|translate|escape:'html'}"
- {literal}}{/literal};
+ var iframeSrc = 'index.php?module=Overlay&action=startOverlaySession&idsite={$idSite}&period={$period}&date={$date}';
+ Piwik_Overlay.init(iframeSrc, '{$idSite}', '{$period}', '{$date}');
+
+ Piwik_Overlay_Translations = {literal}{{/literal}
+ domain: "{'Overlay_Domain'|translate|escape:'html'}"
+ {literal}}{/literal};
</script>
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 @@
<h1>{'Overlay_Overlay'|translate|escape:'html'}</h1>
<div id="Overlay_NoFrame">
-
- <script type="text/javascript">
- var newLocation = 'index.php?module=Overlay&action=startOverlaySession&idsite={$idSite}&period={$period}&date={$date}';
-
- {literal}
-
- var locationParts = window.location.href.split('#');
- if (locationParts.length > 1) {
- var url = broadcast.getParamValue('l', locationParts[1]);
- url = Overlay_Helper.decodeFrameUrl(url);
- newLocation += '#' + url;
+
+ <script type="text/javascript">
+ var newLocation = 'index.php?module=Overlay&action=startOverlaySession&idsite={$idSite}&period={$period}&date={$date}';
+
+ {literal}
+
+ var locationParts = window.location.href.split('#');
+ if (locationParts.length > 1) {
+ var url = broadcast.getParamValue('l', locationParts[1]);
+ url = Overlay_Helper.decodeFrameUrl(url);
+ newLocation += '#' + url;
}
-
- window.location.href = newLocation;
-
- {/literal}
- </script>
+
+ window.location.href = newLocation;
+
+ {/literal}
+ </script>
</div>
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 @@
<html>
<head>
- <title></title>
+ <title></title>
</head>
<body>
- <script type="text/javascript">
- // go up two iframes to find the piwik window
- var piwikWindow = window.parent.parent;
- // notify piwik of location change
- // the location has been passed as the hash part of the url from the overlay session
- piwikWindow.Piwik_Overlay.setCurrentUrl(window.location.hash.substring(1));
- </script>
+<script type="text/javascript">
+ // go up two iframes to find the piwik window
+ var piwikWindow = window.parent.parent;
+ // notify piwik of location change
+ // the location has been passed as the hash part of the url from the overlay session
+ piwikWindow.Piwik_Overlay.setCurrentUrl(window.location.hash.substring(1));
+</script>
</body>
</html> \ 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 = $('<textarea>').html(link).val(); // remove html entities
-
- actionA.attr({
- target: '_blank',
- href: Overlay_Helper.getOverlayLink(this.dataTable.param.idSite, 'month', 'today', link)
- });
- }
-
- return true;
+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 = $('<textarea>').html(link).val(); // remove html entities
+
+ actionA.attr({
+ target: '_blank',
+ href: Overlay_Helper.getOverlayLink(this.dataTable.param.idSite, 'month', 'today', link)
+ });
+ }
+
+ return true;
};
DataTable_RowActions_Registry.register({
- name: 'Overlay',
+ name: 'Overlay',
+
+ dataTableIcon: 'plugins/Overlay/templates/overlay_icon.png',
+ dataTableIconHover: 'plugins/Overlay/templates/overlay_icon_hover.png',
- dataTableIcon: 'plugins/Overlay/templates/overlay_icon.png',
- dataTableIconHover: 'plugins/Overlay/templates/overlay_icon_hover.png',
-
- order: 30,
+ order: 30,
- dataTableIconTooltip: [
- _pk_translate('General_OverlayRowActionTooltipTitle_js'),
- _pk_translate('General_OverlayRowActionTooltip_js')
- ],
+ dataTableIconTooltip: [
+ _pk_translate('General_OverlayRowActionTooltipTitle_js'),
+ _pk_translate('General_OverlayRowActionTooltip_js')
+ ],
- createInstance: function(dataTable) {
- return new DataTable_RowActions_Overlay(dataTable);
- },
+ createInstance: function (dataTable) {
+ return new DataTable_RowActions_Overlay(dataTable);
+ },
- isAvailableOnReport: function(dataTableParams) {
- return DataTable_RowActions_Transitions.isPageUrlReport(dataTableParams.module, dataTableParams.action);
- },
+ isAvailableOnReport: function (dataTableParams) {
+ return DataTable_RowActions_Transitions.isPageUrlReport(dataTableParams.module, dataTableParams.action);
+ },
- isAvailableOnRow: function(dataTableParams, tr) {
- var transitions = DataTable_RowActions_Registry.getActionByName('Transitions');
- return transitions.isAvailableOnRow(dataTableParams, tr);
- }
+ isAvailableOnRow: function (dataTableParams, tr) {
+ var transitions = DataTable_RowActions_Registry.getActionByName('Transitions');
+ return transitions.isAvailableOnRow(dataTableParams, tr);
+ }
});
diff --git a/plugins/Overlay/templates/sidebar.tpl b/plugins/Overlay/templates/sidebar.tpl
index 2421fe9791..d0e6a040ad 100644
--- a/plugins/Overlay/templates/sidebar.tpl
+++ b/plugins/Overlay/templates/sidebar.tpl
@@ -1,23 +1,23 @@
<div> <!-- Wrapper is needed that the html can be jqueryfied -->
- <!-- This div is removed by JS and the content is put in the location div -->
- <div class="Overlay_Location">
- <b>{'Overlay_Location'|translate|escape:'html'}:</b>
+ <!-- This div is removed by JS and the content is put in the location div -->
+ <div class="Overlay_Location">
+ <b>{'Overlay_Location'|translate|escape:'html'}:</b>
<span data-normalized-url="{$normalizedUrl|escape:'html'}" data-label="{$label|escape:'html'}">
{$location|escape:'html'}
</span>
- </div>
-
- {if count($data)}
- <h2 class="Overlay_MainMetrics">{'Overlay_MainMetrics'|translate|escape:'html'}</h2>
- {foreach from=$data item=metric}
- <div class="Overlay_Metric">
- <span class="Overlay_MetricValue">{$metric.value}</span> {$metric.name|escape:'html'}
- </div>
- {/foreach}
- {else}
- <!-- note: the class Overlay_NoData is used in index.js -->
- <div class="Overlay_NoData">{'Overlay_NoData'|translate|escape:'html'}</div>
- {/if}
-
+ </div>
+
+ {if count($data)}
+ <h2 class="Overlay_MainMetrics">{'Overlay_MainMetrics'|translate|escape:'html'}</h2>
+ {foreach from=$data item=metric}
+ <div class="Overlay_Metric">
+ <span class="Overlay_MetricValue">{$metric.value}</span> {$metric.name|escape:'html'}
+ </div>
+ {/foreach}
+ {else}
+ <!-- note: the class Overlay_NoData is used in index.js -->
+ <div class="Overlay_NoData">{'Overlay_NoData'|translate|escape:'html'}</div>
+ {/if}
+
</div> \ No newline at end of file
diff --git a/plugins/PDFReports/API.php b/plugins/PDFReports/API.php
index f563120531..373b19fe0f 100644
--- a/plugins/PDFReports/API.php
+++ b/plugins/PDFReports/API.php
@@ -1,435 +1,415 @@
<?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_PDFReports
*/
/**
* The PDFReports API lets you manage Scheduled Email reports, as well as generate, download or email any existing report.
- *
+ *
* "generateReport" will generate the requested report (for a specific date range, website and in the requested language).
- * "sendEmailReport" will send the report by email to the recipients specified for this report.
- *
- * You can also get the list of all existing reports via "getReports", create new reports via "addReport",
+ * "sendEmailReport" will send the report by email to the recipients specified for this report.
+ *
+ * You can also get the list of all existing reports via "getReports", create new reports via "addReport",
* or manage existing reports with "updateReport" and "deleteReport".
* See also the documentation about <a href='http://piwik.org/docs/email-reports/' target='_blank'>Scheduled Email reports</a> in Piwik.
- *
+ *
* @package Piwik_PDFReports
*/
class Piwik_PDFReports_API
{
- const VALIDATE_PARAMETERS_EVENT = 'PDFReports.validateReportParameters';
- const GET_REPORT_PARAMETERS_EVENT = 'PDFReports.getReportParameters';
- const GET_REPORT_METADATA_EVENT = 'PDFReports.getReportMetadata';
- const GET_REPORT_TYPES_EVENT = 'PDFReports.getReportTypes';
- const GET_REPORT_FORMATS_EVENT = 'PDFReports.getReportFormats';
- const GET_RENDERER_INSTANCE_EVENT = 'PDFReports.getRendererInstance';
- const PROCESS_REPORTS_EVENT = 'PDFReports.processReports';
- const GET_REPORT_RECIPIENTS_EVENT = 'PDFReports.getReportRecipients';
- const ALLOW_MULTIPLE_REPORTS_EVENT = 'PDFReports.allowMultipleReports';
- const SEND_REPORT_EVENT = 'PDFReports.sendReport';
-
- const OUTPUT_DOWNLOAD = 1;
- const OUTPUT_SAVE_ON_DISK = 2;
- const OUTPUT_INLINE = 3;
- const OUTPUT_RETURN = 4;
-
- const REPORT_TYPE_INFO_KEY = 'reportType';
- const OUTPUT_TYPE_INFO_KEY = 'outputType';
- const ID_SITE_INFO_KEY = 'idSite';
- const REPORT_KEY = 'report';
- const REPORT_CONTENT_KEY = 'contents';
- const FILENAME_KEY = 'filename';
- const PRETTY_DATE_KEY = 'prettyDate';
- const WEBSITE_NAME_KEY = 'websiteName';
- const ADDITIONAL_FILES_KEY = 'additionalFiles';
-
- const REPORT_TRUNCATE = 23;
-
- static private $instance = null;
-
- /**
- * @return Piwik_PDFReports_API
- */
- static public function getInstance()
- {
- if (self::$instance == null)
- {
- self::$instance = new self;
- }
- return self::$instance;
- }
-
- /**
- * Creates a new report and schedules it.
- *
- * @param int $idSite
- * @param string $description Report description
- * @param string $period Schedule frequency: day, week or month
- * @param int $hour Hour (0-23) when the report should be sent
- * @param string $reportType 'email' or any other format provided via the PDFReports.getReportTypes hook
- * @param string $reportFormat 'pdf', 'html' or any other format provided via the PDFReports.getReportFormats hook
- * @param array $reports array of reports
- * @param array $parameters array of parameters
- *
- * @return int idReport generated
- */
- public function addReport($idSite, $description, $period, $hour, $reportType, $reportFormat, $reports, $parameters)
- {
- Piwik::checkUserIsNotAnonymous();
- Piwik::checkUserHasViewAccess($idSite);
-
- $currentUser = Piwik::getCurrentUserLogin();
- self::ensureLanguageSetForUser($currentUser);
-
- self::validateCommonReportAttributes($period, $hour, $description, $reportType, $reportFormat);
-
- // report parameters validations
- $parameters = self::validateReportParameters($reportType, $parameters);
-
- // validation of requested reports
- $reports = self::validateRequestedReports($idSite, $reportType, $reports);
-
- $db = Zend_Registry::get('db');
- $idReport = $db->fetchOne("SELECT max(idreport) + 1 FROM ".Piwik_Common::prefixTable('report'));
-
- if($idReport == false)
- {
- $idReport = 1;
- }
-
- $db->insert(Piwik_Common::prefixTable('report'),
- array(
- 'idreport' => $idReport,
- 'idsite' => $idSite,
- 'login' => $currentUser,
- 'description' => $description,
- 'period' => $period,
- 'hour' => $hour,
- 'type' => $reportType,
- 'format' => $reportFormat,
- 'parameters' => $parameters,
- 'reports' => $reports,
- 'ts_created' => Piwik_Date::now()->getDatetime(),
- 'deleted' => 0,
- ));
-
- return $idReport;
- }
-
- private static function ensureLanguageSetForUser($currentUser)
- {
- $lang = Piwik_LanguagesManager_API::getInstance()->getLanguageForUser( $currentUser );
- if(empty($lang))
- {
- Piwik_LanguagesManager_API::getInstance()->setLanguageForUser( $currentUser, Piwik_LanguagesManager::getLanguageCodeForCurrentUser() );
- }
- }
-
- /**
- * Updates an existing report.
- *
- * @see addReport()
- */
- public function updateReport( $idReport, $idSite, $description, $period, $hour, $reportType, $reportFormat, $reports, $parameters)
- {
- Piwik::checkUserIsNotAnonymous();
- Piwik::checkUserHasViewAccess($idSite);
-
- $pdfReports = $this->getReports($idSite, $periodSearch = false, $idReport);
- $report = reset($pdfReports);
- $idReport = $report['idreport'];
-
- $currentUser = Piwik::getCurrentUserLogin();
- self::ensureLanguageSetForUser($currentUser);
-
- self::validateCommonReportAttributes($period, $hour, $description, $reportType, $reportFormat);
-
- // report parameters validations
- $parameters = self::validateReportParameters($reportType, $parameters);
-
- // validation of requested reports
- $reports = self::validateRequestedReports($idSite, $reportType, $reports);
-
- Zend_Registry::get('db')->update( Piwik_Common::prefixTable('report'),
- array(
- 'description' => $description,
- 'period' => $period,
- 'hour' => $hour,
- 'type' => $reportType,
- 'format' => $reportFormat,
- 'parameters' => $parameters,
- 'reports' => $reports,
- ),
- "idreport = '$idReport'"
- );
-
- self::$cache = array();
- }
-
- /**
- * Deletes a specific report
- *
- * @param int $idReport
- */
- public function deleteReport($idReport)
- {
- $pdfReports = $this->getReports($idSite = false, $periodSearch = false, $idReport);
- $report = reset($pdfReports);
- Piwik::checkUserIsSuperUserOrTheUser($report['login']);
-
- Zend_Registry::get('db')->update( Piwik_Common::prefixTable('report'),
- array(
- 'deleted' => 1,
- ),
- "idreport = '$idReport'"
- );
- self::$cache = array();
- }
-
- // static cache storing reports
- public static $cache = array();
-
- /**
- * Returns the list of reports matching the passed parameters
- *
- * @param int $idSite If specified, will filter reports that belong to a specific idsite
- * @param string $period If specified, will filter reports that are scheduled for this period (day,week,month)
- * @param int $idReport If specified, will filter the report that has the given idReport
- * @return array
- * @throws Exception if $idReport was specified but the report wasn't found
- */
- public function getReports($idSite = false, $period = false, $idReport = false, $ifSuperUserReturnOnlySuperUserReports = false)
- {
- Piwik::checkUserHasSomeViewAccess();
- $cacheKey = (int)$idSite .'.'. (string)$period .'.'. (int)$idReport .'.'. (int)$ifSuperUserReturnOnlySuperUserReports;
- if(isset(self::$cache[$cacheKey]))
- {
- return self::$cache[$cacheKey];
- }
-
- $sqlWhere = '';
- $bind = array();
-
- // Super user gets all reports back, other users only their own
- if(!Piwik::isUserIsSuperUser()
- || $ifSuperUserReturnOnlySuperUserReports)
- {
- $sqlWhere .= "AND login = ?";
- $bind[] = Piwik::getCurrentUserLogin();
- }
-
- if(!empty($period))
- {
- $this->validateReportPeriod($period);
- $sqlWhere .= " AND period = ? ";
- $bind[] = $period;
- }
- if(!empty($idSite))
- {
- Piwik::checkUserHasViewAccess($idSite);
- $sqlWhere .= " AND ".Piwik_Common::prefixTable('site').".idsite = ?";
- $bind[] = $idSite;
- }
- if(!empty($idReport))
- {
- $sqlWhere .= " AND idreport = ?";
- $bind[] = $idReport;
- }
-
- // Joining with the site table to work around pre-1.3 where reports could still be linked to a deleted site
- $reports = Piwik_FetchAll("SELECT *
- FROM ".Piwik_Common::prefixTable('report')."
- JOIN ".Piwik_Common::prefixTable('site')."
+ const VALIDATE_PARAMETERS_EVENT = 'PDFReports.validateReportParameters';
+ const GET_REPORT_PARAMETERS_EVENT = 'PDFReports.getReportParameters';
+ const GET_REPORT_METADATA_EVENT = 'PDFReports.getReportMetadata';
+ const GET_REPORT_TYPES_EVENT = 'PDFReports.getReportTypes';
+ const GET_REPORT_FORMATS_EVENT = 'PDFReports.getReportFormats';
+ const GET_RENDERER_INSTANCE_EVENT = 'PDFReports.getRendererInstance';
+ const PROCESS_REPORTS_EVENT = 'PDFReports.processReports';
+ const GET_REPORT_RECIPIENTS_EVENT = 'PDFReports.getReportRecipients';
+ const ALLOW_MULTIPLE_REPORTS_EVENT = 'PDFReports.allowMultipleReports';
+ const SEND_REPORT_EVENT = 'PDFReports.sendReport';
+
+ const OUTPUT_DOWNLOAD = 1;
+ const OUTPUT_SAVE_ON_DISK = 2;
+ const OUTPUT_INLINE = 3;
+ const OUTPUT_RETURN = 4;
+
+ const REPORT_TYPE_INFO_KEY = 'reportType';
+ const OUTPUT_TYPE_INFO_KEY = 'outputType';
+ const ID_SITE_INFO_KEY = 'idSite';
+ const REPORT_KEY = 'report';
+ const REPORT_CONTENT_KEY = 'contents';
+ const FILENAME_KEY = 'filename';
+ const PRETTY_DATE_KEY = 'prettyDate';
+ const WEBSITE_NAME_KEY = 'websiteName';
+ const ADDITIONAL_FILES_KEY = 'additionalFiles';
+
+ const REPORT_TRUNCATE = 23;
+
+ static private $instance = null;
+
+ /**
+ * @return Piwik_PDFReports_API
+ */
+ static public function getInstance()
+ {
+ if (self::$instance == null) {
+ self::$instance = new self;
+ }
+ return self::$instance;
+ }
+
+ /**
+ * Creates a new report and schedules it.
+ *
+ * @param int $idSite
+ * @param string $description Report description
+ * @param string $period Schedule frequency: day, week or month
+ * @param int $hour Hour (0-23) when the report should be sent
+ * @param string $reportType 'email' or any other format provided via the PDFReports.getReportTypes hook
+ * @param string $reportFormat 'pdf', 'html' or any other format provided via the PDFReports.getReportFormats hook
+ * @param array $reports array of reports
+ * @param array $parameters array of parameters
+ *
+ * @return int idReport generated
+ */
+ public function addReport($idSite, $description, $period, $hour, $reportType, $reportFormat, $reports, $parameters)
+ {
+ Piwik::checkUserIsNotAnonymous();
+ Piwik::checkUserHasViewAccess($idSite);
+
+ $currentUser = Piwik::getCurrentUserLogin();
+ self::ensureLanguageSetForUser($currentUser);
+
+ self::validateCommonReportAttributes($period, $hour, $description, $reportType, $reportFormat);
+
+ // report parameters validations
+ $parameters = self::validateReportParameters($reportType, $parameters);
+
+ // validation of requested reports
+ $reports = self::validateRequestedReports($idSite, $reportType, $reports);
+
+ $db = Zend_Registry::get('db');
+ $idReport = $db->fetchOne("SELECT max(idreport) + 1 FROM " . Piwik_Common::prefixTable('report'));
+
+ if ($idReport == false) {
+ $idReport = 1;
+ }
+
+ $db->insert(Piwik_Common::prefixTable('report'),
+ array(
+ 'idreport' => $idReport,
+ 'idsite' => $idSite,
+ 'login' => $currentUser,
+ 'description' => $description,
+ 'period' => $period,
+ 'hour' => $hour,
+ 'type' => $reportType,
+ 'format' => $reportFormat,
+ 'parameters' => $parameters,
+ 'reports' => $reports,
+ 'ts_created' => Piwik_Date::now()->getDatetime(),
+ 'deleted' => 0,
+ ));
+
+ return $idReport;
+ }
+
+ private static function ensureLanguageSetForUser($currentUser)
+ {
+ $lang = Piwik_LanguagesManager_API::getInstance()->getLanguageForUser($currentUser);
+ if (empty($lang)) {
+ Piwik_LanguagesManager_API::getInstance()->setLanguageForUser($currentUser, Piwik_LanguagesManager::getLanguageCodeForCurrentUser());
+ }
+ }
+
+ /**
+ * Updates an existing report.
+ *
+ * @see addReport()
+ */
+ public function updateReport($idReport, $idSite, $description, $period, $hour, $reportType, $reportFormat, $reports, $parameters)
+ {
+ Piwik::checkUserIsNotAnonymous();
+ Piwik::checkUserHasViewAccess($idSite);
+
+ $pdfReports = $this->getReports($idSite, $periodSearch = false, $idReport);
+ $report = reset($pdfReports);
+ $idReport = $report['idreport'];
+
+ $currentUser = Piwik::getCurrentUserLogin();
+ self::ensureLanguageSetForUser($currentUser);
+
+ self::validateCommonReportAttributes($period, $hour, $description, $reportType, $reportFormat);
+
+ // report parameters validations
+ $parameters = self::validateReportParameters($reportType, $parameters);
+
+ // validation of requested reports
+ $reports = self::validateRequestedReports($idSite, $reportType, $reports);
+
+ Zend_Registry::get('db')->update(Piwik_Common::prefixTable('report'),
+ array(
+ 'description' => $description,
+ 'period' => $period,
+ 'hour' => $hour,
+ 'type' => $reportType,
+ 'format' => $reportFormat,
+ 'parameters' => $parameters,
+ 'reports' => $reports,
+ ),
+ "idreport = '$idReport'"
+ );
+
+ self::$cache = array();
+ }
+
+ /**
+ * Deletes a specific report
+ *
+ * @param int $idReport
+ */
+ public function deleteReport($idReport)
+ {
+ $pdfReports = $this->getReports($idSite = false, $periodSearch = false, $idReport);
+ $report = reset($pdfReports);
+ Piwik::checkUserIsSuperUserOrTheUser($report['login']);
+
+ Zend_Registry::get('db')->update(Piwik_Common::prefixTable('report'),
+ array(
+ 'deleted' => 1,
+ ),
+ "idreport = '$idReport'"
+ );
+ self::$cache = array();
+ }
+
+ // static cache storing reports
+ public static $cache = array();
+
+ /**
+ * Returns the list of reports matching the passed parameters
+ *
+ * @param int $idSite If specified, will filter reports that belong to a specific idsite
+ * @param string $period If specified, will filter reports that are scheduled for this period (day,week,month)
+ * @param int $idReport If specified, will filter the report that has the given idReport
+ * @return array
+ * @throws Exception if $idReport was specified but the report wasn't found
+ */
+ public function getReports($idSite = false, $period = false, $idReport = false, $ifSuperUserReturnOnlySuperUserReports = false)
+ {
+ Piwik::checkUserHasSomeViewAccess();
+ $cacheKey = (int)$idSite . '.' . (string)$period . '.' . (int)$idReport . '.' . (int)$ifSuperUserReturnOnlySuperUserReports;
+ if (isset(self::$cache[$cacheKey])) {
+ return self::$cache[$cacheKey];
+ }
+
+ $sqlWhere = '';
+ $bind = array();
+
+ // Super user gets all reports back, other users only their own
+ if (!Piwik::isUserIsSuperUser()
+ || $ifSuperUserReturnOnlySuperUserReports
+ ) {
+ $sqlWhere .= "AND login = ?";
+ $bind[] = Piwik::getCurrentUserLogin();
+ }
+
+ if (!empty($period)) {
+ $this->validateReportPeriod($period);
+ $sqlWhere .= " AND period = ? ";
+ $bind[] = $period;
+ }
+ if (!empty($idSite)) {
+ Piwik::checkUserHasViewAccess($idSite);
+ $sqlWhere .= " AND " . Piwik_Common::prefixTable('site') . ".idsite = ?";
+ $bind[] = $idSite;
+ }
+ if (!empty($idReport)) {
+ $sqlWhere .= " AND idreport = ?";
+ $bind[] = $idReport;
+ }
+
+ // Joining with the site table to work around pre-1.3 where reports could still be linked to a deleted site
+ $reports = Piwik_FetchAll("SELECT *
+ FROM " . Piwik_Common::prefixTable('report') . "
+ JOIN " . Piwik_Common::prefixTable('site') . "
USING (idsite)
WHERE deleted = 0
$sqlWhere", $bind);
- // When a specific report was requested and not found, throw an error
- if($idReport !== false
- && empty($reports))
- {
- throw new Exception("Requested report couldn't be found.");
- }
-
- foreach($reports as &$report) {
- // decode report parameters
- $report['parameters'] = Piwik_Common::json_decode($report['parameters'], true);
-
- // decode report list
- $report['reports'] = Piwik_Common::json_decode($report['reports'], true);
- }
-
- // static cache
- self::$cache[$cacheKey] = $reports;
-
- return $reports;
- }
-
+ // When a specific report was requested and not found, throw an error
+ if ($idReport !== false
+ && empty($reports)
+ ) {
+ throw new Exception("Requested report couldn't be found.");
+ }
+
+ foreach ($reports as &$report) {
+ // decode report parameters
+ $report['parameters'] = Piwik_Common::json_decode($report['parameters'], true);
+
+ // decode report list
+ $report['reports'] = Piwik_Common::json_decode($report['reports'], true);
+ }
+
+ // static cache
+ self::$cache[$cacheKey] = $reports;
+
+ return $reports;
+ }
+
/**
- * Generates a report file.
- *
+ * Generates a report file.
+ *
* @param int $idReport ID of the report to generate.
* @param string $date YYYY-MM-DD
- * @param bool|false|string $language If not passed, will use default language.
- * @param bool|false|int $outputType 1 = download report, 2 = save report to disk, 3 = output report in browser, 4 = return report content to caller, defaults to download
- * @param bool|false|string $period Defaults to 'day'. If not specified, will default to the report's period set when creating the report
- * @param bool|false|string $reportFormat 'pdf', 'html' or any other format provided via the PDFReports.getReportFormats hook
- * @param bool|false|array $parameters array of parameters
- * @return array|void
- */
- public function generateReport($idReport, $date, $language = false, $outputType = false, $period = false, $reportFormat = false, $parameters = false)
- {
- Piwik::checkUserIsNotAnonymous();
-
- // load specified language
- if(empty($language))
- {
- $language = Piwik_Translate::getInstance()->getLanguageDefault();
- }
-
- Piwik_Translate::getInstance()->reloadLanguage($language);
-
- $reports = $this->getReports($idSite = false, $_period = false, $idReport);
- $report = reset($reports);
-
- $idSite = $report['idsite'];
- $reportType = $report['type'];
-
- // override report period
- if(empty($period))
- {
- $period = $report['period'];
- }
-
- // override report format
- if(!empty($reportFormat))
- {
- self::validateReportFormat($reportType, $reportFormat);
- $report['format'] = $reportFormat;
- }
- else
- {
- $reportFormat = $report['format'];
- }
-
- // override and/or validate report parameters
- $report['parameters'] = Piwik_Common::json_decode(
- self::validateReportParameters($reportType, empty($parameters) ? $report['parameters'] : $parameters),
- true
- );
-
- // available reports
- $availableReportMetadata = Piwik_API_API::getInstance()->getReportMetadata($idSite);
-
- // we need to lookup which reports metadata are registered in this report
- $reportMetadata = array();
- foreach($availableReportMetadata as $metadata)
- {
- if(in_array($metadata['uniqueId'], $report['reports']))
- {
- $reportMetadata[] = $metadata;
- }
- }
-
- // the report will be rendered with the first 23 rows and will aggregate other rows in a summary row
- // 23 rows table fits in one portrait page
- $initialFilterTruncate = Piwik_Common::getRequestVar('filter_truncate', false);
- $_GET['filter_truncate'] = self::REPORT_TRUNCATE;
-
- $prettyDate = null;
- $processedReports = array();
- foreach ($reportMetadata as $action)
- {
- $apiModule = $action['module'];
- $apiAction = $action['action'];
- $apiParameters = array();
- if(isset($action['parameters']))
- {
- $apiParameters = $action['parameters'];
- }
-
- $mustRestoreGET = false;
-
- // all Websites dashboard should not be truncated in the report
- if($apiModule == 'MultiSites')
- {
- $mustRestoreGET = $_GET;
- $_GET['enhanced'] = true;
-
- if($apiAction == 'getAll')
- {
- $_GET['filter_truncate'] = false;
-
- // when a view/admin user created a report, workaround the fact that "Super User"
- // is enforced in Scheduled tasks, and ensure Multisites.getAll only return the websites that this user can access
- $userLogin = $report['login'];
- if(!empty($userLogin)
- && $userLogin != Piwik_Config::getInstance()->superuser['login'])
- {
- $_GET['_restrictSitesToLogin'] = $userLogin;
- }
- }
- }
-
- $processedReport = Piwik_API_API::getInstance()->getProcessedReport(
- $idSite, $period, $date, $apiModule, $apiAction,
- $segment = false, $apiParameters, $idGoal = false, $language
- );
-
- // TODO add static method getPrettyDate($period, $date) in Piwik_Period
- $prettyDate = $processedReport['prettyDate'];
-
- if($mustRestoreGET)
- {
- $_GET = $mustRestoreGET;
- }
-
- $processedReports[] = $processedReport;
- }
-
- // restore filter truncate parameter value
- if($initialFilterTruncate !== false)
- {
- $_GET['filter_truncate'] = $initialFilterTruncate;
- }
-
- $notificationInfo = array(
- self::REPORT_TYPE_INFO_KEY => $reportType,
- self::OUTPUT_TYPE_INFO_KEY => $outputType,
- self::REPORT_KEY => $report,
- );
-
- // allow plugins to alter processed reports
- Piwik_PostEvent(
- self::PROCESS_REPORTS_EVENT,
- $processedReports,
- $notificationInfo
- );
-
- // retrieve report renderer instance
- $reportRenderer = null;
- Piwik_PostEvent(
- self::GET_RENDERER_INSTANCE_EVENT,
- $reportRenderer,
- $notificationInfo
- );
-
- // init report renderer
- $reportRenderer->setLocale($language);
-
- // render report
+ * @param bool|false|string $language If not passed, will use default language.
+ * @param bool|false|int $outputType 1 = download report, 2 = save report to disk, 3 = output report in browser, 4 = return report content to caller, defaults to download
+ * @param bool|false|string $period Defaults to 'day'. If not specified, will default to the report's period set when creating the report
+ * @param bool|false|string $reportFormat 'pdf', 'html' or any other format provided via the PDFReports.getReportFormats hook
+ * @param bool|false|array $parameters array of parameters
+ * @return array|void
+ */
+ public function generateReport($idReport, $date, $language = false, $outputType = false, $period = false, $reportFormat = false, $parameters = false)
+ {
+ Piwik::checkUserIsNotAnonymous();
+
+ // load specified language
+ if (empty($language)) {
+ $language = Piwik_Translate::getInstance()->getLanguageDefault();
+ }
+
+ Piwik_Translate::getInstance()->reloadLanguage($language);
+
+ $reports = $this->getReports($idSite = false, $_period = false, $idReport);
+ $report = reset($reports);
+
+ $idSite = $report['idsite'];
+ $reportType = $report['type'];
+
+ // override report period
+ if (empty($period)) {
+ $period = $report['period'];
+ }
+
+ // override report format
+ if (!empty($reportFormat)) {
+ self::validateReportFormat($reportType, $reportFormat);
+ $report['format'] = $reportFormat;
+ } else {
+ $reportFormat = $report['format'];
+ }
+
+ // override and/or validate report parameters
+ $report['parameters'] = Piwik_Common::json_decode(
+ self::validateReportParameters($reportType, empty($parameters) ? $report['parameters'] : $parameters),
+ true
+ );
+
+ // available reports
+ $availableReportMetadata = Piwik_API_API::getInstance()->getReportMetadata($idSite);
+
+ // we need to lookup which reports metadata are registered in this report
+ $reportMetadata = array();
+ foreach ($availableReportMetadata as $metadata) {
+ if (in_array($metadata['uniqueId'], $report['reports'])) {
+ $reportMetadata[] = $metadata;
+ }
+ }
+
+ // the report will be rendered with the first 23 rows and will aggregate other rows in a summary row
+ // 23 rows table fits in one portrait page
+ $initialFilterTruncate = Piwik_Common::getRequestVar('filter_truncate', false);
+ $_GET['filter_truncate'] = self::REPORT_TRUNCATE;
+
+ $prettyDate = null;
+ $processedReports = array();
+ foreach ($reportMetadata as $action) {
+ $apiModule = $action['module'];
+ $apiAction = $action['action'];
+ $apiParameters = array();
+ if (isset($action['parameters'])) {
+ $apiParameters = $action['parameters'];
+ }
+
+ $mustRestoreGET = false;
+
+ // all Websites dashboard should not be truncated in the report
+ if ($apiModule == 'MultiSites') {
+ $mustRestoreGET = $_GET;
+ $_GET['enhanced'] = true;
+
+ if ($apiAction == 'getAll') {
+ $_GET['filter_truncate'] = false;
+
+ // when a view/admin user created a report, workaround the fact that "Super User"
+ // is enforced in Scheduled tasks, and ensure Multisites.getAll only return the websites that this user can access
+ $userLogin = $report['login'];
+ if (!empty($userLogin)
+ && $userLogin != Piwik_Config::getInstance()->superuser['login']
+ ) {
+ $_GET['_restrictSitesToLogin'] = $userLogin;
+ }
+ }
+ }
+
+ $processedReport = Piwik_API_API::getInstance()->getProcessedReport(
+ $idSite, $period, $date, $apiModule, $apiAction,
+ $segment = false, $apiParameters, $idGoal = false, $language
+ );
+
+ // TODO add static method getPrettyDate($period, $date) in Piwik_Period
+ $prettyDate = $processedReport['prettyDate'];
+
+ if ($mustRestoreGET) {
+ $_GET = $mustRestoreGET;
+ }
+
+ $processedReports[] = $processedReport;
+ }
+
+ // restore filter truncate parameter value
+ if ($initialFilterTruncate !== false) {
+ $_GET['filter_truncate'] = $initialFilterTruncate;
+ }
+
+ $notificationInfo = array(
+ self::REPORT_TYPE_INFO_KEY => $reportType,
+ self::OUTPUT_TYPE_INFO_KEY => $outputType,
+ self::REPORT_KEY => $report,
+ );
+
+ // allow plugins to alter processed reports
+ Piwik_PostEvent(
+ self::PROCESS_REPORTS_EVENT,
+ $processedReports,
+ $notificationInfo
+ );
+
+ // retrieve report renderer instance
+ $reportRenderer = null;
+ Piwik_PostEvent(
+ self::GET_RENDERER_INSTANCE_EVENT,
+ $reportRenderer,
+ $notificationInfo
+ );
+
+ // init report renderer
+ $reportRenderer->setLocale($language);
+
+ // render report
$description = str_replace(array("\r", "\n"), ' ', $report['description']);
// if the only report is "All websites", we don't display the site name
$websiteName = Piwik_Translate('General_Website') . " " . Piwik_Site::getNameFor($idSite);
- if(count($report['reports']) == 1
- && $report['reports'][0] == 'MultiSites_getAll')
- {
+ if (count($report['reports']) == 1
+ && $report['reports'][0] == 'MultiSites_getAll'
+ ) {
$websiteName = Piwik_Translate('General_MultiSitesSummary');
}
$reportTitle = "$websiteName - $prettyDate - $description";
@@ -437,335 +417,315 @@ class Piwik_PDFReports_API
$reportRenderer->renderFrontPage($websiteName, $prettyDate, $description, $reportMetadata);
array_walk($processedReports, array($reportRenderer, 'renderReport'));
- switch($outputType)
- {
- case self::OUTPUT_SAVE_ON_DISK:
- $outputFilename = strtoupper($reportFormat) . ' ' . ucfirst($reportType) .' Report - ' . $idReport . '.' . $date . '.' . $idSite . '.' . $language;
- $outputFilename = $reportRenderer->sendToDisk($outputFilename);
-
- $additionalFiles = array();
- if($reportRenderer instanceof Piwik_ReportRenderer_Html)
- {
- foreach ($processedReports as &$report) {
- if($report['displayGraph'])
- {
- $additionalFile = array();
- $additionalFile['filename'] = $report['metadata']['name'].'.png';
- $additionalFile['cid'] = $report['metadata']['uniqueId'];
- $additionalFile['content'] =
- Piwik_ReportRenderer::getStaticGraph(
- $report['metadata'],
- Piwik_ReportRenderer_Html::IMAGE_GRAPH_WIDTH,
- Piwik_ReportRenderer_Html::IMAGE_GRAPH_HEIGHT,
- $report['evolutionGraph']
- );
- $additionalFile['mimeType'] = 'image/png';
- $additionalFile['encoding'] = Zend_Mime::ENCODING_BASE64;
-
- $additionalFiles[] = $additionalFile;
- }
- }
- }
-
- return array(
- $outputFilename,
- $prettyDate,
- $websiteName,
- $additionalFiles,
- );
- break;
-
- case self::OUTPUT_INLINE:
-
- $reportRenderer->sendToBrowserInline($reportTitle);
- break;
-
- case self::OUTPUT_RETURN:
-
- return $reportRenderer->getRenderedReport();
- break;
-
- default:
- case self::OUTPUT_DOWNLOAD:
- $reportRenderer->sendToBrowserDownload($reportTitle);
- break;
- }
- }
-
- public function sendReport($idReport, $period = false, $date = false)
- {
- Piwik::checkUserIsNotAnonymous();
-
- $reports = $this->getReports($idSite = false, false, $idReport);
- $report = reset($reports);
-
- if($report['period'] == 'never')
- {
- $report['period'] = 'day';
- }
-
- if(!empty($period))
- {
- $report['period'] = $period;
- }
-
- if(empty($date))
- {
- $date = Piwik_Date::now()->subPeriod(1, $report['period'])->toString();
- }
-
- $language = Piwik_LanguagesManager_API::getInstance()->getLanguageForUser($report['login']);
-
- // generate report
- list($outputFilename, $prettyDate, $websiteName, $additionalFiles) =
- $this->generateReport(
- $idReport,
- $date,
- $language,
- self::OUTPUT_SAVE_ON_DISK,
- $report['period']
- );
-
- if(!file_exists($outputFilename))
- {
- throw new Exception("The report file wasn't found in $outputFilename");
- }
-
- $filename = basename($outputFilename);
- $handle = fopen($outputFilename, "r");
- $contents = fread($handle, filesize($outputFilename));
- fclose($handle);
-
- $notificationObject = null;
- Piwik_PostEvent(
- self::SEND_REPORT_EVENT,
- $notificationObject,
- $notificationInfo = array(
- self::REPORT_TYPE_INFO_KEY => $report['type'],
- self::REPORT_KEY => $report,
- self::REPORT_CONTENT_KEY => $contents,
- self::FILENAME_KEY => $filename,
- self::PRETTY_DATE_KEY => $prettyDate,
- self::WEBSITE_NAME_KEY => $websiteName,
- self::ADDITIONAL_FILES_KEY => $additionalFiles,
- )
- );
-
- // Update flag in DB
- Zend_Registry::get('db')->update( Piwik_Common::prefixTable('report'),
- array( 'ts_last_sent' => Piwik_Date::now()->getDatetime() ),
- "idreport = " . $report['idreport']
- );
-
- // If running from piwik.php with debug, do not delete the PDF after sending the email
- if(!isset($GLOBALS['PIWIK_TRACKER_DEBUG']) || !$GLOBALS['PIWIK_TRACKER_DEBUG'])
- {
- @chmod($outputFilename, 0600);
- }
- }
-
- private static function validateReportParameters($reportType, $parameters)
- {
- // get list of valid parameters
- $availableParameters = array();
-
- $notificationInfo = array(
- self::REPORT_TYPE_INFO_KEY => $reportType
- );
-
- Piwik_PostEvent(self::GET_REPORT_PARAMETERS_EVENT, $availableParameters, $notificationInfo);
-
- // unset invalid parameters
- $availableParameterKeys = array_keys($availableParameters);
- foreach ($parameters as $key => $value)
- {
- if(!in_array($key, $availableParameterKeys))
- {
- unset($parameters[$key]);
- }
- }
-
- // test that all required parameters are provided
- foreach($availableParameters as $parameter => $mandatory)
- {
- if($mandatory && !isset($parameters[$parameter]))
- {
- throw new Exception('Missing parameter : ' . $parameter);
- }
- }
-
- // delegate report parameter validation
- Piwik_PostEvent(self::VALIDATE_PARAMETERS_EVENT, $parameters, $notificationInfo);
-
- return Piwik_Common::json_encode($parameters);
- }
-
- private static function validateAndTruncateDescription(&$description)
- {
- $description = substr($description, 0, 250);
- }
-
- private static function validateRequestedReports($idSite, $reportType, $requestedReports)
- {
- if(!self::allowMultipleReports($reportType))
- {
- //sms can only contain one report, we silently discard all but the first
- $requestedReports = array_slice($requestedReports, 0, 1);
- }
-
- // retrieve available reports
- $availableReportMetadata = self::getReportMetadata($idSite, $reportType);
-
- $availableReportIds = array();
- foreach($availableReportMetadata as $reportMetadata)
- {
- $availableReportIds[] = $reportMetadata['uniqueId'];
- }
-
- foreach($requestedReports as $report)
- {
- if(!in_array($report, $availableReportIds))
- {
- throw new Exception("Report $report is unknown or not available for report type '$reportType'.");
- }
- }
-
- return Piwik_Common::json_encode($requestedReports);
- }
-
- private static function validateCommonReportAttributes($period, $hour, &$description, $reportType, $reportFormat)
- {
- self::validateReportPeriod($period);
- self::validateReportHour($hour);
- self::validateAndTruncateDescription($description);
- self::validateReportType($reportType);
- self::validateReportFormat($reportType, $reportFormat);
- }
-
- private static function validateReportPeriod($period)
- {
- $availablePeriods = array('day', 'week', 'month', 'never');
- if(!in_array($period, $availablePeriods))
- {
- throw new Exception('Period schedule must be one of the following: ' . implode(', ', $availablePeriods));
- }
- }
-
- private static function validateReportHour($hour)
- {
- if(!is_numeric($hour) || $hour < 0 || $hour > 23)
- {
- throw new Exception('Invalid hour schedule. Should be anything from 0 to 23 inclusive.');
- }
- }
-
- private static function validateReportType($reportType)
- {
- $reportTypes = array_keys(self::getReportTypes());
-
- if(!in_array($reportType, $reportTypes))
- {
- throw new Exception(
- 'Report type \'' . $reportType . '\' not valid. Try one of the following ' . implode(', ', $reportTypes)
- );
- }
- }
-
- private static function validateReportFormat($reportType, $reportFormat)
- {
- $reportFormats = array_keys(self::getReportFormats($reportType));
-
- if(!in_array($reportFormat, $reportFormats))
- {
- throw new Exception(
- Piwik_TranslateException(
- 'General_ExceptionInvalidReportRendererFormat',
- array($reportFormat, implode(', ', $reportFormats))
- )
- );
- }
- }
-
- /**
- * @ignore
- */
- static public function getReportMetadata($idSite, $reportType)
- {
- $notificationInfo = array(
- self::REPORT_TYPE_INFO_KEY => $reportType,
- self::ID_SITE_INFO_KEY => $idSite,
- );
-
- // retrieve available reports
- $availableReportMetadata = array();
- Piwik_PostEvent(
- self::GET_REPORT_METADATA_EVENT,
- $availableReportMetadata,
- $notificationInfo
- );
-
- return $availableReportMetadata;
- }
-
- /**
- * @ignore
- */
- static public function allowMultipleReports($reportType)
- {
- $allowMultipleReports = null;
- Piwik_PostEvent(
- self::ALLOW_MULTIPLE_REPORTS_EVENT,
- $allowMultipleReports,
- $notificationInfo = array(
- self::REPORT_TYPE_INFO_KEY => $reportType,
- )
- );
- return $allowMultipleReports;
- }
-
- /**
- * @ignore
- */
- static public function getReportTypes()
- {
- $reportTypes = array();
- Piwik_PostEvent(self::GET_REPORT_TYPES_EVENT, $reportTypes);
-
- return $reportTypes;
- }
-
- /**
- * @ignore
- */
- static public function getReportFormats($reportType)
- {
- $reportFormats = array();
-
- Piwik_PostEvent(
- self::GET_REPORT_FORMATS_EVENT,
- $reportFormats,
- $notificationInfo = array(
- self::REPORT_TYPE_INFO_KEY => $reportType
- )
- );
-
- return $reportFormats;
- }
-
- /**
- * @ignore
- */
- static public function getReportRecipients($report)
- {
- $notificationInfo = array(
- self::REPORT_TYPE_INFO_KEY => $report['type'],
- self::REPORT_KEY => $report,
- );
-
- // retrieve report renderer instance
- $recipients = array();
- Piwik_PostEvent(self::GET_REPORT_RECIPIENTS_EVENT, $recipients, $notificationInfo);
-
- return $recipients;
- }
+ switch ($outputType) {
+ case self::OUTPUT_SAVE_ON_DISK:
+ $outputFilename = strtoupper($reportFormat) . ' ' . ucfirst($reportType) . ' Report - ' . $idReport . '.' . $date . '.' . $idSite . '.' . $language;
+ $outputFilename = $reportRenderer->sendToDisk($outputFilename);
+
+ $additionalFiles = array();
+ if ($reportRenderer instanceof Piwik_ReportRenderer_Html) {
+ foreach ($processedReports as &$report) {
+ if ($report['displayGraph']) {
+ $additionalFile = array();
+ $additionalFile['filename'] = $report['metadata']['name'] . '.png';
+ $additionalFile['cid'] = $report['metadata']['uniqueId'];
+ $additionalFile['content'] =
+ Piwik_ReportRenderer::getStaticGraph(
+ $report['metadata'],
+ Piwik_ReportRenderer_Html::IMAGE_GRAPH_WIDTH,
+ Piwik_ReportRenderer_Html::IMAGE_GRAPH_HEIGHT,
+ $report['evolutionGraph']
+ );
+ $additionalFile['mimeType'] = 'image/png';
+ $additionalFile['encoding'] = Zend_Mime::ENCODING_BASE64;
+
+ $additionalFiles[] = $additionalFile;
+ }
+ }
+ }
+
+ return array(
+ $outputFilename,
+ $prettyDate,
+ $websiteName,
+ $additionalFiles,
+ );
+ break;
+
+ case self::OUTPUT_INLINE:
+
+ $reportRenderer->sendToBrowserInline($reportTitle);
+ break;
+
+ case self::OUTPUT_RETURN:
+
+ return $reportRenderer->getRenderedReport();
+ break;
+
+ default:
+ case self::OUTPUT_DOWNLOAD:
+ $reportRenderer->sendToBrowserDownload($reportTitle);
+ break;
+ }
+ }
+
+ public function sendReport($idReport, $period = false, $date = false)
+ {
+ Piwik::checkUserIsNotAnonymous();
+
+ $reports = $this->getReports($idSite = false, false, $idReport);
+ $report = reset($reports);
+
+ if ($report['period'] == 'never') {
+ $report['period'] = 'day';
+ }
+
+ if (!empty($period)) {
+ $report['period'] = $period;
+ }
+
+ if (empty($date)) {
+ $date = Piwik_Date::now()->subPeriod(1, $report['period'])->toString();
+ }
+
+ $language = Piwik_LanguagesManager_API::getInstance()->getLanguageForUser($report['login']);
+
+ // generate report
+ list($outputFilename, $prettyDate, $websiteName, $additionalFiles) =
+ $this->generateReport(
+ $idReport,
+ $date,
+ $language,
+ self::OUTPUT_SAVE_ON_DISK,
+ $report['period']
+ );
+
+ if (!file_exists($outputFilename)) {
+ throw new Exception("The report file wasn't found in $outputFilename");
+ }
+
+ $filename = basename($outputFilename);
+ $handle = fopen($outputFilename, "r");
+ $contents = fread($handle, filesize($outputFilename));
+ fclose($handle);
+
+ $notificationObject = null;
+ Piwik_PostEvent(
+ self::SEND_REPORT_EVENT,
+ $notificationObject,
+ $notificationInfo = array(
+ self::REPORT_TYPE_INFO_KEY => $report['type'],
+ self::REPORT_KEY => $report,
+ self::REPORT_CONTENT_KEY => $contents,
+ self::FILENAME_KEY => $filename,
+ self::PRETTY_DATE_KEY => $prettyDate,
+ self::WEBSITE_NAME_KEY => $websiteName,
+ self::ADDITIONAL_FILES_KEY => $additionalFiles,
+ )
+ );
+
+ // Update flag in DB
+ Zend_Registry::get('db')->update(Piwik_Common::prefixTable('report'),
+ array('ts_last_sent' => Piwik_Date::now()->getDatetime()),
+ "idreport = " . $report['idreport']
+ );
+
+ // If running from piwik.php with debug, do not delete the PDF after sending the email
+ if (!isset($GLOBALS['PIWIK_TRACKER_DEBUG']) || !$GLOBALS['PIWIK_TRACKER_DEBUG']) {
+ @chmod($outputFilename, 0600);
+ }
+ }
+
+ private static function validateReportParameters($reportType, $parameters)
+ {
+ // get list of valid parameters
+ $availableParameters = array();
+
+ $notificationInfo = array(
+ self::REPORT_TYPE_INFO_KEY => $reportType
+ );
+
+ Piwik_PostEvent(self::GET_REPORT_PARAMETERS_EVENT, $availableParameters, $notificationInfo);
+
+ // unset invalid parameters
+ $availableParameterKeys = array_keys($availableParameters);
+ foreach ($parameters as $key => $value) {
+ if (!in_array($key, $availableParameterKeys)) {
+ unset($parameters[$key]);
+ }
+ }
+
+ // test that all required parameters are provided
+ foreach ($availableParameters as $parameter => $mandatory) {
+ if ($mandatory && !isset($parameters[$parameter])) {
+ throw new Exception('Missing parameter : ' . $parameter);
+ }
+ }
+
+ // delegate report parameter validation
+ Piwik_PostEvent(self::VALIDATE_PARAMETERS_EVENT, $parameters, $notificationInfo);
+
+ return Piwik_Common::json_encode($parameters);
+ }
+
+ private static function validateAndTruncateDescription(&$description)
+ {
+ $description = substr($description, 0, 250);
+ }
+
+ private static function validateRequestedReports($idSite, $reportType, $requestedReports)
+ {
+ if (!self::allowMultipleReports($reportType)) {
+ //sms can only contain one report, we silently discard all but the first
+ $requestedReports = array_slice($requestedReports, 0, 1);
+ }
+
+ // retrieve available reports
+ $availableReportMetadata = self::getReportMetadata($idSite, $reportType);
+
+ $availableReportIds = array();
+ foreach ($availableReportMetadata as $reportMetadata) {
+ $availableReportIds[] = $reportMetadata['uniqueId'];
+ }
+
+ foreach ($requestedReports as $report) {
+ if (!in_array($report, $availableReportIds)) {
+ throw new Exception("Report $report is unknown or not available for report type '$reportType'.");
+ }
+ }
+
+ return Piwik_Common::json_encode($requestedReports);
+ }
+
+ private static function validateCommonReportAttributes($period, $hour, &$description, $reportType, $reportFormat)
+ {
+ self::validateReportPeriod($period);
+ self::validateReportHour($hour);
+ self::validateAndTruncateDescription($description);
+ self::validateReportType($reportType);
+ self::validateReportFormat($reportType, $reportFormat);
+ }
+
+ private static function validateReportPeriod($period)
+ {
+ $availablePeriods = array('day', 'week', 'month', 'never');
+ if (!in_array($period, $availablePeriods)) {
+ throw new Exception('Period schedule must be one of the following: ' . implode(', ', $availablePeriods));
+ }
+ }
+
+ private static function validateReportHour($hour)
+ {
+ if (!is_numeric($hour) || $hour < 0 || $hour > 23) {
+ throw new Exception('Invalid hour schedule. Should be anything from 0 to 23 inclusive.');
+ }
+ }
+
+ private static function validateReportType($reportType)
+ {
+ $reportTypes = array_keys(self::getReportTypes());
+
+ if (!in_array($reportType, $reportTypes)) {
+ throw new Exception(
+ 'Report type \'' . $reportType . '\' not valid. Try one of the following ' . implode(', ', $reportTypes)
+ );
+ }
+ }
+
+ private static function validateReportFormat($reportType, $reportFormat)
+ {
+ $reportFormats = array_keys(self::getReportFormats($reportType));
+
+ if (!in_array($reportFormat, $reportFormats)) {
+ throw new Exception(
+ Piwik_TranslateException(
+ 'General_ExceptionInvalidReportRendererFormat',
+ array($reportFormat, implode(', ', $reportFormats))
+ )
+ );
+ }
+ }
+
+ /**
+ * @ignore
+ */
+ static public function getReportMetadata($idSite, $reportType)
+ {
+ $notificationInfo = array(
+ self::REPORT_TYPE_INFO_KEY => $reportType,
+ self::ID_SITE_INFO_KEY => $idSite,
+ );
+
+ // retrieve available reports
+ $availableReportMetadata = array();
+ Piwik_PostEvent(
+ self::GET_REPORT_METADATA_EVENT,
+ $availableReportMetadata,
+ $notificationInfo
+ );
+
+ return $availableReportMetadata;
+ }
+
+ /**
+ * @ignore
+ */
+ static public function allowMultipleReports($reportType)
+ {
+ $allowMultipleReports = null;
+ Piwik_PostEvent(
+ self::ALLOW_MULTIPLE_REPORTS_EVENT,
+ $allowMultipleReports,
+ $notificationInfo = array(
+ self::REPORT_TYPE_INFO_KEY => $reportType,
+ )
+ );
+ return $allowMultipleReports;
+ }
+
+ /**
+ * @ignore
+ */
+ static public function getReportTypes()
+ {
+ $reportTypes = array();
+ Piwik_PostEvent(self::GET_REPORT_TYPES_EVENT, $reportTypes);
+
+ return $reportTypes;
+ }
+
+ /**
+ * @ignore
+ */
+ static public function getReportFormats($reportType)
+ {
+ $reportFormats = array();
+
+ Piwik_PostEvent(
+ self::GET_REPORT_FORMATS_EVENT,
+ $reportFormats,
+ $notificationInfo = array(
+ self::REPORT_TYPE_INFO_KEY => $reportType
+ )
+ );
+
+ return $reportFormats;
+ }
+
+ /**
+ * @ignore
+ */
+ static public function getReportRecipients($report)
+ {
+ $notificationInfo = array(
+ self::REPORT_TYPE_INFO_KEY => $report['type'],
+ self::REPORT_KEY => $report,
+ );
+
+ // retrieve report renderer instance
+ $recipients = array();
+ Piwik_PostEvent(self::GET_REPORT_RECIPIENTS_EVENT, $recipients, $notificationInfo);
+
+ return $recipients;
+ }
}
diff --git a/plugins/PDFReports/Controller.php b/plugins/PDFReports/Controller.php
index 3c996c2302..32e879c66d 100644
--- a/plugins/PDFReports/Controller.php
+++ b/plugins/PDFReports/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_PDFReports
*/
@@ -15,65 +15,61 @@
*/
class Piwik_PDFReports_Controller extends Piwik_Controller
{
- const DEFAULT_REPORT_TYPE = Piwik_PDFReports::EMAIL_TYPE;
+ const DEFAULT_REPORT_TYPE = Piwik_PDFReports::EMAIL_TYPE;
- public function index()
- {
- $view = Piwik_View::factory('index');
- $this->setGeneralVariablesView($view);
+ public function index()
+ {
+ $view = Piwik_View::factory('index');
+ $this->setGeneralVariablesView($view);
- $view->countWebsites = count(Piwik_SitesManager_API::getInstance()->getSitesIdWithAtLeastViewAccess());
+ $view->countWebsites = count(Piwik_SitesManager_API::getInstance()->getSitesIdWithAtLeastViewAccess());
- // get report types
- $reportTypes = Piwik_PDFReports_API::getReportTypes();
- $view->reportTypes = $reportTypes;
- $view->defaultReportType = self::DEFAULT_REPORT_TYPE;
- $view->defaultReportFormat = Piwik_PDFReports::DEFAULT_REPORT_FORMAT;
+ // get report types
+ $reportTypes = Piwik_PDFReports_API::getReportTypes();
+ $view->reportTypes = $reportTypes;
+ $view->defaultReportType = self::DEFAULT_REPORT_TYPE;
+ $view->defaultReportFormat = Piwik_PDFReports::DEFAULT_REPORT_FORMAT;
- $reportsByCategoryByType = array();
- $reportFormatsByReportType = array();
- $allowMultipleReportsByReportType = array();
- foreach($reportTypes as $reportType => $reportTypeIcon)
- {
- // get report formats
- $reportFormatsByReportType[$reportType] = Piwik_PDFReports_API::getReportFormats($reportType);
- $allowMultipleReportsByReportType[$reportType] = Piwik_PDFReports_API::allowMultipleReports($reportType);
+ $reportsByCategoryByType = array();
+ $reportFormatsByReportType = array();
+ $allowMultipleReportsByReportType = array();
+ foreach ($reportTypes as $reportType => $reportTypeIcon) {
+ // get report formats
+ $reportFormatsByReportType[$reportType] = Piwik_PDFReports_API::getReportFormats($reportType);
+ $allowMultipleReportsByReportType[$reportType] = Piwik_PDFReports_API::allowMultipleReports($reportType);
- // get report metadata
- $reportsByCategory = array();
- $availableReportMetadata = Piwik_PDFReports_API::getReportMetadata($this->idSite, $reportType);
- foreach($availableReportMetadata as $reportMetadata)
- {
- $reportsByCategory[$reportMetadata['category']][] = $reportMetadata;
- }
- $reportsByCategoryByType[$reportType] = $reportsByCategory;
- }
- $view->reportsByCategoryByReportType = $reportsByCategoryByType;
- $view->reportFormatsByReportType = $reportFormatsByReportType;
- $view->allowMultipleReportsByReportType = $allowMultipleReportsByReportType;
+ // get report metadata
+ $reportsByCategory = array();
+ $availableReportMetadata = Piwik_PDFReports_API::getReportMetadata($this->idSite, $reportType);
+ foreach ($availableReportMetadata as $reportMetadata) {
+ $reportsByCategory[$reportMetadata['category']][] = $reportMetadata;
+ }
+ $reportsByCategoryByType[$reportType] = $reportsByCategory;
+ }
+ $view->reportsByCategoryByReportType = $reportsByCategoryByType;
+ $view->reportFormatsByReportType = $reportFormatsByReportType;
+ $view->allowMultipleReportsByReportType = $allowMultipleReportsByReportType;
- $reports = array();
- $reportsById = array();
- if(!Piwik::isUserIsAnonymous())
- {
- $reports = Piwik_PDFReports_API::getInstance()->getReports($this->idSite, $period = false, $idReport = false, $ifSuperUserReturnOnlySuperUserReports = true);
- foreach($reports as &$report)
- {
- $report['recipients'] = Piwik_PDFReports_API::getReportRecipients($report);
- $reportsById[$report['idreport']] = $report;
- }
- }
- $view->reports = $reports;
- $view->reportsJSON = Piwik_Common::json_encode($reportsById);
+ $reports = array();
+ $reportsById = array();
+ if (!Piwik::isUserIsAnonymous()) {
+ $reports = Piwik_PDFReports_API::getInstance()->getReports($this->idSite, $period = false, $idReport = false, $ifSuperUserReturnOnlySuperUserReports = true);
+ foreach ($reports as &$report) {
+ $report['recipients'] = Piwik_PDFReports_API::getReportRecipients($report);
+ $reportsById[$report['idreport']] = $report;
+ }
+ }
+ $view->reports = $reports;
+ $view->reportsJSON = Piwik_Common::json_encode($reportsById);
- $view->downloadOutputType = Piwik_PDFReports_API::OUTPUT_INLINE;
+ $view->downloadOutputType = Piwik_PDFReports_API::OUTPUT_INLINE;
- $view->periods = Piwik_PDFReports::getPeriodToFrequency();
- $view->defaultPeriod = Piwik_PDFReports::DEFAULT_PERIOD;
- $view->defaultHour = Piwik_PDFReports::DEFAULT_HOUR;
+ $view->periods = Piwik_PDFReports::getPeriodToFrequency();
+ $view->defaultPeriod = Piwik_PDFReports::DEFAULT_PERIOD;
+ $view->defaultHour = Piwik_PDFReports::DEFAULT_HOUR;
- $view->language = Piwik_LanguagesManager::getLanguageCodeForCurrentUser();
+ $view->language = Piwik_LanguagesManager::getLanguageCodeForCurrentUser();
- echo $view->render();
- }
+ echo $view->render();
+ }
}
diff --git a/plugins/PDFReports/PDFReports.php b/plugins/PDFReports/PDFReports.php
index bf6f58596e..5f295b8366 100644
--- a/plugins/PDFReports/PDFReports.php
+++ b/plugins/PDFReports/PDFReports.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_PDFReports
*/
@@ -15,583 +15,542 @@
*/
class Piwik_PDFReports extends Piwik_Plugin
{
- const MOBILE_MESSAGING_TOP_MENU_TRANSLATION_KEY = 'MobileMessaging_TopMenu';
- const PDF_REPORTS_TOP_MENU_TRANSLATION_KEY = 'PDFReports_EmailReports';
-
- const DISPLAY_FORMAT_GRAPHS_ONLY_FOR_KEY_METRICS = 1; // Display Tables Only (Graphs only for key metrics)
- const DISPLAY_FORMAT_GRAPHS_ONLY = 2; // Display Graphs Only for all reports
- const DISPLAY_FORMAT_TABLES_AND_GRAPHS = 3; // Display Tables and Graphs for all reports
- const DISPLAY_FORMAT_TABLES_ONLY = 4; // Display only tables for all reports
- const DEFAULT_DISPLAY_FORMAT = self::DISPLAY_FORMAT_GRAPHS_ONLY_FOR_KEY_METRICS;
-
- const DEFAULT_REPORT_FORMAT = Piwik_ReportRenderer::HTML_FORMAT;
- const DEFAULT_PERIOD = 'week';
- const DEFAULT_HOUR = '0';
-
- const EMAIL_ME_PARAMETER = 'emailMe';
- const EVOLUTION_GRAPH_PARAMETER = 'evolutionGraph';
- const ADDITIONAL_EMAILS_PARAMETER = 'additionalEmails';
- const DISPLAY_FORMAT_PARAMETER = 'displayFormat';
- const EMAIL_ME_PARAMETER_DEFAULT_VALUE = true;
- const EVOLUTION_GRAPH_PARAMETER_DEFAULT_VALUE = false;
-
- const EMAIL_TYPE = 'email';
-
- static private $availableParameters = array(
- self::EMAIL_ME_PARAMETER => false,
- self::EVOLUTION_GRAPH_PARAMETER => false,
- self::ADDITIONAL_EMAILS_PARAMETER => false,
- self::DISPLAY_FORMAT_PARAMETER => true,
- );
-
- static private $managedReportTypes = array(
- self::EMAIL_TYPE => 'themes/default/images/email.png'
- );
-
- static private $managedReportFormats = array(
- Piwik_ReportRenderer::HTML_FORMAT => 'themes/default/images/html_icon.png',
- Piwik_ReportRenderer::PDF_FORMAT => 'plugins/UserSettings/images/plugins/pdf.gif'
- );
-
- public function getInformation()
- {
- return array(
- 'name' => 'Email Reports Plugin',
- 'description' => Piwik_Translate('PDFReports_PluginDescriptionReports'),
- 'author' => 'Piwik',
- 'author_homepage' => 'http://piwik.org/',
- 'version' => Piwik_Version::VERSION,
- );
- }
- public function getListHooksRegistered()
- {
- return array(
- 'TopMenu.add' => 'addTopMenu',
- 'TaskScheduler.getScheduledTasks' => 'getScheduledTasks',
- 'AssetManager.getJsFiles' => 'getJsFiles',
- 'PDFReports.getReportParameters' => 'getReportParameters',
- 'PDFReports.validateReportParameters' => 'validateReportParameters',
- 'PDFReports.getReportMetadata' => 'getReportMetadata',
- 'PDFReports.getReportTypes' => 'getReportTypes',
- 'PDFReports.getReportFormats' => 'getReportFormats',
- 'PDFReports.getRendererInstance' => 'getRendererInstance',
- 'PDFReports.getReportRecipients' => 'getReportRecipients',
- 'PDFReports.processReports' => 'processReports',
- 'PDFReports.allowMultipleReports' => 'allowMultipleReports',
- 'PDFReports.sendReport' => 'sendReport',
- 'template_reportParametersPDFReports' => 'template_reportParametersPDFReports',
- 'UsersManager.deleteUser' => 'deleteUserReport',
- 'SitesManager.deleteSite' => 'deleteSiteReport',
- );
- }
-
- /**
- * Delete reports for the website
- *
- * @param Piwik_Event_Notification $notification notification object
- */
- function deleteSiteReport( $notification )
- {
- $idSite = &$notification->getNotificationObject();
-
- $idReports = Piwik_PDFReports_API::getInstance()->getReports($idSite);
-
- foreach($idReports as $report)
- {
- $idReport = $report['idreport'];
- Piwik_PDFReports_API::getInstance()->deleteReport($idReport);
- }
- }
-
- /**
- * @param Piwik_Event_Notification $notification notification object
- */
- function getJsFiles( $notification )
- {
- $jsFiles = &$notification->getNotificationObject();
- $jsFiles[] = "plugins/PDFReports/templates/pdf.js";
- }
-
- /**
- * @param Piwik_Event_Notification $notification notification object
- */
- function validateReportParameters( $notification )
- {
- if(self::manageEvent($notification))
- {
- $parameters = &$notification->getNotificationObject();
-
- $reportFormat = $parameters[self::DISPLAY_FORMAT_PARAMETER];
- $availableDisplayFormats = array_keys(self::getDisplayFormats());
- if(!in_array($reportFormat, $availableDisplayFormats))
- {
- throw new Exception(
- Piwik_TranslateException(
- // General_ExceptionInvalidAggregateReportsFormat should be named General_ExceptionInvalidDisplayFormat
- 'General_ExceptionInvalidAggregateReportsFormat',
- array($reportFormat, implode(', ', $availableDisplayFormats))
- )
- );
- }
-
- // emailMe is an optional parameter
- if(!isset($parameters[self::EMAIL_ME_PARAMETER]))
- {
- $parameters[self::EMAIL_ME_PARAMETER] = self::EMAIL_ME_PARAMETER_DEFAULT_VALUE;
- }
- else
- {
- $parameters[self::EMAIL_ME_PARAMETER] = self::valueIsTrue($parameters[self::EMAIL_ME_PARAMETER]);
- }
-
- // evolutionGraph is an optional parameter
- if(!isset($parameters[self::EVOLUTION_GRAPH_PARAMETER]))
- {
- $parameters[self::EVOLUTION_GRAPH_PARAMETER] = self::EVOLUTION_GRAPH_PARAMETER_DEFAULT_VALUE;
- }
- else
- {
- $parameters[self::EVOLUTION_GRAPH_PARAMETER] = self::valueIsTrue($parameters[self::EVOLUTION_GRAPH_PARAMETER]);
- }
-
- // additionalEmails is an optional parameter
- if(isset($parameters[self::ADDITIONAL_EMAILS_PARAMETER]))
- {
- $parameters[self::ADDITIONAL_EMAILS_PARAMETER] = self::checkAdditionalEmails($parameters[self::ADDITIONAL_EMAILS_PARAMETER]);
- }
- }
- }
-
- // based on http://www.php.net/manual/en/filter.filters.validate.php -> FILTER_VALIDATE_BOOLEAN
- static private function valueIsTrue($value)
- {
- return $value == 'true' || $value == 1 || $value == '1' || $value === true;
- }
-
- /**
- * @param Piwik_Event_Notification $notification notification object
- */
- function getReportMetadata( $notification )
- {
- if(self::manageEvent($notification))
- {
- $reportMetadata = &$notification->getNotificationObject();
-
- $notificationInfo = $notification->getNotificationInfo();
- $idSite = $notificationInfo[Piwik_PDFReports_API::ID_SITE_INFO_KEY];
-
- $availableReportMetadata = Piwik_API_API::getInstance()->getReportMetadata($idSite);
-
- $filteredReportMetadata = array();
- foreach($availableReportMetadata as $reportMetadata)
- {
- // removing reports from the API category and MultiSites.getOne
- if(
- $reportMetadata['category'] == 'API' ||
- $reportMetadata['category'] == Piwik_Translate('General_MultiSitesSummary') && $reportMetadata['name'] == Piwik_Translate('General_SingleWebsitesDashboard')
- ) continue;
-
- $filteredReportMetadata[] = $reportMetadata;
- }
-
- $reportMetadata = $filteredReportMetadata;
- }
- }
-
- /**
- * @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 = 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 processReports( $notification )
- {
- if(self::manageEvent($notification))
- {
- $processedReports = &$notification->getNotificationObject();
-
- $notificationInfo = $notification->getNotificationInfo();
- $report = $notificationInfo[Piwik_PDFReports_API::REPORT_KEY];
-
- $displayFormat = $report['parameters'][self::DISPLAY_FORMAT_PARAMETER];
- $evolutionGraph = $report['parameters'][self::EVOLUTION_GRAPH_PARAMETER];
-
- foreach ($processedReports as &$processedReport)
- {
- $metadata = $processedReport['metadata'];
-
- $isAggregateReport = !empty($metadata['dimension']);
-
- $processedReport['displayTable'] = $displayFormat != self::DISPLAY_FORMAT_GRAPHS_ONLY;
-
- $processedReport['displayGraph'] =
- ($isAggregateReport ?
- $displayFormat == self::DISPLAY_FORMAT_GRAPHS_ONLY || $displayFormat == self::DISPLAY_FORMAT_TABLES_AND_GRAPHS
- :
- $displayFormat != self::DISPLAY_FORMAT_TABLES_ONLY)
- && Piwik::isGdExtensionEnabled()
- && Piwik_PluginsManager::getInstance()->isPluginActivated('ImageGraph')
- && !empty($metadata['imageGraphUrl']);
-
- $processedReport['evolutionGraph'] = $evolutionGraph;
-
- // remove evolution metrics from MultiSites.getAll
- if($metadata['module'] == 'MultiSites')
- {
- $columns = $processedReport['columns'];
-
- foreach(Piwik_MultiSites_API::getApiMetrics($enhanced = true) as $metricSettings)
- {
- unset($columns[$metricSettings[Piwik_MultiSites_API::METRIC_EVOLUTION_COL_NAME_KEY]]);
- }
-
- $processedReport['metadata'] = $metadata;
- $processedReport['columns'] = $columns;
- }
- }
- }
- }
-
- /**
- * @param Piwik_Event_Notification $notification notification object
- */
- function getRendererInstance( $notification )
- {
- if(self::manageEvent($notification))
- {
- $reportRenderer = &$notification->getNotificationObject();
- $notificationInfo = $notification->getNotificationInfo();
-
- $reportFormat = $notificationInfo[Piwik_PDFReports_API::REPORT_KEY]['format'];
- $outputType = $notificationInfo[Piwik_PDFReports_API::OUTPUT_TYPE_INFO_KEY];
-
- $reportRenderer = Piwik_ReportRenderer::factory($reportFormat);
-
- if($reportFormat == Piwik_ReportRenderer::HTML_FORMAT)
- {
- $reportRenderer->setRenderImageInline($outputType != Piwik_PDFReports_API::OUTPUT_SAVE_ON_DISK);
- }
- }
- }
-
- /**
- * @param Piwik_Event_Notification $notification notification object
- */
- function allowMultipleReports( $notification )
- {
- if(self::manageEvent($notification))
- {
- $allowMultipleReports = &$notification->getNotificationObject();
- $allowMultipleReports = true;
- }
- }
-
- /**
- * @param Piwik_Event_Notification $notification notification object
- */
- function sendReport( $notification )
- {
- if(self::manageEvent($notification))
- {
- $notificationInfo = $notification->getNotificationInfo();
- $report = $notificationInfo[Piwik_PDFReports_API::REPORT_KEY];
- $websiteName = $notificationInfo[Piwik_PDFReports_API::WEBSITE_NAME_KEY];
- $prettyDate = $notificationInfo[Piwik_PDFReports_API::PRETTY_DATE_KEY];
- $contents = $notificationInfo[Piwik_PDFReports_API::REPORT_CONTENT_KEY];
- $filename = $notificationInfo[Piwik_PDFReports_API::FILENAME_KEY];
- $additionalFiles = $notificationInfo[Piwik_PDFReports_API::ADDITIONAL_FILES_KEY];
-
- $periods = self::getPeriodToFrequency();
- $message = Piwik_Translate('PDFReports_EmailHello');
- $subject = Piwik_Translate('General_Report') . ' '. $websiteName . " - ".$prettyDate;
-
- $mail = new Piwik_Mail();
- $mail->setSubject($subject);
- $fromEmailName = Piwik_Config::getInstance()->branding['use_custom_logo']
- ? Piwik_Translate('CoreHome_WebAnalyticsReports')
- : Piwik_Translate('PDFReports_PiwikReports');
- $fromEmailAddress = Piwik_Config::getInstance()->General['noreply_email_address'];
- $attachmentName = $subject;
- $mail->setFrom($fromEmailAddress, $fromEmailName);
-
- switch ($report['format'])
- {
- case 'html':
-
- // Needed when using images as attachment with cid
- $mail->setType(Zend_Mime::MULTIPART_RELATED);
- $message .= "<br/>" . Piwik_Translate('PDFReports_PleaseFindBelow', array($periods[$report['period']], $websiteName));
- $mail->setBodyHtml($message . "<br/><br/>". $contents);
- break;
-
- default:
- case 'pdf':
- $message .= "\n" . Piwik_Translate('PDFReports_PleaseFindAttachedFile', array($periods[$report['period']], $websiteName));
- $mail->setBodyText($message);
- $mail->createAttachment(
- $contents,
- 'application/pdf',
- Zend_Mime::DISPOSITION_INLINE,
- Zend_Mime::ENCODING_BASE64,
- $attachmentName.'.pdf'
- );
- break;
- }
-
- foreach($additionalFiles as $additionalFile)
- {
- $fileContent = $additionalFile['content'];
- $at = $mail->createAttachment(
- $fileContent,
- $additionalFile['mimeType'],
- Zend_Mime::DISPOSITION_INLINE,
- $additionalFile['encoding'],
- $additionalFile['filename']
- );
- $at->id = $additionalFile['cid'];
-
- unset($fileContent);
- }
-
- // Get user emails and languages
- $reportParameters = $report['parameters'];
- $emails = array();
-
- if(isset($reportParameters[self::ADDITIONAL_EMAILS_PARAMETER]))
- {
- $emails = $reportParameters[self::ADDITIONAL_EMAILS_PARAMETER];
- }
-
- if($reportParameters[self::EMAIL_ME_PARAMETER] == 1)
- {
- if(Piwik::getCurrentUserLogin() == $report['login'])
- {
- $emails[] = Piwik::getCurrentUserEmail();
- }
- elseif($report['login'] == Piwik_Config::getInstance()->superuser['login'])
- {
- $emails[] = Piwik::getSuperUserEmail();
- }
- else
- {
- try {
- $user = Piwik_UsersManager_API::getInstance()->getUser($report['login']);
- } catch(Exception $e) {
- return;
- }
- $emails[] = $user['email'];
- }
- }
-
- foreach ($emails as $email)
- {
- if(empty($email)) {
- continue;
- }
- $mail->addTo($email);
-
- try {
- $mail->send();
- } catch(Exception $e) {
-
- // If running from piwik.php with debug, we ignore the 'email not sent' error
- if(!isset($GLOBALS['PIWIK_TRACKER_DEBUG']) || !$GLOBALS['PIWIK_TRACKER_DEBUG'])
- {
- throw new Exception("An error occured while sending '$filename' ".
- " to ". implode(', ',$mail->getRecipients()).
- ". Error was '". $e->getMessage()."'");
- }
- }
- $mail->clearRecipients();
- }
- }
- }
-
- /**
- * @param Piwik_Event_Notification $notification notification object
- */
- function getReportRecipients( $notification )
- {
- if(self::manageEvent($notification))
- {
- $recipients = &$notification->getNotificationObject();
- $notificationInfo = $notification->getNotificationInfo();
-
- $report = $notificationInfo[Piwik_PDFReports_API::REPORT_KEY];
- $parameters = $report['parameters'];
- $eMailMe = $parameters[self::EMAIL_ME_PARAMETER];
-
- if($eMailMe)
- {
- $recipients[] = Piwik::getCurrentUserEmail();
- }
-
- if(isset($parameters[self::ADDITIONAL_EMAILS_PARAMETER]))
- {
- $additionalEMails = $parameters[self::ADDITIONAL_EMAILS_PARAMETER];
- $recipients = array_merge($recipients, $additionalEMails);
- }
- $recipients = array_filter($recipients);
- }
- }
-
- /**
- * @param Piwik_Event_Notification $notification notification object
- */
- static public function template_reportParametersPDFReports($notification)
- {
- $out =& $notification->getNotificationObject();
-
- $view = Piwik_View::factory('report_parameters');
- $view->currentUserEmail = Piwik::getCurrentUserEmail();
- $view->displayFormats = self::getDisplayFormats();
- $view->reportType = self::EMAIL_TYPE;
- $view->defaultDisplayFormat = self::DEFAULT_DISPLAY_FORMAT;
- $view->defaultEmailMe = self::EMAIL_ME_PARAMETER_DEFAULT_VALUE ? 'true' : 'false';
- $view->defaultEvolutionGraph = self::EVOLUTION_GRAPH_PARAMETER_DEFAULT_VALUE ? 'true' : 'false';
- $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)
- );
- }
-
- /**
- * @param Piwik_Event_Notification $notification notification object
- */
- function getScheduledTasks ( $notification )
- {
- $arbitraryDateInUTC = Piwik_Date::factory('2011-01-01');
- $tasks = &$notification->getNotificationObject();
- foreach(Piwik_PDFReports_API::getInstance()->getReports() as $report)
- {
- if (!$report['deleted'] && $report['period'] != Piwik_ScheduledTime::PERIOD_NEVER)
- {
- $midnightInSiteTimezone =
- date (
- 'H',
- Piwik_Date::factory(
- $arbitraryDateInUTC,
- Piwik_Site::getTimezoneFor($report['idsite'])
- )->getTimestamp()
- );
-
- $hourInUTC = (24 - $midnightInSiteTimezone + $report['hour']) % 24;
-
- $schedule = Piwik_ScheduledTime::getScheduledTimeForPeriod($report['period']);
- $schedule->setHour($hourInUTC);
- $tasks[] = new Piwik_ScheduledTask (
- Piwik_PDFReports_API::getInstance(),
- 'sendReport',
- $report['idreport'], $schedule
- );
- }
- }
- }
+ const MOBILE_MESSAGING_TOP_MENU_TRANSLATION_KEY = 'MobileMessaging_TopMenu';
+ const PDF_REPORTS_TOP_MENU_TRANSLATION_KEY = 'PDFReports_EmailReports';
+
+ const DISPLAY_FORMAT_GRAPHS_ONLY_FOR_KEY_METRICS = 1; // Display Tables Only (Graphs only for key metrics)
+ const DISPLAY_FORMAT_GRAPHS_ONLY = 2; // Display Graphs Only for all reports
+ const DISPLAY_FORMAT_TABLES_AND_GRAPHS = 3; // Display Tables and Graphs for all reports
+ const DISPLAY_FORMAT_TABLES_ONLY = 4; // Display only tables for all reports
+ const DEFAULT_DISPLAY_FORMAT = self::DISPLAY_FORMAT_GRAPHS_ONLY_FOR_KEY_METRICS;
+
+ const DEFAULT_REPORT_FORMAT = Piwik_ReportRenderer::HTML_FORMAT;
+ const DEFAULT_PERIOD = 'week';
+ const DEFAULT_HOUR = '0';
+
+ const EMAIL_ME_PARAMETER = 'emailMe';
+ const EVOLUTION_GRAPH_PARAMETER = 'evolutionGraph';
+ const ADDITIONAL_EMAILS_PARAMETER = 'additionalEmails';
+ const DISPLAY_FORMAT_PARAMETER = 'displayFormat';
+ const EMAIL_ME_PARAMETER_DEFAULT_VALUE = true;
+ const EVOLUTION_GRAPH_PARAMETER_DEFAULT_VALUE = false;
+
+ const EMAIL_TYPE = 'email';
+
+ static private $availableParameters = array(
+ self::EMAIL_ME_PARAMETER => false,
+ self::EVOLUTION_GRAPH_PARAMETER => false,
+ self::ADDITIONAL_EMAILS_PARAMETER => false,
+ self::DISPLAY_FORMAT_PARAMETER => true,
+ );
+
+ static private $managedReportTypes = array(
+ self::EMAIL_TYPE => 'themes/default/images/email.png'
+ );
+
+ static private $managedReportFormats = array(
+ Piwik_ReportRenderer::HTML_FORMAT => 'themes/default/images/html_icon.png',
+ Piwik_ReportRenderer::PDF_FORMAT => 'plugins/UserSettings/images/plugins/pdf.gif'
+ );
+
+ public function getInformation()
+ {
+ return array(
+ 'name' => 'Email Reports Plugin',
+ 'description' => Piwik_Translate('PDFReports_PluginDescriptionReports'),
+ 'author' => 'Piwik',
+ 'author_homepage' => 'http://piwik.org/',
+ 'version' => Piwik_Version::VERSION,
+ );
+ }
+
+ public function getListHooksRegistered()
+ {
+ return array(
+ 'TopMenu.add' => 'addTopMenu',
+ 'TaskScheduler.getScheduledTasks' => 'getScheduledTasks',
+ 'AssetManager.getJsFiles' => 'getJsFiles',
+ 'PDFReports.getReportParameters' => 'getReportParameters',
+ 'PDFReports.validateReportParameters' => 'validateReportParameters',
+ 'PDFReports.getReportMetadata' => 'getReportMetadata',
+ 'PDFReports.getReportTypes' => 'getReportTypes',
+ 'PDFReports.getReportFormats' => 'getReportFormats',
+ 'PDFReports.getRendererInstance' => 'getRendererInstance',
+ 'PDFReports.getReportRecipients' => 'getReportRecipients',
+ 'PDFReports.processReports' => 'processReports',
+ 'PDFReports.allowMultipleReports' => 'allowMultipleReports',
+ 'PDFReports.sendReport' => 'sendReport',
+ 'template_reportParametersPDFReports' => 'template_reportParametersPDFReports',
+ 'UsersManager.deleteUser' => 'deleteUserReport',
+ 'SitesManager.deleteSite' => 'deleteSiteReport',
+ );
+ }
+
+ /**
+ * Delete reports for the website
+ *
+ * @param Piwik_Event_Notification $notification notification object
+ */
+ function deleteSiteReport($notification)
+ {
+ $idSite = & $notification->getNotificationObject();
+
+ $idReports = Piwik_PDFReports_API::getInstance()->getReports($idSite);
+
+ foreach ($idReports as $report) {
+ $idReport = $report['idreport'];
+ Piwik_PDFReports_API::getInstance()->deleteReport($idReport);
+ }
+ }
+
+ /**
+ * @param Piwik_Event_Notification $notification notification object
+ */
+ function getJsFiles($notification)
+ {
+ $jsFiles = & $notification->getNotificationObject();
+ $jsFiles[] = "plugins/PDFReports/templates/pdf.js";
+ }
+
+ /**
+ * @param Piwik_Event_Notification $notification notification object
+ */
+ function validateReportParameters($notification)
+ {
+ if (self::manageEvent($notification)) {
+ $parameters = & $notification->getNotificationObject();
+
+ $reportFormat = $parameters[self::DISPLAY_FORMAT_PARAMETER];
+ $availableDisplayFormats = array_keys(self::getDisplayFormats());
+ if (!in_array($reportFormat, $availableDisplayFormats)) {
+ throw new Exception(
+ Piwik_TranslateException(
+ // General_ExceptionInvalidAggregateReportsFormat should be named General_ExceptionInvalidDisplayFormat
+ 'General_ExceptionInvalidAggregateReportsFormat',
+ array($reportFormat, implode(', ', $availableDisplayFormats))
+ )
+ );
+ }
+
+ // emailMe is an optional parameter
+ if (!isset($parameters[self::EMAIL_ME_PARAMETER])) {
+ $parameters[self::EMAIL_ME_PARAMETER] = self::EMAIL_ME_PARAMETER_DEFAULT_VALUE;
+ } else {
+ $parameters[self::EMAIL_ME_PARAMETER] = self::valueIsTrue($parameters[self::EMAIL_ME_PARAMETER]);
+ }
+
+ // evolutionGraph is an optional parameter
+ if (!isset($parameters[self::EVOLUTION_GRAPH_PARAMETER])) {
+ $parameters[self::EVOLUTION_GRAPH_PARAMETER] = self::EVOLUTION_GRAPH_PARAMETER_DEFAULT_VALUE;
+ } else {
+ $parameters[self::EVOLUTION_GRAPH_PARAMETER] = self::valueIsTrue($parameters[self::EVOLUTION_GRAPH_PARAMETER]);
+ }
+
+ // additionalEmails is an optional parameter
+ if (isset($parameters[self::ADDITIONAL_EMAILS_PARAMETER])) {
+ $parameters[self::ADDITIONAL_EMAILS_PARAMETER] = self::checkAdditionalEmails($parameters[self::ADDITIONAL_EMAILS_PARAMETER]);
+ }
+ }
+ }
+
+ // based on http://www.php.net/manual/en/filter.filters.validate.php -> FILTER_VALIDATE_BOOLEAN
+ static private function valueIsTrue($value)
+ {
+ return $value == 'true' || $value == 1 || $value == '1' || $value === true;
+ }
+
+ /**
+ * @param Piwik_Event_Notification $notification notification object
+ */
+ function getReportMetadata($notification)
+ {
+ if (self::manageEvent($notification)) {
+ $reportMetadata = & $notification->getNotificationObject();
+
+ $notificationInfo = $notification->getNotificationInfo();
+ $idSite = $notificationInfo[Piwik_PDFReports_API::ID_SITE_INFO_KEY];
+
+ $availableReportMetadata = Piwik_API_API::getInstance()->getReportMetadata($idSite);
+
+ $filteredReportMetadata = array();
+ foreach ($availableReportMetadata as $reportMetadata) {
+ // removing reports from the API category and MultiSites.getOne
+ if (
+ $reportMetadata['category'] == 'API' ||
+ $reportMetadata['category'] == Piwik_Translate('General_MultiSitesSummary') && $reportMetadata['name'] == Piwik_Translate('General_SingleWebsitesDashboard')
+ ) continue;
+
+ $filteredReportMetadata[] = $reportMetadata;
+ }
+
+ $reportMetadata = $filteredReportMetadata;
+ }
+ }
+
+ /**
+ * @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 = 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 processReports($notification)
+ {
+ if (self::manageEvent($notification)) {
+ $processedReports = & $notification->getNotificationObject();
+
+ $notificationInfo = $notification->getNotificationInfo();
+ $report = $notificationInfo[Piwik_PDFReports_API::REPORT_KEY];
+
+ $displayFormat = $report['parameters'][self::DISPLAY_FORMAT_PARAMETER];
+ $evolutionGraph = $report['parameters'][self::EVOLUTION_GRAPH_PARAMETER];
+
+ foreach ($processedReports as &$processedReport) {
+ $metadata = $processedReport['metadata'];
+
+ $isAggregateReport = !empty($metadata['dimension']);
+
+ $processedReport['displayTable'] = $displayFormat != self::DISPLAY_FORMAT_GRAPHS_ONLY;
+
+ $processedReport['displayGraph'] =
+ ($isAggregateReport ?
+ $displayFormat == self::DISPLAY_FORMAT_GRAPHS_ONLY || $displayFormat == self::DISPLAY_FORMAT_TABLES_AND_GRAPHS
+ :
+ $displayFormat != self::DISPLAY_FORMAT_TABLES_ONLY)
+ && Piwik::isGdExtensionEnabled()
+ && Piwik_PluginsManager::getInstance()->isPluginActivated('ImageGraph')
+ && !empty($metadata['imageGraphUrl']);
+
+ $processedReport['evolutionGraph'] = $evolutionGraph;
+
+ // remove evolution metrics from MultiSites.getAll
+ if ($metadata['module'] == 'MultiSites') {
+ $columns = $processedReport['columns'];
+
+ foreach (Piwik_MultiSites_API::getApiMetrics($enhanced = true) as $metricSettings) {
+ unset($columns[$metricSettings[Piwik_MultiSites_API::METRIC_EVOLUTION_COL_NAME_KEY]]);
+ }
+
+ $processedReport['metadata'] = $metadata;
+ $processedReport['columns'] = $columns;
+ }
+ }
+ }
+ }
+
+ /**
+ * @param Piwik_Event_Notification $notification notification object
+ */
+ function getRendererInstance($notification)
+ {
+ if (self::manageEvent($notification)) {
+ $reportRenderer = & $notification->getNotificationObject();
+ $notificationInfo = $notification->getNotificationInfo();
+
+ $reportFormat = $notificationInfo[Piwik_PDFReports_API::REPORT_KEY]['format'];
+ $outputType = $notificationInfo[Piwik_PDFReports_API::OUTPUT_TYPE_INFO_KEY];
+
+ $reportRenderer = Piwik_ReportRenderer::factory($reportFormat);
+
+ if ($reportFormat == Piwik_ReportRenderer::HTML_FORMAT) {
+ $reportRenderer->setRenderImageInline($outputType != Piwik_PDFReports_API::OUTPUT_SAVE_ON_DISK);
+ }
+ }
+ }
+
+ /**
+ * @param Piwik_Event_Notification $notification notification object
+ */
+ function allowMultipleReports($notification)
+ {
+ if (self::manageEvent($notification)) {
+ $allowMultipleReports = & $notification->getNotificationObject();
+ $allowMultipleReports = true;
+ }
+ }
+
+ /**
+ * @param Piwik_Event_Notification $notification notification object
+ */
+ function sendReport($notification)
+ {
+ if (self::manageEvent($notification)) {
+ $notificationInfo = $notification->getNotificationInfo();
+ $report = $notificationInfo[Piwik_PDFReports_API::REPORT_KEY];
+ $websiteName = $notificationInfo[Piwik_PDFReports_API::WEBSITE_NAME_KEY];
+ $prettyDate = $notificationInfo[Piwik_PDFReports_API::PRETTY_DATE_KEY];
+ $contents = $notificationInfo[Piwik_PDFReports_API::REPORT_CONTENT_KEY];
+ $filename = $notificationInfo[Piwik_PDFReports_API::FILENAME_KEY];
+ $additionalFiles = $notificationInfo[Piwik_PDFReports_API::ADDITIONAL_FILES_KEY];
+
+ $periods = self::getPeriodToFrequency();
+ $message = Piwik_Translate('PDFReports_EmailHello');
+ $subject = Piwik_Translate('General_Report') . ' ' . $websiteName . " - " . $prettyDate;
+
+ $mail = new Piwik_Mail();
+ $mail->setSubject($subject);
+ $fromEmailName = Piwik_Config::getInstance()->branding['use_custom_logo']
+ ? Piwik_Translate('CoreHome_WebAnalyticsReports')
+ : Piwik_Translate('PDFReports_PiwikReports');
+ $fromEmailAddress = Piwik_Config::getInstance()->General['noreply_email_address'];
+ $attachmentName = $subject;
+ $mail->setFrom($fromEmailAddress, $fromEmailName);
+
+ switch ($report['format']) {
+ case 'html':
+
+ // Needed when using images as attachment with cid
+ $mail->setType(Zend_Mime::MULTIPART_RELATED);
+ $message .= "<br/>" . Piwik_Translate('PDFReports_PleaseFindBelow', array($periods[$report['period']], $websiteName));
+ $mail->setBodyHtml($message . "<br/><br/>" . $contents);
+ break;
+
+ default:
+ case 'pdf':
+ $message .= "\n" . Piwik_Translate('PDFReports_PleaseFindAttachedFile', array($periods[$report['period']], $websiteName));
+ $mail->setBodyText($message);
+ $mail->createAttachment(
+ $contents,
+ 'application/pdf',
+ Zend_Mime::DISPOSITION_INLINE,
+ Zend_Mime::ENCODING_BASE64,
+ $attachmentName . '.pdf'
+ );
+ break;
+ }
+
+ foreach ($additionalFiles as $additionalFile) {
+ $fileContent = $additionalFile['content'];
+ $at = $mail->createAttachment(
+ $fileContent,
+ $additionalFile['mimeType'],
+ Zend_Mime::DISPOSITION_INLINE,
+ $additionalFile['encoding'],
+ $additionalFile['filename']
+ );
+ $at->id = $additionalFile['cid'];
+
+ unset($fileContent);
+ }
+
+ // Get user emails and languages
+ $reportParameters = $report['parameters'];
+ $emails = array();
+
+ if (isset($reportParameters[self::ADDITIONAL_EMAILS_PARAMETER])) {
+ $emails = $reportParameters[self::ADDITIONAL_EMAILS_PARAMETER];
+ }
+
+ if ($reportParameters[self::EMAIL_ME_PARAMETER] == 1) {
+ if (Piwik::getCurrentUserLogin() == $report['login']) {
+ $emails[] = Piwik::getCurrentUserEmail();
+ } elseif ($report['login'] == Piwik_Config::getInstance()->superuser['login']) {
+ $emails[] = Piwik::getSuperUserEmail();
+ } else {
+ try {
+ $user = Piwik_UsersManager_API::getInstance()->getUser($report['login']);
+ } catch (Exception $e) {
+ return;
+ }
+ $emails[] = $user['email'];
+ }
+ }
+
+ foreach ($emails as $email) {
+ if (empty($email)) {
+ continue;
+ }
+ $mail->addTo($email);
+
+ try {
+ $mail->send();
+ } catch (Exception $e) {
+
+ // If running from piwik.php with debug, we ignore the 'email not sent' error
+ if (!isset($GLOBALS['PIWIK_TRACKER_DEBUG']) || !$GLOBALS['PIWIK_TRACKER_DEBUG']) {
+ throw new Exception("An error occured while sending '$filename' " .
+ " to " . implode(', ', $mail->getRecipients()) .
+ ". Error was '" . $e->getMessage() . "'");
+ }
+ }
+ $mail->clearRecipients();
+ }
+ }
+ }
+
+ /**
+ * @param Piwik_Event_Notification $notification notification object
+ */
+ function getReportRecipients($notification)
+ {
+ if (self::manageEvent($notification)) {
+ $recipients = & $notification->getNotificationObject();
+ $notificationInfo = $notification->getNotificationInfo();
+
+ $report = $notificationInfo[Piwik_PDFReports_API::REPORT_KEY];
+ $parameters = $report['parameters'];
+ $eMailMe = $parameters[self::EMAIL_ME_PARAMETER];
+
+ if ($eMailMe) {
+ $recipients[] = Piwik::getCurrentUserEmail();
+ }
+
+ if (isset($parameters[self::ADDITIONAL_EMAILS_PARAMETER])) {
+ $additionalEMails = $parameters[self::ADDITIONAL_EMAILS_PARAMETER];
+ $recipients = array_merge($recipients, $additionalEMails);
+ }
+ $recipients = array_filter($recipients);
+ }
+ }
+
+ /**
+ * @param Piwik_Event_Notification $notification notification object
+ */
+ static public function template_reportParametersPDFReports($notification)
+ {
+ $out =& $notification->getNotificationObject();
+
+ $view = Piwik_View::factory('report_parameters');
+ $view->currentUserEmail = Piwik::getCurrentUserEmail();
+ $view->displayFormats = self::getDisplayFormats();
+ $view->reportType = self::EMAIL_TYPE;
+ $view->defaultDisplayFormat = self::DEFAULT_DISPLAY_FORMAT;
+ $view->defaultEmailMe = self::EMAIL_ME_PARAMETER_DEFAULT_VALUE ? 'true' : 'false';
+ $view->defaultEvolutionGraph = self::EVOLUTION_GRAPH_PARAMETER_DEFAULT_VALUE ? 'true' : 'false';
+ $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)
+ );
+ }
+
+ /**
+ * @param Piwik_Event_Notification $notification notification object
+ */
+ function getScheduledTasks($notification)
+ {
+ $arbitraryDateInUTC = Piwik_Date::factory('2011-01-01');
+ $tasks = & $notification->getNotificationObject();
+ foreach (Piwik_PDFReports_API::getInstance()->getReports() as $report) {
+ if (!$report['deleted'] && $report['period'] != Piwik_ScheduledTime::PERIOD_NEVER) {
+ $midnightInSiteTimezone =
+ date(
+ 'H',
+ Piwik_Date::factory(
+ $arbitraryDateInUTC,
+ Piwik_Site::getTimezoneFor($report['idsite'])
+ )->getTimestamp()
+ );
+
+ $hourInUTC = (24 - $midnightInSiteTimezone + $report['hour']) % 24;
+
+ $schedule = Piwik_ScheduledTime::getScheduledTimeForPeriod($report['period']);
+ $schedule->setHour($hourInUTC);
+ $tasks[] = new Piwik_ScheduledTask (
+ Piwik_PDFReports_API::getInstance(),
+ 'sendReport',
+ $report['idreport'], $schedule
+ );
+ }
+ }
+ }
function addTopMenu()
{
- Piwik_AddTopMenu(
- $this->getTopMenuTranslationKey(),
- array('module' => 'PDFReports', 'action' => 'index'),
- true,
- 13,
- $isHTML = false,
- $tooltip = Piwik_Translate(
- Piwik_PluginsManager::getInstance()->isPluginActivated('MobileMessaging')
- ? 'MobileMessaging_TopLinkTooltip' : 'PDFReports_TopLinkTooltip'
- )
- );
+ Piwik_AddTopMenu(
+ $this->getTopMenuTranslationKey(),
+ array('module' => 'PDFReports', 'action' => 'index'),
+ true,
+ 13,
+ $isHTML = false,
+ $tooltip = Piwik_Translate(
+ Piwik_PluginsManager::getInstance()->isPluginActivated('MobileMessaging')
+ ? 'MobileMessaging_TopLinkTooltip' : 'PDFReports_TopLinkTooltip'
+ )
+ );
}
- function getTopMenuTranslationKey()
- {
- // if MobileMessaging is not activated, display 'Email reports'
- if(!Piwik_PluginsManager::getInstance()->isPluginActivated('MobileMessaging'))
- return self::PDF_REPORTS_TOP_MENU_TRANSLATION_KEY;
-
- if(Piwik::isUserIsAnonymous())
- {
- return self::MOBILE_MESSAGING_TOP_MENU_TRANSLATION_KEY;
- }
-
- $reports = Piwik_PDFReports_API::getInstance()->getReports();
- $reportCount = count($reports);
-
- // if there are no reports and the mobile account is
- // not configured, display 'Email reports'
- // configured, display 'Email & SMS reports'
- if($reportCount == 0)
- return Piwik_MobileMessaging_API::getInstance()->areSMSAPICredentialProvided() ?
- self::MOBILE_MESSAGING_TOP_MENU_TRANSLATION_KEY : self::PDF_REPORTS_TOP_MENU_TRANSLATION_KEY;
-
- $anyMobileReport = false;
- foreach($reports as $report)
- {
- if($report['type'] == Piwik_MobileMessaging::MOBILE_TYPE)
- {
- $anyMobileReport = true;
- break;
- }
- }
-
- // if there is at least one sms report, display 'Email & SMS reports'
- if($anyMobileReport)
- {
- return self::MOBILE_MESSAGING_TOP_MENU_TRANSLATION_KEY;
- }
-
- return self::PDF_REPORTS_TOP_MENU_TRANSLATION_KEY;
- }
-
- /**
- * @param Piwik_Event_Notification $notification notification object
- */
+ function getTopMenuTranslationKey()
+ {
+ // if MobileMessaging is not activated, display 'Email reports'
+ if (!Piwik_PluginsManager::getInstance()->isPluginActivated('MobileMessaging'))
+ return self::PDF_REPORTS_TOP_MENU_TRANSLATION_KEY;
+
+ if (Piwik::isUserIsAnonymous()) {
+ return self::MOBILE_MESSAGING_TOP_MENU_TRANSLATION_KEY;
+ }
+
+ $reports = Piwik_PDFReports_API::getInstance()->getReports();
+ $reportCount = count($reports);
+
+ // if there are no reports and the mobile account is
+ // not configured, display 'Email reports'
+ // configured, display 'Email & SMS reports'
+ if ($reportCount == 0)
+ return Piwik_MobileMessaging_API::getInstance()->areSMSAPICredentialProvided() ?
+ self::MOBILE_MESSAGING_TOP_MENU_TRANSLATION_KEY : self::PDF_REPORTS_TOP_MENU_TRANSLATION_KEY;
+
+ $anyMobileReport = false;
+ foreach ($reports as $report) {
+ if ($report['type'] == Piwik_MobileMessaging::MOBILE_TYPE) {
+ $anyMobileReport = true;
+ break;
+ }
+ }
+
+ // if there is at least one sms report, display 'Email & SMS reports'
+ if ($anyMobileReport) {
+ return self::MOBILE_MESSAGING_TOP_MENU_TRANSLATION_KEY;
+ }
+
+ return self::PDF_REPORTS_TOP_MENU_TRANSLATION_KEY;
+ }
+
+ /**
+ * @param Piwik_Event_Notification $notification notification object
+ */
function deleteUserReport($notification)
- {
- $userLogin = $notification->getNotificationObject();
- Piwik_Query('DELETE FROM ' . Piwik_Common::prefixTable('report') . ' WHERE login = ?', $userLogin);
+ {
+ $userLogin = $notification->getNotificationObject();
+ Piwik_Query('DELETE FROM ' . Piwik_Common::prefixTable('report') . ' WHERE login = ?', $userLogin);
}
-
+
function install()
- {
- $queries[] = '
- CREATE TABLE `'.Piwik_Common::prefixTable('report').'` (
+ {
+ $queries[] = '
+ CREATE TABLE `' . Piwik_Common::prefixTable('report') . '` (
`idreport` INT(11) NOT NULL AUTO_INCREMENT,
`idsite` INTEGER(11) NOT NULL,
`login` VARCHAR(100) NOT NULL,
@@ -608,61 +567,54 @@ class Piwik_PDFReports extends Piwik_Plugin
PRIMARY KEY (`idreport`)
) DEFAULT CHARSET=utf8';
try {
- foreach($queries as $query)
- {
- Piwik_Exec($query);
- }
- }
- catch(Exception $e) {
- if(!Zend_Registry::get('db')->isErrNo($e, '1050'))
- {
- throw $e;
- }
- }
- }
-
- private static function checkAdditionalEmails($additionalEmails)
- {
- foreach($additionalEmails as &$email)
- {
- $email = trim($email);
- if(empty($email))
- {
- $email = false;
- }
- elseif(!Piwik::isValidEmailString($email))
- {
- throw new Exception(Piwik_TranslateException('UsersManager_ExceptionInvalidEmail') . ' ('.$email.')');
- }
- }
- $additionalEmails = array_filter($additionalEmails);
- return $additionalEmails;
- }
-
- private static function getDisplayFormats()
- {
- $displayFormats = array(
- // PDFReports_AggregateReportsFormat_TablesOnly should be named PDFReports_DisplayFormat_GraphsOnlyForKeyMetrics
- self::DISPLAY_FORMAT_GRAPHS_ONLY_FOR_KEY_METRICS => Piwik_Translate('PDFReports_AggregateReportsFormat_TablesOnly'),
- // PDFReports_AggregateReportsFormat_GraphsOnly should be named PDFReports_DisplayFormat_GraphsOnly
- self::DISPLAY_FORMAT_GRAPHS_ONLY => Piwik_Translate('PDFReports_AggregateReportsFormat_GraphsOnly'),
- // PDFReports_AggregateReportsFormat_TablesAndGraphs should be named PDFReports_DisplayFormat_TablesAndGraphs
- self::DISPLAY_FORMAT_TABLES_AND_GRAPHS => Piwik_Translate('PDFReports_AggregateReportsFormat_TablesAndGraphs'),
- self::DISPLAY_FORMAT_TABLES_ONLY => Piwik_Translate('PDFReports_DisplayFormat_TablesOnly'),
- );
- return $displayFormats;
- }
-
- /**
- * @ignore
- */
- static public function getPeriodToFrequency()
- {
- return array(
- Piwik_ScheduledTime::PERIOD_NEVER => Piwik_Translate('General_Never'),
- Piwik_ScheduledTime::PERIOD_DAY => Piwik_Translate('General_Daily'),
- Piwik_ScheduledTime::PERIOD_WEEK => Piwik_Translate('General_Weekly'),
- Piwik_ScheduledTime::PERIOD_MONTH => Piwik_Translate('General_Monthly'),
- );
- }
+ foreach ($queries as $query) {
+ Piwik_Exec($query);
+ }
+ } catch (Exception $e) {
+ if (!Zend_Registry::get('db')->isErrNo($e, '1050')) {
+ throw $e;
+ }
+ }
+ }
+
+ private static function checkAdditionalEmails($additionalEmails)
+ {
+ foreach ($additionalEmails as &$email) {
+ $email = trim($email);
+ if (empty($email)) {
+ $email = false;
+ } elseif (!Piwik::isValidEmailString($email)) {
+ throw new Exception(Piwik_TranslateException('UsersManager_ExceptionInvalidEmail') . ' (' . $email . ')');
+ }
+ }
+ $additionalEmails = array_filter($additionalEmails);
+ return $additionalEmails;
+ }
+
+ private static function getDisplayFormats()
+ {
+ $displayFormats = array(
+ // PDFReports_AggregateReportsFormat_TablesOnly should be named PDFReports_DisplayFormat_GraphsOnlyForKeyMetrics
+ self::DISPLAY_FORMAT_GRAPHS_ONLY_FOR_KEY_METRICS => Piwik_Translate('PDFReports_AggregateReportsFormat_TablesOnly'),
+ // PDFReports_AggregateReportsFormat_GraphsOnly should be named PDFReports_DisplayFormat_GraphsOnly
+ self::DISPLAY_FORMAT_GRAPHS_ONLY => Piwik_Translate('PDFReports_AggregateReportsFormat_GraphsOnly'),
+ // PDFReports_AggregateReportsFormat_TablesAndGraphs should be named PDFReports_DisplayFormat_TablesAndGraphs
+ self::DISPLAY_FORMAT_TABLES_AND_GRAPHS => Piwik_Translate('PDFReports_AggregateReportsFormat_TablesAndGraphs'),
+ self::DISPLAY_FORMAT_TABLES_ONLY => Piwik_Translate('PDFReports_DisplayFormat_TablesOnly'),
+ );
+ return $displayFormats;
+ }
+
+ /**
+ * @ignore
+ */
+ static public function getPeriodToFrequency()
+ {
+ return array(
+ Piwik_ScheduledTime::PERIOD_NEVER => Piwik_Translate('General_Never'),
+ Piwik_ScheduledTime::PERIOD_DAY => Piwik_Translate('General_Daily'),
+ Piwik_ScheduledTime::PERIOD_WEEK => Piwik_Translate('General_Weekly'),
+ Piwik_ScheduledTime::PERIOD_MONTH => Piwik_Translate('General_Monthly'),
+ );
+ }
}
diff --git a/plugins/PDFReports/config/tcpdf_config.php b/plugins/PDFReports/config/tcpdf_config.php
index 094fdd1985..096171d164 100644
--- a/plugins/PDFReports/config/tcpdf_config.php
+++ b/plugins/PDFReports/config/tcpdf_config.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_PDFReports
*/
@@ -15,223 +15,223 @@
* @package Piwik_PDFReports
*/
-define('K_PATH_MAIN', PIWIK_INCLUDE_PATH.'/libs/tcpdf/');
-define('K_PATH_CACHE', PIWIK_USER_PATH.'/tmp/tcpdf/');
-define('K_PATH_IMAGES', PIWIK_USER_PATH.'/tmp/tcpdf/');
+define('K_PATH_MAIN', PIWIK_INCLUDE_PATH . '/libs/tcpdf/');
+define('K_PATH_CACHE', PIWIK_USER_PATH . '/tmp/tcpdf/');
+define('K_PATH_IMAGES', PIWIK_USER_PATH . '/tmp/tcpdf/');
if (!defined('K_TCPDF_EXTERNAL_CONFIG')) {
- // DOCUMENT_ROOT fix for IIS Webserver
- if ((!isset($_SERVER['DOCUMENT_ROOT'])) OR (empty($_SERVER['DOCUMENT_ROOT']))) {
- if(isset($_SERVER['SCRIPT_FILENAME'])) {
- $_SERVER['DOCUMENT_ROOT'] = str_replace( '\\', '/', substr($_SERVER['SCRIPT_FILENAME'], 0, 0-strlen($_SERVER['PHP_SELF'])));
- } elseif(isset($_SERVER['PATH_TRANSLATED'])) {
- $_SERVER['DOCUMENT_ROOT'] = str_replace( '\\', '/', substr(str_replace('\\\\', '\\', $_SERVER['PATH_TRANSLATED']), 0, 0-strlen($_SERVER['PHP_SELF'])));
- } else {
- // define here your DOCUMENT_ROOT path if the previous fails
- $_SERVER['DOCUMENT_ROOT'] = '/var/www';
- }
- }
-
- if (!defined('K_PATH_MAIN')) {
- // Automatic calculation for the following K_PATH_MAIN constant
- $k_path_main = str_replace( '\\', '/', realpath(substr(dirname(__FILE__), 0, 0-strlen('config'))));
- if (substr($k_path_main, -1) != '/') {
- $k_path_main .= '/';
- }
-
- /**
- * Installation path (/var/www/tcpdf/).
- * By default it is automatically calculated but you can also set it as a fixed string to improve performances.
- */
- define ('K_PATH_MAIN', $k_path_main);
- }
-
- if (!defined('K_PATH_URL')) {
- // Automatic calculation for the following K_PATH_URL constant
- $k_path_url = K_PATH_MAIN; // default value for console mode
- if (isset($_SERVER['HTTP_HOST']) AND (!empty($_SERVER['HTTP_HOST']))) {
- if(isset($_SERVER['HTTPS']) AND (!empty($_SERVER['HTTPS'])) AND strtolower($_SERVER['HTTPS'])!='off') {
- $k_path_url = 'https://';
- } else {
- $k_path_url = 'http://';
- }
- $k_path_url .= $_SERVER['HTTP_HOST'];
- $k_path_url .= str_replace( '\\', '/', substr(K_PATH_MAIN, (strlen($_SERVER['DOCUMENT_ROOT']) - 1)));
- }
-
- /**
- * URL path to tcpdf installation folder (http://localhost/tcpdf/).
- * By default it is automatically calculated but you can also set it as a fixed string to improve performances.
- */
- define ('K_PATH_URL', $k_path_url);
- }
-
- /**
- * path for PDF fonts
- * use K_PATH_MAIN.'fonts/old/' for old non-UTF8 fonts
- */
- define ('K_PATH_FONTS', K_PATH_MAIN.'fonts/');
-
- /**
- * cache directory for temporary files (full path)
- */
- if (!defined('K_PATH_CACHE')) {
- define ('K_PATH_CACHE', K_PATH_MAIN.'cache/');
- }
-
- /**
- * cache directory for temporary files (url path)
- */
- define ('K_PATH_URL_CACHE', K_PATH_URL.'cache/');
-
- /**
- *images directory
- */
- if (!defined('K_PATH_IMAGES')) {
- define ('K_PATH_IMAGES', K_PATH_MAIN.'images/');
- }
-
- /**
- * blank image
- */
- define ('K_BLANK_IMAGE', K_PATH_IMAGES.'_blank.png');
-
- /**
- * page format
- */
- define ('PDF_PAGE_FORMAT', 'A4');
-
- /**
- * page orientation (P=portrait, L=landscape)
- */
- define ('PDF_PAGE_ORIENTATION', 'P');
-
- /**
- * document creator
- */
- define ('PDF_CREATOR', 'TCPDF');
-
- /**
- * document author
- */
- define ('PDF_AUTHOR', 'TCPDF');
-
- /**
- * header title
- */
- define ('PDF_HEADER_TITLE', 'TCPDF Example');
-
- /**
- * header description string
- */
- define ('PDF_HEADER_STRING', "by Nicola Asuni - Tecnick.com\nwww.tcpdf.org");
-
- /**
- * image logo
- */
- define ('PDF_HEADER_LOGO', 'tcpdf_logo.jpg');
-
- /**
- * header logo image width [mm]
- */
- define ('PDF_HEADER_LOGO_WIDTH', 30);
-
- /**
- * document unit of measure [pt=point, mm=millimeter, cm=centimeter, in=inch]
- */
- define ('PDF_UNIT', 'mm');
-
- /**
- * header margin
- */
- define ('PDF_MARGIN_HEADER', 5);
-
- /**
- * footer margin
- */
- define ('PDF_MARGIN_FOOTER', 10);
-
- /**
- * top margin
- */
- define ('PDF_MARGIN_TOP', 27);
-
- /**
- * bottom margin
- */
- define ('PDF_MARGIN_BOTTOM', 25);
-
- /**
- * left margin
- */
- define ('PDF_MARGIN_LEFT', 15);
-
- /**
- * right margin
- */
- define ('PDF_MARGIN_RIGHT', 15);
-
- /**
- * default main font name
- */
- define ('PDF_FONT_NAME_MAIN', 'helvetica');
-
- /**
- * default main font size
- */
- define ('PDF_FONT_SIZE_MAIN', 10);
-
- /**
- * default data font name
- */
- define ('PDF_FONT_NAME_DATA', 'helvetica');
-
- /**
- * default data font size
- */
- define ('PDF_FONT_SIZE_DATA', 8);
-
- /**
- * default monospaced font name
- */
- define ('PDF_FONT_MONOSPACED', 'courier');
-
- /**
- * ratio used to adjust the conversion of pixels to user units
- */
- define ('PDF_IMAGE_SCALE_RATIO', 1.25);
-
- /**
- * magnification factor for titles
- */
- define('HEAD_MAGNIFICATION', 1.1);
-
- /**
- * height of cell repect font height
- */
- define('K_CELL_HEIGHT_RATIO', 1.25);
-
- /**
- * title magnification respect main font size
- */
- define('K_TITLE_MAGNIFICATION', 1.3);
-
- /**
- * reduction factor for small font
- */
- define('K_SMALL_RATIO', 2/3);
-
- /**
- * set to true to enable the special procedure used to avoid the overlappind of symbols on Thai language
- */
- define('K_THAI_TOPCHARS', true);
-
- /**
- * if true allows to call TCPDF methods using HTML syntax
- * IMPORTANT: For security reason, disable this feature if you are printing user HTML content.
- */
- define('K_TCPDF_CALLS_IN_HTML', true);
+ // DOCUMENT_ROOT fix for IIS Webserver
+ if ((!isset($_SERVER['DOCUMENT_ROOT'])) OR (empty($_SERVER['DOCUMENT_ROOT']))) {
+ if (isset($_SERVER['SCRIPT_FILENAME'])) {
+ $_SERVER['DOCUMENT_ROOT'] = str_replace('\\', '/', substr($_SERVER['SCRIPT_FILENAME'], 0, 0 - strlen($_SERVER['PHP_SELF'])));
+ } elseif (isset($_SERVER['PATH_TRANSLATED'])) {
+ $_SERVER['DOCUMENT_ROOT'] = str_replace('\\', '/', substr(str_replace('\\\\', '\\', $_SERVER['PATH_TRANSLATED']), 0, 0 - strlen($_SERVER['PHP_SELF'])));
+ } else {
+ // define here your DOCUMENT_ROOT path if the previous fails
+ $_SERVER['DOCUMENT_ROOT'] = '/var/www';
+ }
+ }
+
+ if (!defined('K_PATH_MAIN')) {
+ // Automatic calculation for the following K_PATH_MAIN constant
+ $k_path_main = str_replace('\\', '/', realpath(substr(dirname(__FILE__), 0, 0 - strlen('config'))));
+ if (substr($k_path_main, -1) != '/') {
+ $k_path_main .= '/';
+ }
+
+ /**
+ * Installation path (/var/www/tcpdf/).
+ * By default it is automatically calculated but you can also set it as a fixed string to improve performances.
+ */
+ define ('K_PATH_MAIN', $k_path_main);
+ }
+
+ if (!defined('K_PATH_URL')) {
+ // Automatic calculation for the following K_PATH_URL constant
+ $k_path_url = K_PATH_MAIN; // default value for console mode
+ if (isset($_SERVER['HTTP_HOST']) AND (!empty($_SERVER['HTTP_HOST']))) {
+ if (isset($_SERVER['HTTPS']) AND (!empty($_SERVER['HTTPS'])) AND strtolower($_SERVER['HTTPS']) != 'off') {
+ $k_path_url = 'https://';
+ } else {
+ $k_path_url = 'http://';
+ }
+ $k_path_url .= $_SERVER['HTTP_HOST'];
+ $k_path_url .= str_replace('\\', '/', substr(K_PATH_MAIN, (strlen($_SERVER['DOCUMENT_ROOT']) - 1)));
+ }
+
+ /**
+ * URL path to tcpdf installation folder (http://localhost/tcpdf/).
+ * By default it is automatically calculated but you can also set it as a fixed string to improve performances.
+ */
+ define ('K_PATH_URL', $k_path_url);
+ }
+
+ /**
+ * path for PDF fonts
+ * use K_PATH_MAIN.'fonts/old/' for old non-UTF8 fonts
+ */
+ define ('K_PATH_FONTS', K_PATH_MAIN . 'fonts/');
+
+ /**
+ * cache directory for temporary files (full path)
+ */
+ if (!defined('K_PATH_CACHE')) {
+ define ('K_PATH_CACHE', K_PATH_MAIN . 'cache/');
+ }
+
+ /**
+ * cache directory for temporary files (url path)
+ */
+ define ('K_PATH_URL_CACHE', K_PATH_URL . 'cache/');
+
+ /**
+ *images directory
+ */
+ if (!defined('K_PATH_IMAGES')) {
+ define ('K_PATH_IMAGES', K_PATH_MAIN . 'images/');
+ }
+
+ /**
+ * blank image
+ */
+ define ('K_BLANK_IMAGE', K_PATH_IMAGES . '_blank.png');
+
+ /**
+ * page format
+ */
+ define ('PDF_PAGE_FORMAT', 'A4');
+
+ /**
+ * page orientation (P=portrait, L=landscape)
+ */
+ define ('PDF_PAGE_ORIENTATION', 'P');
+
+ /**
+ * document creator
+ */
+ define ('PDF_CREATOR', 'TCPDF');
+
+ /**
+ * document author
+ */
+ define ('PDF_AUTHOR', 'TCPDF');
+
+ /**
+ * header title
+ */
+ define ('PDF_HEADER_TITLE', 'TCPDF Example');
+
+ /**
+ * header description string
+ */
+ define ('PDF_HEADER_STRING', "by Nicola Asuni - Tecnick.com\nwww.tcpdf.org");
+
+ /**
+ * image logo
+ */
+ define ('PDF_HEADER_LOGO', 'tcpdf_logo.jpg');
+
+ /**
+ * header logo image width [mm]
+ */
+ define ('PDF_HEADER_LOGO_WIDTH', 30);
+
+ /**
+ * document unit of measure [pt=point, mm=millimeter, cm=centimeter, in=inch]
+ */
+ define ('PDF_UNIT', 'mm');
+
+ /**
+ * header margin
+ */
+ define ('PDF_MARGIN_HEADER', 5);
+
+ /**
+ * footer margin
+ */
+ define ('PDF_MARGIN_FOOTER', 10);
+
+ /**
+ * top margin
+ */
+ define ('PDF_MARGIN_TOP', 27);
+
+ /**
+ * bottom margin
+ */
+ define ('PDF_MARGIN_BOTTOM', 25);
+
+ /**
+ * left margin
+ */
+ define ('PDF_MARGIN_LEFT', 15);
+
+ /**
+ * right margin
+ */
+ define ('PDF_MARGIN_RIGHT', 15);
+
+ /**
+ * default main font name
+ */
+ define ('PDF_FONT_NAME_MAIN', 'helvetica');
+
+ /**
+ * default main font size
+ */
+ define ('PDF_FONT_SIZE_MAIN', 10);
+
+ /**
+ * default data font name
+ */
+ define ('PDF_FONT_NAME_DATA', 'helvetica');
+
+ /**
+ * default data font size
+ */
+ define ('PDF_FONT_SIZE_DATA', 8);
+
+ /**
+ * default monospaced font name
+ */
+ define ('PDF_FONT_MONOSPACED', 'courier');
+
+ /**
+ * ratio used to adjust the conversion of pixels to user units
+ */
+ define ('PDF_IMAGE_SCALE_RATIO', 1.25);
+
+ /**
+ * magnification factor for titles
+ */
+ define('HEAD_MAGNIFICATION', 1.1);
+
+ /**
+ * height of cell repect font height
+ */
+ define('K_CELL_HEIGHT_RATIO', 1.25);
+
+ /**
+ * title magnification respect main font size
+ */
+ define('K_TITLE_MAGNIFICATION', 1.3);
+
+ /**
+ * reduction factor for small font
+ */
+ define('K_SMALL_RATIO', 2 / 3);
+
+ /**
+ * set to true to enable the special procedure used to avoid the overlappind of symbols on Thai language
+ */
+ define('K_THAI_TOPCHARS', true);
+
+ /**
+ * if true allows to call TCPDF methods using HTML syntax
+ * IMPORTANT: For security reason, disable this feature if you are printing user HTML content.
+ */
+ define('K_TCPDF_CALLS_IN_HTML', true);
}
// define the constant K_TCPDF_EXTERNAL_CONFIG to ignore tcpdf's default settings
diff --git a/plugins/PDFReports/templates/add.tpl b/plugins/PDFReports/templates/add.tpl
index ceccb9bcee..2149863a57 100644
--- a/plugins/PDFReports/templates/add.tpl
+++ b/plugins/PDFReports/templates/add.tpl
@@ -1,141 +1,145 @@
<div class='entityAddContainer' style='display:none'>
-<div class='entityCancel'>
- {'PDFReports_CancelAndReturnToReports'|translate:"<a class='entityCancelLink'>":"</a>"}
-</div>
-<div class='clear'></div>
-<form id='addEditReport'>
-<table class="dataTable entityTable">
- <thead>
- <tr class="first">
- <th colspan="2">{'PDFReports_CreateAndScheduleReport'|translate}</th>
- <tr>
- </thead>
- <tbody>
- <tr>
- <td class="first">{'General_Website'|translate} </td>
- <td style="width:650px">
- {$siteName}
- </td>
- </tr>
- <tr>
- <td class="first">{'General_Description'|translate} </td>
- <td>
- <textarea cols="30" rows="3" id="report_description" class="inp"></textarea>
- <div class="entityInlineHelp">
- {'PDFReports_DescriptionOnFirstPage'|translate}
- </div>
- </td>
- </tr>
- <tr>
- <td class="first">{'PDFReports_EmailSchedule'|translate}</td>
- <td>
- <select id="report_period" class="inp">
- {foreach from=$periods item=period key=periodId}
- <option value="{$periodId}">
- {$period}
- </option>
- {/foreach}
- </select>
-
- <div class="entityInlineHelp">
- {'PDFReports_WeeklyScheduleHelp'|translate}
- <br/>
- {'PDFReports_MonthlyScheduleHelp'|translate}
- <br/>
- {'PDFReports_ReportHour'|translate}
- <input type="text" style="height: 0.9em;padding-left: 8px;width: 17px;" id="report_hour" class="inp" size="1">
- {'PDFReports_OClock'|translate}
- </div>
- </td>
- </tr>
+ <div class='entityCancel'>
+ {'PDFReports_CancelAndReturnToReports'|translate:"<a class='entityCancelLink'>":"</a>"}
+ </div>
+ <div class='clear'></div>
+ <form id='addEditReport'>
+ <table class="dataTable entityTable">
+ <thead>
+ <tr class="first">
+ <th colspan="2">{'PDFReports_CreateAndScheduleReport'|translate}</th>
+ <tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td class="first">{'General_Website'|translate} </td>
+ <td style="width:650px">
+ {$siteName}
+ </td>
+ </tr>
+ <tr>
+ <td class="first">{'General_Description'|translate} </td>
+ <td>
+ <textarea cols="30" rows="3" id="report_description" class="inp"></textarea>
- <tr {if $reportTypes|@count eq 1}style='display:none'{/if}>
- <td class='first'>
- {'PDFReports_ReportType'|translate}
- </td>
- <td>
- <select id='report_type'>
- {foreach from=$reportTypes key=reportType item=reportTypeIcon}
- <option value="{$reportType}">{$reportType|upper}</option>
- {/foreach}
- </select>
- </td>
- </tr>
+ <div class="entityInlineHelp">
+ {'PDFReports_DescriptionOnFirstPage'|translate}
+ </div>
+ </td>
+ </tr>
+ <tr>
+ <td class="first">{'PDFReports_EmailSchedule'|translate}</td>
+ <td>
+ <select id="report_period" class="inp">
+ {foreach from=$periods item=period key=periodId}
+ <option value="{$periodId}">
+ {$period}
+ </option>
+ {/foreach}
+ </select>
- <tr>
- <td class='first'>
- {'PDFReports_ReportFormat'|translate}
- </td>
+ <div class="entityInlineHelp">
+ {'PDFReports_WeeklyScheduleHelp'|translate}
+ <br/>
+ {'PDFReports_MonthlyScheduleHelp'|translate}
+ <br/>
+ {'PDFReports_ReportHour'|translate}
+ <input type="text" style="height: 0.9em;padding-left: 8px;width: 17px;" id="report_hour" class="inp" size="1">
+ {'PDFReports_OClock'|translate}
+ </div>
+ </td>
+ </tr>
- <td>
- {foreach from=$reportFormatsByReportType key=reportType item=reportFormats}
- <select name='report_format' class='{$reportType}'>
- {foreach from=$reportFormats key=reportFormat item=reportFormatIcon}
- <option value="{$reportFormat}">{$reportFormat|upper}</option>
- {/foreach}
- </select>
- {/foreach}
- </td>
- </tr>
+ <tr {if $reportTypes|@count eq 1}style='display:none'{/if}>
+ <td class='first'>
+ {'PDFReports_ReportType'|translate}
+ </td>
+ <td>
+ <select id='report_type'>
+ {foreach from=$reportTypes key=reportType item=reportTypeIcon}
+ <option value="{$reportType}">{$reportType|upper}</option>
+ {/foreach}
+ </select>
+ </td>
+ </tr>
- {postEvent name="template_reportParametersPDFReports"}
+ <tr>
+ <td class='first'>
+ {'PDFReports_ReportFormat'|translate}
+ </td>
- <tr>
- <td class="first">{'PDFReports_ReportsIncluded'|translate}</td>
- <td>
- {foreach from=$reportsByCategoryByReportType key=reportType item=reportsByCategory}
- <div name='reportsList' class='{$reportType}'>
+ <td>
+ {foreach from=$reportFormatsByReportType key=reportType item=reportFormats}
+ <select name='report_format' class='{$reportType}'>
+ {foreach from=$reportFormats key=reportFormat item=reportFormatIcon}
+ <option value="{$reportFormat}">{$reportFormat|upper}</option>
+ {/foreach}
+ </select>
+ {/foreach}
+ </td>
+ </tr>
- {if $allowMultipleReportsByReportType[$reportType]}
- {assign var=reportInputType value='checkbox'}
- {else}
- {assign var=reportInputType value='radio'}
- {/if}
+ {postEvent name="template_reportParametersPDFReports"}
- {assign var=countCategory value=0}
+ <tr>
+ <td class="first">{'PDFReports_ReportsIncluded'|translate}</td>
+ <td>
+ {foreach from=$reportsByCategoryByReportType key=reportType item=reportsByCategory}
+ <div name='reportsList' class='{$reportType}'>
- {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}
- <div id='leftcolumn'>
- {foreach from=$reportsByCategory item=reports key=category name=reports}
- {if $countCategory >= $newColumnAfter && $newColumnAfter != 0}
- {assign var=newColumnAfter value=0}
- </div><div id='rightcolumn'>
- {/if}
- <div class='reportCategory'>{$category}</div><ul class='listReports'>
- {foreach from=$reports item=report}
- <li>
- <input type='{$reportInputType}' id="{$reportType}{$report.uniqueId}" report-unique-id='{$report.uniqueId}' name='{$reportType}Reports'/>
- <label for="{$reportType}{$report.uniqueId}">
- {$report.name|escape:"html"}
- {if $report.uniqueId=='MultiSites_getAll'}
- <div class="entityInlineHelp">{'PDFReports_ReportIncludeNWebsites'|translate:"$countWebsites "}</div>
- {/if}
- </label>
- </li>
- {/foreach}
- {assign var=countCategory value=$countCategory+1}
- </ul>
- <br/>
- {/foreach}
- </div>
- </div>
- {/foreach}
- </td>
- </tr>
-
- </tbody>
-</table>
+ {assign var=countCategory value=0}
- <input type="hidden" id="report_idreport" value="">
- <input type="submit" id="report_submit" name="submit" class="submit"/>
+ {math
+ equation="ceil (reportsByCategoryCount / 2)"
+ reportsByCategoryCount=$reportsByCategory|@count
+ assign=newColumnAfter
+ }
-</form>
-<div class='entityCancel'>
- {'General_OrCancel'|translate:"<a class='entityCancelLink'>":"</a>"}
-</div>
+ <div id='leftcolumn'>
+ {foreach from=$reportsByCategory item=reports key=category name=reports}
+ {if $countCategory >= $newColumnAfter && $newColumnAfter != 0}
+ {assign var=newColumnAfter value=0}
+ </div>
+ <div id='rightcolumn'>
+ {/if}
+ <div class='reportCategory'>{$category}</div>
+ <ul class='listReports'>
+ {foreach from=$reports item=report}
+ <li>
+ <input type='{$reportInputType}' id="{$reportType}{$report.uniqueId}" report-unique-id='{$report.uniqueId}'
+ name='{$reportType}Reports'/>
+ <label for="{$reportType}{$report.uniqueId}">
+ {$report.name|escape:"html"}
+ {if $report.uniqueId=='MultiSites_getAll'}
+ <div class="entityInlineHelp">{'PDFReports_ReportIncludeNWebsites'|translate:"$countWebsites "}</div>
+ {/if}
+ </label>
+ </li>
+ {/foreach}
+ {assign var=countCategory value=$countCategory+1}
+ </ul>
+ <br/>
+ {/foreach}
+ </div>
+ </div>
+ {/foreach}
+ </td>
+ </tr>
+
+ </tbody>
+ </table>
+
+ <input type="hidden" id="report_idreport" value="">
+ <input type="submit" id="report_submit" name="submit" class="submit"/>
+
+ </form>
+ <div class='entityCancel'>
+ {'General_OrCancel'|translate:"<a class='entityCancelLink'>":"</a>"}
+ </div>
</div> \ 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 @@
</div>
<div class="centerLargeDiv">
- <h2>{'PDFReports_ManageEmailReports'|translate}</h2>
-
- <div class="entityContainer">
- {ajaxErrorDiv}
- {ajaxLoadingDiv}
- {include file="PDFReports/templates/list.tpl"}
- {include file="PDFReports/templates/add.tpl"}
- <a id='bottom'></a>
- </div>
+ <h2>{'PDFReports_ManageEmailReports'|translate}</h2>
+
+ <div class="entityContainer">
+ {ajaxErrorDiv}
+ {ajaxLoadingDiv}
+ {include file="PDFReports/templates/list.tpl"}
+ {include file="PDFReports/templates/add.tpl"}
+ <a id='bottom'></a>
+ </div>
</div>
<div class="ui-confirm" id="confirm">
- <h2>{'PDFReports_AreYouSureDeleteReport'|translate}</h2>
- <input role="yes" type="button" value="{'General_Yes'|translate}" />
- <input role="no" type="button" value="{'General_No'|translate}" />
-</div>
+ <h2>{'PDFReports_AreYouSureDeleteReport'|translate}</h2>
+ <input role="yes" type="button" value="{'General_Yes'|translate}"/>
+ <input role="no" type="button" value="{'General_No'|translate}"/>
+</div>
<script type="text/javascript">
-var ReportPlugin = new Object();
-ReportPlugin.defaultPeriod = '{$defaultPeriod}';
-ReportPlugin.defaultHour = '{$defaultHour}';
-ReportPlugin.defaultReportType = '{$defaultReportType}';
-ReportPlugin.defaultReportFormat = '{$defaultReportFormat}';
-ReportPlugin.reportList = {$reportsJSON};
-ReportPlugin.createReportString = "{'PDFReports_CreateReport'|translate}";
-ReportPlugin.updateReportString = "{'PDFReports_UpdateReport'|translate}";
-{literal}
-$(document).ready( function() {
- initManagePdf();
-});
+ var ReportPlugin = new Object();
+ ReportPlugin.defaultPeriod = '{$defaultPeriod}';
+ ReportPlugin.defaultHour = '{$defaultHour}';
+ ReportPlugin.defaultReportType = '{$defaultReportType}';
+ ReportPlugin.defaultReportFormat = '{$defaultReportFormat}';
+ ReportPlugin.reportList = {$reportsJSON};
+ ReportPlugin.createReportString = "{'PDFReports_CreateReport'|translate}";
+ ReportPlugin.updateReportString = "{'PDFReports_UpdateReport'|translate}";
+ {literal}
+ $(document).ready(function () {
+ initManagePdf();
+ });
</script>
-<style type="text/css">
-.reportCategory {
- font-weight:bold;
- margin-bottom:5px;
-}
-</style>
+ <style type="text/css">
+ .reportCategory {
+ font-weight: bold;
+ margin-bottom: 5px;
+ }
+ </style>
{/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 @@
<div id='entityEditContainer'>
- <table class="dataTable entityTable">
- <thead>
- <tr>
- <th class="first">{'General_Description'|translate}</th>
- <th>{'PDFReports_EmailSchedule'|translate}</th>
- <th>{'PDFReports_ReportFormat'|translate}</th>
- <th>{'PDFReports_SendReportTo'|translate}</th>
- <th>{'General_Download'|translate}</th>
- <th>{'General_Edit'|translate}</th>
- <th>{'General_Delete'|translate}</th>
- </tr>
- </thead>
-
- {if $userLogin=='anonymous'}
- <tr><td colspan='7'>
- <br/>
- {'PDFReports_MustBeLoggedIn'|translate}
- <br/>&rsaquo; <a href='index.php?module={$loginModule}'>{'Login_LogIn'|translate}</a></strong>
- <br/><br/>
- </td></tr>
- </table>
- {elseif empty($reports)}
- <tr><td colspan='7'>
- <br/>
- {'PDFReports_ThereIsNoReportToManage'|translate:$siteName}.
- <br/><br/>
- <a onclick='' id='linkAddReport'>&rsaquo; {'PDFReports_CreateAndScheduleReport'|translate}</a>
- <br/><br/>
- </td></tr>
- </table>
- {else}
- {foreach from=$reports item=report}
- <tr>
- <td class="first">{$report.description}</td>
- <td>{$periods[$report.period]}
- <!-- Last sent on {$report.ts_last_sent} -->
- </td>
- <td>
- {if !empty($report.format)}
- {$report.format|upper}
- {/if}
- </td>
- <td>
- {*report recipients*}
- {if $report.recipients|@count eq 0}
- {'PDFReports_NoRecipients'|translate}
- {else}
- {foreach name=recipients from=$report.recipients item=recipient}
- {$recipient}<br/>
- {/foreach}
- {*send now link*}
- <a href='#' idreport='{$report.idreport}' name='linkSendNow' class="link_but" style='margin-top:3px'>
- <img border=0 src='{$reportTypes[$report.type]}'/>
- {'PDFReports_SendReportNow'|translate}
- </a>
- {/if}
- </td>
- <td>
- {*download link*}
- <a href="{url module=API token_auth=$token_auth method='PDFReports.generateReport' date=$rawDate idReport=$report.idreport outputType=$downloadOutputType language=$language}"
- target="_blank" name="linkDownloadReport" id="{$report.idreport}" class="link_but">
- <img src='{$reportFormatsByReportType[$report.type][$report.format]}' border="0" />
- {'General_Download'|translate}
- </a>
- </td>
- <td>
- {*edit link*}
- <a href='#' name="linkEditReport" id="{$report.idreport}" class="link_but">
- <img src='themes/default/images/ico_edit.png' border="0" />
- {'General_Edit'|translate}
- </a>
- </td>
- <td>
- {*delete link *}
- <a href='#' name="linkDeleteReport" id="{$report.idreport}" class="link_but">
- <img src='themes/default/images/ico_delete.png' border="0" />
- {'General_Delete'|translate}
- </a>
- </td>
- </tr>
- {/foreach}
- </table>
- {if $userLogin != 'anonymous'}
- <br/>
- <a onclick='' id='linkAddReport'>&rsaquo; {'PDFReports_CreateAndScheduleReport'|translate}</a>
- <br/><br/>
- {/if}
- {/if}
+ <table class="dataTable entityTable">
+ <thead>
+ <tr>
+ <th class="first">{'General_Description'|translate}</th>
+ <th>{'PDFReports_EmailSchedule'|translate}</th>
+ <th>{'PDFReports_ReportFormat'|translate}</th>
+ <th>{'PDFReports_SendReportTo'|translate}</th>
+ <th>{'General_Download'|translate}</th>
+ <th>{'General_Edit'|translate}</th>
+ <th>{'General_Delete'|translate}</th>
+ </tr>
+ </thead>
+
+ {if $userLogin=='anonymous'}
+ <tr>
+ <td colspan='7'>
+ <br/>
+ {'PDFReports_MustBeLoggedIn'|translate}
+ <br/>&rsaquo; <a href='index.php?module={$loginModule}'>{'Login_LogIn'|translate}</a></strong>
+ <br/><br/>
+ </td>
+ </tr>
+ </table>
+ {elseif empty($reports)}
+ <tr>
+ <td colspan='7'>
+ <br/>
+ {'PDFReports_ThereIsNoReportToManage'|translate:$siteName}.
+ <br/><br/>
+ <a onclick='' id='linkAddReport'>&rsaquo; {'PDFReports_CreateAndScheduleReport'|translate}</a>
+ <br/><br/>
+ </td>
+ </tr>
+ </table>
+ {else}
+ {foreach from=$reports item=report}
+ <tr>
+ <td class="first">{$report.description}</td>
+ <td>{$periods[$report.period]}
+ <!-- Last sent on {$report.ts_last_sent} -->
+ </td>
+ <td>
+ {if !empty($report.format)}
+ {$report.format|upper}
+ {/if}
+ </td>
+ <td>
+ {*report recipients*}
+ {if $report.recipients|@count eq 0}
+ {'PDFReports_NoRecipients'|translate}
+ {else}
+ {foreach name=recipients from=$report.recipients item=recipient}
+ {$recipient}
+ <br/>
+ {/foreach}
+ {*send now link*}
+ <a href='#' idreport='{$report.idreport}' name='linkSendNow' class="link_but" style='margin-top:3px'>
+ <img border=0 src='{$reportTypes[$report.type]}'/>
+ {'PDFReports_SendReportNow'|translate}
+ </a>
+ {/if}
+ </td>
+ <td>
+ {*download link*}
+ <a href="{url module=API token_auth=$token_auth method='PDFReports.generateReport' date=$rawDate idReport=$report.idreport outputType=$downloadOutputType language=$language}"
+ target="_blank" name="linkDownloadReport" id="{$report.idreport}" class="link_but">
+ <img src='{$reportFormatsByReportType[$report.type][$report.format]}' border="0"/>
+ {'General_Download'|translate}
+ </a>
+ </td>
+ <td>
+ {*edit link*}
+ <a href='#' name="linkEditReport" id="{$report.idreport}" class="link_but">
+ <img src='themes/default/images/ico_edit.png' border="0"/>
+ {'General_Edit'|translate}
+ </a>
+ </td>
+ <td>
+ {*delete link *}
+ <a href='#' name="linkDeleteReport" id="{$report.idreport}" class="link_but">
+ <img src='themes/default/images/ico_delete.png' border="0"/>
+ {'General_Delete'|translate}
+ </a>
+ </td>
+ </tr>
+ {/foreach}
+ </table>
+ {if $userLogin != 'anonymous'}
+ <br/>
+ <a onclick='' id='linkAddReport'>&rsaquo; {'PDFReports_CreateAndScheduleReport'|translate}</a>
+ <br/>
+ <br/>
+ {/if}
+ {/if}
</div>
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 @@
<script>
- function updateEvolutionGraphParameterVisibility ()
- {ldelim}
- var evolutionGraphParameterInput = $('.report_evolution_graph');
- var nonApplicableDisplayFormats = ['1','4'];
- $.inArray($('#display_format option:selected').val(), nonApplicableDisplayFormats) != -1 ?
- evolutionGraphParameterInput.hide() : evolutionGraphParameterInput.show();
- {rdelim}
-
- $(function() {ldelim}
-
- resetReportParametersFunctions ['{$reportType}'] =
- function () {ldelim}
-
- var reportParameters = {ldelim}
- 'displayFormat' : '{$defaultDisplayFormat}',
- 'emailMe' : {$defaultEmailMe},
- 'evolutionGraph' : {$defaultEvolutionGraph},
- 'additionalEmails' : null
- {rdelim};
-
- updateReportParametersFunctions['{$reportType}'](reportParameters);
- {rdelim};
-
- updateReportParametersFunctions['{$reportType}'] =
- function (reportParameters) {ldelim}
-
- if(reportParameters == null) return;
-
- $('#display_format option[value='+reportParameters.displayFormat+']').prop('selected', 'selected');
- updateEvolutionGraphParameterVisibility();
-
- if(reportParameters.emailMe === true)
- $('#report_email_me').prop('checked', 'checked');
- else
- $('#report_email_me').removeProp('checked');
-
- if(reportParameters.evolutionGraph === true)
- $('#report_evolution_graph').prop('checked', 'checked');
- else
- $('#report_evolution_graph').removeProp('checked');
-
- if(reportParameters.additionalEmails != null)
- $('#report_additional_emails').text(reportParameters.additionalEmails.join('\n'));
- else
- $('#report_additional_emails').html('');
- {rdelim};
-
- getReportParametersFunctions['{$reportType}'] =
- function () {ldelim}
-
- var parameters = Object();
-
- parameters.displayFormat = $('#display_format option:selected').val();
- parameters.emailMe = $('#report_email_me').prop('checked');
- parameters.evolutionGraph = $('#report_evolution_graph').prop('checked');
-
- additionalEmails = $('#report_additional_emails').val();
- parameters.additionalEmails =
- additionalEmails != '' ? additionalEmails.split('\n') : [];
-
- return parameters;
- {rdelim};
-
- $('#display_format').change(updateEvolutionGraphParameterVisibility);
-
- {rdelim});
+ function updateEvolutionGraphParameterVisibility() {ldelim}
+ var evolutionGraphParameterInput = $('.report_evolution_graph');
+ var nonApplicableDisplayFormats = ['1', '4'];
+ $.inArray($('#display_format option:selected').val(), nonApplicableDisplayFormats) != -1 ?
+ evolutionGraphParameterInput.hide() : evolutionGraphParameterInput.show();
+ {rdelim
+ }
+
+ $(function () {ldelim}
+
+ resetReportParametersFunctions ['{$reportType}'] =
+ function () {ldelim}
+
+ var reportParameters = {ldelim}
+ 'displayFormat': '{$defaultDisplayFormat}',
+ 'emailMe': {$defaultEmailMe},
+ 'evolutionGraph': {$defaultEvolutionGraph},
+ 'additionalEmails': null
+ {rdelim};
+
+ updateReportParametersFunctions['{$reportType}'](reportParameters);
+ {rdelim
+ };
+
+ updateReportParametersFunctions['{$reportType}'] =
+ function (reportParameters) {ldelim}
+
+ if (reportParameters == null) return;
+
+ $('#display_format option[value=' + reportParameters.displayFormat + ']').prop('selected', 'selected');
+ updateEvolutionGraphParameterVisibility();
+
+ if (reportParameters.emailMe === true)
+ $('#report_email_me').prop('checked', 'checked');
+ else
+ $('#report_email_me').removeProp('checked');
+
+ if (reportParameters.evolutionGraph === true)
+ $('#report_evolution_graph').prop('checked', 'checked');
+ else
+ $('#report_evolution_graph').removeProp('checked');
+
+ if (reportParameters.additionalEmails != null)
+ $('#report_additional_emails').text(reportParameters.additionalEmails.join('\n'));
+ else
+ $('#report_additional_emails').html('');
+ {rdelim
+ };
+
+ getReportParametersFunctions['{$reportType}'] =
+ function () {ldelim}
+
+ var parameters = Object();
+
+ parameters.displayFormat = $('#display_format option:selected').val();
+ parameters.emailMe = $('#report_email_me').prop('checked');
+ parameters.evolutionGraph = $('#report_evolution_graph').prop('checked');
+
+ additionalEmails = $('#report_additional_emails').val();
+ parameters.additionalEmails =
+ additionalEmails != '' ? additionalEmails.split('\n') : [];
+
+ return parameters;
+ {rdelim
+ };
+
+ $('#display_format').change(updateEvolutionGraphParameterVisibility);
+
+ {rdelim
+ });
</script>
<tr class='{$reportType}'>
- <td style='width:240px;' class="first">{'PDFReports_SendReportTo'|translate}
- </td>
- <td>
- <input type="checkbox" id="report_email_me"/>
- <label for="report_email_me">{'PDFReports_SentToMe'|translate} (<i>{$currentUserEmail}</i>) </label>
- <br/><br/>
- {'PDFReports_AlsoSendReportToTheseEmails'|translate}<br/>
- <textarea cols="30" rows="3" id="report_additional_emails" class="inp"></textarea>
- </td>
+ <td style='width:240px;' class="first">{'PDFReports_SendReportTo'|translate}
+ </td>
+ <td>
+ <input type="checkbox" id="report_email_me"/>
+ <label for="report_email_me">{'PDFReports_SentToMe'|translate} (<i>{$currentUserEmail}</i>) </label>
+ <br/><br/>
+ {'PDFReports_AlsoSendReportToTheseEmails'|translate}<br/>
+ <textarea cols="30" rows="3" id="report_additional_emails" class="inp"></textarea>
+ </td>
</tr>
<tr class='{$reportType}'>
- <td class="first">
- {*PDFReports_AggregateReportsFormat should be named PDFReports_DisplayFormat*}
- {'PDFReports_AggregateReportsFormat'|translate}
- </td>
- <td>
- <select id="display_format">
- {foreach from=$displayFormats key=formatValue item=formatLabel}
- <option {if $formatValue==1}selected{/if} value="{$formatValue}">{$formatLabel}</option>
- {/foreach}
- </select>
- <div class='report_evolution_graph'>
- <br/>
- <input type="checkbox" id="report_evolution_graph"/>
- <label for="report_evolution_graph"><i>{'PDFReports_EvolutionGraph'|translate:5}</i></label>
- </div>
- </td>
+ <td class="first">
+ {*PDFReports_AggregateReportsFormat should be named PDFReports_DisplayFormat*}
+ {'PDFReports_AggregateReportsFormat'|translate}
+ </td>
+ <td>
+ <select id="display_format">
+ {foreach from=$displayFormats key=formatValue item=formatLabel}
+ <option {if $formatValue==1}selected{/if} value="{$formatValue}">{$formatLabel}</option>
+ {/foreach}
+ </select>
+
+ <div class='report_evolution_graph'>
+ <br/>
+ <input type="checkbox" id="report_evolution_graph"/>
+ <label for="report_evolution_graph"><i>{'PDFReports_EvolutionGraph'|translate:5}</i></label>
+ </div>
+ </td>
</tr>
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"] =
- "<br/>".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"] =
+ "<br/>" . 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 @@
<p>{'PrivacyManager_CurrentDBSize'|translate}: {$dbStats.currentSize}</p>
{if isset($dbStats.sizeAfterPurge)}
-<p>{'PrivacyManager_EstimatedDBSizeAfterPurge'|translate}: <b>{$dbStats.sizeAfterPurge}</b></p>
+ <p>{'PrivacyManager_EstimatedDBSizeAfterPurge'|translate}: <b>{$dbStats.sizeAfterPurge}</b></p>
{/if}
{if isset($dbStats.spaceSaved)}
-<p>{'PrivacyManager_EstimatedSpaceSaved'|translate}: {$dbStats.spaceSaved}</p>
+ <p>{'PrivacyManager_EstimatedSpaceSaved'|translate}: {$dbStats.spaceSaved}</p>
{/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; i<data.length; i++) {
+ for (var i = 0; i < data.length; i++) {
formData[data[i].name] = data[i].value;
}
- if (forceEstimate === true) {
+ if (forceEstimate === true) {
formData['forceEstimate'] = 1;
- }
+ }
currentRequest = new ajaxHelper();
currentRequest.setLoadingElement('#deleteDataEstimateSect .loadingPiwik');
@@ -67,107 +67,103 @@ $(document).ready(function() {
);
currentRequest.setFormat('html');
currentRequest.send(false);
- }
-
- // 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
+ }
+
+ // 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}
+ <h2>{'PrivacyManager_TeaserHeadline'|translate}</h2>
+ <p>{'PrivacyManager_Teaser'|translate:'<a href="#anonymizeIPAnchor">':"</a>":'<a href="#deleteLogsAnchor">':"</a>":'<a href="#optOutAnchor">':"</a>"}
+ See also our official guide <b><a href='http://piwik.org/privacy/' target='_blank'>Web Analytics Privacy</a></b></p>
+ <a name="anonymizeIPAnchor"></a>
+ <h2>{'PrivacyManager_UseAnonymizeIp'|translate}</h2>
+ <form method="post" action="{url action=saveSettings form=formMaskLength token_auth=$token_auth}" id="formMaskLength" name="formMaskLength">
+ <div id='anonymizeIpSettings'>
+ <table class="adminTable" style='width:800px;'>
+ <tr>
+ <td width="250">{'PrivacyManager_UseAnonymizeIp'|translate}<br/>
+ <span class="form-description">{'PrivacyManager_AnonymizeIpDescription'|translate}</span>
+ </td>
+ <td width='500'>
+ <label><input type="radio" name="anonymizeIPEnable" value="1" {if $anonymizeIP.enabled eq '1'}
+ checked {/if}/> {'General_Yes'|translate}</label>
+ <label><input type="radio" name="anonymizeIPEnable" value="0"
+ style="margin-left:20px;" {if $anonymizeIP.enabled eq '0'} checked {/if}/> {'General_No'|translate}
+ </label>
+ <input type="hidden" name="token_auth" value="{$token_auth}"/>
+ <input type="hidden" name="pluginName" value="{$anonymizeIP.name}"/>
+ </td>
+ <td width="200">
+ {'AnonymizeIP_PluginDescription'|translate|inlineHelp}
+ </td>
+ </tr>
+ </table>
+ </div>
+ <div id="anonymizeIPenabled">
+ <table class="adminTable" style='width:800px;'>
+ <tr>
+ <td width="250">{'PrivacyManager_AnonymizeIpMaskLengtDescription'|translate}</td>
+ <td width="500">
+ <label><input type="radio" name="maskLength" value="1" {if $anonymizeIP.maskLength eq '1'}
+ checked {/if}/> {'PrivacyManager_AnonymizeIpMaskLength'|translate:"1":"192.168.100.xxx"}
+ </label><br/>
+ <label><input type="radio" name="maskLength" value="2" {if $anonymizeIP.maskLength eq '2'}
+ checked {/if}/> {'PrivacyManager_AnonymizeIpMaskLength'|translate:"2":"192.168.xxx.xxx"} <span
+ class="form-description">{'General_Recommended'|translate}</span></label><br/>
+ <label><input type="radio" name="maskLength" value="3" {if $anonymizeIP.maskLength eq '3'}
+ checked {/if}/> {'PrivacyManager_AnonymizeIpMaskLength'|translate:"3":"192.xxx.xxx.xxx"}</label>
+ </td>
+ <td width="200">
+ {'PrivacyManager_GeolocationAnonymizeIpNote'|translate|inlineHelp}
+ </td>
+ </tr>
+ </table>
+ </div>
+ <input type="submit" value="{'General_Save'|translate}" id="privacySettingsSubmit" class="submit"/>
+ </form>
+ <div class="ui-confirm" id="confirmDeleteSettings">
+ <h2 id="deleteLogsConfirm">{'PrivacyManager_DeleteLogsConfirm'|translate}</h2>
-<h2>{'PrivacyManager_TeaserHeadline'|translate}</h2>
-<p>{'PrivacyManager_Teaser'|translate:'<a href="#anonymizeIPAnchor">':"</a>":'<a href="#deleteLogsAnchor">':"</a>":'<a href="#optOutAnchor">':"</a>"}
-See also our official guide <b><a href='http://piwik.org/privacy/' target='_blank'>Web Analytics Privacy</a></b></p>
+ <h2 id="deleteReportsConfirm">{'PrivacyManager_DeleteReportsConfirm'|translate}</h2>
-<a name="anonymizeIPAnchor"></a>
-<h2>{'PrivacyManager_UseAnonymizeIp'|translate}</h2>
-<form method="post" action="{url action=saveSettings form=formMaskLength token_auth=$token_auth}" id="formMaskLength" name="formMaskLength">
- <div id='anonymizeIpSettings'>
- <table class="adminTable" style='width:800px;'>
- <tr>
- <td width="250">{'PrivacyManager_UseAnonymizeIp'|translate}<br/>
- <span class="form-description">{'PrivacyManager_AnonymizeIpDescription'|translate}</span>
- </td>
- <td width='500'>
- <label><input type="radio" name="anonymizeIPEnable" value="1" {if $anonymizeIP.enabled eq '1'}
- checked {/if}/> {'General_Yes'|translate}</label>
- <label><input type="radio" name="anonymizeIPEnable" value="0"
- style="margin-left:20px;" {if $anonymizeIP.enabled eq '0'} checked {/if}/> {'General_No'|translate}
- </label>
- <input type="hidden" name="token_auth" value="{$token_auth}"/>
- <input type="hidden" name="pluginName" value="{$anonymizeIP.name}"/>
- </td>
- <td width="200">
- {'AnonymizeIP_PluginDescription'|translate|inlineHelp}
- </td>
- </tr>
- </table>
- </div>
- <div id="anonymizeIPenabled">
- <table class="adminTable" style='width:800px;'>
- <tr>
- <td width="250">{'PrivacyManager_AnonymizeIpMaskLengtDescription'|translate}</td>
- <td width="500">
- <label><input type="radio" name="maskLength" value="1" {if $anonymizeIP.maskLength eq '1'}
- checked {/if}/> {'PrivacyManager_AnonymizeIpMaskLength'|translate:"1":"192.168.100.xxx"}
- </label><br/>
- <label><input type="radio" name="maskLength" value="2" {if $anonymizeIP.maskLength eq '2'}
- checked {/if}/> {'PrivacyManager_AnonymizeIpMaskLength'|translate:"2":"192.168.xxx.xxx"} <span class="form-description">{'General_Recommended'|translate}</span></label><br/>
- <label><input type="radio" name="maskLength" value="3" {if $anonymizeIP.maskLength eq '3'}
- checked {/if}/> {'PrivacyManager_AnonymizeIpMaskLength'|translate:"3":"192.xxx.xxx.xxx"}</label>
- </td>
- <td width="200">
- {'PrivacyManager_GeolocationAnonymizeIpNote'|translate|inlineHelp}
- </td>
- </tr>
- </table>
- </div>
- <input type="submit" value="{'General_Save'|translate}" id="privacySettingsSubmit" class="submit"/>
-</form>
+ <h2 id="deleteBothConfirm">{'PrivacyManager_DeleteBothConfirm'|translate}</h2>
+ <input role="yes" type="button" value="{'General_Yes'|translate}"/>
+ <input role="no" type="button" value="{'General_No'|translate}"/>
+ </div>
+ <div class="ui-confirm" id="saveSettingsBeforePurge">
+ <h2>{'PrivacyManager_SaveSettingsBeforePurge'|translate}</h2>
+ <input role="yes" type="button" value="{'General_Ok'|translate}"/>
+ </div>
+ <div class="ui-confirm" id="confirmPurgeNow">
+ <h2>{'PrivacyManager_PurgeNowConfirm'|translate}</h2>
+ <input role="yes" type="button" value="{'General_Yes'|translate}"/>
+ <input role="no" type="button" value="{'General_No'|translate}"/>
+ </div>
+ <a name="deleteLogsAnchor"></a>
+ <h2>{'PrivacyManager_DeleteDataSettings'|translate}</h2>
+ <p>{'PrivacyManager_DeleteDataDescription'|translate} {'PrivacyManager_DeleteDataDescription2'|translate}</p>
+ <form method="post" action="{url action=saveSettings form=formDeleteSettings token_auth=$token_auth}" id="formDeleteSettings" name="formMaskLength">
+ <table class="adminTable" style='width:800px;'>
+ <tr id='deleteLogSettingEnabled'>
+ <td width="250">{'PrivacyManager_UseDeleteLog'|translate}<br/>
-<div class="ui-confirm" id="confirmDeleteSettings">
- <h2 id="deleteLogsConfirm">{'PrivacyManager_DeleteLogsConfirm'|translate}</h2>
- <h2 id="deleteReportsConfirm">{'PrivacyManager_DeleteReportsConfirm'|translate}</h2>
- <h2 id="deleteBothConfirm">{'PrivacyManager_DeleteBothConfirm'|translate}</h2>
- <input role="yes" type="button" value="{'General_Yes'|translate}" />
- <input role="no" type="button" value="{'General_No'|translate}" />
-</div>
-
-<div class="ui-confirm" id="saveSettingsBeforePurge">
- <h2>{'PrivacyManager_SaveSettingsBeforePurge'|translate}</h2>
- <input role="yes" type="button" value="{'General_Ok'|translate}"/>
-</div>
-
-<div class="ui-confirm" id="confirmPurgeNow">
- <h2>{'PrivacyManager_PurgeNowConfirm'|translate}</h2>
- <input role="yes" type="button" value="{'General_Yes'|translate}" />
- <input role="no" type="button" value="{'General_No'|translate}" />
-</div>
-
-<a name="deleteLogsAnchor"></a>
-<h2>{'PrivacyManager_DeleteDataSettings'|translate}</h2>
-<p>{'PrivacyManager_DeleteDataDescription'|translate} {'PrivacyManager_DeleteDataDescription2'|translate}</p>
-<form method="post" action="{url action=saveSettings form=formDeleteSettings token_auth=$token_auth}" id="formDeleteSettings" name="formMaskLength">
- <table class="adminTable" style='width:800px;'>
- <tr id='deleteLogSettingEnabled'>
- <td width="250">{'PrivacyManager_UseDeleteLog'|translate}<br/>
-
- </td>
- <td width='500'>
- <label><input type="radio" name="deleteEnable" value="1" {if $deleteData.config.delete_logs_enable eq '1'}
- checked {/if}/> {'General_Yes'|translate}</label>
- <label><input type="radio" name="deleteEnable" value="0"
- style="margin-left:20px;" {if $deleteData.config.delete_logs_enable eq '0'}
- checked {/if}/> {'General_No'|translate}
- </label>
+ </td>
+ <td width='500'>
+ <label><input type="radio" name="deleteEnable" value="1" {if $deleteData.config.delete_logs_enable eq '1'}
+ checked {/if}/> {'General_Yes'|translate}</label>
+ <label><input type="radio" name="deleteEnable" value="0"
+ style="margin-left:20px;" {if $deleteData.config.delete_logs_enable eq '0'}
+ checked {/if}/> {'General_No'|translate}
+ </label>
<span class="ajaxSuccess">
{'PrivacyManager_DeleteLogDescription2'|translate}
- <a href="http://piwik.org/faq/general/#faq_125" target="_blank">
- {'General_ClickHere'|translate}
- </a>
+ <a href="http://piwik.org/faq/general/#faq_125" target="_blank">
+ {'General_ClickHere'|translate}
+ </a>
</span>
- </td>
- <td width="200">
- {capture assign=deleteLogInfo}
- {'PrivacyManager_DeleteLogInfo'|translate:$deleteData.deleteTables}
- {if !$canDeleteLogActions}
- <br/><br/>{'PrivacyManager_CannotLockSoDeleteLogActions'|translate:$dbUser}
- {/if}
- {/capture}
- {$deleteLogInfo|inlineHelp}
- </td>
- </tr>
- <tr id="deleteLogSettings">
- <td width="250">&nbsp;</td>
- <td width="500">
- <label>{'PrivacyManager_DeleteLogsOlderThan'|translate}
- <input type="text" id="deleteOlderThan" value="{$deleteData.config.delete_logs_older_than}" style="width:30px;"
- name="deleteOlderThan"/>
- {'CoreHome_PeriodDays'|translate}</label><br/>
- <span class="form-description">{'PrivacyManager_LeastDaysInput'|translate:"1"}</span>
- </td>
- <td width="200">
+ </td>
+ <td width="200">
+ {capture assign=deleteLogInfo}
+ {'PrivacyManager_DeleteLogInfo'|translate:$deleteData.deleteTables}
+ {if !$canDeleteLogActions}
+ <br/>
+ <br/>
+ {'PrivacyManager_CannotLockSoDeleteLogActions'|translate:$dbUser}
+ {/if}
+ {/capture}
+ {$deleteLogInfo|inlineHelp}
+ </td>
+ </tr>
+ <tr id="deleteLogSettings">
+ <td width="250">&nbsp;</td>
+ <td width="500">
+ <label>{'PrivacyManager_DeleteLogsOlderThan'|translate}
+ <input type="text" id="deleteOlderThan" value="{$deleteData.config.delete_logs_older_than}" style="width:30px;"
+ name="deleteOlderThan"/>
+ {'CoreHome_PeriodDays'|translate}</label><br/>
+ <span class="form-description">{'PrivacyManager_LeastDaysInput'|translate:"1"}</span>
+ </td>
+ <td width="200">
- </td>
- </tr>
- <tr id='deleteReportsSettingEnabled'>
- <td width="250">{'PrivacyManager_UseDeleteReports'|translate}
- </td>
- <td width="500">
- <label><input type="radio" name="deleteReportsEnable" value="1" {if $deleteData.config.delete_reports_enable eq '1'}checked="true"{/if}/> {'General_Yes'|translate}</label>
- <label><input type="radio" name="deleteReportsEnable" value="0" {if $deleteData.config.delete_reports_enable eq '0'}checked="true"{/if} style="margin-left:20px;"/> {'General_No'|translate}
- </label>
+ </td>
+ </tr>
+ <tr id='deleteReportsSettingEnabled'>
+ <td width="250">{'PrivacyManager_UseDeleteReports'|translate}
+ </td>
+ <td width="500">
+ <label><input type="radio" name="deleteReportsEnable" value="1"
+ {if $deleteData.config.delete_reports_enable eq '1'}checked="true"{/if}/> {'General_Yes'|translate}</label>
+ <label><input type="radio" name="deleteReportsEnable" value="0" {if $deleteData.config.delete_reports_enable eq '0'}checked="true"{/if}
+ style="margin-left:20px;"/> {'General_No'|translate}
+ </label>
<span class="ajaxSuccess">
{capture assign=deleteOldLogs}{'PrivacyManager_UseDeleteLog'|translate}{/capture}
- {'PrivacyManager_DeleteReportsInfo'|translate:'<em>':'</em>'}
- <span id='deleteOldReportsMoreInfo'><br/><br/>
- {'PrivacyManager_DeleteReportsInfo2'|translate:$deleteOldLogs}<br/><br/>
- {'PrivacyManager_DeleteReportsInfo3'|translate:$deleteOldLogs}</span>
+ {'PrivacyManager_DeleteReportsInfo'|translate:'<em>':'</em>'}
+ <span id='deleteOldReportsMoreInfo'><br/><br/>
+ {'PrivacyManager_DeleteReportsInfo2'|translate:$deleteOldLogs}<br/><br/>
+ {'PrivacyManager_DeleteReportsInfo3'|translate:$deleteOldLogs}</span>
</span>
- </td>
- <td width="200">
- {'PrivacyManager_DeleteReportsDetailedInfo'|translate:'archive_numeric_*':'archive_blob_*'|inlineHelp}
- </td>
- </tr>
- <tr id='deleteReportsSettings'>
- <td width="250">&nbsp;</td>
- <td width="500">
- <label>{'PrivacyManager_DeleteReportsOlderThan'|translate}
- <input type="text" id="deleteReportsOlderThan" value="{$deleteData.config.delete_reports_older_than}" style="width:30px;"
- name="deleteReportsOlderThan"/>
- {'CoreHome_PeriodMonths'|translate}
- </label><br/>
- <span class="form-description">{'PrivacyManager_LeastMonthsInput'|translate:"3"}</span><br/><br/>
- <label><input type="checkbox" name="deleteReportsKeepBasic" value="1" {if $deleteData.config.delete_reports_keep_basic_metrics}checked="true"{/if}>{'PrivacyManager_KeepBasicMetrics'|translate}<span class="form-description">{'General_Recommended'|translate}</span></input>
- </label><br/><br/>
- {'PrivacyManager_KeepDataFor'|translate}<br/>
- <label><input type="checkbox" name="deleteReportsKeepDay" value="1" {if $deleteData.config.delete_reports_keep_day_reports}checked="true"{/if}>{'General_DailyReports'|translate}</input></label><br/>
- <label><input type="checkbox" name="deleteReportsKeepWeek" value="1" {if $deleteData.config.delete_reports_keep_week_reports}checked="true"{/if}>{'General_WeeklyReports'|translate}</input></label><br/>
- <label><input type="checkbox" name="deleteReportsKeepMonth" value="1" {if $deleteData.config.delete_reports_keep_month_reports}checked="true"{/if}>{'General_MonthlyReports'|translate}<span class="form-description">{'General_Recommended'|translate}</span></input></label><br/>
- <label><input type="checkbox" name="deleteReportsKeepYear" value="1" {if $deleteData.config.delete_reports_keep_year_reports}checked="true"{/if}>{'General_YearlyReports'|translate}<span class="form-description">{'General_Recommended'|translate}</span></input></label><br/>
- <label><input type="checkbox" name="deleteReportsKeepRange" value="1" {if $deleteData.config.delete_reports_keep_range_reports}checked="true"{/if}>{'General_RangeReports'|translate}</input></label><br/><br/>
- <label><input type="checkbox" name="deleteReportsKeepSegments" value="1" {if $deleteData.config.delete_reports_keep_segment_reports}checked="true"{/if}>{'PrivacyManager_KeepReportSegments'|translate}</input></label><br/>
- </td>
- <td width="200">
-
- </td>
- </tr>
- <tr id="deleteDataEstimateSect" {if $deleteData.config.delete_reports_enable eq '0' and $deleteData.config.delete_logs_enable eq '0'}style="display:none;"{/if}>
- <td width="250">{'PrivacyManager_ReportsDataSavedEstimate'|translate}<br/></td>
- <td width="500">
- <div id="deleteDataEstimate"></div>
- <span class='loadingPiwik' style='display:none'><img src='./themes/default/images/loading-blue.gif' /> {'General_LoadingData'|translate}</span>
- </td>
- <td width="200">
- {if $deleteData.config.enable_auto_database_size_estimate eq '0'}
- {capture assign=manualEstimate}
- <em><a id="getPurgeEstimateLink" class="ui-inline-help" href="#">{'PrivacyManager_GetPurgeEstimate'|translate}</a></em>
- {/capture}
- {$manualEstimate|inlineHelp}
- {/if}
- </td>
- </tr>
- <tr id="deleteSchedulingSettings">
- <td width="250">{'PrivacyManager_DeleteSchedulingSettings'|translate}<br/></td>
- <td width="500">
- <label>{'PrivacyManager_DeleteDataInterval'|translate}
- <select id="deleteLowestInterval" name="deleteLowestInterval">
- <option {if $deleteData.config.delete_logs_schedule_lowest_interval eq '1'} selected="selected" {/if}
- value="1"> {'CoreHome_PeriodDay'|translate}</option>
- <option {if $deleteData.config.delete_logs_schedule_lowest_interval eq '7'} selected="selected" {/if}
- value="7">{'CoreHome_PeriodWeek'|translate}</option>
- <option {if $deleteData.config.delete_logs_schedule_lowest_interval eq '30'} selected="selected" {/if}
- value="30">{'CoreHome_PeriodMonth'|translate}</option>
- </select></label><br/><br/>
- </td>
- <td width="200">
- {capture assign=purgeStats}
- {if $deleteData.lastRun}<strong>{'PrivacyManager_LastDelete'|translate}:</strong>
- {$deleteData.lastRunPretty}
- <br/><br/>{/if}
- <strong>{'PrivacyManager_NextDelete'|translate}:</strong>
- {$deleteData.nextRunPretty}
- <br/><br/><em><a id="purgeDataNowLink" href="#">{'PrivacyManager_PurgeNow'|translate}</a></em>
- <span class='loadingPiwik' style='display:none'><img src='./themes/default/images/loading-blue.gif' /> {'PrivacyManager_PurgingData'|translate}</span>
- <span id="db-purged-message" style="display: none;"><em>{'PrivacyManager_DBPurged'|translate}</em></span>
- {/capture}
- {$purgeStats|inlineHelp}
- </td>
- </tr>
- </table>
- <input type="button" value="{'General_Save'|translate}" id="deleteLogSettingsSubmit" class="submit"/>
-</form>
+ </td>
+ <td width="200">
+ {'PrivacyManager_DeleteReportsDetailedInfo'|translate:'archive_numeric_*':'archive_blob_*'|inlineHelp}
+ </td>
+ </tr>
+ <tr id='deleteReportsSettings'>
+ <td width="250">&nbsp;</td>
+ <td width="500">
+ <label>{'PrivacyManager_DeleteReportsOlderThan'|translate}
+ <input type="text" id="deleteReportsOlderThan" value="{$deleteData.config.delete_reports_older_than}" style="width:30px;"
+ name="deleteReportsOlderThan"/>
+ {'CoreHome_PeriodMonths'|translate}
+ </label><br/>
+ <span class="form-description">{'PrivacyManager_LeastMonthsInput'|translate:"3"}</span><br/><br/>
+ <label><input type="checkbox" name="deleteReportsKeepBasic" value="1"
+ {if $deleteData.config.delete_reports_keep_basic_metrics}checked="true"{/if}>{'PrivacyManager_KeepBasicMetrics'|translate}
+ <span class="form-description">{'General_Recommended'|translate}</span></input>
+ </label><br/><br/>
+ {'PrivacyManager_KeepDataFor'|translate}<br/>
+ <label><input type="checkbox" name="deleteReportsKeepDay" value="1"
+ {if $deleteData.config.delete_reports_keep_day_reports}checked="true"{/if}>{'General_DailyReports'|translate}</input></label><br/>
+ <label><input type="checkbox" name="deleteReportsKeepWeek" value="1"
+ {if $deleteData.config.delete_reports_keep_week_reports}checked="true"{/if}>{'General_WeeklyReports'|translate}</input></label><br/>
+ <label><input type="checkbox" name="deleteReportsKeepMonth" value="1"
+ {if $deleteData.config.delete_reports_keep_month_reports}checked="true"{/if}>{'General_MonthlyReports'|translate}<span
+ class="form-description">{'General_Recommended'|translate}</span></input></label><br/>
+ <label><input type="checkbox" name="deleteReportsKeepYear" value="1"
+ {if $deleteData.config.delete_reports_keep_year_reports}checked="true"{/if}>{'General_YearlyReports'|translate}<span
+ class="form-description">{'General_Recommended'|translate}</span></input></label><br/>
+ <label><input type="checkbox" name="deleteReportsKeepRange" value="1"
+ {if $deleteData.config.delete_reports_keep_range_reports}checked="true"{/if}>{'General_RangeReports'|translate}</input></label><br/><br/>
+ <label><input type="checkbox" name="deleteReportsKeepSegments" value="1"
+ {if $deleteData.config.delete_reports_keep_segment_reports}checked="true"{/if}>{'PrivacyManager_KeepReportSegments'|translate}</input></label><br/>
+ </td>
+ <td width="200">
-
-
-<a name="DNT"></a>
-<h2>{'PrivacyManager_DoNotTrack_SupportDNTPreference'|translate}</h2>
-
-<table class="adminTable" style='width:800px;'>
- <tr>
- <td width="650">
- <p>{if $dntSupport}
- {assign var=action value=deactivate}
- <b>{'PrivacyManager_DoNotTrack_Enabled'|translate}</b> <br/>{'PrivacyManager_DoNotTrack_EnabledMoreInfo'|translate}
- {else}
- {assign var=action value=activate}
- {'PrivacyManager_DoNotTrack_Disabled'|translate} {'PrivacyManager_DoNotTrack_DisabledMoreInfo'|translate}
- {/if}</p>
+ </td>
+ </tr>
+ <tr id="deleteDataEstimateSect"
+ {if $deleteData.config.delete_reports_enable eq '0' and $deleteData.config.delete_logs_enable eq '0'}style="display:none;"{/if}>
+ <td width="250">{'PrivacyManager_ReportsDataSavedEstimate'|translate}<br/></td>
+ <td width="500">
+ <div id="deleteDataEstimate"></div>
+ <span class='loadingPiwik' style='display:none'><img
+ src='./themes/default/images/loading-blue.gif'/> {'General_LoadingData'|translate}</span>
+ </td>
+ <td width="200">
+ {if $deleteData.config.enable_auto_database_size_estimate eq '0'}
+ {capture assign=manualEstimate}
+ <em><a id="getPurgeEstimateLink" class="ui-inline-help" href="#">{'PrivacyManager_GetPurgeEstimate'|translate}</a></em>
+ {/capture}
+ {$manualEstimate|inlineHelp}
+ {/if}
+ </td>
+ </tr>
+ <tr id="deleteSchedulingSettings">
+ <td width="250">{'PrivacyManager_DeleteSchedulingSettings'|translate}<br/></td>
+ <td width="500">
+ <label>{'PrivacyManager_DeleteDataInterval'|translate}
+ <select id="deleteLowestInterval" name="deleteLowestInterval">
+ <option {if $deleteData.config.delete_logs_schedule_lowest_interval eq '1'} selected="selected" {/if}
+ value="1"> {'CoreHome_PeriodDay'|translate}</option>
+ <option {if $deleteData.config.delete_logs_schedule_lowest_interval eq '7'} selected="selected" {/if}
+ value="7">{'CoreHome_PeriodWeek'|translate}</option>
+ <option {if $deleteData.config.delete_logs_schedule_lowest_interval eq '30'} selected="selected" {/if}
+ value="30">{'CoreHome_PeriodMonth'|translate}</option>
+ </select></label><br/><br/>
+ </td>
+ <td width="200">
+ {capture assign=purgeStats}
+ {if $deleteData.lastRun}<strong>{'PrivacyManager_LastDelete'|translate}:</strong>
+ {$deleteData.lastRunPretty}
+ <br/>
+ <br/>
+ {/if}
+ <strong>{'PrivacyManager_NextDelete'|translate}:</strong>
+ {$deleteData.nextRunPretty}
+ <br/>
+ <br/>
+ <em><a id="purgeDataNowLink" href="#">{'PrivacyManager_PurgeNow'|translate}</a></em>
+ <span class='loadingPiwik' style='display:none'><img
+ src='./themes/default/images/loading-blue.gif'/> {'PrivacyManager_PurgingData'|translate}</span>
+ <span id="db-purged-message" style="display: none;"><em>{'PrivacyManager_DBPurged'|translate}</em></span>
+ {/capture}
+ {$purgeStats|inlineHelp}
+ </td>
+ </tr>
+ </table>
+ <input type="button" value="{'General_Save'|translate}" id="deleteLogSettingsSubmit" class="submit"/>
+ </form>
+ <a name="DNT"></a>
+ <h2>{'PrivacyManager_DoNotTrack_SupportDNTPreference'|translate}</h2>
+ <table class="adminTable" style='width:800px;'>
+ <tr>
+ <td width="650">
+ <p>{if $dntSupport}
+ {assign var=action value=deactivate}
+ <b>{'PrivacyManager_DoNotTrack_Enabled'|translate}</b>
+ <br/>
+ {'PrivacyManager_DoNotTrack_EnabledMoreInfo'|translate}
+ {else}
+ {assign var=action value=activate}
+ {'PrivacyManager_DoNotTrack_Disabled'|translate} {'PrivacyManager_DoNotTrack_DisabledMoreInfo'|translate}
+ {/if}</p>
<span style='margin-left:20px'>
- <a href='{url module=CorePluginsAdmin token_auth=$token_auth action=$action pluginName=DoNotTrack}#DNT'>&rsaquo;
- {if $dntSupport}{'PrivacyManager_DoNotTrack_Disable'|translate} {'General_NotRecommended'|translate}
- {else}{'PrivacyManager_DoNotTrack_Enable'|translate} {'General_Recommended'|translate}{/if}
- <br />
- </a></span>
- </td>
- <td width="200">
- {'PrivacyManager_DoNotTrack_Description'|translate|inlineHelp}
- </td>
- </tr>
-</table>
-
+ <a href='{url module=CorePluginsAdmin token_auth=$token_auth action=$action pluginName=DoNotTrack}#DNT'>&rsaquo;
+ {if $dntSupport}{'PrivacyManager_DoNotTrack_Disable'|translate} {'General_NotRecommended'|translate}
+ {else}{'PrivacyManager_DoNotTrack_Enable'|translate} {'General_Recommended'|translate}{/if}
+ <br/>
+ </a></span>
+ </td>
+ <td width="200">
+ {'PrivacyManager_DoNotTrack_Description'|translate|inlineHelp}
+ </td>
+ </tr>
+ </table>
{/if}
<a name="optOutAnchor"></a>
<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>
<div style='height:100px'></div>
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 @@
<?php
/**
* Piwik - Open source web analytics
- *
+ *
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
- *
+ *
* @category Piwik_Plugins
* @package Piwik_Provider
*/
@@ -16,33 +16,32 @@ require_once PIWIK_INCLUDE_PATH . '/plugins/Provider/functions.php';
/**
* The Provider API lets you access reports for your visitors Internet Providers.
- *
+ *
* @package Piwik_Provider
*/
-class Piwik_Provider_API
+class Piwik_Provider_API
{
- static private $instance = null;
-
- static public function getInstance()
- {
- if (self::$instance == null)
- {
- self::$instance = new self;
- }
- return self::$instance;
- }
+ static private $instance = null;
- 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;
- }
+ 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 @@
<?php
/**
* Piwik - Open source web analytics
- *
+ *
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
- *
+ *
* @category Piwik_Plugins
* @package Piwik_Provider
*/
@@ -13,31 +13,30 @@
*
* @package Piwik_Provider
*/
-class Piwik_Provider_Controller extends Piwik_Controller
+class Piwik_Provider_Controller extends Piwik_Controller
{
- /**
- * 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);
- }
-
+ /**
+ * 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', '<br />'),
- '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 = '<div>
- <h2>'.Piwik_Translate('Provider_WidgetProviders').'</h2>';
- $out .= Piwik_FrontController::getInstance()->fetchDispatch('Provider','getProvider');
- $out .= '</div>';
- }
+ 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', '<br />'),
+ '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 = '<div>
+ <h2>' . Piwik_Translate('Provider_WidgetProviders') . '</h2>';
+ $out .= Piwik_FrontController::getInstance()->fetchDispatch('Provider', 'getProvider');
+ $out .= '</div>';
+ }
}
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 @@
<?php
/**
* Piwik - Open source web analytics
- *
+ *
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
- *
+ *
* @category Piwik_Plugins
* @package Piwik_Provider
*/
@@ -17,19 +17,16 @@
*/
function Piwik_getHostnameName($in)
{
- if(empty($in))
- {
- return Piwik_Translate('General_Unknown');
- }
- if(strtolower($in) === 'ip')
- {
- return "IP";
- }
- if(($positionDot = strpos($in, '.')) !== false)
- {
- return ucfirst(substr($in, 0, $positionDot));
- }
- return $in;
+ if (empty($in)) {
+ return Piwik_Translate('General_Unknown');
+ }
+ if (strtolower($in) === 'ip') {
+ return "IP";
+ }
+ if (($positionDot = strpos($in, '.')) !== false) {
+ return ucfirst(substr($in, 0, $positionDot));
+ }
+ return $in;
}
/**
@@ -40,24 +37,20 @@ function Piwik_getHostnameName($in)
*/
function Piwik_getHostnameUrl($in)
{
- if($in == Piwik_DataTable::LABEL_SUMMARY_ROW)
- {
- return false;
- }
- if(empty($in)
- || strtolower($in) === 'ip')
- {
- // link to "what does 'IP' mean?"
- return "http://piwik.org/faq/general/#faq_52";
- }
-
- // if the name looks like it can be used in a URL, use it in one, otherwise link to startpage
- if (preg_match("/^[-a-zA-Z0-9_.]+$/", $in))
- {
- return "http://www.".$in."/";
- }
- else
- {
- return "https://startpage.com/do/search?q=".urlencode($in);
- }
+ if ($in == Piwik_DataTable::LABEL_SUMMARY_ROW) {
+ return false;
+ }
+ if (empty($in)
+ || strtolower($in) === 'ip'
+ ) {
+ // link to "what does 'IP' mean?"
+ return "http://piwik.org/faq/general/#faq_52";
+ }
+
+ // if the name looks like it can be used in a URL, use it in one, otherwise link to startpage
+ if (preg_match("/^[-a-zA-Z0-9_.]+$/", $in)) {
+ return "http://www." . $in . "/";
+ } else {
+ return "https://startpage.com/do/search?q=" . urlencode($in);
+ }
}
diff --git a/plugins/Proxy/Controller.php b/plugins/Proxy/Controller.php
index 5681f6db0d..12e8435703 100644
--- a/plugins/Proxy/Controller.php
+++ b/plugins/Proxy/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_Proxy
*/
@@ -15,167 +15,156 @@
* @package Piwik_Proxy
*/
class Piwik_Proxy_Controller extends Piwik_Controller
-{
- 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
+{
+ 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.
- <br/><br/>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 '<html><head><meta http-equiv="refresh" content="0;url=' . $url . '" /></head></html>';
-
- 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, '&#59') !== 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;
- }
+ <br/><br/>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 '<html><head><meta http-equiv="refresh" content="0;url=' . $url . '" /></head></html>';
+
+ 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, '&#59') !== 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 @@
<?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_Proxy
*/
@@ -16,21 +16,21 @@
*/
class Piwik_Proxy extends Piwik_Plugin
{
- /**
- * 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,
- );
- }
+ /**
+ * 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 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
- "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd" />
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd" />
<html xmlns="http://www.w3.org/1999/xhtml">
- <head>
- <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
- <title>{'General_ExportAsImage_js'|translate}</title>
- </head>
- <body>
- <img title="Piwik Graph" src="{$imageData}" /><br /><br />
- <p>{'General_SaveImageOnYourComputer_js'|translate}</p>
- </body>
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
+ <title>{'General_ExportAsImage_js'|translate}</title>
+</head>
+<body>
+<img title="Piwik Graph" src="{$imageData}"/><br/><br/>
+
+<p>{'General_SaveImageOnYourComputer_js'|translate}</p>
+</body>
</html>
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 <a href='http://piwik.org/docs/analytics-api/reference/#toc-metric-definitions' target='_blank'>general analytics metrics</a> 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 <a href='http://piwik.org/docs/analytics-api/reference/#toc-metric-definitions' target='_blank'>general analytics metrics</a> 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 <a href='http://demo.piwik.org/index.php?module=Widgetize&action=iframe&moduleToWidgetize=Referers&actionToWidgetize=getKeywordsForPage&idSite=7&period=day&date=2011-02-15&disableLink=1' target='_blank'>"Top keywords used to find this page"</a> 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 '<span style="color:#999"><em>('.$label.')</em></span>';
- }
+ $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( '<a target="_blank" href="http://piwik.org/docs/tracking-campaigns/">',
- '</a> - <a target="_blank" href="http://piwik.org/docs/tracking-campaigns/url-builder/">',
- '</a>'
- ));
- $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').'<br />'
- .Piwik_Translate('General_BrokenDownReportDocumentation').'<br />'
- .Piwik_Translate('Referers_EvolutionDocumentationMoreInfo', '&quot;'.Piwik_Translate('Referers_DetailsByRefererType').'&quot;'));
-
- 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 '<span style="color:#999"><em>(' . $label . ')</em></span>';
+ }
+
+ 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('<a target="_blank" href="http://piwik.org/docs/tracking-campaigns/">',
+ '</a> - <a target="_blank" href="http://piwik.org/docs/tracking-campaigns/url-builder/">',
+ '</a>'
+ ));
+ $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') . '<br />'
+ . Piwik_Translate('General_BrokenDownReportDocumentation') . '<br />'
+ . Piwik_Translate('Referers_EvolutionDocumentationMoreInfo', '&quot;' . Piwik_Translate('Referers_DetailsByRefererType') . '&quot;'));
+
+ 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 "<p>This widget is designed to work in your website directly.
+ $jsonRequest = str_replace('format=php', 'format=json', $api);
+ echo "<p>This widget is designed to work in your website directly.
This widget makes it easy to use Piwik to <i>automatically display the list of Top Keywords</i>, for each of your website Page URLs.</p>
<p>
<b>Example API URL</b> - For example if you would like to get the top 10 keywords, used last week, to land on the page <a target='_blank' href='$topPageUrl'>$topPageUrl</a>,
- in format JSON: you would dynamically fetch the data using <a target='_blank' href='$jsonRequest&url=".urlencode($topPageUrl)."'>this API request URL</a>. Make sure you encode the 'url' parameter in the URL.</p>
+ in format JSON: you would dynamically fetch the data using <a target='_blank' href='$jsonRequest&url=" . urlencode($topPageUrl) . "'>this API request URL</a>. Make sure you encode the 'url' parameter in the URL.</p>
<p><b>PHP Function ready to use!</b> - 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 <code>DisplayTopKeywords();</code> 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 "<div style='width:400px;margin-left:20px;padding:10px;border:1px solid black;'>";
- 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 <a href=\'".$api."\'>Top Keywords from Piwik</a>";
- return;
- }
-
- // Display the list in HTML
- $url = htmlspecialchars($url, ENT_QUOTES);
- $output = "<h2>Top Keywords for <a href=\'$url\'>$url</a></h2><ul>";
- foreach($keywords as $keyword) {
- $output .= "<li>". $keyword[0]. "</li>";
- }
- if(empty($keywords)) { $output .= "Nothing yet..."; }
- $output .= "</ul>";
- echo $output;
- }
- DisplayTopKeywords($topPageUrl, $api);
-
- echo "</div><br/>
+
+ echo "<div style='width:400px;margin-left:20px;padding:10px;border:1px solid black;'>";
+ 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 <a href=\'".$api."\'>Top Keywords from Piwik</a>";
+ return;
+ }
+
+ // Display the list in HTML
+ $url = htmlspecialchars($url, ENT_QUOTES);
+ $output = "<h2>Top Keywords for <a href=\'$url\'>$url</a></h2><ul>";
+ foreach ($keywords as $keyword) {
+ $output .= "<li>" . $keyword[0] . "</li>";
+ }
+ if (empty($keywords)) {
+ $output .= "Nothing yet...";
+ }
+ $output .= "</ul>";
+ echo $output;
+ }
+
+ DisplayTopKeywords($topPageUrl, $api);
+
+ echo "</div><br/>
<p>Here is the PHP function that you can paste in your pages:</P>
<textarea cols=60 rows=8>&lt;?php\n" . htmlspecialchars($code) . "\n DisplayTopKeywords();</textarea>
";
-
- echo "
+
+ echo "
<p><b>Notes</b>: You can for example edit the code to to make the Top search keywords link to your Website search result pages.
<br/>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.
</p>
";
-
- }
-
- /**
- * 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').'<br />'
- .'<b>'.Piwik_Translate('Referers_DirectEntry').':</b> '.Piwik_Translate('Referers_DirectEntryDocumentation').'<br />'
- .'<b>'.Piwik_Translate('Referers_SearchEngines').':</b> '.Piwik_Translate('Referers_SearchEnginesDocumentation',
- array('<br />', '&quot;'.Piwik_Translate('Referers_SubmenuSearchEngines').'&quot;')).'<br />'
- .'<b>'.Piwik_Translate('Referers_Websites').':</b> '.Piwik_Translate('Referers_WebsitesDocumentation',
- array('<br />', '&quot;'.Piwik_Translate('Referers_SubmenuWebsites').'&quot;')).'<br />'
- .'<b>'.Piwik_Translate('Referers_Campaigns').':</b> '.Piwik_Translate('Referers_CampaignsDocumentation',
- array('<br />', '&quot;'.Piwik_Translate('Referers_SubmenuCampaigns').'&quot;')),
- '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', '<br />'),
- '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', '<br />'),
- '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', '<br />'),
- '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', '<br />'),
- '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', '<br />'),
- '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', '<br />'),
- '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', '<br />'),
- '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('<br />', '<a href="http://piwik.org/docs/tracking-campaigns/" target="_blank">', '</a>')),
- '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('<br />', '<a href="http://piwik.org/docs/tracking-campaigns/" target="_blank">', '</a>')),
- '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', '<br />'),
- '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') . '<br />'
+ . '<b>' . Piwik_Translate('Referers_DirectEntry') . ':</b> ' . Piwik_Translate('Referers_DirectEntryDocumentation') . '<br />'
+ . '<b>' . Piwik_Translate('Referers_SearchEngines') . ':</b> ' . Piwik_Translate('Referers_SearchEnginesDocumentation',
+ array('<br />', '&quot;' . Piwik_Translate('Referers_SubmenuSearchEngines') . '&quot;')) . '<br />'
+ . '<b>' . Piwik_Translate('Referers_Websites') . ':</b> ' . Piwik_Translate('Referers_WebsitesDocumentation',
+ array('<br />', '&quot;' . Piwik_Translate('Referers_SubmenuWebsites') . '&quot;')) . '<br />'
+ . '<b>' . Piwik_Translate('Referers_Campaigns') . ':</b> ' . Piwik_Translate('Referers_CampaignsDocumentation',
+ array('<br />', '&quot;' . Piwik_Translate('Referers_SubmenuCampaigns') . '&quot;')),
+ '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', '<br />'),
+ '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', '<br />'),
+ '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', '<br />'),
+ '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', '<br />'),
+ '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', '<br />'),
+ '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', '<br />'),
+ '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', '<br />'),
+ '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('<br />', '<a href="http://piwik.org/docs/tracking-campaigns/" target="_blank">', '</a>')),
+ '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('<br />', '<a href="http://piwik.org/docs/tracking-campaigns/" target="_blank">', '</a>')),
+ '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', '<br />'),
+ '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 @@
<?php
/**
* Piwik - Open source web analytics
- *
+ *
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
- *
+ *
* @category Piwik_Plugins
* @package Piwik_Referers
*/
@@ -17,67 +17,63 @@
*/
function Piwik_getPathFromUrl($url)
{
- $path = Piwik_Common::getPathAndQueryFromUrl($url);
- if(empty($path))
- {
- return 'index';
- }
- return $path;
+ $path = Piwik_Common::getPathAndQueryFromUrl($url);
+ if (empty($path)) {
+ return 'index';
+ }
+ return $path;
}
/**
* Returns the last parts of the domain of a URL.
- *
+ *
* @param string $url e.g. http://www.facebook.com/?sdlfk=lksdfj
* @return string|false e.g. facebook.com
*/
-function Piwik_Referrers_cleanSocialUrl( $url )
+function Piwik_Referrers_cleanSocialUrl($url)
{
- $segment = '[^.:\/]+';
- preg_match('/(?:https?:\/\/)?(?:'.$segment.'\.)?('.$segment.'(?:\.'.$segment.')+)/', $url, $matches);
- return isset($matches[1]) ? $matches[1] : false;
+ $segment = '[^.:\/]+';
+ preg_match('/(?:https?:\/\/)?(?:' . $segment . '\.)?(' . $segment . '(?:\.' . $segment . ')+)/', $url, $matches);
+ return isset($matches[1]) ? $matches[1] : false;
}
/**
* Get's social network name from URL.
- *
+ *
* @param string $url
* @return string
*/
-function Piwik_Referrers_getSocialNetworkFromDomain( $url )
+function Piwik_Referrers_getSocialNetworkFromDomain($url)
{
- $domain = Piwik_Referrers_cleanSocialUrl($url);
-
- if (isset($GLOBALS['Piwik_socialUrl'][$domain]))
- {
- return $GLOBALS['Piwik_socialUrl'][$domain];
- }
- else
- {
- return Piwik_Translate('General_Unknown');
- }
+ $domain = Piwik_Referrers_cleanSocialUrl($url);
+
+ if (isset($GLOBALS['Piwik_socialUrl'][$domain])) {
+ return $GLOBALS['Piwik_socialUrl'][$domain];
+ } else {
+ return Piwik_Translate('General_Unknown');
+ }
}
/**
* Returns true if a URL belongs to a social network, false if otherwise.
- *
+ *
* @param string $url The URL to check.
* @param string|false $socialName The social network's name to check for, or false to check
* for any.
* @return bool
*/
-function Piwik_Referrers_isSocialUrl( $url, $socialName = false )
+function Piwik_Referrers_isSocialUrl($url, $socialName = false)
{
- $domain = Piwik_Referrers_cleanSocialUrl($url);
-
- if (isset($GLOBALS['Piwik_socialUrl'][$domain])
- && ($socialName === false
- || $GLOBALS['Piwik_socialUrl'][$domain] == $socialName))
- {
- return true;
- }
-
- return false;
+ $domain = Piwik_Referrers_cleanSocialUrl($url);
+
+ if (isset($GLOBALS['Piwik_socialUrl'][$domain])
+ && ($socialName === false
+ || $GLOBALS['Piwik_socialUrl'][$domain] == $socialName)
+ ) {
+ return true;
+ }
+
+ return false;
}
/* Return social network logo path by URL
@@ -88,26 +84,21 @@ function Piwik_Referrers_isSocialUrl( $url, $socialName = false )
*/
function Piwik_getSocialsLogoFromUrl($domain)
{
- 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';
- }
+ 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 @@
<div id='leftcolumn'>
- <h2>{'Referers_Websites'|translate}</h2>
- {$websites}
+ <h2>{'Referers_Websites'|translate}</h2>
+ {$websites}
</div>
<div id='rightcolumn'>
- <h2>{'Referers_Socials'|translate}</h2>
- {$socials}
+ <h2>{'Referers_Socials'|translate}</h2>
+ {$socials}
</div>
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 @@
<h2>{'Referers_Evolution'|translate}</h2>
{$graphEvolutionReferers}
-<br />
+<br/>
<div id='leftcolumn' style="position:relative">
- <h2>{'Referers_Type'|translate}</h2>
- <div id='leftcolumn'>
- <div class="sparkline">{sparkline src=$urlSparklineDirectEntry}
- {'Referers_TypeDirectEntries'|translate:"<strong>$visitorsFromDirectEntry</strong>"}{if !empty($visitorsFromDirectEntryPercent)}, <strong>{$visitorsFromDirectEntryPercent}%</strong> of visits{/if}{if !empty($visitorsFromDirectEntryEvolution)} {$visitorsFromDirectEntryEvolution}{/if}
- </div>
- <div class="sparkline">{sparkline src=$urlSparklineSearchEngines}
- {'Referers_TypeSearchEngines'|translate:"<strong>$visitorsFromSearchEngines</strong>"}{if !empty($visitorsFromSearchEnginesPercent)}, <strong>{$visitorsFromSearchEnginesPercent}%</strong> of visits{/if}{if !empty($visitorsFromSearchEnginesEvolution)} {$visitorsFromSearchEnginesEvolution}{/if}
- </div>
- </div>
- <div id='rightcolumn'>
- <div class="sparkline">{sparkline src=$urlSparklineWebsites}
- {'Referers_TypeWebsites'|translate:"<strong>$visitorsFromWebsites</strong>"}{if !empty($visitorsFromWebsitesPercent)}, <strong>{$visitorsFromWebsitesPercent}%</strong> of visits{/if}{if !empty($visitorsFromWebsitesEvolution)} {$visitorsFromWebsitesEvolution}{/if}
- </div>
- <div class="sparkline">{sparkline src=$urlSparklineCampaigns}
- {'Referers_TypeCampaigns'|translate:"<strong>$visitorsFromCampaigns</strong>"}{if !empty($visitorsFromCampaignsPercent)}, <strong>{$visitorsFromCampaignsPercent}%</strong> of visits{/if}{if !empty($visitorsFromCampaignsEvolution)} {$visitorsFromCampaignsEvolution}{/if}
- </div>
- </div>
-
- <div style="clear:both" />
-
- <div style="float:left">
- <br/>
- <h2>{'General_MoreDetails'|translate}&nbsp;<a href="#" class="section-toggler-link" data-section-id="distinctReferrersByType">({'General_Show_js'|translate})</a></h2>
- </div>
+ <h2>{'Referers_Type'|translate}</h2>
- <div id="distinctReferrersByType" style="display:none;float:left">
- <table cellpadding="15">
- <tr><td width="50%">
- <div class="sparkline">{sparkline src=$urlSparklineDistinctSearchEngines}
- <strong>{$numberDistinctSearchEngines}</strong> {'Referers_DistinctSearchEngines'|translate}{if !empty($numberDistinctSearchEnginesEvolution)} {$numberDistinctSearchEnginesEvolution}{/if}
- </div>
- <div class="sparkline">{sparkline src=$urlSparklineDistinctKeywords}
- <strong>{$numberDistinctKeywords}</strong> {'Referers_DistinctKeywords'|translate}{if !empty($numberDistinctKeywordsEvolution)} {$numberDistinctKeywordsEvolution}{/if}
- </div>
- </td>
- <td width="50%">
- <div class="sparkline">{sparkline src=$urlSparklineDistinctWebsites}
- <strong>{$numberDistinctWebsites}</strong> {'Referers_DistinctWebsites'|translate} {'Referers_UsingNDistinctUrls'|translate:"<strong>$numberDistinctWebsitesUrls</strong>"}{if !empty($numberDistinctWebsitesEvolution)} {$numberDistinctWebsitesEvolution}{/if}
- </div>
- <div class="sparkline">{sparkline src=$urlSparklineDistinctCampaigns}
- <strong>{$numberDistinctCampaigns}</strong> {'Referers_DistinctCampaigns'|translate}{if !empty($numberDistinctCampaignsEvolution)} {$numberDistinctCampaignsEvolution}{/if}
- </div>
- </td></tr>
- </table>
- <br/>
- </div>
-
- <p style="clear:both"/>
- <div style="float:left">{'General_View'|translate}
- <a href="javascript:broadcast.propagateAjax('module=Referers&action=getSearchEnginesAndKeywords')">{'Referers_SubmenuSearchEngines'|translate}</a>,
- <a href="javascript:broadcast.propagateAjax('module=Referers&action=indexWebsites')">{'Referers_SubmenuWebsites'|translate}</a>,
- <a href="javascript:broadcast.propagateAjax('module=Referers&action=indexCampaigns')">{'Referers_SubmenuCampaigns'|translate}</a>.
- </div>
+ <div id='leftcolumn'>
+ <div class="sparkline">{sparkline src=$urlSparklineDirectEntry}
+ {'Referers_TypeDirectEntries'|translate:"<strong>$visitorsFromDirectEntry</strong>"}{if !empty($visitorsFromDirectEntryPercent)},
+ <strong>{$visitorsFromDirectEntryPercent}%</strong>
+ of visits{/if}{if !empty($visitorsFromDirectEntryEvolution)} {$visitorsFromDirectEntryEvolution}{/if}
+ </div>
+ <div class="sparkline">{sparkline src=$urlSparklineSearchEngines}
+ {'Referers_TypeSearchEngines'|translate:"<strong>$visitorsFromSearchEngines</strong>"}{if !empty($visitorsFromSearchEnginesPercent)},
+ <strong>{$visitorsFromSearchEnginesPercent}%</strong>
+ of visits{/if}{if !empty($visitorsFromSearchEnginesEvolution)} {$visitorsFromSearchEnginesEvolution}{/if}
+ </div>
+ </div>
+ <div id='rightcolumn'>
+ <div class="sparkline">{sparkline src=$urlSparklineWebsites}
+ {'Referers_TypeWebsites'|translate:"<strong>$visitorsFromWebsites</strong>"}{if !empty($visitorsFromWebsitesPercent)},
+ <strong>{$visitorsFromWebsitesPercent}%</strong>
+ of visits{/if}{if !empty($visitorsFromWebsitesEvolution)} {$visitorsFromWebsitesEvolution}{/if}
+ </div>
+ <div class="sparkline">{sparkline src=$urlSparklineCampaigns}
+ {'Referers_TypeCampaigns'|translate:"<strong>$visitorsFromCampaigns</strong>"}{if !empty($visitorsFromCampaignsPercent)},
+ <strong>{$visitorsFromCampaignsPercent}%</strong>
+ of visits{/if}{if !empty($visitorsFromCampaignsEvolution)} {$visitorsFromCampaignsEvolution}{/if}
+ </div>
+ </div>
+
+ <div style="clear:both"/>
+
+ <div style="float:left">
+ <br/>
+
+ <h2>{'General_MoreDetails'|translate}&nbsp;<a href="#" class="section-toggler-link"
+ data-section-id="distinctReferrersByType">({'General_Show_js'|translate})</a></h2>
+ </div>
+
+ <div id="distinctReferrersByType" style="display:none;float:left">
+ <table cellpadding="15">
+ <tr>
+ <td width="50%">
+ <div class="sparkline">{sparkline src=$urlSparklineDistinctSearchEngines}
+ <strong>{$numberDistinctSearchEngines}</strong> {'Referers_DistinctSearchEngines'|translate}{if !empty($numberDistinctSearchEnginesEvolution)} {$numberDistinctSearchEnginesEvolution}{/if}
+ </div>
+ <div class="sparkline">{sparkline src=$urlSparklineDistinctKeywords}
+ <strong>{$numberDistinctKeywords}</strong> {'Referers_DistinctKeywords'|translate}{if !empty($numberDistinctKeywordsEvolution)} {$numberDistinctKeywordsEvolution}{/if}
+ </div>
+ </td>
+ <td width="50%">
+ <div class="sparkline">{sparkline src=$urlSparklineDistinctWebsites}
+ <strong>{$numberDistinctWebsites}</strong> {'Referers_DistinctWebsites'|translate} {'Referers_UsingNDistinctUrls'|translate:"<strong>$numberDistinctWebsitesUrls</strong>"}{if !empty($numberDistinctWebsitesEvolution)} {$numberDistinctWebsitesEvolution}{/if}
+ </div>
+ <div class="sparkline">{sparkline src=$urlSparklineDistinctCampaigns}
+ <strong>{$numberDistinctCampaigns}</strong> {'Referers_DistinctCampaigns'|translate}{if !empty($numberDistinctCampaignsEvolution)} {$numberDistinctCampaignsEvolution}{/if}
+ </div>
+ </td>
+ </tr>
+ </table>
+ <br/>
+ </div>
+
+ <p style="clear:both"/>
+
+ <div style="float:left">{'General_View'|translate}
+ <a href="javascript:broadcast.propagateAjax('module=Referers&action=getSearchEnginesAndKeywords')">{'Referers_SubmenuSearchEngines'|translate}</a>,
+ <a href="javascript:broadcast.propagateAjax('module=Referers&action=indexWebsites')">{'Referers_SubmenuWebsites'|translate}</a>,
+ <a href="javascript:broadcast.propagateAjax('module=Referers&action=indexCampaigns')">{'Referers_SubmenuCampaigns'|translate}</a>.
+ </div>
</div>
<div id='rightcolumn'>
- <h2>{'Referers_DetailsByRefererType'|translate}</h2>
- {$dataTableRefererType}
+ <h2>{'Referers_DetailsByRefererType'|translate}</h2>
+ {$dataTableRefererType}
</div>
<div style="clear:both;"></div>
{if $totalVisits > 0}
-<h2>{'Referers_ReferrersOverview'|translate}</h2>
-{$referrersReportsByDimension}
+ <h2>{'Referers_ReferrersOverview'|translate}</h2>
+ {$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 @@
<div id='leftcolumn'>
- <h2>{'Referers_Keywords'|translate}</h2>
- {$keywords}
+ <h2>{'Referers_Keywords'|translate}</h2>
+ {$keywords}
</div>
<div id='rightcolumn'>
- <h2>{'Referers_SearchEngines'|translate}</h2>
- {$searchEngines}
+ <h2>{'Referers_SearchEngines'|translate}</h2>
+ {$searchEngines}
</div>
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 @@
<?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_SEO
*/
-
+
/**
* @package Piwik_SEO
*/
class Piwik_SEO_Controller extends Piwik_Controller
-{
- function getRank()
- {
- $idSite = Piwik_Common::getRequestVar('idSite');
- $site = new Piwik_Site($idSite);
+{
+ function getRank()
+ {
+ $idSite = Piwik_Common::getRequestVar('idSite');
+ $site = new Piwik_Site($idSite);
+
+ $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();
+ }
+
+ $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('&nbsp;', ' ', 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('&nbsp;', ' ', 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('#<a href=\"([^>]*)' . 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 @@
<?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_SEO
*/
@@ -14,24 +14,24 @@
*/
class Piwik_SEO extends Piwik_Plugin
{
- 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');
- }
+ 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 @@
<div id='SeoRanks'>
- <script type="text/javascript" src="plugins/SEO/templates/rank.js"></script>
-
- <form method="post" style="padding: 8px;" >
- <div align="left" class="mediumtext">
- {'Installation_SetupWebSiteURL'|translate|ucfirst}
- <input type="text" id="seoUrl" size="15" value="{$urlToRank|escape:'html'}" class="textbox" />
+ <script type="text/javascript" src="plugins/SEO/templates/rank.js"></script>
+
+ <form method="post" style="padding: 8px;">
+ <div align="left" class="mediumtext">
+ {'Installation_SetupWebSiteURL'|translate|ucfirst}
+ <input type="text" id="seoUrl" size="15" value="{$urlToRank|escape:'html'}" class="textbox"/>
<span style="padding-left:2px;">
- <input type="submit" id="rankbutton" value="{'SEO_Rank'|translate}" />
+ <input type="submit" id="rankbutton" value="{'SEO_Rank'|translate}"/>
</span>
- </div>
-
- {ajaxLoadingDiv id=ajaxLoadingSEO}
+ </div>
+
+ {ajaxLoadingDiv id=ajaxLoadingSEO}
+
+ <div id="rankStats" align="left" style='margin-top:10px'>
+ {if empty($ranks)}
+ {'General_Error'|translate}
+ {else}
+ {capture name=cleanUrl}
+ <a href='{$urlToRank|escape:'html'}' target='_blank'>{$urlToRank|escape:'html'}</a>
+ {/capture}
+ {'SEO_SEORankingsFor'|translate:$smarty.capture.cleanUrl}
+ <table cellspacing='2' style='margin:auto;line-height:1.5em;padding-top:10px'>
+ {foreach from=$ranks item=rank}
+ <tr>
+ <td>{if !empty($rank.logo_link)}<a href="{$rank.logo_link}" target="_blank"
+ {if !empty($rank.logo_tooltip)}title="{$rank.logo_tooltip}"{/if}>{/if}<img
+ style='vertical-align:middle;margin-right:6px;' src='{$rank.logo}' border='0'
+ alt="{$rank.label}">{if !empty($rank.logo_link)}</a>{/if} {$rank.label}
+ </td>
+ <td>
+ <div style='margin-left:15px'>
+ {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}
+ </div>
+ </td>
+ </tr>
+ {/foreach}
- <div id="rankStats" align="left" style='margin-top:10px'>
- {if empty($ranks)}
- {'General_Error'|translate}
- {else}
-{capture name=cleanUrl}
-<a href='{$urlToRank|escape:'html'}' target='_blank'>{$urlToRank|escape:'html'}</a>
-{/capture}
- {'SEO_SEORankingsFor'|translate:$smarty.capture.cleanUrl}
- <table cellspacing='2' style='margin:auto;line-height:1.5em;padding-top:10px'>
- {foreach from=$ranks item=rank}
- <tr>
- <td>{if !empty($rank.logo_link)}<a href="{$rank.logo_link}" target="_blank" {if !empty($rank.logo_tooltip)}title="{$rank.logo_tooltip}"{/if}>{/if}<img style='vertical-align:middle;margin-right:6px;' src='{$rank.logo}' border='0' alt="{$rank.label}">{if !empty($rank.logo_link)}</a>{/if} {$rank.label}
- </td><td>
- <div style='margin-left:15px'>
- {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}
- </div>
- </td>
- </tr>
- {/foreach}
-
- </table>
- {/if}
- </div>
- </form>
+ </table>
+ {/if}
+ </div>
+ </form>
</div>
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 @@
<?php
/**
* Piwik - Open source web analytics
- *
+ *
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*
@@ -14,30 +14,30 @@
*/
class Piwik_SecurityInfo_Controller extends Piwik_Controller_Admin
{
- function index()
- {
- Piwik::checkUserIsSuperUser();
+ function index()
+ {
+ Piwik::checkUserIsSuperUser();
- require_once(dirname(__FILE__) . '/PhpSecInfo/PhpSecInfo.php');
+ require_once(dirname(__FILE__) . '/PhpSecInfo/PhpSecInfo.php');
- // instantiate the class
- $psi = new PhpSecInfo();
+ // instantiate the class
+ $psi = new PhpSecInfo();
- // load and run all tests
- $psi->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:
- * <code>
- * $result['result'] = PHPSECINFO_TEST_RESULT_NOTICE;
- * $result['message'] = "a string describing the test results and what they mean";
- * </code>
- *
- * @var array
- */
- var $test_results = array();
-
-
- /**
- * An array of tests that were not run
- *
- * <code>
- * $result['result'] = PHPSECINFO_TEST_RESULT_NOTRUN;
- * $result['message'] = "a string explaining why the test was not run";
- * </code>
- *
- * @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 "<pre>"; echo print_r($test_root, true); echo "</pre>";
-
- while (false !== ($entry = $test_root->read())) {
- if ( is_dir($test_root->path.DIRECTORY_SEPARATOR.$entry) && !preg_match('~^(\.|_vti)(.*)$~', $entry) ) {
- $test_dirs[] = $entry;
- }
- }
- //echo "<pre>"; echo print_r($test_dirs, true); echo "</pre>";
-
- // 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:
+ * <code>
+ * $result['result'] = PHPSECINFO_TEST_RESULT_NOTICE;
+ * $result['message'] = "a string describing the test results and what they mean";
+ * </code>
+ *
+ * @var array
+ */
+ var $test_results = array();
+
+
+ /**
+ * An array of tests that were not run
+ *
+ * <code>
+ * $result['result'] = PHPSECINFO_TEST_RESULT_NOTRUN;
+ * $result['message'] = "a string explaining why the test was not run";
+ * </code>
+ *
+ * @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 "<pre>"; echo print_r($test_root, true); echo "</pre>";
+
+ while (false !== ($entry = $test_root->read())) {
+ if (is_dir($test_root->path . DIRECTORY_SEPARATOR . $entry) && !preg_match('~^(\.|_vti)(.*)$~', $entry)) {
+ $test_dirs[] = $entry;
+ }
+ }
+ //echo "<pre>"; echo print_r($test_dirs, true); echo "</pre>";
+
+ // 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 <b>unique</b>, 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 <strong>serious</strong> 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 <strong>serious</strong> 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 <b>unique</b>, 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 <strong>serious</strong> 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 <strong>serious</strong> 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 <b>unique</b>, 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 <b>unique</b>, 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 <a href="http://php.net/manual/en/ref.curl.php" target="_blank">PHP cURL functions</a> 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 <em>disabled</em>, 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 <a href="http://php.net/manual/en/ref.curl.php" target="_blank">PHP cURL functions</a> 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 <em>disabled</em>, 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 <b>unique</b>, 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 <a href="http://php.net/manual/en/ref.curl.php" target="_blank">PHP cURL functions</a> instead.');
- }
+ /**
+ * This should be a <b>unique</b>, 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 <a href="http://php.net/manual/en/ref.curl.php" target="_blank">PHP cURL functions</a> 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 @@
<?php
/**
* Test class for display_errors
- *
+ *
* @package PhpSecInfo
* @author Ed Finkler <coj@funkatron.com>
*/
@@ -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 <b>unique</b>, 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 <b>unique</b>, 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 @@
<?php
/**
* Test class for expose_php
- *
+ *
* @package PhpSecInfo
* @author Ed Finkler <coj@funkatron.com>
*/
@@ -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 <b>unique</b>, 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 <b>unique</b>, 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 @@
<?php
/**
* Test Class for file_uploads
- *
+ *
* @package PhpSecInfo
* @author Ed Finkler <coj@funkatron.com>
*/
@@ -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 <b>unique</b>, 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 <b>unique</b>, 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 <b>unique</b>, 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 <b>unique</b>, 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 <b>unique</b>, human-readable identifier for this test
- *
- * @var string
- */
- var $test_name = "magic_quotes_gpc";
+ /**
+ * This should be a <b>unique</b>, 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 <i>not</i> 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 <b>unique</b>, human-readable identifier for this test
- *
- * @var string
- */
- var $test_name = "memory_limit";
+ /**
+ * This should be a <b>unique</b>, 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 @@
<?php
/**
* Test Class for open_basedir
- *
+ *
* @package PhpSecInfo
* @author Ed Finkler <coj@funkatron.com>
*/
@@ -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 <b>unique</b>, human-readable identifier for this test
- *
- * @var string
- */
- var $test_name = "open_basedir";
+ /**
+ * This should be a <b>unique</b>, 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 <b>unique</b>, human-readable identifier for this test
- *
- * @var string
- */
- var $test_name = "post_max_size";
+ /**
+ * This should be a <b>unique</b>, 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 <b>unique</b>, human-readable identifier for this test
- *
- * @var string
- */
- var $test_name = "register_globals";
+ /**
+ * This should be a <b>unique</b>, 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 <b>unique</b>, 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 <b>unique</b>, 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 <b>unique</b>, 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 <b>unique</b>, 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 <b>unique</b>, human-readable identifier for this test
- *
- * @var string
- */
- var $test_name = "upload_tmp_dir";
+ /**
+ * This should be a <b>unique</b>, 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 <b>unique</b>, human-readable identifier for this test
- *
- * @var string
- */
- var $test_name = "file_support";
+ /**
+ * This should be a <b>unique</b>, 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 <b>unique</b>, 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 <b>unique</b>, 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 <b>unique</b>, human-readable identifier for this test
- *
- * @var string
- */
- var $test_name = "use_trans_sid";
+ /**
+ * This should be a <b>unique</b>, 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 <b>unique</b>, 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
- *
- * <code>
- * $this->setMessageForResult(PHPSECINFO_TEST_RESULT_NOTRUN, 'en', 'This test cannot be run');
- * </code>
- *
- * @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
- *
- * <code>
- * echo 'post_max_size in bytes = ' . $this->return_bytes(ini_get('post_max_size'));
- * </code>
- *
- * @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] = '<unknown>';
- }
- $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 <b>unique</b>, 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
+ *
+ * <code>
+ * $this->setMessageForResult(PHPSECINFO_TEST_RESULT_NOTRUN, 'en', 'This test cannot be run');
+ * </code>
+ *
+ * @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
+ *
+ * <code>
+ * echo 'post_max_size in bytes = ' . $this->return_bytes(ini_get('post_max_size'));
+ * </code>
+ *
+ * @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] = '<unknown>';
+ }
+ $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 @@
<?php
/**
* Skeleton Test class file for Application group
- *
+ *
* @package PhpSecInfo
* @author Anthon Pang
*/
@@ -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,36 +18,38 @@ require_once(PHPSECINFO_BASE_DIR.'/Test/Test.php');
*/
class PhpSecInfo_Test_Application 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 = '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;
- }
- }
+
+ /**
+ * 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 @@
<?php
/**
* Skeleton Test class file for Core group
- *
+ *
* @package PhpSecInfo
* @author Ed Finkler <coj@funkatron.com>
*/
@@ -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 @@
<?php
/**
* Skeleton Test class file for Session group
- *
+ *
* @package PhpSecInfo
* @author Ed Finkler <coj@funkatron.com>
*/
@@ -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 @@
<?php
/**
* Skeleton Test class file for Suhosin group
- *
+ *
* @package PhpSecInfo
* @author Anthon Pang
*/
@@ -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,34 +18,36 @@ require_once(PHPSECINFO_BASE_DIR.'/Test/Test.php');
*/
class PhpSecInfo_Test_Suhosin 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 = '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;
- }
- }
+ /**
+ * 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 @@
<?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_SecurityInfo
*/
@@ -14,30 +14,30 @@
* @package Piwik_SecurityInfo
*/
class Piwik_SecurityInfo extends Piwik_Plugin
-{
- 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);
- }
+{
+ 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 @@
<h2>{'SecurityInfo_SecurityInformation'|translate}</h2>
<p>{'SecurityInfo_PluginDescription'|translate}</p>
-<p>Learn more: read our guide <a target='_blank' href='http://piwik.org/security/how-to-secure-piwik/'>Hardening Piwik: How to make Piwik and your web server more secure?</a></p>
+<p>Learn more: read our guide <a target='_blank' href='http://piwik.org/security/how-to-secure-piwik/'>Hardening Piwik: How to make Piwik and your web server
+ more secure?</a></p>
<div style="max-width:980px;">
-{foreach from=$results.test_results key=i item=section}
-<h2>{$i}</h2>
-<table class="dataTable entityTable">
- <thead>
- <tr>
- <th>{'SecurityInfo_Test'|translate}</th>
- <th>{'SecurityInfo_Result'|translate}</th>
- </tr>
- </thead>
- <tbody>
- {foreach from=$section key=j item=test}
- <tr>
- <td>{$j}</td>
- <td style="{if $test.result==-1}background-color:green;color:white;{elseif $test.result==-2}background-color:yellow;color:black;{else if $test.result=--4}background-color:red;color:white;{/if}">{$test.message}</td>
- </tr>
- {/foreach}
- </tbody>
-</table>
-{/foreach}
+ {foreach from=$results.test_results key=i item=section}
+ <h2>{$i}</h2>
+ <table class="dataTable entityTable">
+ <thead>
+ <tr>
+ <th>{'SecurityInfo_Test'|translate}</th>
+ <th>{'SecurityInfo_Result'|translate}</th>
+ </tr>
+ </thead>
+ <tbody>
+ {foreach from=$section key=j item=test}
+ <tr>
+ <td>{$j}</td>
+ <td style="{if $test.result==-1}background-color:green;color:white;{elseif $test.result==-2}background-color:yellow;color:black;{else if $test.result=--4}background-color:red;color:white;{/if}">{$test.message}</td>
+ </tr>
+ {/foreach}
+ </tbody>
+ </table>
+ {/foreach}
</div>
{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 @@
<?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_SitesManager
*/
/**
- * The SitesManager API gives you full control on Websites in Piwik (create, update and delete), and many methods to retrieve websites based on various attributes.
- *
+ * The SitesManager API gives you full control on Websites in Piwik (create, update and delete), and many methods to retrieve websites based on various attributes.
+ *
* This API lets you create websites via "addSite", update existing websites via "updateSite" and delete websites via "deleteSite".
* When creating websites, it can be useful to access internal codes used by Piwik for currencies via "getCurrencyList", or timezones via "getTimezonesList".
- *
+ *
* There are also many ways to request a list of websites: from the website ID via "getSiteFromId" or the site URL via "getSitesIdFromSiteUrl".
* Often, the most useful technique is to list all websites that are known to a current user, based on the token_auth, via
* "getSitesWithAdminAccess", "getSitesWithViewAccess" or "getSitesWithAtLeastViewAccess" (which returns both).
- *
+ *
* Some methods will affect all websites globally: "setGlobalExcludedIps" will set the list of IPs to be excluded on all websites,
* "setGlobalExcludedQueryParameters" will set the list of URL parameters to remove from URLs for all websites.
- * The existing values can be fetched via "getExcludedIpsGlobal" and "getExcludedQueryParametersGlobal".
+ * The existing values can be fetched via "getExcludedIpsGlobal" and "getExcludedQueryParametersGlobal".
* See also the documentation about <a href='http://piwik.org/docs/manage-websites/' target='_blank'>Managing Websites</a> 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('<br>','<br />','<br/>'), '', $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('<br>', '<br />', '<br/>'), '', $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(',','<br/>', $site['excluded_ips']);
- $site['excluded_parameters'] = str_replace(',','<br/>', $site['excluded_parameters']);
- $site['excluded_user_agents'] = str_replace(',', '<br/>', $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')."&nbsp;<span class='autocompleteMatched'>$pattern</span>.", '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, '<span class="autocompleteMatched">'.$match.'</span>', $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(',', '<br/>', $site['excluded_ips']);
+ $site['excluded_parameters'] = str_replace(',', '<br/>', $site['excluded_parameters']);
+ $site['excluded_user_agents'] = str_replace(',', '<br/>', $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') . "&nbsp;<span class='autocompleteMatched'>$pattern</span>.", '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, '<span class="autocompleteMatched">' . $match . '</span>', $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 @@
<h3>Image Tracker code</h3>
-The Image Tracker code can be used when Javascript is not allowed.
-<br/><div class='toggleHelp' id='imageTracker' style='display:none'><a name='image'>› Display Image Tracker code </a></div>
+The Image Tracker code can be used when Javascript is not allowed.
+<br/>
+<div class='toggleHelp' id='imageTracker' style='display:none'><a name='image'>› Display Image Tracker code </a></div>
<div class='imageTracker'>
-<p>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.
-<br/>
-<b>Note</b>: the code doesn't use Javascript so <b>Piwik will not be able to track some user information</b>
- such as search keywords, referrers, screen resolutions, browser plugins and page titles.
-</p>
-<code>
-&lt;!-- Piwik Image Tracker --&gt;<br/>
-&lt;img src="{if isset($piwikUrlRequest)}{$piwikUrlRequest}{else}{$piwikUrl}{/if}piwik.php?idsite={$idSite}&amp;amp;rec=1" style="border:0" alt="" /&gt;<br/>
-&lt;!-- End Piwik --&gt;<br/>
-</code>
-<br/>
-The following parameters can also be passed to the image URL:
-<ul>
- <li><i>rec</i> - (required) The parameter &rec=1 is required to force the request to be recorded</li>
- <li><i>idsite</i> - (required) Defines the Website ID being tracked</li>
- <li><i>action_name</i> - Defines the custom Page Title for this page view</li>
- <li><i>urlref</i> - 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 <pre>$_SERVER['HTTP_REFERER']</pre></li>
- <li><i>idgoal</i> - The request will trigger the given Goal</li>
- <li><i>revenue</i> - Used with idgoal, defines the custom revenue for this conversion</li>
- <li><i>and more!</i> - There are many more parameters you can set beyond the main ones above. See the <a href='http://piwik.org/docs/tracking-api/reference/'>Tracking API documentation page</a>.</li>
-</ul>
+ <p>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.
+ <br/>
+ <b>Note</b>: the code doesn't use Javascript so <b>Piwik will not be able to track some user information</b>
+ such as search keywords, referrers, screen resolutions, browser plugins and page titles.
+ </p>
+ <code>
+ &lt;!-- Piwik Image Tracker --&gt;<br/>
+ &lt;img src="{if isset($piwikUrlRequest)}{$piwikUrlRequest}{else}{$piwikUrl}{/if}piwik.php?idsite={$idSite}&amp;amp;rec=1" style="border:0" alt="" /&gt;<br/>
+ &lt;!-- End Piwik --&gt;<br/>
+ </code>
+ <br/>
+ The following parameters can also be passed to the image URL:
+ <ul>
+ <li><i>rec</i> - (required) The parameter &rec=1 is required to force the request to be recorded</li>
+ <li><i>idsite</i> - (required) Defines the Website ID being tracked</li>
+ <li><i>action_name</i> - Defines the custom Page Title for this page view</li>
+ <li><i>urlref</i> - 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
+ <pre>$_SERVER['HTTP_REFERER']</pre>
+ </li>
+ <li><i>idgoal</i> - The request will trigger the given Goal</li>
+ <li><i>revenue</i> - Used with idgoal, defines the custom revenue for this conversion</li>
+ <li><i>and more!</i> - There are many more parameters you can set beyond the main ones above. See the <a
+ href='http://piwik.org/docs/tracking-api/reference/'>Tracking API documentation page</a>.
+ </li>
+ </ul>
</div>
<h3>Piwik Tracking API (Advanced users)</h3>
-It is also possible to call the Piwik Tracking API using your favorite programming language.
-<br/><div class='toggleHelp' id='trackingAPI' style='display:none'><a name='image'>› Display Piwik Tracking API documentation </a></div>
+It is also possible to call the Piwik Tracking API using your favorite programming language.
+<br/>
+<div class='toggleHelp' id='trackingAPI' style='display:none'><a name='image'>› Display Piwik Tracking API documentation </a></div>
<div class='trackingAPI'>
-<p>
-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.).
-</p>
+ <p>
+ 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.).
+ </p>
-<p>We currently provide a <b>PHP client</b> 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 <a target='_blank' href='http://dev.piwik.org/'>create a ticket</a> in our developer area (please attach the client code to the ticket).
-</p><p>Follow these instructions to get started with the Tracking API:
-<ul style='list-style-type:decimal;'>
-<li><a href='{if isset($piwikUrlRequest)}{$piwikUrlRequest}{else}{$piwikUrl}{/if}{url action=downloadPiwikTracker}' target='_blank'>Click here to download the file PiwikTracker.php</a>
-</li><li>Upload the PiwikTracker.php file in the same path as your project files
-</li><li>Copy the following code, then paste it onto every page you want to track.
-<code>
-&lt;?php <br/>
-// -- Piwik Tracking API init -- <br/>
-require_once "/path/to/PiwikTracker.php";<br/>
-PiwikTracker::$URL = '{if isset($piwikUrlRequest)}{$piwikUrlRequest}{else}{$piwikUrl}{/if}';<br/>
- ?&gt;
-</code>
-</li><li>Choose a Tracking method, then paste the code onto every page you want to track.
+ <p>We currently provide a <b>PHP client</b> 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 <a target='_blank'
+ href='http://dev.piwik.org/'>create
+ a ticket</a> in our developer area (please attach the client code to the ticket).
+ </p>
-<ul>
-<li><b>Method 1: Advanced Image Tracker</b>
-<br/>
-<p>The client is used to generate the tracking URL that is wrapped inside a HTML &lt;img src=''&gt; code.
-<br/>Paste this code before the &lt;/body&gt; code in your pages.
-<code>
-&lt;?php <br/>
-// Example 1: Tracks a pageview for Website id = {$idSite}<br/>
-echo '&lt;img src="'. str_replace("&amp;","&amp;amp;", Piwik_getUrlTrackPageView( $idSite = {$idSite}, $customTitle = 'This title will appear in the report Actions > Page titles')) . '" alt="" /&gt;';<br/>
-// Example 2: Triggers a Goal conversion for Website id = {$idSite} and Goal id = 2<br/>
-// $customRevenue is optional and is set to the amount generated by the current transaction (in online shops for example)<br/>
-echo '&lt;img src="'. str_replace("&amp;","&amp;amp;", Piwik_getUrlTrackGoal( $idSite = {$idSite}, $idGoal = 2, $customRevenue = 39)) . '" alt="" /&gt;';<br/>
- ?&gt;
-</code>
-<br/>
-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).
-</p>
-
- </li>
- <li><b>Method 2: HTTP Request</b>
- <br/>
-<p>You can also query the Piwik Tracker API remotely via HTTP.
-This is useful for environment where you can't execute HTML nor Javascript.
-<br/>Paste this code anywhere in your code where you wish to track a user interaction.
-
-<code>
-&lt;?php <br/>
-$piwikTracker = new PiwikTracker( $idSite = {$idSite} );<br/>
-// You can manually set the visitor details (resolution, time, plugins, etc.) <br/>
-// See all other ->set* functions available in the PiwikTracker.php file<br/>
-$piwikTracker->setResolution(1600, 1400);<br/><br/>
-// Sends Tracker request via http<br/>
-$piwikTracker->doTrackPageView('Document title of current page view');<br/><br/>
-// You can also track Goal conversions<br/>
-$piwikTracker->doTrackGoal($idGoal = 1, $revenue = 42);<br/>
- ?&gt;
-</code>
-</p>
-</li></ul>
-</li>
-</ul>
-</p>
-{if !isset($calledExternally) || !$calledExternally}
- <p>
- Read more about the Piwik Tracking API <a href='http://piwik.org/docs/tracking-api/' target='_blank'>in the documentation</a>
- </p>
-{/if}
+ <p>Follow these instructions to get started with the Tracking API:
+ <ul style='list-style-type:decimal;'>
+ <li><a href='{if isset($piwikUrlRequest)}{$piwikUrlRequest}{else}{$piwikUrl}{/if}{url action=downloadPiwikTracker}' target='_blank'>Click here to
+ download the file PiwikTracker.php</a>
+ </li>
+ <li>Upload the PiwikTracker.php file in the same path as your project files
+ </li>
+ <li>Copy the following code, then paste it onto every page you want to track.
+ <code>
+ &lt;?php <br/>
+ // -- Piwik Tracking API init -- <br/>
+ require_once "/path/to/PiwikTracker.php";<br/>
+ PiwikTracker::$URL = '{if isset($piwikUrlRequest)}{$piwikUrlRequest}{else}{$piwikUrl}{/if}';<br/>
+ ?&gt;
+ </code>
+ </li>
+ <li>Choose a Tracking method, then paste the code onto every page you want to track.
+
+ <ul>
+ <li><b>Method 1: Advanced Image Tracker</b>
+ <br/>
+
+ <p>The client is used to generate the tracking URL that is wrapped inside a HTML &lt;img src=''&gt; code.
+ <br/>Paste this code before the &lt;/body&gt; code in your pages.
+ <code>
+ &lt;?php <br/>
+ // Example 1: Tracks a pageview for Website id = {$idSite}<br/>
+ echo '&lt;img src="'. str_replace("&amp;","&amp;amp;", Piwik_getUrlTrackPageView( $idSite = {$idSite}, $customTitle = 'This title
+ will appear in the report Actions > Page titles')) . '" alt="" /&gt;';<br/>
+ // Example 2: Triggers a Goal conversion for Website id = {$idSite} and Goal id = 2<br/>
+ // $customRevenue is optional and is set to the amount generated by the current transaction (in online shops for example)<br/>
+ echo '&lt;img src="'. str_replace("&amp;","&amp;amp;", Piwik_getUrlTrackGoal( $idSite = {$idSite}, $idGoal = 2, $customRevenue =
+ 39)) . '" alt="" /&gt;';<br/>
+ ?&gt;
+ </code>
+ <br/>
+ 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).
+ </p>
+
+ </li>
+ <li><b>Method 2: HTTP Request</b>
+ <br/>
+
+ <p>You can also query the Piwik Tracker API remotely via HTTP.
+ This is useful for environment where you can't execute HTML nor Javascript.
+ <br/>Paste this code anywhere in your code where you wish to track a user interaction.
+
+ <code>
+ &lt;?php <br/>
+ $piwikTracker = new PiwikTracker( $idSite = {$idSite} );<br/>
+ // You can manually set the visitor details (resolution, time, plugins, etc.) <br/>
+ // See all other ->set* functions available in the PiwikTracker.php file<br/>
+ $piwikTracker->setResolution(1600, 1400);<br/><br/>
+ // Sends Tracker request via http<br/>
+ $piwikTracker->doTrackPageView('Document title of current page view');<br/><br/>
+ // You can also track Goal conversions<br/>
+ $piwikTracker->doTrackGoal($idGoal = 1, $revenue = 42);<br/>
+ ?&gt;
+ </code>
+ </p>
+ </li>
+ </ul>
+ </li>
+ </ul>
+ </p>
+ {if !isset($calledExternally) || !$calledExternally}
+ <p>
+ Read more about the Piwik Tracking API <a href='http://piwik.org/docs/tracking-api/' target='_blank'>in the documentation</a>
+ </p>
+ {/if}
</div>
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}
-<style type="text/css">
-.trackingHelp ul {
- padding-left:40px;
- list-style-type:square;
-}
-.trackingHelp ul li {
- margin-bottom:10px;
-}
-.trackingHelp h2 {
- margin-top:20px;
-}
-p {
- text-align:justify;
-}
-</style>
+ <style type="text/css">
+ .trackingHelp ul {
+ padding-left: 40px;
+ list-style-type: square;
+ }
+
+ .trackingHelp ul li {
+ margin-bottom: 10px;
+ }
+
+ .trackingHelp h2 {
+ margin-top: 20px;
+ }
+
+ p {
+ text-align: justify;
+ }
+ </style>
{/literal}
<h2>{'SitesManager_TrackingTags'|translate:$displaySiteName}</h2>
<div class='trackingHelp'>
-{'Installation_JSTracking_Intro'|translate}
-<br/><br/>
-{'CoreAdminHome_JSTrackingIntro3'|translate:'<a href="http://piwik.org/integrate/" target="_blank">':'</a>'}
+ {'Installation_JSTracking_Intro'|translate}
+ <br/><br/>
+ {'CoreAdminHome_JSTrackingIntro3'|translate:'<a href="http://piwik.org/integrate/" target="_blank">':'</a>'}
-<h3>{'SitesManager_JsTrackingTag'|translate}</h3>
-<p>{'CoreAdminHome_JSTracking_CodeNote'|translate:"&lt;/body&gt;"}</p>
+ <h3>{'SitesManager_JsTrackingTag'|translate}</h3>
-<pre class="code-pre"><code>{$jsTag}</code></pre>
+ <p>{'CoreAdminHome_JSTracking_CodeNote'|translate:"&lt;/body&gt;"}</p>
-<br />
-{'CoreAdminHome_JSTrackingIntro5'|translate:'<a target="_blank" href="http://piwik.org/docs/javascript-tracking/">':'</a>'}
-<br/><br/>
-{'Installation_JSTracking_EndNote'|translate:'<em>':'</em>'}
+ <pre class="code-pre"><code>{$jsTag}</code></pre>
+
+ <br/>
+ {'CoreAdminHome_JSTrackingIntro5'|translate:'<a target="_blank" href="http://piwik.org/docs/javascript-tracking/">':'</a>'}
+ <br/><br/>
+ {'Installation_JSTracking_EndNote'|translate:'<em>':'</em>'}
</div>
{literal}
-<script type="text/javascript">
-$(document).ready(function () {
- // when code element is clicked, select the text
- $('code').click(function () {
- // credit where credit is due:
- // http://stackoverflow.com/questions/1173194/select-all-div-text-with-single-mouse-click
- var range;
- if (document.body.createTextRange) // MSIE
- {
- range = document.body.createTextRange();
- range.moveToElementText(this);
- range.select();
- }
- else if (window.getSelection) // others
- {
- range = document.createRange();
- range.selectNodeContents(this);
-
- var selection = window.getSelection();
- selection.removeAllRanges();
- selection.addRange(range);
- }
- });
-
- $('code').click();
-});
-</script>
+ <script type="text/javascript">
+ $(document).ready(function () {
+ // when code element is clicked, select the text
+ $('code').click(function () {
+ // credit where credit is due:
+ // http://stackoverflow.com/questions/1173194/select-all-div-text-with-single-mouse-click
+ var range;
+ if (document.body.createTextRange) // MSIE
+ {
+ range = document.body.createTextRange();
+ range.moveToElementText(this);
+ range.select();
+ }
+ else if (window.getSelection) // others
+ {
+ range = document.createRange();
+ range.selectNodeContents(this);
+
+ var selection = window.getSelection();
+ selection.removeAllRanges();
+ selection.addRange(range);
+ }
+ });
+
+ $('code').click();
+ });
+ </script>
{/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 = '<input type="submit" class="addsite submit" value="' + _pk_translate('General_Save_js') +'" />';
- $(' <tr id="'+newRowId+'">\
+ }
+
+ this.init = function () {
+ $('.addRowSite').click(function () {
+ piwikHelper.hideAjaxError();
+ $('.addRowSite').toggle();
+
+ var numberOfRows = $('table#editSites')[0].rows.length;
+ var newRowId = 'rowNew' + numberOfRows;
+ var submitButtonHtml = '<input type="submit" class="addsite submit" value="' + _pk_translate('General_Save_js') + '" />';
+ $(' <tr id="' + newRowId + '">\
<td>&nbsp;</td>\
- <td><input id="name" value="Name" size="15" /><br/><br/><br/>'+submitButtonHtml+'</td>\
- <td><textarea cols="25" rows="3" id="urls">http://siteUrl.com/\nhttp://siteUrl2.com/</textarea><br />'+aliasUrlsHelp+keepURLFragmentSelectHTML+'</td>\
- <td><textarea cols="20" rows="4" id="excludedIps"></textarea><br />'+excludedIpHelp+'</td>\
- <td><textarea cols="20" rows="4" id="excludedQueryParameters"></textarea><br />'+excludedQueryParametersHelp+'</td>\
- <td><textarea cols="20" rows="4" id="excludedUserAgents"></textarea><br />'+excludedUserAgentsHelp+'</td>\
- <td>'+getSitesearchSelector(false)+'</td>\
- <td>'+getTimezoneSelector(defaultTimezone)+'<br />' + timezoneHelp + '</td>\
- <td>'+getCurrencySelector(defaultCurrency)+'<br />' + currencyHelp + '</td>\
- <td>'+getEcommerceSelector(0) + '<br />' + ecommerceHelp+ '</td>\
- <td>'+submitButtonHtml+'</td>\
- <td><span class="cancel link_but">'+sprintf(_pk_translate('General_OrCancel_js'),"","")+'</span></td>\
+ <td><input id="name" value="Name" size="15" /><br/><br/><br/>' + submitButtonHtml + '</td>\
+ <td><textarea cols="25" rows="3" id="urls">http://siteUrl.com/\nhttp://siteUrl2.com/</textarea><br />' + aliasUrlsHelp + keepURLFragmentSelectHTML + '</td>\
+ <td><textarea cols="20" rows="4" id="excludedIps"></textarea><br />' + excludedIpHelp + '</td>\
+ <td><textarea cols="20" rows="4" id="excludedQueryParameters"></textarea><br />' + excludedQueryParametersHelp + '</td>\
+ <td><textarea cols="20" rows="4" id="excludedUserAgents"></textarea><br />' + excludedUserAgentsHelp + '</td>\
+ <td>' + getSitesearchSelector(false) + '</td>\
+ <td>' + getTimezoneSelector(defaultTimezone) + '<br />' + timezoneHelp + '</td>\
+ <td>' + getCurrencySelector(defaultCurrency) + '<br />' + currencyHelp + '</td>\
+ <td>' + getEcommerceSelector(0) + '<br />' + ecommerceHelp + '</td>\
+ <td>' + submitButtonHtml + '</td>\
+ <td><span class="cancel link_but">' + sprintf(_pk_translate('General_OrCancel_js'), "", "") + '</span></td>\
</tr>')
- .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'), '"'+$("<div/>").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 = '<input id="'+idName+'" value="'+piwikHelper.htmlEntities(contentBefore)+'" size="15" />';
-
- var inputSave = $('<br/><input style="margin-top:50px" type="submit" class="submit" value="'+_pk_translate('General_Save_js')+'" />')
- .click( function(){ submitUpdateSite($(this).parent()); });
- var spanCancel = $('<div><br/>'+sprintf(_pk_translate('General_OrCancel_js'),"","")+'</div>')
- .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 = '<textarea cols="25" rows="3" id="urls">'+contentBefore.replace(/<br *\/? *>/gi,"\n")+'</textarea>';
- contentAfter += '<br />'+aliasUrlsHelp+keepURLFragmentSelectHTML;
- $(n).html(contentAfter).find('select').val(keepURLFragmentsForSite);
- }
- else if(idName == 'excludedIps')
- {
- var contentAfter = '<textarea cols="20" rows="4" id="excludedIps">'+contentBefore.replace(/<br *\/? *>/gi,"\n")+'</textarea>';
- contentAfter += '<br />'+excludedIpHelp;
- $(n).html(contentAfter);
- }
- else if(idName == 'excludedQueryParameters')
- {
- var contentAfter = '<textarea cols="20" rows="4" id="excludedQueryParameters">'+contentBefore.replace(/<br *\/? *>/gi,"\n")+'</textarea>';
- contentAfter += '<br />'+excludedQueryParametersHelp;
- $(n).html(contentAfter);
- }
- else if (idName == 'excludedUserAgents')
- {
- var contentAfter = '<textarea cols="20" rows="4" id="excludedUserAgents">' +
- contentBefore.replace(/<br *\/? *>/gi,"\n")+'</textarea><br />'+excludedUserAgentsHelp;
- $(n).html(contentAfter);
- }
- else if(idName == 'timezone')
- {
- var contentAfter = getTimezoneSelector(contentBefore);
- contentAfter += '<br />' + timezoneHelp;
- $(n).html(contentAfter);
- }
- else if(idName == 'currency')
- {
- var contentAfter = getCurrencySelector(contentBefore);
- contentAfter += '<br />' + currencyHelp;
- $(n).html(contentAfter);
- }
- else if(idName == 'ecommerce')
- {
- ecommerceActive = contentBefore.indexOf("ecommerceActive") > 0 ? 1 : 0;
- contentAfter = getEcommerceSelector(ecommerceActive) + '<br />' + 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'), '"' + $("<div/>").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 = '<input id="' + idName + '" value="' + piwikHelper.htmlEntities(contentBefore) + '" size="15" />';
+
+ var inputSave = $('<br/><input style="margin-top:50px" type="submit" class="submit" value="' + _pk_translate('General_Save_js') + '" />')
+ .click(function () { submitUpdateSite($(this).parent()); });
+ var spanCancel = $('<div><br/>' + sprintf(_pk_translate('General_OrCancel_js'), "", "") + '</div>')
+ .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 = '<textarea cols="25" rows="3" id="urls">' + contentBefore.replace(/<br *\/? *>/gi, "\n") + '</textarea>';
+ contentAfter += '<br />' + aliasUrlsHelp + keepURLFragmentSelectHTML;
+ $(n).html(contentAfter).find('select').val(keepURLFragmentsForSite);
+ }
+ else if (idName == 'excludedIps') {
+ var contentAfter = '<textarea cols="20" rows="4" id="excludedIps">' + contentBefore.replace(/<br *\/? *>/gi, "\n") + '</textarea>';
+ contentAfter += '<br />' + excludedIpHelp;
+ $(n).html(contentAfter);
+ }
+ else if (idName == 'excludedQueryParameters') {
+ var contentAfter = '<textarea cols="20" rows="4" id="excludedQueryParameters">' + contentBefore.replace(/<br *\/? *>/gi, "\n") + '</textarea>';
+ contentAfter += '<br />' + excludedQueryParametersHelp;
+ $(n).html(contentAfter);
+ }
+ else if (idName == 'excludedUserAgents') {
+ var contentAfter = '<textarea cols="20" rows="4" id="excludedUserAgents">' +
+ contentBefore.replace(/<br *\/? *>/gi, "\n") + '</textarea><br />' + excludedUserAgentsHelp;
+ $(n).html(contentAfter);
+ }
+ else if (idName == 'timezone') {
+ var contentAfter = getTimezoneSelector(contentBefore);
+ contentAfter += '<br />' + timezoneHelp;
+ $(n).html(contentAfter);
+ }
+ else if (idName == 'currency') {
+ var contentAfter = getCurrencySelector(contentBefore);
+ contentAfter += '<br />' + currencyHelp;
+ $(n).html(contentAfter);
+ }
+ else if (idName == 'ecommerce') {
+ ecommerceActive = contentBefore.indexOf("ecommerceActive") > 0 ? 1 : 0;
+ contentAfter = getEcommerceSelector(ecommerceActive) + '<br />' + ecommerceHelp;
+ $(n).html(contentAfter);
+ }
+ else if (idName == 'sitesearch') {
+ contentAfter = getSitesearchSelector(contentBefore);
+ $(n).html(contentAfter);
+ onClickSiteSearchUseDefault();
+ }
}
- }
- );
- $(this)
- .toggle()
- .parent()
- .prepend( $('<input type="submit" class="updateSite submit" value="' + _pk_translate('General_Save_js') + '" />')
- .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($('<input type="submit" class="updateSite submit" value="' + _pk_translate('General_Save_js') + '" />')
+ .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 += '</select>';
html += '<span style="font-size: 11px;"><br/>';
- if(searchGlobalHasValues)
- {
+ if (searchGlobalHasValues) {
checkedStr = checked ? ' checked ' : '';
- html += '<label><span id="sitesearchUseDefault"'+ (!enabled ? ' style="display:none" ' : '') +'><input type="checkbox" '+checkedStr+' id="sitesearchUseDefaultCheck" onclick="return onClickSiteSearchUseDefault();"> '+sitesearchUseDefault+' </span>';
+ html += '<label><span id="sitesearchUseDefault"' + (!enabled ? ' style="display:none" ' : '') + '><input type="checkbox" ' + checkedStr + ' id="sitesearchUseDefaultCheck" onclick="return onClickSiteSearchUseDefault();"> ' + sitesearchUseDefault + ' </span>';
html += '</label>';
html += '<div ' + ((checked && enabled) ? '' : 'style="display-none"') + ' class="searchDisplayParams form-description">'
+ searchKeywordLabel + ' (' + strDefault + ') ' + ': '
+ globalKeywordParameters
- + (globalCategoryParameters.length ? ', '+ searchCategoryLabel + ': ' + globalCategoryParameters: '')
+ + (globalCategoryParameters.length ? ', ' + searchCategoryLabel + ': ' + globalCategoryParameters : '')
+ '</div>';
}
- html += '<div id="sitesearchIntro">'+sitesearchIntro+'</div>';
+ html += '<div id="sitesearchIntro">' + sitesearchIntro + '</div>';
html += '<div id="searchSiteParameters">';
- html += '<br/><label><div style="margin-bottom:3px">'+searchKeywordLabel+'</div><input type="text" size="22" id="searchKeywordParameters" value="'+searchKeywordParameters+'" style="margin-bottom: -10px;font-size:9pt;font-family:monospace"></input>'+searchKeywordHelp+'</label>';
+ html += '<br/><label><div style="margin-bottom:3px">' + searchKeywordLabel + '</div><input type="text" size="22" id="searchKeywordParameters" value="' + searchKeywordParameters + '" style="margin-bottom: -10px;font-size:9pt;font-family:monospace"></input>' + searchKeywordHelp + '</label>';
// if custom var plugin is disabled, category tracking not supported
- if(globalCategoryParameters!='globalSearchCategoryParametersIsDisabled') {
- html += '<br/><label><div style="margin-bottom:3px">'+searchCategoryLabel+'</div><input type="text" size="22" id="searchCategoryParameters" value="'+searchCategoryParameters+'" style="margin-bottom: -10px;font-size:9pt;font-family:monospace"></input>'+searchCategoryHelp+'</label>';
+ if (globalCategoryParameters != 'globalSearchCategoryParametersIsDisabled') {
+ html += '<br/><label><div style="margin-bottom:3px">' + searchCategoryLabel + '</div><input type="text" size="22" id="searchCategoryParameters" value="' + searchCategoryParameters + '" style="margin-bottom: -10px;font-size:9pt;font-family:monospace"></input>' + searchCategoryHelp + '</label>';
}
html += '</div></span>';
return html;
}
- function getEcommerceSelector(enabled)
- {
- var html = '<select id="ecommerce">';
- selected = ' selected="selected" ';
- html += '<option ' + (enabled ? '' : selected) + ' value="0">' + ecommerceDisabled + '</option>';
- html += '<option ' + (enabled ? selected : '') + ' value="1">' + ecommerceEnabled + '</option>';
- html += '</select>';
- return html;
- }
-
- function getTimezoneSelector(selectedTimezone)
- {
- var html = '<select id="timezones">';
- for(var continent in timezones) {
- html += '<optgroup label="' + continent + '">';
- for(var timezoneId in timezones[continent]) {
- var selected = '';
- if(timezoneId == selectedTimezone) {
- selected = ' selected="selected" ';
- }
- html += '<option ' + selected + ' value="'+ timezoneId + '">' + timezones[continent][timezoneId] + '</option>';
- }
- html += "</optgroup>\n";
- }
- html += '</select>';
- return html;
- }
-
-
- function getCurrencySelector(selectedCurrency)
- {
- var html = '<select id="currencies">';
- for(var currency in currencies) {
- var selected = '';
- if(currency == selectedCurrency) {
- selected = ' selected="selected" ';
- }
- html += '<option ' + selected + ' value="'+ currency + '">' + currencies[currency] + '</option>';
- }
- html += '</select>';
- 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 = '<select id="ecommerce">';
+ selected = ' selected="selected" ';
+ html += '<option ' + (enabled ? '' : selected) + ' value="0">' + ecommerceDisabled + '</option>';
+ html += '<option ' + (enabled ? selected : '') + ' value="1">' + ecommerceEnabled + '</option>';
+ html += '</select>';
+ return html;
+ }
+
+ function getTimezoneSelector(selectedTimezone) {
+ var html = '<select id="timezones">';
+ for (var continent in timezones) {
+ html += '<optgroup label="' + continent + '">';
+ for (var timezoneId in timezones[continent]) {
+ var selected = '';
+ if (timezoneId == selectedTimezone) {
+ selected = ' selected="selected" ';
+ }
+ html += '<option ' + selected + ' value="' + timezoneId + '">' + timezones[continent][timezoneId] + '</option>';
+ }
+ html += "</optgroup>\n";
+ }
+ html += '</select>';
+ return html;
+ }
+
+
+ function getCurrencySelector(selectedCurrency) {
+ var html = '<select id="currencies">';
+ for (var currency in currencies) {
+ var selected = '';
+ if (currency == selectedCurrency) {
+ selected = ' selected="selected" ';
+ }
+ html += '<option ' + selected + ' value="' + currency + '">' + currencies[currency] + '</option>';
+ }
+ html += '</select>';
+ 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'}
<script type="text/javascript">
-{capture assign=excludedIpHelpPlain}{'SitesManager_HelpExcludedIps'|translate:"1.2.3.*":"1.2.*.*"}<br /><br /> {'SitesManager_YourCurrentIpAddressIs'|translate:"<i>$currentIpAddress</i>"}{/capture}
-{assign var=excludedIpHelp value=$excludedIpHelpPlain|inlineHelp}
-var excludedIpHelp = '{$excludedIpHelp|escape:javascript}';
-var aliasUrlsHelp = '{'SitesManager_AliasUrlHelp'|translate|inlineHelp|escape:javascript}';
-{capture assign=defaultTimezoneHelpPlain}
- {if $timezoneSupported}
- {'SitesManager_ChooseCityInSameTimezoneAsYou'|translate}
- {else}
- {'SitesManager_AdvancedTimezoneSupportNotFound'|translate}
- {/if} <br /><br />{'SitesManager_UTCTimeIs'|translate:$utcTime}
-{/capture}
+ {capture assign=excludedIpHelpPlain}{'SitesManager_HelpExcludedIps'|translate:"1.2.3.*":"1.2.*.*"}<
+ br / > <br/>
+ {'SitesManager_YourCurrentIpAddressIs'|translate:"<i>$currentIpAddress</i>"}{/capture}
+ {assign var=excludedIpHelp value=$excludedIpHelpPlain|inlineHelp}
+ var excludedIpHelp = '{$excludedIpHelp|escape:javascript}';
+ var aliasUrlsHelp = '{'SitesManager_AliasUrlHelp'|translate|inlineHelp|escape:javascript}';
+ {capture assign=defaultTimezoneHelpPlain}
+ {if $timezoneSupported}
+ {'SitesManager_ChooseCityInSameTimezoneAsYou'|translate}
+ {else}
+ {'SitesManager_AdvancedTimezoneSupportNotFound'|translate}
+ {/if} <
+ br / > <br/>{'SitesManager_UTCTimeIs'|translate:$utcTime}
+ {/capture}
-{capture assign=timezoneHelpPlain}
- {$defaultTimezoneHelpPlain}
- <br /><br />{'SitesManager_ChangingYourTimezoneWillOnlyAffectDataForward'|translate}
-{/capture}
+ {capture assign=timezoneHelpPlain}
+ {$defaultTimezoneHelpPlain}
+ <br/> <br/>{'SitesManager_ChangingYourTimezoneWillOnlyAffectDataForward'|translate}
+ {/capture}
-{capture assign=currencyHelpPlain}
- {'SitesManager_CurrencySymbolWillBeUsedForGoals'|translate|inlineHelp}
-{/capture}
+ {capture assign=currencyHelpPlain}
+ {'SitesManager_CurrencySymbolWillBeUsedForGoals'|translate|inlineHelp}
+ {/capture}
-{capture assign=ecommerceHelpPlain}
- {'SitesManager_EcommerceHelp'|translate}
- <br />
- {'SitesManager_PiwikOffersEcommerceAnalytics'|translate:"<a href='http://piwik.org/docs/ecommerce-analytics/' target='_blank'>":"</a>"}
-{/capture}
+ {capture assign=ecommerceHelpPlain}
+ {'SitesManager_EcommerceHelp'|translate}
+ <br/>
+ {'SitesManager_PiwikOffersEcommerceAnalytics'|translate:"<a href='http://piwik.org/docs/ecommerce-analytics/' target='_blank'>":"</a>"}
+ {/capture}
-{capture assign=excludedQueryParametersHelp}
- {'SitesManager_ListOfQueryParametersToExclude'|translate}
- <br /><br />
- {'SitesManager_PiwikWillAutomaticallyExcludeCommonSessionParameters'|translate:"phpsessid, sessionid, ..."}
-{/capture}
-{assign var=excludedQueryParametersHelp value=$excludedQueryParametersHelp|inlineHelp}
+ {capture assign=excludedQueryParametersHelp}
+ {'SitesManager_ListOfQueryParametersToExclude'|translate}
+ <br/> <br/>
+ {'SitesManager_PiwikWillAutomaticallyExcludeCommonSessionParameters'|translate:"phpsessid, sessionid, ..."}
+ {/capture}
+ {assign var=excludedQueryParametersHelp value=$excludedQueryParametersHelp|inlineHelp}
-{capture assign=excludedUserAgentsHelp}
- {'SitesManager_GlobalExcludedUserAgentHelp1'|translate}
- <br/><br/>
- {'SitesManager_GlobalListExcludedUserAgents_Desc'|translate} {'SitesManager_GlobalExcludedUserAgentHelp2'|translate}
-{/capture}
-{assign var=excludedUserAgentsHelp value=$excludedUserAgentsHelp|inlineHelp}
+ {capture assign=excludedUserAgentsHelp}
+ {'SitesManager_GlobalExcludedUserAgentHelp1'|translate}
+ <br/> <br/>
+ {'SitesManager_GlobalListExcludedUserAgents_Desc'|translate} {'SitesManager_GlobalExcludedUserAgentHelp2'|translate}
+ {/capture}
+ {assign var=excludedUserAgentsHelp value=$excludedUserAgentsHelp|inlineHelp}
-{capture assign=keepURLFragmentSelectHTML}
- <h4 style="display:inline-block;">{'SitesManager_KeepURLFragmentsLong'|translate}</h4>
+ {capture assign=keepURLFragmentSelectHTML}
+ < h4
+ style = "display:inline-block;" >{'SitesManager_KeepURLFragmentsLong'|translate} < /h4>
- <select id="keepURLFragmentSelect">
- <option value="0">{if $globalKeepURLFragments}{'General_Yes'|translate}{else}{'General_No'|translate}{/if} ({'General_Default'|translate})</option>
- <option value="1">{'General_Yes'|translate}</option>
- <option value="2">{'General_No'|translate}</option>
- </select>
-{/capture}
-var excludedQueryParametersHelp = '{$excludedQueryParametersHelp|escape:javascript}';
-var excludedUserAgentsHelp = '{$excludedUserAgentsHelp|escape:javascript}';
-var timezoneHelp = '{$timezoneHelpPlain|inlineHelp|escape:javascript}';
-var currencyHelp = '{$currencyHelpPlain|escape:javascript}';
-var ecommerceHelp = '{$ecommerceHelpPlain|inlineHelp|escape:javascript}';
-var ecommerceEnabled = '{'SitesManager_EnableEcommerce'|translate|escape:javascript}';
-var ecommerceDisabled = '{'SitesManager_NotAnEcommerceSite'|translate|escape:javascript}';
-{assign var=defaultTimezoneHelp value=$defaultTimezoneHelpPlain|inlineHelp}
-{assign var=searchKeywordHelp value='SitesManager_SearchKeywordParametersDesc'|translate|inlineHelp}
-{capture assign=searchCategoryHelpText}{'Goals_Optional'|translate} {'SitesManager_SearchCategoryParametersDesc'|translate}{/capture}
-{assign var=searchCategoryHelp value=$searchCategoryHelpText|inlineHelp}
-var sitesearchEnabled = '{'SitesManager_EnableSiteSearch'|translate|escape:javascript}';
-var sitesearchDisabled = '{'SitesManager_DisableSiteSearch'|translate|escape:javascript}';
-var searchKeywordHelp = '{$searchKeywordHelp|escape:javascript}';
-var searchCategoryHelp = '{$searchCategoryHelp|escape:javascript}';
-var sitesearchDesc = '{'SitesManager_TrackingSiteSearch'|translate|escape:javascript}';
-var keepURLFragmentSelectHTML = '{$keepURLFragmentSelectHTML|escape:javascript}';
-
-var sitesManager = new SitesManager ( {$timezones}, {$currencies}, '{$defaultTimezone}', '{$defaultCurrency}');
-{assign var=searchKeywordLabel value='SitesManager_SearchKeywordLabel'|translate}
-{assign var=searchCategoryLabel value='SitesManager_SearchCategoryLabel'|translate}
-var searchKeywordLabel = '{$searchKeywordLabel|escape:javascript}';
-var searchCategoryLabel = '{$searchCategoryLabel|escape:javascript}';
-{assign var=sitesearchIntro value='SitesManager_SiteSearchUse'|translate}
-var sitesearchIntro = '{$sitesearchIntro|inlineHelp|escape:javascript}';
-var sitesearchUseDefault = '{if $isSuperUser}{'SitesManager_SearchUseDefault'|translate:'<a href="#globalSiteSearch">':'</a>'|escape:'javascript'}{else}{'SitesManager_SearchUseDefault'|translate:'':''|escape:'javascript'}{/if}';
-var strDefault = '{'General_Default'|translate:escape:'javascript'}';
-{literal}
-$(document).ready( function() {
- sitesManager.init();
-});
+ < select
+ id = "keepURLFragmentSelect" >
+ < option
+ value = "0" > {if $globalKeepURLFragments}{'General_Yes'|translate}{else}{'General_No'|translate}{/if} ({'General_Default'|translate}) < /option>
+ < option
+ value = "1" >{'General_Yes'|translate} < /option>
+ < option
+ value = "2" >{'General_No'|translate} < /option>
+ < /select>
+ {/capture}
+ var excludedQueryParametersHelp = '{$excludedQueryParametersHelp|escape:javascript}';
+ var excludedUserAgentsHelp = '{$excludedUserAgentsHelp|escape:javascript}';
+ var timezoneHelp = '{$timezoneHelpPlain|inlineHelp|escape:javascript}';
+ var currencyHelp = '{$currencyHelpPlain|escape:javascript}';
+ var ecommerceHelp = '{$ecommerceHelpPlain|inlineHelp|escape:javascript}';
+ var ecommerceEnabled = '{'SitesManager_EnableEcommerce'|translate|escape:javascript}';
+ var ecommerceDisabled = '{'SitesManager_NotAnEcommerceSite'|translate|escape:javascript}';
+ {assign var=defaultTimezoneHelp value=$defaultTimezoneHelpPlain|inlineHelp}
+ {assign var=searchKeywordHelp value='SitesManager_SearchKeywordParametersDesc'|translate|inlineHelp}
+ {capture assign=searchCategoryHelpText}{'Goals_Optional'|translate} {'SitesManager_SearchCategoryParametersDesc'|translate}{/capture}
+ {assign var=searchCategoryHelp value=$searchCategoryHelpText|inlineHelp}
+ var sitesearchEnabled = '{'SitesManager_EnableSiteSearch'|translate|escape:javascript}';
+ var sitesearchDisabled = '{'SitesManager_DisableSiteSearch'|translate|escape:javascript}';
+ var searchKeywordHelp = '{$searchKeywordHelp|escape:javascript}';
+ var searchCategoryHelp = '{$searchCategoryHelp|escape:javascript}';
+ var sitesearchDesc = '{'SitesManager_TrackingSiteSearch'|translate|escape:javascript}';
+ var keepURLFragmentSelectHTML = '{$keepURLFragmentSelectHTML|escape:javascript}';
+
+ var sitesManager = new SitesManager({$timezones}, {$currencies}, '{$defaultTimezone}', '{$defaultCurrency}');
+ {assign var=searchKeywordLabel value='SitesManager_SearchKeywordLabel'|translate}
+ {assign var=searchCategoryLabel value='SitesManager_SearchCategoryLabel'|translate}
+ var searchKeywordLabel = '{$searchKeywordLabel|escape:javascript}';
+ var searchCategoryLabel = '{$searchCategoryLabel|escape:javascript}';
+ {assign var=sitesearchIntro value='SitesManager_SiteSearchUse'|translate}
+ var sitesearchIntro = '{$sitesearchIntro|inlineHelp|escape:javascript}';
+ var sitesearchUseDefault = '{if $isSuperUser}{'SitesManager_SearchUseDefault'|translate:'<a href="#globalSiteSearch">':'</a>'|escape:'javascript'}{else}{'SitesManager_SearchUseDefault'|translate:'':''|escape:'javascript'}{/if}';
+ var strDefault = '{'General_Default'|translate:escape:'javascript'}';
+ {literal}
+ $(document).ready(function () {
+ sitesManager.init();
+ });
</script>
-<style type="text/css">
-.entityTable tr td {
- vertical-align: top;
- padding-top:7px;
-}
-
-.addRowSite:hover, .editableSite:hover, .addsite:hover, .cancel:hover, .deleteSite:hover, .editSite:hover, .updateSite:hover{
- cursor: pointer;
-}
-.addRowSite a {
- text-decoration: none;
-}
-.addRowSite {
- padding:1em;
- font-weight:bold;
-}
-#editSites {
- vertical-align: top;
-}
-option, select {
- font-size:11px;
-}
-textarea {
-font-size:9pt;
-}
-.admin thead th {
-vertical-align:middle;
-}
-.ecommerceInactive,.sitesearchInactive {
- color: #666666;
-}
-#searchSiteParameters {
- display:none;
-}
-#editSites h4 {
- font-size:.8em;
- margin:1em 0 1em 0;
- font-weight:bold;
-}
-</style>
+ <style type="text/css">
+ .entityTable tr td {
+ vertical-align: top;
+ padding-top: 7px;
+ }
+
+ .addRowSite:hover, .editableSite:hover, .addsite:hover, .cancel:hover, .deleteSite:hover, .editSite:hover, .updateSite:hover {
+ cursor: pointer;
+ }
+
+ .addRowSite a {
+ text-decoration: none;
+ }
+
+ .addRowSite {
+ padding: 1em;
+ font-weight: bold;
+ }
+
+ #editSites {
+ vertical-align: top;
+ }
+
+ option, select {
+ font-size: 11px;
+ }
+
+ textarea {
+ font-size: 9pt;
+ }
+
+ .admin thead th {
+ vertical-align: middle;
+ }
+
+ .ecommerceInactive, .sitesearchInactive {
+ color: #666666;
+ }
+
+ #searchSiteParameters {
+ display: none;
+ }
+
+ #editSites h4 {
+ font-size: .8em;
+ margin: 1em 0 1em 0;
+ font-weight: bold;
+ }
+ </style>
{/literal}
<h2>{'SitesManager_WebsitesManagement'|translate}</h2>
<p>{'SitesManager_MainDescription'|translate}
-{'SitesManager_YouCurrentlyHaveAccessToNWebsites'|translate:"<b>$adminSitesCount</b>"}
-{if $isSuperUser}
-<br />{'SitesManager_SuperUserCan'|translate:"<a href='#globalSettings'>":"</a>"}
-{/if}
+ {'SitesManager_YouCurrentlyHaveAccessToNWebsites'|translate:"<b>$adminSitesCount</b>"}
+ {if $isSuperUser}
+ <br/>
+ {'SitesManager_SuperUserCan'|translate:"<a href='#globalSettings'>":"</a>"}
+ {/if}
</p>
{ajaxErrorDiv}
{ajaxLoadingDiv}
{capture assign=createNewWebsite}
- <div class="addRowSite"><img src='plugins/UsersManager/images/add.png' alt="" /> {'SitesManager_AddSite'|translate}</div>
+ <div class="addRowSite"><img src='plugins/UsersManager/images/add.png' alt=""/> {'SitesManager_AddSite'|translate}</div>
{/capture}
{if $adminSites|@count == 0}
- {'SitesManager_NoWebsites'|translate}
+ {'SitesManager_NoWebsites'|translate}
{else}
<div class="ui-confirm" id="confirm">
<h2></h2>
- <input role="yes" type="button" value="{'General_Yes'|translate}" />
- <input role="no" type="button" value="{'General_No'|translate}" />
+ <input role="yes" type="button" value="{'General_Yes'|translate}"/>
+ <input role="no" type="button" value="{'General_No'|translate}"/>
+ </div>
+ <div class="entityContainer">
+ {if $isSuperUser}
+ {$createNewWebsite}
+ {/if}
+ <table class="entityTable dataTable" id="editSites">
+ <thead>
+ <tr>
+ <th>{'General_Id'|translate}</th>
+ <th>{'General_Name'|translate}</th>
+ <th>{'SitesManager_Urls'|translate}</th>
+ <th>{'SitesManager_ExcludedIps'|translate}</th>
+ <th>{'SitesManager_ExcludedParameters'|translate|replace:" ":"<br />"}</th>
+ <th id='exclude-user-agent-header'
+ {if !$allowSiteSpecificUserAgentExclude}style="display:none"{/if}>{'SitesManager_ExcludedUserAgents'|translate}</th>
+ <th>{'Actions_SubmenuSitesearch'|translate}</th>
+ <th>{'SitesManager_Timezone'|translate}</th>
+ <th>{'SitesManager_Currency'|translate}</th>
+ <th>{'Goals_Ecommerce'|translate}</th>
+ <th></th>
+ <th></th>
+ <th> {'SitesManager_JsTrackingTag'|translate} </th>
+ </tr>
+ </thead>
+ <tbody>
+ {foreach from=$adminSites key=i item=site}
+ <tr id="row{$site.idsite}" data-keep-url-fragments="{$site.keep_url_fragment}">
+ <td id="idSite">{$site.idsite}</td>
+ <td id="siteName" class="editableSite">{$site.name}</td>
+ <td id="urls" class="editableSite">{foreach from=$site.alias_urls item=url}{$url|replace:"http://":""}<br/>{/foreach}</td>
+ <td id="excludedIps" class="editableSite">{foreach from=$site.excluded_ips item=ip}{$ip}<br/>{/foreach}</td>
+ <td id="excludedQueryParameters" class="editableSite">{foreach from=$site.excluded_parameters item=parameter}{$parameter}<br/>{/foreach}
+ </td>
+ <td id="excludedUserAgents" class="editableSite"
+ {if !$allowSiteSpecificUserAgentExclude}style="display:none"{/if}>{foreach from=$site.excluded_user_agents item=ua}{$ua}<br/>{/foreach}
+ </td>
+ <td id="sitesearch" class="editableSite">{if $site.sitesearch}<span class='sitesearchActive'>{'General_Yes'|translate}</span>{else}<span
+ class='sitesearchInactive'>-</span>{/if}<span class='sskp'
+ sitesearch_keyword_parameters="{$site.sitesearch_keyword_parameters|escape:'html'}"
+ sitesearch_category_parameters="{$site.sitesearch_category_parameters|escape:'html'}"
+ id="sitesearch_parameters"></span></td>
+ <td id="timezone" class="editableSite">{$site.timezone}</td>
+ <td id="currency" class="editableSite">{$site.currency}</td>
+ <td id="ecommerce" class="editableSite">{if $site.ecommerce}<span class='ecommerceActive'>{'General_Yes'|translate}</span>{else}
+ <span class='ecommerceInactive'>-</span>
+ {/if}</td>
+ <td><span id="row{$site.idsite}" class='editSite link_but'><img src='themes/default/images/ico_edit.png' title="{'General_Edit'|translate}"
+ border="0"/> {'General_Edit'|translate}</span></td>
+ <td><span id="row{$site.idsite}" class="deleteSite link_but"><img src='themes/default/images/ico_delete.png'
+ title="{'General_Delete'|translate}"
+ border="0"/> {'General_Delete'|translate}</span></td>
+ <td>
+ <a href='{url module=CoreAdminHome action=trackingCodeGenerator idSite=$site.idsite updated=false}'>{'SitesManager_ShowTrackingTag'|translate}</a>
+ </td>
+ </tr>
+ {/foreach}
+ </tbody>
+ </table>
+ {if $isSuperUser}
+ {$createNewWebsite}
+ {/if}
</div>
-
- <div class="entityContainer">
- {if $isSuperUser}
- {$createNewWebsite}
- {/if}
- <table class="entityTable dataTable" id="editSites">
- <thead>
- <tr>
- <th>{'General_Id'|translate}</th>
- <th>{'General_Name'|translate}</th>
- <th>{'SitesManager_Urls'|translate}</th>
- <th>{'SitesManager_ExcludedIps'|translate}</th>
- <th>{'SitesManager_ExcludedParameters'|translate|replace:" ":"<br />"}</th>
- <th id='exclude-user-agent-header' {if !$allowSiteSpecificUserAgentExclude}style="display:none"{/if}>{'SitesManager_ExcludedUserAgents'|translate}</th>
- <th>{'Actions_SubmenuSitesearch'|translate}</th>
- <th>{'SitesManager_Timezone'|translate}</th>
- <th>{'SitesManager_Currency'|translate}</th>
- <th>{'Goals_Ecommerce'|translate}</th>
- <th> </th>
- <th> </th>
- <th> {'SitesManager_JsTrackingTag'|translate} </th>
- </tr>
- </thead>
- <tbody>
- {foreach from=$adminSites key=i item=site}
- <tr id="row{$site.idsite}" data-keep-url-fragments="{$site.keep_url_fragment}">
- <td id="idSite">{$site.idsite}</td>
- <td id="siteName" class="editableSite">{$site.name}</td>
- <td id="urls" class="editableSite">{foreach from=$site.alias_urls item=url}{$url|replace:"http://":""}<br />{/foreach}</td>
- <td id="excludedIps" class="editableSite">{foreach from=$site.excluded_ips item=ip}{$ip}<br />{/foreach}</td>
- <td id="excludedQueryParameters" class="editableSite">{foreach from=$site.excluded_parameters item=parameter}{$parameter}<br />{/foreach}</td>
- <td id="excludedUserAgents" class="editableSite" {if !$allowSiteSpecificUserAgentExclude}style="display:none"{/if}>{foreach from=$site.excluded_user_agents item=ua}{$ua}<br />{/foreach}</td>
- <td id="sitesearch" class="editableSite">{if $site.sitesearch}<span class='sitesearchActive'>{'General_Yes'|translate}</span>{else}<span class='sitesearchInactive'>-</span>{/if}<span class='sskp' sitesearch_keyword_parameters="{$site.sitesearch_keyword_parameters|escape:'html'}" sitesearch_category_parameters="{$site.sitesearch_category_parameters|escape:'html'}" id="sitesearch_parameters"></span></td>
- <td id="timezone" class="editableSite">{$site.timezone}</td>
- <td id="currency" class="editableSite">{$site.currency}</td>
- <td id="ecommerce" class="editableSite">{if $site.ecommerce}<span class='ecommerceActive'>{'General_Yes'|translate}</span>{else}<span class='ecommerceInactive'>-</span>{/if}</td>
- <td><span id="row{$site.idsite}" class='editSite link_but'><img src='themes/default/images/ico_edit.png' title="{'General_Edit'|translate}" border="0"/> {'General_Edit'|translate}</span></td>
- <td><span id="row{$site.idsite}" class="deleteSite link_but"><img src='themes/default/images/ico_delete.png' title="{'General_Delete'|translate}" border="0" /> {'General_Delete'|translate}</span></td>
- <td><a href='{url module=CoreAdminHome action=trackingCodeGenerator idSite=$site.idsite updated=false}'>{'SitesManager_ShowTrackingTag'|translate}</a></td>
- </tr>
- {/foreach}
- </tbody>
- </table>
- {if $isSuperUser}
- {$createNewWebsite}
- {/if}
- </div>
{/if}
{* Admin users use these values for Site Search column, when editing websites *}
{if !$isSuperUser}
-<input type="hidden" size="15" id="globalSearchKeywordParameters" value="{$globalSearchKeywordParameters|escape:'html'}"></input>
-<input type="hidden" size="15" id="globalSearchCategoryParameters" value="{$globalSearchCategoryParameters|escape:'html'}"></input>
+ <input type="hidden" size="15" id="globalSearchKeywordParameters" value="{$globalSearchKeywordParameters|escape:'html'}"></input>
+ <input type="hidden" size="15" id="globalSearchCategoryParameters" value="{$globalSearchCategoryParameters|escape:'html'}"></input>
{/if}
{if $isSuperUser}
-<br />
- <a name='globalSettings'></a>
- <h2>{'SitesManager_GlobalWebsitesSettings'|translate}</h2>
- <br />
- <table style='width:600px' class="adminTable" >
-
- <tr><td colspan="2">
- <b>{'SitesManager_GlobalListExcludedIps'|translate}</b>
- <p>{'SitesManager_ListOfIpsToBeExcludedOnAllWebsites'|translate} </p>
- </td></tr>
- <tr><td>
- <textarea cols="30" rows="3" id="globalExcludedIps">{$globalExcludedIps}
-</textarea>
- </td><td>
- <label for="globalExcludedIps">{$excludedIpHelp}</label>
- </td></tr>
-
- <tr><td colspan="2">
- <b>{'SitesManager_GlobalListExcludedQueryParameters'|translate}</b>
- <p>{'SitesManager_ListOfQueryParametersToBeExcludedOnAllWebsites'|translate} </p>
- </td></tr>
-
- <tr><td>
- <textarea cols="30" rows="3" id="globalExcludedQueryParameters">{$globalExcludedQueryParameters}
-</textarea>
- </td><td><label for="globalExcludedQueryParameters">{$excludedQueryParametersHelp}</label>
- </td></tr>
-
- {* global excluded user agents *}
- <tr><td colspan="2">
- <b>{'SitesManager_GlobalListExcludedUserAgents'|translate}</b>
- <p>{'SitesManager_GlobalListExcludedUserAgents_Desc'|translate}</p>
- </td></tr>
-
- <tr><td>
- <textarea cols="30" rows="3" id="globalExcludedUserAgents">{$globalExcludedUserAgents}</textarea>
- </td><td><label for="globalExcludedUserAgents">{$excludedUserAgentsHelp}</label>
- </td></tr>
-
- <tr><td>
- <input type="checkbox" id="enableSiteUserAgentExclude" name="enableSiteUserAgentExclude" {if $allowSiteSpecificUserAgentExclude}checked="checked"{/if}/><label for="enableSiteUserAgentExclude">{'SitesManager_EnableSiteSpecificUserAgentExclude'|translate}</label>
- <span id='enableSiteUserAgentExclude-loading' class='loadingPiwik' style='display:none'><img src='./themes/default/images/loading-blue.gif' /></span>
- </td><td>{'SitesManager_EnableSiteSpecificUserAgentExclude_Help'|translate:'<a href="#editSites">':'</a>'|inlineHelp}
- </td></tr>
-
- {* global keep URL fragments *}
- <tr><td colspan="2">
- <strong>{'SitesManager_KeepURLFragments'|translate}</strong>
- <p>{'SitesManager_KeepURLFragmentsHelp'|translate:"<em>#</em>":"<em>example.org/index.html#first_section</em>":"<em>example.org/index.html</em>"}
- </p>
- <input type="checkbox" id="globalKeepURLFragments" name="globalKeepURLFragments" {if $globalKeepURLFragments}checked="checked"{/if}/>
- <label for="globalKeepURLFragments">{'SitesManager_KeepURLFragmentsLong'|translate}</label>
- <p>{'SitesManager_KeepURLFragmentsHelp2'|translate}</p>
- </td></tr>
-
- {* global site search *}
- <tr><td colspan="2">
- <a name='globalSiteSearch'></a><b>{'SitesManager_TrackingSiteSearch'|translate}</b>
- <p>{$sitesearchIntro}</p>
- <span class="form-description" style='font-size:8pt'>{'SitesManager_SearchParametersNote'|translate} {'SitesManager_SearchParametersNote2'|translate}</span>
- </td></tr>
- <tr><td colspan="2">
- <label>{$searchKeywordLabel} &nbsp;<input type="text" size="15" id="globalSearchKeywordParameters" value="{$globalSearchKeywordParameters|escape:'html'}"></input>
- <div style='width: 200px;float:right;'>{$searchKeywordHelp}</div></label>
- </td></tr>
-
- <tr><td colspan="2">
- {if !$isSearchCategoryTrackingEnabled}
- <input value='globalSearchCategoryParametersIsDisabled' id="globalSearchCategoryParameters" type='hidden'></input>
- <span class='form-description'>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).</span>
- {else}
- {'Goals_Optional'|translate} {'SitesManager_SearchCategoryDesc'|translate} <br/>
- </td></tr>
- <tr><td colspan="2">
- <label>{$searchCategoryLabel} &nbsp;<input type="text" size="15" id="globalSearchCategoryParameters" value="{$globalSearchCategoryParameters|escape:'html'}"></input>
- <div style='width: 200px;float:right;'>{$searchCategoryHelp}</div></label>
- {/if}
- </td></tr>
-
- <tr><td colspan="2">
- <b>{'SitesManager_DefaultTimezoneForNewWebsites'|translate}</b>
- <p>{'SitesManager_SelectDefaultTimezone'|translate} </p>
- </td></tr>
- <tr><td>
- <div id='defaultTimezone'></div>
- </td><td>
- {$defaultTimezoneHelp}
- </td></tr>
-
- <tr><td colspan="2">
- <b>{'SitesManager_DefaultCurrencyForNewWebsites'|translate}</b>
- <p>{'SitesManager_SelectDefaultCurrency'|translate} </p>
- </td></tr>
- <tr><td>
- <div id='defaultCurrency'></div>
- </td><td>
- {$currencyHelpPlain}
- </td></tr>
- </table>
- <span style='margin-left:20px'>
- <input type="submit" class="submit" id='globalSettingsSubmit' value="{'General_Save'|translate}" />
+ <br/>
+ <a name='globalSettings'></a>
+ <h2>{'SitesManager_GlobalWebsitesSettings'|translate}</h2>
+ <br/>
+ <table style='width:600px' class="adminTable">
+
+ <tr>
+ <td colspan="2">
+ <b>{'SitesManager_GlobalListExcludedIps'|translate}</b>
+
+ <p>{'SitesManager_ListOfIpsToBeExcludedOnAllWebsites'|translate} </p>
+ </td>
+ </tr>
+ <tr>
+ <td>
+ <textarea cols="30" rows="3" id="globalExcludedIps">{$globalExcludedIps}
+ </textarea>
+ </td>
+ <td>
+ <label for="globalExcludedIps">{$excludedIpHelp}</label>
+ </td>
+ </tr>
+
+ <tr>
+ <td colspan="2">
+ <b>{'SitesManager_GlobalListExcludedQueryParameters'|translate}</b>
+
+ <p>{'SitesManager_ListOfQueryParametersToBeExcludedOnAllWebsites'|translate} </p>
+ </td>
+ </tr>
+
+ <tr>
+ <td>
+ <textarea cols="30" rows="3" id="globalExcludedQueryParameters">{$globalExcludedQueryParameters}
+ </textarea>
+ </td>
+ <td><label for="globalExcludedQueryParameters">{$excludedQueryParametersHelp}</label>
+ </td>
+ </tr>
+
+ {* global excluded user agents *}
+ <tr>
+ <td colspan="2">
+ <b>{'SitesManager_GlobalListExcludedUserAgents'|translate}</b>
+
+ <p>{'SitesManager_GlobalListExcludedUserAgents_Desc'|translate}</p>
+ </td>
+ </tr>
+
+ <tr>
+ <td>
+ <textarea cols="30" rows="3" id="globalExcludedUserAgents">{$globalExcludedUserAgents}</textarea>
+ </td>
+ <td><label for="globalExcludedUserAgents">{$excludedUserAgentsHelp}</label>
+ </td>
+ </tr>
+
+ <tr>
+ <td>
+ <input type="checkbox" id="enableSiteUserAgentExclude" name="enableSiteUserAgentExclude"
+ {if $allowSiteSpecificUserAgentExclude}checked="checked"{/if}/><label
+ for="enableSiteUserAgentExclude">{'SitesManager_EnableSiteSpecificUserAgentExclude'|translate}</label>
+ <span id='enableSiteUserAgentExclude-loading' class='loadingPiwik' style='display:none'><img
+ src='./themes/default/images/loading-blue.gif'/></span>
+ </td>
+ <td>{'SitesManager_EnableSiteSpecificUserAgentExclude_Help'|translate:'<a href="#editSites">':'</a>'|inlineHelp}
+ </td>
+ </tr>
+
+ {* global keep URL fragments *}
+ <tr>
+ <td colspan="2">
+ <strong>{'SitesManager_KeepURLFragments'|translate}</strong>
+
+ <p>{'SitesManager_KeepURLFragmentsHelp'|translate:"<em>#</em>":"<em>example.org/index.html#first_section</em>":"<em>example.org/index.html</em>"}
+ </p>
+ <input type="checkbox" id="globalKeepURLFragments" name="globalKeepURLFragments" {if $globalKeepURLFragments}checked="checked"{/if}/>
+ <label for="globalKeepURLFragments">{'SitesManager_KeepURLFragmentsLong'|translate}</label>
+
+ <p>{'SitesManager_KeepURLFragmentsHelp2'|translate}</p>
+ </td>
+ </tr>
+
+ {* global site search *}
+ <tr>
+ <td colspan="2">
+ <a name='globalSiteSearch'></a><b>{'SitesManager_TrackingSiteSearch'|translate}</b>
+
+ <p>{$sitesearchIntro}</p>
+ <span class="form-description"
+ style='font-size:8pt'>{'SitesManager_SearchParametersNote'|translate} {'SitesManager_SearchParametersNote2'|translate}</span>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <label>{$searchKeywordLabel} &nbsp;<input type="text" size="15" id="globalSearchKeywordParameters"
+ value="{$globalSearchKeywordParameters|escape:'html'}"></input>
+
+ <div style='width: 200px;float:right;'>{$searchKeywordHelp}</div>
+ </label>
+ </td>
+ </tr>
+
+ <tr>
+ <td colspan="2">
+ {if !$isSearchCategoryTrackingEnabled}
+ <input value='globalSearchCategoryParametersIsDisabled' id="globalSearchCategoryParameters" type='hidden'></input>
+ <span class='form-description'>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).</span>
+ {else}
+ {'Goals_Optional'|translate} {'SitesManager_SearchCategoryDesc'|translate} <br/>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <label>{$searchCategoryLabel} &nbsp;<input type="text" size="15" id="globalSearchCategoryParameters"
+ value="{$globalSearchCategoryParameters|escape:'html'}"></input>
+
+ <div style='width: 200px;float:right;'>{$searchCategoryHelp}</div>
+ </label>
+ {/if}
+ </td>
+ </tr>
+
+ <tr>
+ <td colspan="2">
+ <b>{'SitesManager_DefaultTimezoneForNewWebsites'|translate}</b>
+
+ <p>{'SitesManager_SelectDefaultTimezone'|translate} </p>
+ </td>
+ </tr>
+ <tr>
+ <td>
+ <div id='defaultTimezone'></div>
+ </td>
+ <td>
+ {$defaultTimezoneHelp}
+ </td>
+ </tr>
+
+ <tr>
+ <td colspan="2">
+ <b>{'SitesManager_DefaultCurrencyForNewWebsites'|translate}</b>
+
+ <p>{'SitesManager_SelectDefaultCurrency'|translate} </p>
+ </td>
+ </tr>
+ <tr>
+ <td>
+ <div id='defaultCurrency'></div>
+ </td>
+ <td>
+ {$currencyHelpPlain}
+ </td>
+ </tr>
+ </table>
+ <span style='margin-left:20px'>
+ <input type="submit" class="submit" id='globalSettingsSubmit' value="{'General_Save'|translate}"/>
</span>
- {ajaxErrorDiv id=ajaxErrorGlobalSettings}
- {ajaxLoadingDiv id=ajaxLoadingGlobalSettings}
+ {ajaxErrorDiv id=ajaxErrorGlobalSettings}
+ {ajaxLoadingDiv id=ajaxLoadingGlobalSettings}
{/if}
{if $showAddSite}
-<script type="text/javascript">{literal}
-$(document).ready(function(){
- $('.addRowSite:first').trigger('click');
-});
-{/literal}</script>
+ <script type="text/javascript">{literal}
+ $(document).ready(function () {
+ $('.addRowSite:first').trigger('click');
+ });
+ {/literal}</script>
{/if}
-<br /><br /><br /><br />
+<br/><br/><br/><br/>
{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 @@
<?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_Transitions
*/
@@ -14,301 +14,285 @@
*/
class Piwik_Transitions_API
{
-
- 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();
- }
-
+
+ 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 @@
<?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_Transitions
*/
@@ -14,78 +14,78 @@
*/
class Piwik_Transitions_Controller extends Piwik_Controller
{
-
- /**
- * 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;
- }
-
+ /**
+ * 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 = $('<textarea>').html(link).val(); // remove html entities
-
- var module = this.dataTable.param.module;
- var action = this.dataTable.param.action;
- if (DataTable_RowActions_Transitions.isPageUrlReport(module, action)) {
- this.openPopover('url:' + link);
- } else if (DataTable_RowActions_Transitions.isPageTitleReport(module, action)) {
- DataTable_RowAction.prototype.trigger.apply(this, [tr, e, subTableLabel]);
- } else {
- alert('Transitions can\'t be used on this report.');
- }
+DataTable_RowActions_Transitions.prototype.trigger = function (tr, e, subTableLabel) {
+ var link = tr.find('> td:first > a').attr('href');
+ link = $('<textarea>').html(link).val(); // remove html entities
+
+ var module = this.dataTable.param.module;
+ var action = this.dataTable.param.action;
+ if (DataTable_RowActions_Transitions.isPageUrlReport(module, action)) {
+ this.openPopover('url:' + link);
+ } else if (DataTable_RowActions_Transitions.isPageTitleReport(module, action)) {
+ DataTable_RowAction.prototype.trigger.apply(this, [tr, e, subTableLabel]);
+ } else {
+ alert('Transitions can\'t be used on this report.');
+ }
};
-DataTable_RowAction.prototype.performAction = function(label, tr, e) {
- var separator = ' > '; // Piwik_API_DataTableManipulator_LabelFilter::SEPARATOR_RECURSIVE_LABEL
- var labelParts = label.split(separator);
- for (var i = 0; i < labelParts.length; i++) {
- labelParts[i] = $.trim(decodeURIComponent(labelParts[i]));
- }
- label = labelParts.join(piwik.config.action_url_category_delimiter);
- this.openPopover('title:' + label);
+DataTable_RowAction.prototype.performAction = function (label, tr, e) {
+ var separator = ' > '; // Piwik_API_DataTableManipulator_LabelFilter::SEPARATOR_RECURSIVE_LABEL
+ var labelParts = label.split(separator);
+ for (var i = 0; i < labelParts.length; i++) {
+ labelParts[i] = $.trim(decodeURIComponent(labelParts[i]));
+ }
+ label = labelParts.join(piwik.config.action_url_category_delimiter);
+ this.openPopover('title:' + label);
};
-DataTable_RowActions_Transitions.prototype.doOpenPopover = function(link) {
- var parts = link.split(':');
- if (parts.length < 2) {
- return;
- }
-
- var actionType = parts[0];
- parts.shift();
- var actionName = parts.join(':');
-
- if (this.transitions === null) {
- this.transitions = new Piwik_Transitions(actionType, actionName, this);
- } else {
- this.transitions.reset(actionType, actionName);
- }
- this.transitions.showPopover();
+DataTable_RowActions_Transitions.prototype.doOpenPopover = function (link) {
+ var parts = link.split(':');
+ if (parts.length < 2) {
+ return;
+ }
+
+ var actionType = parts[0];
+ parts.shift();
+ var actionName = parts.join(':');
+
+ if (this.transitions === null) {
+ this.transitions = new Piwik_Transitions(actionType, actionName, this);
+ } else {
+ this.transitions.reset(actionType, actionName);
+ }
+ this.transitions.showPopover();
};
DataTable_RowActions_Registry.register({
- name: 'Transitions',
-
- dataTableIcon: 'plugins/Transitions/templates/transitions_icon.png',
- dataTableIconHover: 'plugins/Transitions/templates/transitions_icon_hover.png',
-
- order: 20,
-
- dataTableIconTooltip: [
- _pk_translate('General_TransitionsRowActionTooltipTitle_js'),
- _pk_translate('General_TransitionsRowActionTooltip_js')
- ],
-
- createInstance: function(dataTable) {
- return new DataTable_RowActions_Transitions(dataTable);
- },
-
- isAvailableOnReport: function(dataTableParams) {
- return (
- DataTable_RowActions_Transitions.isPageUrlReport(dataTableParams.module, dataTableParams.action) ||
- DataTable_RowActions_Transitions.isPageTitleReport(dataTableParams.module, dataTableParams.action)
- );
- },
-
- isAvailableOnRow: function(dataTableParams, tr) {
- if (tr.attr('id')) {
- // not available on groups (i.e. folders)
- return false;
- }
- if (DataTable_RowActions_Transitions.isPageUrlReport(dataTableParams.module, dataTableParams.action)
- && !tr.find('> td:first span.label').parent().is('a')) {
- // not on page url without link (i.e. "Page URL not defined")
- return false;
- }
- return true;
- }
+ name: 'Transitions',
+
+ dataTableIcon: 'plugins/Transitions/templates/transitions_icon.png',
+ dataTableIconHover: 'plugins/Transitions/templates/transitions_icon_hover.png',
+
+ order: 20,
+
+ dataTableIconTooltip: [
+ _pk_translate('General_TransitionsRowActionTooltipTitle_js'),
+ _pk_translate('General_TransitionsRowActionTooltip_js')
+ ],
+
+ createInstance: function (dataTable) {
+ return new DataTable_RowActions_Transitions(dataTable);
+ },
+
+ isAvailableOnReport: function (dataTableParams) {
+ return (
+ DataTable_RowActions_Transitions.isPageUrlReport(dataTableParams.module, dataTableParams.action) ||
+ DataTable_RowActions_Transitions.isPageTitleReport(dataTableParams.module, dataTableParams.action)
+ );
+ },
+
+ isAvailableOnRow: function (dataTableParams, tr) {
+ if (tr.attr('id')) {
+ // not available on groups (i.e. folders)
+ return false;
+ }
+ if (DataTable_RowActions_Transitions.isPageUrlReport(dataTableParams.module, dataTableParams.action)
+ && !tr.find('> td:first span.label').parent().is('a')) {
+ // not on page url without link (i.e. "Page URL not defined")
+ return false;
+ }
+ return true;
+ }
});
@@ -120,587 +120,587 @@ DataTable_RowActions_Registry.register({
//
function Piwik_Transitions(actionType, actionName, rowAction) {
- this.reset(actionType, actionName);
- this.rowAction = rowAction;
+ this.reset(actionType, actionName);
+ this.rowAction = rowAction;
- this.ajax = new Piwik_Transitions_Ajax();
- this.model = new Piwik_Transitions_Model(this.ajax);
+ this.ajax = new Piwik_Transitions_Ajax();
+ this.model = new Piwik_Transitions_Model(this.ajax);
- this.leftGroups = ['previousPages', 'previousSiteSearches', 'searchEngines', 'websites', 'campaigns'];
- this.rightGroups = ['followingPages', 'followingSiteSearches', 'downloads', 'outlinks'];
+ this.leftGroups = ['previousPages', 'previousSiteSearches', 'searchEngines', 'websites', 'campaigns'];
+ this.rightGroups = ['followingPages', 'followingSiteSearches', 'downloads', 'outlinks'];
}
-Piwik_Transitions.prototype.reset = function(actionType, actionName) {
- this.actionType = actionType;
- this.actionName = actionName;
+Piwik_Transitions.prototype.reset = function (actionType, actionName) {
+ this.actionType = actionType;
+ this.actionName = actionName;
- this.popover = null;
- this.canvas = null;
- this.centerBox = null;
+ this.popover = null;
+ this.canvas = null;
+ this.centerBox = null;
- this.leftOpenGroup = 'previousPages';
- this.rightOpenGroup = 'followingPages';
+ this.leftOpenGroup = 'previousPages';
+ this.rightOpenGroup = 'followingPages';
- this.highlightedGroup = false;
- this.highlightedGroupSide = false;
- this.highlightedGroupCenterEl = false;
+ this.highlightedGroup = false;
+ this.highlightedGroupSide = false;
+ this.highlightedGroupCenterEl = false;
};
/** Open the popover */
-Piwik_Transitions.prototype.showPopover = function() {
- var self = this;
-
- this.popover = Piwik_Popover.showLoading('Transitions', self.actionName, 550);
- Piwik_Popover.addHelpButton('http://piwik.org/docs/transitions');
-
- var bothLoaded = function() {
- Piwik_Popover.setContent(Piwik_Transitions.popoverHtml);
-
- self.preparePopover();
- self.model.htmlLoaded();
-
- if (self.model.searchEnginesNbTransitions > 0 && self.model.websitesNbTransitions > 0
- + self.model.campaignsNbTransitions > 0) {
- self.canvas.narrowMode();
- }
-
- self.render();
- self.canvas.truncateVisibleBoxTexts();
- };
-
- // load the popover HTML (only done once)
- var callbackForHtml = false;
- if (typeof Piwik_Transitions.popoverHtml == 'undefined') {
- this.ajax.callTransitionsController('renderPopover', function(html) {
- Piwik_Transitions.popoverHtml = html;
- if (callbackForHtml !== false) {
- callbackForHtml();
- }
- });
- }
-
- // load the data
- self.model.loadData(self.actionType, self.actionName, function() {
- if (typeof Piwik_Transitions.popoverHtml == 'undefined') {
- // html not there yet
- callbackForHtml = bothLoaded;
- } else {
- // html already loaded
- bothLoaded();
- }
- });
+Piwik_Transitions.prototype.showPopover = function () {
+ var self = this;
+
+ this.popover = Piwik_Popover.showLoading('Transitions', self.actionName, 550);
+ Piwik_Popover.addHelpButton('http://piwik.org/docs/transitions');
+
+ var bothLoaded = function () {
+ Piwik_Popover.setContent(Piwik_Transitions.popoverHtml);
+
+ self.preparePopover();
+ self.model.htmlLoaded();
+
+ if (self.model.searchEnginesNbTransitions > 0 && self.model.websitesNbTransitions > 0
+ + self.model.campaignsNbTransitions > 0) {
+ self.canvas.narrowMode();
+ }
+
+ self.render();
+ self.canvas.truncateVisibleBoxTexts();
+ };
+
+ // load the popover HTML (only done once)
+ var callbackForHtml = false;
+ if (typeof Piwik_Transitions.popoverHtml == 'undefined') {
+ this.ajax.callTransitionsController('renderPopover', function (html) {
+ Piwik_Transitions.popoverHtml = html;
+ if (callbackForHtml !== false) {
+ callbackForHtml();
+ }
+ });
+ }
+
+ // load the data
+ self.model.loadData(self.actionType, self.actionName, function () {
+ if (typeof Piwik_Transitions.popoverHtml == 'undefined') {
+ // html not there yet
+ callbackForHtml = bothLoaded;
+ } else {
+ // html already loaded
+ bothLoaded();
+ }
+ });
};
/** Prepare the popover with the basic DOM to add data later. */
-Piwik_Transitions.prototype.preparePopover = function() {
- var self = this;
-
- var width = 900;
- var height = 550;
-
- var canvasBgLeft = self.prepareCanvas('Background_Left', width, height);
- var canvasBgRight = self.prepareCanvas('Background_Right', width, height);
- var canvasLeft = self.prepareCanvas('Left', width, height);
- var canvasRight = self.prepareCanvas('Right', width, height);
- var canvasLoops = self.prepareCanvas('Loops', width, height);
-
- self.canvas = new Piwik_Transitions_Canvas(canvasBgLeft, canvasBgRight, canvasLeft, canvasRight,
- canvasLoops, width, height);
-
- self.centerBox = self.popover.find('#Transitions_CenterBox');
-
- var title = self.actionName;
- if (self.actionType == 'url') {
- title = Piwik_Transitions_Util.shortenUrl(title, true);
- }
- title = self.centerBox.find('h2')
- .addClass('Transitions_ApplyTextAndTruncate')
- .data('text', title)
- .data('maxLines', 3);
-
- if (self.actionType == 'url') {
- title.click(function() {
- self.openExternalUrl(self.actionName);
- }).css('cursor', 'pointer');
- }
-
- title.add(self.popover.find('p.Transitions_Pageviews')).hover(function() {
- var totalNbPageviews = self.model.getTotalNbPageviews();
- if (totalNbPageviews > 0) {
- var share = Math.round(self.model.pageviews / totalNbPageviews * 1000) / 10;
-
- var text = Piwik_Transitions_Translations.ShareOfAllPageviews;
- text = text.replace(/%s/, self.model.pageviews).replace(/%s/, share + '%');
- text += '<br /><i>' + Piwik_Transitions_Translations.DateRange + ' ' + self.model.date + '</i>';
-
- var title = '<span class="tip-title">' + piwikHelper.addBreakpointsToUrl(self.actionName) +
- '</span><br />';
-
- Piwik_Tooltip.show(title + text, 'Transitions_Tooltip_Small', 300);
- }
- }, function() {
- Piwik_Tooltip.hide();
- });
+Piwik_Transitions.prototype.preparePopover = function () {
+ var self = this;
+
+ var width = 900;
+ var height = 550;
+
+ var canvasBgLeft = self.prepareCanvas('Background_Left', width, height);
+ var canvasBgRight = self.prepareCanvas('Background_Right', width, height);
+ var canvasLeft = self.prepareCanvas('Left', width, height);
+ var canvasRight = self.prepareCanvas('Right', width, height);
+ var canvasLoops = self.prepareCanvas('Loops', width, height);
+
+ self.canvas = new Piwik_Transitions_Canvas(canvasBgLeft, canvasBgRight, canvasLeft, canvasRight,
+ canvasLoops, width, height);
+
+ self.centerBox = self.popover.find('#Transitions_CenterBox');
+
+ var title = self.actionName;
+ if (self.actionType == 'url') {
+ title = Piwik_Transitions_Util.shortenUrl(title, true);
+ }
+ title = self.centerBox.find('h2')
+ .addClass('Transitions_ApplyTextAndTruncate')
+ .data('text', title)
+ .data('maxLines', 3);
+
+ if (self.actionType == 'url') {
+ title.click(function () {
+ self.openExternalUrl(self.actionName);
+ }).css('cursor', 'pointer');
+ }
+
+ title.add(self.popover.find('p.Transitions_Pageviews')).hover(function () {
+ var totalNbPageviews = self.model.getTotalNbPageviews();
+ if (totalNbPageviews > 0) {
+ var share = Math.round(self.model.pageviews / totalNbPageviews * 1000) / 10;
+
+ var text = Piwik_Transitions_Translations.ShareOfAllPageviews;
+ text = text.replace(/%s/, self.model.pageviews).replace(/%s/, share + '%');
+ text += '<br /><i>' + Piwik_Transitions_Translations.DateRange + ' ' + self.model.date + '</i>';
+
+ var title = '<span class="tip-title">' + piwikHelper.addBreakpointsToUrl(self.actionName) +
+ '</span><br />';
+
+ Piwik_Tooltip.show(title + text, 'Transitions_Tooltip_Small', 300);
+ }
+ }, function () {
+ Piwik_Tooltip.hide();
+ });
};
-Piwik_Transitions.prototype.prepareCanvas = function(canvasId, width, height) {
- canvasId = 'Transitions_Canvas_' + canvasId;
- var div = $('#' + canvasId).width(width).height(height);
- var canvas;
-
- if (typeof Piwik_Transitions.canvasCache == 'undefined'
- || typeof window.G_vmlCanvasManager != "undefined") {
- // no recycling for excanvas because they are disposed properly anyway and
- // recycling doesn't work this way in IE8
- Piwik_Transitions.canvasCache = {};
- }
-
- if (typeof Piwik_Transitions.canvasCache[canvasId] == 'undefined') {
- Piwik_Transitions.canvasCache[canvasId] = document.createElement('canvas');
- canvas = Piwik_Transitions.canvasCache[canvasId];
- canvas.width = width;
- canvas.height = height;
- } else {
- canvas = Piwik_Transitions.canvasCache[canvasId];
- canvas.getContext('2d').clearRect(0, 0, width, height);
- }
-
- div.append(canvas);
- return canvas;
+Piwik_Transitions.prototype.prepareCanvas = function (canvasId, width, height) {
+ canvasId = 'Transitions_Canvas_' + canvasId;
+ var div = $('#' + canvasId).width(width).height(height);
+ var canvas;
+
+ if (typeof Piwik_Transitions.canvasCache == 'undefined'
+ || typeof window.G_vmlCanvasManager != "undefined") {
+ // no recycling for excanvas because they are disposed properly anyway and
+ // recycling doesn't work this way in IE8
+ Piwik_Transitions.canvasCache = {};
+ }
+
+ if (typeof Piwik_Transitions.canvasCache[canvasId] == 'undefined') {
+ Piwik_Transitions.canvasCache[canvasId] = document.createElement('canvas');
+ canvas = Piwik_Transitions.canvasCache[canvasId];
+ canvas.width = width;
+ canvas.height = height;
+ } else {
+ canvas = Piwik_Transitions.canvasCache[canvasId];
+ canvas.getContext('2d').clearRect(0, 0, width, height);
+ }
+
+ div.append(canvas);
+ return canvas;
};
/** Render the popover content */
-Piwik_Transitions.prototype.render = function() {
- this.renderCenterBox();
+Piwik_Transitions.prototype.render = function () {
+ this.renderCenterBox();
- this.renderLeftSide();
- this.renderRightSide();
+ this.renderLeftSide();
+ this.renderRightSide();
- this.renderLoops();
+ this.renderLoops();
};
/** Render left side: referrer groups & direct entries */
-Piwik_Transitions.prototype.renderLeftSide = function(onlyBg) {
- this.renderGroups(this.leftGroups, this.leftOpenGroup, 'left', onlyBg);
- this.renderEntries(onlyBg);
+Piwik_Transitions.prototype.renderLeftSide = function (onlyBg) {
+ this.renderGroups(this.leftGroups, this.leftOpenGroup, 'left', onlyBg);
+ this.renderEntries(onlyBg);
- this.reRenderIfNeededToCenter('left', onlyBg);
+ this.reRenderIfNeededToCenter('left', onlyBg);
};
/** Render right side: following pages & exits */
-Piwik_Transitions.prototype.renderRightSide = function(onlyBg) {
- this.renderGroups(this.rightGroups, this.rightOpenGroup, 'right', onlyBg);
- this.renderExits(onlyBg);
+Piwik_Transitions.prototype.renderRightSide = function (onlyBg) {
+ this.renderGroups(this.rightGroups, this.rightOpenGroup, 'right', onlyBg);
+ this.renderExits(onlyBg);
- this.reRenderIfNeededToCenter('right', onlyBg);
+ this.reRenderIfNeededToCenter('right', onlyBg);
};
/** Helper method to render open and closed groups for both sides */
-Piwik_Transitions.prototype.renderGroups = function(groups, openGroup, side, onlyBg) {
- for (var i = 0; i < groups.length; i++) {
- var groupName = groups[i];
- if (groupName == openGroup) {
- if (i != 0) {
- var spacing = this.canvas.isNarrowMode() ? 7 : 13;
- this.canvas.addBoxSpacing(spacing, side);
- }
- this.renderOpenGroup(groupName, side, onlyBg);
- } else {
- this.renderClosedGroup(groupName, side, onlyBg);
- }
- }
-
- this.canvas.addBoxSpacing(13, side);
+Piwik_Transitions.prototype.renderGroups = function (groups, openGroup, side, onlyBg) {
+ for (var i = 0; i < groups.length; i++) {
+ var groupName = groups[i];
+ if (groupName == openGroup) {
+ if (i != 0) {
+ var spacing = this.canvas.isNarrowMode() ? 7 : 13;
+ this.canvas.addBoxSpacing(spacing, side);
+ }
+ this.renderOpenGroup(groupName, side, onlyBg);
+ } else {
+ this.renderClosedGroup(groupName, side, onlyBg);
+ }
+ }
+
+ this.canvas.addBoxSpacing(13, side);
};
/**
* If one side doesn't have much information, it doesn't look good to start from y=0.
* In this case, add some spacing on top and redraw.
*/
-Piwik_Transitions.prototype.reRenderIfNeededToCenter = function(side, onlyBg) {
- var height = (side == 'left' ? this.canvas.leftBoxPositionY : this.canvas.rightBoxPositionY) - 20;
- if (height < 460 && !this.reRendering) {
- var yOffset = (460 - height) / 2;
- this.canvas.clearSide(side, onlyBg);
- this.canvas.addBoxSpacing(yOffset, side);
- this.reRendering = true;
- side == 'left' ? this.renderLeftSide(onlyBg) : this.renderRightSide(onlyBg);
- this.reRendering = false;
- }
+Piwik_Transitions.prototype.reRenderIfNeededToCenter = function (side, onlyBg) {
+ var height = (side == 'left' ? this.canvas.leftBoxPositionY : this.canvas.rightBoxPositionY) - 20;
+ if (height < 460 && !this.reRendering) {
+ var yOffset = (460 - height) / 2;
+ this.canvas.clearSide(side, onlyBg);
+ this.canvas.addBoxSpacing(yOffset, side);
+ this.reRendering = true;
+ side == 'left' ? this.renderLeftSide(onlyBg) : this.renderRightSide(onlyBg);
+ this.reRendering = false;
+ }
};
/** Render the center box with the main metrics */
-Piwik_Transitions.prototype.renderCenterBox = function() {
- var box = this.centerBox;
-
- Piwik_Transitions_Util.replacePlaceholderInHtml(
- box.find('.Transitions_Pageviews'), this.model.pageviews);
-
- var self = this;
- var showMetric = function(cssClass, modelProperty, highlightCurveOnSide, groupCanBeExpanded) {
- var el = box.find('.Transitions_' + cssClass);
- Piwik_Transitions_Util.replacePlaceholderInHtml(el, self.model[modelProperty]);
-
- if (self.model[modelProperty] == 0) {
- el.addClass('Transitions_Value0');
- } else {
- self.addTooltipShowingPercentageOfAllPageviews(el, modelProperty);
- var groupName = cssClass.charAt(0).toLowerCase() + cssClass.substr(1);
- el.hover(function() {
- self.highlightGroup(groupName, highlightCurveOnSide);
- }, function() {
- self.unHighlightGroup(groupName, highlightCurveOnSide);
- });
- if (groupCanBeExpanded) {
- el.click(function() {
- self.openGroup(highlightCurveOnSide, groupName);
- }).css('cursor', 'pointer');
- }
- }
- };
-
- showMetric('DirectEntries', 'directEntries', 'left', false);
- showMetric('PreviousSiteSearches', 'previousSiteSearchesNbTransitions', 'left', true);
- showMetric('PreviousPages', 'previousPagesNbTransitions', 'left', true);
- showMetric('SearchEngines', 'searchEnginesNbTransitions', 'left', true);
- showMetric('Websites', 'websitesNbTransitions', 'left', true);
- showMetric('Campaigns', 'campaignsNbTransitions', 'left', true);
-
- showMetric('FollowingPages', 'followingPagesNbTransitions', 'right', true);
- showMetric('FollowingSiteSearches', 'followingSiteSearchesNbTransitions', 'right', true);
- showMetric('Outlinks', 'outlinksNbTransitions', 'right', true);
- showMetric('Downloads', 'downloadsNbTransitions', 'right', true);
- showMetric('Exits', 'exits', 'right', false);
-
- box.find('.Transitions_CenterBoxMetrics').show();
+Piwik_Transitions.prototype.renderCenterBox = function () {
+ var box = this.centerBox;
+
+ Piwik_Transitions_Util.replacePlaceholderInHtml(
+ box.find('.Transitions_Pageviews'), this.model.pageviews);
+
+ var self = this;
+ var showMetric = function (cssClass, modelProperty, highlightCurveOnSide, groupCanBeExpanded) {
+ var el = box.find('.Transitions_' + cssClass);
+ Piwik_Transitions_Util.replacePlaceholderInHtml(el, self.model[modelProperty]);
+
+ if (self.model[modelProperty] == 0) {
+ el.addClass('Transitions_Value0');
+ } else {
+ self.addTooltipShowingPercentageOfAllPageviews(el, modelProperty);
+ var groupName = cssClass.charAt(0).toLowerCase() + cssClass.substr(1);
+ el.hover(function () {
+ self.highlightGroup(groupName, highlightCurveOnSide);
+ }, function () {
+ self.unHighlightGroup(groupName, highlightCurveOnSide);
+ });
+ if (groupCanBeExpanded) {
+ el.click(function () {
+ self.openGroup(highlightCurveOnSide, groupName);
+ }).css('cursor', 'pointer');
+ }
+ }
+ };
+
+ showMetric('DirectEntries', 'directEntries', 'left', false);
+ showMetric('PreviousSiteSearches', 'previousSiteSearchesNbTransitions', 'left', true);
+ showMetric('PreviousPages', 'previousPagesNbTransitions', 'left', true);
+ showMetric('SearchEngines', 'searchEnginesNbTransitions', 'left', true);
+ showMetric('Websites', 'websitesNbTransitions', 'left', true);
+ showMetric('Campaigns', 'campaignsNbTransitions', 'left', true);
+
+ showMetric('FollowingPages', 'followingPagesNbTransitions', 'right', true);
+ showMetric('FollowingSiteSearches', 'followingSiteSearchesNbTransitions', 'right', true);
+ showMetric('Outlinks', 'outlinksNbTransitions', 'right', true);
+ showMetric('Downloads', 'downloadsNbTransitions', 'right', true);
+ showMetric('Exits', 'exits', 'right', false);
+
+ box.find('.Transitions_CenterBoxMetrics').show();
};
-Piwik_Transitions.prototype.addTooltipShowingPercentageOfAllPageviews = function(element, metric) {
- var self = this;
- element.hover(function() {
- var tip = Piwik_Transitions_Translations.XOfAllPageviews;
- var percentage = self.model.getPercentage(metric, true);
- tip = tip.replace(/%s/, '<b>' + percentage + '</b>');
- Piwik_Tooltip.show(tip, 'Transitions_Tooltip_Small');
- }, function() {
- Piwik_Tooltip.hide();
- });
+Piwik_Transitions.prototype.addTooltipShowingPercentageOfAllPageviews = function (element, metric) {
+ var self = this;
+ element.hover(function () {
+ var tip = Piwik_Transitions_Translations.XOfAllPageviews;
+ var percentage = self.model.getPercentage(metric, true);
+ tip = tip.replace(/%s/, '<b>' + percentage + '</b>');
+ Piwik_Tooltip.show(tip, 'Transitions_Tooltip_Small');
+ }, function () {
+ Piwik_Tooltip.hide();
+ });
};
/** Render the loops (i.e. page reloads) */
-Piwik_Transitions.prototype.renderLoops = function() {
- if (this.model.loops == 0) {
- return;
- }
+Piwik_Transitions.prototype.renderLoops = function () {
+ if (this.model.loops == 0) {
+ return;
+ }
- var loops = this.popover.find('#Transitions_Loops').show();
- Piwik_Transitions_Util.replacePlaceholderInHtml(loops, this.model.loops);
+ var loops = this.popover.find('#Transitions_Loops').show();
+ Piwik_Transitions_Util.replacePlaceholderInHtml(loops, this.model.loops);
- this.addTooltipShowingPercentageOfAllPageviews(loops, 'loops');
+ this.addTooltipShowingPercentageOfAllPageviews(loops, 'loops');
- this.canvas.renderLoops(this.model.getPercentage('loops'));
+ this.canvas.renderLoops(this.model.getPercentage('loops'));
};
-Piwik_Transitions.prototype.renderEntries = function(onlyBg) {
- if (this.model.directEntries > 0) {
- var self = this;
- var gradient = this.canvas.createHorizontalGradient('#FFFFFF', '#BACFE8', 'left');
- if (this.highlightedGroup == 'directEntries') {
- gradient = this.canvas.createHorizontalGradient('#FFFFFF', '#FAD293', 'left');
- }
- this.canvas.renderBox({
- side: 'left',
- onlyBg: onlyBg,
- share: this.model.getPercentage('directEntries'),
- gradient: gradient,
- boxText: Piwik_Transitions_Translations.directEntries,
- boxTextNumLines: 1,
- boxTextCssClass: 'SingleLine',
- smallBox: true,
- onMouseOver: function() {
- self.highlightGroup('directEntries', 'left');
- },
- onMouseOut: function() {
- self.unHighlightGroup('directEntries', 'left');
- }
- });
- this.canvas.addBoxSpacing(20, 'left');
- }
+Piwik_Transitions.prototype.renderEntries = function (onlyBg) {
+ if (this.model.directEntries > 0) {
+ var self = this;
+ var gradient = this.canvas.createHorizontalGradient('#FFFFFF', '#BACFE8', 'left');
+ if (this.highlightedGroup == 'directEntries') {
+ gradient = this.canvas.createHorizontalGradient('#FFFFFF', '#FAD293', 'left');
+ }
+ this.canvas.renderBox({
+ side: 'left',
+ onlyBg: onlyBg,
+ share: this.model.getPercentage('directEntries'),
+ gradient: gradient,
+ boxText: Piwik_Transitions_Translations.directEntries,
+ boxTextNumLines: 1,
+ boxTextCssClass: 'SingleLine',
+ smallBox: true,
+ onMouseOver: function () {
+ self.highlightGroup('directEntries', 'left');
+ },
+ onMouseOut: function () {
+ self.unHighlightGroup('directEntries', 'left');
+ }
+ });
+ this.canvas.addBoxSpacing(20, 'left');
+ }
};
-Piwik_Transitions.prototype.renderExits = function(onlyBg) {
- if (this.model.exits > 0) {
- var self = this;
- var gradient = this.canvas.createHorizontalGradient('#FFFFFF', '#BACFE8', 'right');
- if (this.highlightedGroup == 'exits') {
- gradient = this.canvas.createHorizontalGradient('#FFFFFF', '#FAD293', 'right');
- }
- this.canvas.renderBox({
- side: 'right',
- onlyBg: onlyBg,
- share: this.model.getPercentage('exits'),
- gradient: gradient,
- boxText: Piwik_Transitions_Translations.exits,
- boxTextNumLines: 1,
- boxTextCssClass: 'SingleLine',
- smallBox: true,
- onMouseOver: function() {
- self.highlightGroup('exits', 'right');
- },
- onMouseOut: function() {
- self.unHighlightGroup('exits', 'right');
- }
- });
- this.canvas.addBoxSpacing(20, 'right');
- }
+Piwik_Transitions.prototype.renderExits = function (onlyBg) {
+ if (this.model.exits > 0) {
+ var self = this;
+ var gradient = this.canvas.createHorizontalGradient('#FFFFFF', '#BACFE8', 'right');
+ if (this.highlightedGroup == 'exits') {
+ gradient = this.canvas.createHorizontalGradient('#FFFFFF', '#FAD293', 'right');
+ }
+ this.canvas.renderBox({
+ side: 'right',
+ onlyBg: onlyBg,
+ share: this.model.getPercentage('exits'),
+ gradient: gradient,
+ boxText: Piwik_Transitions_Translations.exits,
+ boxTextNumLines: 1,
+ boxTextCssClass: 'SingleLine',
+ smallBox: true,
+ onMouseOver: function () {
+ self.highlightGroup('exits', 'right');
+ },
+ onMouseOut: function () {
+ self.unHighlightGroup('exits', 'right');
+ }
+ });
+ this.canvas.addBoxSpacing(20, 'right');
+ }
};
/** Render the open group with the detailed data */
-Piwik_Transitions.prototype.renderOpenGroup = function(groupName, side, onlyBg) {
- var self = this;
-
- // get data from the model
- var nbTransitionsVarName = groupName + 'NbTransitions';
- var nbTransitions = self.model[nbTransitionsVarName];
- if (nbTransitions == 0) {
- return;
- }
-
- var totalShare = this.model.getPercentage(nbTransitionsVarName);
- var details = self.model.getDetailsForGroup(groupName);
-
- // prepare gradients
- var gradientItems = this.canvas.createHorizontalGradient('#E3DFD1', '#E8E4D5', side);
- var gradientOthers = this.canvas.createHorizontalGradient('#F5F3EB', '#E8E4D5', side);
- var gradientBackground = this.canvas.createHorizontalGradient('#FFFFFF', '#BACFE8', side);
- if (groupName == this.highlightedGroup) {
- gradientBackground = this.canvas.createHorizontalGradient('#FFFFFF', '#FAD293', side);
- }
-
- // remember current offsets to reset them later for drawing the background
- var boxPositionBefore, curvePositionBefore;
- if (side == 'left') {
- boxPositionBefore = this.canvas.leftBoxPositionY;
- curvePositionBefore = this.canvas.leftCurvePositionY;
- } else {
- boxPositionBefore = this.canvas.rightBoxPositionY;
- curvePositionBefore = this.canvas.rightCurvePositionY;
- }
-
- // headline of the open group
- var titleX, titleClass;
- if (side == 'left') {
- titleX = this.canvas.leftBoxBeginX + 10;
- titleClass = 'BoxTextLeft';
- } else {
- titleX = this.canvas.rightBoxBeginX - 1;
- titleClass = 'BoxTextRight';
- }
- if (!onlyBg) {
- var groupTitle = self.model.getGroupTitle(groupName);
- var titleEl = this.canvas.renderText(groupTitle, titleX, boxPositionBefore + 11,
- [titleClass, 'TitleOfOpenGroup']);
- titleEl.hover(function() {
- self.highlightGroup(groupName, side);
- }, function() {
- self.unHighlightGroup(groupName, side);
- });
- }
- this.canvas.addBoxSpacing(34, side);
-
- // draw detail boxes
- for (var i = 0; i < details.length; i++) {
- var data = details[i];
- var label = (typeof data.url != 'undefined' ? data.url : data.label);
- label = (typeof label != 'undefined' && label !== null ? label : '');
- var isOthers = (label == 'Others');
- var onClick = false;
- if (!isOthers && (groupName == 'previousPages' || groupName == 'followingPages')) {
- onClick = (function(url) {
- return function() {
- self.reloadPopover(url);
- };
- })(label);
- } else if (!isOthers && (groupName == 'outlinks' || groupName == 'websites' || groupName == 'downloads')) {
- onClick = (function(url) {
- return function() {
- self.openExternalUrl(url);
- };
- })(label);
- }
-
- var tooltip = Piwik_Transitions_Translations.XOfY;
- tooltip = '<b>' + tooltip.replace(/%s/, data.referrals + '</b>').replace(/%s/, nbTransitions);
- tooltip = this.model.getShareInGroupTooltip(tooltip, groupName);
-
- var fullLabel = label;
- var shortened = false;
- if ((this.actionType == 'url' && (groupName == 'previousPages' || groupName == 'followingPages'))
- || groupName == 'downloads') {
- // remove http + www + domain for internal URLs
- label = Piwik_Transitions_Util.shortenUrl(label, true);
- shortened = true;
- } else if (groupName == 'outlinks' || groupName == 'websites') {
- // remove http + www for external URLs
- label = Piwik_Transitions_Util.shortenUrl(label);
- shortened = true;
- }
-
- this.canvas.renderBox({
- side: side,
- onlyBg: onlyBg,
- share: data.percentage / 100 * totalShare,
- gradient: isOthers ? gradientOthers : gradientItems,
- boxText: label,
- boxTextTooltip: isOthers || !shortened ? false : fullLabel,
- boxTextNumLines: 3,
- curveText: data.percentage + '%',
- curveTextTooltip: tooltip,
- onClick: onClick
- });
- }
-
- // draw background
- var boxPositionAfter, curvePositionAfter;
- if (side == 'left') {
- boxPositionAfter = this.canvas.leftBoxPositionY;
- curvePositionAfter = this.canvas.leftCurvePositionY;
- this.canvas.leftBoxPositionY = boxPositionBefore;
- this.canvas.leftCurvePositionY = curvePositionBefore;
- } else {
- boxPositionAfter = this.canvas.rightBoxPositionY;
- curvePositionAfter = this.canvas.rightCurvePositionY;
- this.canvas.rightBoxPositionY = boxPositionBefore;
- this.canvas.rightCurvePositionY = curvePositionBefore;
- }
-
- this.canvas.renderBox({
- side: side,
- boxHeight: boxPositionAfter - boxPositionBefore - this.canvas.boxSpacing - 2,
- curveHeight: curvePositionAfter - curvePositionBefore - this.canvas.curveSpacing,
- gradient: gradientBackground,
- bgCanvas: true
- });
-
- var spacing = this.canvas.isNarrowMode() ? 8 : 15;
- this.canvas.addBoxSpacing(spacing, side);
+Piwik_Transitions.prototype.renderOpenGroup = function (groupName, side, onlyBg) {
+ var self = this;
+
+ // get data from the model
+ var nbTransitionsVarName = groupName + 'NbTransitions';
+ var nbTransitions = self.model[nbTransitionsVarName];
+ if (nbTransitions == 0) {
+ return;
+ }
+
+ var totalShare = this.model.getPercentage(nbTransitionsVarName);
+ var details = self.model.getDetailsForGroup(groupName);
+
+ // prepare gradients
+ var gradientItems = this.canvas.createHorizontalGradient('#E3DFD1', '#E8E4D5', side);
+ var gradientOthers = this.canvas.createHorizontalGradient('#F5F3EB', '#E8E4D5', side);
+ var gradientBackground = this.canvas.createHorizontalGradient('#FFFFFF', '#BACFE8', side);
+ if (groupName == this.highlightedGroup) {
+ gradientBackground = this.canvas.createHorizontalGradient('#FFFFFF', '#FAD293', side);
+ }
+
+ // remember current offsets to reset them later for drawing the background
+ var boxPositionBefore, curvePositionBefore;
+ if (side == 'left') {
+ boxPositionBefore = this.canvas.leftBoxPositionY;
+ curvePositionBefore = this.canvas.leftCurvePositionY;
+ } else {
+ boxPositionBefore = this.canvas.rightBoxPositionY;
+ curvePositionBefore = this.canvas.rightCurvePositionY;
+ }
+
+ // headline of the open group
+ var titleX, titleClass;
+ if (side == 'left') {
+ titleX = this.canvas.leftBoxBeginX + 10;
+ titleClass = 'BoxTextLeft';
+ } else {
+ titleX = this.canvas.rightBoxBeginX - 1;
+ titleClass = 'BoxTextRight';
+ }
+ if (!onlyBg) {
+ var groupTitle = self.model.getGroupTitle(groupName);
+ var titleEl = this.canvas.renderText(groupTitle, titleX, boxPositionBefore + 11,
+ [titleClass, 'TitleOfOpenGroup']);
+ titleEl.hover(function () {
+ self.highlightGroup(groupName, side);
+ }, function () {
+ self.unHighlightGroup(groupName, side);
+ });
+ }
+ this.canvas.addBoxSpacing(34, side);
+
+ // draw detail boxes
+ for (var i = 0; i < details.length; i++) {
+ var data = details[i];
+ var label = (typeof data.url != 'undefined' ? data.url : data.label);
+ label = (typeof label != 'undefined' && label !== null ? label : '');
+ var isOthers = (label == 'Others');
+ var onClick = false;
+ if (!isOthers && (groupName == 'previousPages' || groupName == 'followingPages')) {
+ onClick = (function (url) {
+ return function () {
+ self.reloadPopover(url);
+ };
+ })(label);
+ } else if (!isOthers && (groupName == 'outlinks' || groupName == 'websites' || groupName == 'downloads')) {
+ onClick = (function (url) {
+ return function () {
+ self.openExternalUrl(url);
+ };
+ })(label);
+ }
+
+ var tooltip = Piwik_Transitions_Translations.XOfY;
+ tooltip = '<b>' + tooltip.replace(/%s/, data.referrals + '</b>').replace(/%s/, nbTransitions);
+ tooltip = this.model.getShareInGroupTooltip(tooltip, groupName);
+
+ var fullLabel = label;
+ var shortened = false;
+ if ((this.actionType == 'url' && (groupName == 'previousPages' || groupName == 'followingPages'))
+ || groupName == 'downloads') {
+ // remove http + www + domain for internal URLs
+ label = Piwik_Transitions_Util.shortenUrl(label, true);
+ shortened = true;
+ } else if (groupName == 'outlinks' || groupName == 'websites') {
+ // remove http + www for external URLs
+ label = Piwik_Transitions_Util.shortenUrl(label);
+ shortened = true;
+ }
+
+ this.canvas.renderBox({
+ side: side,
+ onlyBg: onlyBg,
+ share: data.percentage / 100 * totalShare,
+ gradient: isOthers ? gradientOthers : gradientItems,
+ boxText: label,
+ boxTextTooltip: isOthers || !shortened ? false : fullLabel,
+ boxTextNumLines: 3,
+ curveText: data.percentage + '%',
+ curveTextTooltip: tooltip,
+ onClick: onClick
+ });
+ }
+
+ // draw background
+ var boxPositionAfter, curvePositionAfter;
+ if (side == 'left') {
+ boxPositionAfter = this.canvas.leftBoxPositionY;
+ curvePositionAfter = this.canvas.leftCurvePositionY;
+ this.canvas.leftBoxPositionY = boxPositionBefore;
+ this.canvas.leftCurvePositionY = curvePositionBefore;
+ } else {
+ boxPositionAfter = this.canvas.rightBoxPositionY;
+ curvePositionAfter = this.canvas.rightCurvePositionY;
+ this.canvas.rightBoxPositionY = boxPositionBefore;
+ this.canvas.rightCurvePositionY = curvePositionBefore;
+ }
+
+ this.canvas.renderBox({
+ side: side,
+ boxHeight: boxPositionAfter - boxPositionBefore - this.canvas.boxSpacing - 2,
+ curveHeight: curvePositionAfter - curvePositionBefore - this.canvas.curveSpacing,
+ gradient: gradientBackground,
+ bgCanvas: true
+ });
+
+ var spacing = this.canvas.isNarrowMode() ? 8 : 15;
+ this.canvas.addBoxSpacing(spacing, side);
};
/** Render a closed group without detailed data, only one box for the sum */
-Piwik_Transitions.prototype.renderClosedGroup = function(groupName, side, onlyBg) {
- var self = this;
- var gradient = this.canvas.createHorizontalGradient('#DDE4ED', '#9BBADE', side);
- if (groupName == this.highlightedGroup) {
- gradient = this.canvas.createHorizontalGradient('#FAE2C0', '#FAD293', side);
- }
-
- var nbTransitionsVarName = groupName + 'NbTransitions';
-
- if (self.model[nbTransitionsVarName] == 0) {
- return;
- }
-
- self.canvas.renderBox({
- side: side,
- onlyBg: onlyBg,
- share: self.model.getPercentage(nbTransitionsVarName),
- gradient: gradient,
- boxText: self.model.getGroupTitle(groupName),
- boxTextNumLines: 1,
- boxTextCssClass: 'SingleLine',
- boxIcon: 'themes/default/images/plus_blue.png',
- smallBox: true,
- onClick: function() {
- self.unHighlightGroup(groupName, side);
- self.openGroup(side, groupName);
- },
- onMouseOver: function() {
- self.highlightGroup(groupName, side);
- },
- onMouseOut: function() {
- self.unHighlightGroup(groupName, side);
- }
- });
+Piwik_Transitions.prototype.renderClosedGroup = function (groupName, side, onlyBg) {
+ var self = this;
+ var gradient = this.canvas.createHorizontalGradient('#DDE4ED', '#9BBADE', side);
+ if (groupName == this.highlightedGroup) {
+ gradient = this.canvas.createHorizontalGradient('#FAE2C0', '#FAD293', side);
+ }
+
+ var nbTransitionsVarName = groupName + 'NbTransitions';
+
+ if (self.model[nbTransitionsVarName] == 0) {
+ return;
+ }
+
+ self.canvas.renderBox({
+ side: side,
+ onlyBg: onlyBg,
+ share: self.model.getPercentage(nbTransitionsVarName),
+ gradient: gradient,
+ boxText: self.model.getGroupTitle(groupName),
+ boxTextNumLines: 1,
+ boxTextCssClass: 'SingleLine',
+ boxIcon: 'themes/default/images/plus_blue.png',
+ smallBox: true,
+ onClick: function () {
+ self.unHighlightGroup(groupName, side);
+ self.openGroup(side, groupName);
+ },
+ onMouseOver: function () {
+ self.highlightGroup(groupName, side);
+ },
+ onMouseOut: function () {
+ self.unHighlightGroup(groupName, side);
+ }
+ });
};
/** Reload the entire popover for a different URL */
-Piwik_Transitions.prototype.reloadPopover = function(url) {
- if (this.rowAction) {
- this.rowAction.openPopover(this.actionType + ':' + url);
- } else {
- this.reset(this.actionType, url);
- this.showPopover();
- }
+Piwik_Transitions.prototype.reloadPopover = function (url) {
+ if (this.rowAction) {
+ this.rowAction.openPopover(this.actionType + ':' + url);
+ } else {
+ this.reset(this.actionType, url);
+ this.showPopover();
+ }
};
/** Redraw the left or right sides with a different group opened */
-Piwik_Transitions.prototype.openGroup = function(side, groupName) {
+Piwik_Transitions.prototype.openGroup = function (side, groupName) {
- this.canvas.clearSide(side);
+ this.canvas.clearSide(side);
- if (side == 'left') {
- this.leftOpenGroup = groupName;
- this.renderLeftSide();
- } else {
- this.rightOpenGroup = groupName;
- this.renderRightSide();
- }
+ if (side == 'left') {
+ this.leftOpenGroup = groupName;
+ this.renderLeftSide();
+ } else {
+ this.rightOpenGroup = groupName;
+ this.renderRightSide();
+ }
- this.renderLoops();
+ this.renderLoops();
- this.canvas.truncateVisibleBoxTexts();
+ this.canvas.truncateVisibleBoxTexts();
};
/** Highlight a group: change curve color and highlight metric in the center box */
-Piwik_Transitions.prototype.highlightGroup = function(groupName, side) {
- if (this.highlightedGroup == groupName) {
- return;
- }
- if (this.highlightedGroup !== false) {
- this.unHighlightGroup(this.highlightedGroup, this.highlightedGroupSide);
- }
-
- this.highlightedGroup = groupName;
- this.highlightedGroupSide = side;
-
- var cssClass = 'Transitions_' + groupName.charAt(0).toUpperCase() + groupName.substr(1);
- this.highlightedGroupCenterEl = this.canvas.container.find('.' + cssClass);
- this.highlightedGroupCenterEl.addClass('Transitions_Highlighted');
-
- this.canvas.clearSide(side, true);
- if (side == 'left') {
- this.renderLeftSide(true);
- } else {
- this.renderRightSide(true);
- }
- this.renderLoops();
+Piwik_Transitions.prototype.highlightGroup = function (groupName, side) {
+ if (this.highlightedGroup == groupName) {
+ return;
+ }
+ if (this.highlightedGroup !== false) {
+ this.unHighlightGroup(this.highlightedGroup, this.highlightedGroupSide);
+ }
+
+ this.highlightedGroup = groupName;
+ this.highlightedGroupSide = side;
+
+ var cssClass = 'Transitions_' + groupName.charAt(0).toUpperCase() + groupName.substr(1);
+ this.highlightedGroupCenterEl = this.canvas.container.find('.' + cssClass);
+ this.highlightedGroupCenterEl.addClass('Transitions_Highlighted');
+
+ this.canvas.clearSide(side, true);
+ if (side == 'left') {
+ this.renderLeftSide(true);
+ } else {
+ this.renderRightSide(true);
+ }
+ this.renderLoops();
};
/** Remove highlight after using highlightGroup() */
-Piwik_Transitions.prototype.unHighlightGroup = function(groupName, side) {
- if (this.highlightedGroup === false) {
- return;
- }
-
- this.highlightedGroupCenterEl.removeClass('Transitions_Highlighted');
-
- this.highlightedGroup = false;
- this.highlightedGroupSide = false;
- this.highlightedGroupCenterEl = false;
-
- this.canvas.clearSide(side, true);
- if (side == 'left') {
- this.renderLeftSide(true);
- } else {
- this.renderRightSide(true);
- }
- this.renderLoops();
+Piwik_Transitions.prototype.unHighlightGroup = function (groupName, side) {
+ if (this.highlightedGroup === false) {
+ return;
+ }
+
+ this.highlightedGroupCenterEl.removeClass('Transitions_Highlighted');
+
+ this.highlightedGroup = false;
+ this.highlightedGroupSide = false;
+ this.highlightedGroupCenterEl = false;
+
+ this.canvas.clearSide(side, true);
+ if (side == 'left') {
+ this.renderLeftSide(true);
+ } else {
+ this.renderRightSide(true);
+ }
+ this.renderLoops();
};
/** Open a link in a new tab */
-Piwik_Transitions.prototype.openExternalUrl = function(url) {
- url = piwik.piwik_url + '?module=Proxy&action=redirect&url=' + encodeURIComponent(url);
- window.open(url, '_newtab');
+Piwik_Transitions.prototype.openExternalUrl = function (url) {
+ url = piwik.piwik_url + '?module=Proxy&action=redirect&url=' + encodeURIComponent(url);
+ window.open(url, '_newtab');
};
@@ -710,151 +710,151 @@ Piwik_Transitions.prototype.openExternalUrl = function(url) {
function Piwik_Transitions_Canvas(canvasBgLeft, canvasBgRight, canvasLeft, canvasRight, canvasLoops, width, height) {
- if (typeof window.G_vmlCanvasManager != "undefined") {
- window.G_vmlCanvasManager.initElement(canvasBgLeft);
- window.G_vmlCanvasManager.initElement(canvasBgRight);
- window.G_vmlCanvasManager.initElement(canvasLeft);
- window.G_vmlCanvasManager.initElement(canvasRight);
- window.G_vmlCanvasManager.initElement(canvasLoops);
- }
-
- if (!canvasBgLeft.getContext) {
- alert('Your browser is not supported.');
- return;
- }
-
- /** DOM element that contains the canvas */
- this.container = $(canvasBgLeft).parent().parent();
-
- /** Drawing context of the canvases */
- this.contextBgLeft = canvasBgLeft.getContext('2d');
- this.contextBgRight = canvasBgRight.getContext('2d');
- this.contextLeft = canvasLeft.getContext('2d');
- this.contextRight = canvasRight.getContext('2d');
- this.contextLoops = canvasLoops.getContext('2d');
-
- /** Width of the entire canvas */
- this.width = width;
- /** Height of the entire canvas */
- this.height = height;
-
- /** Current Y positions */
- this.leftBoxPositionY = this.originalBoxPositionY = 0;
- this.leftCurvePositionY = this.originalCurvePositionY = 110;
- this.rightBoxPositionY = this.originalBoxPositionY;
- this.rightCurvePositionY = this.originalCurvePositionY;
-
- /** Width of the rectangular box */
- this.boxWidth = 175;
- /** Height of the rectangular box */
- this.boxHeight = 53;
- /** Height of a smaller rectangular box */
- this.smallBoxHeight = 30;
- /** Width of the curve that connects the boxes to the center */
- this.curveWidth = 170;
- /** Line-height of the text */
- this.lineHeight = 14;
- /** Spacing between rectangular boxes */
- this.boxSpacing = 7;
- /** Spacing between the curves where they connect to the center */
- this.curveSpacing = 1.5;
-
- /** The total net height (without curve spacing) of the curves as they connect to the center */
- this.totalHeightOfConnections = 205;
-
- /** X positions of the left box - begin means left, end means right */
- this.leftBoxBeginX = 0;
- this.leftCurveBeginX = this.leftBoxBeginX + this.boxWidth;
- this.leftCurveEndX = this.leftCurveBeginX + this.curveWidth;
-
- /** X positions of the right box - begin means left, end means right */
- this.rightBoxEndX = this.width;
- this.rightBoxBeginX = this.rightCurveEndX = this.rightBoxEndX - this.boxWidth;
- this.rightCurveBeginX = this.rightCurveEndX - this.curveWidth;
+ if (typeof window.G_vmlCanvasManager != "undefined") {
+ window.G_vmlCanvasManager.initElement(canvasBgLeft);
+ window.G_vmlCanvasManager.initElement(canvasBgRight);
+ window.G_vmlCanvasManager.initElement(canvasLeft);
+ window.G_vmlCanvasManager.initElement(canvasRight);
+ window.G_vmlCanvasManager.initElement(canvasLoops);
+ }
+
+ if (!canvasBgLeft.getContext) {
+ alert('Your browser is not supported.');
+ return;
+ }
+
+ /** DOM element that contains the canvas */
+ this.container = $(canvasBgLeft).parent().parent();
+
+ /** Drawing context of the canvases */
+ this.contextBgLeft = canvasBgLeft.getContext('2d');
+ this.contextBgRight = canvasBgRight.getContext('2d');
+ this.contextLeft = canvasLeft.getContext('2d');
+ this.contextRight = canvasRight.getContext('2d');
+ this.contextLoops = canvasLoops.getContext('2d');
+
+ /** Width of the entire canvas */
+ this.width = width;
+ /** Height of the entire canvas */
+ this.height = height;
+
+ /** Current Y positions */
+ this.leftBoxPositionY = this.originalBoxPositionY = 0;
+ this.leftCurvePositionY = this.originalCurvePositionY = 110;
+ this.rightBoxPositionY = this.originalBoxPositionY;
+ this.rightCurvePositionY = this.originalCurvePositionY;
+
+ /** Width of the rectangular box */
+ this.boxWidth = 175;
+ /** Height of the rectangular box */
+ this.boxHeight = 53;
+ /** Height of a smaller rectangular box */
+ this.smallBoxHeight = 30;
+ /** Width of the curve that connects the boxes to the center */
+ this.curveWidth = 170;
+ /** Line-height of the text */
+ this.lineHeight = 14;
+ /** Spacing between rectangular boxes */
+ this.boxSpacing = 7;
+ /** Spacing between the curves where they connect to the center */
+ this.curveSpacing = 1.5;
+
+ /** The total net height (without curve spacing) of the curves as they connect to the center */
+ this.totalHeightOfConnections = 205;
+
+ /** X positions of the left box - begin means left, end means right */
+ this.leftBoxBeginX = 0;
+ this.leftCurveBeginX = this.leftBoxBeginX + this.boxWidth;
+ this.leftCurveEndX = this.leftCurveBeginX + this.curveWidth;
+
+ /** X positions of the right box - begin means left, end means right */
+ this.rightBoxEndX = this.width;
+ this.rightBoxBeginX = this.rightCurveEndX = this.rightBoxEndX - this.boxWidth;
+ this.rightCurveBeginX = this.rightCurveEndX - this.curveWidth;
}
/**
* Activate narrow mode: draw groups a bit more compact in order to save space
* for more than 3 referrer groups.
*/
-Piwik_Transitions_Canvas.prototype.narrowMode = function() {
- this.smallBoxHeight = 26;
- this.boxSpacing = 4;
- this.narrowMode = true;
+Piwik_Transitions_Canvas.prototype.narrowMode = function () {
+ this.smallBoxHeight = 26;
+ this.boxSpacing = 4;
+ this.narrowMode = true;
};
-Piwik_Transitions_Canvas.prototype.isNarrowMode = function() {
- return typeof this.narrowMode != 'undefined';
+Piwik_Transitions_Canvas.prototype.isNarrowMode = function () {
+ return typeof this.narrowMode != 'undefined';
};
/**
* Helper to create horizontal gradients
* @param position left|right
*/
-Piwik_Transitions_Canvas.prototype.createHorizontalGradient = function(lightColor, darkColor, position) {
- var fromX, toX, fromColor, toColor;
-
- if (position == 'left') {
- // gradient is used to fill a box on the left
- fromX = this.leftBoxBeginX + 50;
- toX = this.leftCurveEndX - 20;
- fromColor = lightColor;
- toColor = darkColor;
- } else {
- // gradient is used to fill a box on the right
- fromX = this.rightCurveBeginX + 20;
- toX = this.rightBoxEndX - 50;
- fromColor = darkColor;
- toColor = lightColor;
- }
-
- var gradient = this.contextBgLeft.createLinearGradient(fromX, 0, toX, 0);
- gradient.addColorStop(0, fromColor);
- gradient.addColorStop(1, toColor);
-
- return gradient;
+Piwik_Transitions_Canvas.prototype.createHorizontalGradient = function (lightColor, darkColor, position) {
+ var fromX, toX, fromColor, toColor;
+
+ if (position == 'left') {
+ // gradient is used to fill a box on the left
+ fromX = this.leftBoxBeginX + 50;
+ toX = this.leftCurveEndX - 20;
+ fromColor = lightColor;
+ toColor = darkColor;
+ } else {
+ // gradient is used to fill a box on the right
+ fromX = this.rightCurveBeginX + 20;
+ toX = this.rightBoxEndX - 50;
+ fromColor = darkColor;
+ toColor = lightColor;
+ }
+
+ var gradient = this.contextBgLeft.createLinearGradient(fromX, 0, toX, 0);
+ gradient.addColorStop(0, fromColor);
+ gradient.addColorStop(1, toColor);
+
+ return gradient;
};
/** Render text using a div inside the container */
-Piwik_Transitions_Canvas.prototype.renderText = function(text, x, y, cssClass, onClick, icon, maxLines) {
- var div = this.addDomElement('div', 'Text');
- div.css({
- left: x + 'px',
- top: y + 'px'
- });
- if (icon) {
- div.addClass('Transitions_HasBackground');
- div.css({backgroundImage: 'url(' + icon + ')'});
- }
- if (cssClass) {
- if (typeof cssClass == 'object') {
- for (var i = 0; i < cssClass.length; i++) {
- div.addClass('Transitions_' + cssClass[i]);
- }
- } else {
- div.addClass('Transitions_' + cssClass);
- }
- }
- if (onClick) {
- div.css('cursor', 'pointer').hover(function() {
- $(this).addClass('Transitions_Hover');
- },function() {
- $(this).removeClass('Transitions_Hover');
- }).click(onClick);
- }
- if (maxLines) {
- div.addClass('Transitions_ApplyTextAndTruncate').data('text', text);
- } else {
- div.html(text);
- }
- return div;
+Piwik_Transitions_Canvas.prototype.renderText = function (text, x, y, cssClass, onClick, icon, maxLines) {
+ var div = this.addDomElement('div', 'Text');
+ div.css({
+ left: x + 'px',
+ top: y + 'px'
+ });
+ if (icon) {
+ div.addClass('Transitions_HasBackground');
+ div.css({backgroundImage: 'url(' + icon + ')'});
+ }
+ if (cssClass) {
+ if (typeof cssClass == 'object') {
+ for (var i = 0; i < cssClass.length; i++) {
+ div.addClass('Transitions_' + cssClass[i]);
+ }
+ } else {
+ div.addClass('Transitions_' + cssClass);
+ }
+ }
+ if (onClick) {
+ div.css('cursor', 'pointer').hover(function () {
+ $(this).addClass('Transitions_Hover');
+ },function () {
+ $(this).removeClass('Transitions_Hover');
+ }).click(onClick);
+ }
+ if (maxLines) {
+ div.addClass('Transitions_ApplyTextAndTruncate').data('text', text);
+ } else {
+ div.html(text);
+ }
+ return div;
};
/** Add a DOM element inside the container (as a sibling of the canvas) */
-Piwik_Transitions_Canvas.prototype.addDomElement = function(tagName, cssClass) {
- var el = $(document.createElement('div')).addClass('Transitions_' + cssClass);
- this.container.append(el);
- return el;
+Piwik_Transitions_Canvas.prototype.addDomElement = function (tagName, cssClass) {
+ var el = $(document.createElement('div')).addClass('Transitions_' + cssClass);
+ this.container.append(el);
+ return el;
};
/**
@@ -862,36 +862,36 @@ Piwik_Transitions_Canvas.prototype.addDomElement = function(tagName, cssClass) {
* This method looks for the class Transitions_ApplyTextAndTruncate.
* It then looks up data-text and truncates it until it fits.
*/
-Piwik_Transitions_Canvas.prototype.truncateVisibleBoxTexts = function() {
- this.container.find('.Transitions_ApplyTextAndTruncate').each(function() {
- var container = $(this).html('<span>');
- var span = container.find('span');
-
- var text = container.data('text');
- span.html(piwikHelper.addBreakpointsToUrl(text));
-
- var divHeight = container.innerHeight();
- if (container.data('maxLines')) {
- divHeight = container.data('maxLines') * (parseInt(container.css('lineHeight'), 10) + .2);
- }
-
- var leftPart = false;
- var rightPart = false;
-
- while (divHeight < span.outerHeight()) {
- if (leftPart === false) {
- var middle = Math.round(text.length / 2);
- leftPart = text.substring(0, middle);
- rightPart = text.substring(middle, text.length);
- }
- leftPart = leftPart.substring(0, leftPart.length - 2);
- rightPart = rightPart.substring(2, rightPart.length);
- text = leftPart + '...' + rightPart;
- span.html(piwikHelper.addBreakpointsToUrl(text));
- }
-
- span.removeClass('Transitions_Truncate');
- });
+Piwik_Transitions_Canvas.prototype.truncateVisibleBoxTexts = function () {
+ this.container.find('.Transitions_ApplyTextAndTruncate').each(function () {
+ var container = $(this).html('<span>');
+ var span = container.find('span');
+
+ var text = container.data('text');
+ span.html(piwikHelper.addBreakpointsToUrl(text));
+
+ var divHeight = container.innerHeight();
+ if (container.data('maxLines')) {
+ divHeight = container.data('maxLines') * (parseInt(container.css('lineHeight'), 10) + .2);
+ }
+
+ var leftPart = false;
+ var rightPart = false;
+
+ while (divHeight < span.outerHeight()) {
+ if (leftPart === false) {
+ var middle = Math.round(text.length / 2);
+ leftPart = text.substring(0, middle);
+ rightPart = text.substring(middle, text.length);
+ }
+ leftPart = leftPart.substring(0, leftPart.length - 2);
+ rightPart = rightPart.substring(2, rightPart.length);
+ text = leftPart + '...' + rightPart;
+ span.html(piwikHelper.addBreakpointsToUrl(text));
+ }
+
+ span.removeClass('Transitions_Truncate');
+ });
};
/**
@@ -920,268 +920,268 @@ Piwik_Transitions_Canvas.prototype.truncateVisibleBoxTexts = function() {
* boxHeight: fix box height in px
* bgCanvas: true to draw on background canvas
*/
-Piwik_Transitions_Canvas.prototype.renderBox = function(params) {
- var curveHeight = params.curveHeight ? params.curveHeight :
- Math.round(this.totalHeightOfConnections * params.share);
- curveHeight = Math.max(curveHeight, 1);
-
- var boxHeight = this.boxHeight;
- if (params.smallBox) {
- boxHeight = this.smallBoxHeight;
- }
- if (params.boxHeight) {
- boxHeight = params.boxHeight;
- }
-
- var context;
- if (params.bgCanvas) {
- context = params.side == 'left' ? this.contextBgLeft : this.contextBgRight;
- } else {
- context = params.side == 'left' ? this.contextLeft : this.contextRight;
- }
-
- // background
- context.fillStyle = params.gradient;
- context.beginPath();
- if (params.side == 'left') {
- this.renderLeftBoxBg(context, boxHeight, curveHeight);
- } else {
- this.renderRightBoxBg(context, boxHeight, curveHeight);
- }
- if (typeof context.endPath == 'function') {
- context.endPath();
- }
-
- // text inside the box
- if (params.boxText && !params.onlyBg) {
- var onClick = typeof params.onClick == 'function' ? params.onClick : false;
- var boxTextLeft, boxTextTop, el;
- if (params.side == 'left') {
- boxTextLeft = this.leftBoxBeginX + 10;
- boxTextTop = this.leftBoxPositionY + boxHeight / 2 - params.boxTextNumLines * this.lineHeight / 2;
- el = this.renderText(params.boxText, boxTextLeft, boxTextTop, 'BoxTextLeft', onClick, params.boxIcon,
- params.boxTextNumLines);
- } else {
- boxTextLeft = this.rightBoxBeginX;
- boxTextTop = this.rightBoxPositionY + boxHeight / 2 - params.boxTextNumLines * this.lineHeight / 2;
- el = this.renderText(params.boxText, boxTextLeft, boxTextTop, 'BoxTextRight', onClick, params.boxIcon,
- params.boxTextNumLines);
- }
- if (params.boxTextCssClass) {
- el.addClass('Transitions_' + params.boxTextCssClass);
- }
- // tooltip
- if (params.boxTextTooltip) {
- el.hover(function() {
- var tip = piwikHelper.addBreakpointsToUrl(params.boxTextTooltip);
- Piwik_Tooltip.show(tip, 'Transitions_Tooltip_Small', 300);
- }, function() {
- Piwik_Tooltip.hide();
- });
- if (onClick) {
- el.click(function() {
- Piwik_Tooltip.hide();
- });
- }
- }
- if (typeof params.onMouseOver == 'function') {
- el.mouseenter(params.onMouseOver);
- }
- if (typeof params.onMouseOut == 'function') {
- el.mouseleave(params.onMouseOut);
- }
- }
-
- // text at the beginning of the curve
- if (params.curveText && !params.onlyBg) {
- var curveTextLeft, curveTextTop;
- if (params.side == 'left') {
- curveTextLeft = this.leftBoxBeginX + this.boxWidth + 3;
- curveTextTop = this.leftBoxPositionY + boxHeight / 2 - this.lineHeight / 2;
- } else {
- curveTextLeft = this.rightBoxBeginX - 37;
- curveTextTop = this.rightBoxPositionY + boxHeight / 2 - this.lineHeight / 2;
- }
- var textDiv = this.renderText(params.curveText, curveTextLeft, curveTextTop,
- params.side == 'left' ? 'CurveTextLeft' : 'CurveTextRight');
- // tooltip
- if (params.curveTextTooltip) {
- textDiv.hover(function() {
- Piwik_Tooltip.show(params.curveTextTooltip, 'Transitions_Tooltip_Small');
- }, function() {
- Piwik_Tooltip.hide();
- });
- }
- }
-
- if (params.side == 'left') {
- this.leftBoxPositionY += boxHeight + this.boxSpacing;
- this.leftCurvePositionY += curveHeight + this.curveSpacing;
- } else {
- this.rightBoxPositionY += boxHeight + this.boxSpacing;
- this.rightCurvePositionY += curveHeight + this.curveSpacing;
- }
+Piwik_Transitions_Canvas.prototype.renderBox = function (params) {
+ var curveHeight = params.curveHeight ? params.curveHeight :
+ Math.round(this.totalHeightOfConnections * params.share);
+ curveHeight = Math.max(curveHeight, 1);
+
+ var boxHeight = this.boxHeight;
+ if (params.smallBox) {
+ boxHeight = this.smallBoxHeight;
+ }
+ if (params.boxHeight) {
+ boxHeight = params.boxHeight;
+ }
+
+ var context;
+ if (params.bgCanvas) {
+ context = params.side == 'left' ? this.contextBgLeft : this.contextBgRight;
+ } else {
+ context = params.side == 'left' ? this.contextLeft : this.contextRight;
+ }
+
+ // background
+ context.fillStyle = params.gradient;
+ context.beginPath();
+ if (params.side == 'left') {
+ this.renderLeftBoxBg(context, boxHeight, curveHeight);
+ } else {
+ this.renderRightBoxBg(context, boxHeight, curveHeight);
+ }
+ if (typeof context.endPath == 'function') {
+ context.endPath();
+ }
+
+ // text inside the box
+ if (params.boxText && !params.onlyBg) {
+ var onClick = typeof params.onClick == 'function' ? params.onClick : false;
+ var boxTextLeft, boxTextTop, el;
+ if (params.side == 'left') {
+ boxTextLeft = this.leftBoxBeginX + 10;
+ boxTextTop = this.leftBoxPositionY + boxHeight / 2 - params.boxTextNumLines * this.lineHeight / 2;
+ el = this.renderText(params.boxText, boxTextLeft, boxTextTop, 'BoxTextLeft', onClick, params.boxIcon,
+ params.boxTextNumLines);
+ } else {
+ boxTextLeft = this.rightBoxBeginX;
+ boxTextTop = this.rightBoxPositionY + boxHeight / 2 - params.boxTextNumLines * this.lineHeight / 2;
+ el = this.renderText(params.boxText, boxTextLeft, boxTextTop, 'BoxTextRight', onClick, params.boxIcon,
+ params.boxTextNumLines);
+ }
+ if (params.boxTextCssClass) {
+ el.addClass('Transitions_' + params.boxTextCssClass);
+ }
+ // tooltip
+ if (params.boxTextTooltip) {
+ el.hover(function () {
+ var tip = piwikHelper.addBreakpointsToUrl(params.boxTextTooltip);
+ Piwik_Tooltip.show(tip, 'Transitions_Tooltip_Small', 300);
+ }, function () {
+ Piwik_Tooltip.hide();
+ });
+ if (onClick) {
+ el.click(function () {
+ Piwik_Tooltip.hide();
+ });
+ }
+ }
+ if (typeof params.onMouseOver == 'function') {
+ el.mouseenter(params.onMouseOver);
+ }
+ if (typeof params.onMouseOut == 'function') {
+ el.mouseleave(params.onMouseOut);
+ }
+ }
+
+ // text at the beginning of the curve
+ if (params.curveText && !params.onlyBg) {
+ var curveTextLeft, curveTextTop;
+ if (params.side == 'left') {
+ curveTextLeft = this.leftBoxBeginX + this.boxWidth + 3;
+ curveTextTop = this.leftBoxPositionY + boxHeight / 2 - this.lineHeight / 2;
+ } else {
+ curveTextLeft = this.rightBoxBeginX - 37;
+ curveTextTop = this.rightBoxPositionY + boxHeight / 2 - this.lineHeight / 2;
+ }
+ var textDiv = this.renderText(params.curveText, curveTextLeft, curveTextTop,
+ params.side == 'left' ? 'CurveTextLeft' : 'CurveTextRight');
+ // tooltip
+ if (params.curveTextTooltip) {
+ textDiv.hover(function () {
+ Piwik_Tooltip.show(params.curveTextTooltip, 'Transitions_Tooltip_Small');
+ }, function () {
+ Piwik_Tooltip.hide();
+ });
+ }
+ }
+
+ if (params.side == 'left') {
+ this.leftBoxPositionY += boxHeight + this.boxSpacing;
+ this.leftCurvePositionY += curveHeight + this.curveSpacing;
+ } else {
+ this.rightBoxPositionY += boxHeight + this.boxSpacing;
+ this.rightCurvePositionY += curveHeight + this.curveSpacing;
+ }
};
-Piwik_Transitions_Canvas.prototype.renderLeftBoxBg = function(context, boxHeight, curveHeight) {
- // derive coordinates for ths curve
- var leftUpper = {x: this.leftCurveBeginX, y: this.leftBoxPositionY};
- var leftLower = {x: this.leftCurveBeginX, y: this.leftBoxPositionY + boxHeight};
- var rightUpper = {x: this.leftCurveEndX, y: this.leftCurvePositionY};
- var rightLower = {x: this.leftCurveEndX, y: this.leftCurvePositionY + curveHeight};
-
- // derive control points for bezier curve
- var center = (this.leftCurveBeginX + this.leftCurveEndX) / 2;
- var cp1Upper = {x: center, y: leftUpper.y};
- var cp2Upper = {x: center, y: rightUpper.y};
- var cp1Lower = {x: center, y: rightLower.y};
- var cp2Lower = {x: center, y: leftLower.y};
-
- // the flow
- context.moveTo(leftUpper.x, leftUpper.y);
- context.bezierCurveTo(cp1Upper.x, cp1Upper.y, cp2Upper.x, cp2Upper.y, rightUpper.x, rightUpper.y);
- context.lineTo(rightLower.x, rightLower.y);
- context.bezierCurveTo(cp1Lower.x, cp1Lower.y, cp2Lower.x, cp2Lower.y, leftLower.x, leftLower.y);
-
- // the box
- context.lineTo(leftLower.x - this.boxWidth + 2, leftLower.y);
- context.lineTo(leftLower.x - this.boxWidth, leftUpper.y);
- context.lineTo(leftUpper.x, leftUpper.y);
- context.fill();
+Piwik_Transitions_Canvas.prototype.renderLeftBoxBg = function (context, boxHeight, curveHeight) {
+ // derive coordinates for ths curve
+ var leftUpper = {x: this.leftCurveBeginX, y: this.leftBoxPositionY};
+ var leftLower = {x: this.leftCurveBeginX, y: this.leftBoxPositionY + boxHeight};
+ var rightUpper = {x: this.leftCurveEndX, y: this.leftCurvePositionY};
+ var rightLower = {x: this.leftCurveEndX, y: this.leftCurvePositionY + curveHeight};
+
+ // derive control points for bezier curve
+ var center = (this.leftCurveBeginX + this.leftCurveEndX) / 2;
+ var cp1Upper = {x: center, y: leftUpper.y};
+ var cp2Upper = {x: center, y: rightUpper.y};
+ var cp1Lower = {x: center, y: rightLower.y};
+ var cp2Lower = {x: center, y: leftLower.y};
+
+ // the flow
+ context.moveTo(leftUpper.x, leftUpper.y);
+ context.bezierCurveTo(cp1Upper.x, cp1Upper.y, cp2Upper.x, cp2Upper.y, rightUpper.x, rightUpper.y);
+ context.lineTo(rightLower.x, rightLower.y);
+ context.bezierCurveTo(cp1Lower.x, cp1Lower.y, cp2Lower.x, cp2Lower.y, leftLower.x, leftLower.y);
+
+ // the box
+ context.lineTo(leftLower.x - this.boxWidth + 2, leftLower.y);
+ context.lineTo(leftLower.x - this.boxWidth, leftUpper.y);
+ context.lineTo(leftUpper.x, leftUpper.y);
+ context.fill();
};
-Piwik_Transitions_Canvas.prototype.renderRightBoxBg = function(context, boxHeight, curveHeight) {
- // derive coordinates for curve
- var leftUpper = {x: this.rightCurveBeginX, y: this.rightCurvePositionY};
- var leftLower = {x: this.rightCurveBeginX, y: this.rightCurvePositionY + curveHeight};
- var rightUpper = {x: this.rightCurveEndX, y: this.rightBoxPositionY};
- var rightLower = {x: this.rightCurveEndX, y: this.rightBoxPositionY + boxHeight};
-
- // derive control points for bezier curve
- var center = (this.rightCurveBeginX + this.rightCurveEndX) / 2;
- var cp1Upper = {x: center, y: leftUpper.y};
- var cp2Upper = {x: center, y: rightUpper.y};
- var cp1Lower = {x: center, y: rightLower.y};
- var cp2Lower = {x: center, y: leftLower.y};
-
- // the flow part 1
- context.moveTo(leftUpper.x, leftUpper.y);
- context.bezierCurveTo(cp1Upper.x, cp1Upper.y, cp2Upper.x, cp2Upper.y, rightUpper.x, rightUpper.y);
-
- // the box
- context.lineTo(rightUpper.x + this.boxWidth, rightUpper.y);
- context.lineTo(rightLower.x + this.boxWidth - 2, rightLower.y);
- context.lineTo(rightLower.x, rightLower.y);
-
- // the flow part 2
- context.bezierCurveTo(cp1Lower.x, cp1Lower.y, cp2Lower.x, cp2Lower.y, leftLower.x, leftLower.y);
- context.lineTo(leftUpper.x, leftUpper.y);
- context.fill();
+Piwik_Transitions_Canvas.prototype.renderRightBoxBg = function (context, boxHeight, curveHeight) {
+ // derive coordinates for curve
+ var leftUpper = {x: this.rightCurveBeginX, y: this.rightCurvePositionY};
+ var leftLower = {x: this.rightCurveBeginX, y: this.rightCurvePositionY + curveHeight};
+ var rightUpper = {x: this.rightCurveEndX, y: this.rightBoxPositionY};
+ var rightLower = {x: this.rightCurveEndX, y: this.rightBoxPositionY + boxHeight};
+
+ // derive control points for bezier curve
+ var center = (this.rightCurveBeginX + this.rightCurveEndX) / 2;
+ var cp1Upper = {x: center, y: leftUpper.y};
+ var cp2Upper = {x: center, y: rightUpper.y};
+ var cp1Lower = {x: center, y: rightLower.y};
+ var cp2Lower = {x: center, y: leftLower.y};
+
+ // the flow part 1
+ context.moveTo(leftUpper.x, leftUpper.y);
+ context.bezierCurveTo(cp1Upper.x, cp1Upper.y, cp2Upper.x, cp2Upper.y, rightUpper.x, rightUpper.y);
+
+ // the box
+ context.lineTo(rightUpper.x + this.boxWidth, rightUpper.y);
+ context.lineTo(rightLower.x + this.boxWidth - 2, rightLower.y);
+ context.lineTo(rightLower.x, rightLower.y);
+
+ // the flow part 2
+ context.bezierCurveTo(cp1Lower.x, cp1Lower.y, cp2Lower.x, cp2Lower.y, leftLower.x, leftLower.y);
+ context.lineTo(leftUpper.x, leftUpper.y);
+ context.fill();
};
/** Add spacing after the current box */
-Piwik_Transitions_Canvas.prototype.addBoxSpacing = function(spacing, side) {
- if (side == 'left') {
- this.leftBoxPositionY += spacing;
- } else {
- this.rightBoxPositionY += spacing;
- }
+Piwik_Transitions_Canvas.prototype.addBoxSpacing = function (spacing, side) {
+ if (side == 'left') {
+ this.leftBoxPositionY += spacing;
+ } else {
+ this.rightBoxPositionY += spacing;
+ }
};
-Piwik_Transitions_Canvas.prototype.renderLoops = function(share) {
- var curveHeight = Math.round(this.totalHeightOfConnections * share);
- curveHeight = Math.max(curveHeight, 1);
+Piwik_Transitions_Canvas.prototype.renderLoops = function (share) {
+ var curveHeight = Math.round(this.totalHeightOfConnections * share);
+ curveHeight = Math.max(curveHeight, 1);
- // create gradient
- var gradient = this.contextLoops.createLinearGradient(this.leftCurveEndX - 50, 0, this.rightCurveBeginX + 50, 0);
- var light = '#F5F3EB';
- var dark = '#E8E4D5';
- gradient.addColorStop(0, dark);
- gradient.addColorStop(.5, light);
- gradient.addColorStop(1, dark);
+ // create gradient
+ var gradient = this.contextLoops.createLinearGradient(this.leftCurveEndX - 50, 0, this.rightCurveBeginX + 50, 0);
+ var light = '#F5F3EB';
+ var dark = '#E8E4D5';
+ gradient.addColorStop(0, dark);
+ gradient.addColorStop(.5, light);
+ gradient.addColorStop(1, dark);
- this.contextLoops.fillStyle = gradient;
+ this.contextLoops.fillStyle = gradient;
- this.contextLoops.beginPath();
+ this.contextLoops.beginPath();
- // curve from the upper left connection to the center box to the lower left connection to the text box
- var point1 = {x: this.leftCurveEndX, y: this.leftCurvePositionY};
- var point2 = {x: this.leftCurveEndX, y: 470};
+ // curve from the upper left connection to the center box to the lower left connection to the text box
+ var point1 = {x: this.leftCurveEndX, y: this.leftCurvePositionY};
+ var point2 = {x: this.leftCurveEndX, y: 470};
- var cpLeftX = (this.leftCurveBeginX + this.leftCurveEndX) / 2 + 30;
- var cp1 = {x: cpLeftX, y: point1.y};
- var cp2 = {x: cpLeftX, y: point2.y};
+ var cpLeftX = (this.leftCurveBeginX + this.leftCurveEndX) / 2 + 30;
+ var cp1 = {x: cpLeftX, y: point1.y};
+ var cp2 = {x: cpLeftX, y: point2.y};
- this.contextLoops.moveTo(point1.x, point1.y);
- this.contextLoops.bezierCurveTo(cp1.x, cp1.y, cp2.x, cp2.y, point2.x, point2.y);
+ this.contextLoops.moveTo(point1.x, point1.y);
+ this.contextLoops.bezierCurveTo(cp1.x, cp1.y, cp2.x, cp2.y, point2.x, point2.y);
- // lower line of text box
- var point3 = {x: this.rightCurveBeginX, y: point2.y};
- this.contextLoops.lineTo(point3.x, point3.y);
+ // lower line of text box
+ var point3 = {x: this.rightCurveBeginX, y: point2.y};
+ this.contextLoops.lineTo(point3.x, point3.y);
- // curve to upper right connection to the center box
- var point4 = {x: this.rightCurveBeginX, y: this.rightCurvePositionY};
- var cpRightX = (this.rightCurveBeginX + this.rightCurveEndX) / 2 - 30;
- var cp3 = {x: cpRightX, y: point3.y};
- var cp4 = {x: cpRightX, y: point4.y};
- this.contextLoops.bezierCurveTo(cp3.x, cp3.y, cp4.x, cp4.y, point4.x, point4.y);
+ // curve to upper right connection to the center box
+ var point4 = {x: this.rightCurveBeginX, y: this.rightCurvePositionY};
+ var cpRightX = (this.rightCurveBeginX + this.rightCurveEndX) / 2 - 30;
+ var cp3 = {x: cpRightX, y: point3.y};
+ var cp4 = {x: cpRightX, y: point4.y};
+ this.contextLoops.bezierCurveTo(cp3.x, cp3.y, cp4.x, cp4.y, point4.x, point4.y);
- // line to lower right connection to the center box
- var point5 = {x: point4.x, y: point4.y + curveHeight};
- this.contextLoops.lineTo(point5.x, point5.y);
+ // line to lower right connection to the center box
+ var point5 = {x: point4.x, y: point4.y + curveHeight};
+ this.contextLoops.lineTo(point5.x, point5.y);
- // curve to upper right connection to the text box
- var point6 = {x: point5.x, y: point2.y - 25};
- cpRightX -= 30;
- var cp5 = {x: cpRightX, y: point5.y};
- var cp6 = {x: cpRightX, y: point6.y};
- this.contextLoops.bezierCurveTo(cp5.x, cp5.y, cp6.x, cp6.y, point6.x, point6.y);
+ // curve to upper right connection to the text box
+ var point6 = {x: point5.x, y: point2.y - 25};
+ cpRightX -= 30;
+ var cp5 = {x: cpRightX, y: point5.y};
+ var cp6 = {x: cpRightX, y: point6.y};
+ this.contextLoops.bezierCurveTo(cp5.x, cp5.y, cp6.x, cp6.y, point6.x, point6.y);
- // upper line of the text box
- var point7 = {x: point1.x, y: point6.y};
- this.contextLoops.lineTo(point7.x, point7.y);
+ // upper line of the text box
+ var point7 = {x: point1.x, y: point6.y};
+ this.contextLoops.lineTo(point7.x, point7.y);
- // line to lower left connection to the center box
- var point8 = {x: point1.x, y: point1.y + curveHeight};
- cpLeftX += 30;
- var cp7 = {x: cpLeftX, y: point7.y};
- var cp8 = {x: cpLeftX, y: point8.y};
- this.contextLoops.bezierCurveTo(cp7.x, cp7.y, cp8.x, cp8.y, point8.x, point8.y);
+ // line to lower left connection to the center box
+ var point8 = {x: point1.x, y: point1.y + curveHeight};
+ cpLeftX += 30;
+ var cp7 = {x: cpLeftX, y: point7.y};
+ var cp8 = {x: cpLeftX, y: point8.y};
+ this.contextLoops.bezierCurveTo(cp7.x, cp7.y, cp8.x, cp8.y, point8.x, point8.y);
- this.contextLoops.fill();
+ this.contextLoops.fill();
- if (typeof this.contextLoops.endPath == 'function') {
- this.contextLoops.endPath();
- }
+ if (typeof this.contextLoops.endPath == 'function') {
+ this.contextLoops.endPath();
+ }
};
/** Clear one side for redrawing */
-Piwik_Transitions_Canvas.prototype.clearSide = function(side, onlyBg) {
- if (side == 'left') {
- this.contextBgLeft.clearRect(0, 0, this.width, this.height);
- this.contextLeft.clearRect(0, 0, this.width, this.height);
- } else {
- this.contextBgRight.clearRect(0, 0, this.width, this.height);
- this.contextRight.clearRect(0, 0, this.width, this.height);
- }
- this.contextLoops.clearRect(0, 0, this.width, this.height);
-
- if (side == 'left') {
- if (!onlyBg) {
- this.container.find('.Transitions_BoxTextLeft').remove();
- this.container.find('.Transitions_CurveTextLeft').remove();
- }
- this.leftBoxPositionY = this.originalBoxPositionY;
- this.leftCurvePositionY = this.originalCurvePositionY;
- } else {
- if (!onlyBg) {
- this.container.find('.Transitions_BoxTextRight').remove();
- this.container.find('.Transitions_CurveTextRight').remove();
- }
- this.rightBoxPositionY = this.originalBoxPositionY;
- this.rightCurvePositionY = this.originalCurvePositionY;
- }
+Piwik_Transitions_Canvas.prototype.clearSide = function (side, onlyBg) {
+ if (side == 'left') {
+ this.contextBgLeft.clearRect(0, 0, this.width, this.height);
+ this.contextLeft.clearRect(0, 0, this.width, this.height);
+ } else {
+ this.contextBgRight.clearRect(0, 0, this.width, this.height);
+ this.contextRight.clearRect(0, 0, this.width, this.height);
+ }
+ this.contextLoops.clearRect(0, 0, this.width, this.height);
+
+ if (side == 'left') {
+ if (!onlyBg) {
+ this.container.find('.Transitions_BoxTextLeft').remove();
+ this.container.find('.Transitions_CurveTextLeft').remove();
+ }
+ this.leftBoxPositionY = this.originalBoxPositionY;
+ this.leftCurvePositionY = this.originalCurvePositionY;
+ } else {
+ if (!onlyBg) {
+ this.container.find('.Transitions_BoxTextRight').remove();
+ this.container.find('.Transitions_CurveTextRight').remove();
+ }
+ this.rightBoxPositionY = this.originalBoxPositionY;
+ this.rightCurvePositionY = this.originalCurvePositionY;
+ }
};
@@ -1190,185 +1190,185 @@ Piwik_Transitions_Canvas.prototype.clearSide = function(side, onlyBg) {
// --------------------------------------
function Piwik_Transitions_Model(ajax) {
- this.ajax = ajax;
+ this.ajax = ajax;
- this.groupTitles = {};
+ this.groupTitles = {};
}
-Piwik_Transitions_Model.prototype.htmlLoaded = function() {
- this.groupTitles.previousPages = Piwik_Transitions_Translations.fromPreviousPages;
- this.groupTitles.previousSiteSearches = Piwik_Transitions_Translations.fromPreviousSiteSearches;
- this.groupTitles.followingPages = Piwik_Transitions_Translations.toFollowingPages;
- this.groupTitles.followingSiteSearches = Piwik_Transitions_Translations.toFollowingSiteSearches;
- this.groupTitles.outlinks = Piwik_Transitions_Translations.outlinks;
- this.groupTitles.downloads = Piwik_Transitions_Translations.downloads;
-
- this.shareInGroupTexts = {
- previousPages: Piwik_Transitions_Translations.fromPreviousPagesInline,
- previousSiteSearches: Piwik_Transitions_Translations.fromPreviousSiteSearchesInline,
- followingPages: Piwik_Transitions_Translations.toFollowingPagesInline,
- followingSiteSearches: Piwik_Transitions_Translations.toFollowingSiteSearchesInline,
- searchEngines: Piwik_Transitions_Translations.fromSearchEnginesInline,
- websites: Piwik_Transitions_Translations.fromWebsitesInline,
- campaigns: Piwik_Transitions_Translations.fromCampaignsInline,
- outlinks: Piwik_Transitions_Translations.outlinksInline,
- downloads: Piwik_Transitions_Translations.downloadsInline
- };
+Piwik_Transitions_Model.prototype.htmlLoaded = function () {
+ this.groupTitles.previousPages = Piwik_Transitions_Translations.fromPreviousPages;
+ this.groupTitles.previousSiteSearches = Piwik_Transitions_Translations.fromPreviousSiteSearches;
+ this.groupTitles.followingPages = Piwik_Transitions_Translations.toFollowingPages;
+ this.groupTitles.followingSiteSearches = Piwik_Transitions_Translations.toFollowingSiteSearches;
+ this.groupTitles.outlinks = Piwik_Transitions_Translations.outlinks;
+ this.groupTitles.downloads = Piwik_Transitions_Translations.downloads;
+
+ this.shareInGroupTexts = {
+ previousPages: Piwik_Transitions_Translations.fromPreviousPagesInline,
+ previousSiteSearches: Piwik_Transitions_Translations.fromPreviousSiteSearchesInline,
+ followingPages: Piwik_Transitions_Translations.toFollowingPagesInline,
+ followingSiteSearches: Piwik_Transitions_Translations.toFollowingSiteSearchesInline,
+ searchEngines: Piwik_Transitions_Translations.fromSearchEnginesInline,
+ websites: Piwik_Transitions_Translations.fromWebsitesInline,
+ campaigns: Piwik_Transitions_Translations.fromCampaignsInline,
+ outlinks: Piwik_Transitions_Translations.outlinksInline,
+ downloads: Piwik_Transitions_Translations.downloadsInline
+ };
};
-Piwik_Transitions_Model.prototype.loadData = function(actionType, actionName, callback) {
- var self = this;
-
- this.pageviews = 0;
- this.exits = 0;
- this.loops = 0;
-
- this.directEntries = 0;
-
- this.searchEnginesNbTransitions = 0;
- this.searchEngines = [];
-
- this.websitesNbTransitions = 0;
- this.websites = [];
-
- this.campaignsNbTransitions = 0;
- this.campaigns = [];
-
- this.previousPagesNbTransitions = 0;
- this.previousPages = [];
-
- this.followingPagesNbTransitions = 0;
- this.followingPages = [];
-
- this.downloadsNbTransitions = 0;
- this.downloads = [];
-
- this.outlinksNbTransitions = 0;
- this.outlinks = [];
-
- this.previousSiteSearchesNbTransitions = 0;
- this.previousSiteSearches = [];
-
- this.followingSiteSearchesNbTransitions = 0;
- this.followingSiteSearches = [];
-
- this.date = '';
-
- this.ajax.callApi('Transitions.getTransitionsForAction', {
- actionType: actionType,
- actionName: actionName,
- expanded: 1
- },
- function(report) {
- self.date = report.date;
-
- // load page metrics
- self.pageviews = report.pageMetrics.pageviews;
- self.loops = report.pageMetrics.loops;
- self.exits = report.pageMetrics.exits;
-
- // load referrers: split direct entries and others
- for (var i = 0; i < report.referrers.length; i++) {
- var referrer = report.referrers[i];
- if (referrer.shortName == 'direct') {
- self.directEntries = referrer.visits;
- } else if (referrer.shortName == 'search') {
- self.searchEnginesNbTransitions = referrer.visits;
- self.searchEngines = referrer.details;
- self.groupTitles.searchEngines = referrer.label;
- } else if (referrer.shortName == 'website') {
- self.websitesNbTransitions = referrer.visits;
- self.websites = referrer.details;
- self.groupTitles.websites = referrer.label;
- } else if (referrer.shortName == 'campaign') {
- self.campaignsNbTransitions = referrer.visits;
- self.campaigns = referrer.details;
- self.groupTitles.campaigns = referrer.label;
- }
- }
-
- self.loadAndSumReport(report, 'previousPages');
- self.loadAndSumReport(report, 'previousSiteSearches');
- self.loadAndSumReport(report, 'followingPages');
- self.loadAndSumReport(report, 'followingSiteSearches');
- self.loadAndSumReport(report, 'downloads');
- self.loadAndSumReport(report, 'outlinks');
-
- if (typeof Piwik_Transitions_Model.totalNbPageviews == 'undefined') {
- Piwik_Transitions_Model.totalNbPageviews = false;
- self.ajax.loadTotalNbPageviews(function(nbPageviews) {
- Piwik_Transitions_Model.totalNbPageviews = nbPageviews;
- });
- }
-
- callback();
- });
+Piwik_Transitions_Model.prototype.loadData = function (actionType, actionName, callback) {
+ var self = this;
+
+ this.pageviews = 0;
+ this.exits = 0;
+ this.loops = 0;
+
+ this.directEntries = 0;
+
+ this.searchEnginesNbTransitions = 0;
+ this.searchEngines = [];
+
+ this.websitesNbTransitions = 0;
+ this.websites = [];
+
+ this.campaignsNbTransitions = 0;
+ this.campaigns = [];
+
+ this.previousPagesNbTransitions = 0;
+ this.previousPages = [];
+
+ this.followingPagesNbTransitions = 0;
+ this.followingPages = [];
+
+ this.downloadsNbTransitions = 0;
+ this.downloads = [];
+
+ this.outlinksNbTransitions = 0;
+ this.outlinks = [];
+
+ this.previousSiteSearchesNbTransitions = 0;
+ this.previousSiteSearches = [];
+
+ this.followingSiteSearchesNbTransitions = 0;
+ this.followingSiteSearches = [];
+
+ this.date = '';
+
+ this.ajax.callApi('Transitions.getTransitionsForAction', {
+ actionType: actionType,
+ actionName: actionName,
+ expanded: 1
+ },
+ function (report) {
+ self.date = report.date;
+
+ // load page metrics
+ self.pageviews = report.pageMetrics.pageviews;
+ self.loops = report.pageMetrics.loops;
+ self.exits = report.pageMetrics.exits;
+
+ // load referrers: split direct entries and others
+ for (var i = 0; i < report.referrers.length; i++) {
+ var referrer = report.referrers[i];
+ if (referrer.shortName == 'direct') {
+ self.directEntries = referrer.visits;
+ } else if (referrer.shortName == 'search') {
+ self.searchEnginesNbTransitions = referrer.visits;
+ self.searchEngines = referrer.details;
+ self.groupTitles.searchEngines = referrer.label;
+ } else if (referrer.shortName == 'website') {
+ self.websitesNbTransitions = referrer.visits;
+ self.websites = referrer.details;
+ self.groupTitles.websites = referrer.label;
+ } else if (referrer.shortName == 'campaign') {
+ self.campaignsNbTransitions = referrer.visits;
+ self.campaigns = referrer.details;
+ self.groupTitles.campaigns = referrer.label;
+ }
+ }
+
+ self.loadAndSumReport(report, 'previousPages');
+ self.loadAndSumReport(report, 'previousSiteSearches');
+ self.loadAndSumReport(report, 'followingPages');
+ self.loadAndSumReport(report, 'followingSiteSearches');
+ self.loadAndSumReport(report, 'downloads');
+ self.loadAndSumReport(report, 'outlinks');
+
+ if (typeof Piwik_Transitions_Model.totalNbPageviews == 'undefined') {
+ Piwik_Transitions_Model.totalNbPageviews = false;
+ self.ajax.loadTotalNbPageviews(function (nbPageviews) {
+ Piwik_Transitions_Model.totalNbPageviews = nbPageviews;
+ });
+ }
+
+ callback();
+ });
};
-Piwik_Transitions_Model.prototype.loadAndSumReport = function(apiData, reportName) {
- var data = this[reportName] = apiData[reportName];
- var sumVarName = reportName + 'NbTransitions';
+Piwik_Transitions_Model.prototype.loadAndSumReport = function (apiData, reportName) {
+ var data = this[reportName] = apiData[reportName];
+ var sumVarName = reportName + 'NbTransitions';
- this[sumVarName] = 0;
- for (var i = 0; i < data.length; i++) {
- this[sumVarName] += data[i].referrals;
- }
+ this[sumVarName] = 0;
+ for (var i = 0; i < data.length; i++) {
+ this[sumVarName] += data[i].referrals;
+ }
};
-Piwik_Transitions_Model.prototype.getTotalNbPageviews = function() {
- if (typeof Piwik_Transitions_Model.totalNbPageviews == 'undefined') {
- return false;
- }
- return Piwik_Transitions_Model.totalNbPageviews;
+Piwik_Transitions_Model.prototype.getTotalNbPageviews = function () {
+ if (typeof Piwik_Transitions_Model.totalNbPageviews == 'undefined') {
+ return false;
+ }
+ return Piwik_Transitions_Model.totalNbPageviews;
};
-Piwik_Transitions_Model.prototype.getGroupTitle = function(groupName) {
- if (typeof this.groupTitles[groupName] != 'undefined') {
- return this.groupTitles[groupName];
- }
- return groupName;
+Piwik_Transitions_Model.prototype.getGroupTitle = function (groupName) {
+ if (typeof this.groupTitles[groupName] != 'undefined') {
+ return this.groupTitles[groupName];
+ }
+ return groupName;
};
-Piwik_Transitions_Model.prototype.getShareInGroupTooltip = function(share, groupName) {
- var tip = this.shareInGroupTexts[groupName];
- return tip.replace(/%s/, share);
+Piwik_Transitions_Model.prototype.getShareInGroupTooltip = function (share, groupName) {
+ var tip = this.shareInGroupTexts[groupName];
+ return tip.replace(/%s/, share);
};
-Piwik_Transitions_Model.prototype.getDetailsForGroup = function(groupName) {
- return this.addPercentagesToData(this[groupName]);
+Piwik_Transitions_Model.prototype.getDetailsForGroup = function (groupName) {
+ return this.addPercentagesToData(this[groupName]);
};
-Piwik_Transitions_Model.prototype.getPercentage = function(metric, formatted) {
- var percentage = (this.pageviews == 0 ? 0 : this[metric] / this.pageviews);
+Piwik_Transitions_Model.prototype.getPercentage = function (metric, formatted) {
+ var percentage = (this.pageviews == 0 ? 0 : this[metric] / this.pageviews);
- if (formatted) {
- percentage = this.roundPercentage(percentage);
- percentage += '%';
- }
+ if (formatted) {
+ percentage = this.roundPercentage(percentage);
+ percentage += '%';
+ }
- return percentage;
+ return percentage;
};
-Piwik_Transitions_Model.prototype.addPercentagesToData = function(data) {
- var total = 0;
+Piwik_Transitions_Model.prototype.addPercentagesToData = function (data) {
+ var total = 0;
- for (var i = 0; i < data.length; i++) {
- total += parseInt(data[i].referrals, 10);
- }
+ for (var i = 0; i < data.length; i++) {
+ total += parseInt(data[i].referrals, 10);
+ }
- for (i = 0; i < data.length; i++) {
- data[i].percentage = this.roundPercentage(data[i].referrals / total);
- }
+ for (i = 0; i < data.length; i++) {
+ data[i].percentage = this.roundPercentage(data[i].referrals / total);
+ }
- return data;
+ return data;
};
-Piwik_Transitions_Model.prototype.roundPercentage = function(value) {
- if (value < .1) {
- return Math.round(value * 1000) / 10.0;
- } else {
- return Math.round(value * 100);
- }
+Piwik_Transitions_Model.prototype.roundPercentage = function (value) {
+ if (value < .1) {
+ return Math.round(value * 1000) / 10.0;
+ } else {
+ return Math.round(value * 100);
+ }
};
@@ -1379,16 +1379,16 @@ Piwik_Transitions_Model.prototype.roundPercentage = function(value) {
function Piwik_Transitions_Ajax() {
}
-Piwik_Transitions_Ajax.prototype.loadTotalNbPageviews = function(callback) {
- this.callApi('Actions.get', {
- columns: 'nb_pageviews'
- }, function(response) {
- var value = typeof response.value != 'undefined' ? response.value : false;
- callback(value);
- });
+Piwik_Transitions_Ajax.prototype.loadTotalNbPageviews = function (callback) {
+ this.callApi('Actions.get', {
+ columns: 'nb_pageviews'
+ }, function (response) {
+ var value = typeof response.value != 'undefined' ? response.value : false;
+ callback(value);
+ });
};
-Piwik_Transitions_Ajax.prototype.callTransitionsController = function(action, callback) {
+Piwik_Transitions_Ajax.prototype.callTransitionsController = function (action, callback) {
var ajaxRequest = new ajaxHelper();
ajaxRequest.addParams({
module: 'Transitions',
@@ -1399,16 +1399,16 @@ Piwik_Transitions_Ajax.prototype.callTransitionsController = function(action, ca
ajaxRequest.send(false);
};
-Piwik_Transitions_Ajax.prototype.callApi = function(method, params, callback) {
- var self = this;
+Piwik_Transitions_Ajax.prototype.callApi = function (method, params, callback) {
+ var self = this;
- params.format = 'JSON';
- params.module = 'API';
+ params.format = 'JSON';
+ params.module = 'API';
params.method = method;
var ajaxRequest = new ajaxHelper();
ajaxRequest.addParams(params, 'get');
- ajaxRequest.useCallbackInCaseOfError();
+ ajaxRequest.useCallbackInCaseOfError();
ajaxRequest.setCallback(
function (result) {
if (typeof result.result != 'undefined' && result.result == 'error') {
@@ -1463,55 +1463,55 @@ Piwik_Transitions_Ajax.prototype.callApi = function(method, params, callback) {
Piwik_Transitions_Util = {
- /**
- * Removes protocol, www and trailing slashes from a URL.
- * If removeDomain is set, the domain is removed as well.
- */
- shortenUrl: function(url, removeDomain) {
- if (url == 'Others') {
- return url;
- }
-
- var urlBackup = url;
- url = url.replace(/http(s)?:\/\/(www\.)?/, '');
-
- if (urlBackup == url) {
- return url;
- }
-
- if (removeDomain) {
- url = url.replace(/[^\/]*/, '');
- if (url == '/') {
- url = urlBackup;
- }
- }
-
- url = url.replace(/\/$/, '');
-
- return url;
- },
-
- /**
- * Replaces a %s placeholder in the HTML.
- * The special feature is that it can be called multiple times, replacing the already
- * replaced placeholder again. It creates a span that can be assigned a class using the
- * spanClass parameter. The default class is 'Transitions_Metric'.
- */
- replacePlaceholderInHtml: function(container, value, spanClass) {
- var span = container.find('span');
- if (span.size() == 0) {
- var html = container.html().replace(/%s/, '<span></span>');
- span = container.html(html).find('span');
- if (!spanClass) {
- spanClass = 'Transitions_Metric';
- }
- span.addClass(spanClass);
- }
- if ($.browser.msie && parseFloat($.browser.version) < 8) {
- // ie7 fix
- value += '&nbsp;';
- }
- span.html(value);
- }
+ /**
+ * Removes protocol, www and trailing slashes from a URL.
+ * If removeDomain is set, the domain is removed as well.
+ */
+ shortenUrl: function (url, removeDomain) {
+ if (url == 'Others') {
+ return url;
+ }
+
+ var urlBackup = url;
+ url = url.replace(/http(s)?:\/\/(www\.)?/, '');
+
+ if (urlBackup == url) {
+ return url;
+ }
+
+ if (removeDomain) {
+ url = url.replace(/[^\/]*/, '');
+ if (url == '/') {
+ url = urlBackup;
+ }
+ }
+
+ url = url.replace(/\/$/, '');
+
+ return url;
+ },
+
+ /**
+ * Replaces a %s placeholder in the HTML.
+ * The special feature is that it can be called multiple times, replacing the already
+ * replaced placeholder again. It creates a span that can be assigned a class using the
+ * spanClass parameter. The default class is 'Transitions_Metric'.
+ */
+ replacePlaceholderInHtml: function (container, value, spanClass) {
+ var span = container.find('span');
+ if (span.size() == 0) {
+ var html = container.html().replace(/%s/, '<span></span>');
+ span = container.html(html).find('span');
+ if (!spanClass) {
+ spanClass = 'Transitions_Metric';
+ }
+ span.addClass(spanClass);
+ }
+ if ($.browser.msie && parseFloat($.browser.version) < 8) {
+ // ie7 fix
+ value += '&nbsp;';
+ }
+ span.html(value);
+ }
};
diff --git a/plugins/Transitions/templates/transitions.tpl b/plugins/Transitions/templates/transitions.tpl
index 0006c2e372..9ec71b8fe3 100644
--- a/plugins/Transitions/templates/transitions.tpl
+++ b/plugins/Transitions/templates/transitions.tpl
@@ -1,45 +1,56 @@
-
<div id="Transitions_Container">
- <div id="Transitions_CenterBox" class="Transitions_Text">
- <h2></h2>
- <div class="Transitions_CenterBoxMetrics">
- <p class="Transitions_Pageviews Transitions_Margin">{$translations.pageviewsInline|translate}</p>
-
- <div class="Transitions_IncomingTraffic">
- <h3>{'Transitions_IncomingTraffic'|translate}</h3>
- <p class="Transitions_PreviousPages">{$translations.fromPreviousPagesInline|translate}</p>
- <p class="Transitions_PreviousSiteSearches">{$translations.fromPreviousSiteSearchesInline|translate}</p>
- <p class="Transitions_SearchEngines">{$translations.fromSearchEnginesInline|translate}</p>
- <p class="Transitions_Websites">{$translations.fromWebsitesInline|translate}</p>
- <p class="Transitions_Campaigns">{$translations.fromCampaignsInline|translate}</p>
- <p class="Transitions_DirectEntries">{$translations.directEntriesInline|translate}</p>
- </div>
-
- <div class="Transitions_OutgoingTraffic">
- <h3>{'Transitions_OutgoingTraffic'|translate}</h3>
- <p class="Transitions_FollowingPages">{$translations.toFollowingPagesInline|translate}</p>
- <p class="Transitions_FollowingSiteSearches">{$translations.toFollowingSiteSearchesInline|translate}</p>
- <p class="Transitions_Downloads">{$translations.downloadsInline|translate}</p>
- <p class="Transitions_Outlinks">{$translations.outlinksInline|translate}</p>
- <p class="Transitions_Exits">{$translations.exitsInline|translate}</p>
- </div>
- </div>
- </div>
- <div id="Transitions_Loops" class="Transitions_Text">
- {$translations.loopsInline|translate}
- </div>
- <div id="Transitions_Canvas_Background_Left" class="Transitions_Canvas_Container"></div>
- <div id="Transitions_Canvas_Background_Right" class="Transitions_Canvas_Container"></div>
- <div id="Transitions_Canvas_Left" class="Transitions_Canvas_Container"></div>
- <div id="Transitions_Canvas_Right" class="Transitions_Canvas_Container"></div>
- <div id="Transitions_Canvas_Loops" class="Transitions_Canvas_Container"></div>
+ <div id="Transitions_CenterBox" class="Transitions_Text">
+ <h2></h2>
+
+ <div class="Transitions_CenterBoxMetrics">
+ <p class="Transitions_Pageviews Transitions_Margin">{$translations.pageviewsInline|translate}</p>
+
+ <div class="Transitions_IncomingTraffic">
+ <h3>{'Transitions_IncomingTraffic'|translate}</h3>
+
+ <p class="Transitions_PreviousPages">{$translations.fromPreviousPagesInline|translate}</p>
+
+ <p class="Transitions_PreviousSiteSearches">{$translations.fromPreviousSiteSearchesInline|translate}</p>
+
+ <p class="Transitions_SearchEngines">{$translations.fromSearchEnginesInline|translate}</p>
+
+ <p class="Transitions_Websites">{$translations.fromWebsitesInline|translate}</p>
+
+ <p class="Transitions_Campaigns">{$translations.fromCampaignsInline|translate}</p>
+
+ <p class="Transitions_DirectEntries">{$translations.directEntriesInline|translate}</p>
+ </div>
+
+ <div class="Transitions_OutgoingTraffic">
+ <h3>{'Transitions_OutgoingTraffic'|translate}</h3>
+
+ <p class="Transitions_FollowingPages">{$translations.toFollowingPagesInline|translate}</p>
+
+ <p class="Transitions_FollowingSiteSearches">{$translations.toFollowingSiteSearchesInline|translate}</p>
+
+ <p class="Transitions_Downloads">{$translations.downloadsInline|translate}</p>
+
+ <p class="Transitions_Outlinks">{$translations.outlinksInline|translate}</p>
+
+ <p class="Transitions_Exits">{$translations.exitsInline|translate}</p>
+ </div>
+ </div>
+ </div>
+ <div id="Transitions_Loops" class="Transitions_Text">
+ {$translations.loopsInline|translate}
+ </div>
+ <div id="Transitions_Canvas_Background_Left" class="Transitions_Canvas_Container"></div>
+ <div id="Transitions_Canvas_Background_Right" class="Transitions_Canvas_Container"></div>
+ <div id="Transitions_Canvas_Left" class="Transitions_Canvas_Container"></div>
+ <div id="Transitions_Canvas_Right" class="Transitions_Canvas_Container"></div>
+ <div id="Transitions_Canvas_Loops" class="Transitions_Canvas_Container"></div>
</div>
<script type="text/javascript">
- var Piwik_Transitions_Translations = {literal}{{/literal}
- {foreach from=$translations key=internalKey item=translation}
- "{$internalKey}": "{$translation|escape:'html'}",
- {/foreach}
- "": ""
- {literal}}{/literal};
+ var Piwik_Transitions_Translations = {literal}{{/literal}
+ {foreach from=$translations key=internalKey item=translation}
+ "{$internalKey}": "{$translation|escape:'html'}",
+ {/foreach}
+ "": ""
+ {literal}}{/literal};
</script> \ No newline at end of file
diff --git a/plugins/UserCountry/API.php b/plugins/UserCountry/API.php
index fad2b1a4ca..d8cdb266b6 100644
--- a/plugins/UserCountry/API.php
+++ b/plugins/UserCountry/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_UserCountry
*/
@@ -18,195 +18,192 @@ require_once PIWIK_INCLUDE_PATH . '/plugins/UserCountry/functions.php';
* The UserCountry API lets you access reports about your visitors' Countries and Continents.
* @package Piwik_UserCountry
*/
-class Piwik_UserCountry_API
+class Piwik_UserCountry_API
{
- static private $instance = null;
- static public function getInstance()
- {
- if (self::$instance == null)
- {
- self::$instance = new self;
- }
- return self::$instance;
- }
-
- public function getCountry( $idSite, $period, $date, $segment = false )
- {
- $recordName = Piwik_UserCountry::VISITS_BY_COUNTRY_RECORD_NAME;
- $dataTable = $this->getDataTable($recordName, $idSite, $period, $date, $segment);
-
- // apply filter on the whole datatable in order the inline search to work (searches
- // are done on "beautiful" label)
- $dataTable->filter('ColumnCallbackAddMetadata', array('label', 'code'));
- $dataTable->filter('ColumnCallbackAddMetadata', array('label', 'logo', 'Piwik_getFlagFromCode'));
- $dataTable->filter('ColumnCallbackReplace', array('label', 'Piwik_CountryTranslate'));
- $dataTable->queueFilter('AddConstantMetadata', array('logoWidth', 16));
- $dataTable->queueFilter('AddConstantMetadata', array('logoHeight', 11));
-
- return $dataTable;
- }
-
- public function getContinent( $idSite, $period, $date, $segment = false )
- {
- $recordName = Piwik_UserCountry::VISITS_BY_COUNTRY_RECORD_NAME;
- $dataTable = $this->getDataTable($recordName, $idSite, $period, $date, $segment);
-
- $getContinent = array('Piwik_Common', 'getContinent');
- $dataTable->filter('GroupBy', array('label', $getContinent));
-
- $dataTable->filter('ColumnCallbackReplace', array('label', 'Piwik_ContinentTranslate'));
- $dataTable->queueFilter('ColumnCallbackAddMetadata', array('label', 'code'));
-
- return $dataTable;
- }
-
- /**
- * Returns visit information for every region with at least one visit.
- *
- * @param int|string $idSite
- * @param string $period
- * @param string $date
- * @param string|bool $segment
- * @return Piwik_DataTable
- */
- public function getRegion( $idSite, $period, $date, $segment = false )
- {
- $recordName = Piwik_UserCountry::VISITS_BY_REGION_RECORD_NAME;
- $dataTable = $this->getDataTable($recordName, $idSite, $period, $date, $segment);
-
- $separator = Piwik_UserCountry::LOCATION_SEPARATOR;
- $unk = Piwik_Tracker_Visit::UNKNOWN_CODE;
-
- // split the label and put the elements into the 'region' and 'country' metadata fields
- $dataTable->filter('ColumnCallbackAddMetadata',
- array('label', 'region', 'Piwik_UserCountry_getElementFromStringArray', array($separator, 0, $unk)));
- $dataTable->filter('ColumnCallbackAddMetadata',
- array('label', 'country', 'Piwik_UserCountry_getElementFromStringArray', array($separator, 1, $unk)));
-
- // add country name metadata
- $dataTable->filter('MetadataCallbackAddMetadata',
- array('country', 'country_name', 'Piwik_CountryTranslate', $applyToSummaryRow = false));
-
- // get the region name of each row and put it into the 'region_name' metadata
- $dataTable->filter('ColumnCallbackAddMetadata',
- array('label', 'region_name', 'Piwik_UserCountry_getRegionName', $params = null,
- $applyToSummaryRow = false));
-
- // add the country flag as a url to the 'logo' metadata field
- $dataTable->filter('MetadataCallbackAddMetadata', array('country', 'logo', 'Piwik_getFlagFromCode'));
-
- // prettify the region label
- $dataTable->filter('ColumnCallbackReplace', array('label', 'Piwik_UserCountry_getPrettyRegionName'));
-
- $dataTable->queueFilter('ReplaceSummaryRowLabel');
-
- return $dataTable;
- }
-
- /**
- * Returns visit information for every city with at least one visit.
- *
- * @param int|string $idSite
- * @param string $period
- * @param string $date
- * @param string|bool $segment
- * @return Piwik_DataTable
- */
- public function getCity( $idSite, $period, $date, $segment = false )
- {
- $recordName = Piwik_UserCountry::VISITS_BY_CITY_RECORD_NAME;
- $dataTable = $this->getDataTable($recordName, $idSite, $period, $date, $segment);
-
- $separator = Piwik_UserCountry::LOCATION_SEPARATOR;
- $unk = Piwik_Tracker_Visit::UNKNOWN_CODE;
-
- // split the label and put the elements into the 'city_name', 'region', 'country',
- // 'lat' & 'long' metadata fields
- $strUnknown = Piwik_Translate('General_Unknown');
- $dataTable->filter('ColumnCallbackAddMetadata',
- array('label', 'city_name', 'Piwik_UserCountry_getElementFromStringArray',
- array($separator, 0, $strUnknown)));
- $dataTable->filter('MetadataCallbackAddMetadata',
- array('city_name', 'city', create_function('$city',' if ($city == "'.$strUnknown.'") { return "xx"; } else { return false; } ')));
- $dataTable->filter('ColumnCallbackAddMetadata',
- array('label', 'region', 'Piwik_UserCountry_getElementFromStringArray', array($separator, 1, $unk)));
- $dataTable->filter('ColumnCallbackAddMetadata',
- array('label', 'country', 'Piwik_UserCountry_getElementFromStringArray', array($separator, 2, $unk)));
-
- // backwards compatibility: for reports that have lat|long in label
- $dataTable->filter('ColumnCallbackAddMetadata',
- array('label', 'lat', 'Piwik_UserCountry_getElementFromStringArray', array($separator, 3, false)));
- $dataTable->filter('ColumnCallbackAddMetadata',
- array('label', 'long', 'Piwik_UserCountry_getElementFromStringArray', array($separator, 4, false)));
-
- // add country name & region name metadata
- $dataTable->filter('MetadataCallbackAddMetadata',
- array('country', 'country_name', 'Piwik_CountryTranslate', $applyToSummaryRow = false));
-
- $getRegionName = array('Piwik_UserCountry_LocationProvider_GeoIp', 'getRegionNameFromCodes');
- $dataTable->filter('MetadataCallbackAddMetadata', array(
- array('country', 'region'), 'region_name', $getRegionName, $applyToSummaryRow = false));
-
- // add the country flag as a url to the 'logo' metadata field
- $dataTable->filter('MetadataCallbackAddMetadata', array('country', 'logo', 'Piwik_getFlagFromCode'));
-
- // prettify the label
- $dataTable->filter('ColumnCallbackReplace', array('label', 'Piwik_UserCountry_getPrettyCityName'));
-
- $dataTable->queueFilter('ReplaceSummaryRowLabel');
-
- return $dataTable;
- }
-
- /**
- * Uses a location provider to find/guess the location of an IP address.
- *
- * See Piwik_UserCountry_LocationProvider::getLocation to see the details
- * of the result of this function.
- *
- * @param string $ip The IP address.
- * @param string|false $provider The ID of the provider to use or false to use the
- * currently configured one.
- */
- public function getLocationFromIP( $ip, $provider = false )
- {
- Piwik::checkUserHasSomeViewAccess();
-
- if ($provider === false)
- {
- $provider = Piwik_UserCountry_LocationProvider::getCurrentProviderId();
- }
-
- $oProvider = Piwik_UserCountry_LocationProvider::getProviderById($provider);
- if ($oProvider === false)
- {
- throw new Exception("Cannot find the '$provider' provider. It is either an invalid provider "
- . "ID or the ID of a provider that is not working.");
- }
-
- $location = $oProvider->getLocation(array('ip' => $ip));
- if (empty($location))
- {
- throw new Exception("Could not geolocate '$ip'!");
- }
- $location['ip'] = $ip;
- return $location;
- }
-
- protected function getDataTable($name, $idSite, $period, $date, $segment)
- {
- Piwik::checkUserHasViewAccess( $idSite );
- $archive = Piwik_Archive::build($idSite, $period, $date, $segment );
- $dataTable = $archive->getDataTable($name);
- $dataTable->filter('Sort', array(Piwik_Archive::INDEX_NB_VISITS));
- $dataTable->queueFilter('ReplaceColumnNames');
- return $dataTable;
- }
-
- public function getNumberOfDistinctCountries($idSite, $period, $date, $segment = false)
- {
- Piwik::checkUserHasViewAccess( $idSite );
- $archive = Piwik_Archive::build($idSite, $period, $date, $segment );
- return $archive->getDataTableFromNumeric('UserCountry_distinctCountries');
- }
+ static private $instance = null;
+
+ static public function getInstance()
+ {
+ if (self::$instance == null) {
+ self::$instance = new self;
+ }
+ return self::$instance;
+ }
+
+ public function getCountry($idSite, $period, $date, $segment = false)
+ {
+ $recordName = Piwik_UserCountry::VISITS_BY_COUNTRY_RECORD_NAME;
+ $dataTable = $this->getDataTable($recordName, $idSite, $period, $date, $segment);
+
+ // apply filter on the whole datatable in order the inline search to work (searches
+ // are done on "beautiful" label)
+ $dataTable->filter('ColumnCallbackAddMetadata', array('label', 'code'));
+ $dataTable->filter('ColumnCallbackAddMetadata', array('label', 'logo', 'Piwik_getFlagFromCode'));
+ $dataTable->filter('ColumnCallbackReplace', array('label', 'Piwik_CountryTranslate'));
+ $dataTable->queueFilter('AddConstantMetadata', array('logoWidth', 16));
+ $dataTable->queueFilter('AddConstantMetadata', array('logoHeight', 11));
+
+ return $dataTable;
+ }
+
+ public function getContinent($idSite, $period, $date, $segment = false)
+ {
+ $recordName = Piwik_UserCountry::VISITS_BY_COUNTRY_RECORD_NAME;
+ $dataTable = $this->getDataTable($recordName, $idSite, $period, $date, $segment);
+
+ $getContinent = array('Piwik_Common', 'getContinent');
+ $dataTable->filter('GroupBy', array('label', $getContinent));
+
+ $dataTable->filter('ColumnCallbackReplace', array('label', 'Piwik_ContinentTranslate'));
+ $dataTable->queueFilter('ColumnCallbackAddMetadata', array('label', 'code'));
+
+ return $dataTable;
+ }
+
+ /**
+ * Returns visit information for every region with at least one visit.
+ *
+ * @param int|string $idSite
+ * @param string $period
+ * @param string $date
+ * @param string|bool $segment
+ * @return Piwik_DataTable
+ */
+ public function getRegion($idSite, $period, $date, $segment = false)
+ {
+ $recordName = Piwik_UserCountry::VISITS_BY_REGION_RECORD_NAME;
+ $dataTable = $this->getDataTable($recordName, $idSite, $period, $date, $segment);
+
+ $separator = Piwik_UserCountry::LOCATION_SEPARATOR;
+ $unk = Piwik_Tracker_Visit::UNKNOWN_CODE;
+
+ // split the label and put the elements into the 'region' and 'country' metadata fields
+ $dataTable->filter('ColumnCallbackAddMetadata',
+ array('label', 'region', 'Piwik_UserCountry_getElementFromStringArray', array($separator, 0, $unk)));
+ $dataTable->filter('ColumnCallbackAddMetadata',
+ array('label', 'country', 'Piwik_UserCountry_getElementFromStringArray', array($separator, 1, $unk)));
+
+ // add country name metadata
+ $dataTable->filter('MetadataCallbackAddMetadata',
+ array('country', 'country_name', 'Piwik_CountryTranslate', $applyToSummaryRow = false));
+
+ // get the region name of each row and put it into the 'region_name' metadata
+ $dataTable->filter('ColumnCallbackAddMetadata',
+ array('label', 'region_name', 'Piwik_UserCountry_getRegionName', $params = null,
+ $applyToSummaryRow = false));
+
+ // add the country flag as a url to the 'logo' metadata field
+ $dataTable->filter('MetadataCallbackAddMetadata', array('country', 'logo', 'Piwik_getFlagFromCode'));
+
+ // prettify the region label
+ $dataTable->filter('ColumnCallbackReplace', array('label', 'Piwik_UserCountry_getPrettyRegionName'));
+
+ $dataTable->queueFilter('ReplaceSummaryRowLabel');
+
+ return $dataTable;
+ }
+
+ /**
+ * Returns visit information for every city with at least one visit.
+ *
+ * @param int|string $idSite
+ * @param string $period
+ * @param string $date
+ * @param string|bool $segment
+ * @return Piwik_DataTable
+ */
+ public function getCity($idSite, $period, $date, $segment = false)
+ {
+ $recordName = Piwik_UserCountry::VISITS_BY_CITY_RECORD_NAME;
+ $dataTable = $this->getDataTable($recordName, $idSite, $period, $date, $segment);
+
+ $separator = Piwik_UserCountry::LOCATION_SEPARATOR;
+ $unk = Piwik_Tracker_Visit::UNKNOWN_CODE;
+
+ // split the label and put the elements into the 'city_name', 'region', 'country',
+ // 'lat' & 'long' metadata fields
+ $strUnknown = Piwik_Translate('General_Unknown');
+ $dataTable->filter('ColumnCallbackAddMetadata',
+ array('label', 'city_name', 'Piwik_UserCountry_getElementFromStringArray',
+ array($separator, 0, $strUnknown)));
+ $dataTable->filter('MetadataCallbackAddMetadata',
+ array('city_name', 'city', create_function('$city', ' if ($city == "' . $strUnknown . '") { return "xx"; } else { return false; } ')));
+ $dataTable->filter('ColumnCallbackAddMetadata',
+ array('label', 'region', 'Piwik_UserCountry_getElementFromStringArray', array($separator, 1, $unk)));
+ $dataTable->filter('ColumnCallbackAddMetadata',
+ array('label', 'country', 'Piwik_UserCountry_getElementFromStringArray', array($separator, 2, $unk)));
+
+ // backwards compatibility: for reports that have lat|long in label
+ $dataTable->filter('ColumnCallbackAddMetadata',
+ array('label', 'lat', 'Piwik_UserCountry_getElementFromStringArray', array($separator, 3, false)));
+ $dataTable->filter('ColumnCallbackAddMetadata',
+ array('label', 'long', 'Piwik_UserCountry_getElementFromStringArray', array($separator, 4, false)));
+
+ // add country name & region name metadata
+ $dataTable->filter('MetadataCallbackAddMetadata',
+ array('country', 'country_name', 'Piwik_CountryTranslate', $applyToSummaryRow = false));
+
+ $getRegionName = array('Piwik_UserCountry_LocationProvider_GeoIp', 'getRegionNameFromCodes');
+ $dataTable->filter('MetadataCallbackAddMetadata', array(
+ array('country', 'region'), 'region_name', $getRegionName, $applyToSummaryRow = false));
+
+ // add the country flag as a url to the 'logo' metadata field
+ $dataTable->filter('MetadataCallbackAddMetadata', array('country', 'logo', 'Piwik_getFlagFromCode'));
+
+ // prettify the label
+ $dataTable->filter('ColumnCallbackReplace', array('label', 'Piwik_UserCountry_getPrettyCityName'));
+
+ $dataTable->queueFilter('ReplaceSummaryRowLabel');
+
+ return $dataTable;
+ }
+
+ /**
+ * Uses a location provider to find/guess the location of an IP address.
+ *
+ * See Piwik_UserCountry_LocationProvider::getLocation to see the details
+ * of the result of this function.
+ *
+ * @param string $ip The IP address.
+ * @param string|false $provider The ID of the provider to use or false to use the
+ * currently configured one.
+ */
+ public function getLocationFromIP($ip, $provider = false)
+ {
+ Piwik::checkUserHasSomeViewAccess();
+
+ if ($provider === false) {
+ $provider = Piwik_UserCountry_LocationProvider::getCurrentProviderId();
+ }
+
+ $oProvider = Piwik_UserCountry_LocationProvider::getProviderById($provider);
+ if ($oProvider === false) {
+ throw new Exception("Cannot find the '$provider' provider. It is either an invalid provider "
+ . "ID or the ID of a provider that is not working.");
+ }
+
+ $location = $oProvider->getLocation(array('ip' => $ip));
+ if (empty($location)) {
+ throw new Exception("Could not geolocate '$ip'!");
+ }
+ $location['ip'] = $ip;
+ return $location;
+ }
+
+ protected function getDataTable($name, $idSite, $period, $date, $segment)
+ {
+ Piwik::checkUserHasViewAccess($idSite);
+ $archive = Piwik_Archive::build($idSite, $period, $date, $segment);
+ $dataTable = $archive->getDataTable($name);
+ $dataTable->filter('Sort', array(Piwik_Archive::INDEX_NB_VISITS));
+ $dataTable->queueFilter('ReplaceColumnNames');
+ return $dataTable;
+ }
+
+ public function getNumberOfDistinctCountries($idSite, $period, $date, $segment = false)
+ {
+ Piwik::checkUserHasViewAccess($idSite);
+ $archive = Piwik_Archive::build($idSite, $period, $date, $segment);
+ return $archive->getDataTableFromNumeric('UserCountry_distinctCountries');
+ }
}
diff --git a/plugins/UserCountry/Controller.php b/plugins/UserCountry/Controller.php
index 01d7066fe5..4e305d27c7 100644
--- a/plugins/UserCountry/Controller.php
+++ b/plugins/UserCountry/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_UserCountry
*/
@@ -15,494 +15,467 @@
*/
class Piwik_UserCountry_Controller extends Piwik_Controller_Admin
{
- function index()
- {
- $view = Piwik_View::factory('index');
-
- $view->urlSparklineCountries = $this->getUrlSparkline('getLastDistinctCountriesGraph');
- $view->numberDistinctCountries = $this->getNumberOfDistinctCountries(true);
-
- $view->dataTableCountry = $this->getCountry(true);
- $view->dataTableContinent = $this->getContinent(true);
- $view->dataTableRegion = $this->getRegion(true);
- $view->dataTableCity = $this->getCity(true);
-
- echo $view->render();
- }
-
- function adminIndex()
- {
- Piwik::checkUserIsSuperUser();
- $view = Piwik_View::factory('adminIndex');
-
- $allProviderInfo = Piwik_UserCountry_LocationProvider::getAllProviderInfo(
- $newline = '<br/>', $includeExtra = true);
- $view->locationProviders = $allProviderInfo;
- $view->currentProviderId = Piwik_UserCountry_LocationProvider::getCurrentProviderId();
- $view->thisIP = Piwik_IP::getIpFromHeader();
- $geoIPDatabasesInstalled = Piwik_UserCountry_LocationProvider_GeoIp::isDatabaseInstalled();
- $view->geoIPDatabasesInstalled = $geoIPDatabasesInstalled;
-
- // check if there is a working provider (that isn't the default one)
- $isThereWorkingProvider = false;
- foreach ($allProviderInfo as $id => $provider)
- {
- if ($id != Piwik_UserCountry_LocationProvider_Default::ID
- && $provider['status'] == Piwik_UserCountry_LocationProvider::INSTALLED)
- {
- $isThereWorkingProvider = true;
- break;
- }
- }
- $view->isThereWorkingProvider = $isThereWorkingProvider;
-
- // if using either the Apache or PECL module, they are working and there are no databases
- // in misc, then the databases are located outside of Piwik, so we cannot update them
- $view->showGeoIPUpdateSection = true;
- $currentProviderId = Piwik_UserCountry_LocationProvider::getCurrentProviderId();
- if (!$geoIPDatabasesInstalled
- && ($currentProviderId == Piwik_UserCountry_LocationProvider_GeoIp_ServerBased::ID
- || $currentProviderId == Piwik_UserCountry_LocationProvider_GeoIp_Pecl::ID)
- && $allProviderInfo[$currentProviderId]['status'] == Piwik_UserCountry_LocationProvider::INSTALLED)
- {
- $view->showGeoIPUpdateSection = false;
- }
-
- $this->setUpdaterManageVars($view);
- $this->setBasicVariablesView($view);
- Piwik_Controller_Admin::setBasicVariablesAdminView($view);
- $view->menu = Piwik_GetAdminMenu();
-
- echo $view->render();
- }
-
- /**
- * Starts or continues download of GeoLiteCity.dat.
- *
- * To avoid a server/PHP timeout & to show progress of the download to the user, we
- * use the HTTP Range header to download one chunk of the file at a time. After each
- * chunk, it is the browser's responsibility to call the method again to continue the download.
- *
- * Input:
- * 'continue' query param - if set to 1, will assume we are currently downloading & use
- * Range: HTTP header to get another chunk of the file.
- *
- * Output (in JSON):
- * 'current_size' - Current size of the partially downloaded file on disk.
- * 'expected_file_size' - The expected finished file size as returned by the HTTP server.
- * 'next_screen' - When the download finishes, this is the next screen that should be shown.
- * 'error' - When an error occurs, the message is returned in this property.
- */
- public function downloadFreeGeoIPDB()
- {
- Piwik::checkUserIsSuperUser();
- if ($_SERVER["REQUEST_METHOD"] == "POST")
- {
- $this->checkTokenInUrl();
- Piwik_DataTable_Renderer_Json::sendHeaderJSON();
- $outputPath = Piwik_UserCountry_LocationProvider_GeoIp::getPathForGeoIpDatabase('GeoIPCity.dat').'.gz';
- try
- {
- $result = Piwik_Http::downloadChunk(
- $url = Piwik_UserCountry_LocationProvider_GeoIp::GEO_LITE_URL,
- $outputPath,
- $continue = Piwik_Common::getRequestVar('continue', true, 'int')
- );
-
- // if the file is done
- if ($result['current_size'] >= $result['expected_file_size'])
- {
- Piwik_UserCountry_GeoIPAutoUpdater::unzipDownloadedFile($outputPath, $unlink = true);
-
- // setup the auto updater
- Piwik_UserCountry_GeoIPAutoUpdater::setUpdaterOptions(array(
- 'loc_db' => Piwik_UserCountry_LocationProvider_GeoIp::GEO_LITE_URL,
- 'period' => Piwik_UserCountry_GeoIPAutoUpdater::SCHEDULE_PERIOD_MONTHLY,
- ));
-
- // make sure to echo out the geoip updater management screen
- $result['next_screen'] = $this->getGeoIpUpdaterManageScreen();
- }
-
- echo Piwik_Common::json_encode($result);
- }
- catch (Exception $ex)
- {
- echo Piwik_Common::json_encode(array('error' => $ex->getMessage()));
- }
- }
- }
-
- /**
- * Renders and returns the HTML that manages the GeoIP auto-updater.
- *
- * @return string
- */
- private function getGeoIpUpdaterManageScreen()
- {
- $view = Piwik_View::factory('updaterSetup');
- $view->geoIPDatabasesInstalled = true;
- $this->setUpdaterManageVars($view);
- return $view->render();
- }
-
- /**
- * Sets some variables needed by the updaterSetup.tpl template.
- *
- * @param Piwik_View $view
- */
- private function setUpdaterManageVars( $view )
- {
- $urls = Piwik_UserCountry_GeoIPAutoUpdater::getConfiguredUrls();
-
- $view->geoIPLocUrl = $urls['loc'];
- $view->geoIPIspUrl = $urls['isp'];
- $view->geoIPOrgUrl = $urls['org'];
- $view->geoIPUpdatePeriod = Piwik_UserCountry_GeoIPAutoUpdater::getSchedulePeriod();
-
- $view->geoLiteUrl = Piwik_UserCountry_LocationProvider_GeoIp::GEO_LITE_URL;
-
- $lastRunTime = Piwik_UserCountry_GeoIPAutoUpdater::getLastRunTime();
- if ($lastRunTime !== false)
- {
- $view->lastTimeUpdaterRun = '<strong><em>'.$lastRunTime->toString().'</em></strong>';
- }
- }
-
- /**
- * Sets the URLs used to download new versions of the installed GeoIP databases.
- *
- * Input (query params):
- * 'loc_db' - URL for a GeoIP location database.
- * 'isp_db' - URL for a GeoIP ISP database (optional).
- * 'org_db' - URL for a GeoIP Org database (optional).
- * 'period' - 'weekly' or 'monthly'. Determines how often update is run.
- *
- * Output (json):
- * 'error' - if an error occurs its message is set as the resulting JSON object's
- * 'error' property.
- */
- public function updateGeoIPLinks()
- {
- Piwik::checkUserIsSuperUser();
- if ($_SERVER["REQUEST_METHOD"] == "POST")
- {
- Piwik_DataTable_Renderer_Json::sendHeaderJSON();
- try
- {
- $this->checkTokenInUrl();
-
- Piwik_UserCountry_GeoIPAutoUpdater::setUpdaterOptionsFromUrl();
-
- // if there is a updater URL for a database, but its missing from the misc dir, tell
- // the browser so it can download it next
- $info = $this->getNextMissingDbUrlInfo();
- if ($info !== false)
- {
- echo Piwik_Common::json_encode($info);
- return;
- }
- }
- catch (Exception $ex)
- {
- echo Piwik_Common::json_encode(array('error' => $ex->getMessage()));
- }
- }
- }
-
- /**
- * Starts or continues a download for a missing GeoIP database. A database is missing if
- * it has an update URL configured, but the actual database is not available in the misc
- * directory.
- *
- * Input:
- * 'url' - The URL to download the database from.
- * 'continue' - 1 if we're continuing a download, 0 if we're starting one.
- *
- * Output:
- * 'error' - If an error occurs this describes the error.
- * 'to_download' - The URL of a missing database that should be downloaded next (if any).
- * 'to_download_label' - The label to use w/ the progress bar that describes what we're
- * downloading.
- * 'current_size' - Size of the current file on disk.
- * 'expected_file_size' - Size of the completely downloaded file.
- */
- public function downloadMissingGeoIpDb()
- {
- Piwik::checkUserIsSuperUser();
- if ($_SERVER["REQUEST_METHOD"] == "POST")
- {
- try
- {
- $this->checkTokenInUrl();
-
- Piwik_DataTable_Renderer_Json::sendHeaderJSON();
-
- // based on the database type (provided by the 'key' query param) determine the
- // url & output file name
- $key = Piwik_Common::getRequestVar('key', null, 'string');
- $url = Piwik_UserCountry_GeoIPAutoUpdater::getConfiguredUrl($key);
-
- $ext = Piwik_UserCountry_GeoIPAutoUpdater::getGeoIPUrlExtension($url);
- $filename = Piwik_UserCountry_LocationProvider_GeoIp::$dbNames[$key][0].'.'.$ext;
-
- if (substr($filename, 0, 15) == 'GeoLiteCity.dat')
- {
- $filename = 'GeoIPCity.dat'.substr($filename, 15);
- }
- $outputPath = Piwik_UserCountry_LocationProvider_GeoIp::getPathForGeoIpDatabase($filename);
-
- // download part of the file
- $result = Piwik_Http::downloadChunk(
- $url, $outputPath, Piwik_Common::getRequestVar('continue', true, 'int'));
-
- // if the file is done
- if ($result['current_size'] >= $result['expected_file_size'])
- {
- Piwik_UserCountry_GeoIPAutoUpdater::unzipDownloadedFile($outputPath, $unlink = true);
-
- $info = $this->getNextMissingDbUrlInfo();
- if ($info !== false)
- {
- echo Piwik_Common::json_encode($info);
- return;
- }
- }
-
- echo Piwik_Common::json_encode($result);
- }
- catch (Exception $ex)
- {
- echo Piwik_Common::json_encode(array('error' => $ex->getMessage()));
- }
- }
- }
-
- /**
- * Sets the current LocationProvider type.
- *
- * Input:
- * Requires the 'id' query parameter to be set to the desired LocationProvider's ID.
- *
- * Output:
- * Nothing.
- */
- public function setCurrentLocationProvider()
- {
- Piwik::checkUserIsSuperUser();
- if ($_SERVER["REQUEST_METHOD"] == "POST")
- {
- $this->checkTokenInUrl();
-
- $providerId = Piwik_Common::getRequestVar('id');
- $provider = Piwik_UserCountry_LocationProvider::setCurrentProvider($providerId);
- if ($provider === false)
- {
- throw new Exception("Invalid provider ID: '$providerId'.");
- }
- }
- }
-
- /**
- * Echo's a pretty formatted location using a specific LocationProvider.
- *
- * Input:
- * The 'id' query parameter must be set to the ID of the LocationProvider to use.
- *
- * Output:
- * The pretty formatted location that was obtained. Will be HTML.
- */
- public function getLocationUsingProvider()
- {
- $providerId = Piwik_Common::getRequestVar('id');
- $provider = $provider = Piwik_UserCountry_LocationProvider::getProviderById($providerId);
- if ($provider === false)
- {
- throw new Exception("Invalid provider ID: '$providerId'.");
- }
-
- $location = $provider->getLocation(array('ip' => Piwik_IP::getIpFromHeader(),
- 'lang' => Piwik_Common::getBrowserLanguage(),
- 'disable_fallbacks' => true));
- $location = Piwik_UserCountry_LocationProvider::prettyFormatLocation(
- $location, $newline = '<br/>', $includeExtra = true);
-
- echo $location;
- }
-
- function getCountry( $fetch = false)
- {
- $view = $this->getStandardDataTableUserCountry(__FUNCTION__, "UserCountry.getCountry");
- $view->setLimit( 5 );
- $view->setColumnTranslation('label', Piwik_Translate('UserCountry_Country'));
- $view->setReportDocumentation(Piwik_Translate('UserCountry_getCountryDocumentation'));
- return $this->renderView($view, $fetch);
- }
-
- function getContinent( $fetch = false)
- {
- $view = $this->getStandardDataTableUserCountry(__FUNCTION__, "UserCountry.getContinent", 'table');
- $view->disableSearchBox();
- $view->disableOffsetInformationAndPaginationControls();
- $view->setColumnTranslation('label', Piwik_Translate('UserCountry_Continent'));
- $view->setReportDocumentation(Piwik_Translate('UserCountry_getContinentDocumentation'));
- return $this->renderView($view, $fetch);
- }
-
- /**
- * Echo's or returns an HTML view of the visits by region report.
- *
- * @param bool $fetch If true, returns the HTML as a string, otherwise it is echo'd.
- * @return string
- */
- public function getRegion( $fetch = false )
- {
- $view = $this->getStandardDataTableUserCountry(__FUNCTION__, "UserCountry.getRegion");
- $view->setLimit(5);
- $view->setColumnTranslation('label', Piwik_Translate('UserCountry_Region'));
- $view->setReportDocumentation(Piwik_Translate('UserCountry_getRegionDocumentation').'<br/>'
- . $this->getGeoIPReportDocSuffix());
- $this->checkIfNoDataForGeoIpReport($view);
- return $this->renderView($view, $fetch);
- }
-
- /**
- * Echo's or returns an HTML view of the visits by city report.
- *
- * @param bool $fetch If true, returns the HTML as a string, otherwise it is echo'd.
- * @return string
- */
- public function getCity( $fetch = false )
- {
- $view = $this->getStandardDataTableUserCountry(__FUNCTION__, "UserCountry.getCity");
- $view->setLimit(5);
- $view->setColumnTranslation('label', Piwik_Translate('UserCountry_City'));
- $view->setReportDocumentation(Piwik_Translate('UserCountry_getCityDocumentation').'<br/>'
- . $this->getGeoIPReportDocSuffix());
- $this->checkIfNoDataForGeoIpReport($view);
- return $this->renderView($view, $fetch);
- }
-
- private function getGeoIPReportDocSuffix()
- {
- return Piwik_Translate('UserCountry_GeoIPDocumentationSuffix', array(
- '<a target="_blank" href="http://www.maxmind.com/?rId=piwik">',
- '</a>',
- '<a target="_blank" href="http://www.maxmind.com/en/city_accuracy?rId=piwik">',
- '</a>'
- ));
- }
-
- protected function getStandardDataTableUserCountry( $currentControllerAction,
- $APItoCall,
- $defaultDatatableType = null )
- {
- $view = Piwik_ViewDataTable::factory( $defaultDatatableType );
- $view->init( $this->pluginName, $currentControllerAction, $APItoCall );
- $view->disableExcludeLowPopulation();
-
- $this->setPeriodVariablesView($view);
- $this->setMetricsVariablesView($view);
-
- $view->enableShowGoals();
-
- return $view;
- }
-
- function getNumberOfDistinctCountries( $fetch = false)
- {
- return $this->getNumericValue('UserCountry.getNumberOfDistinctCountries');
- }
-
- function getLastDistinctCountriesGraph( $fetch = false )
- {
- $view = $this->getLastUnitGraph('UserCountry',__FUNCTION__, "UserCountry.getNumberOfDistinctCountries");
- $view->setColumnsToDisplay('UserCountry_distinctCountries');
- return $this->renderView($view, $fetch);
- }
-
- /**
- * Checks if a datatable for a view is empty and if so, displays a message in the footer
- * telling users to configure GeoIP.
- */
- private function checkIfNoDataForGeoIpReport( $view )
- {
- // only display on HTML tables since the datatable for HTML graphs aren't accessible
- if (!($view instanceof Piwik_ViewDataTable_HtmlTable))
- {
- return;
- }
-
- // if there's only one row whose label is 'Unknown', display a message saying there's no data
- $view->main();
- $dataTable = $view->getDataTable();
- if ($dataTable->getRowsCount() == 1
- && $dataTable->getFirstRow()->getColumn('label') == Piwik_Translate('General_Unknown'))
- {
- $footerMessage = Piwik_Translate('UserCountry_NoDataForGeoIPReport1');
-
- // if GeoIP is working, don't display this part of the message
- if (!$this->isGeoIPWorking())
- {
- $params = array('module' => 'UserCountry', 'action' => 'adminIndex');
- $footerMessage .= ' '.Piwik_Translate('UserCountry_NoDataForGeoIPReport2', array(
- '<a target="_blank" href="'.Piwik_Url::getCurrentQueryStringWithParametersModified($params).'">',
- '</a>',
- '<a target="_blank" href="http://dev.maxmind.com/geoip/geolite?rId=piwik">',
- '</a>'
- ));
- }
- else
- {
- $footerMessage .= ' '.Piwik_Translate('UserCountry_ToGeolocateOldVisits', array(
- '<a target="_blank" href="http://piwik.org/faq/how-to/#faq_167">',
- '</a>'
- ));
- }
-
- // HACK! Can't use setFooterMessage because the view gets built in the main function,
- // so instead we set the property by hand.
- $realView = $view->getView();
- $properties = $realView->properties;
- $properties['show_footer_message'] = $footerMessage;
- $realView->properties = $properties;
- }
- }
-
- /**
- * Gets information for the first missing GeoIP database (if any).
- *
- * @return bool
- */
- private function getNextMissingDbUrlInfo()
- {
- $missingDbs = Piwik_UserCountry_GeoIPAutoUpdater::getMissingDatabases();
- if (!empty($missingDbs))
- {
- $missingDbKey = $missingDbs[0];
- $missingDbName = Piwik_UserCountry_LocationProvider_GeoIp::$dbNames[$missingDbKey][0];
- $url = Piwik_UserCountry_GeoIPAutoUpdater::getConfiguredUrl($missingDbKey);
-
- $link = '<a href="'.$url.'">'.$missingDbName.'</a>';
-
- return array(
- 'to_download' => $missingDbKey,
- 'to_download_label' => Piwik_Translate('UserCountry_DownloadingDb', $link).'...',
- );
- }
- return false;
- }
-
- /**
- * Returns true if a GeoIP provider is installed & working, false if otherwise.
- *
- * @return bool
- */
- private function isGeoIPWorking()
- {
- $provider = Piwik_UserCountry_LocationProvider::getCurrentProvider();
- return $provider instanceof Piwik_UserCountry_LocationProvider_GeoIp
- && $provider->isAvailable() === true
- && $provider->isWorking() === true;
- }
+ function index()
+ {
+ $view = Piwik_View::factory('index');
+
+ $view->urlSparklineCountries = $this->getUrlSparkline('getLastDistinctCountriesGraph');
+ $view->numberDistinctCountries = $this->getNumberOfDistinctCountries(true);
+
+ $view->dataTableCountry = $this->getCountry(true);
+ $view->dataTableContinent = $this->getContinent(true);
+ $view->dataTableRegion = $this->getRegion(true);
+ $view->dataTableCity = $this->getCity(true);
+
+ echo $view->render();
+ }
+
+ function adminIndex()
+ {
+ Piwik::checkUserIsSuperUser();
+ $view = Piwik_View::factory('adminIndex');
+
+ $allProviderInfo = Piwik_UserCountry_LocationProvider::getAllProviderInfo(
+ $newline = '<br/>', $includeExtra = true);
+ $view->locationProviders = $allProviderInfo;
+ $view->currentProviderId = Piwik_UserCountry_LocationProvider::getCurrentProviderId();
+ $view->thisIP = Piwik_IP::getIpFromHeader();
+ $geoIPDatabasesInstalled = Piwik_UserCountry_LocationProvider_GeoIp::isDatabaseInstalled();
+ $view->geoIPDatabasesInstalled = $geoIPDatabasesInstalled;
+
+ // check if there is a working provider (that isn't the default one)
+ $isThereWorkingProvider = false;
+ foreach ($allProviderInfo as $id => $provider) {
+ if ($id != Piwik_UserCountry_LocationProvider_Default::ID
+ && $provider['status'] == Piwik_UserCountry_LocationProvider::INSTALLED
+ ) {
+ $isThereWorkingProvider = true;
+ break;
+ }
+ }
+ $view->isThereWorkingProvider = $isThereWorkingProvider;
+
+ // if using either the Apache or PECL module, they are working and there are no databases
+ // in misc, then the databases are located outside of Piwik, so we cannot update them
+ $view->showGeoIPUpdateSection = true;
+ $currentProviderId = Piwik_UserCountry_LocationProvider::getCurrentProviderId();
+ if (!$geoIPDatabasesInstalled
+ && ($currentProviderId == Piwik_UserCountry_LocationProvider_GeoIp_ServerBased::ID
+ || $currentProviderId == Piwik_UserCountry_LocationProvider_GeoIp_Pecl::ID)
+ && $allProviderInfo[$currentProviderId]['status'] == Piwik_UserCountry_LocationProvider::INSTALLED
+ ) {
+ $view->showGeoIPUpdateSection = false;
+ }
+
+ $this->setUpdaterManageVars($view);
+ $this->setBasicVariablesView($view);
+ Piwik_Controller_Admin::setBasicVariablesAdminView($view);
+ $view->menu = Piwik_GetAdminMenu();
+
+ echo $view->render();
+ }
+
+ /**
+ * Starts or continues download of GeoLiteCity.dat.
+ *
+ * To avoid a server/PHP timeout & to show progress of the download to the user, we
+ * use the HTTP Range header to download one chunk of the file at a time. After each
+ * chunk, it is the browser's responsibility to call the method again to continue the download.
+ *
+ * Input:
+ * 'continue' query param - if set to 1, will assume we are currently downloading & use
+ * Range: HTTP header to get another chunk of the file.
+ *
+ * Output (in JSON):
+ * 'current_size' - Current size of the partially downloaded file on disk.
+ * 'expected_file_size' - The expected finished file size as returned by the HTTP server.
+ * 'next_screen' - When the download finishes, this is the next screen that should be shown.
+ * 'error' - When an error occurs, the message is returned in this property.
+ */
+ public function downloadFreeGeoIPDB()
+ {
+ Piwik::checkUserIsSuperUser();
+ if ($_SERVER["REQUEST_METHOD"] == "POST") {
+ $this->checkTokenInUrl();
+ Piwik_DataTable_Renderer_Json::sendHeaderJSON();
+ $outputPath = Piwik_UserCountry_LocationProvider_GeoIp::getPathForGeoIpDatabase('GeoIPCity.dat') . '.gz';
+ try {
+ $result = Piwik_Http::downloadChunk(
+ $url = Piwik_UserCountry_LocationProvider_GeoIp::GEO_LITE_URL,
+ $outputPath,
+ $continue = Piwik_Common::getRequestVar('continue', true, 'int')
+ );
+
+ // if the file is done
+ if ($result['current_size'] >= $result['expected_file_size']) {
+ Piwik_UserCountry_GeoIPAutoUpdater::unzipDownloadedFile($outputPath, $unlink = true);
+
+ // setup the auto updater
+ Piwik_UserCountry_GeoIPAutoUpdater::setUpdaterOptions(array(
+ 'loc_db' => Piwik_UserCountry_LocationProvider_GeoIp::GEO_LITE_URL,
+ 'period' => Piwik_UserCountry_GeoIPAutoUpdater::SCHEDULE_PERIOD_MONTHLY,
+ ));
+
+ // make sure to echo out the geoip updater management screen
+ $result['next_screen'] = $this->getGeoIpUpdaterManageScreen();
+ }
+
+ echo Piwik_Common::json_encode($result);
+ } catch (Exception $ex) {
+ echo Piwik_Common::json_encode(array('error' => $ex->getMessage()));
+ }
+ }
+ }
+
+ /**
+ * Renders and returns the HTML that manages the GeoIP auto-updater.
+ *
+ * @return string
+ */
+ private function getGeoIpUpdaterManageScreen()
+ {
+ $view = Piwik_View::factory('updaterSetup');
+ $view->geoIPDatabasesInstalled = true;
+ $this->setUpdaterManageVars($view);
+ return $view->render();
+ }
+
+ /**
+ * Sets some variables needed by the updaterSetup.tpl template.
+ *
+ * @param Piwik_View $view
+ */
+ private function setUpdaterManageVars($view)
+ {
+ $urls = Piwik_UserCountry_GeoIPAutoUpdater::getConfiguredUrls();
+
+ $view->geoIPLocUrl = $urls['loc'];
+ $view->geoIPIspUrl = $urls['isp'];
+ $view->geoIPOrgUrl = $urls['org'];
+ $view->geoIPUpdatePeriod = Piwik_UserCountry_GeoIPAutoUpdater::getSchedulePeriod();
+
+ $view->geoLiteUrl = Piwik_UserCountry_LocationProvider_GeoIp::GEO_LITE_URL;
+
+ $lastRunTime = Piwik_UserCountry_GeoIPAutoUpdater::getLastRunTime();
+ if ($lastRunTime !== false) {
+ $view->lastTimeUpdaterRun = '<strong><em>' . $lastRunTime->toString() . '</em></strong>';
+ }
+ }
+
+ /**
+ * Sets the URLs used to download new versions of the installed GeoIP databases.
+ *
+ * Input (query params):
+ * 'loc_db' - URL for a GeoIP location database.
+ * 'isp_db' - URL for a GeoIP ISP database (optional).
+ * 'org_db' - URL for a GeoIP Org database (optional).
+ * 'period' - 'weekly' or 'monthly'. Determines how often update is run.
+ *
+ * Output (json):
+ * 'error' - if an error occurs its message is set as the resulting JSON object's
+ * 'error' property.
+ */
+ public function updateGeoIPLinks()
+ {
+ Piwik::checkUserIsSuperUser();
+ if ($_SERVER["REQUEST_METHOD"] == "POST") {
+ Piwik_DataTable_Renderer_Json::sendHeaderJSON();
+ try {
+ $this->checkTokenInUrl();
+
+ Piwik_UserCountry_GeoIPAutoUpdater::setUpdaterOptionsFromUrl();
+
+ // if there is a updater URL for a database, but its missing from the misc dir, tell
+ // the browser so it can download it next
+ $info = $this->getNextMissingDbUrlInfo();
+ if ($info !== false) {
+ echo Piwik_Common::json_encode($info);
+ return;
+ }
+ } catch (Exception $ex) {
+ echo Piwik_Common::json_encode(array('error' => $ex->getMessage()));
+ }
+ }
+ }
+
+ /**
+ * Starts or continues a download for a missing GeoIP database. A database is missing if
+ * it has an update URL configured, but the actual database is not available in the misc
+ * directory.
+ *
+ * Input:
+ * 'url' - The URL to download the database from.
+ * 'continue' - 1 if we're continuing a download, 0 if we're starting one.
+ *
+ * Output:
+ * 'error' - If an error occurs this describes the error.
+ * 'to_download' - The URL of a missing database that should be downloaded next (if any).
+ * 'to_download_label' - The label to use w/ the progress bar that describes what we're
+ * downloading.
+ * 'current_size' - Size of the current file on disk.
+ * 'expected_file_size' - Size of the completely downloaded file.
+ */
+ public function downloadMissingGeoIpDb()
+ {
+ Piwik::checkUserIsSuperUser();
+ if ($_SERVER["REQUEST_METHOD"] == "POST") {
+ try {
+ $this->checkTokenInUrl();
+
+ Piwik_DataTable_Renderer_Json::sendHeaderJSON();
+
+ // based on the database type (provided by the 'key' query param) determine the
+ // url & output file name
+ $key = Piwik_Common::getRequestVar('key', null, 'string');
+ $url = Piwik_UserCountry_GeoIPAutoUpdater::getConfiguredUrl($key);
+
+ $ext = Piwik_UserCountry_GeoIPAutoUpdater::getGeoIPUrlExtension($url);
+ $filename = Piwik_UserCountry_LocationProvider_GeoIp::$dbNames[$key][0] . '.' . $ext;
+
+ if (substr($filename, 0, 15) == 'GeoLiteCity.dat') {
+ $filename = 'GeoIPCity.dat' . substr($filename, 15);
+ }
+ $outputPath = Piwik_UserCountry_LocationProvider_GeoIp::getPathForGeoIpDatabase($filename);
+
+ // download part of the file
+ $result = Piwik_Http::downloadChunk(
+ $url, $outputPath, Piwik_Common::getRequestVar('continue', true, 'int'));
+
+ // if the file is done
+ if ($result['current_size'] >= $result['expected_file_size']) {
+ Piwik_UserCountry_GeoIPAutoUpdater::unzipDownloadedFile($outputPath, $unlink = true);
+
+ $info = $this->getNextMissingDbUrlInfo();
+ if ($info !== false) {
+ echo Piwik_Common::json_encode($info);
+ return;
+ }
+ }
+
+ echo Piwik_Common::json_encode($result);
+ } catch (Exception $ex) {
+ echo Piwik_Common::json_encode(array('error' => $ex->getMessage()));
+ }
+ }
+ }
+
+ /**
+ * Sets the current LocationProvider type.
+ *
+ * Input:
+ * Requires the 'id' query parameter to be set to the desired LocationProvider's ID.
+ *
+ * Output:
+ * Nothing.
+ */
+ public function setCurrentLocationProvider()
+ {
+ Piwik::checkUserIsSuperUser();
+ if ($_SERVER["REQUEST_METHOD"] == "POST") {
+ $this->checkTokenInUrl();
+
+ $providerId = Piwik_Common::getRequestVar('id');
+ $provider = Piwik_UserCountry_LocationProvider::setCurrentProvider($providerId);
+ if ($provider === false) {
+ throw new Exception("Invalid provider ID: '$providerId'.");
+ }
+ }
+ }
+
+ /**
+ * Echo's a pretty formatted location using a specific LocationProvider.
+ *
+ * Input:
+ * The 'id' query parameter must be set to the ID of the LocationProvider to use.
+ *
+ * Output:
+ * The pretty formatted location that was obtained. Will be HTML.
+ */
+ public function getLocationUsingProvider()
+ {
+ $providerId = Piwik_Common::getRequestVar('id');
+ $provider = $provider = Piwik_UserCountry_LocationProvider::getProviderById($providerId);
+ if ($provider === false) {
+ throw new Exception("Invalid provider ID: '$providerId'.");
+ }
+
+ $location = $provider->getLocation(array('ip' => Piwik_IP::getIpFromHeader(),
+ 'lang' => Piwik_Common::getBrowserLanguage(),
+ 'disable_fallbacks' => true));
+ $location = Piwik_UserCountry_LocationProvider::prettyFormatLocation(
+ $location, $newline = '<br/>', $includeExtra = true);
+
+ echo $location;
+ }
+
+ function getCountry($fetch = false)
+ {
+ $view = $this->getStandardDataTableUserCountry(__FUNCTION__, "UserCountry.getCountry");
+ $view->setLimit(5);
+ $view->setColumnTranslation('label', Piwik_Translate('UserCountry_Country'));
+ $view->setReportDocumentation(Piwik_Translate('UserCountry_getCountryDocumentation'));
+ return $this->renderView($view, $fetch);
+ }
+
+ function getContinent($fetch = false)
+ {
+ $view = $this->getStandardDataTableUserCountry(__FUNCTION__, "UserCountry.getContinent", 'table');
+ $view->disableSearchBox();
+ $view->disableOffsetInformationAndPaginationControls();
+ $view->setColumnTranslation('label', Piwik_Translate('UserCountry_Continent'));
+ $view->setReportDocumentation(Piwik_Translate('UserCountry_getContinentDocumentation'));
+ return $this->renderView($view, $fetch);
+ }
+
+ /**
+ * Echo's or returns an HTML view of the visits by region report.
+ *
+ * @param bool $fetch If true, returns the HTML as a string, otherwise it is echo'd.
+ * @return string
+ */
+ public function getRegion($fetch = false)
+ {
+ $view = $this->getStandardDataTableUserCountry(__FUNCTION__, "UserCountry.getRegion");
+ $view->setLimit(5);
+ $view->setColumnTranslation('label', Piwik_Translate('UserCountry_Region'));
+ $view->setReportDocumentation(Piwik_Translate('UserCountry_getRegionDocumentation') . '<br/>'
+ . $this->getGeoIPReportDocSuffix());
+ $this->checkIfNoDataForGeoIpReport($view);
+ return $this->renderView($view, $fetch);
+ }
+
+ /**
+ * Echo's or returns an HTML view of the visits by city report.
+ *
+ * @param bool $fetch If true, returns the HTML as a string, otherwise it is echo'd.
+ * @return string
+ */
+ public function getCity($fetch = false)
+ {
+ $view = $this->getStandardDataTableUserCountry(__FUNCTION__, "UserCountry.getCity");
+ $view->setLimit(5);
+ $view->setColumnTranslation('label', Piwik_Translate('UserCountry_City'));
+ $view->setReportDocumentation(Piwik_Translate('UserCountry_getCityDocumentation') . '<br/>'
+ . $this->getGeoIPReportDocSuffix());
+ $this->checkIfNoDataForGeoIpReport($view);
+ return $this->renderView($view, $fetch);
+ }
+
+ private function getGeoIPReportDocSuffix()
+ {
+ return Piwik_Translate('UserCountry_GeoIPDocumentationSuffix', array(
+ '<a target="_blank" href="http://www.maxmind.com/?rId=piwik">',
+ '</a>',
+ '<a target="_blank" href="http://www.maxmind.com/en/city_accuracy?rId=piwik">',
+ '</a>'
+ ));
+ }
+
+ protected function getStandardDataTableUserCountry($currentControllerAction,
+ $APItoCall,
+ $defaultDatatableType = null)
+ {
+ $view = Piwik_ViewDataTable::factory($defaultDatatableType);
+ $view->init($this->pluginName, $currentControllerAction, $APItoCall);
+ $view->disableExcludeLowPopulation();
+
+ $this->setPeriodVariablesView($view);
+ $this->setMetricsVariablesView($view);
+
+ $view->enableShowGoals();
+
+ return $view;
+ }
+
+ function getNumberOfDistinctCountries($fetch = false)
+ {
+ return $this->getNumericValue('UserCountry.getNumberOfDistinctCountries');
+ }
+
+ function getLastDistinctCountriesGraph($fetch = false)
+ {
+ $view = $this->getLastUnitGraph('UserCountry', __FUNCTION__, "UserCountry.getNumberOfDistinctCountries");
+ $view->setColumnsToDisplay('UserCountry_distinctCountries');
+ return $this->renderView($view, $fetch);
+ }
+
+ /**
+ * Checks if a datatable for a view is empty and if so, displays a message in the footer
+ * telling users to configure GeoIP.
+ */
+ private function checkIfNoDataForGeoIpReport($view)
+ {
+ // only display on HTML tables since the datatable for HTML graphs aren't accessible
+ if (!($view instanceof Piwik_ViewDataTable_HtmlTable)) {
+ return;
+ }
+
+ // if there's only one row whose label is 'Unknown', display a message saying there's no data
+ $view->main();
+ $dataTable = $view->getDataTable();
+ if ($dataTable->getRowsCount() == 1
+ && $dataTable->getFirstRow()->getColumn('label') == Piwik_Translate('General_Unknown')
+ ) {
+ $footerMessage = Piwik_Translate('UserCountry_NoDataForGeoIPReport1');
+
+ // if GeoIP is working, don't display this part of the message
+ if (!$this->isGeoIPWorking()) {
+ $params = array('module' => 'UserCountry', 'action' => 'adminIndex');
+ $footerMessage .= ' ' . Piwik_Translate('UserCountry_NoDataForGeoIPReport2', array(
+ '<a target="_blank" href="' . Piwik_Url::getCurrentQueryStringWithParametersModified($params) . '">',
+ '</a>',
+ '<a target="_blank" href="http://dev.maxmind.com/geoip/geolite?rId=piwik">',
+ '</a>'
+ ));
+ } else {
+ $footerMessage .= ' ' . Piwik_Translate('UserCountry_ToGeolocateOldVisits', array(
+ '<a target="_blank" href="http://piwik.org/faq/how-to/#faq_167">',
+ '</a>'
+ ));
+ }
+
+ // HACK! Can't use setFooterMessage because the view gets built in the main function,
+ // so instead we set the property by hand.
+ $realView = $view->getView();
+ $properties = $realView->properties;
+ $properties['show_footer_message'] = $footerMessage;
+ $realView->properties = $properties;
+ }
+ }
+
+ /**
+ * Gets information for the first missing GeoIP database (if any).
+ *
+ * @return bool
+ */
+ private function getNextMissingDbUrlInfo()
+ {
+ $missingDbs = Piwik_UserCountry_GeoIPAutoUpdater::getMissingDatabases();
+ if (!empty($missingDbs)) {
+ $missingDbKey = $missingDbs[0];
+ $missingDbName = Piwik_UserCountry_LocationProvider_GeoIp::$dbNames[$missingDbKey][0];
+ $url = Piwik_UserCountry_GeoIPAutoUpdater::getConfiguredUrl($missingDbKey);
+
+ $link = '<a href="' . $url . '">' . $missingDbName . '</a>';
+
+ return array(
+ 'to_download' => $missingDbKey,
+ 'to_download_label' => Piwik_Translate('UserCountry_DownloadingDb', $link) . '...',
+ );
+ }
+ return false;
+ }
+
+ /**
+ * Returns true if a GeoIP provider is installed & working, false if otherwise.
+ *
+ * @return bool
+ */
+ private function isGeoIPWorking()
+ {
+ $provider = Piwik_UserCountry_LocationProvider::getCurrentProvider();
+ return $provider instanceof Piwik_UserCountry_LocationProvider_GeoIp
+ && $provider->isAvailable() === true
+ && $provider->isWorking() === true;
+ }
}
diff --git a/plugins/UserCountry/GeoIPAutoUpdater.php b/plugins/UserCountry/GeoIPAutoUpdater.php
index 1f29d8a1bb..a9aab90c63 100755
--- a/plugins/UserCountry/GeoIPAutoUpdater.php
+++ b/plugins/UserCountry/GeoIPAutoUpdater.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_UserCountry
*/
@@ -15,588 +15,540 @@
*/
class Piwik_UserCountry_GeoIPAutoUpdater
{
- const SCHEDULE_PERIOD_MONTHLY = 'month';
- const SCHEDULE_PERIOD_WEEKLY = 'week';
-
- const SCHEDULE_PERIOD_OPTION_NAME = 'geoip.updater_period';
- const LOC_URL_OPTION_NAME = 'geoip.loc_db_url';
- const ISP_URL_OPTION_NAME = 'geoip.isp_db_url';
- const ORG_URL_OPTION_NAME = 'geoip.org_db_url';
-
- const LAST_RUN_TIME_OPTION_NAME = 'geoip.updater_last_run_time';
-
- private static $urlOptions = array(
- 'loc' => self::LOC_URL_OPTION_NAME,
- 'isp' => self::ISP_URL_OPTION_NAME,
- 'org' => self::ORG_URL_OPTION_NAME,
- );
-
- /**
- * PHP Error caught through a custom error handler while trying to use a downloaded
- * GeoIP database. See catchGeoIPError for more info.
- *
- * @var array
- */
- private static $unzipPhpError = null;
-
- /**
- * Attempts to download new location, ISP & organization GeoIP databases and
- * replace the existing ones w/ them.
- */
- public function update()
- {
- try
- {
- Piwik_SetOption(self::LAST_RUN_TIME_OPTION_NAME, Piwik_Date::factory('today')->getTimestamp());
-
- $locUrl = Piwik_GetOption(self::LOC_URL_OPTION_NAME);
- if (!empty($locUrl))
- {
- $this->downloadFile('loc', $locUrl);
- }
-
- $ispUrl = Piwik_GetOption(self::ISP_URL_OPTION_NAME);
- if (!empty($ispUrl))
- {
- $this->downloadFile('isp', $ispUrl);
- }
-
- $orgUrl = Piwik_GetOption(self::ORG_URL_OPTION_NAME);
- if (!empty($orgUrl))
- {
- $this->downloadFile('org', $orgUrl);
- }
- }
- catch (Exception $ex)
- {
- // message will already be prefixed w/ 'Piwik_UserCountry_GeoIPAutoUpdater: '
- Piwik::log($ex->getMessage());
- $this->performRedundantDbChecks();
- throw $ex;
- }
-
- $this->performRedundantDbChecks();
- }
-
- /**
- * Downloads a GeoIP database archive, extracts the .dat file and overwrites the existing
- * old database.
- *
- * If something happens that causes the download to fail, no exception is thrown, but
- * an error is logged.
- *
- * @param string $url URL to the database to download. The type of database is determined
- * from this URL.
- */
- private function downloadFile( $dbType, $url )
- {
- $ext = Piwik_UserCountry_GeoIPAutoUpdater::getGeoIPUrlExtension($url);
- $zippedFilename = Piwik_UserCountry_LocationProvider_GeoIp::$dbNames[$dbType][0].'.'.$ext;
-
- $zippedOutputPath = Piwik_UserCountry_LocationProvider_GeoIp::getPathForGeoIpDatabase($zippedFilename);
-
- // download zipped file to misc dir
- try
- {
- $success = Piwik_Http::sendHttpRequest($url, $timeout = 3600, $userAgent = null, $zippedOutputPath);
- }
- catch (Exception $ex)
- {
- throw new Exception("Piwik_UserCountry_GeoIPAutoUpdater: failed to download '$url' to "
- . "'$zippedOutputPath': " . $ex->getMessage());
- }
-
- if ($success !== true)
- {
- throw new Exception("Piwik_UserCountry_GeoIPAutoUpdater: failed to download '$url' to "
- . "'$zippedOutputPath'! (Unknown error)");
- }
-
- Piwik::log(sprintf("Piwik_UserCountry_GeoIPAutoUpdater: successfully downloaded '%s'", $url));
-
- try
- {
- self::unzipDownloadedFile($zippedOutputPath, $unlink = true);
- }
- catch (Exception $ex)
- {
- throw new Exception("Piwik_UserCountry_GeoIPAutoUpdater: failed to unzip '$zippedOutputPath' after "
- . "downloading " . "'$url': ".$ex->getMessage());
- }
-
- Piwik::log(sprintf("Piwik_UserCountry_GeoIPAutoUpdater: successfully updated GeoIP database '%s'", $url));
- }
-
- /**
- * Unzips a downloaded GeoIP database. Only unzips .gz & .tar.gz files.
- *
- * @param string $path Path to zipped file.
- * @param bool $unlink Whether to unlink archive or not.
- */
- public static function unzipDownloadedFile( $path, $unlink = false )
- {
- $parts = explode('.', basename($path));
- $filenameStart = $parts[0];
-
- $dbFilename = $filenameStart.'.dat';
- $tempFilename = $filenameStart.'.dat.new';
- $outputPath = Piwik_UserCountry_LocationProvider_GeoIp::getPathForGeoIpDatabase($tempFilename);
-
- // extract file
- if (substr($path, -7, 7) == '.tar.gz')
- {
- // find the .dat file in the tar archive
- $unzip = Piwik_Unzip::factory('tar.gz', $path);
- $content = $unzip->listContent();
-
- if (empty($content))
- {
- throw new Exception(Piwik_Translate('UserCountry_CannotListContent',
- array("'$path'", $unzip->errorInfo())));
- }
-
- $datFile = null;
- foreach ($content as $info)
- {
- $archivedPath = $info['filename'];
- if (basename($archivedPath) === $dbFilename)
- {
- $datFile = $archivedPath;
- }
- }
-
- if ($datFile === null)
- {
- throw new Exception(Piwik_Translate('UserCountry_CannotFindGeoIPDatabaseInArchive',
- array($dbFilename, "'$path'")));
- }
-
- // extract JUST the .dat file
- $unzipped = $unzip->extractInString($datFile);
-
- if (empty($unzipped))
- {
- throw new Exception(Piwik_Translate('UserCountry_CannotUnzipDatFile',
- array("'$path'", $unzip->errorInfo())));
- }
-
- // write unzipped to file
- $fd = fopen($outputPath, 'wb');
- fwrite($fd, $unzipped);
- fclose($fd);
- }
- else if (substr($path, -3, 3) == '.gz')
- {
- $unzip = Piwik_Unzip::factory('gz', $path);
- $success = $unzip->extract($outputPath);
-
- if ($success !== true)
- {
- throw new Exception(Piwik_Translate('UserCountry_CannotUnzipDatFile',
- array("'$path'", $unzip->errorInfo())));
- }
- }
- else
- {
- $ext = end(explode(basename($path), '.', 2));
- throw new Exception(Piwik_Translate('UserCountry_UnsupportedArchiveType', "'$ext'"));
- }
-
- try
- {
- // test that the new archive is a valid GeoIP database
- $dbType = Piwik_UserCountry_LocationProvider_GeoIp::getGeoIPDatabaseTypeFromFilename($dbFilename);
- if ($dbType === false) // sanity check
- {
- throw new Exception("Unexpected GeoIP archive file name '$path'.");
- }
-
- $customDbNames = array(
- 'loc' => array(),
- 'isp' => array(),
- 'org' => array()
- );
- $customDbNames[$dbType] = array($tempFilename);
-
- $phpProvider = new Piwik_UserCountry_LocationProvider_GeoIp_Php($customDbNames);
-
- $location = self::getTestLocationCatchPhpErrors($phpProvider);
-
- if (empty($location)
- || self::$unzipPhpError !== null)
- {
- if (self::$unzipPhpError !== null)
- {
- list($errno, $errstr, $errfile, $errline) = self::$unzipPhpError;
- Piwik::log("Piwik_UserCountry_GeoIPAutoUpdater: Encountered PHP error when testing newly downloaded".
- " GeoIP database: $errno: $errstr on line $errline of $errfile.");
- }
-
- throw new Exception(Piwik_Translate('UserCountry_ThisUrlIsNotAValidGeoIPDB'));
- }
-
- // delete the existing GeoIP database (if any) and rename the downloaded file
- $oldDbFile = Piwik_UserCountry_LocationProvider_GeoIp::getPathForGeoIpDatabase($dbFilename);
- if (file_exists($oldDbFile))
- {
- unlink($oldDbFile);
- }
-
- $tempFile = Piwik_UserCountry_LocationProvider_GeoIp::getPathForGeoIpDatabase($tempFilename);
- rename($existing = $tempFile, $newName = $oldDbFile);
-
- // delete original archive
- if ($unlink)
- {
- unlink($path);
- }
- }
- catch (Exception $ex)
- {
- // remove downloaded files
- if (file_exists($outputPath))
- {
- unlink($outputPath);
- }
- unlink($path);
-
- throw $ex;
- }
- }
-
- /**
- * Creates a ScheduledTask instance based on set option values.
- *
- * @return Piwik_ScheduledTask
- */
- public static function makeScheduledTask()
- {
- $instance = new Piwik_UserCountry_GeoIPAutoUpdater();
-
- $schedulePeriodStr = self::getSchedulePeriod();
-
- // created the scheduledtime instance, also, since GeoIP updates are done on tuesdays,
- // get new DBs on Wednesday
- switch ($schedulePeriodStr)
- {
- case self::SCHEDULE_PERIOD_WEEKLY:
- $schedulePeriod = new Piwik_ScheduledTime_Weekly();
- $schedulePeriod->setDay(3);
- break;
- case self::SCHEDULE_PERIOD_MONTHLY:
- default:
- $schedulePeriod = new Piwik_ScheduledTime_Monthly();
- $schedulePeriod->setDayOfWeek(3, 0);
- break;
- }
-
- return new Piwik_ScheduledTask($instance, 'update', null, $schedulePeriod, Piwik_ScheduledTask::LOWEST_PRIORITY);
- }
-
- /**
- * Sets the options used by this class based on query parameter values.
- *
- * See setUpdaterOptions for query params used.
- */
- public static function setUpdaterOptionsFromUrl()
- {
- self::setUpdaterOptions(array(
- 'loc' => Piwik_Common::getRequestVar('loc_db', false, 'string'),
- 'isp' => Piwik_Common::getRequestVar('isp_db', false, 'string'),
- 'org' => Piwik_Common::getRequestVar('org_db', false, 'string'),
- 'period' => Piwik_Common::getRequestVar('period', false, 'string'),
- ));
- }
-
- /**
- * Sets the options used by this class based on the elements in $options.
- *
- * The following elements of $options are used:
- * 'loc' - URL for location database.
- * 'isp' - URL for ISP database.
- * 'org' - URL for Organization database.
- * 'period' - 'weekly' or 'monthly'. When to run the updates.
- *
- * @param array $options
- */
- public static function setUpdaterOptions( $options )
- {
- // set url options
- foreach (self::$urlOptions as $optionKey => $optionName)
- {
- if (!isset($options[$optionKey]))
- {
- continue;
- }
-
- Piwik_SetOption($optionName, $url = $options[$optionKey]);
- }
-
- // set period option
- if (!empty($options['period']))
- {
- $period = $options['period'];
- if ($period != self::SCHEDULE_PERIOD_MONTHLY
- && $period != self::SCHEDULE_PERIOD_WEEKLY)
- {
- throw new Exception(Piwik_Translate(
- 'UserCountry_InvalidGeoIPUpdatePeriod',
- array("'$period'", "'".self::SCHEDULE_PERIOD_MONTHLY."', '".self::SCHEDULE_PERIOD_WEEKLY."'")
- ));
- }
-
- Piwik_SetOption(self::SCHEDULE_PERIOD_OPTION_NAME, $period);
- }
- }
-
- /**
- * Returns true if the auto-updater is setup to update at least one type of
- * database. False if otherwise.
- *
- * @return bool
- */
- public static function isUpdaterSetup()
- {
- if (Piwik_GetOption(self::LOC_URL_OPTION_NAME) !== false
- || Piwik_GetOption(self::ISP_URL_OPTION_NAME) !== false
- || Piwik_GetOption(self::ORG_URL_OPTION_NAME) !== false)
- {
- return true;
- }
-
- return false;
- }
-
- /**
- * Retrieves the URLs used to update various GeoIP database files.
- *
- * @return array
- */
- public static function getConfiguredUrls()
- {
- $result = array();
- foreach (self::$urlOptions as $key => $optionName)
- {
- $result[$key] = Piwik_GetOption($optionName);
- }
- return $result;
- }
-
- /**
- * Returns the confiured URL (if any) for a type of database.
- *
- * @param string $key 'loc', 'isp' or 'org'
- * @return string|false
- */
- public static function getConfiguredUrl( $key )
- {
- if(empty(self::$urlOptions[$key])) {
- throw new Exception("Invalid key $key");
- }
- $url = Piwik_GetOption(self::$urlOptions[$key]);
- return $url;
- }
-
- /**
- * Performs a GeoIP database update.
- */
- public static function performUpdate()
- {
- $instance = new Piwik_UserCountry_GeoIPAutoUpdater();
- $instance->update();
- }
-
- /**
- * Returns the configured update period, either 'week' or 'month'. Defaults to
- * 'month'.
- *
- * @return string
- */
- public static function getSchedulePeriod()
- {
- $period = Piwik_GetOption(self::SCHEDULE_PERIOD_OPTION_NAME);
- if ($period === false)
- {
- $period = self::SCHEDULE_PERIOD_MONTHLY;
- }
- return $period;
- }
-
- /**
- * Returns an array of strings for GeoIP databases that have update URLs configured, but
- * are not present in the misc directory. Each string is a key describing the type of
- * database (ie, 'loc', 'isp' or 'org').
- *
- * @return array
- */
- public static function getMissingDatabases()
- {
- $result = array();
- foreach (self::getConfiguredUrls() as $key => $url)
- {
- if (!empty($url))
- {
- // if a database of the type does not exist, but there's a url to update, then
- // a database is missing
- $path = Piwik_UserCountry_LocationProvider_GeoIp::getPathToGeoIpDatabase(
- Piwik_UserCountry_LocationProvider_GeoIp::$dbNames[$key]);
- if ($path === false)
- {
- $result[] = $key;
- }
- }
- }
- return $result;
- }
-
- /**
- * Returns the extension of a URL used to update a GeoIP database, if it can be found.
- */
- public static function getGeoIPUrlExtension( $url )
- {
- // check for &suffix= query param that is special to MaxMind URLs
- if (preg_match('/suffix=([^&]+)/', $url, $matches))
- {
- return $matches[1];
- }
-
- // use basename of url
- $filenameParts = explode('.', basename($url), 2);
- if (count($filenameParts) > 1)
- {
- return end($filenameParts);
- }
- else
- {
- return reset($filenameParts);
- }
- }
-
- /**
- * Tests a location provider using a test IP address and catches PHP errors
- * (ie, notices) if they occur. PHP error information is held in self::$unzipPhpError.
- *
- * @param Piwik_UserCountry_LocationProvider $provider The provider to test.
- * @return array|false $location The result of geolocation. False if no location
- * can be found.
- */
- private static function getTestLocationCatchPhpErrors( $provider )
- {
- // note: in most cases where this will fail, the error will usually be a PHP fatal error/notice.
- // in order to delete the files in such a case (which can be caused by a man-in-the-middle attack)
- // we need to catch them, so we set a new error handler.
- self::$unzipPhpError = null;
- set_error_handler(array('Piwik_UserCountry_GeoIPAutoUpdater', 'catchGeoIPError'));
-
- $location = $provider->getLocation(array('ip' => Piwik_UserCountry_LocationProvider_GeoIp::TEST_IP));
-
- restore_error_handler();
-
- return $location;
- }
-
- /**
- * Utility function that checks if geolocation works with each installed database,
- * and if one or more doesn't, they are renamed to make sure tracking will work.
- * This is a safety measure used to make sure tracking isn't affected if strange
- * update errors occur.
- *
- * Databases are renamed to ${original}.broken .
- *
- * Note: method is protected for testability.
- */
- protected function performRedundantDbChecks()
- {
- $databaseTypes = array_keys(Piwik_UserCountry_LocationProvider_GeoIp::$dbNames);
-
- foreach ($databaseTypes as $type)
- {
- $customNames = array(
- 'loc' => array(),
- 'isp' => array(),
- 'org' => array()
- );
- $customNames[$type] = Piwik_UserCountry_LocationProvider_GeoIp::$dbNames[$type];
-
- // create provider that only uses the DB type we're testing
- $provider = new Piwik_UserCountry_LocationProvider_GeoIp_Php($customNames);
-
- // test the provider. on error, we rename the broken DB.
- self::getTestLocationCatchPhpErrors($provider);
- if (self::$unzipPhpError !== null)
- {
- list($errno, $errstr, $errfile, $errline) = self::$unzipPhpError;
- Piwik::log("Piwik_UserCountry_GeoIPAutoUpdater: Encountered PHP error when performing redundant ".
- "tests on GeoIP $type database: $errno: $errstr on line $errline of $errfile.");
-
- // get the current filename for the DB and an available new one to rename it to
- list($oldPath, $newPath) = $this->getOldAndNewPathsForBrokenDb($customNames[$type]);
-
- // rename the DB so tracking will not fail
- if ($oldPath !== false
- && $newPath !== false)
- {
- if (file_exists($newPath))
- {
- unlink($newPath);
- }
-
- rename($oldPath, $newPath);
- }
- }
- }
- }
-
- /**
- * Returns the path to a GeoIP database and a path to rename it to if it's broken.
- *
- * @param array $possibleDbNames The possible names of the database.
- * @return array Array with two elements, the path to the existing database, and
- * the path to rename it to if it is broken. The second will end
- * with something like .broken .
- */
- private function getOldAndNewPathsForBrokenDb( $possibleDbNames )
- {
- $pathToDb = Piwik_UserCountry_LocationProvider_GeoIp::getPathToGeoIpDatabase($possibleDbNames);
- $newPath = false;
-
- if ($pathToDb !== false)
- {
- $newPath = $pathToDb.".broken";
- }
-
- return array($pathToDb, $newPath);
- }
-
- /**
- * Custom PHP error handler used to catch any PHP errors that occur when
- * testing a downloaded GeoIP file.
- *
- * If we download a file that is supposed to be a GeoIP database, we need to make
- * sure it is one. This is done simply by attempting to use it. If this fails, it
- * will most of the time fail as a PHP error, which we catch w/ this function
- * after it is passed to set_error_handler.
- *
- * The PHP error is stored in self::$unzipPhpError.
- *
- * @param int $errno
- * @param string $errstr
- * @param string $errfile
- * @param int $errline
- */
- public static function catchGeoIPError( $errno, $errstr, $errfile, $errline )
- {
- self::$unzipPhpError = array($errno, $errstr, $errfile, $errline);
- }
-
- /**
- * Returns the time the auto updater was last run.
- *
- * @return Piwik_Date|false
- */
- public static function getLastRunTime()
- {
- $timestamp = Piwik_GetOption(self::LAST_RUN_TIME_OPTION_NAME);
- return $timestamp === false ? false : Piwik_Date::factory((int)$timestamp);
- }
+ const SCHEDULE_PERIOD_MONTHLY = 'month';
+ const SCHEDULE_PERIOD_WEEKLY = 'week';
+
+ const SCHEDULE_PERIOD_OPTION_NAME = 'geoip.updater_period';
+ const LOC_URL_OPTION_NAME = 'geoip.loc_db_url';
+ const ISP_URL_OPTION_NAME = 'geoip.isp_db_url';
+ const ORG_URL_OPTION_NAME = 'geoip.org_db_url';
+
+ const LAST_RUN_TIME_OPTION_NAME = 'geoip.updater_last_run_time';
+
+ private static $urlOptions = array(
+ 'loc' => self::LOC_URL_OPTION_NAME,
+ 'isp' => self::ISP_URL_OPTION_NAME,
+ 'org' => self::ORG_URL_OPTION_NAME,
+ );
+
+ /**
+ * PHP Error caught through a custom error handler while trying to use a downloaded
+ * GeoIP database. See catchGeoIPError for more info.
+ *
+ * @var array
+ */
+ private static $unzipPhpError = null;
+
+ /**
+ * Attempts to download new location, ISP & organization GeoIP databases and
+ * replace the existing ones w/ them.
+ */
+ public function update()
+ {
+ try {
+ Piwik_SetOption(self::LAST_RUN_TIME_OPTION_NAME, Piwik_Date::factory('today')->getTimestamp());
+
+ $locUrl = Piwik_GetOption(self::LOC_URL_OPTION_NAME);
+ if (!empty($locUrl)) {
+ $this->downloadFile('loc', $locUrl);
+ }
+
+ $ispUrl = Piwik_GetOption(self::ISP_URL_OPTION_NAME);
+ if (!empty($ispUrl)) {
+ $this->downloadFile('isp', $ispUrl);
+ }
+
+ $orgUrl = Piwik_GetOption(self::ORG_URL_OPTION_NAME);
+ if (!empty($orgUrl)) {
+ $this->downloadFile('org', $orgUrl);
+ }
+ } catch (Exception $ex) {
+ // message will already be prefixed w/ 'Piwik_UserCountry_GeoIPAutoUpdater: '
+ Piwik::log($ex->getMessage());
+ $this->performRedundantDbChecks();
+ throw $ex;
+ }
+
+ $this->performRedundantDbChecks();
+ }
+
+ /**
+ * Downloads a GeoIP database archive, extracts the .dat file and overwrites the existing
+ * old database.
+ *
+ * If something happens that causes the download to fail, no exception is thrown, but
+ * an error is logged.
+ *
+ * @param string $url URL to the database to download. The type of database is determined
+ * from this URL.
+ */
+ private function downloadFile($dbType, $url)
+ {
+ $ext = Piwik_UserCountry_GeoIPAutoUpdater::getGeoIPUrlExtension($url);
+ $zippedFilename = Piwik_UserCountry_LocationProvider_GeoIp::$dbNames[$dbType][0] . '.' . $ext;
+
+ $zippedOutputPath = Piwik_UserCountry_LocationProvider_GeoIp::getPathForGeoIpDatabase($zippedFilename);
+
+ // download zipped file to misc dir
+ try {
+ $success = Piwik_Http::sendHttpRequest($url, $timeout = 3600, $userAgent = null, $zippedOutputPath);
+ } catch (Exception $ex) {
+ throw new Exception("Piwik_UserCountry_GeoIPAutoUpdater: failed to download '$url' to "
+ . "'$zippedOutputPath': " . $ex->getMessage());
+ }
+
+ if ($success !== true) {
+ throw new Exception("Piwik_UserCountry_GeoIPAutoUpdater: failed to download '$url' to "
+ . "'$zippedOutputPath'! (Unknown error)");
+ }
+
+ Piwik::log(sprintf("Piwik_UserCountry_GeoIPAutoUpdater: successfully downloaded '%s'", $url));
+
+ try {
+ self::unzipDownloadedFile($zippedOutputPath, $unlink = true);
+ } catch (Exception $ex) {
+ throw new Exception("Piwik_UserCountry_GeoIPAutoUpdater: failed to unzip '$zippedOutputPath' after "
+ . "downloading " . "'$url': " . $ex->getMessage());
+ }
+
+ Piwik::log(sprintf("Piwik_UserCountry_GeoIPAutoUpdater: successfully updated GeoIP database '%s'", $url));
+ }
+
+ /**
+ * Unzips a downloaded GeoIP database. Only unzips .gz & .tar.gz files.
+ *
+ * @param string $path Path to zipped file.
+ * @param bool $unlink Whether to unlink archive or not.
+ */
+ public static function unzipDownloadedFile($path, $unlink = false)
+ {
+ $parts = explode('.', basename($path));
+ $filenameStart = $parts[0];
+
+ $dbFilename = $filenameStart . '.dat';
+ $tempFilename = $filenameStart . '.dat.new';
+ $outputPath = Piwik_UserCountry_LocationProvider_GeoIp::getPathForGeoIpDatabase($tempFilename);
+
+ // extract file
+ if (substr($path, -7, 7) == '.tar.gz') {
+ // find the .dat file in the tar archive
+ $unzip = Piwik_Unzip::factory('tar.gz', $path);
+ $content = $unzip->listContent();
+
+ if (empty($content)) {
+ throw new Exception(Piwik_Translate('UserCountry_CannotListContent',
+ array("'$path'", $unzip->errorInfo())));
+ }
+
+ $datFile = null;
+ foreach ($content as $info) {
+ $archivedPath = $info['filename'];
+ if (basename($archivedPath) === $dbFilename) {
+ $datFile = $archivedPath;
+ }
+ }
+
+ if ($datFile === null) {
+ throw new Exception(Piwik_Translate('UserCountry_CannotFindGeoIPDatabaseInArchive',
+ array($dbFilename, "'$path'")));
+ }
+
+ // extract JUST the .dat file
+ $unzipped = $unzip->extractInString($datFile);
+
+ if (empty($unzipped)) {
+ throw new Exception(Piwik_Translate('UserCountry_CannotUnzipDatFile',
+ array("'$path'", $unzip->errorInfo())));
+ }
+
+ // write unzipped to file
+ $fd = fopen($outputPath, 'wb');
+ fwrite($fd, $unzipped);
+ fclose($fd);
+ } else if (substr($path, -3, 3) == '.gz') {
+ $unzip = Piwik_Unzip::factory('gz', $path);
+ $success = $unzip->extract($outputPath);
+
+ if ($success !== true) {
+ throw new Exception(Piwik_Translate('UserCountry_CannotUnzipDatFile',
+ array("'$path'", $unzip->errorInfo())));
+ }
+ } else {
+ $ext = end(explode(basename($path), '.', 2));
+ throw new Exception(Piwik_Translate('UserCountry_UnsupportedArchiveType', "'$ext'"));
+ }
+
+ try {
+ // test that the new archive is a valid GeoIP database
+ $dbType = Piwik_UserCountry_LocationProvider_GeoIp::getGeoIPDatabaseTypeFromFilename($dbFilename);
+ if ($dbType === false) // sanity check
+ {
+ throw new Exception("Unexpected GeoIP archive file name '$path'.");
+ }
+
+ $customDbNames = array(
+ 'loc' => array(),
+ 'isp' => array(),
+ 'org' => array()
+ );
+ $customDbNames[$dbType] = array($tempFilename);
+
+ $phpProvider = new Piwik_UserCountry_LocationProvider_GeoIp_Php($customDbNames);
+
+ $location = self::getTestLocationCatchPhpErrors($phpProvider);
+
+ if (empty($location)
+ || self::$unzipPhpError !== null
+ ) {
+ if (self::$unzipPhpError !== null) {
+ list($errno, $errstr, $errfile, $errline) = self::$unzipPhpError;
+ Piwik::log("Piwik_UserCountry_GeoIPAutoUpdater: Encountered PHP error when testing newly downloaded" .
+ " GeoIP database: $errno: $errstr on line $errline of $errfile.");
+ }
+
+ throw new Exception(Piwik_Translate('UserCountry_ThisUrlIsNotAValidGeoIPDB'));
+ }
+
+ // delete the existing GeoIP database (if any) and rename the downloaded file
+ $oldDbFile = Piwik_UserCountry_LocationProvider_GeoIp::getPathForGeoIpDatabase($dbFilename);
+ if (file_exists($oldDbFile)) {
+ unlink($oldDbFile);
+ }
+
+ $tempFile = Piwik_UserCountry_LocationProvider_GeoIp::getPathForGeoIpDatabase($tempFilename);
+ rename($existing = $tempFile, $newName = $oldDbFile);
+
+ // delete original archive
+ if ($unlink) {
+ unlink($path);
+ }
+ } catch (Exception $ex) {
+ // remove downloaded files
+ if (file_exists($outputPath)) {
+ unlink($outputPath);
+ }
+ unlink($path);
+
+ throw $ex;
+ }
+ }
+
+ /**
+ * Creates a ScheduledTask instance based on set option values.
+ *
+ * @return Piwik_ScheduledTask
+ */
+ public static function makeScheduledTask()
+ {
+ $instance = new Piwik_UserCountry_GeoIPAutoUpdater();
+
+ $schedulePeriodStr = self::getSchedulePeriod();
+
+ // created the scheduledtime instance, also, since GeoIP updates are done on tuesdays,
+ // get new DBs on Wednesday
+ switch ($schedulePeriodStr) {
+ case self::SCHEDULE_PERIOD_WEEKLY:
+ $schedulePeriod = new Piwik_ScheduledTime_Weekly();
+ $schedulePeriod->setDay(3);
+ break;
+ case self::SCHEDULE_PERIOD_MONTHLY:
+ default:
+ $schedulePeriod = new Piwik_ScheduledTime_Monthly();
+ $schedulePeriod->setDayOfWeek(3, 0);
+ break;
+ }
+
+ return new Piwik_ScheduledTask($instance, 'update', null, $schedulePeriod, Piwik_ScheduledTask::LOWEST_PRIORITY);
+ }
+
+ /**
+ * Sets the options used by this class based on query parameter values.
+ *
+ * See setUpdaterOptions for query params used.
+ */
+ public static function setUpdaterOptionsFromUrl()
+ {
+ self::setUpdaterOptions(array(
+ 'loc' => Piwik_Common::getRequestVar('loc_db', false, 'string'),
+ 'isp' => Piwik_Common::getRequestVar('isp_db', false, 'string'),
+ 'org' => Piwik_Common::getRequestVar('org_db', false, 'string'),
+ 'period' => Piwik_Common::getRequestVar('period', false, 'string'),
+ ));
+ }
+
+ /**
+ * Sets the options used by this class based on the elements in $options.
+ *
+ * The following elements of $options are used:
+ * 'loc' - URL for location database.
+ * 'isp' - URL for ISP database.
+ * 'org' - URL for Organization database.
+ * 'period' - 'weekly' or 'monthly'. When to run the updates.
+ *
+ * @param array $options
+ */
+ public static function setUpdaterOptions($options)
+ {
+ // set url options
+ foreach (self::$urlOptions as $optionKey => $optionName) {
+ if (!isset($options[$optionKey])) {
+ continue;
+ }
+
+ Piwik_SetOption($optionName, $url = $options[$optionKey]);
+ }
+
+ // set period option
+ if (!empty($options['period'])) {
+ $period = $options['period'];
+ if ($period != self::SCHEDULE_PERIOD_MONTHLY
+ && $period != self::SCHEDULE_PERIOD_WEEKLY
+ ) {
+ throw new Exception(Piwik_Translate(
+ 'UserCountry_InvalidGeoIPUpdatePeriod',
+ array("'$period'", "'" . self::SCHEDULE_PERIOD_MONTHLY . "', '" . self::SCHEDULE_PERIOD_WEEKLY . "'")
+ ));
+ }
+
+ Piwik_SetOption(self::SCHEDULE_PERIOD_OPTION_NAME, $period);
+ }
+ }
+
+ /**
+ * Returns true if the auto-updater is setup to update at least one type of
+ * database. False if otherwise.
+ *
+ * @return bool
+ */
+ public static function isUpdaterSetup()
+ {
+ if (Piwik_GetOption(self::LOC_URL_OPTION_NAME) !== false
+ || Piwik_GetOption(self::ISP_URL_OPTION_NAME) !== false
+ || Piwik_GetOption(self::ORG_URL_OPTION_NAME) !== false
+ ) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Retrieves the URLs used to update various GeoIP database files.
+ *
+ * @return array
+ */
+ public static function getConfiguredUrls()
+ {
+ $result = array();
+ foreach (self::$urlOptions as $key => $optionName) {
+ $result[$key] = Piwik_GetOption($optionName);
+ }
+ return $result;
+ }
+
+ /**
+ * Returns the confiured URL (if any) for a type of database.
+ *
+ * @param string $key 'loc', 'isp' or 'org'
+ * @return string|false
+ */
+ public static function getConfiguredUrl($key)
+ {
+ if (empty(self::$urlOptions[$key])) {
+ throw new Exception("Invalid key $key");
+ }
+ $url = Piwik_GetOption(self::$urlOptions[$key]);
+ return $url;
+ }
+
+ /**
+ * Performs a GeoIP database update.
+ */
+ public static function performUpdate()
+ {
+ $instance = new Piwik_UserCountry_GeoIPAutoUpdater();
+ $instance->update();
+ }
+
+ /**
+ * Returns the configured update period, either 'week' or 'month'. Defaults to
+ * 'month'.
+ *
+ * @return string
+ */
+ public static function getSchedulePeriod()
+ {
+ $period = Piwik_GetOption(self::SCHEDULE_PERIOD_OPTION_NAME);
+ if ($period === false) {
+ $period = self::SCHEDULE_PERIOD_MONTHLY;
+ }
+ return $period;
+ }
+
+ /**
+ * Returns an array of strings for GeoIP databases that have update URLs configured, but
+ * are not present in the misc directory. Each string is a key describing the type of
+ * database (ie, 'loc', 'isp' or 'org').
+ *
+ * @return array
+ */
+ public static function getMissingDatabases()
+ {
+ $result = array();
+ foreach (self::getConfiguredUrls() as $key => $url) {
+ if (!empty($url)) {
+ // if a database of the type does not exist, but there's a url to update, then
+ // a database is missing
+ $path = Piwik_UserCountry_LocationProvider_GeoIp::getPathToGeoIpDatabase(
+ Piwik_UserCountry_LocationProvider_GeoIp::$dbNames[$key]);
+ if ($path === false) {
+ $result[] = $key;
+ }
+ }
+ }
+ return $result;
+ }
+
+ /**
+ * Returns the extension of a URL used to update a GeoIP database, if it can be found.
+ */
+ public static function getGeoIPUrlExtension($url)
+ {
+ // check for &suffix= query param that is special to MaxMind URLs
+ if (preg_match('/suffix=([^&]+)/', $url, $matches)) {
+ return $matches[1];
+ }
+
+ // use basename of url
+ $filenameParts = explode('.', basename($url), 2);
+ if (count($filenameParts) > 1) {
+ return end($filenameParts);
+ } else {
+ return reset($filenameParts);
+ }
+ }
+
+ /**
+ * Tests a location provider using a test IP address and catches PHP errors
+ * (ie, notices) if they occur. PHP error information is held in self::$unzipPhpError.
+ *
+ * @param Piwik_UserCountry_LocationProvider $provider The provider to test.
+ * @return array|false $location The result of geolocation. False if no location
+ * can be found.
+ */
+ private static function getTestLocationCatchPhpErrors($provider)
+ {
+ // note: in most cases where this will fail, the error will usually be a PHP fatal error/notice.
+ // in order to delete the files in such a case (which can be caused by a man-in-the-middle attack)
+ // we need to catch them, so we set a new error handler.
+ self::$unzipPhpError = null;
+ set_error_handler(array('Piwik_UserCountry_GeoIPAutoUpdater', 'catchGeoIPError'));
+
+ $location = $provider->getLocation(array('ip' => Piwik_UserCountry_LocationProvider_GeoIp::TEST_IP));
+
+ restore_error_handler();
+
+ return $location;
+ }
+
+ /**
+ * Utility function that checks if geolocation works with each installed database,
+ * and if one or more doesn't, they are renamed to make sure tracking will work.
+ * This is a safety measure used to make sure tracking isn't affected if strange
+ * update errors occur.
+ *
+ * Databases are renamed to ${original}.broken .
+ *
+ * Note: method is protected for testability.
+ */
+ protected function performRedundantDbChecks()
+ {
+ $databaseTypes = array_keys(Piwik_UserCountry_LocationProvider_GeoIp::$dbNames);
+
+ foreach ($databaseTypes as $type) {
+ $customNames = array(
+ 'loc' => array(),
+ 'isp' => array(),
+ 'org' => array()
+ );
+ $customNames[$type] = Piwik_UserCountry_LocationProvider_GeoIp::$dbNames[$type];
+
+ // create provider that only uses the DB type we're testing
+ $provider = new Piwik_UserCountry_LocationProvider_GeoIp_Php($customNames);
+
+ // test the provider. on error, we rename the broken DB.
+ self::getTestLocationCatchPhpErrors($provider);
+ if (self::$unzipPhpError !== null) {
+ list($errno, $errstr, $errfile, $errline) = self::$unzipPhpError;
+ Piwik::log("Piwik_UserCountry_GeoIPAutoUpdater: Encountered PHP error when performing redundant " .
+ "tests on GeoIP $type database: $errno: $errstr on line $errline of $errfile.");
+
+ // get the current filename for the DB and an available new one to rename it to
+ list($oldPath, $newPath) = $this->getOldAndNewPathsForBrokenDb($customNames[$type]);
+
+ // rename the DB so tracking will not fail
+ if ($oldPath !== false
+ && $newPath !== false
+ ) {
+ if (file_exists($newPath)) {
+ unlink($newPath);
+ }
+
+ rename($oldPath, $newPath);
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns the path to a GeoIP database and a path to rename it to if it's broken.
+ *
+ * @param array $possibleDbNames The possible names of the database.
+ * @return array Array with two elements, the path to the existing database, and
+ * the path to rename it to if it is broken. The second will end
+ * with something like .broken .
+ */
+ private function getOldAndNewPathsForBrokenDb($possibleDbNames)
+ {
+ $pathToDb = Piwik_UserCountry_LocationProvider_GeoIp::getPathToGeoIpDatabase($possibleDbNames);
+ $newPath = false;
+
+ if ($pathToDb !== false) {
+ $newPath = $pathToDb . ".broken";
+ }
+
+ return array($pathToDb, $newPath);
+ }
+
+ /**
+ * Custom PHP error handler used to catch any PHP errors that occur when
+ * testing a downloaded GeoIP file.
+ *
+ * If we download a file that is supposed to be a GeoIP database, we need to make
+ * sure it is one. This is done simply by attempting to use it. If this fails, it
+ * will most of the time fail as a PHP error, which we catch w/ this function
+ * after it is passed to set_error_handler.
+ *
+ * The PHP error is stored in self::$unzipPhpError.
+ *
+ * @param int $errno
+ * @param string $errstr
+ * @param string $errfile
+ * @param int $errline
+ */
+ public static function catchGeoIPError($errno, $errstr, $errfile, $errline)
+ {
+ self::$unzipPhpError = array($errno, $errstr, $errfile, $errline);
+ }
+
+ /**
+ * Returns the time the auto updater was last run.
+ *
+ * @return Piwik_Date|false
+ */
+ public static function getLastRunTime()
+ {
+ $timestamp = Piwik_GetOption(self::LAST_RUN_TIME_OPTION_NAME);
+ return $timestamp === false ? false : Piwik_Date::factory((int)$timestamp);
+ }
}
diff --git a/plugins/UserCountry/LocationProvider.php b/plugins/UserCountry/LocationProvider.php
index 3f6b8821ed..34dfac6dfc 100755
--- a/plugins/UserCountry/LocationProvider.php
+++ b/plugins/UserCountry/LocationProvider.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_UserCountry
*/
@@ -21,470 +21,431 @@ require_once PIWIK_INCLUDE_PATH . '/plugins/UserCountry/LocationProvider/GeoIp.p
/**
* The base class of all LocationProviders.
- *
+ *
* LocationProviders attempt to determine a visitor's location using other
* visitor info. All LocationProviders require a visitor's IP address, some
* require more, such as the browser language.
*/
abstract class Piwik_UserCountry_LocationProvider
{
- const NOT_INSTALLED = 0;
- const INSTALLED = 1;
- const BROKEN = 2;
-
- const CURRENT_PROVIDER_OPTION_NAME = 'usercountry.location_provider';
-
- const GEOGRAPHIC_COORD_PRECISION = 3;
-
- const CONTINENT_CODE_KEY = 'continent_code';
- const CONTINENT_NAME_KEY = 'continent_name';
- const COUNTRY_CODE_KEY = 'country_code';
- const COUNTRY_NAME_KEY = 'country_name';
- const REGION_CODE_KEY = 'region_code';
- const REGION_NAME_KEY = 'region_name';
- const CITY_NAME_KEY = 'city_name';
- const AREA_CODE_KEY = 'area_code';
- const LATITUDE_KEY = 'lat';
- const LONGITUDE_KEY = 'long';
- const POSTAL_CODE_KEY = 'postal_code';
- const ISP_KEY = 'isp';
- const ORG_KEY = 'org';
-
- /**
- * An array of all provider instances. Access it through static methods.
- *
- * @var array
- */
- public static $providers = null;
-
- /**
- * Returns location information based on visitor information.
- *
- * The result of this function will be an array. The array can store some or all of
- * the following information:
- *
- * - Continent Code: The code of the visitor's continent.
- * (array key is self::CONTINENT_CODE_KEY)
- * - Continent Name: The name of the visitor's continent.
- * (array key is self::CONTINENT_NAME_KEY)
- * - Country Code: The code of the visitor's country.
- * (array key is self::COUNTRY_CODE_KEY)
- * - Country Name: The name of the visitor's country.
- * (array key is self::COUNTRY_NAME_KEY)
- * - Region Code: The code of the visitor's region.
- * (array key is self::REGION_CODE_KEY)
- * - Region Name: The name of the visitor's region.
- * (array key is self::REGION_NAME_KEY)
- * - City Name: The name of the visitor's city.
- * (array key is self::CITY_NAME_KEY)
- * - Area Code: The visitor's area code.
- * (array key is self::AREA_CODE_KEY)
- * - Latitude: The visitor's latitude.
- * (array key is self::LATITUDE_KEY)
- * - Longitude: The visitor's longitude.
- * (array key is self::LONGITUDE_KEY)
- * - Postal Code: The visitor's postal code.
- * (array key is self::POSTAL_CODE_KEY)
- * - ISP: The visitor's ISP.
- * (array key is self::ISP_KEY)
- * - Org: The company/organization of the visitor's IP.
- * (array key is self::ORG_KEY)
- *
- * All LocationProviders will attempt to return the country of the visitor.
- *
- * @param array $info What this must contain depends on the specific provider
- * implementation. All providers require an 'ip' key mapped
- * to the visitor's IP address.
- * @return array|false
- */
- abstract public function getLocation( $info );
-
- /**
- * Returns true if this provider is available for use, false if otherwise.
- *
- * @return bool
- */
- abstract public function isAvailable();
-
- /**
- * Returns true if this provider is working, false if otherwise.
- *
- * @return bool
- */
- abstract public function isWorking();
-
- /**
- * Returns an array mapping location result keys w/ bool values indicating whether
- * that information is supported by this provider. If it is not supported, that means
- * this provider either cannot get this information, or is not configured to get it.
- *
- * @return array eg. array(self::CONTINENT_CODE_KEY => true,
- * self::CONTINENT_NAME_KEY => true,
- * self::ORG_KEY => false)
- * The result is not guaranteed to have keys for every type of location
- * info.
- */
- abstract public function getSupportedLocationInfo();
-
- /**
- * Returns every available provider instance.
- *
- * @return array
- */
- public static function getAllProviders()
- {
- if (is_null(self::$providers))
- {
- self::$providers = array();
- foreach (get_declared_classes() as $klass)
- {
- if (is_subclass_of($klass, 'Piwik_UserCountry_LocationProvider'))
- {
- $klassInfo = new ReflectionClass($klass);
- if ($klassInfo->isAbstract())
- {
- continue;
- }
-
- self::$providers[] = new $klass;
- }
- }
- }
-
- return self::$providers;
- }
-
- /**
- * Returns all provider instances that are 'available'. An 'available' provider
- * is one that is available for use. They may not necessarily be working.
- *
- * @return array
- */
- public static function getAvailableProviders()
- {
- $result = array();
- foreach (self::getAllProviders() as $provider)
- {
- if ($provider->isAvailable())
- {
- $result[] = $provider;
- }
- }
- return $result;
- }
-
- /**
- * Returns an array mapping provider IDs w/ information about the provider,
- * for each location provider.
- *
- * The following information is provided for each provider:
- * 'id' - The provider's unique string ID.
- * 'title' - The provider's title.
- * 'description' - A description of how the location provider works.
- * 'status' - Either self::NOT_INSTALLED, self::INSTALLED or self::BROKEN.
- * 'statusMessage' - If the status is self::BROKEN, then the message describes why.
- * 'location' - A pretty formatted location of the current IP address
- * (Piwik_IP::getIpFromHeader()).
- *
- * An example result:
- * array(
- * 'geoip_php' => array('id' => 'geoip_php',
- * 'title' => '...',
- * 'desc' => '...',
- * 'status' => Piwik_UserCountry_LocationProvider_GeoIp::BROKEN,
- * 'statusMessage' => '...',
- * 'location' => '...')
- * 'geoip_serverbased' => array(...)
- * )
- *
- * @param string $newline What to separate lines with in the pretty locations.
- * @param bool $includeExtra Whether to include ISP/Org info in formatted location.
- * @return array
- */
- public static function getAllProviderInfo( $newline = "\n", $includeExtra = false )
- {
- $allInfo = array();
- foreach (self::getAllProviders() as $provider)
- {
- $info = $provider->getInfo();
-
- $status = self::INSTALLED;
- $location = false;
- $statusMessage = false;
-
- $availableOrMessage = $provider->isAvailable();
- if ($availableOrMessage !== true)
- {
- $status = self::NOT_INSTALLED;
- if (is_string($availableOrMessage))
- {
- $statusMessage = $availableOrMessage;
- }
- }
- else
- {
- $workingOrError = $provider->isWorking();
- if ($workingOrError === true) // if the implementation is configured correctly, get the location
- {
- $locInfo = array('ip' => Piwik_IP::getIpFromHeader(),
- 'lang' => Piwik_Common::getBrowserLanguage(),
- 'disable_fallbacks' => true);
-
- $location = $provider->getLocation($locInfo);
- $location = self::prettyFormatLocation($location, $newline, $includeExtra);
- }
- else // otherwise set an error message describing why
- {
- $status = self::BROKEN;
- $statusMessage = $workingOrError;
- }
- }
-
- $info['status'] = $status;
- $info['statusMessage'] = $statusMessage;
- $info['location'] = $location;
-
- $allInfo[$info['order']] = $info;
- }
-
- ksort($allInfo);
-
- $result = array();
- foreach ($allInfo as $info)
- {
- $result[$info['id']] = $info;
- }
- return $result;
- }
-
- /**
- * Returns the ID of the currently used location provider.
- *
- * The used provider is stored in the 'usercountry.location_provider' option.
- *
- * This function should not be called by the Tracker.
- *
- * @return string
- */
- public static function getCurrentProviderId()
- {
- $optionValue = Piwik_GetOption(self::CURRENT_PROVIDER_OPTION_NAME);
- return $optionValue === false ? Piwik_UserCountry_LocationProvider_Default::ID : $optionValue;
- }
-
- /**
- * Returns the provider instance of the current location provider.
- *
- * This function should not be called by the Tracker.
- *
- * @return Piwik_UserCountry_LocationProvider
- */
- public static function getCurrentProvider()
- {
- return self::getProviderById(self::getCurrentProviderId());
- }
-
- /**
- * Sets the provider to use when tracking.
- *
- * @param string $providerId The ID of the provider to use.
- * @return Piwik_UserCountry_LocationProvider The new current provider.
- * @throws Exception If the provider ID is invalid.
- */
- public static function setCurrentProvider( $providerId )
- {
- $provider = self::getProviderById($providerId);
- if ($provider === false)
- {
- throw new Exception(
- "Invalid provider ID '$providerId'. The provider either does not exist or is not available");
- }
- Piwik_SetOption(self::CURRENT_PROVIDER_OPTION_NAME, $providerId);
- Piwik_Tracker_Cache::clearCacheGeneral();
- return $provider;
- }
-
- /**
- * Returns a provider instance by ID or false if the ID is invalid or unavailable.
- *
- * @param string $providerId
- * @return Piwik_UserCountry_LocationProvider|false
- */
- public static function getProviderById( $providerId )
- {
- foreach (self::getAvailableProviders() as $provider)
- {
- $info = $provider->getInfo();
- if ($info['id'] == $providerId)
- {
- return $provider;
- }
- }
- return false;
- }
-
- /**
- * Tries to fill in any missing information in a location result.
- *
- * This method will try to set the continent code, continent name and country code
- * using other information.
- *
- * Note: This function must always be called by location providers in getLocation.
- *
- * @param array $location The location information to modify.
- */
- public function completeLocationResult( &$location )
- {
- // fill in continent code if country code is present
- if (empty($location[self::CONTINENT_CODE_KEY])
- && !empty($location[self::COUNTRY_CODE_KEY]))
- {
- $countryCode = strtolower($location[self::COUNTRY_CODE_KEY]);
- $location[self::CONTINENT_CODE_KEY] = Piwik_Common::getContinent($countryCode);
- }
-
- // fill in continent name if continent code is present
- if (empty($location[self::CONTINENT_NAME_KEY])
- && !empty($location[self::CONTINENT_CODE_KEY]))
- {
- $continentCode = strtolower($location[self::CONTINENT_CODE_KEY]);
- $location[self::CONTINENT_NAME_KEY] = Piwik_Translate('UserCountry_continent_'.$continentCode);
- }
-
- // fill in country name if country code is present
- if (empty($location[self::COUNTRY_NAME_KEY])
- && !empty($location[self::COUNTRY_CODE_KEY]))
- {
- $countryCode = strtolower($location[self::COUNTRY_CODE_KEY]);
- $location[self::COUNTRY_NAME_KEY] = Piwik_Translate('UserCountry_country_'.$countryCode);
- }
-
- // deal w/ improper latitude/longitude & round proper values
- if (!empty($location[self::LATITUDE_KEY]))
- {
- if (is_numeric($location[self::LATITUDE_KEY]))
- {
- $location[self::LATITUDE_KEY] = round($location[self::LATITUDE_KEY], self::GEOGRAPHIC_COORD_PRECISION);
- }
- else
- {
- unset($location[self::LATITUDE_KEY]);
- }
- }
-
- if (!empty($location[self::LONGITUDE_KEY]))
- {
- if (is_numeric($location[self::LONGITUDE_KEY]))
- {
- $location[self::LONGITUDE_KEY] = round($location[self::LONGITUDE_KEY], self::GEOGRAPHIC_COORD_PRECISION);
- }
- else
- {
- unset($location[self::LONGITUDE_KEY]);
- }
- }
- }
-
- /**
- * Returns a prettified location result.
- *
- * @param array|false $locationInfo
- * @param string $newline The line separator (ie, \n or <br/>).
- * @param bool $includeExtra Whether to include ISP/Organization info.
- * @return string
- */
- public static function prettyFormatLocation( $locationInfo, $newline = "\n", $includeExtra = false )
- {
- if ($locationInfo === false)
- {
- return Piwik_Translate('General_Unknown');
- }
-
- // add latitude/longitude line
- $lines = array();
- if (!empty($locationInfo[self::LATITUDE_KEY])
- && !empty($locationInfo[self::LONGITUDE_KEY]))
- {
- $lines[] = '('.$locationInfo[self::LATITUDE_KEY].', '.$locationInfo[self::LONGITUDE_KEY].')';
- }
-
- // add city/state line
- $cityState = array();
- if (!empty($locationInfo[self::CITY_NAME_KEY]))
- {
- $cityState[] = $locationInfo[self::CITY_NAME_KEY];
- }
-
- if (!empty($locationInfo[self::REGION_CODE_KEY]))
- {
- $cityState[] = $locationInfo[self::REGION_CODE_KEY];
- }
- else if (!empty($locationInfo[self::REGION_NAME_KEY]))
- {
- $cityState[] = $locationInfo[self::REGION_NAME_KEY];
- }
-
- if (!empty($cityState))
- {
- $lines[] = implode(', ', $cityState);
- }
-
- // add postal code line
- if (!empty($locationInfo[self::POSTAL_CODE_KEY]))
- {
- $lines[] = $locationInfo[self::POSTAL_CODE_KEY];
- }
-
- // add country line
- if (!empty($locationInfo[self::COUNTRY_NAME_KEY]))
- {
- $lines[] = $locationInfo[self::COUNTRY_NAME_KEY];
- }
- else if (!empty($locationInfo[self::COUNTRY_CODE_KEY]))
- {
- $lines[] = $locationInfo[self::COUNTRY_CODE_KEY];
- }
-
- // add extra information (ISP/Organization)
- if ($includeExtra)
- {
- $lines[] = '';
-
- $unknown = Piwik_Translate('General_Unknown');
-
- $org = !empty($locationInfo[self::ORG_KEY]) ? $locationInfo[self::ORG_KEY] : $unknown;
- $lines[] = "Org: $org";
-
- $isp = !empty($locationInfo[self::ISP_KEY]) ? $locationInfo[self::ISP_KEY] : $unknown;
- $lines[] = "ISP: $isp";
- }
-
- return implode($newline, $lines);
- }
-
- /**
- * Returns an IP address from an array that was passed into getLocation. This
- * will return an IPv4 address or false if the address is IPv6 (IPv6 is not
- * supported yet).
- *
- * @param array $ip Must have 'ip' key.
- * @return string|bool
- */
- protected function getIpFromInfo( $info )
- {
- $ip = $info['ip'];
- if (Piwik_IP::isMappedIPv4($ip))
- {
- return Piwik_IP::getIPv4FromMappedIPv6($ip);
- }
- else if (Piwik_IP::isIPv6($ip)) // IPv6 is not supported (yet)
- {
- return false;
- }
- else
- {
- return $ip;
- }
- }
+ const NOT_INSTALLED = 0;
+ const INSTALLED = 1;
+ const BROKEN = 2;
+
+ const CURRENT_PROVIDER_OPTION_NAME = 'usercountry.location_provider';
+
+ const GEOGRAPHIC_COORD_PRECISION = 3;
+
+ const CONTINENT_CODE_KEY = 'continent_code';
+ const CONTINENT_NAME_KEY = 'continent_name';
+ const COUNTRY_CODE_KEY = 'country_code';
+ const COUNTRY_NAME_KEY = 'country_name';
+ const REGION_CODE_KEY = 'region_code';
+ const REGION_NAME_KEY = 'region_name';
+ const CITY_NAME_KEY = 'city_name';
+ const AREA_CODE_KEY = 'area_code';
+ const LATITUDE_KEY = 'lat';
+ const LONGITUDE_KEY = 'long';
+ const POSTAL_CODE_KEY = 'postal_code';
+ const ISP_KEY = 'isp';
+ const ORG_KEY = 'org';
+
+ /**
+ * An array of all provider instances. Access it through static methods.
+ *
+ * @var array
+ */
+ public static $providers = null;
+
+ /**
+ * Returns location information based on visitor information.
+ *
+ * The result of this function will be an array. The array can store some or all of
+ * the following information:
+ *
+ * - Continent Code: The code of the visitor's continent.
+ * (array key is self::CONTINENT_CODE_KEY)
+ * - Continent Name: The name of the visitor's continent.
+ * (array key is self::CONTINENT_NAME_KEY)
+ * - Country Code: The code of the visitor's country.
+ * (array key is self::COUNTRY_CODE_KEY)
+ * - Country Name: The name of the visitor's country.
+ * (array key is self::COUNTRY_NAME_KEY)
+ * - Region Code: The code of the visitor's region.
+ * (array key is self::REGION_CODE_KEY)
+ * - Region Name: The name of the visitor's region.
+ * (array key is self::REGION_NAME_KEY)
+ * - City Name: The name of the visitor's city.
+ * (array key is self::CITY_NAME_KEY)
+ * - Area Code: The visitor's area code.
+ * (array key is self::AREA_CODE_KEY)
+ * - Latitude: The visitor's latitude.
+ * (array key is self::LATITUDE_KEY)
+ * - Longitude: The visitor's longitude.
+ * (array key is self::LONGITUDE_KEY)
+ * - Postal Code: The visitor's postal code.
+ * (array key is self::POSTAL_CODE_KEY)
+ * - ISP: The visitor's ISP.
+ * (array key is self::ISP_KEY)
+ * - Org: The company/organization of the visitor's IP.
+ * (array key is self::ORG_KEY)
+ *
+ * All LocationProviders will attempt to return the country of the visitor.
+ *
+ * @param array $info What this must contain depends on the specific provider
+ * implementation. All providers require an 'ip' key mapped
+ * to the visitor's IP address.
+ * @return array|false
+ */
+ abstract public function getLocation($info);
+
+ /**
+ * Returns true if this provider is available for use, false if otherwise.
+ *
+ * @return bool
+ */
+ abstract public function isAvailable();
+
+ /**
+ * Returns true if this provider is working, false if otherwise.
+ *
+ * @return bool
+ */
+ abstract public function isWorking();
+
+ /**
+ * Returns an array mapping location result keys w/ bool values indicating whether
+ * that information is supported by this provider. If it is not supported, that means
+ * this provider either cannot get this information, or is not configured to get it.
+ *
+ * @return array eg. array(self::CONTINENT_CODE_KEY => true,
+ * self::CONTINENT_NAME_KEY => true,
+ * self::ORG_KEY => false)
+ * The result is not guaranteed to have keys for every type of location
+ * info.
+ */
+ abstract public function getSupportedLocationInfo();
+
+ /**
+ * Returns every available provider instance.
+ *
+ * @return array
+ */
+ public static function getAllProviders()
+ {
+ if (is_null(self::$providers)) {
+ self::$providers = array();
+ foreach (get_declared_classes() as $klass) {
+ if (is_subclass_of($klass, 'Piwik_UserCountry_LocationProvider')) {
+ $klassInfo = new ReflectionClass($klass);
+ if ($klassInfo->isAbstract()) {
+ continue;
+ }
+
+ self::$providers[] = new $klass;
+ }
+ }
+ }
+
+ return self::$providers;
+ }
+
+ /**
+ * Returns all provider instances that are 'available'. An 'available' provider
+ * is one that is available for use. They may not necessarily be working.
+ *
+ * @return array
+ */
+ public static function getAvailableProviders()
+ {
+ $result = array();
+ foreach (self::getAllProviders() as $provider) {
+ if ($provider->isAvailable()) {
+ $result[] = $provider;
+ }
+ }
+ return $result;
+ }
+
+ /**
+ * Returns an array mapping provider IDs w/ information about the provider,
+ * for each location provider.
+ *
+ * The following information is provided for each provider:
+ * 'id' - The provider's unique string ID.
+ * 'title' - The provider's title.
+ * 'description' - A description of how the location provider works.
+ * 'status' - Either self::NOT_INSTALLED, self::INSTALLED or self::BROKEN.
+ * 'statusMessage' - If the status is self::BROKEN, then the message describes why.
+ * 'location' - A pretty formatted location of the current IP address
+ * (Piwik_IP::getIpFromHeader()).
+ *
+ * An example result:
+ * array(
+ * 'geoip_php' => array('id' => 'geoip_php',
+ * 'title' => '...',
+ * 'desc' => '...',
+ * 'status' => Piwik_UserCountry_LocationProvider_GeoIp::BROKEN,
+ * 'statusMessage' => '...',
+ * 'location' => '...')
+ * 'geoip_serverbased' => array(...)
+ * )
+ *
+ * @param string $newline What to separate lines with in the pretty locations.
+ * @param bool $includeExtra Whether to include ISP/Org info in formatted location.
+ * @return array
+ */
+ public static function getAllProviderInfo($newline = "\n", $includeExtra = false)
+ {
+ $allInfo = array();
+ foreach (self::getAllProviders() as $provider) {
+ $info = $provider->getInfo();
+
+ $status = self::INSTALLED;
+ $location = false;
+ $statusMessage = false;
+
+ $availableOrMessage = $provider->isAvailable();
+ if ($availableOrMessage !== true) {
+ $status = self::NOT_INSTALLED;
+ if (is_string($availableOrMessage)) {
+ $statusMessage = $availableOrMessage;
+ }
+ } else {
+ $workingOrError = $provider->isWorking();
+ if ($workingOrError === true) // if the implementation is configured correctly, get the location
+ {
+ $locInfo = array('ip' => Piwik_IP::getIpFromHeader(),
+ 'lang' => Piwik_Common::getBrowserLanguage(),
+ 'disable_fallbacks' => true);
+
+ $location = $provider->getLocation($locInfo);
+ $location = self::prettyFormatLocation($location, $newline, $includeExtra);
+ } else // otherwise set an error message describing why
+ {
+ $status = self::BROKEN;
+ $statusMessage = $workingOrError;
+ }
+ }
+
+ $info['status'] = $status;
+ $info['statusMessage'] = $statusMessage;
+ $info['location'] = $location;
+
+ $allInfo[$info['order']] = $info;
+ }
+
+ ksort($allInfo);
+
+ $result = array();
+ foreach ($allInfo as $info) {
+ $result[$info['id']] = $info;
+ }
+ return $result;
+ }
+
+ /**
+ * Returns the ID of the currently used location provider.
+ *
+ * The used provider is stored in the 'usercountry.location_provider' option.
+ *
+ * This function should not be called by the Tracker.
+ *
+ * @return string
+ */
+ public static function getCurrentProviderId()
+ {
+ $optionValue = Piwik_GetOption(self::CURRENT_PROVIDER_OPTION_NAME);
+ return $optionValue === false ? Piwik_UserCountry_LocationProvider_Default::ID : $optionValue;
+ }
+
+ /**
+ * Returns the provider instance of the current location provider.
+ *
+ * This function should not be called by the Tracker.
+ *
+ * @return Piwik_UserCountry_LocationProvider
+ */
+ public static function getCurrentProvider()
+ {
+ return self::getProviderById(self::getCurrentProviderId());
+ }
+
+ /**
+ * Sets the provider to use when tracking.
+ *
+ * @param string $providerId The ID of the provider to use.
+ * @return Piwik_UserCountry_LocationProvider The new current provider.
+ * @throws Exception If the provider ID is invalid.
+ */
+ public static function setCurrentProvider($providerId)
+ {
+ $provider = self::getProviderById($providerId);
+ if ($provider === false) {
+ throw new Exception(
+ "Invalid provider ID '$providerId'. The provider either does not exist or is not available");
+ }
+ Piwik_SetOption(self::CURRENT_PROVIDER_OPTION_NAME, $providerId);
+ Piwik_Tracker_Cache::clearCacheGeneral();
+ return $provider;
+ }
+
+ /**
+ * Returns a provider instance by ID or false if the ID is invalid or unavailable.
+ *
+ * @param string $providerId
+ * @return Piwik_UserCountry_LocationProvider|false
+ */
+ public static function getProviderById($providerId)
+ {
+ foreach (self::getAvailableProviders() as $provider) {
+ $info = $provider->getInfo();
+ if ($info['id'] == $providerId) {
+ return $provider;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Tries to fill in any missing information in a location result.
+ *
+ * This method will try to set the continent code, continent name and country code
+ * using other information.
+ *
+ * Note: This function must always be called by location providers in getLocation.
+ *
+ * @param array $location The location information to modify.
+ */
+ public function completeLocationResult(&$location)
+ {
+ // fill in continent code if country code is present
+ if (empty($location[self::CONTINENT_CODE_KEY])
+ && !empty($location[self::COUNTRY_CODE_KEY])
+ ) {
+ $countryCode = strtolower($location[self::COUNTRY_CODE_KEY]);
+ $location[self::CONTINENT_CODE_KEY] = Piwik_Common::getContinent($countryCode);
+ }
+
+ // fill in continent name if continent code is present
+ if (empty($location[self::CONTINENT_NAME_KEY])
+ && !empty($location[self::CONTINENT_CODE_KEY])
+ ) {
+ $continentCode = strtolower($location[self::CONTINENT_CODE_KEY]);
+ $location[self::CONTINENT_NAME_KEY] = Piwik_Translate('UserCountry_continent_' . $continentCode);
+ }
+
+ // fill in country name if country code is present
+ if (empty($location[self::COUNTRY_NAME_KEY])
+ && !empty($location[self::COUNTRY_CODE_KEY])
+ ) {
+ $countryCode = strtolower($location[self::COUNTRY_CODE_KEY]);
+ $location[self::COUNTRY_NAME_KEY] = Piwik_Translate('UserCountry_country_' . $countryCode);
+ }
+
+ // deal w/ improper latitude/longitude & round proper values
+ if (!empty($location[self::LATITUDE_KEY])) {
+ if (is_numeric($location[self::LATITUDE_KEY])) {
+ $location[self::LATITUDE_KEY] = round($location[self::LATITUDE_KEY], self::GEOGRAPHIC_COORD_PRECISION);
+ } else {
+ unset($location[self::LATITUDE_KEY]);
+ }
+ }
+
+ if (!empty($location[self::LONGITUDE_KEY])) {
+ if (is_numeric($location[self::LONGITUDE_KEY])) {
+ $location[self::LONGITUDE_KEY] = round($location[self::LONGITUDE_KEY], self::GEOGRAPHIC_COORD_PRECISION);
+ } else {
+ unset($location[self::LONGITUDE_KEY]);
+ }
+ }
+ }
+
+ /**
+ * Returns a prettified location result.
+ *
+ * @param array|false $locationInfo
+ * @param string $newline The line separator (ie, \n or <br/>).
+ * @param bool $includeExtra Whether to include ISP/Organization info.
+ * @return string
+ */
+ public static function prettyFormatLocation($locationInfo, $newline = "\n", $includeExtra = false)
+ {
+ if ($locationInfo === false) {
+ return Piwik_Translate('General_Unknown');
+ }
+
+ // add latitude/longitude line
+ $lines = array();
+ if (!empty($locationInfo[self::LATITUDE_KEY])
+ && !empty($locationInfo[self::LONGITUDE_KEY])
+ ) {
+ $lines[] = '(' . $locationInfo[self::LATITUDE_KEY] . ', ' . $locationInfo[self::LONGITUDE_KEY] . ')';
+ }
+
+ // add city/state line
+ $cityState = array();
+ if (!empty($locationInfo[self::CITY_NAME_KEY])) {
+ $cityState[] = $locationInfo[self::CITY_NAME_KEY];
+ }
+
+ if (!empty($locationInfo[self::REGION_CODE_KEY])) {
+ $cityState[] = $locationInfo[self::REGION_CODE_KEY];
+ } else if (!empty($locationInfo[self::REGION_NAME_KEY])) {
+ $cityState[] = $locationInfo[self::REGION_NAME_KEY];
+ }
+
+ if (!empty($cityState)) {
+ $lines[] = implode(', ', $cityState);
+ }
+
+ // add postal code line
+ if (!empty($locationInfo[self::POSTAL_CODE_KEY])) {
+ $lines[] = $locationInfo[self::POSTAL_CODE_KEY];
+ }
+
+ // add country line
+ if (!empty($locationInfo[self::COUNTRY_NAME_KEY])) {
+ $lines[] = $locationInfo[self::COUNTRY_NAME_KEY];
+ } else if (!empty($locationInfo[self::COUNTRY_CODE_KEY])) {
+ $lines[] = $locationInfo[self::COUNTRY_CODE_KEY];
+ }
+
+ // add extra information (ISP/Organization)
+ if ($includeExtra) {
+ $lines[] = '';
+
+ $unknown = Piwik_Translate('General_Unknown');
+
+ $org = !empty($locationInfo[self::ORG_KEY]) ? $locationInfo[self::ORG_KEY] : $unknown;
+ $lines[] = "Org: $org";
+
+ $isp = !empty($locationInfo[self::ISP_KEY]) ? $locationInfo[self::ISP_KEY] : $unknown;
+ $lines[] = "ISP: $isp";
+ }
+
+ return implode($newline, $lines);
+ }
+
+ /**
+ * Returns an IP address from an array that was passed into getLocation. This
+ * will return an IPv4 address or false if the address is IPv6 (IPv6 is not
+ * supported yet).
+ *
+ * @param array $ip Must have 'ip' key.
+ * @return string|bool
+ */
+ protected function getIpFromInfo($info)
+ {
+ $ip = $info['ip'];
+ if (Piwik_IP::isMappedIPv4($ip)) {
+ return Piwik_IP::getIPv4FromMappedIPv6($ip);
+ } else if (Piwik_IP::isIPv6($ip)) // IPv6 is not supported (yet)
+ {
+ return false;
+ } else {
+ return $ip;
+ }
+ }
}
diff --git a/plugins/UserCountry/LocationProvider/Default.php b/plugins/UserCountry/LocationProvider/Default.php
index a24c3a7cd9..f19fc47641 100755
--- a/plugins/UserCountry/LocationProvider/Default.php
+++ b/plugins/UserCountry/LocationProvider/Default.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_UserCountry
*/
@@ -12,99 +12,99 @@
/**
* The default LocationProvider, this LocationProvider guesses a visitor's country
* using the language they use. This provider is not very accurate.
- *
+ *
* @package Piwik_UserCountry
*/
class Piwik_UserCountry_LocationProvider_Default extends Piwik_UserCountry_LocationProvider
{
- const ID = 'default';
- const TITLE = 'General_Default';
-
- /**
- * Guesses a visitor's location using a visitor's browser language.
- *
- * @param array $info Contains 'ip' & 'lang' keys.
- * @return array Contains the guessed country code mapped to LocationProvider::COUNTRY_CODE_KEY.
- */
- public function getLocation( $info )
- {
- $enableLanguageToCountryGuess = Piwik_Config::getInstance()->Tracker['enable_language_to_country_guess'];
+ const ID = 'default';
+ const TITLE = 'General_Default';
- if(empty($info['lang'])) {
- $info['lang'] = Piwik_Common::getBrowserLanguage();
- }
- $country = Piwik_Common::getCountry($info['lang'], $enableLanguageToCountryGuess, $info['ip']);
-
- $location = array(parent::COUNTRY_CODE_KEY => $country);
- $this->completeLocationResult($location);
-
- return $location;
- }
-
- /**
- * Returns whether this location provider is available.
- *
- * This implementation is always available.
- *
- * @return true
- */
- public function isAvailable()
- {
- return true;
- }
-
- /**
- * Returns whether this location provider is working correctly.
- *
- * This implementation is always working correctly.
- *
- * @return true
- */
- public function isWorking()
- {
- return true;
- }
-
- /**
- * Returns an array describing the types of location information this provider will
- * return.
- *
- * This provider supports the following types of location info:
- * - continent code
- * - continent name
- * - country code
- * - country name
- *
- * @return array
- */
- public function getSupportedLocationInfo()
- {
- return array(self::CONTINENT_CODE_KEY => true,
- self::CONTINENT_NAME_KEY => true,
- self::COUNTRY_CODE_KEY => true,
- self::COUNTRY_NAME_KEY => true);
- }
-
- /**
- * Returns information about this location provider. Contains an id, title & description:
- *
- * array(
- * 'id' => 'default',
- * 'title' => '...',
- * 'description' => '...'
- * );
- *
- * @return array
- */
- public function getInfo()
- {
- $desc = Piwik_Translate('UserCountry_DefaultLocationProviderDesc1') . ' '
- . Piwik_Translate('UserCountry_DefaultLocationProviderDesc2',
- array('<strong>', '<em>', '</em>', '</strong>'))
- . '<p><em><a href="http://piwik.org/faq/how-to/#faq_163" target="_blank">'
- . Piwik_Translate('UserCountry_HowToInstallGeoIPDatabases')
- . '</em></a></p>';
- return array('id' => self::ID, 'title' => self::TITLE, 'description' => $desc, 'order' => 1);
- }
+ /**
+ * Guesses a visitor's location using a visitor's browser language.
+ *
+ * @param array $info Contains 'ip' & 'lang' keys.
+ * @return array Contains the guessed country code mapped to LocationProvider::COUNTRY_CODE_KEY.
+ */
+ public function getLocation($info)
+ {
+ $enableLanguageToCountryGuess = Piwik_Config::getInstance()->Tracker['enable_language_to_country_guess'];
+
+ if (empty($info['lang'])) {
+ $info['lang'] = Piwik_Common::getBrowserLanguage();
+ }
+ $country = Piwik_Common::getCountry($info['lang'], $enableLanguageToCountryGuess, $info['ip']);
+
+ $location = array(parent::COUNTRY_CODE_KEY => $country);
+ $this->completeLocationResult($location);
+
+ return $location;
+ }
+
+ /**
+ * Returns whether this location provider is available.
+ *
+ * This implementation is always available.
+ *
+ * @return true
+ */
+ public function isAvailable()
+ {
+ return true;
+ }
+
+ /**
+ * Returns whether this location provider is working correctly.
+ *
+ * This implementation is always working correctly.
+ *
+ * @return true
+ */
+ public function isWorking()
+ {
+ return true;
+ }
+
+ /**
+ * Returns an array describing the types of location information this provider will
+ * return.
+ *
+ * This provider supports the following types of location info:
+ * - continent code
+ * - continent name
+ * - country code
+ * - country name
+ *
+ * @return array
+ */
+ public function getSupportedLocationInfo()
+ {
+ return array(self::CONTINENT_CODE_KEY => true,
+ self::CONTINENT_NAME_KEY => true,
+ self::COUNTRY_CODE_KEY => true,
+ self::COUNTRY_NAME_KEY => true);
+ }
+
+ /**
+ * Returns information about this location provider. Contains an id, title & description:
+ *
+ * array(
+ * 'id' => 'default',
+ * 'title' => '...',
+ * 'description' => '...'
+ * );
+ *
+ * @return array
+ */
+ public function getInfo()
+ {
+ $desc = Piwik_Translate('UserCountry_DefaultLocationProviderDesc1') . ' '
+ . Piwik_Translate('UserCountry_DefaultLocationProviderDesc2',
+ array('<strong>', '<em>', '</em>', '</strong>'))
+ . '<p><em><a href="http://piwik.org/faq/how-to/#faq_163" target="_blank">'
+ . Piwik_Translate('UserCountry_HowToInstallGeoIPDatabases')
+ . '</em></a></p>';
+ return array('id' => self::ID, 'title' => self::TITLE, 'description' => $desc, 'order' => 1);
+ }
}
diff --git a/plugins/UserCountry/LocationProvider/GeoIp.php b/plugins/UserCountry/LocationProvider/GeoIp.php
index 4af024ae96..f7a9029797 100755
--- a/plugins/UserCountry/LocationProvider/GeoIp.php
+++ b/plugins/UserCountry/LocationProvider/GeoIp.php
@@ -1,274 +1,257 @@
<?php
/**
* Piwik - Open source web analytics
- *
+ *
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
- *
+ *
* @category Piwik_Plugins
* @package Piwik_UserCountry
*/
/**
* Base type for all GeoIP LocationProviders.
- *
+ *
* @package Piwik_UserCountry
*/
abstract class Piwik_UserCountry_LocationProvider_GeoIp extends Piwik_UserCountry_LocationProvider
{
- /* For testing, use: 'http://piwik-team.s3.amazonaws.com/GeoLiteCity.dat.gz' */
- const GEO_LITE_URL = 'http://geolite.maxmind.com/download/geoip/database/GeoLiteCity.dat.gz';
- const TEST_IP = '194.57.91.215';
-
- public static $geoIPDatabaseDir = 'misc';
-
- /**
- * Stores possible database file names categorized by the type of information
- * GeoIP databases hold.
- *
- * @var array
- */
- public static $dbNames = array(
- 'loc' => array('GeoIPCity.dat', 'GeoLiteCity.dat', 'GeoIP.dat'),
- 'isp' => array('GeoIPISP.dat'),
- 'org' => array('GeoIPOrg.dat'),
- );
-
- /**
- * Cached region name array. Data is from geoipregionvars.php.
- *
- * @var array
- */
- private static $regionNames = null;
-
- /**
- * Attempts to fill in some missing information in a GeoIP location.
- *
- * This method will call LocationProvider::completeLocationResult and then
- * try to set the region name of the location if the country code & region
- * code are set.
- *
- * @param array $location The location information to modify.
- */
- public function completeLocationResult( &$location )
- {
- $this->fixupLocation($location);
- parent::completeLocationResult($location);
-
- // set region name if region code is set
- if (empty($location[self::REGION_NAME_KEY])
- && !empty($location[self::REGION_CODE_KEY])
- && !empty($location[self::COUNTRY_CODE_KEY]))
- {
- $countryCode = $location[self::COUNTRY_CODE_KEY];
- $regionCode = (string)$location[self::REGION_CODE_KEY];
- $location[self::REGION_NAME_KEY] = self::getRegionNameFromCodes($countryCode, $regionCode);
- }
- }
-
- /**
- * Fix up data to work with our SVG maps which include 'Tib' boundaries
- */
- protected function fixupLocation( &$location )
- {
- if(!empty($location[self::REGION_CODE_KEY])
- && $location[self::REGION_CODE_KEY] == '14'
- && !empty($location[self::COUNTRY_CODE_KEY])
- && strtoupper($location[self::COUNTRY_CODE_KEY]) == 'CN')
- {
- $location[self::COUNTRY_CODE_KEY] = 'ti';
- $location[self::REGION_CODE_KEY] = '1';
- }
- }
-
- /**
- * Returns true if this provider has been setup correctly, the error message if
- * otherwise.
- *
- * @return bool|string
- */
- public function isWorking()
- {
- // test with an example IP to make sure the provider is working
- // NOTE: At the moment only country, region & city info is tested.
- try
- {
- $supportedInfo = $this->getSupportedLocationInfo();
-
- list($testIp, $expectedResult) = self::getTestIpAndResult();
-
- // get location using test IP
- $location = $this->getLocation(array('ip' => $testIp));
-
- // check that result is the same as expected
- $isResultCorrect = true;
- foreach ($expectedResult as $key => $value)
- {
- // if this provider is not configured to support this information type, skip it
- if (empty($supportedInfo[$key]))
- {
- continue;
- }
-
- if (empty($location[$key])
- || $location[$key] != $value)
- {
- $isResultCorrect = false;
- }
- }
-
- if (!$isResultCorrect)
- {
- $unknown = Piwik_Translate('General_Unknown');
-
- $location = "'"
- . (empty($location[self::CITY_NAME_KEY]) ? $unknown : $location[self::CITY_NAME_KEY])
- . ", "
- . (empty($location[self::REGION_CODE_KEY]) ? $unknown : $location[self::REGION_CODE_KEY])
- . ", "
- . (empty($location[self::COUNTRY_CODE_KEY]) ? $unknown : $location[self::COUNTRY_CODE_KEY])
- . "'"
- ;
-
- $expectedLocation = "'".$expectedResult[self::CITY_NAME_KEY].", "
- . $expectedResult[self::REGION_CODE_KEY].", "
- . $expectedResult[self::COUNTRY_CODE_KEY]."'";
-
- $bind = array($testIp, $location, $expectedLocation);
- return Piwik_Translate('UserCountry_TestIPLocatorFailed', $bind);
- }
-
- return true;
- }
- catch (Exception $ex)
- {
- return $ex->getMessage();
- }
- }
-
- /**
- * Returns a region name for a country code + region code.
- *
- * @param string $countryCode
- * @param string $regionCode
- * @return string The region name or 'Unknown' (translated).
- */
- public static function getRegionNameFromCodes( $countryCode, $regionCode )
- {
- $regionNames = self::getRegionNames();
-
- $countryCode = strtoupper($countryCode);
- $regionCode = strtoupper($regionCode);
-
- if (isset($regionNames[$countryCode][$regionCode]))
- {
- return $regionNames[$countryCode][$regionCode];
- }
- else
- {
- return Piwik_Translate('General_Unknown');
- }
- }
-
- /**
- * Returns an array of region names mapped by country code & region code.
- *
- * @return array
- */
- public static function getRegionNames()
- {
- if (is_null(self::$regionNames))
- {
- require_once PIWIK_INCLUDE_PATH . '/libs/MaxMindGeoIP/geoipregionvars.php';
- self::$regionNames = $GEOIP_REGION_NAME;
- }
-
- return self::$regionNames;
- }
-
- /**
- * Returns the path of an existing GeoIP database or false if none can be found.
- *
- * @param array $possibleFileNames The list of possible file names for the GeoIP database.
- * @return string|false
- */
- public static function getPathToGeoIpDatabase( $possibleFileNames )
- {
- foreach ($possibleFileNames as $filename)
- {
- $path = self::getPathForGeoIpDatabase($filename);
- if (file_exists($path))
- {
- return $path;
- }
- }
- return false;
- }
-
- /**
- * Returns full path for a GeoIP database managed by Piwik.
- *
- * @param string $filename Name of the .dat file.
- * @return string
- */
- public static function getPathForGeoIpDatabase( $filename )
- {
- return PIWIK_INCLUDE_PATH.'/'.self::$geoIPDatabaseDir.'/'.$filename;
- }
-
- /**
- * Returns test IP used by isWorking and expected result.
- *
- * @return array eg. array('1.2.3.4', array(self::COUNTRY_CODE_KEY => ...))
- */
- private static function getTestIpAndResult()
- {
- static $result = null;
- if (is_null($result))
- {
- // TODO: what happens when IP changes? should we get this information from piwik.org?
- $expected = array(self::COUNTRY_CODE_KEY => 'FR',
- self::REGION_CODE_KEY => 'A6',
- self::CITY_NAME_KEY => 'Besançon');
- $result = array(self::TEST_IP, $expected);
- }
- return $result;
- }
-
- /**
- * Returns true if there is a GeoIP database in the 'misc' directory.
- *
- * @return bool
- */
- public static function isDatabaseInstalled()
- {
- return self::getPathToGeoIpDatabase(self::$dbNames['loc'])
- || self::getPathToGeoIpDatabase(self::$dbNames['isp'])
- || self::getPathToGeoIpDatabase(self::$dbNames['org']);
- }
-
- /**
- * Returns the type of GeoIP database ('loc', 'isp' or 'org') based on the
- * filename (eg, 'GeoLiteCity.dat', 'GeoIPISP.dat', etc).
- *
- * @param string $filename
- * @return string|false 'loc', 'isp', 'org', or false if cannot find a database
- * type.
- */
- public static function getGeoIPDatabaseTypeFromFilename( $filename )
- {
- foreach (self::$dbNames as $key => $names)
- {
- foreach ($names as $name)
- {
- if ($name === $filename)
- {
- return $key;
- }
- }
- }
- return false;
- }
+ /* For testing, use: 'http://piwik-team.s3.amazonaws.com/GeoLiteCity.dat.gz' */
+ const GEO_LITE_URL = 'http://geolite.maxmind.com/download/geoip/database/GeoLiteCity.dat.gz';
+ const TEST_IP = '194.57.91.215';
+
+ public static $geoIPDatabaseDir = 'misc';
+
+ /**
+ * Stores possible database file names categorized by the type of information
+ * GeoIP databases hold.
+ *
+ * @var array
+ */
+ public static $dbNames = array(
+ 'loc' => array('GeoIPCity.dat', 'GeoLiteCity.dat', 'GeoIP.dat'),
+ 'isp' => array('GeoIPISP.dat'),
+ 'org' => array('GeoIPOrg.dat'),
+ );
+
+ /**
+ * Cached region name array. Data is from geoipregionvars.php.
+ *
+ * @var array
+ */
+ private static $regionNames = null;
+
+ /**
+ * Attempts to fill in some missing information in a GeoIP location.
+ *
+ * This method will call LocationProvider::completeLocationResult and then
+ * try to set the region name of the location if the country code & region
+ * code are set.
+ *
+ * @param array $location The location information to modify.
+ */
+ public function completeLocationResult(&$location)
+ {
+ $this->fixupLocation($location);
+ parent::completeLocationResult($location);
+
+ // set region name if region code is set
+ if (empty($location[self::REGION_NAME_KEY])
+ && !empty($location[self::REGION_CODE_KEY])
+ && !empty($location[self::COUNTRY_CODE_KEY])
+ ) {
+ $countryCode = $location[self::COUNTRY_CODE_KEY];
+ $regionCode = (string)$location[self::REGION_CODE_KEY];
+ $location[self::REGION_NAME_KEY] = self::getRegionNameFromCodes($countryCode, $regionCode);
+ }
+ }
+
+ /**
+ * Fix up data to work with our SVG maps which include 'Tib' boundaries
+ */
+ protected function fixupLocation(&$location)
+ {
+ if (!empty($location[self::REGION_CODE_KEY])
+ && $location[self::REGION_CODE_KEY] == '14'
+ && !empty($location[self::COUNTRY_CODE_KEY])
+ && strtoupper($location[self::COUNTRY_CODE_KEY]) == 'CN'
+ ) {
+ $location[self::COUNTRY_CODE_KEY] = 'ti';
+ $location[self::REGION_CODE_KEY] = '1';
+ }
+ }
+
+ /**
+ * Returns true if this provider has been setup correctly, the error message if
+ * otherwise.
+ *
+ * @return bool|string
+ */
+ public function isWorking()
+ {
+ // test with an example IP to make sure the provider is working
+ // NOTE: At the moment only country, region & city info is tested.
+ try {
+ $supportedInfo = $this->getSupportedLocationInfo();
+
+ list($testIp, $expectedResult) = self::getTestIpAndResult();
+
+ // get location using test IP
+ $location = $this->getLocation(array('ip' => $testIp));
+
+ // check that result is the same as expected
+ $isResultCorrect = true;
+ foreach ($expectedResult as $key => $value) {
+ // if this provider is not configured to support this information type, skip it
+ if (empty($supportedInfo[$key])) {
+ continue;
+ }
+
+ if (empty($location[$key])
+ || $location[$key] != $value
+ ) {
+ $isResultCorrect = false;
+ }
+ }
+
+ if (!$isResultCorrect) {
+ $unknown = Piwik_Translate('General_Unknown');
+
+ $location = "'"
+ . (empty($location[self::CITY_NAME_KEY]) ? $unknown : $location[self::CITY_NAME_KEY])
+ . ", "
+ . (empty($location[self::REGION_CODE_KEY]) ? $unknown : $location[self::REGION_CODE_KEY])
+ . ", "
+ . (empty($location[self::COUNTRY_CODE_KEY]) ? $unknown : $location[self::COUNTRY_CODE_KEY])
+ . "'";
+
+ $expectedLocation = "'" . $expectedResult[self::CITY_NAME_KEY] . ", "
+ . $expectedResult[self::REGION_CODE_KEY] . ", "
+ . $expectedResult[self::COUNTRY_CODE_KEY] . "'";
+
+ $bind = array($testIp, $location, $expectedLocation);
+ return Piwik_Translate('UserCountry_TestIPLocatorFailed', $bind);
+ }
+
+ return true;
+ } catch (Exception $ex) {
+ return $ex->getMessage();
+ }
+ }
+
+ /**
+ * Returns a region name for a country code + region code.
+ *
+ * @param string $countryCode
+ * @param string $regionCode
+ * @return string The region name or 'Unknown' (translated).
+ */
+ public static function getRegionNameFromCodes($countryCode, $regionCode)
+ {
+ $regionNames = self::getRegionNames();
+
+ $countryCode = strtoupper($countryCode);
+ $regionCode = strtoupper($regionCode);
+
+ if (isset($regionNames[$countryCode][$regionCode])) {
+ return $regionNames[$countryCode][$regionCode];
+ } else {
+ return Piwik_Translate('General_Unknown');
+ }
+ }
+
+ /**
+ * Returns an array of region names mapped by country code & region code.
+ *
+ * @return array
+ */
+ public static function getRegionNames()
+ {
+ if (is_null(self::$regionNames)) {
+ require_once PIWIK_INCLUDE_PATH . '/libs/MaxMindGeoIP/geoipregionvars.php';
+ self::$regionNames = $GEOIP_REGION_NAME;
+ }
+
+ return self::$regionNames;
+ }
+
+ /**
+ * Returns the path of an existing GeoIP database or false if none can be found.
+ *
+ * @param array $possibleFileNames The list of possible file names for the GeoIP database.
+ * @return string|false
+ */
+ public static function getPathToGeoIpDatabase($possibleFileNames)
+ {
+ foreach ($possibleFileNames as $filename) {
+ $path = self::getPathForGeoIpDatabase($filename);
+ if (file_exists($path)) {
+ return $path;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns full path for a GeoIP database managed by Piwik.
+ *
+ * @param string $filename Name of the .dat file.
+ * @return string
+ */
+ public static function getPathForGeoIpDatabase($filename)
+ {
+ return PIWIK_INCLUDE_PATH . '/' . self::$geoIPDatabaseDir . '/' . $filename;
+ }
+
+ /**
+ * Returns test IP used by isWorking and expected result.
+ *
+ * @return array eg. array('1.2.3.4', array(self::COUNTRY_CODE_KEY => ...))
+ */
+ private static function getTestIpAndResult()
+ {
+ static $result = null;
+ if (is_null($result)) {
+ // TODO: what happens when IP changes? should we get this information from piwik.org?
+ $expected = array(self::COUNTRY_CODE_KEY => 'FR',
+ self::REGION_CODE_KEY => 'A6',
+ self::CITY_NAME_KEY => 'Besançon');
+ $result = array(self::TEST_IP, $expected);
+ }
+ return $result;
+ }
+
+ /**
+ * Returns true if there is a GeoIP database in the 'misc' directory.
+ *
+ * @return bool
+ */
+ public static function isDatabaseInstalled()
+ {
+ return self::getPathToGeoIpDatabase(self::$dbNames['loc'])
+ || self::getPathToGeoIpDatabase(self::$dbNames['isp'])
+ || self::getPathToGeoIpDatabase(self::$dbNames['org']);
+ }
+
+ /**
+ * Returns the type of GeoIP database ('loc', 'isp' or 'org') based on the
+ * filename (eg, 'GeoLiteCity.dat', 'GeoIPISP.dat', etc).
+ *
+ * @param string $filename
+ * @return string|false 'loc', 'isp', 'org', or false if cannot find a database
+ * type.
+ */
+ public static function getGeoIPDatabaseTypeFromFilename($filename)
+ {
+ foreach (self::$dbNames as $key => $names) {
+ foreach ($names as $name) {
+ if ($name === $filename) {
+ return $key;
+ }
+ }
+ }
+ return false;
+ }
}
diff --git a/plugins/UserCountry/LocationProvider/GeoIp/Pecl.php b/plugins/UserCountry/LocationProvider/GeoIp/Pecl.php
index b484cbf6b5..9ffc4ffa0a 100755
--- a/plugins/UserCountry/LocationProvider/GeoIp/Pecl.php
+++ b/plugins/UserCountry/LocationProvider/GeoIp/Pecl.php
@@ -1,353 +1,324 @@
<?php
/**
* Piwik - Open source web analytics
- *
+ *
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
- *
+ *
* @category Piwik_Plugins
* @package Piwik_UserCountry
*/
/**
* A LocationProvider that uses the PECL implementation of GeoIP.
- *
+ *
* FIXME: For some reason, if the PECL module is loaded & an organization DB is available, the PHP
* module won't return organization info. If the PECL module is not loaded, organization info is returned.
- *
+ *
* @package Piwik_UserCountry
*/
class Piwik_UserCountry_LocationProvider_GeoIp_Pecl extends Piwik_UserCountry_LocationProvider_GeoIp
{
- const ID = 'geoip_pecl';
- const TITLE = 'GeoIP (PECL)';
-
- /**
- * Uses the GeoIP PECL module to get a visitor's location based on their IP address.
- *
- * This function will return different results based on the data available. If a city
- * database can be detected by the PECL module, it may return the country code,
- * region code, city name, area code, latitude, longitude and postal code of the visitor.
- *
- * Alternatively, if only the country database can be detected, only the country code
- * will be returned.
- *
- * The GeoIP PECL module will detect the following filenames:
- * - GeoIP.dat
- * - GeoIPCity.dat
- * - GeoIPISP.dat
- * - GeoIPOrg.dat
- *
- * Note how GeoLiteCity.dat, the name for the GeoLite city database, is not detected
- * by the PECL module.
- *
- * @param array $info Must have an 'ip' field.
- * @return array
- */
- public function getLocation( $info )
- {
- $ip = $this->getIpFromInfo($info);
-
- $result = array();
-
- // get location data
- if (self::isCityDatabaseAvailable())
- {
- // Must hide errors because missing IPV6:
- $location = @geoip_record_by_name($ip);
- if (!empty($location))
- {
- $result[self::COUNTRY_CODE_KEY] = $location['country_code'];
- $result[self::REGION_CODE_KEY] = $location['region'];
- $result[self::CITY_NAME_KEY] = utf8_encode($location['city']);
- $result[self::AREA_CODE_KEY] = $location['area_code'];
- $result[self::LATITUDE_KEY] = $location['latitude'];
- $result[self::LONGITUDE_KEY] = $location['longitude'];
- $result[self::POSTAL_CODE_KEY] = $location['postal_code'];
- }
- }
- else if (self::isRegionDatabaseAvailable())
- {
- $location = @geoip_region_by_name($ip);
- if (!empty($location))
- {
- $result[self::REGION_CODE_KEY] = $location['region'];
- $result[self::COUNTRY_CODE_KEY] = $location['country_code'];
- }
- }
- else
- {
- $result[self::COUNTRY_CODE_KEY] = @geoip_country_code_by_name($ip);
- }
-
- // get organization data if the org database is available
- if (self::isOrgDatabaseAvailable())
- {
- $org = @geoip_org_by_name($ip);
- if ($org !== false)
- {
- $result[self::ORG_KEY] = utf8_encode($org);
- }
- }
-
- // get isp data if the isp database is available
- if (self::isISPDatabaseAvailable())
- {
- $isp = @geoip_isp_by_name($ip);
- if ($ip !== false)
- {
- $result[self::ISP_KEY] = utf8_encode($isp);
- }
- }
-
- if (empty($result))
- {
- return false;
- }
-
- $this->completeLocationResult($result);
- return $result;
- }
-
- /**
- * Returns true if the PECL module is installed and loaded, false if otherwise.
- *
- * @return bool
- */
- public function isAvailable()
- {
- return function_exists('geoip_db_avail');
- }
-
- /**
- * Returns true if the PECL module that is installed can be successfully used
- * to get the location of an IP address.
- *
- * @return bool
- */
- public function isWorking()
- {
- // if no no location database is available, this implementation is not setup correctly
- if (!self::isLocationDatabaseAvailable())
- {
- $dbDir = dirname(geoip_db_filename(GEOIP_COUNTRY_EDITION)).'/';
- $quotedDir = "'$dbDir'";
-
- // check if the directory the PECL module is looking for exists
- if (!is_dir($dbDir))
- {
- return Piwik_Translate('UserCountry_PeclGeoIPNoDBDir', array($quotedDir, "'geoip.custom_directory'"));
- }
-
- // check if the user named the city database GeoLiteCity.dat
- if (file_exists($dbDir.'GeoLiteCity.dat'))
- {
- return Piwik_Translate('UserCountry_PeclGeoLiteError',
- array($quotedDir, "'GeoLiteCity.dat'", "'GeoIPCity.dat'"));
- }
-
- return Piwik_Translate('UserCountry_CannotFindPeclGeoIPDb',
- array($quotedDir, "'GeoIP.dat'", "'GeoIPCity.dat'"));
- }
-
- return parent::isWorking();
- }
-
- /**
- * Returns an array describing the types of location information this provider will
- * return.
- *
- * The location info this provider supports depends on what GeoIP databases it can
- * find.
- *
- * This provider will always support country & continent information.
- *
- * If a region database is found, then region code & name information will be
- * supported.
- *
- * If a city database is found, then region code, region name, city name,
- * area code, latitude, longitude & postal code are all supported.
- *
- * If an organization database is found, organization information is
- * supported.
- *
- * If an ISP database is found, ISP information is supported.
- *
- * @return array
- */
- public function getSupportedLocationInfo()
- {
- $result = array();
-
- // country & continent info always available
- $result[self::CONTINENT_CODE_KEY] = true;
- $result[self::CONTINENT_NAME_KEY] = true;
- $result[self::COUNTRY_CODE_KEY] = true;
- $result[self::COUNTRY_NAME_KEY] = true;
-
- if (self::isCityDatabaseAvailable())
- {
- $result[self::REGION_CODE_KEY] = true;
- $result[self::REGION_NAME_KEY] = true;
- $result[self::CITY_NAME_KEY] = true;
- $result[self::AREA_CODE_KEY] = true;
- $result[self::LATITUDE_KEY] = true;
- $result[self::LONGITUDE_KEY] = true;
- $result[self::POSTAL_CODE_KEY] = true;
- }
- else if (self::isRegionDatabaseAvailable())
- {
- $result[self::REGION_CODE_KEY] = true;
- $result[self::REGION_NAME_KEY] = true;
- }
-
- // check if organization info is available
- if (self::isOrgDatabaseAvailable())
- {
- $result[self::ORG_KEY] = true;
- }
-
- // check if ISP info is available
- if (self::isISPDatabaseAvailable())
- {
- $result[self::ISP_KEY] = true;
- }
-
- return $result;
- }
-
- /**
- * Returns information about this location provider. Contains an id, title & description:
- *
- * array(
- * 'id' => 'geoip_pecl',
- * 'title' => '...',
- * 'description' => '...'
- * );
- *
- * @return array
- */
- public function getInfo()
- {
- $desc = Piwik_Translate('UserCountry_GeoIpLocationProviderDesc_Pecl1') . '<br/><br/>'
- . Piwik_Translate('UserCountry_GeoIpLocationProviderDesc_Pecl2');
- $installDocs = '<em>'
- . '<a target="_blank" href="http://piwik.org/faq/how-to/#faq_164">'
- . Piwik_Translate('UserCountry_HowToInstallGeoIpPecl')
- . '</a>'
- . '</em>';
-
- $extraMessage = false;
- if ($this->isAvailable())
- {
- $peclDir = ini_get('geoip.custom_directory');
- if ($peclDir === false)
- {
- $extraMessage = Piwik_Translate('UserCountry_GeoIPPeclCustomDirNotSet', "'geoip.custom_directory'");
- }
- else
- {
- $extraMessage = 'The \'geoip.custom_directory\' PHP ini option is set to \''.$peclDir.'\'.';
- }
-
- $availableDatabaseTypes = array();
- if (self::isCityDatabaseAvailable())
- {
- $availableDatabaseTypes[] = Piwik_Translate('UserCountry_City');
- }
- if (self::isRegionDatabaseAvailable())
- {
- $availableDatabaseTypes[] = Piwik_Translate('UserCountry_Region');
- }
- if (self::isCountryDatabaseAvailable())
- {
- $availableDatabaseTypes[] = Piwik_Translate('UserCountry_Country');
- }
- if (self::isISPDatabaseAvailable())
- {
- $availableDatabaseTypes[] = 'ISP';
- }
- if (self::isOrgDatabaseAvailable())
- {
- $availableDatabaseTypes[] = Piwik_Translate('UserCountry_Organization');
- }
-
- $extraMessage .= '<br/><br/>'.Piwik_Translate('UserCountry_GeoIPImplHasAccessTo').':&nbsp;<strong><em>'
- . implode(', ', $availableDatabaseTypes).'</em></strong>.';
-
- $extraMessage = '<strong><em>'.Piwik_Translate('General_Note').':&nbsp;</em></strong>'.$extraMessage;
- }
-
- return array('id' => self::ID,
- 'title' => self::TITLE,
- 'description' => $desc,
- 'install_docs' => $installDocs,
- 'extra_message' => $extraMessage,
- 'order' => 3);
- }
-
- /**
- * Returns true if the PECL module can detect a location database (either a country,
- * region or city will do).
- *
- * @return bool
- */
- public static function isLocationDatabaseAvailable()
- {
- return self::isCityDatabaseAvailable()
- || self::isRegionDatabaseAvailable()
- || self::isCountryDatabaseAvailable();
- }
-
- /**
- * Returns true if the PECL module can detect a city database.
- *
- * @return bool
- */
- public static function isCityDatabaseAvailable()
- {
- return geoip_db_avail(GEOIP_CITY_EDITION_REV0)
- || geoip_db_avail(GEOIP_CITY_EDITION_REV1);
- }
-
- /**
- * Returns true if the PECL module can detect a region database.
- *
- * @return bool
- */
- public static function isRegionDatabaseAvailable()
- {
- return geoip_db_avail(GEOIP_REGION_EDITION_REV0)
- || geoip_db_avail(GEOIP_REGION_EDITION_REV1);
- }
-
- /**
- * Returns true if the PECL module can detect a country database.
- *
- * @return bool
- */
- public static function isCountryDatabaseAvailable()
- {
- return geoip_db_avail(GEOIP_COUNTRY_EDITION);
- }
-
- /**
- * Returns true if the PECL module can detect an organization database.
- *
- * @return bool
- */
- public static function isOrgDatabaseAvailable()
- {
- return geoip_db_avail(GEOIP_ORG_EDITION);
- }
-
- /**
- * Returns true if the PECL module can detect an ISP database.
- *
- * @return bool
- */
- public static function isISPDatabaseAvailable()
- {
- return geoip_db_avail(GEOIP_ISP_EDITION);
- }
+ const ID = 'geoip_pecl';
+ const TITLE = 'GeoIP (PECL)';
+
+ /**
+ * Uses the GeoIP PECL module to get a visitor's location based on their IP address.
+ *
+ * This function will return different results based on the data available. If a city
+ * database can be detected by the PECL module, it may return the country code,
+ * region code, city name, area code, latitude, longitude and postal code of the visitor.
+ *
+ * Alternatively, if only the country database can be detected, only the country code
+ * will be returned.
+ *
+ * The GeoIP PECL module will detect the following filenames:
+ * - GeoIP.dat
+ * - GeoIPCity.dat
+ * - GeoIPISP.dat
+ * - GeoIPOrg.dat
+ *
+ * Note how GeoLiteCity.dat, the name for the GeoLite city database, is not detected
+ * by the PECL module.
+ *
+ * @param array $info Must have an 'ip' field.
+ * @return array
+ */
+ public function getLocation($info)
+ {
+ $ip = $this->getIpFromInfo($info);
+
+ $result = array();
+
+ // get location data
+ if (self::isCityDatabaseAvailable()) {
+ // Must hide errors because missing IPV6:
+ $location = @geoip_record_by_name($ip);
+ if (!empty($location)) {
+ $result[self::COUNTRY_CODE_KEY] = $location['country_code'];
+ $result[self::REGION_CODE_KEY] = $location['region'];
+ $result[self::CITY_NAME_KEY] = utf8_encode($location['city']);
+ $result[self::AREA_CODE_KEY] = $location['area_code'];
+ $result[self::LATITUDE_KEY] = $location['latitude'];
+ $result[self::LONGITUDE_KEY] = $location['longitude'];
+ $result[self::POSTAL_CODE_KEY] = $location['postal_code'];
+ }
+ } else if (self::isRegionDatabaseAvailable()) {
+ $location = @geoip_region_by_name($ip);
+ if (!empty($location)) {
+ $result[self::REGION_CODE_KEY] = $location['region'];
+ $result[self::COUNTRY_CODE_KEY] = $location['country_code'];
+ }
+ } else {
+ $result[self::COUNTRY_CODE_KEY] = @geoip_country_code_by_name($ip);
+ }
+
+ // get organization data if the org database is available
+ if (self::isOrgDatabaseAvailable()) {
+ $org = @geoip_org_by_name($ip);
+ if ($org !== false) {
+ $result[self::ORG_KEY] = utf8_encode($org);
+ }
+ }
+
+ // get isp data if the isp database is available
+ if (self::isISPDatabaseAvailable()) {
+ $isp = @geoip_isp_by_name($ip);
+ if ($ip !== false) {
+ $result[self::ISP_KEY] = utf8_encode($isp);
+ }
+ }
+
+ if (empty($result)) {
+ return false;
+ }
+
+ $this->completeLocationResult($result);
+ return $result;
+ }
+
+ /**
+ * Returns true if the PECL module is installed and loaded, false if otherwise.
+ *
+ * @return bool
+ */
+ public function isAvailable()
+ {
+ return function_exists('geoip_db_avail');
+ }
+
+ /**
+ * Returns true if the PECL module that is installed can be successfully used
+ * to get the location of an IP address.
+ *
+ * @return bool
+ */
+ public function isWorking()
+ {
+ // if no no location database is available, this implementation is not setup correctly
+ if (!self::isLocationDatabaseAvailable()) {
+ $dbDir = dirname(geoip_db_filename(GEOIP_COUNTRY_EDITION)) . '/';
+ $quotedDir = "'$dbDir'";
+
+ // check if the directory the PECL module is looking for exists
+ if (!is_dir($dbDir)) {
+ return Piwik_Translate('UserCountry_PeclGeoIPNoDBDir', array($quotedDir, "'geoip.custom_directory'"));
+ }
+
+ // check if the user named the city database GeoLiteCity.dat
+ if (file_exists($dbDir . 'GeoLiteCity.dat')) {
+ return Piwik_Translate('UserCountry_PeclGeoLiteError',
+ array($quotedDir, "'GeoLiteCity.dat'", "'GeoIPCity.dat'"));
+ }
+
+ return Piwik_Translate('UserCountry_CannotFindPeclGeoIPDb',
+ array($quotedDir, "'GeoIP.dat'", "'GeoIPCity.dat'"));
+ }
+
+ return parent::isWorking();
+ }
+
+ /**
+ * Returns an array describing the types of location information this provider will
+ * return.
+ *
+ * The location info this provider supports depends on what GeoIP databases it can
+ * find.
+ *
+ * This provider will always support country & continent information.
+ *
+ * If a region database is found, then region code & name information will be
+ * supported.
+ *
+ * If a city database is found, then region code, region name, city name,
+ * area code, latitude, longitude & postal code are all supported.
+ *
+ * If an organization database is found, organization information is
+ * supported.
+ *
+ * If an ISP database is found, ISP information is supported.
+ *
+ * @return array
+ */
+ public function getSupportedLocationInfo()
+ {
+ $result = array();
+
+ // country & continent info always available
+ $result[self::CONTINENT_CODE_KEY] = true;
+ $result[self::CONTINENT_NAME_KEY] = true;
+ $result[self::COUNTRY_CODE_KEY] = true;
+ $result[self::COUNTRY_NAME_KEY] = true;
+
+ if (self::isCityDatabaseAvailable()) {
+ $result[self::REGION_CODE_KEY] = true;
+ $result[self::REGION_NAME_KEY] = true;
+ $result[self::CITY_NAME_KEY] = true;
+ $result[self::AREA_CODE_KEY] = true;
+ $result[self::LATITUDE_KEY] = true;
+ $result[self::LONGITUDE_KEY] = true;
+ $result[self::POSTAL_CODE_KEY] = true;
+ } else if (self::isRegionDatabaseAvailable()) {
+ $result[self::REGION_CODE_KEY] = true;
+ $result[self::REGION_NAME_KEY] = true;
+ }
+
+ // check if organization info is available
+ if (self::isOrgDatabaseAvailable()) {
+ $result[self::ORG_KEY] = true;
+ }
+
+ // check if ISP info is available
+ if (self::isISPDatabaseAvailable()) {
+ $result[self::ISP_KEY] = true;
+ }
+
+ return $result;
+ }
+
+ /**
+ * Returns information about this location provider. Contains an id, title & description:
+ *
+ * array(
+ * 'id' => 'geoip_pecl',
+ * 'title' => '...',
+ * 'description' => '...'
+ * );
+ *
+ * @return array
+ */
+ public function getInfo()
+ {
+ $desc = Piwik_Translate('UserCountry_GeoIpLocationProviderDesc_Pecl1') . '<br/><br/>'
+ . Piwik_Translate('UserCountry_GeoIpLocationProviderDesc_Pecl2');
+ $installDocs = '<em>'
+ . '<a target="_blank" href="http://piwik.org/faq/how-to/#faq_164">'
+ . Piwik_Translate('UserCountry_HowToInstallGeoIpPecl')
+ . '</a>'
+ . '</em>';
+
+ $extraMessage = false;
+ if ($this->isAvailable()) {
+ $peclDir = ini_get('geoip.custom_directory');
+ if ($peclDir === false) {
+ $extraMessage = Piwik_Translate('UserCountry_GeoIPPeclCustomDirNotSet', "'geoip.custom_directory'");
+ } else {
+ $extraMessage = 'The \'geoip.custom_directory\' PHP ini option is set to \'' . $peclDir . '\'.';
+ }
+
+ $availableDatabaseTypes = array();
+ if (self::isCityDatabaseAvailable()) {
+ $availableDatabaseTypes[] = Piwik_Translate('UserCountry_City');
+ }
+ if (self::isRegionDatabaseAvailable()) {
+ $availableDatabaseTypes[] = Piwik_Translate('UserCountry_Region');
+ }
+ if (self::isCountryDatabaseAvailable()) {
+ $availableDatabaseTypes[] = Piwik_Translate('UserCountry_Country');
+ }
+ if (self::isISPDatabaseAvailable()) {
+ $availableDatabaseTypes[] = 'ISP';
+ }
+ if (self::isOrgDatabaseAvailable()) {
+ $availableDatabaseTypes[] = Piwik_Translate('UserCountry_Organization');
+ }
+
+ $extraMessage .= '<br/><br/>' . Piwik_Translate('UserCountry_GeoIPImplHasAccessTo') . ':&nbsp;<strong><em>'
+ . implode(', ', $availableDatabaseTypes) . '</em></strong>.';
+
+ $extraMessage = '<strong><em>' . Piwik_Translate('General_Note') . ':&nbsp;</em></strong>' . $extraMessage;
+ }
+
+ return array('id' => self::ID,
+ 'title' => self::TITLE,
+ 'description' => $desc,
+ 'install_docs' => $installDocs,
+ 'extra_message' => $extraMessage,
+ 'order' => 3);
+ }
+
+ /**
+ * Returns true if the PECL module can detect a location database (either a country,
+ * region or city will do).
+ *
+ * @return bool
+ */
+ public static function isLocationDatabaseAvailable()
+ {
+ return self::isCityDatabaseAvailable()
+ || self::isRegionDatabaseAvailable()
+ || self::isCountryDatabaseAvailable();
+ }
+
+ /**
+ * Returns true if the PECL module can detect a city database.
+ *
+ * @return bool
+ */
+ public static function isCityDatabaseAvailable()
+ {
+ return geoip_db_avail(GEOIP_CITY_EDITION_REV0)
+ || geoip_db_avail(GEOIP_CITY_EDITION_REV1);
+ }
+
+ /**
+ * Returns true if the PECL module can detect a region database.
+ *
+ * @return bool
+ */
+ public static function isRegionDatabaseAvailable()
+ {
+ return geoip_db_avail(GEOIP_REGION_EDITION_REV0)
+ || geoip_db_avail(GEOIP_REGION_EDITION_REV1);
+ }
+
+ /**
+ * Returns true if the PECL module can detect a country database.
+ *
+ * @return bool
+ */
+ public static function isCountryDatabaseAvailable()
+ {
+ return geoip_db_avail(GEOIP_COUNTRY_EDITION);
+ }
+
+ /**
+ * Returns true if the PECL module can detect an organization database.
+ *
+ * @return bool
+ */
+ public static function isOrgDatabaseAvailable()
+ {
+ return geoip_db_avail(GEOIP_ORG_EDITION);
+ }
+
+ /**
+ * Returns true if the PECL module can detect an ISP database.
+ *
+ * @return bool
+ */
+ public static function isISPDatabaseAvailable()
+ {
+ return geoip_db_avail(GEOIP_ISP_EDITION);
+ }
}
diff --git a/plugins/UserCountry/LocationProvider/GeoIp/Php.php b/plugins/UserCountry/LocationProvider/GeoIp/Php.php
index c2443822fd..7e60ac520a 100755
--- a/plugins/UserCountry/LocationProvider/GeoIp/Php.php
+++ b/plugins/UserCountry/LocationProvider/GeoIp/Php.php
@@ -1,355 +1,330 @@
<?php
/**
* Piwik - Open source web analytics
- *
+ *
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
- *
+ *
* @category Piwik_Plugins
* @package Piwik_UserCountry
*/
/**
* A LocationProvider that uses the PHP implementation of GeoIP.
- *
+ *
* @package Piwik_UserCountry
*/
class Piwik_UserCountry_LocationProvider_GeoIp_Php extends Piwik_UserCountry_LocationProvider_GeoIp
{
- const ID = 'geoip_php';
- const TITLE = 'GeoIP (Php)';
-
- /**
- * The GeoIP database instances used. This array will contain at most three
- * of them: one for location info, one for ISP info and another for organization
- * info.
- *
- * Each instance is mapped w/ one of the following keys: 'loc', 'isp', 'org'
- *
- * @var array of GeoIP instances
- */
- private $geoIpCache = array();
-
- /**
- * Possible filenames for each type of GeoIP database. When looking for a database
- * file in the 'misc' subdirectory, files with these names will be looked for.
- *
- * This variable is an array mapping either the 'loc', 'isp' or 'org' strings with
- * an array of filenames.
- *
- * By default, this will be set to Piwik_UserCountry_LocationProvider_GeoIp_Php::$dbNames.
- *
- * @var array
- */
- private $customDbNames;
-
- /**
- * Constructor.
- *
- * @param array|false $customDbNames The possible filenames for each type of GeoIP database.
- * eg array(
- * 'loc' => array('GeoLiteCity.dat'),
- * 'isp' => array('GeoIP.dat', 'GeoIPISP.dat')
- * 'org' => array('GeoIPOrg.dat')
- * )
- * If a key is missing (or the parameter not supplied), then the
- * default database names are used.
- */
- public function __construct( $customDbNames = false )
- {
- $this->customDbNames = parent::$dbNames;
- if ($customDbNames !== false)
- {
- foreach ($this->customDbNames as $key => $names)
- {
- if (isset($customDbNames[$key]))
- {
- $this->customDbNames[$key] = $customDbNames[$key];
- }
- }
- }
- }
-
- /**
- * Closes all open geoip instances.
- */
- public function __destruct()
- {
- foreach ($this->geoIpCache as $instance)
- {
- geoip_close($instance);
- }
- }
-
- /**
- * Uses a GeoIP database to get a visitor's location based on their IP address.
- *
- * This function will return different results based on the data used. If a city
- * database is used, it may return the country code, region code, city name, area
- * code, latitude, longitude and postal code of the visitor.
- *
- * Alternatively, if used with a country database, only the country code will be
- * returned.
- *
- * @param array $info Must have an 'ip' field.
- * @return array
- */
- public function getLocation( $info )
- {
- $ip = $this->getIpFromInfo($info);
-
- $result = array();
-
- $locationGeoIp = $this->getGeoIpInstance($key = 'loc');
- if ($locationGeoIp)
- {
- switch ($locationGeoIp->databaseType)
- {
- case GEOIP_CITY_EDITION_REV0: // city database type
- case GEOIP_CITY_EDITION_REV1:
- case GEOIP_CITYCOMBINED_EDITION:
- $location = geoip_record_by_addr($locationGeoIp, $ip);
- if (!empty($location))
- {
- $result[self::COUNTRY_CODE_KEY] = $location->country_code;
- $result[self::REGION_CODE_KEY] = $location->region;
- $result[self::CITY_NAME_KEY] = utf8_encode($location->city);
- $result[self::AREA_CODE_KEY] = $location->area_code;
- $result[self::LATITUDE_KEY] = $location->latitude;
- $result[self::LONGITUDE_KEY] = $location->longitude;
- $result[self::POSTAL_CODE_KEY] = $location->postal_code;
- }
- break;
- case GEOIP_REGION_EDITION_REV0: // region database type
- case GEOIP_REGION_EDITION_REV1:
- $location = geoip_region_by_addr($locationGeoIp, $ip);
- if (!empty($location))
- {
- $result[self::COUNTRY_CODE_KEY] = $location[0];
- $result[self::REGION_CODE_KEY] = $location[1];
- }
- break;
- case GEOIP_COUNTRY_EDITION: // country database type
- $result[self::COUNTRY_CODE_KEY] = geoip_country_code_by_addr($locationGeoIp, $ip);
- break;
- default: // unknown database type, log warning and fallback to country edition
- Piwik::log(sprintf("Found unrecognized database type: %s", $locationGeoIp->databaseType));
-
- $result[self::COUNTRY_CODE_KEY] = geoip_country_code_by_addr($locationGeoIp, $ip);
- break;
- }
- }
-
- // NOTE: ISP & ORG require commercial dbs to test. this code has been tested manually,
- // but not by integration tests.
- $ispGeoIp = $this->getGeoIpInstance($key = 'isp');
- if ($ispGeoIp)
- {
- $isp = geoip_org_by_addr($ispGeoIp, $ip);
- if (!empty($isp))
- {
- $result[self::ISP_KEY] = utf8_encode($isp);
- }
- }
-
- $orgGeoIp = $this->getGeoIpInstance($key = 'org');
- if ($orgGeoIp)
- {
- $org = geoip_org_by_addr($orgGeoIp, $ip);
- if (!empty($org))
- {
- $result[self::ORG_KEY] = utf8_encode($org);
- }
- }
-
- if (empty($result))
- {
- return false;
- }
-
- $this->completeLocationResult($result);
- return $result;
- }
-
- /**
- * Returns true if this location provider is available. Piwik ships w/ the MaxMind
- * PHP library, so this provider is available if a location GeoIP database can be found.
- *
- * @return bool
- */
- public function isAvailable()
- {
- $path = self::getPathToGeoIpDatabase($this->customDbNames['loc']);
- return $path !== false;
- }
-
- /**
- * Returns true if this provider has been setup correctly, the error message if
- * otherwise.
- *
- * @return bool|string
- */
- public function isWorking()
- {
- if (!function_exists('mb_internal_encoding'))
- {
- return Piwik_Translate('UserCountry_GeoIPCannotFindMbstringExtension',
- array('mb_internal_encoding', 'mbstring'));
- }
-
- return parent::isWorking();
- }
-
- /**
- * Returns an array describing the types of location information this provider will
- * return.
- *
- * The location info this provider supports depends on what GeoIP databases it can
- * find.
- *
- * This provider will always support country & continent information.
- *
- * If a region database is found, then region code & name information will be
- * supported.
- *
- * If a city database is found, then region code, region name, city name,
- * area code, latitude, longitude & postal code are all supported.
- *
- * If an organization database is found, organization information is
- * supported.
- *
- * If an ISP database is found, ISP information is supported.
- *
- * @return array
- */
- public function getSupportedLocationInfo()
- {
- $result = array();
-
- // country & continent info always available
- $result[self::CONTINENT_CODE_KEY] = true;
- $result[self::CONTINENT_NAME_KEY] = true;
- $result[self::COUNTRY_CODE_KEY] = true;
- $result[self::COUNTRY_NAME_KEY] = true;
-
- $locationGeoIp = $this->getGeoIpInstance($key = 'loc');
- if ($locationGeoIp)
- {
- switch ($locationGeoIp->databaseType)
- {
- case GEOIP_CITY_EDITION_REV0: // city database type
- case GEOIP_CITY_EDITION_REV1:
- case GEOIP_CITYCOMBINED_EDITION:
- $result[self::REGION_CODE_KEY] = true;
- $result[self::REGION_NAME_KEY] = true;
- $result[self::CITY_NAME_KEY] = true;
- $result[self::AREA_CODE_KEY] = true;
- $result[self::LATITUDE_KEY] = true;
- $result[self::LONGITUDE_KEY] = true;
- $result[self::POSTAL_CODE_KEY] = true;
- break;
- case GEOIP_REGION_EDITION_REV0: // region database type
- case GEOIP_REGION_EDITION_REV1:
- $result[self::REGION_CODE_KEY] = true;
- $result[self::REGION_NAME_KEY] = true;
- break;
- default: // country or unknown database type
- break;
- }
- }
-
- // check if isp info is available
- if ($this->getGeoIpInstance($key = 'isp'))
- {
- $result[self::ISP_KEY] = true;
- }
-
- // check of org info is available
- if ($this->getGeoIpInstance($key = 'org'))
- {
- $result[self::ORG_KEY] = true;
- }
-
- return $result;
- }
-
- /**
- * Returns information about this location provider. Contains an id, title & description:
- *
- * array(
- * 'id' => 'geoip_php',
- * 'title' => '...',
- * 'description' => '...'
- * );
- *
- * @return array
- */
- public function getInfo()
- {
- $desc = Piwik_Translate('UserCountry_GeoIpLocationProviderDesc_Php1') . '<br/><br/>'
- . Piwik_Translate('UserCountry_GeoIpLocationProviderDesc_Php2',
- array('<strong><em>', '</em></strong>', '<strong><em>', '</em></strong>'));
- $installDocs = '<em><a target="_blank" href="http://piwik.org/faq/how-to/#faq_163">'
- . Piwik_Translate('UserCountry_HowToInstallGeoIPDatabases')
- . '</em></a>';
-
- $availableDatabaseTypes = array();
- if (self::getPathToGeoIpDatabase(array('GeoIPCity.dat', 'GeoLiteCity.dat')) !== false)
- {
- $availableDatabaseTypes[] = Piwik_Translate('UserCountry_City');
- }
- if (self::getPathToGeoIpDatabase(array('GeoIPRegion.dat')) !== false)
- {
- $availableDatabaseTypes[] = Piwik_Translate('UserCountry_Region');
- }
- if (self::getPathToGeoIpDatabase(array('GeoIPCountry.dat')) !== false)
- {
- $availableDatabaseTypes[] = Piwik_Translate('UserCountry_Country');
- }
- if (self::getPathToGeoIpDatabase(array('GeoIPISP.dat')) !== false)
- {
- $availableDatabaseTypes[] = 'ISP';
- }
- if (self::getPathToGeoIpDatabase(array('GeoIPOrg.dat')) !== false)
- {
- $availableDatabaseTypes[] = Piwik_Translate('UserCountry_Organization');
- }
-
- $extraMessage = '<strong><em>'.Piwik_Translate('General_Note').'</em></strong>:&nbsp;'
- . Piwik_Translate('UserCountry_GeoIPImplHasAccessTo').':&nbsp;<strong><em>'
- . implode(', ', $availableDatabaseTypes).'</em></strong>.';
-
- return array('id' => self::ID,
- 'title' => self::TITLE,
- 'description' => $desc,
- 'install_docs' => $installDocs,
- 'extra_message' => $extraMessage,
- 'order' => 2);
- }
-
- /**
- * Returns a GeoIP instance. Creates it if necessary.
- *
- * @param string $key 'loc', 'isp' or 'org'. Determines the type of GeoIP database
- * to load.
- * @return object|false
- */
- private function getGeoIpInstance( $key )
- {
- if (empty($this->geoIpCache[$key]))
- {
- // make sure region names are loaded & saved first
- parent::getRegionNames();
- require_once PIWIK_INCLUDE_PATH . '/libs/MaxMindGeoIP/geoipcity.inc';
-
- $pathToDb = self::getPathToGeoIpDatabase($this->customDbNames[$key]);
- if ($pathToDb !== false)
- {
- $this->geoIpCache[$key] = geoip_open($pathToDb, GEOIP_STANDARD); // TODO support shared memory
- }
- }
-
- return empty($this->geoIpCache[$key]) ? false : $this->geoIpCache[$key];
- }
+ const ID = 'geoip_php';
+ const TITLE = 'GeoIP (Php)';
+
+ /**
+ * The GeoIP database instances used. This array will contain at most three
+ * of them: one for location info, one for ISP info and another for organization
+ * info.
+ *
+ * Each instance is mapped w/ one of the following keys: 'loc', 'isp', 'org'
+ *
+ * @var array of GeoIP instances
+ */
+ private $geoIpCache = array();
+
+ /**
+ * Possible filenames for each type of GeoIP database. When looking for a database
+ * file in the 'misc' subdirectory, files with these names will be looked for.
+ *
+ * This variable is an array mapping either the 'loc', 'isp' or 'org' strings with
+ * an array of filenames.
+ *
+ * By default, this will be set to Piwik_UserCountry_LocationProvider_GeoIp_Php::$dbNames.
+ *
+ * @var array
+ */
+ private $customDbNames;
+
+ /**
+ * Constructor.
+ *
+ * @param array|false $customDbNames The possible filenames for each type of GeoIP database.
+ * eg array(
+ * 'loc' => array('GeoLiteCity.dat'),
+ * 'isp' => array('GeoIP.dat', 'GeoIPISP.dat')
+ * 'org' => array('GeoIPOrg.dat')
+ * )
+ * If a key is missing (or the parameter not supplied), then the
+ * default database names are used.
+ */
+ public function __construct($customDbNames = false)
+ {
+ $this->customDbNames = parent::$dbNames;
+ if ($customDbNames !== false) {
+ foreach ($this->customDbNames as $key => $names) {
+ if (isset($customDbNames[$key])) {
+ $this->customDbNames[$key] = $customDbNames[$key];
+ }
+ }
+ }
+ }
+
+ /**
+ * Closes all open geoip instances.
+ */
+ public function __destruct()
+ {
+ foreach ($this->geoIpCache as $instance) {
+ geoip_close($instance);
+ }
+ }
+
+ /**
+ * Uses a GeoIP database to get a visitor's location based on their IP address.
+ *
+ * This function will return different results based on the data used. If a city
+ * database is used, it may return the country code, region code, city name, area
+ * code, latitude, longitude and postal code of the visitor.
+ *
+ * Alternatively, if used with a country database, only the country code will be
+ * returned.
+ *
+ * @param array $info Must have an 'ip' field.
+ * @return array
+ */
+ public function getLocation($info)
+ {
+ $ip = $this->getIpFromInfo($info);
+
+ $result = array();
+
+ $locationGeoIp = $this->getGeoIpInstance($key = 'loc');
+ if ($locationGeoIp) {
+ switch ($locationGeoIp->databaseType) {
+ case GEOIP_CITY_EDITION_REV0: // city database type
+ case GEOIP_CITY_EDITION_REV1:
+ case GEOIP_CITYCOMBINED_EDITION:
+ $location = geoip_record_by_addr($locationGeoIp, $ip);
+ if (!empty($location)) {
+ $result[self::COUNTRY_CODE_KEY] = $location->country_code;
+ $result[self::REGION_CODE_KEY] = $location->region;
+ $result[self::CITY_NAME_KEY] = utf8_encode($location->city);
+ $result[self::AREA_CODE_KEY] = $location->area_code;
+ $result[self::LATITUDE_KEY] = $location->latitude;
+ $result[self::LONGITUDE_KEY] = $location->longitude;
+ $result[self::POSTAL_CODE_KEY] = $location->postal_code;
+ }
+ break;
+ case GEOIP_REGION_EDITION_REV0: // region database type
+ case GEOIP_REGION_EDITION_REV1:
+ $location = geoip_region_by_addr($locationGeoIp, $ip);
+ if (!empty($location)) {
+ $result[self::COUNTRY_CODE_KEY] = $location[0];
+ $result[self::REGION_CODE_KEY] = $location[1];
+ }
+ break;
+ case GEOIP_COUNTRY_EDITION: // country database type
+ $result[self::COUNTRY_CODE_KEY] = geoip_country_code_by_addr($locationGeoIp, $ip);
+ break;
+ default: // unknown database type, log warning and fallback to country edition
+ Piwik::log(sprintf("Found unrecognized database type: %s", $locationGeoIp->databaseType));
+
+ $result[self::COUNTRY_CODE_KEY] = geoip_country_code_by_addr($locationGeoIp, $ip);
+ break;
+ }
+ }
+
+ // NOTE: ISP & ORG require commercial dbs to test. this code has been tested manually,
+ // but not by integration tests.
+ $ispGeoIp = $this->getGeoIpInstance($key = 'isp');
+ if ($ispGeoIp) {
+ $isp = geoip_org_by_addr($ispGeoIp, $ip);
+ if (!empty($isp)) {
+ $result[self::ISP_KEY] = utf8_encode($isp);
+ }
+ }
+
+ $orgGeoIp = $this->getGeoIpInstance($key = 'org');
+ if ($orgGeoIp) {
+ $org = geoip_org_by_addr($orgGeoIp, $ip);
+ if (!empty($org)) {
+ $result[self::ORG_KEY] = utf8_encode($org);
+ }
+ }
+
+ if (empty($result)) {
+ return false;
+ }
+
+ $this->completeLocationResult($result);
+ return $result;
+ }
+
+ /**
+ * Returns true if this location provider is available. Piwik ships w/ the MaxMind
+ * PHP library, so this provider is available if a location GeoIP database can be found.
+ *
+ * @return bool
+ */
+ public function isAvailable()
+ {
+ $path = self::getPathToGeoIpDatabase($this->customDbNames['loc']);
+ return $path !== false;
+ }
+
+ /**
+ * Returns true if this provider has been setup correctly, the error message if
+ * otherwise.
+ *
+ * @return bool|string
+ */
+ public function isWorking()
+ {
+ if (!function_exists('mb_internal_encoding')) {
+ return Piwik_Translate('UserCountry_GeoIPCannotFindMbstringExtension',
+ array('mb_internal_encoding', 'mbstring'));
+ }
+
+ return parent::isWorking();
+ }
+
+ /**
+ * Returns an array describing the types of location information this provider will
+ * return.
+ *
+ * The location info this provider supports depends on what GeoIP databases it can
+ * find.
+ *
+ * This provider will always support country & continent information.
+ *
+ * If a region database is found, then region code & name information will be
+ * supported.
+ *
+ * If a city database is found, then region code, region name, city name,
+ * area code, latitude, longitude & postal code are all supported.
+ *
+ * If an organization database is found, organization information is
+ * supported.
+ *
+ * If an ISP database is found, ISP information is supported.
+ *
+ * @return array
+ */
+ public function getSupportedLocationInfo()
+ {
+ $result = array();
+
+ // country & continent info always available
+ $result[self::CONTINENT_CODE_KEY] = true;
+ $result[self::CONTINENT_NAME_KEY] = true;
+ $result[self::COUNTRY_CODE_KEY] = true;
+ $result[self::COUNTRY_NAME_KEY] = true;
+
+ $locationGeoIp = $this->getGeoIpInstance($key = 'loc');
+ if ($locationGeoIp) {
+ switch ($locationGeoIp->databaseType) {
+ case GEOIP_CITY_EDITION_REV0: // city database type
+ case GEOIP_CITY_EDITION_REV1:
+ case GEOIP_CITYCOMBINED_EDITION:
+ $result[self::REGION_CODE_KEY] = true;
+ $result[self::REGION_NAME_KEY] = true;
+ $result[self::CITY_NAME_KEY] = true;
+ $result[self::AREA_CODE_KEY] = true;
+ $result[self::LATITUDE_KEY] = true;
+ $result[self::LONGITUDE_KEY] = true;
+ $result[self::POSTAL_CODE_KEY] = true;
+ break;
+ case GEOIP_REGION_EDITION_REV0: // region database type
+ case GEOIP_REGION_EDITION_REV1:
+ $result[self::REGION_CODE_KEY] = true;
+ $result[self::REGION_NAME_KEY] = true;
+ break;
+ default: // country or unknown database type
+ break;
+ }
+ }
+
+ // check if isp info is available
+ if ($this->getGeoIpInstance($key = 'isp')) {
+ $result[self::ISP_KEY] = true;
+ }
+
+ // check of org info is available
+ if ($this->getGeoIpInstance($key = 'org')) {
+ $result[self::ORG_KEY] = true;
+ }
+
+ return $result;
+ }
+
+ /**
+ * Returns information about this location provider. Contains an id, title & description:
+ *
+ * array(
+ * 'id' => 'geoip_php',
+ * 'title' => '...',
+ * 'description' => '...'
+ * );
+ *
+ * @return array
+ */
+ public function getInfo()
+ {
+ $desc = Piwik_Translate('UserCountry_GeoIpLocationProviderDesc_Php1') . '<br/><br/>'
+ . Piwik_Translate('UserCountry_GeoIpLocationProviderDesc_Php2',
+ array('<strong><em>', '</em></strong>', '<strong><em>', '</em></strong>'));
+ $installDocs = '<em><a target="_blank" href="http://piwik.org/faq/how-to/#faq_163">'
+ . Piwik_Translate('UserCountry_HowToInstallGeoIPDatabases')
+ . '</em></a>';
+
+ $availableDatabaseTypes = array();
+ if (self::getPathToGeoIpDatabase(array('GeoIPCity.dat', 'GeoLiteCity.dat')) !== false) {
+ $availableDatabaseTypes[] = Piwik_Translate('UserCountry_City');
+ }
+ if (self::getPathToGeoIpDatabase(array('GeoIPRegion.dat')) !== false) {
+ $availableDatabaseTypes[] = Piwik_Translate('UserCountry_Region');
+ }
+ if (self::getPathToGeoIpDatabase(array('GeoIPCountry.dat')) !== false) {
+ $availableDatabaseTypes[] = Piwik_Translate('UserCountry_Country');
+ }
+ if (self::getPathToGeoIpDatabase(array('GeoIPISP.dat')) !== false) {
+ $availableDatabaseTypes[] = 'ISP';
+ }
+ if (self::getPathToGeoIpDatabase(array('GeoIPOrg.dat')) !== false) {
+ $availableDatabaseTypes[] = Piwik_Translate('UserCountry_Organization');
+ }
+
+ $extraMessage = '<strong><em>' . Piwik_Translate('General_Note') . '</em></strong>:&nbsp;'
+ . Piwik_Translate('UserCountry_GeoIPImplHasAccessTo') . ':&nbsp;<strong><em>'
+ . implode(', ', $availableDatabaseTypes) . '</em></strong>.';
+
+ return array('id' => self::ID,
+ 'title' => self::TITLE,
+ 'description' => $desc,
+ 'install_docs' => $installDocs,
+ 'extra_message' => $extraMessage,
+ 'order' => 2);
+ }
+
+ /**
+ * Returns a GeoIP instance. Creates it if necessary.
+ *
+ * @param string $key 'loc', 'isp' or 'org'. Determines the type of GeoIP database
+ * to load.
+ * @return object|false
+ */
+ private function getGeoIpInstance($key)
+ {
+ if (empty($this->geoIpCache[$key])) {
+ // make sure region names are loaded & saved first
+ parent::getRegionNames();
+ require_once PIWIK_INCLUDE_PATH . '/libs/MaxMindGeoIP/geoipcity.inc';
+
+ $pathToDb = self::getPathToGeoIpDatabase($this->customDbNames[$key]);
+ if ($pathToDb !== false) {
+ $this->geoIpCache[$key] = geoip_open($pathToDb, GEOIP_STANDARD); // TODO support shared memory
+ }
+ }
+
+ return empty($this->geoIpCache[$key]) ? false : $this->geoIpCache[$key];
+ }
}
diff --git a/plugins/UserCountry/LocationProvider/GeoIp/ServerBased.php b/plugins/UserCountry/LocationProvider/GeoIp/ServerBased.php
index 7be7324744..cdafeefa6a 100755
--- a/plugins/UserCountry/LocationProvider/GeoIp/ServerBased.php
+++ b/plugins/UserCountry/LocationProvider/GeoIp/ServerBased.php
@@ -1,306 +1,277 @@
<?php
/**
* Piwik - Open source web analytics
- *
+ *
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
- *
+ *
* @category Piwik_Plugins
* @package Piwik_UserCountry
*/
/**
* A LocationProvider that uses an GeoIP module installed in an HTTP Server.
- *
+ *
* To make this provider available, make sure the GEOIP_ADDR server
* variable is set.
- *
+ *
* @package Piwik_UserCountry
*/
class Piwik_UserCountry_LocationProvider_GeoIp_ServerBased extends Piwik_UserCountry_LocationProvider_GeoIp
{
- const ID = 'geoip_serverbased';
- const TITLE = 'GeoIP (%s)';
- const TEST_SERVER_VAR = 'GEOIP_ADDR';
- const TEST_SERVER_VAR_ALT = 'GEOIP_COUNTRY_CODE';
-
- private static $geoIpServerVars = array(
- parent::COUNTRY_CODE_KEY => 'GEOIP_COUNTRY_CODE',
- parent::COUNTRY_NAME_KEY => 'GEOIP_COUNTRY_NAME',
- parent::REGION_CODE_KEY => 'GEOIP_REGION',
- parent::REGION_NAME_KEY => 'GEOIP_REGION_NAME',
- parent::AREA_CODE_KEY => 'GEOIP_AREA_CODE',
- parent::LATITUDE_KEY => 'GEOIP_LATITUDE',
- parent::LONGITUDE_KEY => 'GEOIP_LONGITUDE',
- parent::POSTAL_CODE_KEY => 'GEOIP_POSTAL_CODE',
- );
-
- private static $geoIpUtfServerVars = array(
- parent::CITY_NAME_KEY => 'GEOIP_CITY',
- parent::ISP_KEY => 'GEOIP_ISP',
- parent::ORG_KEY => 'GEOIP_ORGANIZATION',
- );
-
- /**
- * Uses a GeoIP database to get a visitor's location based on their IP address.
- *
- * This function will return different results based on the data used and based
- * on how the GeoIP module is configured.
- *
- * If a region database is used, it may return the country code, region code,
- * city name, area code, latitude, longitude and postal code of the visitor.
- *
- * Alternatively, only the country code may be returned for another database.
- *
- * If your HTTP server is not configured to include all GeoIP information, some
- * information will not be available to Piwik.
- *
- * @param array $info Must have an 'ip' field.
- * @return array
- */
- public function getLocation( $info )
- {
- $ip = $this->getIpFromInfo($info);
-
- // geoip modules that are built into servers can't use a forced IP. in this case we try
- // to fallback to another version.
- $myIP = Piwik_IP::getIpFromHeader();
- if (!self::isSameOrAnonymizedIp($ip, $myIP)
- && (!isset($info['disable_fallbacks'])
- || !$info['disable_fallbacks']))
- {
- printDebug("The request is for IP address: ".$info['ip'] . " but your IP is: $myIP. GeoIP Server Module (apache/nginx) does not support this use case... ");
- $fallbacks = array(
- Piwik_UserCountry_LocationProvider_GeoIp_Pecl::ID,
- Piwik_UserCountry_LocationProvider_GeoIp_Php::ID
- );
- foreach ($fallbacks as $fallbackProviderId)
- {
- $otherProvider = Piwik_UserCountry_LocationProvider::getProviderById($fallbackProviderId);
- if ($otherProvider)
- {
- printDebug("Used $fallbackProviderId to detect this visitor IP");
- return $otherProvider->getLocation($info);
- }
- }
- printDebug("FAILED to lookup the geo location of this IP address, as no fallback location providers is configured. We recommend to configure Geolocation PECL module to fix this error.");
-
- return false;
- }
-
- $result = array();
- foreach (self::$geoIpServerVars as $resultKey => $geoipVarName)
- {
- if (!empty($_SERVER[$geoipVarName]))
- {
- $result[$resultKey] = $_SERVER[$geoipVarName];
- }
- }
- foreach (self::$geoIpUtfServerVars as $resultKey => $geoipVarName)
- {
- if (!empty($_SERVER[$geoipVarName]))
- {
- $result[$resultKey] = utf8_encode($_SERVER[$geoipVarName]);
- }
- }
- $this->completeLocationResult($result);
- return $result;
- }
-
- /**
- * Returns an array describing the types of location information this provider will
- * return.
- *
- * There's no way to tell exactly what database the HTTP server is using, so we just
- * assume country and continent information is available. This can make diagnostics
- * a bit more difficult, unfortunately.
- *
- * @return array
- */
- public function getSupportedLocationInfo()
- {
- $result = array();
-
- // assume country info is always available. it's an error if it's not.
- $result[self::COUNTRY_CODE_KEY] = true;
- $result[self::COUNTRY_NAME_KEY] = true;
- $result[self::CONTINENT_CODE_KEY] = true;
- $result[self::CONTINENT_NAME_KEY] = true;
-
- return $result;
- }
-
- /**
- * Checks if an HTTP server module has been installed. It checks by looking for
- * the GEOIP_ADDR server variable.
- *
- * There's a special check for the Apache module, but we can't check specifically
- * for anything else.
- *
- * @return bool|string
- */
- public function isAvailable()
- {
- // check if apache module is installed
- if (function_exists('apache_get_modules'))
- {
- foreach (apache_get_modules() as $name)
- {
- if (strpos($name, 'geoip') !== false)
- {
- return true;
- }
- }
- }
-
- $available = !empty($_SERVER[self::TEST_SERVER_VAR])
- || !empty($_SERVER[self::TEST_SERVER_VAR_ALT]);
-
- if ($available)
- {
- return true;
- }
- else // if not available return message w/ extra info
- {
- if (!function_exists('apache_get_modules'))
- {
- return Piwik_Translate('General_Note').':&nbsp;'.Piwik_Translate('UserCountry_AssumingNonApache');
- }
-
- $message = "<strong><em>".Piwik_Translate('General_Note').':&nbsp;'
- . Piwik_Translate('UserCountry_FoundApacheModules')
- . "</em></strong>:<br/><br/>\n<ul style=\"list-style:disc;margin-left:24px\">\n";
- foreach (apache_get_modules() as $name)
- {
- $message .= "<li>$name</li>\n";
- }
- $message .= "</ul>";
- return $message;
- }
- }
-
- /**
- * Returns true if the GEOIP_ADDR server variable is defined.
- *
- * @return bool
- */
- public function isWorking()
- {
- if (empty($_SERVER[self::TEST_SERVER_VAR])
- && empty($_SERVER[self::TEST_SERVER_VAR_ALT]))
- {
- return Piwik_Translate("UserCountry_CannotFindGeoIPServerVar", self::TEST_SERVER_VAR.' $_SERVER');
- }
-
- return true; // can't check for another IP
- }
-
- /**
- * Returns information about this location provider. Contains an id, title & description:
- *
- * array(
- * 'id' => 'geoip_serverbased',
- * 'title' => '...',
- * 'description' => '...'
- * );
- *
- * @return array
- */
- public function getInfo()
- {
- if (function_exists('apache_note'))
- {
- $serverDesc = 'Apache';
- }
- else
- {
- $serverDesc = Piwik_Translate('UserCountry_HttpServerModule');
- }
-
- $title = sprintf(self::TITLE, $serverDesc);
- $desc = Piwik_Translate('UserCountry_GeoIpLocationProviderDesc_ServerBased1', array('<strong>', '</strong>'))
- . '<br/><br/>'
- . '<em>'.Piwik_Translate('UserCountry_GeoIpLocationProviderDesc_ServerBasedAnonWarn').'</em>'
- . '<br/><br/>'
- . Piwik_Translate('UserCountry_GeoIpLocationProviderDesc_ServerBased2',
- array('<strong><em>', '</em></strong>', '<strong><em>', '</em></strong>'));
- $installDocs =
- '<em><a target="_blank" href="http://piwik.org/faq/how-to/#faq_165">'
- . Piwik_Translate('UserCountry_HowToInstallApacheModule')
- . '</a></em><br/><em>'
- . '<a target="_blank" href="http://piwik.org/faq/how-to/#faq_166">'
- . Piwik_Translate('UserCountry_HowToInstallNginxModule')
- . '</a></em>';
-
- $geoipServerVars = array();
- foreach ($_SERVER as $key => $value)
- {
- if (strpos($key, 'GEOIP') === 0)
- {
- $geoipServerVars[] = $key;
- }
- }
-
- if (empty($geoipServerVars))
- {
- $extraMessage = '<strong><em>'.Piwik_Translate('UserCountry_GeoIPNoServerVars', '$_SERVER').'</em></strong>';
- }
- else
- {
- $extraMessage = '<strong><em>'.Piwik_Translate('UserCountry_GeoIPServerVarsFound', '$_SERVER')
- .":</em></strong><br/><br/>\n<ul style=\"list-style:disc;margin-left:24px\">\n";
- foreach ($geoipServerVars as $key)
- {
- $extraMessage .= '<li>'.$key."</li>\n";
- }
- $extraMessage .= '</ul>';
- }
-
- return array('id' => self::ID,
- 'title' => $title,
- 'description' => $desc,
- 'order' => 4,
- 'install_docs' => $installDocs,
- 'extra_message' => $extraMessage);
- }
-
- /**
- * Checks if two IP addresses are the same or if the first is the anonymized
- * version of the other.
- *
- * @param string $ip
- * @param string $currentIp This IP should not be anonymized.
- * @return bool
- */
- public static function isSameOrAnonymizedIp( $ip, $currentIp )
- {
- $ip = array_reverse(explode('.', $ip));
- $currentIp = array_reverse(explode('.', $currentIp));
-
- if (count($ip) != count($currentIp))
- {
- return false;
- }
-
- foreach ($ip as $i => $byte)
- {
- if ($byte == 0)
- {
- $currentIp[$i] = 0;
- }
- else
- {
- break;
- }
- }
-
- foreach ($ip as $i => $byte)
- {
- if ($byte != $currentIp[$i])
- {
- return false;
- }
- }
- return true;
- }
+ const ID = 'geoip_serverbased';
+ const TITLE = 'GeoIP (%s)';
+ const TEST_SERVER_VAR = 'GEOIP_ADDR';
+ const TEST_SERVER_VAR_ALT = 'GEOIP_COUNTRY_CODE';
+
+ private static $geoIpServerVars = array(
+ parent::COUNTRY_CODE_KEY => 'GEOIP_COUNTRY_CODE',
+ parent::COUNTRY_NAME_KEY => 'GEOIP_COUNTRY_NAME',
+ parent::REGION_CODE_KEY => 'GEOIP_REGION',
+ parent::REGION_NAME_KEY => 'GEOIP_REGION_NAME',
+ parent::AREA_CODE_KEY => 'GEOIP_AREA_CODE',
+ parent::LATITUDE_KEY => 'GEOIP_LATITUDE',
+ parent::LONGITUDE_KEY => 'GEOIP_LONGITUDE',
+ parent::POSTAL_CODE_KEY => 'GEOIP_POSTAL_CODE',
+ );
+
+ private static $geoIpUtfServerVars = array(
+ parent::CITY_NAME_KEY => 'GEOIP_CITY',
+ parent::ISP_KEY => 'GEOIP_ISP',
+ parent::ORG_KEY => 'GEOIP_ORGANIZATION',
+ );
+
+ /**
+ * Uses a GeoIP database to get a visitor's location based on their IP address.
+ *
+ * This function will return different results based on the data used and based
+ * on how the GeoIP module is configured.
+ *
+ * If a region database is used, it may return the country code, region code,
+ * city name, area code, latitude, longitude and postal code of the visitor.
+ *
+ * Alternatively, only the country code may be returned for another database.
+ *
+ * If your HTTP server is not configured to include all GeoIP information, some
+ * information will not be available to Piwik.
+ *
+ * @param array $info Must have an 'ip' field.
+ * @return array
+ */
+ public function getLocation($info)
+ {
+ $ip = $this->getIpFromInfo($info);
+
+ // geoip modules that are built into servers can't use a forced IP. in this case we try
+ // to fallback to another version.
+ $myIP = Piwik_IP::getIpFromHeader();
+ if (!self::isSameOrAnonymizedIp($ip, $myIP)
+ && (!isset($info['disable_fallbacks'])
+ || !$info['disable_fallbacks'])
+ ) {
+ printDebug("The request is for IP address: " . $info['ip'] . " but your IP is: $myIP. GeoIP Server Module (apache/nginx) does not support this use case... ");
+ $fallbacks = array(
+ Piwik_UserCountry_LocationProvider_GeoIp_Pecl::ID,
+ Piwik_UserCountry_LocationProvider_GeoIp_Php::ID
+ );
+ foreach ($fallbacks as $fallbackProviderId) {
+ $otherProvider = Piwik_UserCountry_LocationProvider::getProviderById($fallbackProviderId);
+ if ($otherProvider) {
+ printDebug("Used $fallbackProviderId to detect this visitor IP");
+ return $otherProvider->getLocation($info);
+ }
+ }
+ printDebug("FAILED to lookup the geo location of this IP address, as no fallback location providers is configured. We recommend to configure Geolocation PECL module to fix this error.");
+
+ return false;
+ }
+
+ $result = array();
+ foreach (self::$geoIpServerVars as $resultKey => $geoipVarName) {
+ if (!empty($_SERVER[$geoipVarName])) {
+ $result[$resultKey] = $_SERVER[$geoipVarName];
+ }
+ }
+ foreach (self::$geoIpUtfServerVars as $resultKey => $geoipVarName) {
+ if (!empty($_SERVER[$geoipVarName])) {
+ $result[$resultKey] = utf8_encode($_SERVER[$geoipVarName]);
+ }
+ }
+ $this->completeLocationResult($result);
+ return $result;
+ }
+
+ /**
+ * Returns an array describing the types of location information this provider will
+ * return.
+ *
+ * There's no way to tell exactly what database the HTTP server is using, so we just
+ * assume country and continent information is available. This can make diagnostics
+ * a bit more difficult, unfortunately.
+ *
+ * @return array
+ */
+ public function getSupportedLocationInfo()
+ {
+ $result = array();
+
+ // assume country info is always available. it's an error if it's not.
+ $result[self::COUNTRY_CODE_KEY] = true;
+ $result[self::COUNTRY_NAME_KEY] = true;
+ $result[self::CONTINENT_CODE_KEY] = true;
+ $result[self::CONTINENT_NAME_KEY] = true;
+
+ return $result;
+ }
+
+ /**
+ * Checks if an HTTP server module has been installed. It checks by looking for
+ * the GEOIP_ADDR server variable.
+ *
+ * There's a special check for the Apache module, but we can't check specifically
+ * for anything else.
+ *
+ * @return bool|string
+ */
+ public function isAvailable()
+ {
+ // check if apache module is installed
+ if (function_exists('apache_get_modules')) {
+ foreach (apache_get_modules() as $name) {
+ if (strpos($name, 'geoip') !== false) {
+ return true;
+ }
+ }
+ }
+
+ $available = !empty($_SERVER[self::TEST_SERVER_VAR])
+ || !empty($_SERVER[self::TEST_SERVER_VAR_ALT]);
+
+ if ($available) {
+ return true;
+ } else // if not available return message w/ extra info
+ {
+ if (!function_exists('apache_get_modules')) {
+ return Piwik_Translate('General_Note') . ':&nbsp;' . Piwik_Translate('UserCountry_AssumingNonApache');
+ }
+
+ $message = "<strong><em>" . Piwik_Translate('General_Note') . ':&nbsp;'
+ . Piwik_Translate('UserCountry_FoundApacheModules')
+ . "</em></strong>:<br/><br/>\n<ul style=\"list-style:disc;margin-left:24px\">\n";
+ foreach (apache_get_modules() as $name) {
+ $message .= "<li>$name</li>\n";
+ }
+ $message .= "</ul>";
+ return $message;
+ }
+ }
+
+ /**
+ * Returns true if the GEOIP_ADDR server variable is defined.
+ *
+ * @return bool
+ */
+ public function isWorking()
+ {
+ if (empty($_SERVER[self::TEST_SERVER_VAR])
+ && empty($_SERVER[self::TEST_SERVER_VAR_ALT])
+ ) {
+ return Piwik_Translate("UserCountry_CannotFindGeoIPServerVar", self::TEST_SERVER_VAR . ' $_SERVER');
+ }
+
+ return true; // can't check for another IP
+ }
+
+ /**
+ * Returns information about this location provider. Contains an id, title & description:
+ *
+ * array(
+ * 'id' => 'geoip_serverbased',
+ * 'title' => '...',
+ * 'description' => '...'
+ * );
+ *
+ * @return array
+ */
+ public function getInfo()
+ {
+ if (function_exists('apache_note')) {
+ $serverDesc = 'Apache';
+ } else {
+ $serverDesc = Piwik_Translate('UserCountry_HttpServerModule');
+ }
+
+ $title = sprintf(self::TITLE, $serverDesc);
+ $desc = Piwik_Translate('UserCountry_GeoIpLocationProviderDesc_ServerBased1', array('<strong>', '</strong>'))
+ . '<br/><br/>'
+ . '<em>' . Piwik_Translate('UserCountry_GeoIpLocationProviderDesc_ServerBasedAnonWarn') . '</em>'
+ . '<br/><br/>'
+ . Piwik_Translate('UserCountry_GeoIpLocationProviderDesc_ServerBased2',
+ array('<strong><em>', '</em></strong>', '<strong><em>', '</em></strong>'));
+ $installDocs =
+ '<em><a target="_blank" href="http://piwik.org/faq/how-to/#faq_165">'
+ . Piwik_Translate('UserCountry_HowToInstallApacheModule')
+ . '</a></em><br/><em>'
+ . '<a target="_blank" href="http://piwik.org/faq/how-to/#faq_166">'
+ . Piwik_Translate('UserCountry_HowToInstallNginxModule')
+ . '</a></em>';
+
+ $geoipServerVars = array();
+ foreach ($_SERVER as $key => $value) {
+ if (strpos($key, 'GEOIP') === 0) {
+ $geoipServerVars[] = $key;
+ }
+ }
+
+ if (empty($geoipServerVars)) {
+ $extraMessage = '<strong><em>' . Piwik_Translate('UserCountry_GeoIPNoServerVars', '$_SERVER') . '</em></strong>';
+ } else {
+ $extraMessage = '<strong><em>' . Piwik_Translate('UserCountry_GeoIPServerVarsFound', '$_SERVER')
+ . ":</em></strong><br/><br/>\n<ul style=\"list-style:disc;margin-left:24px\">\n";
+ foreach ($geoipServerVars as $key) {
+ $extraMessage .= '<li>' . $key . "</li>\n";
+ }
+ $extraMessage .= '</ul>';
+ }
+
+ return array('id' => self::ID,
+ 'title' => $title,
+ 'description' => $desc,
+ 'order' => 4,
+ 'install_docs' => $installDocs,
+ 'extra_message' => $extraMessage);
+ }
+
+ /**
+ * Checks if two IP addresses are the same or if the first is the anonymized
+ * version of the other.
+ *
+ * @param string $ip
+ * @param string $currentIp This IP should not be anonymized.
+ * @return bool
+ */
+ public static function isSameOrAnonymizedIp($ip, $currentIp)
+ {
+ $ip = array_reverse(explode('.', $ip));
+ $currentIp = array_reverse(explode('.', $currentIp));
+
+ if (count($ip) != count($currentIp)) {
+ return false;
+ }
+
+ foreach ($ip as $i => $byte) {
+ if ($byte == 0) {
+ $currentIp[$i] = 0;
+ } else {
+ break;
+ }
+ }
+
+ foreach ($ip as $i => $byte) {
+ if ($byte != $currentIp[$i]) {
+ return false;
+ }
+ }
+ return true;
+ }
}
diff --git a/plugins/UserCountry/UserCountry.php b/plugins/UserCountry/UserCountry.php
index 31bda64852..20d2935248 100644
--- a/plugins/UserCountry/UserCountry.php
+++ b/plugins/UserCountry/UserCountry.php
@@ -20,525 +20,505 @@ require_once PIWIK_INCLUDE_PATH . '/plugins/UserCountry/GeoIPAutoUpdater.php';
*/
class Piwik_UserCountry extends Piwik_Plugin
{
- const VISITS_BY_COUNTRY_RECORD_NAME = 'UserCountry_country';
- const VISITS_BY_REGION_RECORD_NAME = 'UserCountry_region';
- const VISITS_BY_CITY_RECORD_NAME = 'UserCountry_city';
-
- const DISTINCT_COUNTRIES_METRIC = 'UserCountry_distinctCountries';
-
- // separate region, city & country info in stored report labels
- const LOCATION_SEPARATOR = '|';
-
- public function getInformation()
- {
- $info = array(
- 'description' => Piwik_Translate('UserCountry_PluginDescription'),
- 'author' => 'Piwik',
- 'author_homepage' => 'http://piwik.org/',
- 'version' => Piwik_Version::VERSION,
- 'TrackerPlugin' => true,
- );
- return $info;
- }
-
- function getListHooksRegistered()
- {
- $hooks = array(
- 'ArchiveProcessing_Day.compute' => 'archiveDay',
- 'ArchiveProcessing_Period.compute' => 'archivePeriod',
- 'WidgetsList.add' => 'addWidgets',
- 'Menu.add' => 'addMenu',
- 'AdminMenu.add' => 'addAdminMenu',
- 'Goals.getReportsWithGoalMetrics' => 'getReportsWithGoalMetrics',
- 'API.getReportMetadata' => 'getReportMetadata',
- 'API.getSegmentsMetadata' => 'getSegmentsMetadata',
- 'AssetManager.getCssFiles' => 'getCssFiles',
- 'AssetManager.getJsFiles' => 'getJsFiles',
- 'Tracker.getVisitorLocation' => 'getVisitorLocation',
- 'TaskScheduler.getScheduledTasks' => 'getScheduledTasks',
- );
- return $hooks;
- }
-
- /**
- * @param Piwik_Event_Notification $notification notification object
- */
- function getScheduledTasks($notification)
- {
- $tasks = &$notification->getNotificationObject();
-
- // add the auto updater task
- $tasks[] = Piwik_UserCountry_GeoIPAutoUpdater::makeScheduledTask();
- }
-
- /**
- * @param Piwik_Event_Notification $notification notification object
- */
- function getCssFiles( $notification )
- {
- $cssFiles = &$notification->getNotificationObject();
-
- $cssFiles[] = "plugins/UserCountry/templates/styles.css";
- }
-
- /**
- * @param Piwik_Event_Notification $notification notification object
- */
- public function getJsFiles( $notification )
+ const VISITS_BY_COUNTRY_RECORD_NAME = 'UserCountry_country';
+ const VISITS_BY_REGION_RECORD_NAME = 'UserCountry_region';
+ const VISITS_BY_CITY_RECORD_NAME = 'UserCountry_city';
+
+ const DISTINCT_COUNTRIES_METRIC = 'UserCountry_distinctCountries';
+
+ // separate region, city & country info in stored report labels
+ const LOCATION_SEPARATOR = '|';
+
+ public function getInformation()
+ {
+ $info = array(
+ 'description' => Piwik_Translate('UserCountry_PluginDescription'),
+ 'author' => 'Piwik',
+ 'author_homepage' => 'http://piwik.org/',
+ 'version' => Piwik_Version::VERSION,
+ 'TrackerPlugin' => true,
+ );
+ return $info;
+ }
+
+ function getListHooksRegistered()
+ {
+ $hooks = array(
+ 'ArchiveProcessing_Day.compute' => 'archiveDay',
+ 'ArchiveProcessing_Period.compute' => 'archivePeriod',
+ 'WidgetsList.add' => 'addWidgets',
+ 'Menu.add' => 'addMenu',
+ 'AdminMenu.add' => 'addAdminMenu',
+ 'Goals.getReportsWithGoalMetrics' => 'getReportsWithGoalMetrics',
+ 'API.getReportMetadata' => 'getReportMetadata',
+ 'API.getSegmentsMetadata' => 'getSegmentsMetadata',
+ 'AssetManager.getCssFiles' => 'getCssFiles',
+ 'AssetManager.getJsFiles' => 'getJsFiles',
+ 'Tracker.getVisitorLocation' => 'getVisitorLocation',
+ 'TaskScheduler.getScheduledTasks' => 'getScheduledTasks',
+ );
+ return $hooks;
+ }
+
+ /**
+ * @param Piwik_Event_Notification $notification notification object
+ */
+ function getScheduledTasks($notification)
+ {
+ $tasks = & $notification->getNotificationObject();
+
+ // add the auto updater task
+ $tasks[] = Piwik_UserCountry_GeoIPAutoUpdater::makeScheduledTask();
+ }
+
+ /**
+ * @param Piwik_Event_Notification $notification notification object
+ */
+ function getCssFiles($notification)
{
- $jsFiles = &$notification->getNotificationObject();
+ $cssFiles = & $notification->getNotificationObject();
+
+ $cssFiles[] = "plugins/UserCountry/templates/styles.css";
+ }
+
+ /**
+ * @param Piwik_Event_Notification $notification notification object
+ */
+ public function getJsFiles($notification)
+ {
+ $jsFiles = & $notification->getNotificationObject();
$jsFiles[] = "plugins/UserCountry/templates/admin.js";
}
-
- /**
- * @param Piwik_Event_Notification $notification notification object
- */
- public function getVisitorLocation( $notification )
- {
- require_once PIWIK_INCLUDE_PATH . "/plugins/UserCountry/LocationProvider.php";
- $location = &$notification->getNotificationObject();
- $visitorInfo = $notification->getNotificationInfo();
-
- $id = Piwik_Common::getCurrentLocationProviderId();
- $provider = Piwik_UserCountry_LocationProvider::getProviderById($id);
- if ($provider === false)
- {
- $id = Piwik_UserCountry_LocationProvider_Default::ID;
- $provider = Piwik_UserCountry_LocationProvider::getProviderById($id);
- printDebug("GEO: no current location provider sent, falling back to default '$id' one.");
- }
-
- $location = $provider->getLocation($visitorInfo);
-
- // if we can't find a location, use default provider
- if ($location === false)
- {
- $defaultId = Piwik_UserCountry_LocationProvider_Default::ID;
- $provider = Piwik_UserCountry_LocationProvider::getProviderById($defaultId);
- $location = $provider->getLocation($visitorInfo);
- printDebug("GEO: couldn't find a location with Geo Module '$id', using Default '$defaultId' provider as fallback...");
- $id = $defaultId;
- }
- printDebug("GEO: Found IP location (provider '". $id . "'): ". var_export($location, true));
- }
-
- function addWidgets()
- {
- $widgetContinentLabel = Piwik_Translate('UserCountry_WidgetLocation')
- . ' ('.Piwik_Translate('UserCountry_Continent').')';
- $widgetCountryLabel = Piwik_Translate('UserCountry_WidgetLocation')
- . ' ('.Piwik_Translate('UserCountry_Country').')';
- $widgetRegionLabel = Piwik_Translate('UserCountry_WidgetLocation')
- . ' ('.Piwik_Translate('UserCountry_Region').')';
- $widgetCityLabel = Piwik_Translate('UserCountry_WidgetLocation')
- . ' ('.Piwik_Translate('UserCountry_City').')';
-
- Piwik_AddWidget( 'General_Visitors', $widgetContinentLabel, 'UserCountry', 'getContinent');
- Piwik_AddWidget( 'General_Visitors', $widgetCountryLabel, 'UserCountry', 'getCountry');
- Piwik_AddWidget( 'General_Visitors', $widgetRegionLabel, 'UserCountry', 'getRegion');
- Piwik_AddWidget( 'General_Visitors', $widgetCityLabel, 'UserCountry', 'getCity');
- }
-
- function addMenu()
- {
- Piwik_AddMenu('General_Visitors', 'UserCountry_SubmenuLocations', array('module' => 'UserCountry', 'action' => 'index'));
- }
-
- /**
- * Event handler. Adds menu items to the Admin menu.
- */
- function addAdminMenu()
- {
- Piwik_AddAdminSubMenu('General_Settings', 'UserCountry_Geolocation',
- array('module' => 'UserCountry', 'action' => 'adminIndex'),
- Piwik::isUserIsSuperUser(),
- $order = 8);
- }
-
- /**
- * @param Piwik_Event_Notification $notification notification object
- */
- public function getSegmentsMetadata($notification)
- {
- $segments =& $notification->getNotificationObject();
- $segments[] = array(
- 'type' => 'dimension',
- 'category' => 'Visit Location',
- 'name' => Piwik_Translate('UserCountry_Country'),
- 'segment' => 'country',
- 'sqlSegment' => 'log_visit.location_country',
- 'acceptedValues' => 'de, us, fr, in, es, etc.',
- );
- $segments[] = array(
- 'type' => 'dimension',
- 'category' => 'Visit Location',
- 'name' => Piwik_Translate('UserCountry_Continent'),
- 'segment' => 'continent',
- 'sqlSegment' => 'log_visit.location_country',
- 'acceptedValues' => 'eur, asi, amc, amn, ams, afr, ant, oce',
- 'sqlFilter' => array('Piwik_UserCountry', 'getCountriesForContinent'),
- );
- $segments[] = array(
- 'type' => 'dimension',
- 'category' => 'Visit Location',
- 'name' => Piwik_Translate('UserCountry_Region'),
- 'segment' => 'region',
- 'sqlSegment' => 'log_visit.location_region',
- 'acceptedValues' => '01 02, OR, P8, etc.<br/>eg. region=A1;country=fr',
- );
- $segments[] = array(
- 'type' => 'dimension',
- 'category' => 'Visit Location',
- 'name' => Piwik_Translate('UserCountry_City'),
- 'segment' => 'city',
- 'sqlSegment' => 'log_visit.location_city',
- 'acceptedValues' => 'Sydney, Sao Paolo, Rome, etc.',
- );
- $segments[] = array(
- 'type' => 'dimension',
- 'category' => 'Visit Location',
- 'name' => Piwik_Translate('UserCountry_Latitude'),
- 'segment' => 'lat',
- 'sqlSegment' => 'log_visit.location_latitude',
- 'acceptedValues' => '-33.578, 40.830, etc.<br/>You can select visitors within a lat/long range using &segment=lat&gt;X;lat&lt;Y;long&gt;M;long&lt;N.',
- );
- $segments[] = array(
- 'type' => 'dimension',
- 'category' => 'Visit Location',
- 'name' => Piwik_Translate('UserCountry_Longitude'),
- 'segment' => 'long',
- 'sqlSegment' => 'log_visit.location_longitude',
- 'acceptedValues' => '-70.664, 14.326, etc.',
- );
- }
-
- /**
- * @param Piwik_Event_Notification $notification notification object
- */
- public function getReportMetadata($notification)
- {
- $metrics = array(
- 'nb_visits' => Piwik_Translate('General_ColumnNbVisits'),
- 'nb_uniq_visitors' => Piwik_Translate('General_ColumnNbUniqVisitors'),
- 'nb_actions' => Piwik_Translate('General_ColumnNbActions'),
- );
-
- $reports = &$notification->getNotificationObject();
-
- $reports[] = array(
- 'category' => Piwik_Translate('General_Visitors'),
- 'name' => Piwik_Translate('UserCountry_Country'),
- 'module' => 'UserCountry',
- 'action' => 'getCountry',
- 'dimension' => Piwik_Translate('UserCountry_Country'),
- 'metrics' => $metrics,
- 'order' => 5,
- );
-
- $reports[] = array(
- 'category' => Piwik_Translate('General_Visitors'),
- 'name' => Piwik_Translate('UserCountry_Continent'),
- 'module' => 'UserCountry',
- 'action' => 'getContinent',
- 'dimension' => Piwik_Translate('UserCountry_Continent'),
- 'metrics' => $metrics,
- 'order' => 6,
- );
-
- $reports[] = array(
- 'category' => Piwik_Translate('General_Visitors'),
- 'name' => Piwik_Translate('UserCountry_Region'),
- 'module' => 'UserCountry',
- 'action' => 'getRegion',
- 'dimension' => Piwik_Translate('UserCountry_Region'),
- 'metrics' => $metrics,
- 'order' => 7,
- );
-
- $reports[] = array(
- 'category' => Piwik_Translate('General_Visitors'),
- 'name' => Piwik_Translate('UserCountry_City'),
- 'module' => 'UserCountry',
- 'action' => 'getCity',
- 'dimension' => Piwik_Translate('UserCountry_City'),
- 'metrics' => $metrics,
- 'order' => 8,
- );
- }
-
- /**
- * @param Piwik_Event_Notification $notification notification object
- */
- function getReportsWithGoalMetrics( $notification )
- {
- $dimensions =& $notification->getNotificationObject();
- $dimensions = array_merge($dimensions, array(
- array( 'category' => Piwik_Translate('General_Visit'),
- 'name' => Piwik_Translate('UserCountry_Country'),
- 'module' => 'UserCountry',
- 'action' => 'getCountry',
- ),
- array( 'category' => Piwik_Translate('General_Visit'),
- 'name' => Piwik_Translate('UserCountry_Continent'),
- 'module' => 'UserCountry',
- 'action' => 'getContinent',
- ),
- array('category' => Piwik_Translate('General_Visit'),
- 'name' => Piwik_Translate('UserCountry_Region'),
- 'module' => 'UserCountry',
- 'action' => 'getRegion'),
- array('category' => Piwik_Translate('General_Visit'),
- 'name' => Piwik_Translate('UserCountry_City'),
- 'module' => 'UserCountry',
- 'action' => 'getCity'),
- ));
- }
-
- /**
- * @param Piwik_Event_Notification $notification notification object
- * @return mixed
- */
- function archivePeriod( $notification )
- {
- /**
- * @param Piwik_ArchiveProcessing_Period $archiveProcessing
- */
- $archiveProcessing = $notification->getNotificationObject();
-
- if(!$archiveProcessing->shouldProcessReportsForPlugin($this->getPluginName())) return;
-
- $dataTableToSum = array(
- self::VISITS_BY_COUNTRY_RECORD_NAME,
- self::VISITS_BY_REGION_RECORD_NAME,
- self::VISITS_BY_CITY_RECORD_NAME,
- );
-
- $nameToCount = $archiveProcessing->archiveDataTable($dataTableToSum);
- $archiveProcessing->insertNumericRecord(self::DISTINCT_COUNTRIES_METRIC,
- $nameToCount[self::VISITS_BY_COUNTRY_RECORD_NAME]['level0']);
- }
-
- private $interestTables = null;
- private $latLongForCities = null;
-
- /**
- * @param Piwik_Event_Notification $notification notification object
- * @return mixed
- */
- function archiveDay($notification)
- {
- /**
- * @var Piwik_ArchiveProcessing
- */
- $archiveProcessing = $notification->getNotificationObject();
-
- if(!$archiveProcessing->shouldProcessReportsForPlugin($this->getPluginName())) return;
-
- $this->interestTables = array('location_country' => array(),
- 'location_region' => array(),
- 'location_city' => array());
- $this->latLongForCities = array();
-
- $this->archiveDayAggregateVisits($archiveProcessing);
- $this->archiveDayAggregateGoals($archiveProcessing);
- $this->archiveDayRecordInDatabase($archiveProcessing);
-
- unset($this->interestTables);
- unset($this->latLongForCities);
- }
-
- /**
- * @param Piwik_ArchiveProcessing_Day $archiveProcessing
- */
- protected function archiveDayAggregateVisits($archiveProcessing)
- {
- $dimensions = array_keys($this->interestTables);
- $query = $archiveProcessing->queryVisitsByDimension(
- $dimensions,
- $where = '',
- $metrics = false,
- $orderBy = false,
- $rankingQuery = null,
- $addSelect = 'MAX(log_visit.location_latitude) as location_latitude,
+
+ /**
+ * @param Piwik_Event_Notification $notification notification object
+ */
+ public function getVisitorLocation($notification)
+ {
+ require_once PIWIK_INCLUDE_PATH . "/plugins/UserCountry/LocationProvider.php";
+ $location = & $notification->getNotificationObject();
+ $visitorInfo = $notification->getNotificationInfo();
+
+ $id = Piwik_Common::getCurrentLocationProviderId();
+ $provider = Piwik_UserCountry_LocationProvider::getProviderById($id);
+ if ($provider === false) {
+ $id = Piwik_UserCountry_LocationProvider_Default::ID;
+ $provider = Piwik_UserCountry_LocationProvider::getProviderById($id);
+ printDebug("GEO: no current location provider sent, falling back to default '$id' one.");
+ }
+
+ $location = $provider->getLocation($visitorInfo);
+
+ // if we can't find a location, use default provider
+ if ($location === false) {
+ $defaultId = Piwik_UserCountry_LocationProvider_Default::ID;
+ $provider = Piwik_UserCountry_LocationProvider::getProviderById($defaultId);
+ $location = $provider->getLocation($visitorInfo);
+ printDebug("GEO: couldn't find a location with Geo Module '$id', using Default '$defaultId' provider as fallback...");
+ $id = $defaultId;
+ }
+ printDebug("GEO: Found IP location (provider '" . $id . "'): " . var_export($location, true));
+ }
+
+ function addWidgets()
+ {
+ $widgetContinentLabel = Piwik_Translate('UserCountry_WidgetLocation')
+ . ' (' . Piwik_Translate('UserCountry_Continent') . ')';
+ $widgetCountryLabel = Piwik_Translate('UserCountry_WidgetLocation')
+ . ' (' . Piwik_Translate('UserCountry_Country') . ')';
+ $widgetRegionLabel = Piwik_Translate('UserCountry_WidgetLocation')
+ . ' (' . Piwik_Translate('UserCountry_Region') . ')';
+ $widgetCityLabel = Piwik_Translate('UserCountry_WidgetLocation')
+ . ' (' . Piwik_Translate('UserCountry_City') . ')';
+
+ Piwik_AddWidget('General_Visitors', $widgetContinentLabel, 'UserCountry', 'getContinent');
+ Piwik_AddWidget('General_Visitors', $widgetCountryLabel, 'UserCountry', 'getCountry');
+ Piwik_AddWidget('General_Visitors', $widgetRegionLabel, 'UserCountry', 'getRegion');
+ Piwik_AddWidget('General_Visitors', $widgetCityLabel, 'UserCountry', 'getCity');
+ }
+
+ function addMenu()
+ {
+ Piwik_AddMenu('General_Visitors', 'UserCountry_SubmenuLocations', array('module' => 'UserCountry', 'action' => 'index'));
+ }
+
+ /**
+ * Event handler. Adds menu items to the Admin menu.
+ */
+ function addAdminMenu()
+ {
+ Piwik_AddAdminSubMenu('General_Settings', 'UserCountry_Geolocation',
+ array('module' => 'UserCountry', 'action' => 'adminIndex'),
+ Piwik::isUserIsSuperUser(),
+ $order = 8);
+ }
+
+ /**
+ * @param Piwik_Event_Notification $notification notification object
+ */
+ public function getSegmentsMetadata($notification)
+ {
+ $segments =& $notification->getNotificationObject();
+ $segments[] = array(
+ 'type' => 'dimension',
+ 'category' => 'Visit Location',
+ 'name' => Piwik_Translate('UserCountry_Country'),
+ 'segment' => 'country',
+ 'sqlSegment' => 'log_visit.location_country',
+ 'acceptedValues' => 'de, us, fr, in, es, etc.',
+ );
+ $segments[] = array(
+ 'type' => 'dimension',
+ 'category' => 'Visit Location',
+ 'name' => Piwik_Translate('UserCountry_Continent'),
+ 'segment' => 'continent',
+ 'sqlSegment' => 'log_visit.location_country',
+ 'acceptedValues' => 'eur, asi, amc, amn, ams, afr, ant, oce',
+ 'sqlFilter' => array('Piwik_UserCountry', 'getCountriesForContinent'),
+ );
+ $segments[] = array(
+ 'type' => 'dimension',
+ 'category' => 'Visit Location',
+ 'name' => Piwik_Translate('UserCountry_Region'),
+ 'segment' => 'region',
+ 'sqlSegment' => 'log_visit.location_region',
+ 'acceptedValues' => '01 02, OR, P8, etc.<br/>eg. region=A1;country=fr',
+ );
+ $segments[] = array(
+ 'type' => 'dimension',
+ 'category' => 'Visit Location',
+ 'name' => Piwik_Translate('UserCountry_City'),
+ 'segment' => 'city',
+ 'sqlSegment' => 'log_visit.location_city',
+ 'acceptedValues' => 'Sydney, Sao Paolo, Rome, etc.',
+ );
+ $segments[] = array(
+ 'type' => 'dimension',
+ 'category' => 'Visit Location',
+ 'name' => Piwik_Translate('UserCountry_Latitude'),
+ 'segment' => 'lat',
+ 'sqlSegment' => 'log_visit.location_latitude',
+ 'acceptedValues' => '-33.578, 40.830, etc.<br/>You can select visitors within a lat/long range using &segment=lat&gt;X;lat&lt;Y;long&gt;M;long&lt;N.',
+ );
+ $segments[] = array(
+ 'type' => 'dimension',
+ 'category' => 'Visit Location',
+ 'name' => Piwik_Translate('UserCountry_Longitude'),
+ 'segment' => 'long',
+ 'sqlSegment' => 'log_visit.location_longitude',
+ 'acceptedValues' => '-70.664, 14.326, etc.',
+ );
+ }
+
+ /**
+ * @param Piwik_Event_Notification $notification notification object
+ */
+ public function getReportMetadata($notification)
+ {
+ $metrics = array(
+ 'nb_visits' => Piwik_Translate('General_ColumnNbVisits'),
+ 'nb_uniq_visitors' => Piwik_Translate('General_ColumnNbUniqVisitors'),
+ 'nb_actions' => Piwik_Translate('General_ColumnNbActions'),
+ );
+
+ $reports = & $notification->getNotificationObject();
+
+ $reports[] = array(
+ 'category' => Piwik_Translate('General_Visitors'),
+ 'name' => Piwik_Translate('UserCountry_Country'),
+ 'module' => 'UserCountry',
+ 'action' => 'getCountry',
+ 'dimension' => Piwik_Translate('UserCountry_Country'),
+ 'metrics' => $metrics,
+ 'order' => 5,
+ );
+
+ $reports[] = array(
+ 'category' => Piwik_Translate('General_Visitors'),
+ 'name' => Piwik_Translate('UserCountry_Continent'),
+ 'module' => 'UserCountry',
+ 'action' => 'getContinent',
+ 'dimension' => Piwik_Translate('UserCountry_Continent'),
+ 'metrics' => $metrics,
+ 'order' => 6,
+ );
+
+ $reports[] = array(
+ 'category' => Piwik_Translate('General_Visitors'),
+ 'name' => Piwik_Translate('UserCountry_Region'),
+ 'module' => 'UserCountry',
+ 'action' => 'getRegion',
+ 'dimension' => Piwik_Translate('UserCountry_Region'),
+ 'metrics' => $metrics,
+ 'order' => 7,
+ );
+
+ $reports[] = array(
+ 'category' => Piwik_Translate('General_Visitors'),
+ 'name' => Piwik_Translate('UserCountry_City'),
+ 'module' => 'UserCountry',
+ 'action' => 'getCity',
+ 'dimension' => Piwik_Translate('UserCountry_City'),
+ 'metrics' => $metrics,
+ 'order' => 8,
+ );
+ }
+
+ /**
+ * @param Piwik_Event_Notification $notification notification object
+ */
+ function getReportsWithGoalMetrics($notification)
+ {
+ $dimensions =& $notification->getNotificationObject();
+ $dimensions = array_merge($dimensions, array(
+ array('category' => Piwik_Translate('General_Visit'),
+ 'name' => Piwik_Translate('UserCountry_Country'),
+ 'module' => 'UserCountry',
+ 'action' => 'getCountry',
+ ),
+ array('category' => Piwik_Translate('General_Visit'),
+ 'name' => Piwik_Translate('UserCountry_Continent'),
+ 'module' => 'UserCountry',
+ 'action' => 'getContinent',
+ ),
+ array('category' => Piwik_Translate('General_Visit'),
+ 'name' => Piwik_Translate('UserCountry_Region'),
+ 'module' => 'UserCountry',
+ 'action' => 'getRegion'),
+ array('category' => Piwik_Translate('General_Visit'),
+ 'name' => Piwik_Translate('UserCountry_City'),
+ 'module' => 'UserCountry',
+ 'action' => 'getCity'),
+ ));
+ }
+
+ /**
+ * @param Piwik_Event_Notification $notification notification object
+ * @return mixed
+ */
+ function archivePeriod($notification)
+ {
+ /**
+ * @param Piwik_ArchiveProcessing_Period $archiveProcessing
+ */
+ $archiveProcessing = $notification->getNotificationObject();
+
+ if (!$archiveProcessing->shouldProcessReportsForPlugin($this->getPluginName())) return;
+
+ $dataTableToSum = array(
+ self::VISITS_BY_COUNTRY_RECORD_NAME,
+ self::VISITS_BY_REGION_RECORD_NAME,
+ self::VISITS_BY_CITY_RECORD_NAME,
+ );
+
+ $nameToCount = $archiveProcessing->archiveDataTable($dataTableToSum);
+ $archiveProcessing->insertNumericRecord(self::DISTINCT_COUNTRIES_METRIC,
+ $nameToCount[self::VISITS_BY_COUNTRY_RECORD_NAME]['level0']);
+ }
+
+ private $interestTables = null;
+ private $latLongForCities = null;
+
+ /**
+ * @param Piwik_Event_Notification $notification notification object
+ * @return mixed
+ */
+ function archiveDay($notification)
+ {
+ /**
+ * @var Piwik_ArchiveProcessing
+ */
+ $archiveProcessing = $notification->getNotificationObject();
+
+ if (!$archiveProcessing->shouldProcessReportsForPlugin($this->getPluginName())) return;
+
+ $this->interestTables = array('location_country' => array(),
+ 'location_region' => array(),
+ 'location_city' => array());
+ $this->latLongForCities = array();
+
+ $this->archiveDayAggregateVisits($archiveProcessing);
+ $this->archiveDayAggregateGoals($archiveProcessing);
+ $this->archiveDayRecordInDatabase($archiveProcessing);
+
+ unset($this->interestTables);
+ unset($this->latLongForCities);
+ }
+
+ /**
+ * @param Piwik_ArchiveProcessing_Day $archiveProcessing
+ */
+ protected function archiveDayAggregateVisits($archiveProcessing)
+ {
+ $dimensions = array_keys($this->interestTables);
+ $query = $archiveProcessing->queryVisitsByDimension(
+ $dimensions,
+ $where = '',
+ $metrics = false,
+ $orderBy = false,
+ $rankingQuery = null,
+ $addSelect = 'MAX(log_visit.location_latitude) as location_latitude,
MAX(log_visit.location_longitude) as location_longitude'
- );
-
- if ($query === false)
- {
- return;
- }
-
- while ($row = $query->fetch())
- {
- // get latitude/longitude if there's a city
- $lat = $long = false;
- if (!empty($row['location_city']))
- {
- if (!empty($row['location_latitude']))
- {
- $lat = $row['location_latitude'];
- }
- if (!empty($row['location_longitude']))
- {
- $long = $row['location_longitude'];
- }
- }
-
- // make sure regions & cities w/ the same name don't get merged
- $this->setLongCityRegionId($row);
-
- // store latitude/longitude, if we should
- if ($lat !== false && $long !== false)
- {
- $this->latLongForCities[$row['location_city']] = array($lat, $long);
- }
-
- // add the stats to each dimension's table
- foreach ($this->interestTables as $dimension => &$table)
- {
- $label = (string)$row[$dimension];
-
- if (!isset($table[$label]))
- {
- $table[$label] = $archiveProcessing->getNewInterestRow();
- }
- $archiveProcessing->updateInterestStats($row, $table[$label]);
- }
- }
- }
-
- /**
- * @param Piwik_ArchiveProcessing_Day $archiveProcessing
- */
- protected function archiveDayAggregateGoals($archiveProcessing)
- {
- $dimensions = array_keys($this->interestTables);
- $query = $archiveProcessing->queryConversionsByDimension($dimensions);
-
- if ($query === false)
- {
- return;
- }
-
- while ($row = $query->fetch())
- {
- // make sure regions & cities w/ the same name don't get merged
- $this->setLongCityRegionId($row);
-
- $idGoal = $row['idgoal'];
- foreach ($this->interestTables as $dimension => &$table)
- {
- $label = (string)$row[$dimension];
-
- if (!isset($table[$label][Piwik_Archive::INDEX_GOALS][$idGoal]))
- {
- $table[$label][Piwik_Archive::INDEX_GOALS][$idGoal] = $archiveProcessing->getNewGoalRow($idGoal);
- }
-
- $archiveProcessing->updateGoalStats($row, $table[$label][Piwik_Archive::INDEX_GOALS][$idGoal]);
- }
- }
-
- foreach ($this->interestTables as &$table)
- {
- $archiveProcessing->enrichConversionsByLabelArray($table);
- }
- }
-
- /**
- * @param Piwik_ArchiveProcessing_Day $archiveProcessing
- */
- protected function archiveDayRecordInDatabase($archiveProcessing)
- {
- $maximumRows = Piwik_Config::getInstance()->General['datatable_archiving_maximum_rows_standard'];
-
- $tableCountry = Piwik_ArchiveProcessing_Day::getDataTableFromArray($this->interestTables['location_country']);
- $archiveProcessing->insertBlobRecord(self::VISITS_BY_COUNTRY_RECORD_NAME, $tableCountry->getSerialized());
- $archiveProcessing->insertNumericRecord(self::DISTINCT_COUNTRIES_METRIC, $tableCountry->getRowsCount());
- destroy($tableCountry);
-
- $tableRegion = Piwik_ArchiveProcessing_Day::getDataTableFromArray($this->interestTables['location_region']);
- $serialized = $tableRegion->getSerialized($maximumRows, $maximumRows, Piwik_Archive::INDEX_NB_VISITS);
- $archiveProcessing->insertBlobRecord(self::VISITS_BY_REGION_RECORD_NAME, $serialized);
- destroy($tableRegion);
-
- $tableCity = Piwik_ArchiveProcessing_Day::getDataTableFromArray($this->interestTables['location_city']);
- $this->setLatitudeLongitude($tableCity);
- $serialized = $tableCity->getSerialized($maximumRows, $maximumRows, Piwik_Archive::INDEX_NB_VISITS);
- $archiveProcessing->insertBlobRecord(self::VISITS_BY_CITY_RECORD_NAME, $serialized);
- destroy($tableCity);
- }
-
- /**
- * Makes sure the region and city of a query row are unique.
- *
- * @param array $row
- */
- private function setLongCityRegionId( &$row )
- {
- static $locationColumns = array('location_region', 'location_country', 'location_city');
-
- // to be on the safe side, remove the location separator from the region/city/country we
- // get from the query
- foreach ($locationColumns as $column)
- {
- $row[$column] = str_replace(self::LOCATION_SEPARATOR, '', $row[$column]);
- }
-
- if (!empty($row['location_region'])) // do not differentiate between unknown regions
- {
- $row['location_region'] = $row['location_region'].self::LOCATION_SEPARATOR.$row['location_country'];
- }
-
- if (!empty($row['location_city'])) // do not differentiate between unknown cities
- {
- $row['location_city'] = $row['location_city'].self::LOCATION_SEPARATOR.$row['location_region'];
- }
- }
-
- /**
- * Returns a list of country codes for a given continent code.
- *
- * @param string $continent The continent code.
- * @return array
- */
- public static function getCountriesForContinent( $continent )
- {
- $continent = strtolower($continent);
-
- $result = array();
- foreach (Piwik_Common::getCountriesList() as $countryCode => $continentCode)
- {
- if ($continent == $continentCode)
- {
- $result[] = $countryCode;
- }
- }
- return array('SQL' => "'".implode("', '", $result)."', ?",
- 'bind' => '-'); // HACK: SegmentExpression requires a $bind, even if there's nothing to bind
- }
-
- /**
- * Utility method, appends latitude/longitude pairs to city table labels, if that data
- * exists for the city.
- */
- private function setLatitudeLongitude( $tableCity )
- {
- foreach ($tableCity->getRows() as $row)
- {
- $label = $row->getColumn('label');
- if (isset($this->latLongForCities[$label]))
- {
- // get lat/long for city
- list($lat, $long) = $this->latLongForCities[$label];
- $lat = round($lat, Piwik_UserCountry_LocationProvider::GEOGRAPHIC_COORD_PRECISION);
- $long = round($long, Piwik_UserCountry_LocationProvider::GEOGRAPHIC_COORD_PRECISION);
-
- // set latitude + longitude metadata
- $row->setMetadata('lat', $lat);
- $row->setMetadata('long', $long);
- }
- }
- }
+ );
+
+ if ($query === false) {
+ return;
+ }
+
+ while ($row = $query->fetch()) {
+ // get latitude/longitude if there's a city
+ $lat = $long = false;
+ if (!empty($row['location_city'])) {
+ if (!empty($row['location_latitude'])) {
+ $lat = $row['location_latitude'];
+ }
+ if (!empty($row['location_longitude'])) {
+ $long = $row['location_longitude'];
+ }
+ }
+
+ // make sure regions & cities w/ the same name don't get merged
+ $this->setLongCityRegionId($row);
+
+ // store latitude/longitude, if we should
+ if ($lat !== false && $long !== false) {
+ $this->latLongForCities[$row['location_city']] = array($lat, $long);
+ }
+
+ // add the stats to each dimension's table
+ foreach ($this->interestTables as $dimension => &$table) {
+ $label = (string)$row[$dimension];
+
+ if (!isset($table[$label])) {
+ $table[$label] = $archiveProcessing->getNewInterestRow();
+ }
+ $archiveProcessing->updateInterestStats($row, $table[$label]);
+ }
+ }
+ }
+
+ /**
+ * @param Piwik_ArchiveProcessing_Day $archiveProcessing
+ */
+ protected function archiveDayAggregateGoals($archiveProcessing)
+ {
+ $dimensions = array_keys($this->interestTables);
+ $query = $archiveProcessing->queryConversionsByDimension($dimensions);
+
+ if ($query === false) {
+ return;
+ }
+
+ while ($row = $query->fetch()) {
+ // make sure regions & cities w/ the same name don't get merged
+ $this->setLongCityRegionId($row);
+
+ $idGoal = $row['idgoal'];
+ foreach ($this->interestTables as $dimension => &$table) {
+ $label = (string)$row[$dimension];
+
+ if (!isset($table[$label][Piwik_Archive::INDEX_GOALS][$idGoal])) {
+ $table[$label][Piwik_Archive::INDEX_GOALS][$idGoal] = $archiveProcessing->getNewGoalRow($idGoal);
+ }
+
+ $archiveProcessing->updateGoalStats($row, $table[$label][Piwik_Archive::INDEX_GOALS][$idGoal]);
+ }
+ }
+
+ foreach ($this->interestTables as &$table) {
+ $archiveProcessing->enrichConversionsByLabelArray($table);
+ }
+ }
+
+ /**
+ * @param Piwik_ArchiveProcessing_Day $archiveProcessing
+ */
+ protected function archiveDayRecordInDatabase($archiveProcessing)
+ {
+ $maximumRows = Piwik_Config::getInstance()->General['datatable_archiving_maximum_rows_standard'];
+
+ $tableCountry = Piwik_ArchiveProcessing_Day::getDataTableFromArray($this->interestTables['location_country']);
+ $archiveProcessing->insertBlobRecord(self::VISITS_BY_COUNTRY_RECORD_NAME, $tableCountry->getSerialized());
+ $archiveProcessing->insertNumericRecord(self::DISTINCT_COUNTRIES_METRIC, $tableCountry->getRowsCount());
+ destroy($tableCountry);
+
+ $tableRegion = Piwik_ArchiveProcessing_Day::getDataTableFromArray($this->interestTables['location_region']);
+ $serialized = $tableRegion->getSerialized($maximumRows, $maximumRows, Piwik_Archive::INDEX_NB_VISITS);
+ $archiveProcessing->insertBlobRecord(self::VISITS_BY_REGION_RECORD_NAME, $serialized);
+ destroy($tableRegion);
+
+ $tableCity = Piwik_ArchiveProcessing_Day::getDataTableFromArray($this->interestTables['location_city']);
+ $this->setLatitudeLongitude($tableCity);
+ $serialized = $tableCity->getSerialized($maximumRows, $maximumRows, Piwik_Archive::INDEX_NB_VISITS);
+ $archiveProcessing->insertBlobRecord(self::VISITS_BY_CITY_RECORD_NAME, $serialized);
+ destroy($tableCity);
+ }
+
+ /**
+ * Makes sure the region and city of a query row are unique.
+ *
+ * @param array $row
+ */
+ private function setLongCityRegionId(&$row)
+ {
+ static $locationColumns = array('location_region', 'location_country', 'location_city');
+
+ // to be on the safe side, remove the location separator from the region/city/country we
+ // get from the query
+ foreach ($locationColumns as $column) {
+ $row[$column] = str_replace(self::LOCATION_SEPARATOR, '', $row[$column]);
+ }
+
+ if (!empty($row['location_region'])) // do not differentiate between unknown regions
+ {
+ $row['location_region'] = $row['location_region'] . self::LOCATION_SEPARATOR . $row['location_country'];
+ }
+
+ if (!empty($row['location_city'])) // do not differentiate between unknown cities
+ {
+ $row['location_city'] = $row['location_city'] . self::LOCATION_SEPARATOR . $row['location_region'];
+ }
+ }
+
+ /**
+ * Returns a list of country codes for a given continent code.
+ *
+ * @param string $continent The continent code.
+ * @return array
+ */
+ public static function getCountriesForContinent($continent)
+ {
+ $continent = strtolower($continent);
+
+ $result = array();
+ foreach (Piwik_Common::getCountriesList() as $countryCode => $continentCode) {
+ if ($continent == $continentCode) {
+ $result[] = $countryCode;
+ }
+ }
+ return array('SQL' => "'" . implode("', '", $result) . "', ?",
+ 'bind' => '-'); // HACK: SegmentExpression requires a $bind, even if there's nothing to bind
+ }
+
+ /**
+ * Utility method, appends latitude/longitude pairs to city table labels, if that data
+ * exists for the city.
+ */
+ private function setLatitudeLongitude($tableCity)
+ {
+ foreach ($tableCity->getRows() as $row) {
+ $label = $row->getColumn('label');
+ if (isset($this->latLongForCities[$label])) {
+ // get lat/long for city
+ list($lat, $long) = $this->latLongForCities[$label];
+ $lat = round($lat, Piwik_UserCountry_LocationProvider::GEOGRAPHIC_COORD_PRECISION);
+ $long = round($long, Piwik_UserCountry_LocationProvider::GEOGRAPHIC_COORD_PRECISION);
+
+ // set latitude + longitude metadata
+ $row->setMetadata('lat', $lat);
+ $row->setMetadata('long', $long);
+ }
+ }
+ }
}
diff --git a/plugins/UserCountry/functions.php b/plugins/UserCountry/functions.php
index a7e78dec52..ab1ff7e26a 100644
--- a/plugins/UserCountry/functions.php
+++ b/plugins/UserCountry/functions.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_UserCountry
*/
@@ -17,14 +17,13 @@
*/
function Piwik_getFlagFromCode($code)
{
- $pathInPiwik = 'plugins/UserCountry/flags/%s.png';
- $pathWithCode = sprintf($pathInPiwik, $code);
- $absolutePath = PIWIK_INCLUDE_PATH . '/' . $pathWithCode;
- if(file_exists($absolutePath))
- {
- return $pathWithCode;
- }
- return sprintf($pathInPiwik, Piwik_Tracker_Visit::UNKNOWN_CODE);
+ $pathInPiwik = 'plugins/UserCountry/flags/%s.png';
+ $pathWithCode = sprintf($pathInPiwik, $code);
+ $absolutePath = PIWIK_INCLUDE_PATH . '/' . $pathWithCode;
+ if (file_exists($absolutePath)) {
+ return $pathWithCode;
+ }
+ return sprintf($pathInPiwik, Piwik_Tracker_Visit::UNKNOWN_CODE);
}
/**
@@ -35,11 +34,10 @@ function Piwik_getFlagFromCode($code)
*/
function Piwik_ContinentTranslate($label)
{
- if($label == 'unk' || $label == '')
- {
- return Piwik_Translate('General_Unknown');
- }
- return Piwik_Translate('UserCountry_continent_'. $label);
+ if ($label == 'unk' || $label == '') {
+ return Piwik_Translate('General_Unknown');
+ }
+ return Piwik_Translate('UserCountry_continent_' . $label);
}
/**
@@ -50,16 +48,15 @@ function Piwik_ContinentTranslate($label)
*/
function Piwik_CountryTranslate($label)
{
- if($label == Piwik_Tracker_Visit::UNKNOWN_CODE || $label == '')
- {
- return Piwik_Translate('General_Unknown');
- }
- return Piwik_Translate('UserCountry_country_'. $label);
+ if ($label == Piwik_Tracker_Visit::UNKNOWN_CODE || $label == '') {
+ return Piwik_Translate('General_Unknown');
+ }
+ return Piwik_Translate('UserCountry_country_' . $label);
}
/**
* Splits a label by a certain separator and returns the N-th element.
- *
+ *
* @param string $label
* @param string $separator eg. ',' or '|'
* @param int $index The element index to extract.
@@ -68,111 +65,100 @@ function Piwik_CountryTranslate($label)
* @return string|false Returns false if $label == DataTable::LABEL_SUMMARY_ROW, otherwise
* explode($separator, $label)[$index].
*/
-function Piwik_UserCountry_getElementFromStringArray( $label, $separator, $index, $emptyValue = false )
+function Piwik_UserCountry_getElementFromStringArray($label, $separator, $index, $emptyValue = false)
{
- if ($label == Piwik_DataTable::LABEL_SUMMARY_ROW)
- {
- return false; // so no metadata/column is added
- }
-
- $segments = explode($separator, $label);
- return empty($segments[$index]) ? $emptyValue : $segments[$index];
+ if ($label == Piwik_DataTable::LABEL_SUMMARY_ROW) {
+ return false; // so no metadata/column is added
+ }
+
+ $segments = explode($separator, $label);
+ return empty($segments[$index]) ? $emptyValue : $segments[$index];
}
/**
* Returns the region name using the label of a Visits by Region report.
- *
+ *
* @param string $label A label containing a region code followed by '|' and a country code, eg,
* 'P3|GB'.
* @return string|false The region name or false if $label == Piwik_DataTable::LABEL_SUMMARY_ROW.
*/
-function Piwik_UserCountry_getRegionName( $label )
+function Piwik_UserCountry_getRegionName($label)
{
- if ($label == Piwik_DataTable::LABEL_SUMMARY_ROW)
- {
- return false; // so no metadata/column is added
- }
-
- if ($label == '')
- {
- return Piwik_Translate('General_Unknown');
- }
-
- list($regionCode, $countryCode) = explode(Piwik_UserCountry::LOCATION_SEPARATOR, $label);
- return Piwik_UserCountry_LocationProvider_GeoIp::getRegionNameFromCodes($countryCode, $regionCode);
+ if ($label == Piwik_DataTable::LABEL_SUMMARY_ROW) {
+ return false; // so no metadata/column is added
+ }
+
+ if ($label == '') {
+ return Piwik_Translate('General_Unknown');
+ }
+
+ list($regionCode, $countryCode) = explode(Piwik_UserCountry::LOCATION_SEPARATOR, $label);
+ return Piwik_UserCountry_LocationProvider_GeoIp::getRegionNameFromCodes($countryCode, $regionCode);
}
/**
* Returns the name of a region + the name of the region's country using the label of
* a Visits by Region report.
- *
+ *
* @param string $label A label containing a region code followed by '|' and a country code, eg,
* 'P3|GB'.
* @return string|false eg. 'Ile de France, France' or false if $label == Piwik_DataTable::LABEL_SUMMARY_ROW.
*/
-function Piwik_UserCountry_getPrettyRegionName( $label )
+function Piwik_UserCountry_getPrettyRegionName($label)
{
- if ($label == Piwik_DataTable::LABEL_SUMMARY_ROW)
- {
- return $label;
- }
-
- if ($label == '')
- {
- return Piwik_Translate('General_Unknown');
- }
-
- list($regionCode, $countryCode) = explode(Piwik_UserCountry::LOCATION_SEPARATOR, $label);
-
- $result = Piwik_UserCountry_LocationProvider_GeoIp::getRegionNameFromCodes($countryCode, $regionCode);
- if ($countryCode != Piwik_Tracker_Visit::UNKNOWN_CODE && $countryCode != '')
- {
- $result .= ', '.Piwik_CountryTranslate($countryCode);
- }
- return $result;
+ if ($label == Piwik_DataTable::LABEL_SUMMARY_ROW) {
+ return $label;
+ }
+
+ if ($label == '') {
+ return Piwik_Translate('General_Unknown');
+ }
+
+ list($regionCode, $countryCode) = explode(Piwik_UserCountry::LOCATION_SEPARATOR, $label);
+
+ $result = Piwik_UserCountry_LocationProvider_GeoIp::getRegionNameFromCodes($countryCode, $regionCode);
+ if ($countryCode != Piwik_Tracker_Visit::UNKNOWN_CODE && $countryCode != '') {
+ $result .= ', ' . Piwik_CountryTranslate($countryCode);
+ }
+ return $result;
}
/**
* Returns the name of a city + the name of its region + the name of its country using
* the label of a Visits by City report.
- *
+ *
* @param string $label A label containing a city name, region code + country code,
* separated by two '|' chars: 'Paris|A8|FR'
* @return string|false eg. 'Paris, Ile de France, France' or false if $label ==
* Piwik_DataTable::LABEL_SUMMARY_ROW.
*/
-function Piwik_UserCountry_getPrettyCityName( $label )
+function Piwik_UserCountry_getPrettyCityName($label)
{
- if ($label == Piwik_DataTable::LABEL_SUMMARY_ROW)
- {
- return $label;
- }
-
- if ($label == '')
- {
- return Piwik_Translate('General_Unknown');
- }
-
- // get city name, region code & country code
- $parts = explode(Piwik_UserCountry::LOCATION_SEPARATOR, $label);
- $cityName = $parts[0];
- $regionCode = $parts[1];
- $countryCode = $parts[2];
-
- if ($cityName == Piwik_Tracker_Visit::UNKNOWN_CODE || $cityName == '')
- {
- $cityName = Piwik_Translate('General_Unknown');
- }
-
- $result = $cityName;
- if ($countryCode != Piwik_Tracker_Visit::UNKNOWN_CODE && $countryCode != '')
- {
- if ($regionCode != '' && $regionCode != Piwik_Tracker_Visit::UNKNOWN_CODE)
- {
- $regionName = Piwik_UserCountry_LocationProvider_GeoIp::getRegionNameFromCodes($countryCode, $regionCode);
- $result .= ', '.$regionName;
- }
- $result .= ', '.Piwik_CountryTranslate($countryCode);
- }
- return $result;
+ if ($label == Piwik_DataTable::LABEL_SUMMARY_ROW) {
+ return $label;
+ }
+
+ if ($label == '') {
+ return Piwik_Translate('General_Unknown');
+ }
+
+ // get city name, region code & country code
+ $parts = explode(Piwik_UserCountry::LOCATION_SEPARATOR, $label);
+ $cityName = $parts[0];
+ $regionCode = $parts[1];
+ $countryCode = $parts[2];
+
+ if ($cityName == Piwik_Tracker_Visit::UNKNOWN_CODE || $cityName == '') {
+ $cityName = Piwik_Translate('General_Unknown');
+ }
+
+ $result = $cityName;
+ if ($countryCode != Piwik_Tracker_Visit::UNKNOWN_CODE && $countryCode != '') {
+ if ($regionCode != '' && $regionCode != Piwik_Tracker_Visit::UNKNOWN_CODE) {
+ $regionName = Piwik_UserCountry_LocationProvider_GeoIp::getRegionNameFromCodes($countryCode, $regionCode);
+ $result .= ', ' . $regionName;
+ }
+ $result .= ', ' . Piwik_CountryTranslate($countryCode);
+ }
+ return $result;
}
diff --git a/plugins/UserCountry/templates/admin.js b/plugins/UserCountry/templates/admin.js
index 46e7931add..1bb3d22d76 100755
--- a/plugins/UserCountry/templates/admin.js
+++ b/plugins/UserCountry/templates/admin.js
@@ -5,52 +5,52 @@
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
-$(document).ready(function() {
- $('#geoip-download-progress,#geoip-updater-progressbar').progressbar({value: 1});
-
- // handle switch current location provider
- $('.location-provider').change(function() {
- if (!$(this).is(':checked')) return; // only handle radio buttons that get checked
-
- var parent = $(this).parent(),
- loading = $('.loadingPiwik', parent),
- ajaxSuccess = $('.ajaxSuccess', parent);
+$(document).ready(function () {
+ $('#geoip-download-progress,#geoip-updater-progressbar').progressbar({value: 1});
+
+ // handle switch current location provider
+ $('.location-provider').change(function () {
+ if (!$(this).is(':checked')) return; // only handle radio buttons that get checked
+
+ var parent = $(this).parent(),
+ loading = $('.loadingPiwik', parent),
+ ajaxSuccess = $('.ajaxSuccess', parent);
var ajaxRequest = new ajaxHelper();
ajaxRequest.setLoadingElement(loading);
ajaxRequest.addParams({
module: 'UserCountry',
action: 'setCurrentLocationProvider',
- id: $(this).val()
+ id: $(this).val()
}, 'get');
ajaxRequest.setCallback(
function () {
- ajaxSuccess.fadeIn(1000, function() {
- setTimeout(function() {
+ ajaxSuccess.fadeIn(1000, function () {
+ setTimeout(function () {
ajaxSuccess.fadeOut(1000);
}, 2000);
});
}
);
ajaxRequest.send(false);
- });
-
- // handle 'refresh location' link click
- $('.refresh-loc').click(function(e) {
- e.preventDefault();
-
- var cell = $(this).parent().parent(),
- loading = $('.loadingPiwik', cell),
- location = $('.location', cell);
-
- location.css('visibility', 'hidden');
+ });
+
+ // handle 'refresh location' link click
+ $('.refresh-loc').click(function (e) {
+ e.preventDefault();
+
+ var cell = $(this).parent().parent(),
+ loading = $('.loadingPiwik', cell),
+ location = $('.location', cell);
+
+ location.css('visibility', 'hidden');
var ajaxRequest = new ajaxHelper();
ajaxRequest.setLoadingElement(loading);
ajaxRequest.addParams({
module: 'UserCountry',
action: 'getLocationUsingProvider',
- id: $(this).attr('data-impl-id')
+ id: $(this).attr('data-impl-id')
}, 'get');
ajaxRequest.setCallback(
function (response) {
@@ -60,146 +60,134 @@ $(document).ready(function() {
ajaxRequest.setFormat('html');
ajaxRequest.send(false);
- return false;
- });
-
- // geoip database wizard
- var downloadNextChunk = function(action, thisId, progressBarId, cont, extraData, callback)
- {
- var data = {
- module: 'UserCountry',
- action: action,
- token_auth: piwik.token_auth,
- 'continue': cont ? 1 : 0
- };
- for (var k in extraData)
- {
- data[k] = extraData[k];
- }
-
+ return false;
+ });
+
+ // geoip database wizard
+ var downloadNextChunk = function (action, thisId, progressBarId, cont, extraData, callback) {
+ var data = {
+ module: 'UserCountry',
+ action: action,
+ token_auth: piwik.token_auth,
+ 'continue': cont ? 1 : 0
+ };
+ for (var k in extraData) {
+ data[k] = extraData[k];
+ }
+
var ajaxRequest = new ajaxHelper();
ajaxRequest.addParams(data, 'post');
- ajaxRequest.setCallback(function(response) {
- if (!response || response.error)
- {
- callback(response);
- }
- else
- {
- // update progress bar
- var newProgressVal = Math.ceil((response.current_size / response.expected_file_size) * 100);
- newProgressVal = Math.min(newProgressVal, 100);
- $('#'+progressBarId).progressbar('option', 'value', newProgressVal);
-
- // if incomplete, download next chunk, otherwise, show updater manager
- if (newProgressVal < 100)
- {
- downloadNextChunk(action, thisId, progressBarId, true, extraData, callback);
- }
- else
- {
- callback(response);
- }
- }
- });
- ajaxRequest.setErrorCallback(function() {
- callback({error: _pk_translate('UserCountry_FatalErrorDuringDownload_js')});
- });
+ ajaxRequest.setCallback(function (response) {
+ if (!response || response.error) {
+ callback(response);
+ }
+ else {
+ // update progress bar
+ var newProgressVal = Math.ceil((response.current_size / response.expected_file_size) * 100);
+ newProgressVal = Math.min(newProgressVal, 100);
+ $('#' + progressBarId).progressbar('option', 'value', newProgressVal);
+
+ // if incomplete, download next chunk, otherwise, show updater manager
+ if (newProgressVal < 100) {
+ downloadNextChunk(action, thisId, progressBarId, true, extraData, callback);
+ }
+ else {
+ callback(response);
+ }
+ }
+ });
+ ajaxRequest.setErrorCallback(function () {
+ callback({error: _pk_translate('UserCountry_FatalErrorDuringDownload_js')});
+ });
ajaxRequest.send(false);
- };
-
- $('#start-download-free-geoip').click(function() {
- $('#geoipdb-screen1').hide("slide", {direction: "left"}, 800, function() {
- $('#geoipdb-screen2-download').fadeIn(1000);
-
- // start download of free dbs
- downloadNextChunk(
- 'downloadFreeGeoIPDB',
- 'geoipdb-screen2-download',
- 'geoip-download-progress',
- false,
- {},
- function(response) {
- if (response.error)
- {
- // on error, show error & stop downloading
- $('#'+thisId).fadeOut(1000, function() {
- $('#manage-geoip-dbs').html(response.error);
- });
- }
- else
- {
- $('#geoipdb-screen2-download').fadeOut(1000, function() {
- $('#manage-geoip-dbs').html(response.next_screen);
- });
- }
- }
- );
- });
- });
-
- $('body').on('click', '#start-automatic-update-geoip', function() {
- $('#geoipdb-screen1').hide("slide", {direction: "left"}, 800, function () {
- $('#geoip-db-mangement').text(_pk_translate('UserCountry_SetupAutomaticUpdatesOfGeoIP_js'));
- $('#geoipdb-update-info').fadeIn(1000);
- });
- });
-
- $('body').on('click', '#update-geoip-links', function() {
- $('#geoipdb-update-info-error').hide();
-
- var currentDownloading = null,
- updateGeoIPSuccess = function(response)
- {
- if (response && response.error)
- {
- $('#geoip-progressbar-container').hide();
- $('#geoipdb-update-info-error').html(response.error).show();
- }
- else if (response && response.to_download)
- {
- var continuing = currentDownloading == response.to_download;
- currentDownloading = response.to_download;
-
- // show progress bar w/ message
- $('#geoip-updater-progressbar').progressbar('option', 'value', 1);
- $('#geoip-updater-progressbar-label').html(response.to_download_label);
- $('#geoip-progressbar-container').show();
-
- // start/continue download
- downloadNextChunk(
- 'downloadMissingGeoIpDb', 'geoipdb-update-info', 'geoip-updater-progressbar',
- continuing, {key: response.to_download}, updateGeoIPSuccess);
- }
- else
- {
- $('#geoipdb-update-info-error').hide();
- $('#geoip-updater-progressbar-label').html('');
- $('#geoip-progressbar-container').hide();
-
- // fade in/out Done message
- $('#done-updating-updater').fadeIn(1000, function() {
- setTimeout(function() {
- $('#done-updating-updater').fadeOut(1000);
- }, 3000);
- });
- }
- };
-
- // setup the auto-updater
+ };
+
+ $('#start-download-free-geoip').click(function () {
+ $('#geoipdb-screen1').hide("slide", {direction: "left"}, 800, function () {
+ $('#geoipdb-screen2-download').fadeIn(1000);
+
+ // start download of free dbs
+ downloadNextChunk(
+ 'downloadFreeGeoIPDB',
+ 'geoipdb-screen2-download',
+ 'geoip-download-progress',
+ false,
+ {},
+ function (response) {
+ if (response.error) {
+ // on error, show error & stop downloading
+ $('#' + thisId).fadeOut(1000, function () {
+ $('#manage-geoip-dbs').html(response.error);
+ });
+ }
+ else {
+ $('#geoipdb-screen2-download').fadeOut(1000, function () {
+ $('#manage-geoip-dbs').html(response.next_screen);
+ });
+ }
+ }
+ );
+ });
+ });
+
+ $('body').on('click', '#start-automatic-update-geoip', function () {
+ $('#geoipdb-screen1').hide("slide", {direction: "left"}, 800, function () {
+ $('#geoip-db-mangement').text(_pk_translate('UserCountry_SetupAutomaticUpdatesOfGeoIP_js'));
+ $('#geoipdb-update-info').fadeIn(1000);
+ });
+ });
+
+ $('body').on('click', '#update-geoip-links', function () {
+ $('#geoipdb-update-info-error').hide();
+
+ var currentDownloading = null,
+ updateGeoIPSuccess = function (response) {
+ if (response && response.error) {
+ $('#geoip-progressbar-container').hide();
+ $('#geoipdb-update-info-error').html(response.error).show();
+ }
+ else if (response && response.to_download) {
+ var continuing = currentDownloading == response.to_download;
+ currentDownloading = response.to_download;
+
+ // show progress bar w/ message
+ $('#geoip-updater-progressbar').progressbar('option', 'value', 1);
+ $('#geoip-updater-progressbar-label').html(response.to_download_label);
+ $('#geoip-progressbar-container').show();
+
+ // start/continue download
+ downloadNextChunk(
+ 'downloadMissingGeoIpDb', 'geoipdb-update-info', 'geoip-updater-progressbar',
+ continuing, {key: response.to_download}, updateGeoIPSuccess);
+ }
+ else {
+ $('#geoipdb-update-info-error').hide();
+ $('#geoip-updater-progressbar-label').html('');
+ $('#geoip-progressbar-container').hide();
+
+ // fade in/out Done message
+ $('#done-updating-updater').fadeIn(1000, function () {
+ setTimeout(function () {
+ $('#done-updating-updater').fadeOut(1000);
+ }, 3000);
+ });
+ }
+ };
+
+ // setup the auto-updater
var ajaxRequest = new ajaxHelper();
ajaxRequest.addParams({
- period: $('#geoip-update-period-cell>input:checked').val()
+ period: $('#geoip-update-period-cell>input:checked').val()
}, 'get');
ajaxRequest.addParams({
module: 'UserCountry',
action: 'updateGeoIPLinks',
- token_auth: piwik.token_auth,
- loc_db: $('#geoip-location-db').val(),
- isp_db: $('#geoip-isp-db').val(),
- org_db: $('#geoip-org-db').val()
+ token_auth: piwik.token_auth,
+ loc_db: $('#geoip-location-db').val(),
+ isp_db: $('#geoip-isp-db').val(),
+ org_db: $('#geoip-org-db').val()
}, 'post');
ajaxRequest.setCallback(updateGeoIPSuccess);
ajaxRequest.send(false);
- });
+ });
});
diff --git a/plugins/UserCountry/templates/adminIndex.tpl b/plugins/UserCountry/templates/adminIndex.tpl
index 51676f3fba..2a40f652c0 100755
--- a/plugins/UserCountry/templates/adminIndex.tpl
+++ b/plugins/UserCountry/templates/adminIndex.tpl
@@ -5,116 +5,119 @@
<div style="width:900px">
-<p>{'UserCountry_GeolocationPageDesc'|translate}</p>
+ <p>{'UserCountry_GeolocationPageDesc'|translate}</p>
-{if !$isThereWorkingProvider}
-<h3 style="margin-top:0">{'UserCountry_HowToSetupGeoIP'|translate}</h3>
-<p>{'UserCountry_HowToSetupGeoIPIntro'|translate}</p>
+ {if !$isThereWorkingProvider}
+ <h3 style="margin-top:0">{'UserCountry_HowToSetupGeoIP'|translate}</h3>
+ <p>{'UserCountry_HowToSetupGeoIPIntro'|translate}</p>
+ <ul style="list-style:disc;margin-left:2em">
+ <li>{'UserCountry_HowToSetupGeoIP_Step1'|translate:'<a href="http://geolite.maxmind.com/download/geoip/database/GeoLiteCity.dat.gz">':'</a>':'<a target="_blank" href="http://www.maxmind.com/?rId=piwik">':'</a>'}</li>
+ <li>{'UserCountry_HowToSetupGeoIP_Step2'|translate:"'GeoLiteCity.dat'":'<strong>':'</strong>'}</li>
+ <li>{'UserCountry_HowToSetupGeoIP_Step3'|translate:'<strong>':'</strong>':'<span style="color:green"><strong>':'</strong></span>'}</li>
+ <li>{'UserCountry_HowToSetupGeoIP_Step4'|translate}</li>
+ </ul>
+ <p>&nbsp;</p>
+ {/if}
-<ul style="list-style:disc;margin-left:2em">
- <li>{'UserCountry_HowToSetupGeoIP_Step1'|translate:'<a href="http://geolite.maxmind.com/download/geoip/database/GeoLiteCity.dat.gz">':'</a>':'<a target="_blank" href="http://www.maxmind.com/?rId=piwik">':'</a>'}</li>
- <li>{'UserCountry_HowToSetupGeoIP_Step2'|translate:"'GeoLiteCity.dat'":'<strong>':'</strong>'}</li>
- <li>{'UserCountry_HowToSetupGeoIP_Step3'|translate:'<strong>':'</strong>':'<span style="color:green"><strong>':'</strong></span>'}</li>
- <li>{'UserCountry_HowToSetupGeoIP_Step4'|translate}</li>
-</ul>
+ <table class="adminTable locationProviderTable">
+ <tr>
+ <th>{'UserCountry_LocationProvider'|translate}</th>
+ <th>{'General_Description'|translate}</th>
+ <th>{'General_InfoFor'|translate:$thisIP}</th>
+ </tr>
+ {foreach from=$locationProviders key=id item=provider}
+ <tr>
+ <td width="140">
+ <p>
+ <input class="location-provider" name="location-provider" value="{$id}" type="radio" {if $currentProviderId eq $id}checked="checked"{/if}
+ id="provider_input_{$id}" {if $provider.status neq 1}disabled="disabled"{/if}/>
+ <label for="provider_input_{$id}">{$provider.title|translate}</label><br/>
+ <span class='loadingPiwik' style='display:none'><img src='./themes/default/images/loading-blue.gif'/></span>
+ <span class="ajaxSuccess" style='display:none'>{'General_Done'|translate}</span>
+ </p>
-<p>&nbsp;</p>
-{/if}
-
-<table class="adminTable locationProviderTable">
- <tr>
- <th>{'UserCountry_LocationProvider'|translate}</th>
- <th>{'General_Description'|translate}</th>
- <th>{'General_InfoFor'|translate:$thisIP}</th>
- </tr>
- {foreach from=$locationProviders key=id item=provider}
- <tr>
- <td width="140">
- <p>
- <input class="location-provider" name="location-provider" value="{$id}" type="radio" {if $currentProviderId eq $id}checked="checked"{/if} id="provider_input_{$id}" {if $provider.status neq 1}disabled="disabled"{/if}/>
- <label for="provider_input_{$id}">{$provider.title|translate}</label><br/>
- <span class='loadingPiwik' style='display:none'><img src='./themes/default/images/loading-blue.gif' /></span>
- <span class="ajaxSuccess" style='display:none'>{'General_Done'|translate}</span>
- </p>
- <p class="loc-provider-status">
- <strong><em>
- {if $provider.status eq 0}
- <span class="is-not-installed">{'General_NotInstalled'|translate}</span>
- {elseif $provider.status eq 1}
- <span class="is-installed">{'General_Installed'|translate}</span>
- {elseif $provider.status eq 2}
- <span class="is-broken">{'General_Broken'|translate}</span>
- {/if}
- </em></strong>
- </p>
- </td>
- <td>
- <p>{$provider.description|translate}</p>
- {if $provider.status neq 1 && isset($provider.install_docs)}
- <p>{$provider.install_docs}</p>
- {/if}
- </td>
- <td width="164">
- {if $provider.status eq 1}
- {capture assign=currentLocation}
- {if $thisIP neq '127.0.0.1'}
- {'UserCountry_CurrentLocationIntro'|translate}:
- <div style="text-align:left;">
- <br/>
- <span class='loadingPiwik' style='display:none;position:absolute'><img src='./themes/default/images/loading-blue.gif' /> {'General_Loading_js'|translate}</span>
- <span class='location'><strong><em>{$provider.location}</em></strong></span>
- </div>
- <div style="text-align:right;">
- <a href="#" class="refresh-loc" data-impl-id="{$id}"><em>{'Dashboard_Refresh_js'|translate}</em></a>
- </div>
- {else}
- {'UserCountry_CannotLocalizeLocalIP'|translate:$thisIP}
- {/if}
- {/capture}
- {$currentLocation|inlineHelp}
- {/if}
- {if isset($provider.statusMessage) && $provider.statusMessage}
- {capture assign=brokenReason}
- {if $provider.status eq 2}<strong><em>{'General_Error'|translate}:</strong></em> {/if}{$provider.statusMessage}
- {/capture}
- {$brokenReason|inlineHelp}
- {/if}
- {if isset($provider.extra_message) && $provider.extra_message}
- {capture assign=extraMessage}
- {$provider.extra_message}
- {/capture}
- <br/>
- {$extraMessage|inlineHelp}
- {/if}
- </td>
- {/foreach}
-</table>
+ <p class="loc-provider-status">
+ <strong><em>
+ {if $provider.status eq 0}
+ <span class="is-not-installed">{'General_NotInstalled'|translate}</span>
+ {elseif $provider.status eq 1}
+ <span class="is-installed">{'General_Installed'|translate}</span>
+ {elseif $provider.status eq 2}
+ <span class="is-broken">{'General_Broken'|translate}</span>
+ {/if}
+ </em></strong>
+ </p>
+ </td>
+ <td>
+ <p>{$provider.description|translate}</p>
+ {if $provider.status neq 1 && isset($provider.install_docs)}
+ <p>{$provider.install_docs}</p>
+ {/if}
+ </td>
+ <td width="164">
+ {if $provider.status eq 1}
+ {capture assign=currentLocation}
+ {if $thisIP neq '127.0.0.1'}
+ {'UserCountry_CurrentLocationIntro'|translate}:
+ <div style="text-align:left;">
+ <br/>
+ <span class='loadingPiwik' style='display:none;position:absolute'><img
+ src='./themes/default/images/loading-blue.gif'/> {'General_Loading_js'|translate}</span>
+ <span class='location'><strong><em>{$provider.location}</em></strong></span>
+ </div>
+ <div style="text-align:right;">
+ <a href="#" class="refresh-loc" data-impl-id="{$id}"><em>{'Dashboard_Refresh_js'|translate}</em></a>
+ </div>
+ {else}
+ {'UserCountry_CannotLocalizeLocalIP'|translate:$thisIP}
+ {/if}
+ {/capture}
+ {$currentLocation|inlineHelp}
+ {/if}
+ {if isset($provider.statusMessage) && $provider.statusMessage}
+ {capture assign=brokenReason}
+ {if $provider.status eq 2}<strong><em>{'General_Error'|translate}:</strong></em> {/if}{$provider.statusMessage}
+ {/capture}
+ {$brokenReason|inlineHelp}
+ {/if}
+ {if isset($provider.extra_message) && $provider.extra_message}
+ {capture assign=extraMessage}
+ {$provider.extra_message}
+ {/capture}
+ <br/>
+ {$extraMessage|inlineHelp}
+ {/if}
+ </td>
+ {/foreach}
+ </table>
</div>
{if !$geoIPDatabasesInstalled}
-<h2 id="geoip-db-mangement">{'UserCountry_GeoIPDatabases'|translate}</h2>
+ <h2 id="geoip-db-mangement">{'UserCountry_GeoIPDatabases'|translate}</h2>
{else}
-<h2 id="geoip-db-mangement">{'UserCountry_SetupAutomaticUpdatesOfGeoIP_js'|translate}</h2>
+ <h2 id="geoip-db-mangement">{'UserCountry_SetupAutomaticUpdatesOfGeoIP_js'|translate}</h2>
{/if}
{if $showGeoIPUpdateSection}
-<div id="manage-geoip-dbs" style="width:900px" class="adminTable">
+ <div id="manage-geoip-dbs" style="width:900px" class="adminTable">
-{if !$geoIPDatabasesInstalled}
-<div id="geoipdb-screen1">
- <p>{'UserCountry_PiwikNotManagingGeoIPDBs'|translate}</p>
- <div class="geoipdb-column-1">
- <p>{'UserCountry_IWantToDownloadFreeGeoIP'|translate}</p>
- <input type="button" class="submit" value="{'General_GetStarted'|translate}..." id="start-download-free-geoip"/>
- </div>
- <div class="geoipdb-column-2">
- <p>{'UserCountry_IPurchasedGeoIPDBs'|translate:'<a href="http://www.maxmind.com/en/geolocation_landing?rId=piwik">':'</a>'}</p>
- <input type="button" class="submit" value="{'General_GetStarted'|translate}..." id="start-automatic-update-geoip"/>
- </div>
-</div>
-<div id="geoipdb-screen2-download" style="display:none">
- <p class='loadingPiwik'><img src='./themes/default/images/loading-blue.gif' />{'UserCountry_DownloadingDb'|translate:"<a href=\"$geoLiteUrl\">GeoLiteCity.dat</a>"}...</p>
+ {if !$geoIPDatabasesInstalled}
+ <div id="geoipdb-screen1">
+ <p>{'UserCountry_PiwikNotManagingGeoIPDBs'|translate}</p>
+
+ <div class="geoipdb-column-1">
+ <p>{'UserCountry_IWantToDownloadFreeGeoIP'|translate}</p>
+ <input type="button" class="submit" value="{'General_GetStarted'|translate}..." id="start-download-free-geoip"/>
+ </div>
+ <div class="geoipdb-column-2">
+ <p>{'UserCountry_IPurchasedGeoIPDBs'|translate:'<a href="http://www.maxmind.com/en/geolocation_landing?rId=piwik">':'</a>'}</p>
+ <input type="button" class="submit" value="{'General_GetStarted'|translate}..." id="start-automatic-update-geoip"/>
+ </div>
+ </div>
+ <div id="geoipdb-screen2-download" style="display:none">
+ <p class='loadingPiwik'><img src='./themes/default/images/loading-blue.gif'/>
+ {'UserCountry_DownloadingDb'|translate:"<a href=\"$geoLiteUrl\">GeoLiteCity.dat</a>"}...</p>
<div id="geoip-download-progress"></div>
</div>
{/if}
diff --git a/plugins/UserCountry/templates/index.tpl b/plugins/UserCountry/templates/index.tpl
index ab29fa57ef..5de5bdaf5e 100644
--- a/plugins/UserCountry/templates/index.tpl
+++ b/plugins/UserCountry/templates/index.tpl
@@ -1,29 +1,28 @@
-
<div id="leftcolumn">
-{postEvent name="template_leftColumnUserCountry"}
+ {postEvent name="template_leftColumnUserCountry"}
- <h2>{'UserCountry_Continent'|translate}</h2>
- {$dataTableContinent}
+ <h2>{'UserCountry_Continent'|translate}</h2>
+ {$dataTableContinent}
- <div class="sparkline">
- {sparkline src=$urlSparklineCountries}
- {'UserCountry_DistinctCountries'|translate:"<strong>$numberDistinctCountries</strong>"}
- </div>
+ <div class="sparkline">
+ {sparkline src=$urlSparklineCountries}
+ {'UserCountry_DistinctCountries'|translate:"<strong>$numberDistinctCountries</strong>"}
+ </div>
-{postEvent name="template_footerUserCountry"}
+ {postEvent name="template_footerUserCountry"}
</div>
<div id="rightcolumn">
- <h2>{'UserCountry_Country'|translate}</h2>
- {$dataTableCountry}
+ <h2>{'UserCountry_Country'|translate}</h2>
+ {$dataTableCountry}
- <h2>{'UserCountry_Region'|translate}</h2>
- {$dataTableRegion}
+ <h2>{'UserCountry_Region'|translate}</h2>
+ {$dataTableRegion}
- <h2>{'UserCountry_City'|translate}</h2>
- {$dataTableCity}
+ <h2>{'UserCountry_City'|translate}</h2>
+ {$dataTableCity}
</div>
diff --git a/plugins/UserCountry/templates/styles.css b/plugins/UserCountry/templates/styles.css
index 57cf159777..50c26af3a3 100755
--- a/plugins/UserCountry/templates/styles.css
+++ b/plugins/UserCountry/templates/styles.css
@@ -1,72 +1,75 @@
.locationProviderTable label {
- font-size: 1.2em;
+ font-size: 1.2em;
}
input.location-provider {
- cursor:pointer;
+ cursor: pointer;
}
span.is-installed {
- color:green;
+ color: green;
}
span.is-broken {
- color:red;
+ color: red;
}
.loc-provider-status {
- margin-left:.5em;
+ margin-left: .5em;
}
#manage-geoip-dbs {
- height: 20em;
+ height: 20em;
}
-#geoipdb-screen1, #geoipdb-screen2-download,#geoipdb-update-info {
- width:900px;
+#geoipdb-screen1, #geoipdb-screen2-download, #geoipdb-update-info {
+ width: 900px;
}
-#geoipdb-update-info tr input[type="text"],#geoipdb-screen2-update tr input[type="text"] {
- width: 90%;
+#geoipdb-update-info tr input[type="text"], #geoipdb-screen2-update tr input[type="text"] {
+ width: 90%;
}
#geoipdb-screen1>div {
- display: inline-block;
- vertical-align:top;
+ display: inline-block;
+ vertical-align: top;
}
#geoipdb-screen1>div>p {
- font-size: 2em;
- height: 4em;
+ font-size: 2em;
+ height: 4em;
}
-.geoipdb-column-1,.geoipdb-column-2 {
- width:396px;
+.geoipdb-column-1, .geoipdb-column-2 {
+ width: 396px;
}
+
.geoipdb-column-1 {
- margin-right: 50px;
+ margin-right: 50px;
}
+
.geoipdb-column-2 {
- border-left: solid #999 1px;
- padding-left: 50px;
+ border-left: solid #999 1px;
+ padding-left: 50px;
}
+
.geoipdb-column-1>p {
- padding-left: 20px;
+ padding-left: 20px;
}
.error {
- font-weight:bold;
- color:red;
- padding:4px 8px 4px 8px;
+ font-weight: bold;
+ color: red;
+ padding: 4px 8px 4px 8px;
}
#geoip-updater-progressbar-label {
- float: left;
- margin: -24px 24px;
+ float: left;
+ margin: -24px 24px;
}
-#geoip-progressbar-container,#geoipdb-update-info-error {
- margin: 22px 24px;
- display:inline-block;
+#geoip-progressbar-container, #geoipdb-update-info-error {
+ margin: 22px 24px;
+ display: inline-block;
}
diff --git a/plugins/UserCountry/templates/updaterSetup.tpl b/plugins/UserCountry/templates/updaterSetup.tpl
index 7960526b2e..7f29616de5 100755
--- a/plugins/UserCountry/templates/updaterSetup.tpl
+++ b/plugins/UserCountry/templates/updaterSetup.tpl
@@ -1,6 +1,7 @@
<div id="geoipdb-update-info" {if !$geoIPDatabasesInstalled}style="display:none"{/if}>
- <p>{'UserCountry_GeoIPUpdaterInstructions'|translate:'<a href="http://www.maxmind.com/en/download_files?rId=piwik" _target="blank">':'</a>':'<a href="http://www.maxmind.com/?rId=piwik">':'</a>'}<br/><br/>
- {'UserCountry_GeoLiteCityLink'|translate:"<a href=\"$geoLiteUrl\">":$geoLiteUrl:'</a>'}
+ <p>{'UserCountry_GeoIPUpdaterInstructions'|translate:'<a href="http://www.maxmind.com/en/download_files?rId=piwik" _target="blank">':'</a>':'<a href="http://www.maxmind.com/?rId=piwik">':'</a>'}
+ <br/><br/>
+{'UserCountry_GeoLiteCityLink'|translate:"<a href=\"$geoLiteUrl\">":$geoLiteUrl:'</a>'}
{if $geoIPDatabasesInstalled}
<br/><br/>{'UserCountry_GeoIPUpdaterIntro'|translate}:
{/if}
diff --git a/plugins/UserCountryMap/Controller.php b/plugins/UserCountryMap/Controller.php
index 0a18dc2027..b0ca11de21 100644
--- a/plugins/UserCountryMap/Controller.php
+++ b/plugins/UserCountryMap/Controller.php
@@ -16,202 +16,200 @@
class Piwik_UserCountryMap_Controller extends Piwik_Controller
{
- // By default plot up to the last 30 days of visitors on the map, for low traffic sites
- const REAL_TIME_WINDOW = 'last30';
-
- public function visitorMap()
- {
- $this->checkUserCountryPluginEnabled();
-
- $idSite = Piwik_Common::getRequestVar('idSite', 1, 'int');
- Piwik::checkUserHasViewAccess($idSite);
-
- $period = Piwik_Common::getRequestVar('period');
- $date = Piwik_Common::getRequestVar('date');
- $token_auth = Piwik::getCurrentUserTokenAuth();
-
- $view = Piwik_View::factory('visitor-map');
-
- // request visits summary
- $request = new Piwik_API_Request(
- 'method=VisitsSummary.get&format=PHP'
- . '&idSite=' . $idSite
- . '&period=' . $period
- . '&date=' . $date
- . '&token_auth=' . $token_auth
- . '&filter_limit=-1'
- );
- $config = array();
- $config['visitsSummary'] = unserialize($request->process());
- $config['countryDataUrl'] = $this->_report('UserCountry', 'getCountry',
- $idSite, $period, $date, $token_auth);
- $config['regionDataUrl'] = $this->_report('UserCountry', 'getRegion',
- $idSite, $period, $date, $token_auth, true);
- $config['cityDataUrl'] = $this->_report('UserCountry', 'getCity',
- $idSite, $period, $date, $token_auth, true);
- $config['countrySummaryUrl'] = $this->getApiRequestUrl('VisitsSummary', 'get',
- $idSite, $period, $date, $token_auth, true);
- $view->defaultMetric = 'nb_visits';
-
- // some translations
- $view->localeJSON = Piwik_Common::json_encode(array(
- 'nb_visits' => Piwik_Translate('VisitsSummary_NbVisits'),
- 'one_visit' => Piwik_Translate('General_OneVisit'),
- 'no_visit' => Piwik_Translate('UserCountryMap_NoVisit'),
- 'nb_actions' => Piwik_Translate('VisitsSummary_NbActionsDescription'),
- 'nb_actions_per_visit' => Piwik_Translate('VisitsSummary_NbActionsPerVisit'),
- 'bounce_rate' => Piwik_Translate('VisitsSummary_NbVisitsBounced'),
- 'avg_time_on_site' => Piwik_Translate('VisitsSummary_AverageVisitDuration'),
- 'and_n_others' => Piwik_Translate('UserCountryMap_AndNOthers'),
- 'no_data' => Piwik_Translate('CoreHome_ThereIsNoDataForThisReport')
- ));
-
- // template for ajax requests
- $view->reqParamsJSON = Piwik_Common::json_encode(array(
- 'period' => $period,
- 'idSite' => $idSite,
- 'date' => $date,
- 'token_auth' => $token_auth,
- 'format' => 'json',
- 'segment' => Piwik_Common::unsanitizeInputValue(Piwik_Common::getRequestVar('segment', '')),
- 'showRawMetrics' => 1,
- 'enable_filter_excludelowpop' => 1,
- 'filter_excludelowpop_value' => -1
- ));
- $view->metrics = $config['metrics'] = $this->getMetrics($idSite, $period, $date, $token_auth);
- $config['svgBasePath'] = 'plugins/UserCountryMap/svg/';
- $config['mapCssPath'] = 'plugins/UserCountryMap/css/map.css';
- $view->config = Piwik_Common::json_encode($config);
- $view->noData = empty($config['visitsSummary']['nb_visits']);
-
- echo $view->render();
- }
-
- /**
- * Used to build the report Visitor > Real time map
- */
- public function realtimeWorldMap()
- {
- return $this->realtimeMap($standalone = true);
- }
-
- /**
- * @param bool $standalone When set to true, the Top controls will be hidden to provide better full screen view
- */
- public function realtimeMap($standalone = false)
- {
- $this->checkUserCountryPluginEnabled();
-
- $idSite = Piwik_Common::getRequestVar('idSite', 1, 'int');
- Piwik::checkUserHasViewAccess($idSite);
-
- $token_auth = Piwik::getCurrentUserTokenAuth();
- $view = Piwik_View::factory('realtime-map');
-
- $view->mapIsStandaloneNotWidget = $standalone;
-
- $view->metrics = $this->getMetrics($idSite, 'range', self::REAL_TIME_WINDOW, $token_auth);
- $view->defaultMetric = 'nb_visits';
- $view->liveRefreshAfterMs = (int)Piwik_Config::getInstance()->General['live_widget_refresh_after_seconds'] * 1000;
-
- $goals = Piwik_Goals_API::getInstance()->getGoals($idSite);
- $site = new Piwik_Site($idSite);
- $view->hasGoals = !empty($goals) || $site->isEcommerceEnabled() ? 'true' : 'false';
-
- // maximum number of visits to be displayed in the map
- $view->maxVisits = Piwik_Common::getRequestVar('format_limit', 100, 'int');
-
- // some translations
- $view->localeJSON = json_encode(array(
- 'nb_actions' => Piwik_Translate('VisitsSummary_NbActionsDescription'),
- 'local_time' => Piwik_Translate('VisitTime_ColumnLocalTime'),
- 'from' => Piwik_Translate('General_FromReferrer'),
- 'seconds' => Piwik_Translate('UserCountryMap_Seconds'),
- 'seconds_ago' => Piwik_Translate('UserCountryMap_SecondsAgo'),
- 'minutes' => Piwik_Translate('UserCountryMap_Minutes'),
- 'minutes_ago' => Piwik_Translate('UserCountryMap_MinutesAgo'),
- 'hours' => Piwik_Translate('UserCountryMap_Hours'),
- 'hours_ago' => Piwik_Translate('UserCountryMap_HoursAgo'),
- 'days_ago' => Piwik_Translate('UserCountryMap_DaysAgo'),
- 'actions' => Piwik_Translate('VisitsSummary_NbPageviewsDescription'),
- 'searches' => Piwik_Translate('UserCountryMap_Searches'),
- 'goal_conversions' => Piwik_Translate('UserCountryMap_GoalConversions'),
- ));
-
- $view->reqParamsJSON = json_encode(array(
- 'period' => 'range',
- 'idSite' => $idSite,
- 'date' => self::REAL_TIME_WINDOW,
- 'token_auth' => $token_auth,
- 'format' => 'json',
- 'segment' => Piwik_Common::unsanitizeInputValue(Piwik_Common::getRequestVar('segment', '')),
- 'showRawMetrics' => 1
- ));
-
- echo $view->render();
- }
-
- private function checkUserCountryPluginEnabled()
- {
- if (!Piwik_PluginsManager::getInstance()->isPluginActivated('UserCountry')) {
- throw new Exception(Piwik_Translate('General_Required', 'Plugin UserCountry'));
- }
- }
-
- private function getMetrics($idSite, $period, $date, $token_auth)
- {
- $request = new Piwik_API_Request(
- 'method=API.getMetadata&format=PHP'
- . '&apiModule=UserCountry&apiAction=getCountry'
- . '&idSite=' . $idSite
- . '&period=' . $period
- . '&date=' . $date
- . '&token_auth=' . $token_auth
- . '&filter_limit=-1'
- );
- $metaData = $request->process();
-
- $metrics = array();
- foreach ($metaData[0]['metrics'] as $id => $val)
- {
- if (Piwik_Common::getRequestVar('period') == 'day' || $id != 'nb_uniq_visitors') {
- $metrics[] = array($id, $val);
- }
- }
- foreach ($metaData[0]['processedMetrics'] as $id => $val)
- {
- $metrics[] = array($id, $val);
- }
- return $metrics;
- }
-
- private function getApiRequestUrl($module, $action, $idSite, $period, $date, $token_auth, $filter_by_country = false)
- {
- // use processed reports
- $url = "?module=" . $module
- . "&method=".$module.".".$action."&format=JSON"
- . "&idSite=" . $idSite
- . "&period=" . $period
- . "&date=" . $date
- . "&token_auth=" . $token_auth
- . "&segment=" . Piwik_Common::unsanitizeInputValue(Piwik_Common::getRequestVar('segment', ''))
- . "&enable_filter_excludelowpop=1"
- . "&showRawMetrics=1";
-
- if ($filter_by_country) {
- $url .= "&filter_column=country"
- . "&filter_sort_column=nb_visits"
- . "&filter_limit=-1"
- . "&filter_pattern=";
- } else {
- $url .= "&filter_limit=-1";
- }
- return $url;
- }
-
- private function _report($module, $action, $idSite, $period, $date, $token_auth, $filter_by_country = false)
- {
- return $this->getApiRequestUrl('API', 'getProcessedReport&apiModule='.$module.'&apiAction='.$action, $idSite, $period, $date, $token_auth, $filter_by_country);
- }
+ // By default plot up to the last 30 days of visitors on the map, for low traffic sites
+ const REAL_TIME_WINDOW = 'last30';
+
+ public function visitorMap()
+ {
+ $this->checkUserCountryPluginEnabled();
+
+ $idSite = Piwik_Common::getRequestVar('idSite', 1, 'int');
+ Piwik::checkUserHasViewAccess($idSite);
+
+ $period = Piwik_Common::getRequestVar('period');
+ $date = Piwik_Common::getRequestVar('date');
+ $token_auth = Piwik::getCurrentUserTokenAuth();
+
+ $view = Piwik_View::factory('visitor-map');
+
+ // request visits summary
+ $request = new Piwik_API_Request(
+ 'method=VisitsSummary.get&format=PHP'
+ . '&idSite=' . $idSite
+ . '&period=' . $period
+ . '&date=' . $date
+ . '&token_auth=' . $token_auth
+ . '&filter_limit=-1'
+ );
+ $config = array();
+ $config['visitsSummary'] = unserialize($request->process());
+ $config['countryDataUrl'] = $this->_report('UserCountry', 'getCountry',
+ $idSite, $period, $date, $token_auth);
+ $config['regionDataUrl'] = $this->_report('UserCountry', 'getRegion',
+ $idSite, $period, $date, $token_auth, true);
+ $config['cityDataUrl'] = $this->_report('UserCountry', 'getCity',
+ $idSite, $period, $date, $token_auth, true);
+ $config['countrySummaryUrl'] = $this->getApiRequestUrl('VisitsSummary', 'get',
+ $idSite, $period, $date, $token_auth, true);
+ $view->defaultMetric = 'nb_visits';
+
+ // some translations
+ $view->localeJSON = Piwik_Common::json_encode(array(
+ 'nb_visits' => Piwik_Translate('VisitsSummary_NbVisits'),
+ 'one_visit' => Piwik_Translate('General_OneVisit'),
+ 'no_visit' => Piwik_Translate('UserCountryMap_NoVisit'),
+ 'nb_actions' => Piwik_Translate('VisitsSummary_NbActionsDescription'),
+ 'nb_actions_per_visit' => Piwik_Translate('VisitsSummary_NbActionsPerVisit'),
+ 'bounce_rate' => Piwik_Translate('VisitsSummary_NbVisitsBounced'),
+ 'avg_time_on_site' => Piwik_Translate('VisitsSummary_AverageVisitDuration'),
+ 'and_n_others' => Piwik_Translate('UserCountryMap_AndNOthers'),
+ 'no_data' => Piwik_Translate('CoreHome_ThereIsNoDataForThisReport')
+ ));
+
+ // template for ajax requests
+ $view->reqParamsJSON = Piwik_Common::json_encode(array(
+ 'period' => $period,
+ 'idSite' => $idSite,
+ 'date' => $date,
+ 'token_auth' => $token_auth,
+ 'format' => 'json',
+ 'segment' => Piwik_Common::unsanitizeInputValue(Piwik_Common::getRequestVar('segment', '')),
+ 'showRawMetrics' => 1,
+ 'enable_filter_excludelowpop' => 1,
+ 'filter_excludelowpop_value' => -1
+ ));
+ $view->metrics = $config['metrics'] = $this->getMetrics($idSite, $period, $date, $token_auth);
+ $config['svgBasePath'] = 'plugins/UserCountryMap/svg/';
+ $config['mapCssPath'] = 'plugins/UserCountryMap/css/map.css';
+ $view->config = Piwik_Common::json_encode($config);
+ $view->noData = empty($config['visitsSummary']['nb_visits']);
+
+ echo $view->render();
+ }
+
+ /**
+ * Used to build the report Visitor > Real time map
+ */
+ public function realtimeWorldMap()
+ {
+ return $this->realtimeMap($standalone = true);
+ }
+
+ /**
+ * @param bool $standalone When set to true, the Top controls will be hidden to provide better full screen view
+ */
+ public function realtimeMap($standalone = false)
+ {
+ $this->checkUserCountryPluginEnabled();
+
+ $idSite = Piwik_Common::getRequestVar('idSite', 1, 'int');
+ Piwik::checkUserHasViewAccess($idSite);
+
+ $token_auth = Piwik::getCurrentUserTokenAuth();
+ $view = Piwik_View::factory('realtime-map');
+
+ $view->mapIsStandaloneNotWidget = $standalone;
+
+ $view->metrics = $this->getMetrics($idSite, 'range', self::REAL_TIME_WINDOW, $token_auth);
+ $view->defaultMetric = 'nb_visits';
+ $view->liveRefreshAfterMs = (int)Piwik_Config::getInstance()->General['live_widget_refresh_after_seconds'] * 1000;
+
+ $goals = Piwik_Goals_API::getInstance()->getGoals($idSite);
+ $site = new Piwik_Site($idSite);
+ $view->hasGoals = !empty($goals) || $site->isEcommerceEnabled() ? 'true' : 'false';
+
+ // maximum number of visits to be displayed in the map
+ $view->maxVisits = Piwik_Common::getRequestVar('format_limit', 100, 'int');
+
+ // some translations
+ $view->localeJSON = json_encode(array(
+ 'nb_actions' => Piwik_Translate('VisitsSummary_NbActionsDescription'),
+ 'local_time' => Piwik_Translate('VisitTime_ColumnLocalTime'),
+ 'from' => Piwik_Translate('General_FromReferrer'),
+ 'seconds' => Piwik_Translate('UserCountryMap_Seconds'),
+ 'seconds_ago' => Piwik_Translate('UserCountryMap_SecondsAgo'),
+ 'minutes' => Piwik_Translate('UserCountryMap_Minutes'),
+ 'minutes_ago' => Piwik_Translate('UserCountryMap_MinutesAgo'),
+ 'hours' => Piwik_Translate('UserCountryMap_Hours'),
+ 'hours_ago' => Piwik_Translate('UserCountryMap_HoursAgo'),
+ 'days_ago' => Piwik_Translate('UserCountryMap_DaysAgo'),
+ 'actions' => Piwik_Translate('VisitsSummary_NbPageviewsDescription'),
+ 'searches' => Piwik_Translate('UserCountryMap_Searches'),
+ 'goal_conversions' => Piwik_Translate('UserCountryMap_GoalConversions'),
+ ));
+
+ $view->reqParamsJSON = json_encode(array(
+ 'period' => 'range',
+ 'idSite' => $idSite,
+ 'date' => self::REAL_TIME_WINDOW,
+ 'token_auth' => $token_auth,
+ 'format' => 'json',
+ 'segment' => Piwik_Common::unsanitizeInputValue(Piwik_Common::getRequestVar('segment', '')),
+ 'showRawMetrics' => 1
+ ));
+
+ echo $view->render();
+ }
+
+ private function checkUserCountryPluginEnabled()
+ {
+ if (!Piwik_PluginsManager::getInstance()->isPluginActivated('UserCountry')) {
+ throw new Exception(Piwik_Translate('General_Required', 'Plugin UserCountry'));
+ }
+ }
+
+ private function getMetrics($idSite, $period, $date, $token_auth)
+ {
+ $request = new Piwik_API_Request(
+ 'method=API.getMetadata&format=PHP'
+ . '&apiModule=UserCountry&apiAction=getCountry'
+ . '&idSite=' . $idSite
+ . '&period=' . $period
+ . '&date=' . $date
+ . '&token_auth=' . $token_auth
+ . '&filter_limit=-1'
+ );
+ $metaData = $request->process();
+
+ $metrics = array();
+ foreach ($metaData[0]['metrics'] as $id => $val) {
+ if (Piwik_Common::getRequestVar('period') == 'day' || $id != 'nb_uniq_visitors') {
+ $metrics[] = array($id, $val);
+ }
+ }
+ foreach ($metaData[0]['processedMetrics'] as $id => $val) {
+ $metrics[] = array($id, $val);
+ }
+ return $metrics;
+ }
+
+ private function getApiRequestUrl($module, $action, $idSite, $period, $date, $token_auth, $filter_by_country = false)
+ {
+ // use processed reports
+ $url = "?module=" . $module
+ . "&method=" . $module . "." . $action . "&format=JSON"
+ . "&idSite=" . $idSite
+ . "&period=" . $period
+ . "&date=" . $date
+ . "&token_auth=" . $token_auth
+ . "&segment=" . Piwik_Common::unsanitizeInputValue(Piwik_Common::getRequestVar('segment', ''))
+ . "&enable_filter_excludelowpop=1"
+ . "&showRawMetrics=1";
+
+ if ($filter_by_country) {
+ $url .= "&filter_column=country"
+ . "&filter_sort_column=nb_visits"
+ . "&filter_limit=-1"
+ . "&filter_pattern=";
+ } else {
+ $url .= "&filter_limit=-1";
+ }
+ return $url;
+ }
+
+ private function _report($module, $action, $idSite, $period, $date, $token_auth, $filter_by_country = false)
+ {
+ return $this->getApiRequestUrl('API', 'getProcessedReport&apiModule=' . $module . '&apiAction=' . $action, $idSite, $period, $date, $token_auth, $filter_by_country);
+ }
}
diff --git a/plugins/UserCountryMap/UserCountryMap.php b/plugins/UserCountryMap/UserCountryMap.php
index c50d520e2c..9b5fb337ee 100644
--- a/plugins/UserCountryMap/UserCountryMap.php
+++ b/plugins/UserCountryMap/UserCountryMap.php
@@ -18,11 +18,11 @@ class Piwik_UserCountryMap extends Piwik_Plugin
public function getInformation()
{
return array(
- 'name' => 'User Country Map',
- 'description' => 'This plugin provides the widgets Visitor Map and Real-time Map. Note: Requires the UserCountry plugin enabled.',
- 'author' => 'Piwik',
+ 'name' => 'User Country Map',
+ 'description' => 'This plugin provides the widgets Visitor Map and Real-time Map. Note: Requires the UserCountry plugin enabled.',
+ 'author' => 'Piwik',
'author_homepage' => 'http://piwik.org/',
- 'version' => Piwik_Version::VERSION
+ 'version' => Piwik_Version::VERSION
);
}
@@ -31,37 +31,37 @@ class Piwik_UserCountryMap extends Piwik_Plugin
Piwik_AddWidget('General_Visitors', Piwik_Translate('UserCountryMap_VisitorMap'), 'UserCountryMap', 'visitorMap');
Piwik_AddWidget('Live!', Piwik_Translate('UserCountryMap_RealTimeMap'), 'UserCountryMap', 'realtimeMap');
- Piwik_AddAction('template_leftColumnUserCountry', array('Piwik_UserCountryMap', 'insertMapInLocationReport'));
+ Piwik_AddAction('template_leftColumnUserCountry', array('Piwik_UserCountryMap', 'insertMapInLocationReport'));
}
- static public function insertMapInLocationReport($notification)
- {
- $out =& $notification->getNotificationObject();
- $out = '<h2>'.Piwik_Translate('UserCountryMap_VisitorMap').'</h2>';
- $out .= Piwik_FrontController::getInstance()->fetchDispatch('UserCountryMap','visitorMap');
- }
+ static public function insertMapInLocationReport($notification)
+ {
+ $out =& $notification->getNotificationObject();
+ $out = '<h2>' . Piwik_Translate('UserCountryMap_VisitorMap') . '</h2>';
+ $out .= Piwik_FrontController::getInstance()->fetchDispatch('UserCountryMap', 'visitorMap');
+ }
- public function getListHooksRegistered()
+ public function getListHooksRegistered()
{
$hooks = array(
- 'AssetManager.getJsFiles' => 'getJsFiles',
+ 'AssetManager.getJsFiles' => 'getJsFiles',
'AssetManager.getCssFiles' => 'getCssFiles',
- 'Menu.add' => 'addMenu',
+ 'Menu.add' => 'addMenu',
);
return $hooks;
}
- function addMenu()
- {
- Piwik_AddMenu('General_Visitors', 'UserCountryMap_RealTimeMap', array('module' => 'UserCountryMap', 'action' => 'realtimeWorldMap'), true, $order = 70);
- }
+ function addMenu()
+ {
+ Piwik_AddMenu('General_Visitors', 'UserCountryMap_RealTimeMap', array('module' => 'UserCountryMap', 'action' => 'realtimeWorldMap'), true, $order = 70);
+ }
- /**
+ /**
* @param Piwik_Event_Notification $notification notification object
*/
public function getJsFiles($notification)
{
- $jsFiles = &$notification->getNotificationObject();
+ $jsFiles = & $notification->getNotificationObject();
$jsFiles[] = "plugins/UserCountryMap/js/vendor/raphael-min.js";
$jsFiles[] = "plugins/UserCountryMap/js/vendor/jquery.qtip.min.js";
$jsFiles[] = "plugins/UserCountryMap/js/vendor/kartograph.min.js";
@@ -72,7 +72,7 @@ class Piwik_UserCountryMap extends Piwik_Plugin
public function getCssFiles($notification)
{
- $cssFiles = &$notification->getNotificationObject();
+ $cssFiles = & $notification->getNotificationObject();
$cssFiles[] = "plugins/UserCountryMap/css/qtip.css";
$cssFiles[] = "plugins/UserCountryMap/css/visitor-map.css";
$cssFiles[] = "plugins/UserCountryMap/css/realtime-map.css";
diff --git a/plugins/UserCountryMap/css/map.css b/plugins/UserCountryMap/css/map.css
index 7cc4a480aa..400e28384d 100644
--- a/plugins/UserCountryMap/css/map.css
+++ b/plugins/UserCountryMap/css/map.css
@@ -34,20 +34,19 @@
stroke: #fff;
fill: none;
}
+
.UserCountryMap_map svg .regionBG-fill {
stroke: #555;
stroke-width: 0.2px;
fill: #F6F5F3;
}
-
.UserCountryMap_map svg .regionBG-3 {
stroke: #ccc;
fill: #F2F1ED;
stroke-width: 1px;
}
-
.UserCountryMap_map svg .countryBG {
stroke: #fff;
fill: #fff;
@@ -67,7 +66,7 @@
.UserCountryMap_map svg .countryLabelBg {
font-weight: bold;
font-size: 10px;
- font-family: Arial,Verdana,Helvetica,sans-serif;
+ font-family: Arial, Verdana, Helvetica, sans-serif;
fill: #F6F5F3;
stroke: #F6F5F3;
stroke-width: 3px;
@@ -78,7 +77,7 @@
.UserCountryMap_map svg .countryLabel {
font-weight: bold;
font-size: 10px;
- font-family: Arial,Verdana,Helvetica,sans-serif;
+ font-family: Arial, Verdana, Helvetica, sans-serif;
fill: #808888;
}
diff --git a/plugins/UserCountryMap/css/qtip.css b/plugins/UserCountryMap/css/qtip.css
index 6b69dcb0c0..05057adc22 100644
--- a/plugins/UserCountryMap/css/qtip.css
+++ b/plugins/UserCountryMap/css/qtip.css
@@ -13,136 +13,140 @@
*/
/* Core qTip styles */
-.ui-tooltip, .qtip{
- position: absolute;
- left: -28000px;
- top: -28000px;
- display: none;
-
- max-width: 280px;
- min-width: 50px;
-
- font-size: 12px;
- line-height: 14px;
-
- z-index: 15000;
-}
-
- /* Fluid class for determining actual width in IE */
- .ui-tooltip-fluid{
- display: block;
- visibility: hidden;
- position: static !important;
- float: left !important;
- }
-
- .ui-tooltip-content{
- position: relative;
- padding: 5px 9px;
- overflow: hidden;
-
- border-width: 1px;
- border-style: solid;
-
- text-align: left;
- word-wrap: break-word;
- overflow: hidden;
- color: #444;
- }
-
- .ui-tooltip-titlebar{
- position: relative;
- min-height: 14px;
- padding: 5px 35px 0px 10px;
- overflow: hidden;
-
- border-width: 1px 1px 0;
- border-style: solid;
-
- font-weight: bold;
-
- }
-
- .ui-tooltip-titlebar + .ui-tooltip-content{ border-top-width: 0px !important; }
-
- /*! Default close button class */
- .ui-tooltip-titlebar .ui-state-default{
- position: absolute;
- right: 4px;
- top: 50%;
- margin-top: -9px;
-
- cursor: pointer;
- outline: medium none;
-
- border-width: 1px;
- border-style: solid;
- }
-
- * html .ui-tooltip-titlebar .ui-state-default{ top: 16px; } /* IE fix */
-
- .ui-tooltip-titlebar .ui-icon,
- .ui-tooltip-icon .ui-icon{
- display: block;
- text-indent: -1000em;
- }
-
- .ui-tooltip-icon, .ui-tooltip-icon .ui-icon{
- -moz-border-radius: 3px;
- -webkit-border-radius: 3px;
- border-radius: 3px;
- }
-
- .ui-tooltip-icon .ui-icon{
- width: 18px;
- height: 14px;
-
- text-align: center;
-
- color: inherit;
- background: transparent none no-repeat -100em -100em;
- }
+.ui-tooltip, .qtip {
+ position: absolute;
+ left: -28000px;
+ top: -28000px;
+ display: none;
+ max-width: 280px;
+ min-width: 50px;
+
+ font-size: 12px;
+ line-height: 14px;
+
+ z-index: 15000;
+}
+
+/* Fluid class for determining actual width in IE */
+.ui-tooltip-fluid {
+ display: block;
+ visibility: hidden;
+ position: static !important;
+ float: left !important;
+}
+
+.ui-tooltip-content {
+ position: relative;
+ padding: 5px 9px;
+ overflow: hidden;
+
+ border-width: 1px;
+ border-style: solid;
+
+ text-align: left;
+ word-wrap: break-word;
+ overflow: hidden;
+ color: #444;
+}
+
+.ui-tooltip-titlebar {
+ position: relative;
+ min-height: 14px;
+ padding: 5px 35px 0px 10px;
+ overflow: hidden;
+
+ border-width: 1px 1px 0;
+ border-style: solid;
+
+ font-weight: bold;
+
+}
+
+.ui-tooltip-titlebar + .ui-tooltip-content {
+ border-top-width: 0px !important;
+}
+
+/*! Default close button class */
+.ui-tooltip-titlebar .ui-state-default {
+ position: absolute;
+ right: 4px;
+ top: 50%;
+ margin-top: -9px;
+
+ cursor: pointer;
+ outline: medium none;
+
+ border-width: 1px;
+ border-style: solid;
+}
+
+* html .ui-tooltip-titlebar .ui-state-default {
+ top: 16px;
+}
+
+/* IE fix */
+
+.ui-tooltip-titlebar .ui-icon,
+.ui-tooltip-icon .ui-icon {
+ display: block;
+ text-indent: -1000em;
+}
+
+.ui-tooltip-icon, .ui-tooltip-icon .ui-icon {
+ -moz-border-radius: 3px;
+ -webkit-border-radius: 3px;
+ border-radius: 3px;
+}
+
+.ui-tooltip-icon .ui-icon {
+ width: 18px;
+ height: 14px;
+
+ text-align: center;
+
+ color: inherit;
+ background: transparent none no-repeat -100em -100em;
+}
/* Applied to 'focused' tooltips e.g. most recently displayed/interacted with */
-.ui-tooltip-focus{
+.ui-tooltip-focus {
}
/* Applied on hover of tooltips i.e. added/removed on mouseenter/mouseleave respectively */
-.ui-tooltip-hover{
-
-}
+.ui-tooltip-hover {
+}
/*! Default tooltip style */
.ui-tooltip-default {
-
+
}
.ui-tooltip-default .ui-tooltip-titlebar,
-.ui-tooltip-default .ui-tooltip-content{
- border-color: #E7E8E7;
- background-color: #F7F7F7;
- box-shadow: 1px 1px 3px rgba(0,0,0,.5);
+.ui-tooltip-default .ui-tooltip-content {
+ border-color: #E7E8E7;
+ background-color: #F7F7F7;
+ box-shadow: 1px 1px 3px rgba(0, 0, 0, .5);
}
-.ui-tooltip-default .ui-tooltip-titlebar .ui-state-hover{
- border-color: #E7E8E7;
- color: #111;
+.ui-tooltip-default .ui-tooltip-titlebar .ui-state-hover {
+ border-color: #E7E8E7;
+ color: #111;
}
.ui-tooltip-titlebar {
- border-bottom: 0;
+ border-bottom: 0;
}
.ui-tooltip-content {
- border-radius: 5px;
+ border-radius: 5px;
}
.ui-tooltip-content h3 {
- font-weight: bold;
- color: #7E7363;
- font-size: 13px;
- margin: 0 0 5px;
+ font-weight: bold;
+ color: #7E7363;
+ font-size: 13px;
+ margin: 0 0 5px;
} \ No newline at end of file
diff --git a/plugins/UserCountryMap/css/realtime-map.css b/plugins/UserCountryMap/css/realtime-map.css
index ba54ce63aa..c9d3aa47b0 100644
--- a/plugins/UserCountryMap/css/realtime-map.css
+++ b/plugins/UserCountryMap/css/realtime-map.css
@@ -2,13 +2,15 @@
.dataTableFooterIcons .inactiveIcon:hover {
background-color: #F2F1ED;
}
+
.dataTableFooterIcons .inactiveIcon {
cursor: default;
}
+
.dataTableFooterIcons .inactiveIcon img {
opacity: 0.3;
-moz-opacity: 0.3;
- filter:alpha(opacity=3);
+ filter: alpha(opacity=3);
}
#RealTimeMap-black {
@@ -22,40 +24,39 @@
}
#RealTimeMap .loadingPiwik {
- position: absolute!important;
- top: 42%!important;
- right: 10px!important;
- left: 10px!important;
- z-index: 10002!important;
+ position: absolute !important;
+ top: 42% !important;
+ right: 10px !important;
+ left: 10px !important;
+ z-index: 10002 !important;
display: block;
color: #000;
- vertical-align: middle!important;
+ vertical-align: middle !important;
text-align: center;
text-shadow: 0 0 5px #fff;
}
-
.tableIcon.inactiveIcon {
color: #99a;
}
.RealTimeMap-overlay,
.RealTimeMap-tooltip {
- display:block;
+ display: block;
position: absolute;
- z-index:1000;
+ z-index: 1000;
}
.RealTimeMap-overlay .content,
.RealTimeMap-tooltip .content {
- padding:5px;
- border-radius:3px;
- background:rgba(255,255,255,0.9);
+ padding: 5px;
+ border-radius: 3px;
+ background: rgba(255, 255, 255, 0.9);
}
.RealTimeMap-title {
top: 5px;
- left:5px;
+ left: 5px;
}
.RealTimeMap-legend {
@@ -63,12 +64,14 @@
font-size: 9px;
bottom: 40px;
}
+
.RealTimeMap-info {
left: 5px;
font-size: 11px;
bottom: 60px;
max-width: 42%;
}
+
.RealTimeMap-info-btn {
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAA3NCSVQICAjb4U/gAAAAOVBMVEX///8AAAAAAABXV1dSUlKsrKzExMTd3d3V1dXp6end3d3p6enz8/P7+/v39/f///+vqZ6oopWUjH2LPulWAAAAE3RSTlMAESIzM2Z3mZmqqrvd7u7/////UUgTXgAAAAlwSFlzAAALEgAACxIB0t1+/AAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTNXG14zYAAAAYdEVYdENyZWF0aW9uIFRpbWUAMDMuMDEuMjAxM8rVeD8AAABnSURBVBiVhY/LFoAgCEQZ0p4W6f9/bIJ4slV3oTIeBoaICGADIAO8ibEwWn2IcwVovev7znqmCYRon9kEWUFvg3IysXyIXSil3fOvELupC9XUx7pQx/piDV1sVFLwMNF80sw97hj/AXRPCjtYdmhtAAAAAElFTkSuQmCC);
width: 16px;
@@ -77,18 +80,20 @@
left: 5px;
bottom: 40px;
position: absolute;
- z-index:1000;
+ z-index: 1000;
opacity: 0.9;
}
+
.realTimeMap_overlay {
- position:absolute;
- left:10px;
- bottom:6px;
+ position: absolute;
+ left: 10px;
+ bottom: 6px;
font-size: 12px;
- z-index:10;
- text-shadow:1px 1px 1px #FFFFFF, -1px 1px 1px #FFFFFF,1px -1px 1px #FFFFFF, -1px -1px 1px #FFFFFF,1px 1px 1px #FFFFFF, -1px 1px 1px #FFFFFF,1px -1px 1px #FFFFFF, -1px -1px 1px #FFFFFF;
+ z-index: 10;
+ text-shadow: 1px 1px 1px #FFFFFF, -1px 1px 1px #FFFFFF, 1px -1px 1px #FFFFFF, -1px -1px 1px #FFFFFF, 1px 1px 1px #FFFFFF, -1px 1px 1px #FFFFFF, 1px -1px 1px #FFFFFF, -1px -1px 1px #FFFFFF;
}
+
.realTimeMap_datetime {
bottom: 24px;
color: #887;
diff --git a/plugins/UserCountryMap/css/visitor-map.css b/plugins/UserCountryMap/css/visitor-map.css
index abc5360654..cc3340d895 100644
--- a/plugins/UserCountryMap/css/visitor-map.css
+++ b/plugins/UserCountryMap/css/visitor-map.css
@@ -9,20 +9,19 @@
}
.UserCountryMap .loadingPiwik {
- position: absolute!important;
- top: 42%!important;
- right: 10px!important;
- left: 10px!important;
- z-index: 999!important;
+ position: absolute !important;
+ top: 42% !important;
+ right: 10px !important;
+ left: 10px !important;
+ z-index: 999 !important;
display: block;
font-size: 12px;
color: #000;
- vertical-align: middle!important;
+ vertical-align: middle !important;
text-align: center;
text-shadow: 0 0 5px #fff;
}
-
.tableIcon.inactiveIcon {
color: #99a;
}
@@ -37,21 +36,21 @@
.UserCountryMap-overlay,
.UserCountryMap-tooltip {
- display:block;
+ display: block;
position: absolute;
- z-index:20;
+ z-index: 20;
}
.UserCountryMap-overlay .content,
.UserCountryMap-tooltip .content {
- padding:5px;
- border-radius:3px;
- background:rgba(255,255,255,0.9);
+ padding: 5px;
+ border-radius: 3px;
+ background: rgba(255, 255, 255, 0.9);
}
.UserCountryMap-title {
top: 5px;
- left:5px;
+ left: 5px;
}
.UserCountryMap-legend {
@@ -59,12 +58,14 @@
font-size: 9px;
bottom: 40px;
}
+
.UserCountryMap-info {
left: 5px;
font-size: 11px;
bottom: 60px;
max-width: 42%;
}
+
.UserCountryMap-info-btn {
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAA3NCSVQICAjb4U/gAAAAOVBMVEX///8AAAAAAABXV1dSUlKsrKzExMTd3d3V1dXp6end3d3p6enz8/P7+/v39/f///+vqZ6oopWUjH2LPulWAAAAE3RSTlMAESIzM2Z3mZmqqrvd7u7/////UUgTXgAAAAlwSFlzAAALEgAACxIB0t1+/AAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTNXG14zYAAAAYdEVYdENyZWF0aW9uIFRpbWUAMDMuMDEuMjAxM8rVeD8AAABnSURBVBiVhY/LFoAgCEQZ0p4W6f9/bIJ4slV3oTIeBoaICGADIAO8ibEwWn2IcwVovev7znqmCYRon9kEWUFvg3IysXyIXSil3fOvELupC9XUx7pQx/piDV1sVFLwMNF80sw97hj/AXRPCjtYdmhtAAAAAElFTkSuQmCC);
width: 16px;
@@ -81,13 +82,15 @@
.dataTableFooterIcons .inactiveIcon:hover {
background-color: #F2F1ED;
}
+
.dataTableFooterIcons .inactiveIcon {
cursor: default;
}
+
.dataTableFooterIcons .inactiveIcon img {
opacity: 0.3;
-moz-opacity: 0.3;
- filter:alpha(opacity=3);
+ filter: alpha(opacity=3);
}
.mapWidgetStatus {
diff --git a/plugins/UserCountryMap/js/realtime-map.js b/plugins/UserCountryMap/js/realtime-map.js
index ef0f20c4d6..431b749089 100644
--- a/plugins/UserCountryMap/js/realtime-map.js
+++ b/plugins/UserCountryMap/js/realtime-map.js
@@ -1,4 +1,3 @@
-
/*!
* Piwik - Web Analytics
*
@@ -9,9 +8,9 @@
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
-(function() {
+(function () {
- var RealtimeMap = window.UserCountryMap.RealtimeMap = function(config, theWidget) {
+ var RealtimeMap = window.UserCountryMap.RealtimeMap = function (config, theWidget) {
this.config = config;
this.theWidget = theWidget || false;
this.run();
@@ -19,7 +18,7 @@
$.extend(RealtimeMap.prototype, {
- run: function() {
+ run: function () {
var debug = 0;
var self = this,
@@ -72,10 +71,10 @@
module: 'API',
method: 'Live.getLastVisitsDetails',
filter_limit: maxVisits,
- showColumns: ['latitude','longitude','actions','lastActionTimestamp',
- 'visitLocalTime','city','country','referrerType','referrerName',
- 'referrerTypeName','browserIcon','operatingSystemIcon',
- 'countryFlag','idVisit','actionDetails','continentCode',
+ showColumns: ['latitude', 'longitude', 'actions', 'lastActionTimestamp',
+ 'visitLocalTime', 'city', 'country', 'referrerType', 'referrerName',
+ 'referrerTypeName', 'browserIcon', 'operatingSystemIcon',
+ 'countryFlag', 'idVisit', 'actionDetails', 'continentCode',
'actions', 'searches', 'goalConversions'].join(','),
minTimestamp: firstRun ? -1 : lastTimestamp
});
@@ -101,7 +100,7 @@
*/
function _updateMap(svgUrl, callback) {
if (svgUrl === undefined) return;
- map.loadMap(config.svgBasePath + svgUrl, function() {
+ map.loadMap(config.svgBasePath + svgUrl, function () {
map.clear();
self.resize();
callback();
@@ -131,11 +130,11 @@
}
function relativeTime(ds) {
- var val = function(val) { return '<b>'+Math.round(val)+'</b>'; };
+ var val = function (val) { return '<b>' + Math.round(val) + '</b>'; };
return (ds < 90 ? _.seconds_ago.replace('%s', val(ds))
- : ds < 5400 ? _.minutes_ago.replace('%s', val(ds/60))
- : ds < 129600 ? _.hours_ago.replace('%s', val(ds/3600))
- : _.days_ago.replace('%s', val(ds/86400)));
+ : ds < 5400 ? _.minutes_ago.replace('%s', val(ds / 60))
+ : ds < 129600 ? _.hours_ago.replace('%s', val(ds / 3600))
+ : _.days_ago.replace('%s', val(ds / 86400)));
}
/*
@@ -145,32 +144,32 @@
function visitTooltip(r) {
var ds = new Date().getTime() / 1000 - r.lastActionTimestamp,
ad = r.actionDetails,
- ico = function(src) { return '<img src="'+src+'" alt="" class="icon" />&nbsp;'; };
- return '<h3>'+(r.city ? r.city+' / ' : '')+r.country+'</h3>'+
+ ico = function (src) { return '<img src="' + src + '" alt="" class="icon" />&nbsp;'; };
+ return '<h3>' + (r.city ? r.city + ' / ' : '') + r.country + '</h3>' +
// icons
- ico(r.countryFlag)+ico(r.browserIcon)+ico(r.operatingSystemIcon)+'<br/>'+
+ ico(r.countryFlag) + ico(r.browserIcon) + ico(r.operatingSystemIcon) + '<br/>' +
// last action
- (ad && ad.length && ad[ad.length-1].pageTitle ? '<em>' + ad[ad.length-1].pageTitle+'</em><br/>' : '')+
+ (ad && ad.length && ad[ad.length - 1].pageTitle ? '<em>' + ad[ad.length - 1].pageTitle + '</em><br/>' : '') +
// time of visit
- '<div class="rel-time" data-actiontime="'+r.lastActionTimestamp+'">'+relativeTime(ds)+'</div>'+
+ '<div class="rel-time" data-actiontime="' + r.lastActionTimestamp + '">' + relativeTime(ds) + '</div>' +
// either from or direct
(r.referrerType == "direct" ? r.referrerTypeName :
- _.from + ': '+r.referrerName) + '<br />' +
+ _.from + ': ' + r.referrerName) + '<br />' +
// local time
- '<small>'+_.local_time+': '+r.visitLocalTime+'</small><br />' +
+ '<small>' + _.local_time + ': ' + r.visitLocalTime + '</small><br />' +
// goals, if available
- (self.config.siteHasGoals && r.goalConversions ? '<small>'+_.goal_conversions.replace('%s', '<b>'+r.goalConversions+'</b>') +
- (r.searches > 0 ? ', ' + _.searches.replace('%s', r.searches) : '') + '</small><br />' : '')+
+ (self.config.siteHasGoals && r.goalConversions ? '<small>' + _.goal_conversions.replace('%s', '<b>' + r.goalConversions + '</b>') +
+ (r.searches > 0 ? ', ' + _.searches.replace('%s', r.searches) : '') + '</small><br />' : '') +
// actions and searches
- '<small>'+_.actions.replace('%s', '<b>'+r.actions+'</b>') +
- (r.searches > 0 ? ', ' + _.searches.replace('%s', '<b>'+r.searches+'</b>') : '') + '</small>';
+ '<small>' + _.actions.replace('%s', '<b>' + r.actions + '</b>') +
+ (r.searches > 0 ? ', ' + _.searches.replace('%s', '<b>' + r.searches + '</b>') : '') + '</small>';
}
/*
* the radius of the symbol depends on the lastActionTimestamp
*/
function visitRadius(r) {
- return Math.pow(age(r),4) * (self.maxRad - self.minRad) + self.minRad;
+ return Math.pow(age(r), 4) * (self.maxRad - self.minRad) + self.minRad;
}
/*
@@ -192,7 +191,7 @@
else col = chroma.hsl(
42 * age(r), // hue
Math.sqrt(age(r)), // saturation
- (engaged ? 0.65 : 0.5) - (1-age(r))* 0.45 // lightness
+ (engaged ? 0.65 : 0.5) - (1 - age(r)) * 0.45 // lightness
);
return col;
}
@@ -203,8 +202,8 @@
function visitSymbolAttrs(r) {
return {
fill: visitColor(r).hex(),
- 'fill-opacity': Math.pow(age(r),2) * 0.8 + 0.2,
- 'stroke-opacity': Math.pow(age(r),1.7) * 0.8 + 0.2,
+ 'fill-opacity': Math.pow(age(r), 2) * 0.8 + 0.2,
+ 'stroke-opacity': Math.pow(age(r), 1.7) * 0.8 + 0.2,
stroke: '#fff',
'stroke-width': 1 * age(r),
r: visitRadius(r)
@@ -216,7 +215,7 @@
* that corresponds to a visit on the map
*/
function highlightVisit(r) {
- $('#visitsLive li#'+r.idVisit + ' .datetime')
+ $('#visitsLive li#' + r.idVisit + ' .datetime')
.css('background', '#E4CD74');
}
@@ -225,7 +224,7 @@
* the visit marker on the map
*/
function unhighlightVisit(r) {
- $('#visitsLive li#'+r.idVisit + ' .datetime')
+ $('#visitsLive li#' + r.idVisit + ' .datetime')
.css({ background: '#E4E2D7' });
}
@@ -238,8 +237,8 @@
var c = map.paper.circle().attr(s.path.attrs);
c.insertBefore(s.path);
c.attr({ fill: false });
- c.animate({ r: c.attrs.r*3, 'stroke-width': 7, opacity: 0 }, 2500,
- 'linear', function() { c.remove(); });
+ c.animate({ r: c.attrs.r * 3, 'stroke-width': 7, opacity: 0 }, 2500,
+ 'linear', function () { c.remove(); });
// ..and pop the bubble itself
var col = s.path.attrs.fill,
rad = s.path.attrs.r;
@@ -276,23 +275,23 @@
data: [],
type: Kartograph.Bubble,
/*title: function(d) {
- return visitRadius(d) > 15 && d.actions > 1 ? d.actions : '';
- },
- labelattrs: {
- fill: '#fff',
- 'font-weight': 'bold',
- 'font-size': 11,
- stroke: false,
- cursor: 'pointer'
- },*/
- sortBy: function(r) { return r.lastActionTimestamp; },
+ return visitRadius(d) > 15 && d.actions > 1 ? d.actions : '';
+ },
+ labelattrs: {
+ fill: '#fff',
+ 'font-weight': 'bold',
+ 'font-size': 11,
+ stroke: false,
+ cursor: 'pointer'
+ },*/
+ sortBy: function (r) { return r.lastActionTimestamp; },
radius: visitRadius,
- location: function(r) { return [r.longitude, r.latitude]; },
+ location: function (r) { return [r.longitude, r.latitude]; },
attrs: visitSymbolAttrs,
tooltip: visitTooltip,
mouseenter: highlightVisit,
mouseleave: unhighlightVisit,
- click: function(r, s, evt) {
+ click: function (r, s, evt) {
evt.stopPropagation();
var cont = UserCountryMap.cont2cont[s.data.continentCode];
if (cont && cont != currentMap) {
@@ -307,7 +306,7 @@
if (report.length) {
// filter results without location
- report = $.grep(report, function(r) {
+ report = $.grep(report, function (r) {
return r.latitude !== null;
});
}
@@ -322,26 +321,26 @@
$('.realTimeMap_overlay .no_data').hide();
lastVisits = [].concat(report).concat(lastVisits).slice(0, maxVisits);
- oldest = lastVisits[lastVisits.length-1].lastActionTimestamp;
+ oldest = lastVisits[lastVisits.length - 1].lastActionTimestamp;
// let's try a different strategy
// remove symbols that are too old
//console.info('before', $('circle').length, visitSymbols.symbols.length);
var _removed = 0;
- visitSymbols.remove(function(r) {
+ visitSymbols.remove(function (r) {
if (r.lastActionTimestamp < oldest) _removed++;
return r.lastActionTimestamp < oldest;
});
// update symbols that remain
visitSymbols.update({
- radius: function(d) { return visitSymbolAttrs(d).r; },
+ radius: function (d) { return visitSymbolAttrs(d).r; },
attrs: visitSymbolAttrs
}, true);
// add new symbols
var newSymbols = [];
- $.each(report, function(i, r) {
+ $.each(report, function (i, r) {
newSymbols.push(visitSymbols.add(r));
});
@@ -350,11 +349,11 @@
//console.info('rendered', visitSymbols.symbols.length, $('circle').length);
- $.each(newSymbols, function(i, s) {
- if (i>10) return false;
+ $.each(newSymbols, function (i, s) {
+ if (i > 10) return false;
//if (s.data.lastActionTimestamp > lastTimestamp) {
s.path.hide(); // hide new symbol at first
- var t = setTimeout(function() { animateSymbol(s); },
+ var t = setTimeout(function () { animateSymbol(s); },
1000 * (s.data.lastActionTimestamp - now) + config.liveRefreshAfterMs);
symbolFadeInTimer.push(t);
//}
@@ -396,7 +395,7 @@
stroke: colorTheme[currentTheme].bg,
'stroke-width': 0.2
},
- click: function(d, p, evt) {
+ click: function (d, p, evt) {
evt.stopPropagation();
if (currentMap != 'world') { // zoom out if zoomed in
updateMap('world');
@@ -404,7 +403,7 @@
updateMap(UserCountryMap.ISO3toCONT[d.iso]);
}
},
- title: function(d) {
+ title: function (d) {
// return the country name for educational purpose
return d.name;
}
@@ -427,7 +426,7 @@
*/
function updateMap(_map) {
clearTimeout(nextReqTimer);
- $.each(symbolFadeInTimer, function(i, t) {
+ $.each(symbolFadeInTimer, function (i, t) {
clearTimeout(t);
});
symbolFadeInTimer = [];
@@ -442,12 +441,12 @@
updateMap(location.hash && (location.hash == '#world' || location.hash.match(/^#[A-Z][A-Z]$/)) ? location.hash.substr(1) : 'world'); // TODO: restore last state
// clicking on map background zooms out
- $('#RealTimeMap_map').off('click').click(function() {
+ $('#RealTimeMap_map').off('click').click(function () {
if (currentMap != 'world') updateMap('world');
});
// secret gimmick shortcuts
- $(window).keydown(function(evt) {
+ $(window).keydown(function (evt) {
// shift+alt+C changes color mode
if (evt.shiftKey && evt.altKey && evt.keyCode == 67) {
colorMode = ({
@@ -464,8 +463,8 @@
$('.widget').css({ 'border-width': 1 });
}
map.getLayer('countries')
- .style('fill', colorTheme[currentTheme].fill )
- .style('stroke', colorTheme[currentTheme].bg );
+ .style('fill', colorTheme[currentTheme].fill)
+ .style('stroke', colorTheme[currentTheme].bg);
storeSettings();
}
@@ -489,14 +488,15 @@
function getTimeInSiteTimezone() {
}
+
// setup automatic tooltip updates
- setInterval(function() {
- $('.qtip .rel-time').each(function(i, el) {
+ setInterval(function () {
+ $('.qtip .rel-time').each(function (i, el) {
el = $(el);
var ds = new Date().getTime() / 1000 - el.data('actiontime');
el.html(relativeTime(ds));
});
- var d = new Date(), datetime = d.toTimeString().substr(0,8);
+ var d = new Date(), datetime = d.toTimeString().substr(0, 8);
$('.realTimeMap_datetime').html(datetime);
}, 1000);
},
@@ -504,17 +504,17 @@
/*
* resizes the map to widget dimensions
*/
- resize: function() {
+ resize: function () {
var ratio, w, h, map = this.map;
ratio = map.viewAB.width / map.viewAB.height;
w = map.container.width();
- h = Math.min(w / ratio, $(window).height()-30);
+ h = Math.min(w / ratio, $(window).height() - 30);
var radScale = Math.pow((h * ratio * h) / 130000, 0.3);
this.maxRad = 10 * radScale;
this.minRad = 4 * radScale;
- map.container.height(h-2);
+ map.container.height(h - 2);
map.resize(w, h);
if (map.symbolGroups && map.symbolGroups.length > 0) {
map.symbolGroups[0].update();
@@ -524,7 +524,7 @@
else $('.tableIcon span').show();
},
- destroy: function() {
+ destroy: function () {
this.map.clear();
$(this.map.container).html('');
}
diff --git a/plugins/UserCountryMap/js/vendor/chroma.min.js b/plugins/UserCountryMap/js/vendor/chroma.min.js
index 6e8ed35d5b..971c4da88c 100644
--- a/plugins/UserCountryMap/js/vendor/chroma.min.js
+++ b/plugins/UserCountryMap/js/vendor/chroma.min.js
@@ -1,33 +1,649 @@
/**
- chroma.js - color manipulation in javascript
-
- Copyright (c) 2011-2013, Gregor Aisch
- All rights reserved.
-
- Redistribution and use in source and binary forms, with or without
- modification, are permitted provided that the following conditions are met:
-
- * Redistributions of source code must retain the above copyright notice, this
- list of conditions and the following disclaimer.
-
- * Redistributions in binary form must reproduce the above copyright notice,
- this list of conditions and the following disclaimer in the documentation
- and/or other materials provided with the distribution.
-
- * The name Gregor Aisch may not be used to endorse or promote products
- derived from this software without specific prior written permission.
-
- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
- DISCLAIMED. IN NO EVENT SHALL GREGOR AISCH OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
- INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
- BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
- OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
- NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
- EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
- @source: https://github.com/gka/chroma.js
-*/
-(function(){var Color,ColorScale,K,PITHIRD,TWOPI,X,Y,Z,brewer,chroma,colors,cos,hex2rgb,hsi2rgb,hsl2rgb,hsv2rgb,lab2lch,lab2rgb,lab_xyz,lch2lab,lch2rgb,limit,rgb2hex,rgb2hsi,rgb2hsl,rgb2hsv,rgb2lab,rgb2lch,rgb_xyz,root,type,unpack,xyz_lab,xyz_rgb,_ref,_ref1,_ref2,_ref3,_ref4,_ref5;root=typeof exports!=="undefined"&&exports!==null?exports:this;chroma=(_ref=root.chroma)!=null?_ref:root.chroma={};if(typeof module!=="undefined"&&module!==null){module.exports=chroma}Color=function(){function Color(x,y,z,m){var me,_ref1;me=this;if(!(x!=null)&&!(y!=null)&&!(z!=null)&&!(m!=null)){x=[255,0,255]}if(type(x)==="array"&&x.length===3){if(m==null){m=y}_ref1=x,x=_ref1[0],y=_ref1[1],z=_ref1[2]}if(type(x)==="string"){m="hex"}else{if(m==null){m="rgb"}}if(m==="rgb"){me._rgb=[x,y,z]}else if(m==="hsl"){me._rgb=hsl2rgb(x,y,z)}else if(m==="hsv"){me._rgb=hsv2rgb(x,y,z)}else if(m==="hex"){me._rgb=hex2rgb(x)}else if(m==="lab"){me._rgb=lab2rgb(x,y,z)}else if(m==="lch"){me._rgb=lch2rgb(x,y,z)}else if(m==="hsi"){me._rgb=hsi2rgb(x,y,z)}}Color.prototype.rgb=function(){return this._rgb};Color.prototype.hex=function(){return rgb2hex(this._rgb)};Color.prototype.toString=function(){return this.hex()};Color.prototype.hsl=function(){return rgb2hsl(this._rgb)};Color.prototype.hsv=function(){return rgb2hsv(this._rgb)};Color.prototype.lab=function(){return rgb2lab(this._rgb)};Color.prototype.lch=function(){return rgb2lch(this._rgb)};Color.prototype.hsi=function(){return rgb2hsi(this._rgb)};Color.prototype.interpolate=function(f,col,m){var dh,hue,hue0,hue1,lbv,lbv0,lbv1,me,sat,sat0,sat1,xyz0,xyz1;me=this;if(m==null){m="rgb"}if(type(col)==="string"){col=new Color(col)}if(m==="hsl"||m==="hsv"||m==="lch"||m==="hsi"){if(m==="hsl"){xyz0=me.hsl();xyz1=col.hsl()}else if(m==="hsv"){xyz0=me.hsv();xyz1=col.hsv()}else if(m==="hsi"){xyz0=me.hsi();xyz1=col.hsi()}else if(m==="lch"){xyz0=me.lch();xyz1=col.lch()}if(m.substr(0,1)==="h"){hue0=xyz0[0],sat0=xyz0[1],lbv0=xyz0[2];hue1=xyz1[0],sat1=xyz1[1],lbv1=xyz1[2]}else{lbv0=xyz0[0],sat0=xyz0[1],hue0=xyz0[2];lbv1=xyz1[0],sat1=xyz1[1],hue1=xyz1[2]}if(!isNaN(hue0)&&!isNaN(hue1)){if(hue1>hue0&&hue1-hue0>180){dh=hue1-(hue0+360)}else if(hue1<hue0&&hue0-hue1>180){dh=hue1+360-hue0}else{dh=hue1-hue0}hue=hue0+f*dh}else if(!isNaN(hue0)){hue=hue0;if(lbv1===1||lbv1===0){sat=sat0}}else if(!isNaN(hue1)){hue=hue1;if(lbv0===1||lbv0===0){sat=sat1}}else{hue=void 0}if(sat==null){sat=sat0+f*(sat1-sat0)}lbv=lbv0+f*(lbv1-lbv0);if(m.substr(0,1)==="h"){return new Color(hue,sat,lbv,m)}else{return new Color(lbv,sat,hue,m)}}else if(m==="rgb"){xyz0=me._rgb;xyz1=col._rgb;return new Color(xyz0[0]+f*(xyz1[0]-xyz0[0]),xyz0[1]+f*(xyz1[1]-xyz0[1]),xyz0[2]+f*(xyz1[2]-xyz0[2]),m)}else if(m==="lab"){xyz0=me.lab();xyz1=col.lab();return new Color(xyz0[0]+f*(xyz1[0]-xyz0[0]),xyz0[1]+f*(xyz1[1]-xyz0[1]),xyz0[2]+f*(xyz1[2]-xyz0[2]),m)}else{throw"color mode "+m+" is not supported"}};Color.prototype.darken=function(amount){var lch,me;if(amount==null){amount=.2}me=this;lch=me.lch();lch[2]-=amount;return chroma.lch(lch)};return Color}();hex2rgb=function(hex){var b,g,r,u;if(!hex.match(/^#?([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/)){if(chroma.colors!=null&&chroma.colors[hex]){hex=chroma.colors[hex]}else{throw"unknown color format: "+hex}}if(hex.length===4||hex.length===7){hex=hex.substr(1)}if(hex.length===3){hex=hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2]}u=parseInt(hex,16);r=u>>16;g=u>>8&255;b=u&255;return[r,g,b]};rgb2hex=function(){var b,g,r,str,u,_ref1;_ref1=unpack(arguments),r=_ref1[0],g=_ref1[1],b=_ref1[2];u=r<<16|g<<8|b;str="000000"+u.toString(16).toUpperCase();return"#"+str.substr(str.length-6)};hsv2rgb=function(){var b,f,g,h,i,p,q,r,s,t,v,_ref1,_ref2,_ref3,_ref4,_ref5,_ref6,_ref7;_ref1=unpack(arguments),h=_ref1[0],s=_ref1[1],v=_ref1[2];v*=255;if(s===0){r=g=b=v}else{if(h===360){h=0}if(h>360){h-=360}if(h<0){h+=360}h/=60;i=Math.floor(h);f=h-i;p=v*(1-s);q=v*(1-s*f);t=v*(1-s*(1-f));switch(i){case 0:_ref2=[v,t,p],r=_ref2[0],g=_ref2[1],b=_ref2[2];break;case 1:_ref3=[q,v,p],r=_ref3[0],g=_ref3[1],b=_ref3[2];break;case 2:_ref4=[p,v,t],r=_ref4[0],g=_ref4[1],b=_ref4[2];break;case 3:_ref5=[p,q,v],r=_ref5[0],g=_ref5[1],b=_ref5[2];break;case 4:_ref6=[t,p,v],r=_ref6[0],g=_ref6[1],b=_ref6[2];break;case 5:_ref7=[v,p,q],r=_ref7[0],g=_ref7[1],b=_ref7[2]}}r=Math.round(r);g=Math.round(g);b=Math.round(b);return[r,g,b]};rgb2hsv=function(){var b,delta,g,h,max,min,r,s,v,_ref1;_ref1=unpack(arguments),r=_ref1[0],g=_ref1[1],b=_ref1[2];min=Math.min(r,g,b);max=Math.max(r,g,b);delta=max-min;v=max/255;if(max===0){h=void 0;s=0}else{s=delta/max;if(r===max){h=(g-b)/delta}if(g===max){h=2+(b-r)/delta}if(b===max){h=4+(r-g)/delta}h*=60;if(h<0){h+=360}}return[h,s,v]};hsl2rgb=function(){var b,c,g,h,i,l,r,s,t1,t2,t3,_i,_ref1,_ref2;_ref1=unpack(arguments),h=_ref1[0],s=_ref1[1],l=_ref1[2];if(s===0){r=g=b=l*255}else{t3=[0,0,0];c=[0,0,0];t2=l<.5?l*(1+s):l+s-l*s;t1=2*l-t2;h/=360;t3[0]=h+1/3;t3[1]=h;t3[2]=h-1/3;for(i=_i=0;_i<=2;i=++_i){if(t3[i]<0){t3[i]+=1}if(t3[i]>1){t3[i]-=1}if(6*t3[i]<1){c[i]=t1+(t2-t1)*6*t3[i]}else if(2*t3[i]<1){c[i]=t2}else if(3*t3[i]<2){c[i]=t1+(t2-t1)*(2/3-t3[i])*6}else{c[i]=t1}}_ref2=[Math.round(c[0]*255),Math.round(c[1]*255),Math.round(c[2]*255)],r=_ref2[0],g=_ref2[1],b=_ref2[2]}return[r,g,b]};rgb2hsl=function(r,g,b){var h,l,max,min,s,_ref1;if(r!==void 0&&r.length===3){_ref1=r,r=_ref1[0],g=_ref1[1],b=_ref1[2]}r/=255;g/=255;b/=255;min=Math.min(r,g,b);max=Math.max(r,g,b);l=(max+min)/2;if(max===min){s=0;h=void 0}else{s=l<.5?(max-min)/(max+min):(max-min)/(2-max-min)}if(r===max){h=(g-b)/(max-min)}else if(g===max){h=2+(b-r)/(max-min)}else if(b===max){h=4+(r-g)/(max-min)}h*=60;if(h<0){h+=360}return[h,s,l]};K=18;X=.95047;Y=1;Z=1.08883;lab2rgb=function(l,a,b){var g,r,x,y,z,_ref1,_ref2;if(l!==void 0&&l.length===3){_ref1=l,l=_ref1[0],a=_ref1[1],b=_ref1[2]}if(l!==void 0&&l.length===3){_ref2=l,l=_ref2[0],a=_ref2[1],b=_ref2[2]}y=(l+16)/116;x=y+a/500;z=y-b/200;x=lab_xyz(x)*X;y=lab_xyz(y)*Y;z=lab_xyz(z)*Z;r=xyz_rgb(3.2404542*x-1.5371385*y-.4985314*z);g=xyz_rgb(-.969266*x+1.8760108*y+.041556*z);b=xyz_rgb(.0556434*x-.2040259*y+1.0572252*z);return[limit(r,0,255),limit(g,0,255),limit(b,0,255)]};rgb2lab=function(){var b,g,r,x,y,z,_ref1;_ref1=unpack(arguments),r=_ref1[0],g=_ref1[1],b=_ref1[2];r=rgb_xyz(r);g=rgb_xyz(g);b=rgb_xyz(b);x=xyz_lab((.4124564*r+.3575761*g+.1804375*b)/X);y=xyz_lab((.2126729*r+.7151522*g+.072175*b)/Y);z=xyz_lab((.0193339*r+.119192*g+.9503041*b)/Z);return[116*y-16,500*(x-y),200*(y-z)]};lch2lab=function(){var c,h,l,_ref1;_ref1=unpack(arguments),l=_ref1[0],c=_ref1[1],h=_ref1[2];h=h*Math.PI/180;return[l,Math.cos(h)*c,Math.sin(h)*c]};lch2rgb=function(l,c,h){var L,a,b,g,r,_ref1,_ref2;_ref1=lch2lab(l,c,h),L=_ref1[0],a=_ref1[1],b=_ref1[2];_ref2=lab2rgb(L,a,b),r=_ref2[0],g=_ref2[1],b=_ref2[2];return[limit(r,0,255),limit(g,0,255),limit(b,0,255)]};lab_xyz=function(x){if(x>.206893034){return x*x*x}else{return(x-4/29)/7.787037}};xyz_lab=function(x){if(x>.008856){return Math.pow(x,1/3)}else{return 7.787037*x+4/29}};xyz_rgb=function(r){return Math.round(255*(r<=.00304?12.92*r:1.055*Math.pow(r,1/2.4)-.055))};rgb_xyz=function(r){if((r/=255)<=.04045){return r/12.92}else{return Math.pow((r+.055)/1.055,2.4)}};lab2lch=function(){var a,b,c,h,l,_ref1;_ref1=unpack(arguments),l=_ref1[0],a=_ref1[1],b=_ref1[2];c=Math.sqrt(a*a+b*b);h=Math.atan2(b,a)/Math.PI*180;return[l,c,h]};rgb2lch=function(){var a,b,g,l,r,_ref1,_ref2;_ref1=unpack(arguments),r=_ref1[0],g=_ref1[1],b=_ref1[2];_ref2=rgb2lab(r,g,b),l=_ref2[0],a=_ref2[1],b=_ref2[2];return lab2lch(l,a,b)};rgb2hsi=function(){var TWOPI,b,g,h,i,min,r,s,_ref1;_ref1=unpack(arguments),r=_ref1[0],g=_ref1[1],b=_ref1[2];TWOPI=Math.PI*2;r/=255;g/=255;b/=255;min=Math.min(r,g,b);i=(r+g+b)/3;s=1-min/i;if(s===0){h=0}else{h=(r-g+(r-b))/2;h/=Math.sqrt((r-g)*(r-g)+(r-b)*(g-b));h=Math.acos(h);if(b>g){h=TWOPI-h}h/=TWOPI}return[h*360,s,i]};hsi2rgb=function(h,s,i){var b,g,r,_ref1;_ref1=unpack(arguments),h=_ref1[0],s=_ref1[1],i=_ref1[2];h/=360;if(h<1/3){b=(1-s)/3;r=(1+s*cos(TWOPI*h)/cos(PITHIRD-TWOPI*h))/3;g=1-(b+r)}else if(h<2/3){h-=1/3;r=(1-s)/3;g=(1+s*cos(TWOPI*h)/cos(PITHIRD-TWOPI*h))/3;b=1-(r+g)}else{h-=2/3;g=(1-s)/3;b=(1+s*cos(TWOPI*h)/cos(PITHIRD-TWOPI*h))/3;r=1-(g+b)}r=limit(i*r*3);g=limit(i*g*3);b=limit(i*b*3);return[r*255,g*255,b*255]};chroma.Color=Color;chroma.color=function(x,y,z,m){return new Color(x,y,z,m)};chroma.hsl=function(h,s,l){return new Color(h,s,l,"hsl")};chroma.hsv=function(h,s,v){return new Color(h,s,v,"hsv")};chroma.rgb=function(r,g,b){return new Color(r,g,b,"rgb")};chroma.hex=function(x){return new Color(x)};chroma.lab=function(l,a,b){return new Color(l,a,b,"lab")};chroma.lch=function(l,c,h){return new Color(l,c,h,"lch")};chroma.hsi=function(h,s,i){return new Color(h,s,i,"hsi")};chroma.interpolate=function(a,b,f,m){if(!(a!=null)||!(b!=null)){return"#000"}if(type(a)==="string"){a=new Color(a)}if(type(b)==="string"){b=new Color(b)}return a.interpolate(f,b,m)};root=typeof exports!=="undefined"&&exports!==null?exports:this;chroma=(_ref1=root.chroma)!=null?_ref1:root.chroma={};Color=chroma.Color;ColorScale=function(){function ColorScale(opts){var me,_ref2,_ref3;if(opts==null){opts={}}me=this;me.range(opts.colors,opts.positions);me._mode=(_ref2=opts.mode)!=null?_ref2:"rgb";me._nacol=chroma.hex((_ref3=opts.nacol)!=null?_ref3:chroma.hex("#ccc"));me.domain([0,1]);me}ColorScale.prototype.range=function(colors,positions){var c,col,me,_i,_j,_ref2,_ref3,_ref4;me=this;if(!(colors!=null)){colors=["#ddd","#222"]}if(colors!=null&&type(colors)==="string"&&((_ref2=chroma.brewer)!=null?_ref2[colors]:void 0)!=null){colors=chroma.brewer[colors].slice(0)}for(c=_i=0,_ref3=colors.length-1;0<=_ref3?_i<=_ref3:_i>=_ref3;c=0<=_ref3?++_i:--_i){col=colors[c];if(type(col)==="string"){colors[c]=new Color(col)}}me._colors=colors;if(positions!=null){me._pos=positions}else{me._pos=[];for(c=_j=0,_ref4=colors.length-1;0<=_ref4?_j<=_ref4:_j>=_ref4;c=0<=_ref4?++_j:--_j){me._pos.push(c/(colors.length-1))}}return me};ColorScale.prototype.domain=function(domain){var me;if(domain==null){domain=[]}me=this;me._domain=domain;me._min=domain[0];me._max=domain[domain.length-1];if(domain.length===2){me._numClasses=0}else{me._numClasses=domain.length-1}return me};ColorScale.prototype.get=function(value){var c,f,f0,me;me=this;if(isNaN(value)){return me._nacol}if(me._domain.length>2){c=me.getClass(value);f=c/(me._numClasses-1)}else{f=f0=(value-me._min)/(me._max-me._min);f=Math.min(1,Math.max(0,f))}return me.fColor(f)};ColorScale.prototype.fColor=function(f){var col,cols,i,me,p,_i,_ref2;me=this;cols=me._colors;for(i=_i=0,_ref2=me._pos.length-1;0<=_ref2?_i<=_ref2:_i>=_ref2;i=0<=_ref2?++_i:--_i){p=me._pos[i];if(f<=p){col=cols[i];break}if(f>=p&&i===me._pos.length-1){col=cols[i];break}if(f>p&&f<me._pos[i+1]){f=(f-p)/(me._pos[i+1]-p);col=chroma.interpolate(cols[i],cols[i+1],f,me._mode);break}}return col};ColorScale.prototype.classifyValue=function(value){var domain,i,maxc,me,minc,n,val;me=this;domain=me._domain;val=value;if(domain.length>2){n=domain.length-1;i=me.getClass(value);val=domain[i]+(domain[i+1]-domain[i])*.5;minc=domain[0];maxc=domain[n-1];val=me._min+(val-minc)/(maxc-minc)*(me._max-me._min)}return val};ColorScale.prototype.getClass=function(value){var domain,i,n,self;self=this;domain=self._domain;if(domain!=null){n=domain.length-1;i=0;while(i<n&&value>=domain[i]){i++}return i-1}return 0};ColorScale.prototype.validValue=function(value){return!isNaN(value)};return ColorScale}();chroma.ColorScale=ColorScale;chroma.scale=function(colors,positions){var colscale,f,out;colscale=new chroma.ColorScale;colscale.range(colors,positions);out=false;f=function(v){var c;c=colscale.get(v);if(out&&c[out]){return c[out]()}else{return c}};f.domain=function(domain,classes,mode){var d;if(mode==null){mode="e"}if(classes!=null){d=chroma.analyze(domain);if(classes===0){domain=[d.min,d.max]}else{domain=chroma.limits(d,mode,classes)}}colscale.domain(domain);return f};f.mode=function(_m){colscale._mode=_m;return f};f.range=function(_colors,_pos){colscale.range(_colors,_pos);return f};f.out=function(_o){out=_o;return f};return f};if((_ref2=chroma.scales)==null){chroma.scales={}}chroma.scales.cool=function(){return chroma.scale([chroma.hsl(180,1,.9),chroma.hsl(250,.7,.4)])};chroma.scales.hot=function(){return chroma.scale(["#000","#f00","#ff0","#fff"],[0,.25,.75,1]).mode("rgb")};chroma.analyze=function(data,key,filter){var add,k,r,val,visit,_i,_len;r={min:Number.MAX_VALUE,max:Number.MAX_VALUE*-1,sum:0,values:[],count:0};if(!(filter!=null)){filter=function(){return true}}add=function(val){if(val!=null&&!isNaN(val)){r.values.push(val);r.sum+=val;if(val<r.min){r.min=val}if(val>r.max){r.max=val}r.count+=1}};visit=function(val,k){if(filter(val,k)){if(key!=null&&type(key)==="function"){return add(key(val))}else if(key!=null&&type(key)==="str"||type(key)==="number"){return add(val[key])}else{return add(val)}}};if(type(data)==="array"){for(_i=0,_len=data.length;_i<_len;_i++){val=data[_i];visit(val)}}else{for(k in data){val=data[k];visit(val,k)}}r.domain=[r.min,r.max];r.limits=function(mode,num){return chroma.limits(r,mode,num)};return r};chroma.limits=function(data,mode,num){var assignments,best,centroids,cluster,clusterSizes,dist,i,j,kClusters,limits,max,min,mindist,n,nb_iters,newCentroids,p,pb,pr,repeat,sum,tmpKMeansBreaks,value,values,_i,_j,_k,_l,_m,_n,_o,_p,_q,_r,_ref10,_ref11,_ref12,_ref13,_ref14,_ref15,_ref16,_ref3,_ref4,_ref5,_ref6,_ref7,_ref8,_ref9,_s,_t,_u,_v;if(mode==null){mode="equal"}if(num==null){num=7}if(!(data.values!=null)){data=chroma.analyze(data)}min=data.min;max=data.max;sum=data.sum;values=data.values.sort(function(a,b){return a-b});limits=[];if(mode.substr(0,1)==="c"){limits.push(min);limits.push(max)}if(mode.substr(0,1)==="e"){limits.push(min);for(i=_i=1,_ref3=num-1;1<=_ref3?_i<=_ref3:_i>=_ref3;i=1<=_ref3?++_i:--_i){limits.push(min+i/num*(max-min))}limits.push(max)}else if(mode.substr(0,1)==="q"){limits.push(min);for(i=_j=1,_ref4=num-1;1<=_ref4?_j<=_ref4:_j>=_ref4;i=1<=_ref4?++_j:--_j){p=values.length*i/num;pb=Math.floor(p);if(pb===p){limits.push(values[pb])}else{pr=p-pb;limits.push(values[pb]*pr+values[pb+1]*(1-pr))}}limits.push(max)}else if(mode.substr(0,1)==="k"){n=values.length;assignments=new Array(n);clusterSizes=new Array(num);repeat=true;nb_iters=0;centroids=null;centroids=[];centroids.push(min);for(i=_k=1,_ref5=num-1;1<=_ref5?_k<=_ref5:_k>=_ref5;i=1<=_ref5?++_k:--_k){centroids.push(min+i/num*(max-min))}centroids.push(max);while(repeat){for(j=_l=0,_ref6=num-1;0<=_ref6?_l<=_ref6:_l>=_ref6;j=0<=_ref6?++_l:--_l){clusterSizes[j]=0}for(i=_m=0,_ref7=n-1;0<=_ref7?_m<=_ref7:_m>=_ref7;i=0<=_ref7?++_m:--_m){value=values[i];mindist=Number.MAX_VALUE;for(j=_n=0,_ref8=num-1;0<=_ref8?_n<=_ref8:_n>=_ref8;j=0<=_ref8?++_n:--_n){dist=Math.abs(centroids[j]-value);if(dist<mindist){mindist=dist;best=j}}clusterSizes[best]++;assignments[i]=best}newCentroids=new Array(num);for(j=_o=0,_ref9=num-1;0<=_ref9?_o<=_ref9:_o>=_ref9;j=0<=_ref9?++_o:--_o){newCentroids[j]=null}for(i=_p=0,_ref10=n-1;0<=_ref10?_p<=_ref10:_p>=_ref10;i=0<=_ref10?++_p:--_p){cluster=assignments[i];if(newCentroids[cluster]===null){newCentroids[cluster]=values[i]}else{newCentroids[cluster]+=values[i]}}for(j=_q=0,_ref11=num-1;0<=_ref11?_q<=_ref11:_q>=_ref11;j=0<=_ref11?++_q:--_q){newCentroids[j]*=1/clusterSizes[j]}repeat=false;for(j=_r=0,_ref12=num-1;0<=_ref12?_r<=_ref12:_r>=_ref12;j=0<=_ref12?++_r:--_r){if(newCentroids[j]!==centroids[i]){repeat=true;break}}centroids=newCentroids;nb_iters++;if(nb_iters>200){repeat=false}}kClusters={};for(j=_s=0,_ref13=num-1;0<=_ref13?_s<=_ref13:_s>=_ref13;j=0<=_ref13?++_s:--_s){kClusters[j]=[]}for(i=_t=0,_ref14=n-1;0<=_ref14?_t<=_ref14:_t>=_ref14;i=0<=_ref14?++_t:--_t){cluster=assignments[i];kClusters[cluster].push(values[i])}tmpKMeansBreaks=[];for(j=_u=0,_ref15=num-1;0<=_ref15?_u<=_ref15:_u>=_ref15;j=0<=_ref15?++_u:--_u){tmpKMeansBreaks.push(kClusters[j][0]);tmpKMeansBreaks.push(kClusters[j][kClusters[j].length-1])}tmpKMeansBreaks=tmpKMeansBreaks.sort(function(a,b){return a-b});limits.push(tmpKMeansBreaks[0]);for(i=_v=1,_ref16=tmpKMeansBreaks.length-1;_v<=_ref16;i=_v+=2){if(!isNaN(tmpKMeansBreaks[i])){limits.push(tmpKMeansBreaks[i])}}}return limits};root=typeof exports!=="undefined"&&exports!==null?exports:this;type=function(){var classToType,name,_i,_len,_ref3;classToType={};_ref3="Boolean Number String Function Array Date RegExp Undefined Null".split(" ");for(_i=0,_len=_ref3.length;_i<_len;_i++){name=_ref3[_i];classToType["[object "+name+"]"]=name.toLowerCase()}return function(obj){var strType;strType=Object.prototype.toString.call(obj);return classToType[strType]||"object"}}();if((_ref3=root.type)==null){root.type=type}Array.max=function(array){return Math.max.apply(Math,array)};Array.min=function(array){return Math.min.apply(Math,array)};limit=function(x,min,max){if(min==null){min=0}if(max==null){max=1}if(x<min){x=min}if(x>max){x=max}return x};unpack=function(args){if(args.length===3){return args}else{return args[0]}};TWOPI=Math.PI*2;PITHIRD=Math.PI/3;cos=Math.cos;root=typeof exports!=="undefined"&&exports!==null?exports:this;chroma=(_ref4=root.chroma)!=null?_ref4:root.chroma={};chroma.brewer=brewer={OrRd:["#fff7ec","#fee8c8","#fdd49e","#fdbb84","#fc8d59","#ef6548","#d7301f","#b30000","#7f0000"],PuBu:["#fff7fb","#ece7f2","#d0d1e6","#a6bddb","#74a9cf","#3690c0","#0570b0","#045a8d","#023858"],BuPu:["#f7fcfd","#e0ecf4","#bfd3e6","#9ebcda","#8c96c6","#8c6bb1","#88419d","#810f7c","#4d004b"],Oranges:["#fff5eb","#fee6ce","#fdd0a2","#fdae6b","#fd8d3c","#f16913","#d94801","#a63603","#7f2704"],BuGn:["#f7fcfd","#e5f5f9","#ccece6","#99d8c9","#66c2a4","#41ae76","#238b45","#006d2c","#00441b"],YlOrBr:["#ffffe5","#fff7bc","#fee391","#fec44f","#fe9929","#ec7014","#cc4c02","#993404","#662506"],YlGn:["#ffffe5","#f7fcb9","#d9f0a3","#addd8e","#78c679","#41ab5d","#238443","#006837","#004529"],Reds:["#fff5f0","#fee0d2","#fcbba1","#fc9272","#fb6a4a","#ef3b2c","#cb181d","#a50f15","#67000d"],RdPu:["#fff7f3","#fde0dd","#fcc5c0","#fa9fb5","#f768a1","#dd3497","#ae017e","#7a0177","#49006a"],Greens:["#f7fcf5","#e5f5e0","#c7e9c0","#a1d99b","#74c476","#41ab5d","#238b45","#006d2c","#00441b"],YlGnBu:["#ffffd9","#edf8b1","#c7e9b4","#7fcdbb","#41b6c4","#1d91c0","#225ea8","#253494","#081d58"],Purples:["#fcfbfd","#efedf5","#dadaeb","#bcbddc","#9e9ac8","#807dba","#6a51a3","#54278f","#3f007d"],GnBu:["#f7fcf0","#e0f3db","#ccebc5","#a8ddb5","#7bccc4","#4eb3d3","#2b8cbe","#0868ac","#084081"],Greys:["#ffffff","#f0f0f0","#d9d9d9","#bdbdbd","#969696","#737373","#525252","#252525","#000000"],YlOrRd:["#ffffcc","#ffeda0","#fed976","#feb24c","#fd8d3c","#fc4e2a","#e31a1c","#bd0026","#800026"],PuRd:["#f7f4f9","#e7e1ef","#d4b9da","#c994c7","#df65b0","#e7298a","#ce1256","#980043","#67001f"],Blues:["#f7fbff","#deebf7","#c6dbef","#9ecae1","#6baed6","#4292c6","#2171b5","#08519c","#08306b"],PuBuGn:["#fff7fb","#ece2f0","#d0d1e6","#a6bddb","#67a9cf","#3690c0","#02818a","#016c59","#014636"],Spectral:["#9e0142","#d53e4f","#f46d43","#fdae61","#fee08b","#ffffbf","#e6f598","#abdda4","#66c2a5","#3288bd","#5e4fa2"],RdYlGn:["#a50026","#d73027","#f46d43","#fdae61","#fee08b","#ffffbf","#d9ef8b","#a6d96a","#66bd63","#1a9850","#006837"],RdBu:["#67001f","#b2182b","#d6604d","#f4a582","#fddbc7","#f7f7f7","#d1e5f0","#92c5de","#4393c3","#2166ac","#053061"],PiYG:["#8e0152","#c51b7d","#de77ae","#f1b6da","#fde0ef","#f7f7f7","#e6f5d0","#b8e186","#7fbc41","#4d9221","#276419"],PRGn:["#40004b","#762a83","#9970ab","#c2a5cf","#e7d4e8","#f7f7f7","#d9f0d3","#a6dba0","#5aae61","#1b7837","#00441b"],RdYlBu:["#a50026","#d73027","#f46d43","#fdae61","#fee090","#ffffbf","#e0f3f8","#abd9e9","#74add1","#4575b4","#313695"],BrBG:["#543005","#8c510a","#bf812d","#dfc27d","#f6e8c3","#f5f5f5","#c7eae5","#80cdc1","#35978f","#01665e","#003c30"],RdGy:["#67001f","#b2182b","#d6604d","#f4a582","#fddbc7","#ffffff","#e0e0e0","#bababa","#878787","#4d4d4d","#1a1a1a"],PuOr:["#7f3b08","#b35806","#e08214","#fdb863","#fee0b6","#f7f7f7","#d8daeb","#b2abd2","#8073ac","#542788","#2d004b"],Set2:["#66c2a5","#fc8d62","#8da0cb","#e78ac3","#a6d854","#ffd92f","#e5c494","#b3b3b3"],Accent:["#7fc97f","#beaed4","#fdc086","#ffff99","#386cb0","#f0027f","#bf5b17","#666666"],Set1:["#e41a1c","#377eb8","#4daf4a","#984ea3","#ff7f00","#ffff33","#a65628","#f781bf","#999999"],Set3:["#8dd3c7","#ffffb3","#bebada","#fb8072","#80b1d3","#fdb462","#b3de69","#fccde5","#d9d9d9","#bc80bd","#ccebc5","#ffed6f"],Dark2:["#1b9e77","#d95f02","#7570b3","#e7298a","#66a61e","#e6ab02","#a6761d","#666666"],Paired:["#a6cee3","#1f78b4","#b2df8a","#33a02c","#fb9a99","#e31a1c","#fdbf6f","#ff7f00","#cab2d6","#6a3d9a","#ffff99","#b15928"],Pastel2:["#b3e2cd","#fdcdac","#cbd5e8","#f4cae4","#e6f5c9","#fff2ae","#f1e2cc","#cccccc"],Pastel1:["#fbb4ae","#b3cde3","#ccebc5","#decbe4","#fed9a6","#ffffcc","#e5d8bd","#fddaec","#f2f2f2"]};root=typeof exports!=="undefined"&&exports!==null?exports:this;chroma=(_ref5=root.chroma)!=null?_ref5:root.chroma={};chroma.colors=colors={indigo:"#4b0082",gold:"#ffd700",hotpink:"#ff69b4",firebrick:"#b22222",indianred:"#cd5c5c",yellow:"#ffff00",mistyrose:"#ffe4e1",darkolivegreen:"#556b2f",olive:"#808000",darkseagreen:"#8fbc8f",pink:"#ffc0cb",tomato:"#ff6347",lightcoral:"#f08080",orangered:"#ff4500",navajowhite:"#ffdead",lime:"#00ff00",palegreen:"#98fb98",darkslategrey:"#2f4f4f",greenyellow:"#adff2f",burlywood:"#deb887",seashell:"#fff5ee",mediumspringgreen:"#00fa9a",fuchsia:"#ff00ff",papayawhip:"#ffefd5",blanchedalmond:"#ffebcd",chartreuse:"#7fff00",dimgray:"#696969",black:"#000000",peachpuff:"#ffdab9",springgreen:"#00ff7f",aquamarine:"#7fffd4",white:"#ffffff",orange:"#ffa500",lightsalmon:"#ffa07a",darkslategray:"#2f4f4f",brown:"#a52a2a",ivory:"#fffff0",dodgerblue:"#1e90ff",peru:"#cd853f",lawngreen:"#7cfc00",chocolate:"#d2691e",crimson:"#dc143c",forestgreen:"#228b22",darkgrey:"#a9a9a9",lightseagreen:"#20b2aa",cyan:"#00ffff",mintcream:"#f5fffa",silver:"#c0c0c0",antiquewhite:"#faebd7",mediumorchid:"#ba55d3",skyblue:"#87ceeb",gray:"#808080",darkturquoise:"#00ced1",goldenrod:"#daa520",darkgreen:"#006400",floralwhite:"#fffaf0",darkviolet:"#9400d3",darkgray:"#a9a9a9",moccasin:"#ffe4b5",saddlebrown:"#8b4513",grey:"#808080",darkslateblue:"#483d8b",lightskyblue:"#87cefa",lightpink:"#ffb6c1",mediumvioletred:"#c71585",slategrey:"#708090",red:"#ff0000",deeppink:"#ff1493",limegreen:"#32cd32",darkmagenta:"#8b008b",palegoldenrod:"#eee8aa",plum:"#dda0dd",turquoise:"#40e0d0",lightgrey:"#d3d3d3",lightgoldenrodyellow:"#fafad2",darkgoldenrod:"#b8860b",lavender:"#e6e6fa",maroon:"#800000",yellowgreen:"#9acd32",sandybrown:"#f4a460",thistle:"#d8bfd8",violet:"#ee82ee",navy:"#000080",magenta:"#ff00ff",dimgrey:"#696969",tan:"#d2b48c",rosybrown:"#bc8f8f",olivedrab:"#6b8e23",blue:"#0000ff",lightblue:"#add8e6",ghostwhite:"#f8f8ff",honeydew:"#f0fff0",cornflowerblue:"#6495ed",slateblue:"#6a5acd",linen:"#faf0e6",darkblue:"#00008b",powderblue:"#b0e0e6",seagreen:"#2e8b57",darkkhaki:"#bdb76b",snow:"#fffafa",sienna:"#a0522d",mediumblue:"#0000cd",royalblue:"#4169e1",lightcyan:"#e0ffff",green:"#008000",mediumpurple:"#9370db",midnightblue:"#191970",cornsilk:"#fff8dc",paleturquoise:"#afeeee",bisque:"#ffe4c4",slategray:"#708090",darkcyan:"#008b8b",khaki:"#f0e68c",wheat:"#f5deb3",teal:"#008080",darkorchid:"#9932cc",deepskyblue:"#00bfff",salmon:"#fa8072",darkred:"#8b0000",steelblue:"#4682b4",palevioletred:"#db7093",lightslategray:"#778899",aliceblue:"#f0f8ff",lightslategrey:"#778899",lightgreen:"#90ee90",orchid:"#da70d6",gainsboro:"#dcdcdc",mediumseagreen:"#3cb371",lightgray:"#d3d3d3",mediumturquoise:"#48d1cc",lemonchiffon:"#fffacd",cadetblue:"#5f9ea0",lightyellow:"#ffffe0",lavenderblush:"#fff0f5",coral:"#ff7f50",purple:"#800080",aqua:"#00ffff",whitesmoke:"#f5f5f5",mediumslateblue:"#7b68ee",darkorange:"#ff8c00",mediumaquamarine:"#66cdaa",darksalmon:"#e9967a",beige:"#f5f5dc",blueviolet:"#8a2be2",azure:"#f0ffff",lightsteelblue:"#b0c4de",oldlace:"#fdf5e6"}}).call(this); \ No newline at end of file
+ chroma.js - color manipulation in javascript
+
+ Copyright (c) 2011-2013, Gregor Aisch
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+ * The name Gregor Aisch may not be used to endorse or promote products
+ derived from this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL GREGOR AISCH OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+ @source: https://github.com/gka/chroma.js
+ */
+(function () {
+ var Color, ColorScale, K, PITHIRD, TWOPI, X, Y, Z, brewer, chroma, colors, cos, hex2rgb, hsi2rgb, hsl2rgb, hsv2rgb, lab2lch, lab2rgb, lab_xyz, lch2lab, lch2rgb, limit, rgb2hex, rgb2hsi, rgb2hsl, rgb2hsv, rgb2lab, rgb2lch, rgb_xyz, root, type, unpack, xyz_lab, xyz_rgb, _ref, _ref1, _ref2, _ref3, _ref4, _ref5;
+ root = typeof exports !== "undefined" && exports !== null ? exports : this;
+ chroma = (_ref = root.chroma) != null ? _ref : root.chroma = {};
+ if (typeof module !== "undefined" && module !== null) {module.exports = chroma}
+ Color = function () {
+ function Color(x, y, z, m) {
+ var me, _ref1;
+ me = this;
+ if (!(x != null) && !(y != null) && !(z != null) && !(m != null)) {x = [255, 0, 255]}
+ if (type(x) === "array" && x.length === 3) {
+ if (m == null) {m = y}
+ _ref1 = x, x = _ref1[0], y = _ref1[1], z = _ref1[2]
+ }
+ if (type(x) === "string") {m = "hex"} else {if (m == null) {m = "rgb"}}
+ if (m === "rgb") {me._rgb = [x, y, z]} else if (m === "hsl") {me._rgb = hsl2rgb(x, y, z)} else if (m === "hsv") {me._rgb = hsv2rgb(x, y, z)} else if (m === "hex") {me._rgb = hex2rgb(x)} else if (m === "lab") {me._rgb = lab2rgb(x, y, z)} else if (m === "lch") {me._rgb = lch2rgb(x, y, z)} else if (m === "hsi") {me._rgb = hsi2rgb(x, y, z)}
+ }
+
+ Color.prototype.rgb = function () {return this._rgb};
+ Color.prototype.hex = function () {return rgb2hex(this._rgb)};
+ Color.prototype.toString = function () {return this.hex()};
+ Color.prototype.hsl = function () {return rgb2hsl(this._rgb)};
+ Color.prototype.hsv = function () {return rgb2hsv(this._rgb)};
+ Color.prototype.lab = function () {return rgb2lab(this._rgb)};
+ Color.prototype.lch = function () {return rgb2lch(this._rgb)};
+ Color.prototype.hsi = function () {return rgb2hsi(this._rgb)};
+ Color.prototype.interpolate = function (f, col, m) {
+ var dh, hue, hue0, hue1, lbv, lbv0, lbv1, me, sat, sat0, sat1, xyz0, xyz1;
+ me = this;
+ if (m == null) {m = "rgb"}
+ if (type(col) === "string") {col = new Color(col)}
+ if (m === "hsl" || m === "hsv" || m === "lch" || m === "hsi") {
+ if (m === "hsl") {
+ xyz0 = me.hsl();
+ xyz1 = col.hsl()
+ } else if (m === "hsv") {
+ xyz0 = me.hsv();
+ xyz1 = col.hsv()
+ } else if (m === "hsi") {
+ xyz0 = me.hsi();
+ xyz1 = col.hsi()
+ } else if (m === "lch") {
+ xyz0 = me.lch();
+ xyz1 = col.lch()
+ }
+ if (m.substr(0, 1) === "h") {
+ hue0 = xyz0[0], sat0 = xyz0[1], lbv0 = xyz0[2];
+ hue1 = xyz1[0], sat1 = xyz1[1], lbv1 = xyz1[2]
+ } else {
+ lbv0 = xyz0[0], sat0 = xyz0[1], hue0 = xyz0[2];
+ lbv1 = xyz1[0], sat1 = xyz1[1], hue1 = xyz1[2]
+ }
+ if (!isNaN(hue0) && !isNaN(hue1)) {
+ if (hue1 > hue0 && hue1 - hue0 > 180) {dh = hue1 - (hue0 + 360)} else if (hue1 < hue0 && hue0 - hue1 > 180) {dh = hue1 + 360 - hue0} else {dh = hue1 - hue0}
+ hue = hue0 + f * dh
+ } else if (!isNaN(hue0)) {
+ hue = hue0;
+ if (lbv1 === 1 || lbv1 === 0) {sat = sat0}
+ } else if (!isNaN(hue1)) {
+ hue = hue1;
+ if (lbv0 === 1 || lbv0 === 0) {sat = sat1}
+ } else {hue = void 0}
+ if (sat == null) {sat = sat0 + f * (sat1 - sat0)}
+ lbv = lbv0 + f * (lbv1 - lbv0);
+ if (m.substr(0, 1) === "h") {return new Color(hue, sat, lbv, m)} else {return new Color(lbv, sat, hue, m)}
+ } else if (m === "rgb") {
+ xyz0 = me._rgb;
+ xyz1 = col._rgb;
+ return new Color(xyz0[0] + f * (xyz1[0] - xyz0[0]), xyz0[1] + f * (xyz1[1] - xyz0[1]), xyz0[2] + f * (xyz1[2] - xyz0[2]), m)
+ } else if (m === "lab") {
+ xyz0 = me.lab();
+ xyz1 = col.lab();
+ return new Color(xyz0[0] + f * (xyz1[0] - xyz0[0]), xyz0[1] + f * (xyz1[1] - xyz0[1]), xyz0[2] + f * (xyz1[2] - xyz0[2]), m)
+ } else {throw"color mode " + m + " is not supported"}
+ };
+ Color.prototype.darken = function (amount) {
+ var lch, me;
+ if (amount == null) {amount = .2}
+ me = this;
+ lch = me.lch();
+ lch[2] -= amount;
+ return chroma.lch(lch)
+ };
+ return Color
+ }();
+ hex2rgb = function (hex) {
+ var b, g, r, u;
+ if (!hex.match(/^#?([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/)) {if (chroma.colors != null && chroma.colors[hex]) {hex = chroma.colors[hex]} else {throw"unknown color format: " + hex}}
+ if (hex.length === 4 || hex.length === 7) {hex = hex.substr(1)}
+ if (hex.length === 3) {hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2]}
+ u = parseInt(hex, 16);
+ r = u >> 16;
+ g = u >> 8 & 255;
+ b = u & 255;
+ return[r, g, b]
+ };
+ rgb2hex = function () {
+ var b, g, r, str, u, _ref1;
+ _ref1 = unpack(arguments), r = _ref1[0], g = _ref1[1], b = _ref1[2];
+ u = r << 16 | g << 8 | b;
+ str = "000000" + u.toString(16).toUpperCase();
+ return"#" + str.substr(str.length - 6)
+ };
+ hsv2rgb = function () {
+ var b, f, g, h, i, p, q, r, s, t, v, _ref1, _ref2, _ref3, _ref4, _ref5, _ref6, _ref7;
+ _ref1 = unpack(arguments), h = _ref1[0], s = _ref1[1], v = _ref1[2];
+ v *= 255;
+ if (s === 0) {r = g = b = v} else {
+ if (h === 360) {h = 0}
+ if (h > 360) {h -= 360}
+ if (h < 0) {h += 360}
+ h /= 60;
+ i = Math.floor(h);
+ f = h - i;
+ p = v * (1 - s);
+ q = v * (1 - s * f);
+ t = v * (1 - s * (1 - f));
+ switch (i) {
+ case 0:
+ _ref2 = [v, t, p], r = _ref2[0], g = _ref2[1], b = _ref2[2];
+ break;
+ case 1:
+ _ref3 = [q, v, p], r = _ref3[0], g = _ref3[1], b = _ref3[2];
+ break;
+ case 2:
+ _ref4 = [p, v, t], r = _ref4[0], g = _ref4[1], b = _ref4[2];
+ break;
+ case 3:
+ _ref5 = [p, q, v], r = _ref5[0], g = _ref5[1], b = _ref5[2];
+ break;
+ case 4:
+ _ref6 = [t, p, v], r = _ref6[0], g = _ref6[1], b = _ref6[2];
+ break;
+ case 5:
+ _ref7 = [v, p, q], r = _ref7[0], g = _ref7[1], b = _ref7[2]
+ }
+ }
+ r = Math.round(r);
+ g = Math.round(g);
+ b = Math.round(b);
+ return[r, g, b]
+ };
+ rgb2hsv = function () {
+ var b, delta, g, h, max, min, r, s, v, _ref1;
+ _ref1 = unpack(arguments), r = _ref1[0], g = _ref1[1], b = _ref1[2];
+ min = Math.min(r, g, b);
+ max = Math.max(r, g, b);
+ delta = max - min;
+ v = max / 255;
+ if (max === 0) {
+ h = void 0;
+ s = 0
+ } else {
+ s = delta / max;
+ if (r === max) {h = (g - b) / delta}
+ if (g === max) {h = 2 + (b - r) / delta}
+ if (b === max) {h = 4 + (r - g) / delta}
+ h *= 60;
+ if (h < 0) {h += 360}
+ }
+ return[h, s, v]
+ };
+ hsl2rgb = function () {
+ var b, c, g, h, i, l, r, s, t1, t2, t3, _i, _ref1, _ref2;
+ _ref1 = unpack(arguments), h = _ref1[0], s = _ref1[1], l = _ref1[2];
+ if (s === 0) {r = g = b = l * 255} else {
+ t3 = [0, 0, 0];
+ c = [0, 0, 0];
+ t2 = l < .5 ? l * (1 + s) : l + s - l * s;
+ t1 = 2 * l - t2;
+ h /= 360;
+ t3[0] = h + 1 / 3;
+ t3[1] = h;
+ t3[2] = h - 1 / 3;
+ for (i = _i = 0; _i <= 2; i = ++_i) {
+ if (t3[i] < 0) {t3[i] += 1}
+ if (t3[i] > 1) {t3[i] -= 1}
+ if (6 * t3[i] < 1) {c[i] = t1 + (t2 - t1) * 6 * t3[i]} else if (2 * t3[i] < 1) {c[i] = t2} else if (3 * t3[i] < 2) {c[i] = t1 + (t2 - t1) * (2 / 3 - t3[i]) * 6} else {c[i] = t1}
+ }
+ _ref2 = [Math.round(c[0] * 255), Math.round(c[1] * 255), Math.round(c[2] * 255)], r = _ref2[0], g = _ref2[1], b = _ref2[2]
+ }
+ return[r, g, b]
+ };
+ rgb2hsl = function (r, g, b) {
+ var h, l, max, min, s, _ref1;
+ if (r !== void 0 && r.length === 3) {_ref1 = r, r = _ref1[0], g = _ref1[1], b = _ref1[2]}
+ r /= 255;
+ g /= 255;
+ b /= 255;
+ min = Math.min(r, g, b);
+ max = Math.max(r, g, b);
+ l = (max + min) / 2;
+ if (max === min) {
+ s = 0;
+ h = void 0
+ } else {s = l < .5 ? (max - min) / (max + min) : (max - min) / (2 - max - min)}
+ if (r === max) {h = (g - b) / (max - min)} else if (g === max) {h = 2 + (b - r) / (max - min)} else if (b === max) {h = 4 + (r - g) / (max - min)}
+ h *= 60;
+ if (h < 0) {h += 360}
+ return[h, s, l]
+ };
+ K = 18;
+ X = .95047;
+ Y = 1;
+ Z = 1.08883;
+ lab2rgb = function (l, a, b) {
+ var g, r, x, y, z, _ref1, _ref2;
+ if (l !== void 0 && l.length === 3) {_ref1 = l, l = _ref1[0], a = _ref1[1], b = _ref1[2]}
+ if (l !== void 0 && l.length === 3) {_ref2 = l, l = _ref2[0], a = _ref2[1], b = _ref2[2]}
+ y = (l + 16) / 116;
+ x = y + a / 500;
+ z = y - b / 200;
+ x = lab_xyz(x) * X;
+ y = lab_xyz(y) * Y;
+ z = lab_xyz(z) * Z;
+ r = xyz_rgb(3.2404542 * x - 1.5371385 * y - .4985314 * z);
+ g = xyz_rgb(-.969266 * x + 1.8760108 * y + .041556 * z);
+ b = xyz_rgb(.0556434 * x - .2040259 * y + 1.0572252 * z);
+ return[limit(r, 0, 255), limit(g, 0, 255), limit(b, 0, 255)]
+ };
+ rgb2lab = function () {
+ var b, g, r, x, y, z, _ref1;
+ _ref1 = unpack(arguments), r = _ref1[0], g = _ref1[1], b = _ref1[2];
+ r = rgb_xyz(r);
+ g = rgb_xyz(g);
+ b = rgb_xyz(b);
+ x = xyz_lab((.4124564 * r + .3575761 * g + .1804375 * b) / X);
+ y = xyz_lab((.2126729 * r + .7151522 * g + .072175 * b) / Y);
+ z = xyz_lab((.0193339 * r + .119192 * g + .9503041 * b) / Z);
+ return[116 * y - 16, 500 * (x - y), 200 * (y - z)]
+ };
+ lch2lab = function () {
+ var c, h, l, _ref1;
+ _ref1 = unpack(arguments), l = _ref1[0], c = _ref1[1], h = _ref1[2];
+ h = h * Math.PI / 180;
+ return[l, Math.cos(h) * c, Math.sin(h) * c]
+ };
+ lch2rgb = function (l, c, h) {
+ var L, a, b, g, r, _ref1, _ref2;
+ _ref1 = lch2lab(l, c, h), L = _ref1[0], a = _ref1[1], b = _ref1[2];
+ _ref2 = lab2rgb(L, a, b), r = _ref2[0], g = _ref2[1], b = _ref2[2];
+ return[limit(r, 0, 255), limit(g, 0, 255), limit(b, 0, 255)]
+ };
+ lab_xyz = function (x) {if (x > .206893034) {return x * x * x} else {return(x - 4 / 29) / 7.787037}};
+ xyz_lab = function (x) {if (x > .008856) {return Math.pow(x, 1 / 3)} else {return 7.787037 * x + 4 / 29}};
+ xyz_rgb = function (r) {return Math.round(255 * (r <= .00304 ? 12.92 * r : 1.055 * Math.pow(r, 1 / 2.4) - .055))};
+ rgb_xyz = function (r) {if ((r /= 255) <= .04045) {return r / 12.92} else {return Math.pow((r + .055) / 1.055, 2.4)}};
+ lab2lch = function () {
+ var a, b, c, h, l, _ref1;
+ _ref1 = unpack(arguments), l = _ref1[0], a = _ref1[1], b = _ref1[2];
+ c = Math.sqrt(a * a + b * b);
+ h = Math.atan2(b, a) / Math.PI * 180;
+ return[l, c, h]
+ };
+ rgb2lch = function () {
+ var a, b, g, l, r, _ref1, _ref2;
+ _ref1 = unpack(arguments), r = _ref1[0], g = _ref1[1], b = _ref1[2];
+ _ref2 = rgb2lab(r, g, b), l = _ref2[0], a = _ref2[1], b = _ref2[2];
+ return lab2lch(l, a, b)
+ };
+ rgb2hsi = function () {
+ var TWOPI, b, g, h, i, min, r, s, _ref1;
+ _ref1 = unpack(arguments), r = _ref1[0], g = _ref1[1], b = _ref1[2];
+ TWOPI = Math.PI * 2;
+ r /= 255;
+ g /= 255;
+ b /= 255;
+ min = Math.min(r, g, b);
+ i = (r + g + b) / 3;
+ s = 1 - min / i;
+ if (s === 0) {h = 0} else {
+ h = (r - g + (r - b)) / 2;
+ h /= Math.sqrt((r - g) * (r - g) + (r - b) * (g - b));
+ h = Math.acos(h);
+ if (b > g) {h = TWOPI - h}
+ h /= TWOPI
+ }
+ return[h * 360, s, i]
+ };
+ hsi2rgb = function (h, s, i) {
+ var b, g, r, _ref1;
+ _ref1 = unpack(arguments), h = _ref1[0], s = _ref1[1], i = _ref1[2];
+ h /= 360;
+ if (h < 1 / 3) {
+ b = (1 - s) / 3;
+ r = (1 + s * cos(TWOPI * h) / cos(PITHIRD - TWOPI * h)) / 3;
+ g = 1 - (b + r)
+ } else if (h < 2 / 3) {
+ h -= 1 / 3;
+ r = (1 - s) / 3;
+ g = (1 + s * cos(TWOPI * h) / cos(PITHIRD - TWOPI * h)) / 3;
+ b = 1 - (r + g)
+ } else {
+ h -= 2 / 3;
+ g = (1 - s) / 3;
+ b = (1 + s * cos(TWOPI * h) / cos(PITHIRD - TWOPI * h)) / 3;
+ r = 1 - (g + b)
+ }
+ r = limit(i * r * 3);
+ g = limit(i * g * 3);
+ b = limit(i * b * 3);
+ return[r * 255, g * 255, b * 255]
+ };
+ chroma.Color = Color;
+ chroma.color = function (x, y, z, m) {return new Color(x, y, z, m)};
+ chroma.hsl = function (h, s, l) {return new Color(h, s, l, "hsl")};
+ chroma.hsv = function (h, s, v) {return new Color(h, s, v, "hsv")};
+ chroma.rgb = function (r, g, b) {return new Color(r, g, b, "rgb")};
+ chroma.hex = function (x) {return new Color(x)};
+ chroma.lab = function (l, a, b) {return new Color(l, a, b, "lab")};
+ chroma.lch = function (l, c, h) {return new Color(l, c, h, "lch")};
+ chroma.hsi = function (h, s, i) {return new Color(h, s, i, "hsi")};
+ chroma.interpolate = function (a, b, f, m) {
+ if (!(a != null) || !(b != null)) {return"#000"}
+ if (type(a) === "string") {a = new Color(a)}
+ if (type(b) === "string") {b = new Color(b)}
+ return a.interpolate(f, b, m)
+ };
+ root = typeof exports !== "undefined" && exports !== null ? exports : this;
+ chroma = (_ref1 = root.chroma) != null ? _ref1 : root.chroma = {};
+ Color = chroma.Color;
+ ColorScale = function () {
+ function ColorScale(opts) {
+ var me, _ref2, _ref3;
+ if (opts == null) {opts = {}}
+ me = this;
+ me.range(opts.colors, opts.positions);
+ me._mode = (_ref2 = opts.mode) != null ? _ref2 : "rgb";
+ me._nacol = chroma.hex((_ref3 = opts.nacol) != null ? _ref3 : chroma.hex("#ccc"));
+ me.domain([0, 1]);
+ me
+ }
+
+ ColorScale.prototype.range = function (colors, positions) {
+ var c, col, me, _i, _j, _ref2, _ref3, _ref4;
+ me = this;
+ if (!(colors != null)) {colors = ["#ddd", "#222"]}
+ if (colors != null && type(colors) === "string" && ((_ref2 = chroma.brewer) != null ? _ref2[colors] : void 0) != null) {colors = chroma.brewer[colors].slice(0)}
+ for (c = _i = 0, _ref3 = colors.length - 1; 0 <= _ref3 ? _i <= _ref3 : _i >= _ref3; c = 0 <= _ref3 ? ++_i : --_i) {
+ col = colors[c];
+ if (type(col) === "string") {colors[c] = new Color(col)}
+ }
+ me._colors = colors;
+ if (positions != null) {me._pos = positions} else {
+ me._pos = [];
+ for (c = _j = 0, _ref4 = colors.length - 1; 0 <= _ref4 ? _j <= _ref4 : _j >= _ref4; c = 0 <= _ref4 ? ++_j : --_j) {me._pos.push(c / (colors.length - 1))}
+ }
+ return me
+ };
+ ColorScale.prototype.domain = function (domain) {
+ var me;
+ if (domain == null) {domain = []}
+ me = this;
+ me._domain = domain;
+ me._min = domain[0];
+ me._max = domain[domain.length - 1];
+ if (domain.length === 2) {me._numClasses = 0} else {me._numClasses = domain.length - 1}
+ return me
+ };
+ ColorScale.prototype.get = function (value) {
+ var c, f, f0, me;
+ me = this;
+ if (isNaN(value)) {return me._nacol}
+ if (me._domain.length > 2) {
+ c = me.getClass(value);
+ f = c / (me._numClasses - 1)
+ } else {
+ f = f0 = (value - me._min) / (me._max - me._min);
+ f = Math.min(1, Math.max(0, f))
+ }
+ return me.fColor(f)
+ };
+ ColorScale.prototype.fColor = function (f) {
+ var col, cols, i, me, p, _i, _ref2;
+ me = this;
+ cols = me._colors;
+ for (i = _i = 0, _ref2 = me._pos.length - 1; 0 <= _ref2 ? _i <= _ref2 : _i >= _ref2; i = 0 <= _ref2 ? ++_i : --_i) {
+ p = me._pos[i];
+ if (f <= p) {
+ col = cols[i];
+ break
+ }
+ if (f >= p && i === me._pos.length - 1) {
+ col = cols[i];
+ break
+ }
+ if (f > p && f < me._pos[i + 1]) {
+ f = (f - p) / (me._pos[i + 1] - p);
+ col = chroma.interpolate(cols[i], cols[i + 1], f, me._mode);
+ break
+ }
+ }
+ return col
+ };
+ ColorScale.prototype.classifyValue = function (value) {
+ var domain, i, maxc, me, minc, n, val;
+ me = this;
+ domain = me._domain;
+ val = value;
+ if (domain.length > 2) {
+ n = domain.length - 1;
+ i = me.getClass(value);
+ val = domain[i] + (domain[i + 1] - domain[i]) * .5;
+ minc = domain[0];
+ maxc = domain[n - 1];
+ val = me._min + (val - minc) / (maxc - minc) * (me._max - me._min)
+ }
+ return val
+ };
+ ColorScale.prototype.getClass = function (value) {
+ var domain, i, n, self;
+ self = this;
+ domain = self._domain;
+ if (domain != null) {
+ n = domain.length - 1;
+ i = 0;
+ while (i < n && value >= domain[i]) {i++}
+ return i - 1
+ }
+ return 0
+ };
+ ColorScale.prototype.validValue = function (value) {return!isNaN(value)};
+ return ColorScale
+ }();
+ chroma.ColorScale = ColorScale;
+ chroma.scale = function (colors, positions) {
+ var colscale, f, out;
+ colscale = new chroma.ColorScale;
+ colscale.range(colors, positions);
+ out = false;
+ f = function (v) {
+ var c;
+ c = colscale.get(v);
+ if (out && c[out]) {return c[out]()} else {return c}
+ };
+ f.domain = function (domain, classes, mode) {
+ var d;
+ if (mode == null) {mode = "e"}
+ if (classes != null) {
+ d = chroma.analyze(domain);
+ if (classes === 0) {domain = [d.min, d.max]} else {domain = chroma.limits(d, mode, classes)}
+ }
+ colscale.domain(domain);
+ return f
+ };
+ f.mode = function (_m) {
+ colscale._mode = _m;
+ return f
+ };
+ f.range = function (_colors, _pos) {
+ colscale.range(_colors, _pos);
+ return f
+ };
+ f.out = function (_o) {
+ out = _o;
+ return f
+ };
+ return f
+ };
+ if ((_ref2 = chroma.scales) == null) {chroma.scales = {}}
+ chroma.scales.cool = function () {return chroma.scale([chroma.hsl(180, 1, .9), chroma.hsl(250, .7, .4)])};
+ chroma.scales.hot = function () {return chroma.scale(["#000", "#f00", "#ff0", "#fff"], [0, .25, .75, 1]).mode("rgb")};
+ chroma.analyze = function (data, key, filter) {
+ var add, k, r, val, visit, _i, _len;
+ r = {min: Number.MAX_VALUE, max: Number.MAX_VALUE * -1, sum: 0, values: [], count: 0};
+ if (!(filter != null)) {filter = function () {return true}}
+ add = function (val) {
+ if (val != null && !isNaN(val)) {
+ r.values.push(val);
+ r.sum += val;
+ if (val < r.min) {r.min = val}
+ if (val > r.max) {r.max = val}
+ r.count += 1
+ }
+ };
+ visit = function (val, k) {if (filter(val, k)) {if (key != null && type(key) === "function") {return add(key(val))} else if (key != null && type(key) === "str" || type(key) === "number") {return add(val[key])} else {return add(val)}}};
+ if (type(data) === "array") {
+ for (_i = 0, _len = data.length; _i < _len; _i++) {
+ val = data[_i];
+ visit(val)
+ }
+ } else {
+ for (k in data) {
+ val = data[k];
+ visit(val, k)
+ }
+ }
+ r.domain = [r.min, r.max];
+ r.limits = function (mode, num) {return chroma.limits(r, mode, num)};
+ return r
+ };
+ chroma.limits = function (data, mode, num) {
+ var assignments, best, centroids, cluster, clusterSizes, dist, i, j, kClusters, limits, max, min, mindist, n, nb_iters, newCentroids, p, pb, pr, repeat, sum, tmpKMeansBreaks, value, values, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _ref10, _ref11, _ref12, _ref13, _ref14, _ref15, _ref16, _ref3, _ref4, _ref5, _ref6, _ref7, _ref8, _ref9, _s, _t, _u, _v;
+ if (mode == null) {mode = "equal"}
+ if (num == null) {num = 7}
+ if (!(data.values != null)) {data = chroma.analyze(data)}
+ min = data.min;
+ max = data.max;
+ sum = data.sum;
+ values = data.values.sort(function (a, b) {return a - b});
+ limits = [];
+ if (mode.substr(0, 1) === "c") {
+ limits.push(min);
+ limits.push(max)
+ }
+ if (mode.substr(0, 1) === "e") {
+ limits.push(min);
+ for (i = _i = 1, _ref3 = num - 1; 1 <= _ref3 ? _i <= _ref3 : _i >= _ref3; i = 1 <= _ref3 ? ++_i : --_i) {limits.push(min + i / num * (max - min))}
+ limits.push(max)
+ } else if (mode.substr(0, 1) === "q") {
+ limits.push(min);
+ for (i = _j = 1, _ref4 = num - 1; 1 <= _ref4 ? _j <= _ref4 : _j >= _ref4; i = 1 <= _ref4 ? ++_j : --_j) {
+ p = values.length * i / num;
+ pb = Math.floor(p);
+ if (pb === p) {limits.push(values[pb])} else {
+ pr = p - pb;
+ limits.push(values[pb] * pr + values[pb + 1] * (1 - pr))
+ }
+ }
+ limits.push(max)
+ } else if (mode.substr(0, 1) === "k") {
+ n = values.length;
+ assignments = new Array(n);
+ clusterSizes = new Array(num);
+ repeat = true;
+ nb_iters = 0;
+ centroids = null;
+ centroids = [];
+ centroids.push(min);
+ for (i = _k = 1, _ref5 = num - 1; 1 <= _ref5 ? _k <= _ref5 : _k >= _ref5; i = 1 <= _ref5 ? ++_k : --_k) {centroids.push(min + i / num * (max - min))}
+ centroids.push(max);
+ while (repeat) {
+ for (j = _l = 0, _ref6 = num - 1; 0 <= _ref6 ? _l <= _ref6 : _l >= _ref6; j = 0 <= _ref6 ? ++_l : --_l) {clusterSizes[j] = 0}
+ for (i = _m = 0, _ref7 = n - 1; 0 <= _ref7 ? _m <= _ref7 : _m >= _ref7; i = 0 <= _ref7 ? ++_m : --_m) {
+ value = values[i];
+ mindist = Number.MAX_VALUE;
+ for (j = _n = 0, _ref8 = num - 1; 0 <= _ref8 ? _n <= _ref8 : _n >= _ref8; j = 0 <= _ref8 ? ++_n : --_n) {
+ dist = Math.abs(centroids[j] - value);
+ if (dist < mindist) {
+ mindist = dist;
+ best = j
+ }
+ }
+ clusterSizes[best]++;
+ assignments[i] = best
+ }
+ newCentroids = new Array(num);
+ for (j = _o = 0, _ref9 = num - 1; 0 <= _ref9 ? _o <= _ref9 : _o >= _ref9; j = 0 <= _ref9 ? ++_o : --_o) {newCentroids[j] = null}
+ for (i = _p = 0, _ref10 = n - 1; 0 <= _ref10 ? _p <= _ref10 : _p >= _ref10; i = 0 <= _ref10 ? ++_p : --_p) {
+ cluster = assignments[i];
+ if (newCentroids[cluster] === null) {newCentroids[cluster] = values[i]} else {newCentroids[cluster] += values[i]}
+ }
+ for (j = _q = 0, _ref11 = num - 1; 0 <= _ref11 ? _q <= _ref11 : _q >= _ref11; j = 0 <= _ref11 ? ++_q : --_q) {newCentroids[j] *= 1 / clusterSizes[j]}
+ repeat = false;
+ for (j = _r = 0, _ref12 = num - 1; 0 <= _ref12 ? _r <= _ref12 : _r >= _ref12; j = 0 <= _ref12 ? ++_r : --_r) {
+ if (newCentroids[j] !== centroids[i]) {
+ repeat = true;
+ break
+ }
+ }
+ centroids = newCentroids;
+ nb_iters++;
+ if (nb_iters > 200) {repeat = false}
+ }
+ kClusters = {};
+ for (j = _s = 0, _ref13 = num - 1; 0 <= _ref13 ? _s <= _ref13 : _s >= _ref13; j = 0 <= _ref13 ? ++_s : --_s) {kClusters[j] = []}
+ for (i = _t = 0, _ref14 = n - 1; 0 <= _ref14 ? _t <= _ref14 : _t >= _ref14; i = 0 <= _ref14 ? ++_t : --_t) {
+ cluster = assignments[i];
+ kClusters[cluster].push(values[i])
+ }
+ tmpKMeansBreaks = [];
+ for (j = _u = 0, _ref15 = num - 1; 0 <= _ref15 ? _u <= _ref15 : _u >= _ref15; j = 0 <= _ref15 ? ++_u : --_u) {
+ tmpKMeansBreaks.push(kClusters[j][0]);
+ tmpKMeansBreaks.push(kClusters[j][kClusters[j].length - 1])
+ }
+ tmpKMeansBreaks = tmpKMeansBreaks.sort(function (a, b) {return a - b});
+ limits.push(tmpKMeansBreaks[0]);
+ for (i = _v = 1, _ref16 = tmpKMeansBreaks.length - 1; _v <= _ref16; i = _v += 2) {if (!isNaN(tmpKMeansBreaks[i])) {limits.push(tmpKMeansBreaks[i])}}
+ }
+ return limits
+ };
+ root = typeof exports !== "undefined" && exports !== null ? exports : this;
+ type = function () {
+ var classToType, name, _i, _len, _ref3;
+ classToType = {};
+ _ref3 = "Boolean Number String Function Array Date RegExp Undefined Null".split(" ");
+ for (_i = 0, _len = _ref3.length; _i < _len; _i++) {
+ name = _ref3[_i];
+ classToType["[object " + name + "]"] = name.toLowerCase()
+ }
+ return function (obj) {
+ var strType;
+ strType = Object.prototype.toString.call(obj);
+ return classToType[strType] || "object"
+ }
+ }();
+ if ((_ref3 = root.type) == null) {root.type = type}
+ Array.max = function (array) {return Math.max.apply(Math, array)};
+ Array.min = function (array) {return Math.min.apply(Math, array)};
+ limit = function (x, min, max) {
+ if (min == null) {min = 0}
+ if (max == null) {max = 1}
+ if (x < min) {x = min}
+ if (x > max) {x = max}
+ return x
+ };
+ unpack = function (args) {if (args.length === 3) {return args} else {return args[0]}};
+ TWOPI = Math.PI * 2;
+ PITHIRD = Math.PI / 3;
+ cos = Math.cos;
+ root = typeof exports !== "undefined" && exports !== null ? exports : this;
+ chroma = (_ref4 = root.chroma) != null ? _ref4 : root.chroma = {};
+ chroma.brewer = brewer = {OrRd: ["#fff7ec", "#fee8c8", "#fdd49e", "#fdbb84", "#fc8d59", "#ef6548", "#d7301f", "#b30000", "#7f0000"], PuBu: ["#fff7fb", "#ece7f2", "#d0d1e6", "#a6bddb", "#74a9cf", "#3690c0", "#0570b0", "#045a8d", "#023858"], BuPu: ["#f7fcfd", "#e0ecf4", "#bfd3e6", "#9ebcda", "#8c96c6", "#8c6bb1", "#88419d", "#810f7c", "#4d004b"], Oranges: ["#fff5eb", "#fee6ce", "#fdd0a2", "#fdae6b", "#fd8d3c", "#f16913", "#d94801", "#a63603", "#7f2704"], BuGn: ["#f7fcfd", "#e5f5f9", "#ccece6", "#99d8c9", "#66c2a4", "#41ae76", "#238b45", "#006d2c", "#00441b"], YlOrBr: ["#ffffe5", "#fff7bc", "#fee391", "#fec44f", "#fe9929", "#ec7014", "#cc4c02", "#993404", "#662506"], YlGn: ["#ffffe5", "#f7fcb9", "#d9f0a3", "#addd8e", "#78c679", "#41ab5d", "#238443", "#006837", "#004529"], Reds: ["#fff5f0", "#fee0d2", "#fcbba1", "#fc9272", "#fb6a4a", "#ef3b2c", "#cb181d", "#a50f15", "#67000d"], RdPu: ["#fff7f3", "#fde0dd", "#fcc5c0", "#fa9fb5", "#f768a1", "#dd3497", "#ae017e", "#7a0177", "#49006a"], Greens: ["#f7fcf5", "#e5f5e0", "#c7e9c0", "#a1d99b", "#74c476", "#41ab5d", "#238b45", "#006d2c", "#00441b"], YlGnBu: ["#ffffd9", "#edf8b1", "#c7e9b4", "#7fcdbb", "#41b6c4", "#1d91c0", "#225ea8", "#253494", "#081d58"], Purples: ["#fcfbfd", "#efedf5", "#dadaeb", "#bcbddc", "#9e9ac8", "#807dba", "#6a51a3", "#54278f", "#3f007d"], GnBu: ["#f7fcf0", "#e0f3db", "#ccebc5", "#a8ddb5", "#7bccc4", "#4eb3d3", "#2b8cbe", "#0868ac", "#084081"], Greys: ["#ffffff", "#f0f0f0", "#d9d9d9", "#bdbdbd", "#969696", "#737373", "#525252", "#252525", "#000000"], YlOrRd: ["#ffffcc", "#ffeda0", "#fed976", "#feb24c", "#fd8d3c", "#fc4e2a", "#e31a1c", "#bd0026", "#800026"], PuRd: ["#f7f4f9", "#e7e1ef", "#d4b9da", "#c994c7", "#df65b0", "#e7298a", "#ce1256", "#980043", "#67001f"], Blues: ["#f7fbff", "#deebf7", "#c6dbef", "#9ecae1", "#6baed6", "#4292c6", "#2171b5", "#08519c", "#08306b"], PuBuGn: ["#fff7fb", "#ece2f0", "#d0d1e6", "#a6bddb", "#67a9cf", "#3690c0", "#02818a", "#016c59", "#014636"], Spectral: ["#9e0142", "#d53e4f", "#f46d43", "#fdae61", "#fee08b", "#ffffbf", "#e6f598", "#abdda4", "#66c2a5", "#3288bd", "#5e4fa2"], RdYlGn: ["#a50026", "#d73027", "#f46d43", "#fdae61", "#fee08b", "#ffffbf", "#d9ef8b", "#a6d96a", "#66bd63", "#1a9850", "#006837"], RdBu: ["#67001f", "#b2182b", "#d6604d", "#f4a582", "#fddbc7", "#f7f7f7", "#d1e5f0", "#92c5de", "#4393c3", "#2166ac", "#053061"], PiYG: ["#8e0152", "#c51b7d", "#de77ae", "#f1b6da", "#fde0ef", "#f7f7f7", "#e6f5d0", "#b8e186", "#7fbc41", "#4d9221", "#276419"], PRGn: ["#40004b", "#762a83", "#9970ab", "#c2a5cf", "#e7d4e8", "#f7f7f7", "#d9f0d3", "#a6dba0", "#5aae61", "#1b7837", "#00441b"], RdYlBu: ["#a50026", "#d73027", "#f46d43", "#fdae61", "#fee090", "#ffffbf", "#e0f3f8", "#abd9e9", "#74add1", "#4575b4", "#313695"], BrBG: ["#543005", "#8c510a", "#bf812d", "#dfc27d", "#f6e8c3", "#f5f5f5", "#c7eae5", "#80cdc1", "#35978f", "#01665e", "#003c30"], RdGy: ["#67001f", "#b2182b", "#d6604d", "#f4a582", "#fddbc7", "#ffffff", "#e0e0e0", "#bababa", "#878787", "#4d4d4d", "#1a1a1a"], PuOr: ["#7f3b08", "#b35806", "#e08214", "#fdb863", "#fee0b6", "#f7f7f7", "#d8daeb", "#b2abd2", "#8073ac", "#542788", "#2d004b"], Set2: ["#66c2a5", "#fc8d62", "#8da0cb", "#e78ac3", "#a6d854", "#ffd92f", "#e5c494", "#b3b3b3"], Accent: ["#7fc97f", "#beaed4", "#fdc086", "#ffff99", "#386cb0", "#f0027f", "#bf5b17", "#666666"], Set1: ["#e41a1c", "#377eb8", "#4daf4a", "#984ea3", "#ff7f00", "#ffff33", "#a65628", "#f781bf", "#999999"], Set3: ["#8dd3c7", "#ffffb3", "#bebada", "#fb8072", "#80b1d3", "#fdb462", "#b3de69", "#fccde5", "#d9d9d9", "#bc80bd", "#ccebc5", "#ffed6f"], Dark2: ["#1b9e77", "#d95f02", "#7570b3", "#e7298a", "#66a61e", "#e6ab02", "#a6761d", "#666666"], Paired: ["#a6cee3", "#1f78b4", "#b2df8a", "#33a02c", "#fb9a99", "#e31a1c", "#fdbf6f", "#ff7f00", "#cab2d6", "#6a3d9a", "#ffff99", "#b15928"], Pastel2: ["#b3e2cd", "#fdcdac", "#cbd5e8", "#f4cae4", "#e6f5c9", "#fff2ae", "#f1e2cc", "#cccccc"], Pastel1: ["#fbb4ae", "#b3cde3", "#ccebc5", "#decbe4", "#fed9a6", "#ffffcc", "#e5d8bd", "#fddaec", "#f2f2f2"]};
+ root = typeof exports !== "undefined" && exports !== null ? exports : this;
+ chroma = (_ref5 = root.chroma) != null ? _ref5 : root.chroma = {};
+ chroma.colors = colors = {indigo: "#4b0082", gold: "#ffd700", hotpink: "#ff69b4", firebrick: "#b22222", indianred: "#cd5c5c", yellow: "#ffff00", mistyrose: "#ffe4e1", darkolivegreen: "#556b2f", olive: "#808000", darkseagreen: "#8fbc8f", pink: "#ffc0cb", tomato: "#ff6347", lightcoral: "#f08080", orangered: "#ff4500", navajowhite: "#ffdead", lime: "#00ff00", palegreen: "#98fb98", darkslategrey: "#2f4f4f", greenyellow: "#adff2f", burlywood: "#deb887", seashell: "#fff5ee", mediumspringgreen: "#00fa9a", fuchsia: "#ff00ff", papayawhip: "#ffefd5", blanchedalmond: "#ffebcd", chartreuse: "#7fff00", dimgray: "#696969", black: "#000000", peachpuff: "#ffdab9", springgreen: "#00ff7f", aquamarine: "#7fffd4", white: "#ffffff", orange: "#ffa500", lightsalmon: "#ffa07a", darkslategray: "#2f4f4f", brown: "#a52a2a", ivory: "#fffff0", dodgerblue: "#1e90ff", peru: "#cd853f", lawngreen: "#7cfc00", chocolate: "#d2691e", crimson: "#dc143c", forestgreen: "#228b22", darkgrey: "#a9a9a9", lightseagreen: "#20b2aa", cyan: "#00ffff", mintcream: "#f5fffa", silver: "#c0c0c0", antiquewhite: "#faebd7", mediumorchid: "#ba55d3", skyblue: "#87ceeb", gray: "#808080", darkturquoise: "#00ced1", goldenrod: "#daa520", darkgreen: "#006400", floralwhite: "#fffaf0", darkviolet: "#9400d3", darkgray: "#a9a9a9", moccasin: "#ffe4b5", saddlebrown: "#8b4513", grey: "#808080", darkslateblue: "#483d8b", lightskyblue: "#87cefa", lightpink: "#ffb6c1", mediumvioletred: "#c71585", slategrey: "#708090", red: "#ff0000", deeppink: "#ff1493", limegreen: "#32cd32", darkmagenta: "#8b008b", palegoldenrod: "#eee8aa", plum: "#dda0dd", turquoise: "#40e0d0", lightgrey: "#d3d3d3", lightgoldenrodyellow: "#fafad2", darkgoldenrod: "#b8860b", lavender: "#e6e6fa", maroon: "#800000", yellowgreen: "#9acd32", sandybrown: "#f4a460", thistle: "#d8bfd8", violet: "#ee82ee", navy: "#000080", magenta: "#ff00ff", dimgrey: "#696969", tan: "#d2b48c", rosybrown: "#bc8f8f", olivedrab: "#6b8e23", blue: "#0000ff", lightblue: "#add8e6", ghostwhite: "#f8f8ff", honeydew: "#f0fff0", cornflowerblue: "#6495ed", slateblue: "#6a5acd", linen: "#faf0e6", darkblue: "#00008b", powderblue: "#b0e0e6", seagreen: "#2e8b57", darkkhaki: "#bdb76b", snow: "#fffafa", sienna: "#a0522d", mediumblue: "#0000cd", royalblue: "#4169e1", lightcyan: "#e0ffff", green: "#008000", mediumpurple: "#9370db", midnightblue: "#191970", cornsilk: "#fff8dc", paleturquoise: "#afeeee", bisque: "#ffe4c4", slategray: "#708090", darkcyan: "#008b8b", khaki: "#f0e68c", wheat: "#f5deb3", teal: "#008080", darkorchid: "#9932cc", deepskyblue: "#00bfff", salmon: "#fa8072", darkred: "#8b0000", steelblue: "#4682b4", palevioletred: "#db7093", lightslategray: "#778899", aliceblue: "#f0f8ff", lightslategrey: "#778899", lightgreen: "#90ee90", orchid: "#da70d6", gainsboro: "#dcdcdc", mediumseagreen: "#3cb371", lightgray: "#d3d3d3", mediumturquoise: "#48d1cc", lemonchiffon: "#fffacd", cadetblue: "#5f9ea0", lightyellow: "#ffffe0", lavenderblush: "#fff0f5", coral: "#ff7f50", purple: "#800080", aqua: "#00ffff", whitesmoke: "#f5f5f5", mediumslateblue: "#7b68ee", darkorange: "#ff8c00", mediumaquamarine: "#66cdaa", darksalmon: "#e9967a", beige: "#f5f5dc", blueviolet: "#8a2be2", azure: "#f0ffff", lightsteelblue: "#b0c4de", oldlace: "#fdf5e6"}
+}).call(this); \ No newline at end of file
diff --git a/plugins/UserCountryMap/js/vendor/jquery.qtip.min.js b/plugins/UserCountryMap/js/vendor/jquery.qtip.min.js
index 5e251bc39f..1f757df953 100644
--- a/plugins/UserCountryMap/js/vendor/jquery.qtip.min.js
+++ b/plugins/UserCountryMap/js/vendor/jquery.qtip.min.js
@@ -1,14 +1,352 @@
/*
-* qTip2 - Pretty powerful tooltips
-* http://craigsworks.com/projects/qtip2/
-*
-* Version: nightly
-* Copyright 2009-2010 Craig Michael Thompson - http://craigsworks.com
-*
-* Dual licensed under MIT or GPLv2 licenses
-* http://en.wikipedia.org/wiki/MIT_License
-* http://en.wikipedia.org/wiki/GNU_General_Public_License
-*
-* Date: Mon Nov 21 13:18:18.0000000000 2011
-*//*jslint browser: true, onevar: true, undef: true, nomen: true, bitwise: true, regexp: true, newcap: true, immed: true, strict: true *//*global window: false, jQuery: false, console: false */
-(function(a,b,c){function z(b,c){var i,j,k,l,m,n=a(this),o=a(document.body),p=this===document?o:n,q=n.metadata?n.metadata(c.metadata):f,r=c.metadata.type==="html5"&&q?q[c.metadata.name]:f,s=n.data(c.metadata.name||"qtipopts");try{s=typeof s==="string"?(new Function("return "+s))():s}catch(t){w("Unable to parse HTML5 attribute data: "+s)}l=a.extend(d,{},g.defaults,c,typeof s==="object"?x(s):f,x(r||q)),j=l.position,l.id=b;if("boolean"===typeof l.content.text){k=n.attr(l.content.attr);if(l.content.attr!==e&&k)l.content.text=k;else{w("Unable to locate content for tooltip! Aborting render of tooltip on element: ",n);return e}}j.container===e&&(j.container=o),j.target===e&&(j.target=p),l.show.target===e&&(l.show.target=p),l.show.solo===d&&(l.show.solo=o),l.hide.target===e&&(l.hide.target=p),l.position.viewport===d&&(l.position.viewport=j.container),j.at=new h.Corner(j.at),j.my=new h.Corner(j.my);if(a.data(this,"qtip"))if(l.overwrite)n.qtip("destroy");else if(l.overwrite===e)return e;l.suppress&&(m=a.attr(this,"title"))&&a(this).removeAttr("title").attr(u,m),i=new y(n,l,b,!!k),a.data(this,"qtip",i),n.bind("remove.qtip-"+b,function(){i.destroy()});return i}function y(s,t,w,y){function R(){var c=[t.show.target[0],t.hide.target[0],z.rendered&&G.tooltip[0],t.position.container[0],t.position.viewport[0],b,document];z.rendered?a([]).pushStack(a.grep(c,function(a){return typeof a==="object"})).unbind(F):t.show.target.unbind(F+"-create")}function Q(){function p(a){E.is(":visible")&&z.reposition(a)}function o(a){if(E.hasClass(m))return e;clearTimeout(z.timers.inactive),z.timers.inactive=setTimeout(function(){z.hide(a)},t.hide.inactive)}function l(b){if(E.hasClass(m)||C||D)return e;var d=a(b.relatedTarget||b.target),g=d.closest(n)[0]===E[0],h=d[0]===f.show[0];clearTimeout(z.timers.show),clearTimeout(z.timers.hide);if(c.target==="mouse"&&g||t.hide.fixed&&(/mouse(out|leave|move)/.test(b.type)&&(g||h)))try{b.preventDefault(),b.stopImmediatePropagation()}catch(i){}else t.hide.delay>0?z.timers.hide=setTimeout(function(){z.hide(b)},t.hide.delay):z.hide(b)}function k(a){if(E.hasClass(m))return e;clearTimeout(z.timers.show),clearTimeout(z.timers.hide);var b=function(){z.toggle(d,a)};t.show.delay>0?z.timers.show=setTimeout(b,t.show.delay):b()}var c=t.position,f={show:t.show.target,hide:t.hide.target,viewport:a(c.viewport),document:a(document),body:a(document.body),window:a(b)},h={show:a.trim(""+t.show.event).split(" "),hide:a.trim(""+t.hide.event).split(" ")},j=a.browser.msie&&parseInt(a.browser.version,10)===6;E.bind("mouseenter"+F+" mouseleave"+F,function(a){var b=a.type==="mouseenter";b&&z.focus(a),E.toggleClass(q,b)}),t.hide.fixed&&(f.hide=f.hide.add(E),E.bind("mouseover"+F,function(){E.hasClass(m)||clearTimeout(z.timers.hide)})),/mouse(out|leave)/i.test(t.hide.event)?t.hide.leave==="window"&&f.window.bind("mouseout"+F+" blur"+F,function(a){/select|option/.test(a.target)&&!a.relatedTarget&&z.hide(a)}):/mouse(over|enter)/i.test(t.show.event)&&f.hide.bind("mouseleave"+F,function(a){clearTimeout(z.timers.show)}),(""+t.hide.event).indexOf("unfocus")>-1&&f.body.bind("mousedown"+F,function(b){var c=a(b.target),d=!E.hasClass(m)&&E.is(":visible");c[0]!==E[0]&&c.parents(n).length===0&&c.add(s).length>1&&!c.attr("disabled")&&z.hide(b)}),"number"===typeof t.hide.inactive&&(f.show.bind("qtip-"+w+"-inactive",o),a.each(g.inactiveEvents,function(a,b){f.hide.add(G.tooltip).bind(b+F+"-inactive",o)})),a.each(h.hide,function(b,c){var d=a.inArray(c,h.show),e=a(f.hide);d>-1&&e.add(f.show).length===e.length||c==="unfocus"?(f.show.bind(c+F,function(a){E.is(":visible")?l(a):k(a)}),delete h.show[d]):f.hide.bind(c+F,l)}),a.each(h.show,function(a,b){f.show.bind(b+F,k)}),"number"===typeof t.hide.distance&&f.show.add(E).bind("mousemove"+F,function(a){var b=H.origin||{},c=t.hide.distance,d=Math.abs;(d(a.pageX-b.pageX)>=c||d(a.pageY-b.pageY)>=c)&&z.hide(a)}),c.target==="mouse"&&(f.show.bind("mousemove"+F,function(a){i={pageX:a.pageX,pageY:a.pageY,type:"mousemove"}}),c.adjust.mouse&&(t.hide.event&&E.bind("mouseleave"+F,function(a){(a.relatedTarget||a.target)!==f.show[0]&&z.hide(a)}),f.document.bind("mousemove"+F,function(a){!E.hasClass(m)&&E.is(":visible")&&z.reposition(a||i)}))),(c.adjust.resize||f.viewport.length)&&(a.event.special.resize?f.viewport:f.window).bind("resize"+F,p),(f.viewport.length||j&&E.css("position")==="fixed")&&f.viewport.bind("scroll"+F,p)}function P(b,d){function g(b){function i(c){c&&(delete h[c.src],clearTimeout(z.timers.img[c.src]),a(c).unbind(F)),a.isEmptyObject(h)&&(z.redraw(),d!==e&&z.reposition(H.event),b())}var g,h={};if((g=f.find("img:not([height]):not([width])")).length===0)return i();g.each(function(b,d){if(h[d.src]===c){var e=0,f=3;(function g(){if(d.height||d.width||e>f)return i(d);e+=1,z.timers.img[d.src]=setTimeout(g,700)})(),a(d).bind("error"+F+" load"+F,function(){i(this)}),h[d.src]=d}})}var f=G.content;if(!z.rendered||!b)return e;a.isFunction(b)&&(b=b.call(s,H.event,z)||""),b.jquery&&b.length>0?f.empty().append(b.css({display:"block"})):f.html(b),z.rendered<0?E.queue("fx",g):(D=0,g(a.noop));return z}function O(b,c){var d=G.title;if(!z.rendered||!b)return e;a.isFunction(b)&&(b=b.call(s,H.event,z));if(b===e)return K(e);b.jquery&&b.length>0?d.empty().append(b.css({display:"block"})):d.html(b),z.redraw(),c!==e&&z.rendered&&E.is(":visible")&&z.reposition(H.event)}function N(a){var b=G.button,c=G.title;if(!z.rendered)return e;a?(c||M(),L()):b.remove()}function M(){var b=B+"-title";G.titlebar&&K(),G.titlebar=a("<div />",{"class":k+"-titlebar "+(t.style.widget?"ui-widget-header":"")}).append(G.title=a("<div />",{id:b,"class":k+"-title","aria-atomic":d})).insertBefore(G.content).delegate(".ui-tooltip-close","mousedown keydown mouseup keyup mouseout",function(b){a(this).toggleClass("ui-state-active ui-state-focus",b.type.substr(-4)==="down")}).delegate(".ui-tooltip-close","mouseover mouseout",function(b){a(this).toggleClass("ui-state-hover",b.type==="mouseover")}),t.content.title.button?L():z.rendered&&z.redraw()}function L(){var b=t.content.title.button,c=typeof b==="string",d=c?b:"Close tooltip";G.button&&G.button.remove(),b.jquery?G.button=b:G.button=a("<a />",{"class":"ui-state-default ui-tooltip-close "+(t.style.widget?"":k+"-icon"),title:d,"aria-label":d}).prepend(a("<span />",{"class":"ui-icon ui-icon-close",html:"&times;"})),G.button.appendTo(G.titlebar).attr("role","button").click(function(a){E.hasClass(m)||z.hide(a);return e}),z.redraw()}function K(a){G.title&&(G.titlebar.remove(),G.titlebar=G.title=G.button=f,a!==e&&z.reposition())}function J(){var a=t.style.widget;E.toggleClass(l,a).toggleClass(o,!a),G.content.toggleClass(l+"-content",a),G.titlebar&&G.titlebar.toggleClass(l+"-header",a),G.button&&G.button.toggleClass(k+"-icon",!a)}function I(a){var b=0,c,d=t,e=a.split(".");while(d=d[e[b++]])b<e.length&&(c=d);return[c||t,e.pop()]}var z=this,A=document.body,B=k+"-"+w,C=0,D=0,E=a(),F=".qtip-"+w,G,H;z.id=w,z.rendered=e,z.elements=G={target:s},z.timers={img:{}},z.options=t,z.checks={},z.plugins={},z.cache=H={event:{},target:a(),disabled:e,attr:y},z.checks.builtin={"^id$":function(b,c,f){var h=f===d?g.nextid:f,i=k+"-"+h;h!==e&&h.length>0&&!a("#"+i).length&&(E[0].id=i,G.content[0].id=i+"-content",G.title[0].id=i+"-title")},"^content.text$":function(a,b,c){P(c)},"^content.title.text$":function(a,b,c){if(!c)return K();!G.title&&c&&M(),O(c)},"^content.title.button$":function(a,b,c){N(c)},"^position.(my|at)$":function(a,b,c){"string"===typeof c&&(a[b]=new h.Corner(c))},"^position.container$":function(a,b,c){z.rendered&&E.appendTo(c)},"^show.ready$":function(){z.rendered?z.toggle(d):z.render(1)},"^style.classes$":function(a,b,c){E.attr("class",k+" qtip ui-helper-reset "+c)},"^style.widget|content.title":J,"^events.(render|show|move|hide|focus|blur)$":function(b,c,d){E[(a.isFunction(d)?"":"un")+"bind"]("tooltip"+c,d)},"^(show|hide|position).(event|target|fixed|inactive|leave|distance|viewport|adjust)":function(){var a=t.position;E.attr("tracking",a.target==="mouse"&&a.adjust.mouse),R(),Q()}},a.extend(z,{render:function(b){if(z.rendered)return z;var c=t.content.text,f=t.content.title.text,g=t.position,i=a.Event("tooltiprender");a.attr(s[0],"aria-describedby",B),E=G.tooltip=a("<div/>",{id:B,"class":k+" qtip ui-helper-reset "+o+" "+t.style.classes+" "+k+"-pos-"+t.position.my.abbrev(),width:t.style.width||"",height:t.style.height||"",tracking:g.target==="mouse"&&g.adjust.mouse,role:"alert","aria-live":"polite","aria-atomic":e,"aria-describedby":B+"-content","aria-hidden":d}).toggleClass(m,H.disabled).data("qtip",z).appendTo(t.position.container).append(G.content=a("<div />",{"class":k+"-content",id:B+"-content","aria-atomic":d})),z.rendered=-1,C=D=1,f&&(M(),a.isFunction(f)||O(f,e)),a.isFunction(c)||P(c,e),z.rendered=d,J(),a.each(t.events,function(b,c){a.isFunction(c)&&E.bind(b==="toggle"?"tooltipshow tooltiphide":"tooltip"+b,c)}),a.each(h,function(){this.initialize==="render"&&this(z)}),Q(),E.queue("fx",function(a){i.originalEvent=H.event,E.trigger(i,[z]),C=D=0,z.redraw(),(t.show.ready||b)&&z.toggle(d,H.event),a()});return z},get:function(a){var b,c;switch(a.toLowerCase()){case"dimensions":b={height:E.outerHeight(),width:E.outerWidth()};break;case"offset":b=h.offset(E,t.position.container);break;default:c=I(a.toLowerCase()),b=c[0][c[1]],b=b.precedance?b.string():b}return b},set:function(b,c){function m(a,b){var c,d,e;for(c in k)for(d in k[c])if(e=(new RegExp(d,"i")).exec(a))b.push(e),k[c][d].apply(z,b)}var g=/^position\.(my|at|adjust|target|container)|style|content|show\.ready/i,h=/^content\.(title|attr)|style/i,i=e,j=e,k=z.checks,l;"string"===typeof b?(l=b,b={},b[l]=c):b=a.extend(d,{},b),a.each(b,function(c,d){var e=I(c.toLowerCase()),f;f=e[0][e[1]],e[0][e[1]]="object"===typeof d&&d.nodeType?a(d):d,b[c]=[e[0],e[1],d,f],i=g.test(c)||i,j=h.test(c)||j}),x(t),C=D=1,a.each(b,m),C=D=0,E.is(":visible")&&z.rendered&&(i&&z.reposition(t.position.target==="mouse"?f:H.event),j&&z.redraw());return z},toggle:function(b,c){function q(){b?(a.browser.msie&&E[0].style.removeAttribute("filter"),E.css("overflow",""),"string"===typeof h.autofocus&&a(h.autofocus,E).focus(),p=a.Event("tooltipvisible"),p.originalEvent=c?H.event:f,E.trigger(p,[z]),h.target.trigger("qtip-"+w+"-inactive")):E.css({display:"",visibility:"",opacity:"",left:"",top:""})}if(!z.rendered)return b?z.render(1):z;var g=b?"show":"hide",h=t[g],j=E.is(":visible"),k=!c||t[g].target.length<2||H.target[0]===c.target,l=t.position,m=t.content,o,p;(typeof b).search("boolean|number")&&(b=!j);if(!E.is(":animated")&&j===b&&k)return z;if(c){if(/over|enter/.test(c.type)&&/out|leave/.test(H.event.type)&&c.target===t.show.target[0]&&E.has(c.relatedTarget).length)return z;H.event=a.extend({},c)}p=a.Event("tooltip"+g),p.originalEvent=c?H.event:f,E.trigger(p,[z,90]);if(p.isDefaultPrevented())return z;a.attr(E[0],"aria-hidden",!b),b?(H.origin=a.extend({},i),z.focus(c),a.isFunction(m.text)&&P(m.text,e),a.isFunction(m.title.text)&&O(m.title.text,e),!v&&l.target==="mouse"&&l.adjust.mouse&&(a(document).bind("mousemove.qtip",function(a){i={pageX:a.pageX,pageY:a.pageY,type:"mousemove"}}),v=d),z.reposition(c),(p.solo=!!h.solo)&&a(n,h.solo).not(E).qtip("hide",p)):(clearTimeout(z.timers.show),delete H.origin,v&&!a(n+'[tracking="true"]:visible',h.solo).not(E).length&&(a(document).unbind("mousemove.qtip"),v=e),z.blur(c)),k&&E.stop(0,1),h.effect===e?(E[g](),q.call(E)):a.isFunction(h.effect)?(h.effect.call(E,z),E.queue("fx",function(a){q(),a()})):E.fadeTo(90,b?1:0,q),b&&h.target.trigger("qtip-"+w+"-inactive");return z},show:function(a){return z.toggle(d,a)},hide:function(a){return z.toggle(e,a)},focus:function(b){if(!z.rendered)return z;var c=a(n),d=parseInt(E[0].style.zIndex,10),e=g.zindex+c.length,f=a.extend({},b),h,i;E.hasClass(p)||(i=a.Event("tooltipfocus"),i.originalEvent=f,E.trigger(i,[z,e]),i.isDefaultPrevented()||(d!==e&&(c.each(function(){this.style.zIndex>d&&(this.style.zIndex=this.style.zIndex-1)}),c.filter("."+p).qtip("blur",f)),E.addClass(p)[0].style.zIndex=e));return z},blur:function(b){var c=a.extend({},b),d;E.removeClass(p),d=a.Event("tooltipblur"),d.originalEvent=c,E.trigger(d,[z]);return z},reposition:function(c,d){if(!z.rendered||C)return z;C=1;var f=t.position.target,g=t.position,j=g.my,l=g.at,m=g.adjust,n=m.method.split(" "),o=E.outerWidth(),p=E.outerHeight(),q=0,r=0,s=a.Event("tooltipmove"),u=E.css("position")==="fixed",v=g.viewport,w={left:0,top:0},x=g.container,y=e,B=z.plugins.tip,D={horizontal:n[0],vertical:n[1]=n[1]||n[0],enabled:v.jquery&&f[0]!==b&&f[0]!==A&&m.method!=="none",left:function(a){var b=D.horizontal==="shift",c=-x.offset.left+v.offset.left+v.scrollLeft,d=j.x==="left"?o:j.x==="right"?-o:-o/2,e=l.x==="left"?q:l.x==="right"?-q:-q/2,f=B&&B.size?B.size.width||0:0,g=B&&B.corner&&B.corner.precedance==="x"&&!b?f:0,h=c-a+g,i=a+o-v.width-c+g,k=d-(j.precedance==="x"||j.x===j.y?e:0),n=j.x==="center";b?(g=B&&B.corner&&B.corner.precedance==="y"?f:0,k=(j.x==="left"?1:-1)*d-g,w.left+=h>0?h:i>0?-i:0,w.left=Math.max(-x.offset.left+v.offset.left+(g&&B.corner.x==="center"?B.offset:0),a-k,Math.min(Math.max(-x.offset.left+v.offset.left+v.width,a+k),w.left))):(h>0&&(j.x!=="left"||i>0)?w.left-=k:i>0&&(j.x!=="right"||h>0)&&(w.left-=n?-k:k),w.left!==a&&n&&(w.left-=m.x),w.left<c&&-w.left>i&&(w.left=a));return w.left-a},top:function(a){var b=D.vertical==="shift",c=-x.offset.top+v.offset.top+v.scrollTop,d=j.y==="top"?p:j.y==="bottom"?-p:-p/2,e=l.y==="top"?r:l.y==="bottom"?-r:-r/2,f=B&&B.size?B.size.height||0:0,g=B&&B.corner&&B.corner.precedance==="y"&&!b?f:0,h=c-a+g,i=a+p-v.height-c+g,k=d-(j.precedance==="y"||j.x===j.y?e:0),n=j.y==="center";b?(g=B&&B.corner&&B.corner.precedance==="x"?f:0,k=(j.y==="top"?1:-1)*d-g,w.top+=h>0?h:i>0?-i:0,w.top=Math.max(-x.offset.top+v.offset.top+(g&&B.corner.x==="center"?B.offset:0),a-k,Math.min(Math.max(-x.offset.top+v.offset.top+v.height,a+k),w.top))):(h>0&&(j.y!=="top"||i>0)?w.top-=k:i>0&&(j.y!=="bottom"||h>0)&&(w.top-=n?-k:k),w.top!==a&&n&&(w.top-=m.y),w.top<0&&-w.top>i&&(w.top=a));return w.top-a}},F;if(a.isArray(f)&&f.length===2)l={x:"left",y:"top"},w={left:f[0],top:f[1]};else if(f==="mouse"&&(c&&c.pageX||H.event.pageX))l={x:"left",y:"top"},c=(c&&(c.type==="resize"||c.type==="scroll")?H.event:c&&c.pageX&&c.type==="mousemove"?c:i&&i.pageX&&(m.mouse||!c||!c.pageX)?{pageX:i.pageX,pageY:i.pageY}:!m.mouse&&H.origin&&H.origin.pageX?H.origin:c)||c||H.event||i||{},w={top:c.pageY,left:c.pageX};else{f==="event"?c&&c.target&&c.type!=="scroll"&&c.type!=="resize"?f=H.target=a(c.target):f=H.target:H.target=a(f),f=a(f).eq(0);if(f.length===0)return z;f[0]===document||f[0]===b?(q=h.iOS?b.innerWidth:f.width(),r=h.iOS?b.innerHeight:f.height(),f[0]===b&&(w={top:u||h.iOS?(v||f).scrollTop():0,left:u||h.iOS?(v||f).scrollLeft():0})):f.is("area")&&h.imagemap?w=h.imagemap(f,l,D.enabled?n:e):f[0].namespaceURI==="http://www.w3.org/2000/svg"&&h.svg?w=h.svg(f,l):(q=f.outerWidth(),r=f.outerHeight(),w=h.offset(f,x)),w.offset&&(q=w.width,r=w.height,y=w.flipoffset,w=w.offset);if(h.iOS<4.1&&h.iOS>3.1||h.iOS==4.3||!h.iOS&&u)F=a(b),w.left-=F.scrollLeft(),w.top-=F.scrollTop();w.left+=l.x==="right"?q:l.x==="center"?q/2:0,w.top+=l.y==="bottom"?r:l.y==="center"?r/2:0}w.left+=m.x+(j.x==="right"?-o:j.x==="center"?-o/2:0),w.top+=m.y+(j.y==="bottom"?-p:j.y==="center"?-p/2:0),D.enabled?(v={elem:v,height:v[(v[0]===b?"h":"outerH")+"eight"](),width:v[(v[0]===b?"w":"outerW")+"idth"](),scrollLeft:u?0:v.scrollLeft(),scrollTop:u?0:v.scrollTop(),offset:v.offset()||{left:0,top:0}},x={elem:x,scrollLeft:x.scrollLeft(),scrollTop:x.scrollTop(),offset:x.offset()||{left:0,top:0}},w.adjusted={left:D.horizontal!=="none"?D.left(w.left):0,top:D.vertical!=="none"?D.top(w.top):0},w.adjusted.left+w.adjusted.top&&E.attr("class",E[0].className.replace(/ui-tooltip-pos-\w+/i,k+"-pos-"+j.abbrev())),y&&w.adjusted.left&&(w.left+=y.left),y&&w.adjusted.top&&(w.top+=y.top)):w.adjusted={left:0,top:0},s.originalEvent=a.extend({},c),E.trigger(s,[z,w,v.elem||v]);if(s.isDefaultPrevented())return z;delete w.adjusted,d===e||isNaN(w.left)||isNaN(w.top)||f==="mouse"||!a.isFunction(g.effect)?E.css(w):a.isFunction(g.effect)&&(g.effect.call(E,z,a.extend({},w)),E.queue(function(b){a(this).css({opacity:"",height:""}),a.browser.msie&&this.style.removeAttribute("filter"),b()})),C=0;return z},redraw:function(){if(z.rendered<1||D)return z;var a=t.position.container,b,c,d,e;D=1,t.style.height&&E.css("height",t.style.height),t.style.width?E.css("width",t.style.width):(E.css("width","").addClass(r),c=E.width()+1,d=E.css("max-width")||"",e=E.css("min-width")||"",b=(d+e).indexOf("%")>-1?a.width()/100:0,d=(d.indexOf("%")>-1?b:1)*parseInt(d,10)||c,e=(e.indexOf("%")>-1?b:1)*parseInt(e,10)||0,c=d+e?Math.min(Math.max(c,e),d):c,E.css("width",Math.round(c)).removeClass(r)),D=0;return z},disable:function(b){"boolean"!==typeof b&&(b=!E.hasClass(m)&&!H.disabled),z.rendered?(E.toggleClass(m,b),a.attr(E[0],"aria-disabled",b)):H.disabled=!!b;return z},enable:function(){return z.disable(e)},destroy:function(){var b=s[0],c=a.attr(b,u),d=s.data("qtip");z.rendered&&(E.remove(),a.each(z.plugins,function(){this.destroy&&this.destroy()})),clearTimeout(z.timers.show),clearTimeout(z.timers.hide),R();if(!d||z===d)a.removeData(b,"qtip"),t.suppress&&c&&(a.attr(b,"title",c),s.removeAttr(u)),s.removeAttr("aria-describedby");s.unbind(".qtip-"+w),delete j[z.id];return s}})}function x(b){var c;if(!b||"object"!==typeof b)return e;if(b.metadata===f||"object"!==typeof b.metadata)b.metadata={type:b.metadata};if("content"in b){if(b.content===f||"object"!==typeof b.content||b.content.jquery)b.content={text:b.content};c=b.content.text||e,!a.isFunction(c)&&(!c&&!c.attr||c.length<1||"object"===typeof c&&!c.jquery)&&(b.content.text=e);if("title"in b.content){if(b.content.title===f||"object"!==typeof b.content.title)b.content.title={text:b.content.title};c=b.content.title.text||e,!a.isFunction(c)&&(!c&&!c.attr||c.length<1||"object"===typeof c&&!c.jquery)&&(b.content.title.text=e)}}if("position"in b)if(b.position===f||"object"!==typeof b.position)b.position={my:b.position,at:b.position};if("show"in b)if(b.show===f||"object"!==typeof b.show)b.show.jquery?b.show={target:b.show}:b.show={event:b.show};if("hide"in b)if(b.hide===f||"object"!==typeof b.hide)b.hide.jquery?b.hide={target:b.hide}:b.hide={event:b.hide};if("style"in b)if(b.style===f||"object"!==typeof b.style)b.style={classes:b.style};a.each(h,function(){this.sanitize&&this.sanitize(b)});return b}function w(){w.history=w.history||[],w.history.push(arguments);if("object"===typeof console){var a=console[console.warn?"warn":"log"],b=Array.prototype.slice.call(arguments),c;typeof arguments[0]==="string"&&(b[0]="qTip2: "+b[0]),c=a.apply?a.apply(console,b):a(b)}}"use strict";var d=!0,e=!1,f=null,g,h,i,j={},k="ui-tooltip",l="ui-widget",m="ui-state-disabled",n="div.qtip."+k,o=k+"-default",p=k+"-focus",q=k+"-hover",r=k+"-fluid",s="-31000px",t="_replacedByqTip",u="oldtitle",v;g=a.fn.qtip=function(b,h,i){var j=(""+b).toLowerCase(),k=f,l=a.makeArray(arguments).slice(1),m=l[l.length-1],n=this[0]?a.data(this[0],"qtip"):f;if(!arguments.length&&n||j==="api")return n;if("string"===typeof b){this.each(function(){var b=a.data(this,"qtip");if(!b)return d;m&&m.timeStamp&&(b.cache.event=m);if(j!=="option"&&j!=="options"||!h)b[j]&&b[j].apply(b[j],l);else if(a.isPlainObject(h)||i!==c)b.set(h,i);else{k=b.get(h);return e}});return k!==f?k:this}if("object"===typeof b||!arguments.length){n=x(a.extend(d,{},b));return g.bind.call(this,n,m)}},g.bind=function(b,f){return this.each(function(k){function r(b){function d(){p.render(typeof b==="object"||l.show.ready),m.show.add(m.hide).unbind(o)}if(p.cache.disabled)return e;p.cache.event=a.extend({},b),p.cache.target=b?a(b.target):[c],l.show.delay>0?(clearTimeout(p.timers.show),p.timers.show=setTimeout(d,l.show.delay),n.show!==n.hide&&m.hide.bind(n.hide,function(){clearTimeout(p.timers.show)})):d()}var l,m,n,o,p,q;q=a.isArray(b.id)?b.id[k]:b.id,q=!q||q===e||q.length<1||j[q]?g.nextid++:j[q]=q,o=".qtip-"+q+"-create",p=z.call(this,q,b);if(p===e)return d;l=p.options,a.each(h,function(){this.initialize==="initialize"&&this(p)}),m={show:l.show.target,hide:l.hide.target},n={show:a.trim(""+l.show.event).replace(/ /g,o+" ")+o,hide:a.trim(""+l.hide.event).replace(/ /g,o+" ")+o},/mouse(over|enter)/i.test(n.show)&&!/mouse(out|leave)/i.test(n.hide)&&(n.hide+=" mouseleave"+o),m.show.bind("mousemove"+o,function(a){i={pageX:a.pageX,pageY:a.pageY,type:"mousemove"}}),m.show.bind(n.show,r),(l.show.ready||l.prerender)&&r(f)})},h=g.plugins={Corner:function(a){a=(""+a).replace(/([A-Z])/," $1").replace(/middle/gi,"center").toLowerCase(),this.x=(a.match(/left|right/i)||a.match(/center/)||["inherit"])[0].toLowerCase(),this.y=(a.match(/top|bottom|center/i)||["inherit"])[0].toLowerCase();var b=a.charAt(0);this.precedance=b==="t"||b==="b"?"y":"x",this.string=function(){return this.precedance==="y"?this.y+this.x:this.x+this.y},this.abbrev=function(){var a=this.x.substr(0,1),b=this.y.substr(0,1);return a===b?a:a==="c"||a!=="c"&&b!=="c"?b+a:a+b},this.clone=function(){return{x:this.x,y:this.y,precedance:this.precedance,string:this.string,abbrev:this.abbrev,clone:this.clone}}},offset:function(a,b){function i(a,b){c.left+=b*a.scrollLeft(),c.top+=b*a.scrollTop()}var c=a.offset(),d=b,e=0,f=document.body,g,h;if(d){do{d.css("position")!=="static"&&(g=d[0]===f?{left:parseInt(d.css("left"),10)||0,top:parseInt(d.css("top"),10)||0}:d.position(),c.left-=g.left+(parseInt(d.css("borderLeftWidth"),10)||0)+(parseInt(d.css("marginLeft"),10)||0),c.top-=g.top+(parseInt(d.css("borderTopWidth"),10)||0),h=d.css("overflow"),(h==="scroll"||h==="auto")&&++e);if(d[0]===f)break}while(d=d.offsetParent());b[0]!==f&&e&&i(b,1)}return c},iOS:parseFloat((""+(/CPU.*OS ([0-9_]{1,3})|(CPU like).*AppleWebKit.*Mobile/i.exec(navigator.userAgent)||[0,""])[1]).replace("undefined","3_2").replace("_","."))||e,fn:{attr:function(b,c){if(this.length){var d=this[0],e="title",f=a.data(d,"qtip");if(b===e&&f&&"object"===typeof f&&f.options.suppress){if(arguments.length<2)return a.attr(d,u);f&&f.options.content.attr===e&&f.cache.attr&&f.set("content.text",c);return this.attr(u,c)}}return a.fn["attr"+t].apply(this,arguments)},clone:function(b){var c=a([]),d="title",e=a.fn["clone"+t].apply(this,arguments);b||e.filter("["+u+"]").attr("title",function(){return a.attr(this,u)}).removeAttr(u);return e},remove:a.ui?f:function(b,c){a.ui||a(this).each(function(){c||(!b||a.filter(b,[this]).length)&&a("*",this).add(this).each(function(){a(this).triggerHandler("remove")})})}}},a.each(h.fn,function(b,c){if(!c||a.fn[b+t])return d;var e=a.fn[b+t]=a.fn[b];a.fn[b]=function(){return c.apply(this,arguments)||e.apply(this,arguments)}}),g.version="nightly",g.nextid=0,g.inactiveEvents="click dblclick mousedown mouseup mousemove mouseleave mouseenter".split(" "),g.zindex=15e3,g.defaults={prerender:e,id:e,overwrite:d,suppress:d,content:{text:d,attr:"title",title:{text:e,button:e}},position:{my:"top left",at:"bottom right",target:e,container:e,viewport:e,adjust:{x:0,y:0,mouse:d,resize:d,method:"flip flip"},effect:function(b,c,d){a(this).animate(c,{duration:200,queue:e})}},show:{target:e,event:"mouseenter",effect:d,delay:90,solo:e,ready:e,autofocus:e},hide:{target:e,event:"mouseleave",effect:d,delay:0,fixed:e,inactive:e,leave:"window",distance:e},style:{classes:"",widget:e,width:e,height:e},events:{render:f,move:f,show:f,hide:f,toggle:f,visible:f,focus:f,blur:f}},h.svg=function(b,c){var d=a(document),e=b[0],f={width:0,height:0,offset:{top:1e10,left:1e10}},g,h,i,j,k;if(e.getBBox&&e.parentNode){g=e.getBBox(),h=e.getScreenCTM(),i=e.farthestViewportElement||e;if(!i.createSVGPoint)return f;j=i.createSVGPoint(),j.x=g.x,j.y=g.y,k=j.matrixTransform(h),f.offset.left=k.x,f.offset.top=k.y,j.x+=g.width,j.y+=g.height,k=j.matrixTransform(h),f.width=k.x-f.offset.left,f.height=k.y-f.offset.top,f.offset.left+=d.scrollLeft(),f.offset.top+=d.scrollTop()}return f}})(jQuery,window);
+ * qTip2 - Pretty powerful tooltips
+ * http://craigsworks.com/projects/qtip2/
+ *
+ * Version: nightly
+ * Copyright 2009-2010 Craig Michael Thompson - http://craigsworks.com
+ *
+ * Dual licensed under MIT or GPLv2 licenses
+ * http://en.wikipedia.org/wiki/MIT_License
+ * http://en.wikipedia.org/wiki/GNU_General_Public_License
+ *
+ * Date: Mon Nov 21 13:18:18.0000000000 2011
+ *//*jslint browser: true, onevar: true, undef: true, nomen: true, bitwise: true, regexp: true, newcap: true, immed: true, strict: true *//*global window: false, jQuery: false, console: false */
+(function (a, b, c) {
+ function z(b, c) {
+ var i, j, k, l, m, n = a(this), o = a(document.body), p = this === document ? o : n, q = n.metadata ? n.metadata(c.metadata) : f, r = c.metadata.type === "html5" && q ? q[c.metadata.name] : f, s = n.data(c.metadata.name || "qtipopts");
+ try {s = typeof s === "string" ? (new Function("return " + s))() : s} catch (t) {w("Unable to parse HTML5 attribute data: " + s)}
+ l = a.extend(d, {}, g.defaults, c, typeof s === "object" ? x(s) : f, x(r || q)), j = l.position, l.id = b;
+ if ("boolean" === typeof l.content.text) {
+ k = n.attr(l.content.attr);
+ if (l.content.attr !== e && k)l.content.text = k; else {
+ w("Unable to locate content for tooltip! Aborting render of tooltip on element: ", n);
+ return e
+ }
+ }
+ j.container === e && (j.container = o), j.target === e && (j.target = p), l.show.target === e && (l.show.target = p), l.show.solo === d && (l.show.solo = o), l.hide.target === e && (l.hide.target = p), l.position.viewport === d && (l.position.viewport = j.container), j.at = new h.Corner(j.at), j.my = new h.Corner(j.my);
+ if (a.data(this, "qtip"))if (l.overwrite)n.qtip("destroy"); else if (l.overwrite === e)return e;
+ l.suppress && (m = a.attr(this, "title")) && a(this).removeAttr("title").attr(u, m), i = new y(n, l, b, !!k), a.data(this, "qtip", i), n.bind("remove.qtip-" + b, function () {i.destroy()});
+ return i
+ }
+
+ function y(s, t, w, y) {
+ function R() {
+ var c = [t.show.target[0], t.hide.target[0], z.rendered && G.tooltip[0], t.position.container[0], t.position.viewport[0], b, document];
+ z.rendered ? a([]).pushStack(a.grep(c, function (a) {return typeof a === "object"})).unbind(F) : t.show.target.unbind(F + "-create")
+ }
+
+ function Q() {
+ function p(a) {E.is(":visible") && z.reposition(a)}
+
+ function o(a) {
+ if (E.hasClass(m))return e;
+ clearTimeout(z.timers.inactive), z.timers.inactive = setTimeout(function () {z.hide(a)}, t.hide.inactive)
+ }
+
+ function l(b) {
+ if (E.hasClass(m) || C || D)return e;
+ var d = a(b.relatedTarget || b.target), g = d.closest(n)[0] === E[0], h = d[0] === f.show[0];
+ clearTimeout(z.timers.show), clearTimeout(z.timers.hide);
+ if (c.target === "mouse" && g || t.hide.fixed && (/mouse(out|leave|move)/.test(b.type) && (g || h)))try {b.preventDefault(), b.stopImmediatePropagation()} catch (i) {} else t.hide.delay > 0 ? z.timers.hide = setTimeout(function () {z.hide(b)}, t.hide.delay) : z.hide(b)
+ }
+
+ function k(a) {
+ if (E.hasClass(m))return e;
+ clearTimeout(z.timers.show), clearTimeout(z.timers.hide);
+ var b = function () {z.toggle(d, a)};
+ t.show.delay > 0 ? z.timers.show = setTimeout(b, t.show.delay) : b()
+ }
+
+ var c = t.position, f = {show: t.show.target, hide: t.hide.target, viewport: a(c.viewport), document: a(document), body: a(document.body), window: a(b)}, h = {show: a.trim("" + t.show.event).split(" "), hide: a.trim("" + t.hide.event).split(" ")}, j = a.browser.msie && parseInt(a.browser.version, 10) === 6;
+ E.bind("mouseenter" + F + " mouseleave" + F, function (a) {
+ var b = a.type === "mouseenter";
+ b && z.focus(a), E.toggleClass(q, b)
+ }), t.hide.fixed && (f.hide = f.hide.add(E), E.bind("mouseover" + F, function () {E.hasClass(m) || clearTimeout(z.timers.hide)})), /mouse(out|leave)/i.test(t.hide.event) ? t.hide.leave === "window" && f.window.bind("mouseout" + F + " blur" + F, function (a) {/select|option/.test(a.target) && !a.relatedTarget && z.hide(a)}) : /mouse(over|enter)/i.test(t.show.event) && f.hide.bind("mouseleave" + F, function (a) {clearTimeout(z.timers.show)}), ("" + t.hide.event).indexOf("unfocus") > -1 && f.body.bind("mousedown" + F, function (b) {
+ var c = a(b.target), d = !E.hasClass(m) && E.is(":visible");
+ c[0] !== E[0] && c.parents(n).length === 0 && c.add(s).length > 1 && !c.attr("disabled") && z.hide(b)
+ }), "number" === typeof t.hide.inactive && (f.show.bind("qtip-" + w + "-inactive", o), a.each(g.inactiveEvents, function (a, b) {f.hide.add(G.tooltip).bind(b + F + "-inactive", o)})), a.each(h.hide, function (b, c) {
+ var d = a.inArray(c, h.show), e = a(f.hide);
+ d > -1 && e.add(f.show).length === e.length || c === "unfocus" ? (f.show.bind(c + F, function (a) {E.is(":visible") ? l(a) : k(a)}), delete h.show[d]) : f.hide.bind(c + F, l)
+ }), a.each(h.show, function (a, b) {f.show.bind(b + F, k)}), "number" === typeof t.hide.distance && f.show.add(E).bind("mousemove" + F, function (a) {
+ var b = H.origin || {}, c = t.hide.distance, d = Math.abs;
+ (d(a.pageX - b.pageX) >= c || d(a.pageY - b.pageY) >= c) && z.hide(a)
+ }), c.target === "mouse" && (f.show.bind("mousemove" + F, function (a) {i = {pageX: a.pageX, pageY: a.pageY, type: "mousemove"}}), c.adjust.mouse && (t.hide.event && E.bind("mouseleave" + F, function (a) {(a.relatedTarget || a.target) !== f.show[0] && z.hide(a)}), f.document.bind("mousemove" + F, function (a) {!E.hasClass(m) && E.is(":visible") && z.reposition(a || i)}))), (c.adjust.resize || f.viewport.length) && (a.event.special.resize ? f.viewport : f.window).bind("resize" + F, p), (f.viewport.length || j && E.css("position") === "fixed") && f.viewport.bind("scroll" + F, p)
+ }
+
+ function P(b, d) {
+ function g(b) {
+ function i(c) {c && (delete h[c.src], clearTimeout(z.timers.img[c.src]), a(c).unbind(F)), a.isEmptyObject(h) && (z.redraw(), d !== e && z.reposition(H.event), b())}
+
+ var g, h = {};
+ if ((g = f.find("img:not([height]):not([width])")).length === 0)return i();
+ g.each(function (b, d) {
+ if (h[d.src] === c) {
+ var e = 0, f = 3;
+ (function g() {
+ if (d.height || d.width || e > f)return i(d);
+ e += 1, z.timers.img[d.src] = setTimeout(g, 700)
+ })(), a(d).bind("error" + F + " load" + F, function () {i(this)}), h[d.src] = d
+ }
+ })
+ }
+
+ var f = G.content;
+ if (!z.rendered || !b)return e;
+ a.isFunction(b) && (b = b.call(s, H.event, z) || ""), b.jquery && b.length > 0 ? f.empty().append(b.css({display: "block"})) : f.html(b), z.rendered < 0 ? E.queue("fx", g) : (D = 0, g(a.noop));
+ return z
+ }
+
+ function O(b, c) {
+ var d = G.title;
+ if (!z.rendered || !b)return e;
+ a.isFunction(b) && (b = b.call(s, H.event, z));
+ if (b === e)return K(e);
+ b.jquery && b.length > 0 ? d.empty().append(b.css({display: "block"})) : d.html(b), z.redraw(), c !== e && z.rendered && E.is(":visible") && z.reposition(H.event)
+ }
+
+ function N(a) {
+ var b = G.button, c = G.title;
+ if (!z.rendered)return e;
+ a ? (c || M(), L()) : b.remove()
+ }
+
+ function M() {
+ var b = B + "-title";
+ G.titlebar && K(), G.titlebar = a("<div />", {"class": k + "-titlebar " + (t.style.widget ? "ui-widget-header" : "")}).append(G.title = a("<div />", {id: b, "class": k + "-title", "aria-atomic": d})).insertBefore(G.content).delegate(".ui-tooltip-close", "mousedown keydown mouseup keyup mouseout",function (b) {a(this).toggleClass("ui-state-active ui-state-focus", b.type.substr(-4) === "down")}).delegate(".ui-tooltip-close", "mouseover mouseout", function (b) {a(this).toggleClass("ui-state-hover", b.type === "mouseover")}), t.content.title.button ? L() : z.rendered && z.redraw()
+ }
+
+ function L() {
+ var b = t.content.title.button, c = typeof b === "string", d = c ? b : "Close tooltip";
+ G.button && G.button.remove(), b.jquery ? G.button = b : G.button = a("<a />", {"class": "ui-state-default ui-tooltip-close " + (t.style.widget ? "" : k + "-icon"), title: d, "aria-label": d}).prepend(a("<span />", {"class": "ui-icon ui-icon-close", html: "&times;"})), G.button.appendTo(G.titlebar).attr("role", "button").click(function (a) {
+ E.hasClass(m) || z.hide(a);
+ return e
+ }), z.redraw()
+ }
+
+ function K(a) {G.title && (G.titlebar.remove(), G.titlebar = G.title = G.button = f, a !== e && z.reposition())}
+
+ function J() {
+ var a = t.style.widget;
+ E.toggleClass(l, a).toggleClass(o, !a), G.content.toggleClass(l + "-content", a), G.titlebar && G.titlebar.toggleClass(l + "-header", a), G.button && G.button.toggleClass(k + "-icon", !a)
+ }
+
+ function I(a) {
+ var b = 0, c, d = t, e = a.split(".");
+ while (d = d[e[b++]])b < e.length && (c = d);
+ return[c || t, e.pop()]
+ }
+
+ var z = this, A = document.body, B = k + "-" + w, C = 0, D = 0, E = a(), F = ".qtip-" + w, G, H;
+ z.id = w, z.rendered = e, z.elements = G = {target: s}, z.timers = {img: {}}, z.options = t, z.checks = {}, z.plugins = {}, z.cache = H = {event: {}, target: a(), disabled: e, attr: y}, z.checks.builtin = {"^id$": function (b, c, f) {
+ var h = f === d ? g.nextid : f, i = k + "-" + h;
+ h !== e && h.length > 0 && !a("#" + i).length && (E[0].id = i, G.content[0].id = i + "-content", G.title[0].id = i + "-title")
+ }, "^content.text$": function (a, b, c) {P(c)}, "^content.title.text$": function (a, b, c) {
+ if (!c)return K();
+ !G.title && c && M(), O(c)
+ }, "^content.title.button$": function (a, b, c) {N(c)}, "^position.(my|at)$": function (a, b, c) {"string" === typeof c && (a[b] = new h.Corner(c))}, "^position.container$": function (a, b, c) {z.rendered && E.appendTo(c)}, "^show.ready$": function () {z.rendered ? z.toggle(d) : z.render(1)}, "^style.classes$": function (a, b, c) {E.attr("class", k + " qtip ui-helper-reset " + c)}, "^style.widget|content.title": J, "^events.(render|show|move|hide|focus|blur)$": function (b, c, d) {E[(a.isFunction(d) ? "" : "un") + "bind"]("tooltip" + c, d)}, "^(show|hide|position).(event|target|fixed|inactive|leave|distance|viewport|adjust)": function () {
+ var a = t.position;
+ E.attr("tracking", a.target === "mouse" && a.adjust.mouse), R(), Q()
+ }}, a.extend(z, {render: function (b) {
+ if (z.rendered)return z;
+ var c = t.content.text, f = t.content.title.text, g = t.position, i = a.Event("tooltiprender");
+ a.attr(s[0], "aria-describedby", B), E = G.tooltip = a("<div/>", {id: B, "class": k + " qtip ui-helper-reset " + o + " " + t.style.classes + " " + k + "-pos-" + t.position.my.abbrev(), width: t.style.width || "", height: t.style.height || "", tracking: g.target === "mouse" && g.adjust.mouse, role: "alert", "aria-live": "polite", "aria-atomic": e, "aria-describedby": B + "-content", "aria-hidden": d}).toggleClass(m, H.disabled).data("qtip", z).appendTo(t.position.container).append(G.content = a("<div />", {"class": k + "-content", id: B + "-content", "aria-atomic": d})), z.rendered = -1, C = D = 1, f && (M(), a.isFunction(f) || O(f, e)), a.isFunction(c) || P(c, e), z.rendered = d, J(), a.each(t.events, function (b, c) {a.isFunction(c) && E.bind(b === "toggle" ? "tooltipshow tooltiphide" : "tooltip" + b, c)}), a.each(h, function () {this.initialize === "render" && this(z)}), Q(), E.queue("fx", function (a) {i.originalEvent = H.event, E.trigger(i, [z]), C = D = 0, z.redraw(), (t.show.ready || b) && z.toggle(d, H.event), a()});
+ return z
+ }, get: function (a) {
+ var b, c;
+ switch (a.toLowerCase()) {
+ case"dimensions":
+ b = {height: E.outerHeight(), width: E.outerWidth()};
+ break;
+ case"offset":
+ b = h.offset(E, t.position.container);
+ break;
+ default:
+ c = I(a.toLowerCase()), b = c[0][c[1]], b = b.precedance ? b.string() : b
+ }
+ return b
+ }, set: function (b, c) {
+ function m(a, b) {
+ var c, d, e;
+ for (c in k)for (d in k[c])if (e = (new RegExp(d, "i")).exec(a))b.push(e), k[c][d].apply(z, b)
+ }
+
+ var g = /^position\.(my|at|adjust|target|container)|style|content|show\.ready/i, h = /^content\.(title|attr)|style/i, i = e, j = e, k = z.checks, l;
+ "string" === typeof b ? (l = b, b = {}, b[l] = c) : b = a.extend(d, {}, b), a.each(b, function (c, d) {
+ var e = I(c.toLowerCase()), f;
+ f = e[0][e[1]], e[0][e[1]] = "object" === typeof d && d.nodeType ? a(d) : d, b[c] = [e[0], e[1], d, f], i = g.test(c) || i, j = h.test(c) || j
+ }), x(t), C = D = 1, a.each(b, m), C = D = 0, E.is(":visible") && z.rendered && (i && z.reposition(t.position.target === "mouse" ? f : H.event), j && z.redraw());
+ return z
+ }, toggle: function (b, c) {
+ function q() {b ? (a.browser.msie && E[0].style.removeAttribute("filter"), E.css("overflow", ""), "string" === typeof h.autofocus && a(h.autofocus, E).focus(), p = a.Event("tooltipvisible"), p.originalEvent = c ? H.event : f, E.trigger(p, [z]), h.target.trigger("qtip-" + w + "-inactive")) : E.css({display: "", visibility: "", opacity: "", left: "", top: ""})}
+
+ if (!z.rendered)return b ? z.render(1) : z;
+ var g = b ? "show" : "hide", h = t[g], j = E.is(":visible"), k = !c || t[g].target.length < 2 || H.target[0] === c.target, l = t.position, m = t.content, o, p;
+ (typeof b).search("boolean|number") && (b = !j);
+ if (!E.is(":animated") && j === b && k)return z;
+ if (c) {
+ if (/over|enter/.test(c.type) && /out|leave/.test(H.event.type) && c.target === t.show.target[0] && E.has(c.relatedTarget).length)return z;
+ H.event = a.extend({}, c)
+ }
+ p = a.Event("tooltip" + g), p.originalEvent = c ? H.event : f, E.trigger(p, [z, 90]);
+ if (p.isDefaultPrevented())return z;
+ a.attr(E[0], "aria-hidden", !b), b ? (H.origin = a.extend({}, i), z.focus(c), a.isFunction(m.text) && P(m.text, e), a.isFunction(m.title.text) && O(m.title.text, e), !v && l.target === "mouse" && l.adjust.mouse && (a(document).bind("mousemove.qtip", function (a) {i = {pageX: a.pageX, pageY: a.pageY, type: "mousemove"}}), v = d), z.reposition(c), (p.solo = !!h.solo) && a(n, h.solo).not(E).qtip("hide", p)) : (clearTimeout(z.timers.show), delete H.origin, v && !a(n + '[tracking="true"]:visible', h.solo).not(E).length && (a(document).unbind("mousemove.qtip"), v = e), z.blur(c)), k && E.stop(0, 1), h.effect === e ? (E[g](), q.call(E)) : a.isFunction(h.effect) ? (h.effect.call(E, z), E.queue("fx", function (a) {q(), a()})) : E.fadeTo(90, b ? 1 : 0, q), b && h.target.trigger("qtip-" + w + "-inactive");
+ return z
+ }, show: function (a) {return z.toggle(d, a)}, hide: function (a) {return z.toggle(e, a)}, focus: function (b) {
+ if (!z.rendered)return z;
+ var c = a(n), d = parseInt(E[0].style.zIndex, 10), e = g.zindex + c.length, f = a.extend({}, b), h, i;
+ E.hasClass(p) || (i = a.Event("tooltipfocus"), i.originalEvent = f, E.trigger(i, [z, e]), i.isDefaultPrevented() || (d !== e && (c.each(function () {this.style.zIndex > d && (this.style.zIndex = this.style.zIndex - 1)}), c.filter("." + p).qtip("blur", f)), E.addClass(p)[0].style.zIndex = e));
+ return z
+ }, blur: function (b) {
+ var c = a.extend({}, b), d;
+ E.removeClass(p), d = a.Event("tooltipblur"), d.originalEvent = c, E.trigger(d, [z]);
+ return z
+ }, reposition: function (c, d) {
+ if (!z.rendered || C)return z;
+ C = 1;
+ var f = t.position.target, g = t.position, j = g.my, l = g.at, m = g.adjust, n = m.method.split(" "), o = E.outerWidth(), p = E.outerHeight(), q = 0, r = 0, s = a.Event("tooltipmove"), u = E.css("position") === "fixed", v = g.viewport, w = {left: 0, top: 0}, x = g.container, y = e, B = z.plugins.tip, D = {horizontal: n[0], vertical: n[1] = n[1] || n[0], enabled: v.jquery && f[0] !== b && f[0] !== A && m.method !== "none", left: function (a) {
+ var b = D.horizontal === "shift", c = -x.offset.left + v.offset.left + v.scrollLeft, d = j.x === "left" ? o : j.x === "right" ? -o : -o / 2, e = l.x === "left" ? q : l.x === "right" ? -q : -q / 2, f = B && B.size ? B.size.width || 0 : 0, g = B && B.corner && B.corner.precedance === "x" && !b ? f : 0, h = c - a + g, i = a + o - v.width - c + g, k = d - (j.precedance === "x" || j.x === j.y ? e : 0), n = j.x === "center";
+ b ? (g = B && B.corner && B.corner.precedance === "y" ? f : 0, k = (j.x === "left" ? 1 : -1) * d - g, w.left += h > 0 ? h : i > 0 ? -i : 0, w.left = Math.max(-x.offset.left + v.offset.left + (g && B.corner.x === "center" ? B.offset : 0), a - k, Math.min(Math.max(-x.offset.left + v.offset.left + v.width, a + k), w.left))) : (h > 0 && (j.x !== "left" || i > 0) ? w.left -= k : i > 0 && (j.x !== "right" || h > 0) && (w.left -= n ? -k : k), w.left !== a && n && (w.left -= m.x), w.left < c && -w.left > i && (w.left = a));
+ return w.left - a
+ }, top: function (a) {
+ var b = D.vertical === "shift", c = -x.offset.top + v.offset.top + v.scrollTop, d = j.y === "top" ? p : j.y === "bottom" ? -p : -p / 2, e = l.y === "top" ? r : l.y === "bottom" ? -r : -r / 2, f = B && B.size ? B.size.height || 0 : 0, g = B && B.corner && B.corner.precedance === "y" && !b ? f : 0, h = c - a + g, i = a + p - v.height - c + g, k = d - (j.precedance === "y" || j.x === j.y ? e : 0), n = j.y === "center";
+ b ? (g = B && B.corner && B.corner.precedance === "x" ? f : 0, k = (j.y === "top" ? 1 : -1) * d - g, w.top += h > 0 ? h : i > 0 ? -i : 0, w.top = Math.max(-x.offset.top + v.offset.top + (g && B.corner.x === "center" ? B.offset : 0), a - k, Math.min(Math.max(-x.offset.top + v.offset.top + v.height, a + k), w.top))) : (h > 0 && (j.y !== "top" || i > 0) ? w.top -= k : i > 0 && (j.y !== "bottom" || h > 0) && (w.top -= n ? -k : k), w.top !== a && n && (w.top -= m.y), w.top < 0 && -w.top > i && (w.top = a));
+ return w.top - a
+ }}, F;
+ if (a.isArray(f) && f.length === 2)l = {x: "left", y: "top"}, w = {left: f[0], top: f[1]}; else if (f === "mouse" && (c && c.pageX || H.event.pageX))l = {x: "left", y: "top"}, c = (c && (c.type === "resize" || c.type === "scroll") ? H.event : c && c.pageX && c.type === "mousemove" ? c : i && i.pageX && (m.mouse || !c || !c.pageX) ? {pageX: i.pageX, pageY: i.pageY} : !m.mouse && H.origin && H.origin.pageX ? H.origin : c) || c || H.event || i || {}, w = {top: c.pageY, left: c.pageX}; else {
+ f === "event" ? c && c.target && c.type !== "scroll" && c.type !== "resize" ? f = H.target = a(c.target) : f = H.target : H.target = a(f), f = a(f).eq(0);
+ if (f.length === 0)return z;
+ f[0] === document || f[0] === b ? (q = h.iOS ? b.innerWidth : f.width(), r = h.iOS ? b.innerHeight : f.height(), f[0] === b && (w = {top: u || h.iOS ? (v || f).scrollTop() : 0, left: u || h.iOS ? (v || f).scrollLeft() : 0})) : f.is("area") && h.imagemap ? w = h.imagemap(f, l, D.enabled ? n : e) : f[0].namespaceURI === "http://www.w3.org/2000/svg" && h.svg ? w = h.svg(f, l) : (q = f.outerWidth(), r = f.outerHeight(), w = h.offset(f, x)), w.offset && (q = w.width, r = w.height, y = w.flipoffset, w = w.offset);
+ if (h.iOS < 4.1 && h.iOS > 3.1 || h.iOS == 4.3 || !h.iOS && u)F = a(b), w.left -= F.scrollLeft(), w.top -= F.scrollTop();
+ w.left += l.x === "right" ? q : l.x === "center" ? q / 2 : 0, w.top += l.y === "bottom" ? r : l.y === "center" ? r / 2 : 0
+ }
+ w.left += m.x + (j.x === "right" ? -o : j.x === "center" ? -o / 2 : 0), w.top += m.y + (j.y === "bottom" ? -p : j.y === "center" ? -p / 2 : 0), D.enabled ? (v = {elem: v, height: v[(v[0] === b ? "h" : "outerH") + "eight"](), width: v[(v[0] === b ? "w" : "outerW") + "idth"](), scrollLeft: u ? 0 : v.scrollLeft(), scrollTop: u ? 0 : v.scrollTop(), offset: v.offset() || {left: 0, top: 0}}, x = {elem: x, scrollLeft: x.scrollLeft(), scrollTop: x.scrollTop(), offset: x.offset() || {left: 0, top: 0}}, w.adjusted = {left: D.horizontal !== "none" ? D.left(w.left) : 0, top: D.vertical !== "none" ? D.top(w.top) : 0}, w.adjusted.left + w.adjusted.top && E.attr("class", E[0].className.replace(/ui-tooltip-pos-\w+/i, k + "-pos-" + j.abbrev())), y && w.adjusted.left && (w.left += y.left), y && w.adjusted.top && (w.top += y.top)) : w.adjusted = {left: 0, top: 0}, s.originalEvent = a.extend({}, c), E.trigger(s, [z, w, v.elem || v]);
+ if (s.isDefaultPrevented())return z;
+ delete w.adjusted, d === e || isNaN(w.left) || isNaN(w.top) || f === "mouse" || !a.isFunction(g.effect) ? E.css(w) : a.isFunction(g.effect) && (g.effect.call(E, z, a.extend({}, w)), E.queue(function (b) {a(this).css({opacity: "", height: ""}), a.browser.msie && this.style.removeAttribute("filter"), b()})), C = 0;
+ return z
+ }, redraw: function () {
+ if (z.rendered < 1 || D)return z;
+ var a = t.position.container, b, c, d, e;
+ D = 1, t.style.height && E.css("height", t.style.height), t.style.width ? E.css("width", t.style.width) : (E.css("width", "").addClass(r), c = E.width() + 1, d = E.css("max-width") || "", e = E.css("min-width") || "", b = (d + e).indexOf("%") > -1 ? a.width() / 100 : 0, d = (d.indexOf("%") > -1 ? b : 1) * parseInt(d, 10) || c, e = (e.indexOf("%") > -1 ? b : 1) * parseInt(e, 10) || 0, c = d + e ? Math.min(Math.max(c, e), d) : c, E.css("width", Math.round(c)).removeClass(r)), D = 0;
+ return z
+ }, disable: function (b) {
+ "boolean" !== typeof b && (b = !E.hasClass(m) && !H.disabled), z.rendered ? (E.toggleClass(m, b), a.attr(E[0], "aria-disabled", b)) : H.disabled = !!b;
+ return z
+ }, enable: function () {return z.disable(e)}, destroy: function () {
+ var b = s[0], c = a.attr(b, u), d = s.data("qtip");
+ z.rendered && (E.remove(), a.each(z.plugins, function () {this.destroy && this.destroy()})), clearTimeout(z.timers.show), clearTimeout(z.timers.hide), R();
+ if (!d || z === d)a.removeData(b, "qtip"), t.suppress && c && (a.attr(b, "title", c), s.removeAttr(u)), s.removeAttr("aria-describedby");
+ s.unbind(".qtip-" + w), delete j[z.id];
+ return s
+ }})
+ }
+
+ function x(b) {
+ var c;
+ if (!b || "object" !== typeof b)return e;
+ if (b.metadata === f || "object" !== typeof b.metadata)b.metadata = {type: b.metadata};
+ if ("content"in b) {
+ if (b.content === f || "object" !== typeof b.content || b.content.jquery)b.content = {text: b.content};
+ c = b.content.text || e, !a.isFunction(c) && (!c && !c.attr || c.length < 1 || "object" === typeof c && !c.jquery) && (b.content.text = e);
+ if ("title"in b.content) {
+ if (b.content.title === f || "object" !== typeof b.content.title)b.content.title = {text: b.content.title};
+ c = b.content.title.text || e, !a.isFunction(c) && (!c && !c.attr || c.length < 1 || "object" === typeof c && !c.jquery) && (b.content.title.text = e)
+ }
+ }
+ if ("position"in b)if (b.position === f || "object" !== typeof b.position)b.position = {my: b.position, at: b.position};
+ if ("show"in b)if (b.show === f || "object" !== typeof b.show)b.show.jquery ? b.show = {target: b.show} : b.show = {event: b.show};
+ if ("hide"in b)if (b.hide === f || "object" !== typeof b.hide)b.hide.jquery ? b.hide = {target: b.hide} : b.hide = {event: b.hide};
+ if ("style"in b)if (b.style === f || "object" !== typeof b.style)b.style = {classes: b.style};
+ a.each(h, function () {this.sanitize && this.sanitize(b)});
+ return b
+ }
+
+ function w() {
+ w.history = w.history || [], w.history.push(arguments);
+ if ("object" === typeof console) {
+ var a = console[console.warn ? "warn" : "log"], b = Array.prototype.slice.call(arguments), c;
+ typeof arguments[0] === "string" && (b[0] = "qTip2: " + b[0]), c = a.apply ? a.apply(console, b) : a(b)
+ }
+ }
+
+ "use strict";
+ var d = !0, e = !1, f = null, g, h, i, j = {}, k = "ui-tooltip", l = "ui-widget", m = "ui-state-disabled", n = "div.qtip." + k, o = k + "-default", p = k + "-focus", q = k + "-hover", r = k + "-fluid", s = "-31000px", t = "_replacedByqTip", u = "oldtitle", v;
+ g = a.fn.qtip = function (b, h, i) {
+ var j = ("" + b).toLowerCase(), k = f, l = a.makeArray(arguments).slice(1), m = l[l.length - 1], n = this[0] ? a.data(this[0], "qtip") : f;
+ if (!arguments.length && n || j === "api")return n;
+ if ("string" === typeof b) {
+ this.each(function () {
+ var b = a.data(this, "qtip");
+ if (!b)return d;
+ m && m.timeStamp && (b.cache.event = m);
+ if (j !== "option" && j !== "options" || !h)b[j] && b[j].apply(b[j], l); else if (a.isPlainObject(h) || i !== c)b.set(h, i); else {
+ k = b.get(h);
+ return e
+ }
+ });
+ return k !== f ? k : this
+ }
+ if ("object" === typeof b || !arguments.length) {
+ n = x(a.extend(d, {}, b));
+ return g.bind.call(this, n, m)
+ }
+ }, g.bind = function (b, f) {
+ return this.each(function (k) {
+ function r(b) {
+ function d() {p.render(typeof b === "object" || l.show.ready), m.show.add(m.hide).unbind(o)}
+
+ if (p.cache.disabled)return e;
+ p.cache.event = a.extend({}, b), p.cache.target = b ? a(b.target) : [c], l.show.delay > 0 ? (clearTimeout(p.timers.show), p.timers.show = setTimeout(d, l.show.delay), n.show !== n.hide && m.hide.bind(n.hide, function () {clearTimeout(p.timers.show)})) : d()
+ }
+
+ var l, m, n, o, p, q;
+ q = a.isArray(b.id) ? b.id[k] : b.id, q = !q || q === e || q.length < 1 || j[q] ? g.nextid++ : j[q] = q, o = ".qtip-" + q + "-create", p = z.call(this, q, b);
+ if (p === e)return d;
+ l = p.options, a.each(h, function () {this.initialize === "initialize" && this(p)}), m = {show: l.show.target, hide: l.hide.target}, n = {show: a.trim("" + l.show.event).replace(/ /g, o + " ") + o, hide: a.trim("" + l.hide.event).replace(/ /g, o + " ") + o}, /mouse(over|enter)/i.test(n.show) && !/mouse(out|leave)/i.test(n.hide) && (n.hide += " mouseleave" + o), m.show.bind("mousemove" + o, function (a) {i = {pageX: a.pageX, pageY: a.pageY, type: "mousemove"}}), m.show.bind(n.show, r), (l.show.ready || l.prerender) && r(f)
+ })
+ }, h = g.plugins = {Corner: function (a) {
+ a = ("" + a).replace(/([A-Z])/, " $1").replace(/middle/gi, "center").toLowerCase(), this.x = (a.match(/left|right/i) || a.match(/center/) || ["inherit"])[0].toLowerCase(), this.y = (a.match(/top|bottom|center/i) || ["inherit"])[0].toLowerCase();
+ var b = a.charAt(0);
+ this.precedance = b === "t" || b === "b" ? "y" : "x", this.string = function () {return this.precedance === "y" ? this.y + this.x : this.x + this.y}, this.abbrev = function () {
+ var a = this.x.substr(0, 1), b = this.y.substr(0, 1);
+ return a === b ? a : a === "c" || a !== "c" && b !== "c" ? b + a : a + b
+ }, this.clone = function () {return{x: this.x, y: this.y, precedance: this.precedance, string: this.string, abbrev: this.abbrev, clone: this.clone}}
+ }, offset: function (a, b) {
+ function i(a, b) {c.left += b * a.scrollLeft(), c.top += b * a.scrollTop()}
+
+ var c = a.offset(), d = b, e = 0, f = document.body, g, h;
+ if (d) {
+ do {
+ d.css("position") !== "static" && (g = d[0] === f ? {left: parseInt(d.css("left"), 10) || 0, top: parseInt(d.css("top"), 10) || 0} : d.position(), c.left -= g.left + (parseInt(d.css("borderLeftWidth"), 10) || 0) + (parseInt(d.css("marginLeft"), 10) || 0), c.top -= g.top + (parseInt(d.css("borderTopWidth"), 10) || 0), h = d.css("overflow"), (h === "scroll" || h === "auto") && ++e);
+ if (d[0] === f)break
+ } while (d = d.offsetParent());
+ b[0] !== f && e && i(b, 1)
+ }
+ return c
+ }, iOS: parseFloat(("" + (/CPU.*OS ([0-9_]{1,3})|(CPU like).*AppleWebKit.*Mobile/i.exec(navigator.userAgent) || [0, ""])[1]).replace("undefined", "3_2").replace("_", ".")) || e, fn: {attr: function (b, c) {
+ if (this.length) {
+ var d = this[0], e = "title", f = a.data(d, "qtip");
+ if (b === e && f && "object" === typeof f && f.options.suppress) {
+ if (arguments.length < 2)return a.attr(d, u);
+ f && f.options.content.attr === e && f.cache.attr && f.set("content.text", c);
+ return this.attr(u, c)
+ }
+ }
+ return a.fn["attr" + t].apply(this, arguments)
+ }, clone: function (b) {
+ var c = a([]), d = "title", e = a.fn["clone" + t].apply(this, arguments);
+ b || e.filter("[" + u + "]").attr("title",function () {return a.attr(this, u)}).removeAttr(u);
+ return e
+ }, remove: a.ui ? f : function (b, c) {a.ui || a(this).each(function () {c || (!b || a.filter(b, [this]).length) && a("*", this).add(this).each(function () {a(this).triggerHandler("remove")})})}}}, a.each(h.fn, function (b, c) {
+ if (!c || a.fn[b + t])return d;
+ var e = a.fn[b + t] = a.fn[b];
+ a.fn[b] = function () {return c.apply(this, arguments) || e.apply(this, arguments)}
+ }), g.version = "nightly", g.nextid = 0, g.inactiveEvents = "click dblclick mousedown mouseup mousemove mouseleave mouseenter".split(" "), g.zindex = 15e3, g.defaults = {prerender: e, id: e, overwrite: d, suppress: d, content: {text: d, attr: "title", title: {text: e, button: e}}, position: {my: "top left", at: "bottom right", target: e, container: e, viewport: e, adjust: {x: 0, y: 0, mouse: d, resize: d, method: "flip flip"}, effect: function (b, c, d) {a(this).animate(c, {duration: 200, queue: e})}}, show: {target: e, event: "mouseenter", effect: d, delay: 90, solo: e, ready: e, autofocus: e}, hide: {target: e, event: "mouseleave", effect: d, delay: 0, fixed: e, inactive: e, leave: "window", distance: e}, style: {classes: "", widget: e, width: e, height: e}, events: {render: f, move: f, show: f, hide: f, toggle: f, visible: f, focus: f, blur: f}}, h.svg = function (b, c) {
+ var d = a(document), e = b[0], f = {width: 0, height: 0, offset: {top: 1e10, left: 1e10}}, g, h, i, j, k;
+ if (e.getBBox && e.parentNode) {
+ g = e.getBBox(), h = e.getScreenCTM(), i = e.farthestViewportElement || e;
+ if (!i.createSVGPoint)return f;
+ j = i.createSVGPoint(), j.x = g.x, j.y = g.y, k = j.matrixTransform(h), f.offset.left = k.x, f.offset.top = k.y, j.x += g.width, j.y += g.height, k = j.matrixTransform(h), f.width = k.x - f.offset.left, f.height = k.y - f.offset.top, f.offset.left += d.scrollLeft(), f.offset.top += d.scrollTop()
+ }
+ return f
+ }
+})(jQuery, window);
diff --git a/plugins/UserCountryMap/js/vendor/kartograph.js b/plugins/UserCountryMap/js/vendor/kartograph.js
index 6cb754dfb6..e50442fdc1 100644
--- a/plugins/UserCountryMap/js/vendor/kartograph.js
+++ b/plugins/UserCountryMap/js/vendor/kartograph.js
@@ -15,6003 +15,6015 @@
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
-*/
+ */
+
+
+(function () {
+ var $, Aitoff, Azimuthal, BBox, Balthasart, Behrmann, BlurFilter, Bubble, CEA, CantersModifiedSinusoidalI, Circle, CohenSutherland, Conic, Cylindrical, EckertIV, EquidistantAzimuthal, Equirectangular, Filter, GallPeters, GlowFilter, GoodeHomolosine, Hatano, HoboDyer, HtmlLabel, Icon, Kartograph, LAEA, LCC, LabeledBubble, LatLon, Line, LinearScale, LogScale, LonLat, Loximuthal, MapLayer, MapLayerPath, Mercator, Mollweide, NaturalEarth, Nicolosi, Orthographic, PanAndZoomControl, Path, PieChart, Proj, PseudoConic, PseudoCylindrical, QuantileScale, REbraces, REcomment_string, REfull, REmunged, Robinson, Satellite, Scale, Sinusoidal, SqrtScale, StackedBarChart, Stereographic, SvgLabel, Symbol, SymbolGroup, View, WagnerIV, WagnerV, Winkel3, drawPieChart, filter, kartograph, log, map_layer_path_uid, munge, munged, parsedeclarations, resolve, restore, root, uid, warn, __point_in_polygon, __proj, __type, _base, _base1, _ref, _ref1, _ref2, _ref3, _ref4, _ref5,
+ __hasProp = {}.hasOwnProperty,
+ __extends = function (child, parent) {
+ for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; }
+ function ctor() { this.constructor = child; }
+
+ ctor.prototype = parent.prototype;
+ child.prototype = new ctor();
+ child.__super__ = parent.prototype;
+ return child;
+ },
+ __bind = function (fn, me) { return function () { return fn.apply(me, arguments); }; },
+ __indexOf = [].indexOf || function (item) {
+ for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; }
+ return -1;
+ };
+ root = typeof exports !== "undefined" && exports !== null ? exports : this;
-(function() {
- var $, Aitoff, Azimuthal, BBox, Balthasart, Behrmann, BlurFilter, Bubble, CEA, CantersModifiedSinusoidalI, Circle, CohenSutherland, Conic, Cylindrical, EckertIV, EquidistantAzimuthal, Equirectangular, Filter, GallPeters, GlowFilter, GoodeHomolosine, Hatano, HoboDyer, HtmlLabel, Icon, Kartograph, LAEA, LCC, LabeledBubble, LatLon, Line, LinearScale, LogScale, LonLat, Loximuthal, MapLayer, MapLayerPath, Mercator, Mollweide, NaturalEarth, Nicolosi, Orthographic, PanAndZoomControl, Path, PieChart, Proj, PseudoConic, PseudoCylindrical, QuantileScale, REbraces, REcomment_string, REfull, REmunged, Robinson, Satellite, Scale, Sinusoidal, SqrtScale, StackedBarChart, Stereographic, SvgLabel, Symbol, SymbolGroup, View, WagnerIV, WagnerV, Winkel3, drawPieChart, filter, kartograph, log, map_layer_path_uid, munge, munged, parsedeclarations, resolve, restore, root, uid, warn, __point_in_polygon, __proj, __type, _base, _base1, _ref, _ref1, _ref2, _ref3, _ref4, _ref5,
- __hasProp = {}.hasOwnProperty,
- __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
- __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
- __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
+ kartograph = root.$K = window.Kartograph = (_ref = root.Kartograph) != null ? _ref : root.Kartograph = {};
- root = typeof exports !== "undefined" && exports !== null ? exports : this;
+ kartograph.version = "0.5.2";
- kartograph = root.$K = window.Kartograph = (_ref = root.Kartograph) != null ? _ref : root.Kartograph = {};
+ $ = root.jQuery;
- kartograph.version = "0.5.2";
+ kartograph.__verbose = false;
- $ = root.jQuery;
+ warn = function (s) {
+ try {
+ return console.warn.apply(console, arguments);
+ } catch (e) {
+ try {
+ return opera.postError.apply(opera, arguments);
+ } catch (e) {
+ return alert(Array.prototype.join.call(arguments, ' '));
+ }
+ }
+ };
- kartograph.__verbose = false;
+ log = function (s) {
+ if (kartograph.__verbose) {
+ try {
+ return console.debug.apply(console, arguments);
+ } catch (e) {
+ try {
+ return opera.postError.apply(opera, arguments);
+ } catch (e) {
+ return alert(Array.prototype.join.call(arguments, ' '));
+ }
+ }
+ }
+ };
- warn = function(s) {
- try {
- return console.warn.apply(console, arguments);
- } catch (e) {
- try {
- return opera.postError.apply(opera, arguments);
- } catch (e) {
- return alert(Array.prototype.join.call(arguments, ' '));
- }
+ if ((_ref1 = (_base = String.prototype).trim) == null) {
+ _base.trim = function () {
+ return this.replace(/^\s+|\s+$/g, "");
+ };
}
- };
- log = function(s) {
- if (kartograph.__verbose) {
- try {
- return console.debug.apply(console, arguments);
- } catch (e) {
- try {
- return opera.postError.apply(opera, arguments);
- } catch (e) {
- return alert(Array.prototype.join.call(arguments, ' '));
+ if (!Array.prototype.indexOf) {
+ Array.prototype.indexOf = function (searchElement /*, fromIndex */) {
+ "use strict";
+ if (this == null) {
+ throw new TypeError();
+ }
+ var t = Object(this);
+ var len = t.length >>> 0;
+ if (len === 0) {
+ return -1;
+ }
+ var n = 0;
+ if (arguments.length > 0) {
+ n = Number(arguments[1]);
+ if (n != n) { // shortcut for verifying if it's NaN
+ n = 0;
+ } else if (n != 0 && n != Infinity && n != -Infinity) {
+ n = (n > 0 || -1) * Math.floor(Math.abs(n));
+ }
+ }
+ if (n >= len) {
+ return -1;
+ }
+ var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0);
+ for (; k < len; k++) {
+ if (k in t && t[k] === searchElement) {
+ return k;
+ }
+ }
+ return -1;
}
- }
}
- };
+ ;
- if ((_ref1 = (_base = String.prototype).trim) == null) {
- _base.trim = function() {
- return this.replace(/^\s+|\s+$/g, "");
- };
- }
- if (!Array.prototype.indexOf) {
- Array.prototype.indexOf = function (searchElement /*, fromIndex */ ) {
- "use strict";
- if (this == null) {
- throw new TypeError();
- }
- var t = Object(this);
- var len = t.length >>> 0;
- if (len === 0) {
- return -1;
+ __type = (function () {
+ /*
+ for browser-safe type checking+
+ ported from jQuery's $.type
+ */
+
+ var classToType, name, _i, _len, _ref2;
+ classToType = {};
+ _ref2 = "Boolean Number String Function Array Date RegExp Undefined Null".split(" ");
+ for (_i = 0, _len = _ref2.length; _i < _len; _i++) {
+ name = _ref2[_i];
+ classToType["[object " + name + "]"] = name.toLowerCase();
}
- var n = 0;
- if (arguments.length > 0) {
- n = Number(arguments[1]);
- if (n != n) { // shortcut for verifying if it's NaN
- n = 0;
- } else if (n != 0 && n != Infinity && n != -Infinity) {
- n = (n > 0 || -1) * Math.floor(Math.abs(n));
+ return function (obj) {
+ var strType;
+ strType = Object.prototype.toString.call(obj);
+ return classToType[strType] || "object";
+ };
+ })();
+
+ /*
+ kartograph - a svg mapping library
+ Copyright (C) 2011 Gregor Aisch
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+ BBox = (function () {
+ /*
+ 2D bounding box
+ */
+
+ function BBox(left, top, width, height) {
+ var s;
+ if (left == null) {
+ left = 0;
}
- }
- if (n >= len) {
- return -1;
- }
- var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0);
- for (; k < len; k++) {
- if (k in t && t[k] === searchElement) {
- return k;
+ if (top == null) {
+ top = 0;
+ }
+ if (width == null) {
+ width = null;
}
+ if (height == null) {
+ height = null;
+ }
+ s = this;
+ if (width === null) {
+ s.xmin = Number.MAX_VALUE;
+ s.xmax = Number.MAX_VALUE * -1;
+ } else {
+ s.xmin = s.left = left;
+ s.xmax = s.right = left + width;
+ s.width = width;
+ }
+ if (height === null) {
+ s.ymin = Number.MAX_VALUE;
+ s.ymax = Number.MAX_VALUE * -1;
+ } else {
+ s.ymin = s.top = top;
+ s.ymax = s.bottom = height + top;
+ s.height = height;
+ }
+ return;
}
- return -1;
- }
-};
+ BBox.prototype.update = function (x, y) {
+ var s;
+ if (!(y != null)) {
+ y = x[1];
+ x = x[0];
+ }
+ s = this;
+ s.xmin = Math.min(s.xmin, x);
+ s.ymin = Math.min(s.ymin, y);
+ s.xmax = Math.max(s.xmax, x);
+ s.ymax = Math.max(s.ymax, y);
+ s.left = s.xmin;
+ s.top = s.ymin;
+ s.right = s.xmax;
+ s.bottom = s.ymax;
+ s.width = s.xmax - s.xmin;
+ s.height = s.ymax - s.ymin;
+ return this;
+ };
+
+ BBox.prototype.intersects = function (bbox) {
+ return bbox.left < s.right && bbox.right > s.left && bbox.top < s.bottom && bbox.bottom > s.top;
+ };
- __type = (function() {
- /*
- for browser-safe type checking+
- ported from jQuery's $.type
- */
-
- var classToType, name, _i, _len, _ref2;
- classToType = {};
- _ref2 = "Boolean Number String Function Array Date RegExp Undefined Null".split(" ");
- for (_i = 0, _len = _ref2.length; _i < _len; _i++) {
- name = _ref2[_i];
- classToType["[object " + name + "]"] = name.toLowerCase();
- }
- return function(obj) {
- var strType;
- strType = Object.prototype.toString.call(obj);
- return classToType[strType] || "object";
+ BBox.prototype.inside = function (x, y) {
+ var s;
+ s = this;
+ return x >= s.left && x <= s.right && y >= s.top && y <= s.bottom;
+ };
+
+ BBox.prototype.join = function (bbox) {
+ var s;
+ s = this;
+ s.update(bbox.left, bbox.top);
+ s.update(bbox.right, bbox.bottom);
+ return this;
+ };
+
+ return BBox;
+
+ })();
+
+ BBox.fromXML = function (xml) {
+ var h, w, x, y;
+ x = Number(xml.getAttribute('x'));
+ y = Number(xml.getAttribute('y'));
+ w = Number(xml.getAttribute('w'));
+ h = Number(xml.getAttribute('h'));
+ return new kartograph.BBox(x, y, w, h);
};
- })();
-
- /*
- kartograph - a svg mapping library
- Copyright (C) 2011 Gregor Aisch
-
- This library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Lesser General Public
- License as published by the Free Software Foundation; either
- version 2.1 of the License, or (at your option) any later version.
-
- This library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public
- License along with this library. If not, see <http://www.gnu.org/licenses/>.
- */
-
-
- BBox = (function() {
+
+ kartograph.BBox = BBox;
+
/*
- 2D bounding box
- */
-
- function BBox(left, top, width, height) {
- var s;
- if (left == null) {
- left = 0;
- }
- if (top == null) {
- top = 0;
- }
- if (width == null) {
- width = null;
- }
- if (height == null) {
- height = null;
- }
- s = this;
- if (width === null) {
- s.xmin = Number.MAX_VALUE;
- s.xmax = Number.MAX_VALUE * -1;
- } else {
- s.xmin = s.left = left;
- s.xmax = s.right = left + width;
- s.width = width;
- }
- if (height === null) {
- s.ymin = Number.MAX_VALUE;
- s.ymax = Number.MAX_VALUE * -1;
- } else {
- s.ymin = s.top = top;
- s.ymax = s.bottom = height + top;
- s.height = height;
- }
- return;
+ kartograph - a svg mapping library
+ Copyright (C) 2011 Gregor Aisch
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+ if ((_ref2 = kartograph.geom) == null) {
+ kartograph.geom = {};
}
- BBox.prototype.update = function(x, y) {
- var s;
- if (!(y != null)) {
- y = x[1];
- x = x[0];
- }
- s = this;
- s.xmin = Math.min(s.xmin, x);
- s.ymin = Math.min(s.ymin, y);
- s.xmax = Math.max(s.xmax, x);
- s.ymax = Math.max(s.ymax, y);
- s.left = s.xmin;
- s.top = s.ymin;
- s.right = s.xmax;
- s.bottom = s.ymax;
- s.width = s.xmax - s.xmin;
- s.height = s.ymax - s.ymin;
- return this;
- };
+ if ((_ref3 = (_base1 = kartograph.geom).clipping) == null) {
+ _base1.clipping = {};
+ }
- BBox.prototype.intersects = function(bbox) {
- return bbox.left < s.right && bbox.right > s.left && bbox.top < s.bottom && bbox.bottom > s.top;
- };
+ CohenSutherland = (function () {
+ var BOTTOM, INSIDE, LEFT, RIGHT, TOP;
- BBox.prototype.inside = function(x, y) {
- var s;
- s = this;
- return x >= s.left && x <= s.right && y >= s.top && y <= s.bottom;
- };
+ function CohenSutherland() {}
- BBox.prototype.join = function(bbox) {
- var s;
- s = this;
- s.update(bbox.left, bbox.top);
- s.update(bbox.right, bbox.bottom);
- return this;
- };
+ INSIDE = 0;
- return BBox;
-
- })();
-
- BBox.fromXML = function(xml) {
- var h, w, x, y;
- x = Number(xml.getAttribute('x'));
- y = Number(xml.getAttribute('y'));
- w = Number(xml.getAttribute('w'));
- h = Number(xml.getAttribute('h'));
- return new kartograph.BBox(x, y, w, h);
- };
-
- kartograph.BBox = BBox;
-
- /*
- kartograph - a svg mapping library
- Copyright (C) 2011 Gregor Aisch
-
- This library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Lesser General Public
- License as published by the Free Software Foundation; either
- version 2.1 of the License, or (at your option) any later version.
-
- This library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public
- License along with this library. If not, see <http://www.gnu.org/licenses/>.
- */
-
-
- if ((_ref2 = kartograph.geom) == null) {
- kartograph.geom = {};
- }
-
- if ((_ref3 = (_base1 = kartograph.geom).clipping) == null) {
- _base1.clipping = {};
- }
-
- CohenSutherland = (function() {
- var BOTTOM, INSIDE, LEFT, RIGHT, TOP;
-
- function CohenSutherland() {}
-
- INSIDE = 0;
-
- LEFT = 1;
-
- RIGHT = 2;
-
- BOTTOM = 4;
-
- TOP = 8;
-
- CohenSutherland.prototype.compute_out_code = function(bbox, x, y) {
- var code, self;
- self = this;
- code = self.INSIDE;
- if (x < bbox.left) {
- code |= self.LEFT;
- } else if (x > bbox.right) {
- code |= self.RIGHT;
- }
- if (y < bbox.top) {
- code |= self.TOP;
- } else if (y > bbox.bottom) {
- code |= self.BOTTOM;
- }
- return code;
- };
+ LEFT = 1;
- CohenSutherland.prototype.clip = function(bbox, x0, y0, x1, y1) {
- var accept, code0, code1, cout, self, x, y;
- self = this;
- code0 = self.compute_out_code(bbox, x0, y0);
- code1 = self.compute_out_code(bbox, x1, y1);
- accept = False;
- while (True) {
- if (!(code0 | code1)) {
- accept = True;
- break;
- } else if (code0 & code1) {
- break;
- } else {
- cout = code === 0 ? code1 : code0;
- if (cout & self.TOP) {
- x = x0 + (x1 - x0) * (bbox.top - y0) / (y1 - y0);
- y = bbox.top;
- } else if (cout & self.BOTTOM) {
- x = x0 + (x1 - x0) * (bbox.bottom - y0) / (y1 - y0);
- y = bbox.bottom;
- } else if (cout & self.RIGHT) {
- y = y0 + (y1 - y0) * (bbox.right - x0) / (x1 - x0);
- x = bbox.right;
- } else if (cout & self.LEFT) {
- y = y0 + (y1 - y0) * (bbox.left - x0) / (x1 - x0);
- x = bbox.left;
- }
- if (cout === code0) {
- x0 = x;
- y0 = y;
+ RIGHT = 2;
+
+ BOTTOM = 4;
+
+ TOP = 8;
+
+ CohenSutherland.prototype.compute_out_code = function (bbox, x, y) {
+ var code, self;
+ self = this;
+ code = self.INSIDE;
+ if (x < bbox.left) {
+ code |= self.LEFT;
+ } else if (x > bbox.right) {
+ code |= self.RIGHT;
+ }
+ if (y < bbox.top) {
+ code |= self.TOP;
+ } else if (y > bbox.bottom) {
+ code |= self.BOTTOM;
+ }
+ return code;
+ };
+
+ CohenSutherland.prototype.clip = function (bbox, x0, y0, x1, y1) {
+ var accept, code0, code1, cout, self, x, y;
+ self = this;
code0 = self.compute_out_code(bbox, x0, y0);
- } else {
- x1 = x;
- y1 = y;
code1 = self.compute_out_code(bbox, x1, y1);
- }
- }
- }
- if (accept) {
- return [x0, y0, x1, y1];
- } else {
- return null;
- }
- };
+ accept = False;
+ while (True) {
+ if (!(code0 | code1)) {
+ accept = True;
+ break;
+ } else if (code0 & code1) {
+ break;
+ } else {
+ cout = code === 0 ? code1 : code0;
+ if (cout & self.TOP) {
+ x = x0 + (x1 - x0) * (bbox.top - y0) / (y1 - y0);
+ y = bbox.top;
+ } else if (cout & self.BOTTOM) {
+ x = x0 + (x1 - x0) * (bbox.bottom - y0) / (y1 - y0);
+ y = bbox.bottom;
+ } else if (cout & self.RIGHT) {
+ y = y0 + (y1 - y0) * (bbox.right - x0) / (x1 - x0);
+ x = bbox.right;
+ } else if (cout & self.LEFT) {
+ y = y0 + (y1 - y0) * (bbox.left - x0) / (x1 - x0);
+ x = bbox.left;
+ }
+ if (cout === code0) {
+ x0 = x;
+ y0 = y;
+ code0 = self.compute_out_code(bbox, x0, y0);
+ } else {
+ x1 = x;
+ y1 = y;
+ code1 = self.compute_out_code(bbox, x1, y1);
+ }
+ }
+ }
+ if (accept) {
+ return [x0, y0, x1, y1];
+ } else {
+ return null;
+ }
+ };
- return CohenSutherland;
-
- })();
-
- kartograph.geom.clipping.CohenSutherland = CohenSutherland;
-
- /*
- kartograph - a svg mapping library
- Copyright (C) 2011,2012 Gregor Aisch
-
- This library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Lesser General Public
- License as published by the Free Software Foundation; either
- version 2.1 of the License, or (at your option) any later version.
-
- This library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public
- License along with this library. If not, see <http://www.gnu.org/licenses/>.
- */
-
-
- Kartograph = (function() {
-
- function Kartograph(container, width, height) {
- var cnt, me;
- me = this;
- me.container = cnt = $(container);
- if (width == null) {
- width = cnt.width();
- }
- if (height == null) {
- height = cnt.height();
- }
- if (height === 0) {
- height = 'auto';
- }
- me.size = {
- h: height,
- w: width
- };
- me.markers = [];
- me.pathById = {};
- me.container.addClass('kartograph');
- }
+ return CohenSutherland;
- Kartograph.prototype.createSVGLayer = function(id) {
- var about, cnt, lid, me, paper, svg, vp, _ref4;
- me = this;
- if ((_ref4 = me._layerCnt) == null) {
- me._layerCnt = 0;
- }
- lid = me._layerCnt++;
- vp = me.viewport;
- cnt = me.container;
- paper = Raphael(cnt[0], vp.width, vp.height);
- svg = $(paper.canvas);
- svg.css({
- position: 'absolute',
- top: '0px',
- left: '0px',
- 'z-index': lid + 5
- });
- if (cnt.css('position') === 'static') {
- cnt.css({
- position: 'relative',
- height: vp.height + 'px'
- });
- }
- svg.addClass(id);
- about = $('desc', paper.canvas).text();
- $('desc', paper.canvas).text(about.replace('with ', 'with kartograph ' + kartograph.version + ' and '));
- return paper;
- };
+ })();
- Kartograph.prototype.createHTMLLayer = function(id) {
- var cnt, div, lid, me, vp, _ref4;
- me = this;
- vp = me.viewport;
- cnt = me.container;
- if ((_ref4 = me._layerCnt) == null) {
- me._layerCnt = 0;
- }
- lid = me._layerCnt++;
- div = $('<div class="layer ' + id + '" />');
- div.css({
- position: 'absolute',
- top: '0px',
- left: '0px',
- width: vp.width + 'px',
- height: vp.height + 'px',
- 'z-index': lid + 5
- });
- cnt.append(div);
- return div;
- };
+ kartograph.geom.clipping.CohenSutherland = CohenSutherland;
- Kartograph.prototype.loadMap = function(mapurl, callback, opts) {
- var def, me, _base2, _ref4;
- me = this;
- def = $.Deferred();
- me.clear();
- me.opts = opts != null ? opts : {};
- if ((_ref4 = (_base2 = me.opts).zoom) == null) {
- _base2.zoom = 1;
- }
- me.mapLoadCallback = callback;
- me._loadMapDeferred = def;
- me._lastMapUrl = mapurl;
- if (me.cacheMaps && (kartograph.__mapCache[mapurl] != null)) {
- me._mapLoaded(kartograph.__mapCache[mapurl]);
- } else {
- $.ajax({
- url: mapurl,
- dataType: "text",
- success: me._mapLoaded,
- context: me,
- error: function(a, b, c) {
- return warn(a, b, c);
- }
- });
- }
- return def.promise();
- };
+ /*
+ kartograph - a svg mapping library
+ Copyright (C) 2011,2012 Gregor Aisch
- Kartograph.prototype.setMap = function(svg, opts) {
- var me, _base2, _ref4;
- me = this;
- me.opts = opts != null ? opts : {};
- if ((_ref4 = (_base2 = me.opts).zoom) == null) {
- _base2.zoom = 1;
- }
- me._lastMapUrl = 'string';
- me._mapLoaded(svg);
- };
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
- Kartograph.prototype._mapLoaded = function(xml) {
- var $view, AB, h, halign, me, padding, ratio, valign, vp, w, zoom, _ref4, _ref5, _ref6, _ref7, _ref8;
- me = this;
- if (me.cacheMaps) {
- if ((_ref4 = kartograph.__mapCache) == null) {
- kartograph.__mapCache = {};
- }
- kartograph.__mapCache[me._lastMapUrl] = xml;
- }
- try {
- xml = $(xml);
- } catch (err) {
- warn('something went horribly wrong while parsing svg');
- me._loadMapDeferred.reject('could not parse svg');
- return;
- }
- me.svgSrc = xml;
- $view = $('view', xml);
- log('got svg src', me.svgSrc);
- if (!(me.paper != null)) {
- w = me.size.w;
- h = me.size.h;
- if (h === 'auto') {
- ratio = $view.attr('w') / $view.attr('h');
- h = w / ratio;
- }
- me.viewport = new BBox(0, 0, w, h);
- }
- vp = me.viewport;
- log('got viewport', me.viewport);
- me.viewAB = AB = kartograph.View.fromXML($view[0]);
- log('got first view', me.viewAB);
- padding = (_ref5 = me.opts.padding) != null ? _ref5 : 0;
- halign = (_ref6 = me.opts.halign) != null ? _ref6 : 'center';
- valign = (_ref7 = me.opts.valign) != null ? _ref7 : 'center';
- log('got alignment', halign, valign);
- zoom = (_ref8 = me.opts.zoom) != null ? _ref8 : 1;
- me.viewBC = new kartograph.View(me.viewAB.asBBox(), vp.width * zoom, vp.height * zoom, padding, halign, valign);
- log('got second view', me.viewBC);
- me.proj = kartograph.Proj.fromXML($('proj', $view)[0]);
- log('got projection', me.proj);
- if (me.mapLoadCallback != null) {
- me.mapLoadCallback(me);
- }
- if (me._loadMapDeferred != null) {
- me._loadMapDeferred.resolve(me);
- }
- };
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
- Kartograph.prototype.addLayer = function(id, opts) {
- var $paths, checkEvents, evt, layer, layer_id, me, path_id, prop, src_id, svgLayer, svg_path, titles, val, _i, _j, _len, _len1, _ref4, _ref5, _ref6;
- if (opts == null) {
- opts = {};
- }
- /*
- add new layer
- */
-
- me = this;
- if ((_ref4 = me.layerIds) == null) {
- me.layerIds = [];
- }
- if ((_ref5 = me.layers) == null) {
- me.layers = {};
- }
- if (!(me.paper != null)) {
- me.paper = me.createSVGLayer();
- }
- src_id = id;
- if (__type(opts) === 'object') {
- layer_id = opts.name;
- path_id = opts.key;
- titles = opts.title;
- } else {
- opts = {};
- }
- if (layer_id == null) {
- layer_id = src_id;
- }
- svgLayer = $('#' + src_id, me.svgSrc);
- if (svgLayer.length === 0) {
- return;
- }
- layer = new MapLayer(layer_id, path_id, me, opts.filter);
- $paths = $('*', svgLayer[0]);
- for (_i = 0, _len = $paths.length; _i < _len; _i++) {
- svg_path = $paths[_i];
- layer.addPath(svg_path, titles);
- }
- if (layer.paths.length > 0) {
- me.layers[layer_id] = layer;
- me.layerIds.push(layer_id);
- }
- checkEvents = ['click', 'mouseenter', 'mouseleave', 'dblclick', 'mousedown', 'mouseup', 'mouseover', 'mouseout'];
- for (_j = 0, _len1 = checkEvents.length; _j < _len1; _j++) {
- evt = checkEvents[_j];
- if (__type(opts[evt]) === 'function') {
- layer.on(evt, opts[evt]);
- }
- }
- if (opts.styles != null) {
- _ref6 = opts.styles;
- for (prop in _ref6) {
- val = _ref6[prop];
- layer.style(prop, val);
- }
- }
- if (opts.tooltips != null) {
- layer.tooltips(opts.tooltips);
- }
- return me;
- };
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
- Kartograph.prototype.getLayer = function(layer_id) {
- /* returns a map layer
- */
-
- var me;
- me = this;
- if (!(me.layers[layer_id] != null)) {
- warn('could not find layer ' + layer_id);
- return null;
- }
- return me.layers[layer_id];
- };
- Kartograph.prototype.getLayerPath = function(layer_id, path_id) {
- var layer, me;
- me = this;
- layer = me.getLayer(layer_id);
- if (layer != null) {
- if (__type(path_id) === 'object') {
- return layer.getPaths(path_id)[0];
- } else {
- return layer.getPath(path_id);
+ Kartograph = (function () {
+
+ function Kartograph(container, width, height) {
+ var cnt, me;
+ me = this;
+ me.container = cnt = $(container);
+ if (width == null) {
+ width = cnt.width();
+ }
+ if (height == null) {
+ height = cnt.height();
+ }
+ if (height === 0) {
+ height = 'auto';
+ }
+ me.size = {
+ h: height,
+ w: width
+ };
+ me.markers = [];
+ me.pathById = {};
+ me.container.addClass('kartograph');
}
- }
- return null;
- };
- Kartograph.prototype.onLayerEvent = function(event, callback, layerId) {
- var me;
- me = this;
- me.getLayer(layerId).on(event, callback);
- return me;
- };
+ Kartograph.prototype.createSVGLayer = function (id) {
+ var about, cnt, lid, me, paper, svg, vp, _ref4;
+ me = this;
+ if ((_ref4 = me._layerCnt) == null) {
+ me._layerCnt = 0;
+ }
+ lid = me._layerCnt++;
+ vp = me.viewport;
+ cnt = me.container;
+ paper = Raphael(cnt[0], vp.width, vp.height);
+ svg = $(paper.canvas);
+ svg.css({
+ position: 'absolute',
+ top: '0px',
+ left: '0px',
+ 'z-index': lid + 5
+ });
+ if (cnt.css('position') === 'static') {
+ cnt.css({
+ position: 'relative',
+ height: vp.height + 'px'
+ });
+ }
+ svg.addClass(id);
+ about = $('desc', paper.canvas).text();
+ $('desc', paper.canvas).text(about.replace('with ', 'with kartograph ' + kartograph.version + ' and '));
+ return paper;
+ };
- Kartograph.prototype.addMarker = function(marker) {
- var me, xy;
- me = this;
- me.markers.push(marker);
- xy = me.viewBC.project(me.viewAB.project(me.proj.project(marker.lonlat.lon, marker.lonlat.lat)));
- return marker.render(xy[0], xy[1], me.container, me.paper);
- };
+ Kartograph.prototype.createHTMLLayer = function (id) {
+ var cnt, div, lid, me, vp, _ref4;
+ me = this;
+ vp = me.viewport;
+ cnt = me.container;
+ if ((_ref4 = me._layerCnt) == null) {
+ me._layerCnt = 0;
+ }
+ lid = me._layerCnt++;
+ div = $('<div class="layer ' + id + '" />');
+ div.css({
+ position: 'absolute',
+ top: '0px',
+ left: '0px',
+ width: vp.width + 'px',
+ height: vp.height + 'px',
+ 'z-index': lid + 5
+ });
+ cnt.append(div);
+ return div;
+ };
- Kartograph.prototype.clearMarkers = function() {
- var marker, me, _i, _len, _ref4;
- me = this;
- _ref4 = me.markers;
- for (_i = 0, _len = _ref4.length; _i < _len; _i++) {
- marker = _ref4[_i];
- marker.clear();
- }
- return me.markers = [];
- };
+ Kartograph.prototype.loadMap = function (mapurl, callback, opts) {
+ var def, me, _base2, _ref4;
+ me = this;
+ def = $.Deferred();
+ me.clear();
+ me.opts = opts != null ? opts : {};
+ if ((_ref4 = (_base2 = me.opts).zoom) == null) {
+ _base2.zoom = 1;
+ }
+ me.mapLoadCallback = callback;
+ me._loadMapDeferred = def;
+ me._lastMapUrl = mapurl;
+ if (me.cacheMaps && (kartograph.__mapCache[mapurl] != null)) {
+ me._mapLoaded(kartograph.__mapCache[mapurl]);
+ } else {
+ $.ajax({
+ url: mapurl,
+ dataType: "text",
+ success: me._mapLoaded,
+ context: me,
+ error: function (a, b, c) {
+ return warn(a, b, c);
+ }
+ });
+ }
+ return def.promise();
+ };
- Kartograph.prototype.fadeIn = function(opts) {
- var dur, duration, id, layer_id, me, path, paths, _ref4, _ref5, _ref6, _results;
- if (opts == null) {
- opts = {};
- }
- me = this;
- layer_id = (_ref4 = opts.layer) != null ? _ref4 : me.layerIds[me.layerIds.length - 1];
- duration = (_ref5 = opts.duration) != null ? _ref5 : 500;
- _ref6 = me.layers[layer_id].pathsById;
- _results = [];
- for (id in _ref6) {
- paths = _ref6[id];
- _results.push((function() {
- var _i, _len, _results1;
- _results1 = [];
- for (_i = 0, _len = paths.length; _i < _len; _i++) {
- path = paths[_i];
- if (__type(duration) === "function") {
- dur = duration(path.data);
+ Kartograph.prototype.setMap = function (svg, opts) {
+ var me, _base2, _ref4;
+ me = this;
+ me.opts = opts != null ? opts : {};
+ if ((_ref4 = (_base2 = me.opts).zoom) == null) {
+ _base2.zoom = 1;
+ }
+ me._lastMapUrl = 'string';
+ me._mapLoaded(svg);
+ };
+
+ Kartograph.prototype._mapLoaded = function (xml) {
+ var $view, AB, h, halign, me, padding, ratio, valign, vp, w, zoom, _ref4, _ref5, _ref6, _ref7, _ref8;
+ me = this;
+ if (me.cacheMaps) {
+ if ((_ref4 = kartograph.__mapCache) == null) {
+ kartograph.__mapCache = {};
+ }
+ kartograph.__mapCache[me._lastMapUrl] = xml;
+ }
+ try {
+ xml = $(xml);
+ } catch (err) {
+ warn('something went horribly wrong while parsing svg');
+ me._loadMapDeferred.reject('could not parse svg');
+ return;
+ }
+ me.svgSrc = xml;
+ $view = $('view', xml);
+ log('got svg src', me.svgSrc);
+ if (!(me.paper != null)) {
+ w = me.size.w;
+ h = me.size.h;
+ if (h === 'auto') {
+ ratio = $view.attr('w') / $view.attr('h');
+ h = w / ratio;
+ }
+ me.viewport = new BBox(0, 0, w, h);
+ }
+ vp = me.viewport;
+ log('got viewport', me.viewport);
+ me.viewAB = AB = kartograph.View.fromXML($view[0]);
+ log('got first view', me.viewAB);
+ padding = (_ref5 = me.opts.padding) != null ? _ref5 : 0;
+ halign = (_ref6 = me.opts.halign) != null ? _ref6 : 'center';
+ valign = (_ref7 = me.opts.valign) != null ? _ref7 : 'center';
+ log('got alignment', halign, valign);
+ zoom = (_ref8 = me.opts.zoom) != null ? _ref8 : 1;
+ me.viewBC = new kartograph.View(me.viewAB.asBBox(), vp.width * zoom, vp.height * zoom, padding, halign, valign);
+ log('got second view', me.viewBC);
+ me.proj = kartograph.Proj.fromXML($('proj', $view)[0]);
+ log('got projection', me.proj);
+ if (me.mapLoadCallback != null) {
+ me.mapLoadCallback(me);
+ }
+ if (me._loadMapDeferred != null) {
+ me._loadMapDeferred.resolve(me);
+ }
+ };
+
+ Kartograph.prototype.addLayer = function (id, opts) {
+ var $paths, checkEvents, evt, layer, layer_id, me, path_id, prop, src_id, svgLayer, svg_path, titles, val, _i, _j, _len, _len1, _ref4, _ref5, _ref6;
+ if (opts == null) {
+ opts = {};
+ }
+ /*
+ add new layer
+ */
+
+ me = this;
+ if ((_ref4 = me.layerIds) == null) {
+ me.layerIds = [];
+ }
+ if ((_ref5 = me.layers) == null) {
+ me.layers = {};
+ }
+ if (!(me.paper != null)) {
+ me.paper = me.createSVGLayer();
+ }
+ src_id = id;
+ if (__type(opts) === 'object') {
+ layer_id = opts.name;
+ path_id = opts.key;
+ titles = opts.title;
} else {
- dur = duration;
- }
- path.svgPath.attr('opacity', 0);
- _results1.push(path.svgPath.animate({
- opacity: 1
- }, dur));
- }
- return _results1;
- })());
- }
- return _results;
- };
+ opts = {};
+ }
+ if (layer_id == null) {
+ layer_id = src_id;
+ }
+ svgLayer = $('#' + src_id, me.svgSrc);
+ if (svgLayer.length === 0) {
+ return;
+ }
+ layer = new MapLayer(layer_id, path_id, me, opts.filter);
+ $paths = $('*', svgLayer[0]);
+ for (_i = 0, _len = $paths.length; _i < _len; _i++) {
+ svg_path = $paths[_i];
+ layer.addPath(svg_path, titles);
+ }
+ if (layer.paths.length > 0) {
+ me.layers[layer_id] = layer;
+ me.layerIds.push(layer_id);
+ }
+ checkEvents = ['click', 'mouseenter', 'mouseleave', 'dblclick', 'mousedown', 'mouseup', 'mouseover', 'mouseout'];
+ for (_j = 0, _len1 = checkEvents.length; _j < _len1; _j++) {
+ evt = checkEvents[_j];
+ if (__type(opts[evt]) === 'function') {
+ layer.on(evt, opts[evt]);
+ }
+ }
+ if (opts.styles != null) {
+ _ref6 = opts.styles;
+ for (prop in _ref6) {
+ val = _ref6[prop];
+ layer.style(prop, val);
+ }
+ }
+ if (opts.tooltips != null) {
+ layer.tooltips(opts.tooltips);
+ }
+ return me;
+ };
- /*
- end of public API
- */
-
-
- Kartograph.prototype.loadCoastline = function() {
- var me;
- me = this;
- return $.ajax({
- url: 'coastline.json',
- success: me.renderCoastline,
- context: me
- });
- };
+ Kartograph.prototype.getLayer = function (layer_id) {
+ /* returns a map layer
+ */
- Kartograph.prototype.resize = function(w, h) {
- /*
- forces redraw of every layer
- */
-
- var cnt, halign, id, layer, me, padding, sg, valign, vp, zoom, _i, _len, _ref4, _ref5, _ref6, _ref7, _ref8;
- me = this;
- cnt = me.container;
- if (w == null) {
- w = cnt.width();
- }
- if (h == null) {
- h = cnt.height();
- }
- me.viewport = vp = new kartograph.BBox(0, 0, w, h);
- if (me.paper != null) {
- me.paper.setSize(vp.width, vp.height);
- }
- vp = me.viewport;
- padding = (_ref4 = me.opts.padding) != null ? _ref4 : 0;
- halign = (_ref5 = me.opts.halign) != null ? _ref5 : 'center';
- valign = (_ref6 = me.opts.valign) != null ? _ref6 : 'center';
- zoom = me.opts.zoom;
- me.viewBC = new kartograph.View(me.viewAB.asBBox(), vp.width * zoom, vp.height * zoom, padding, halign, valign);
- _ref7 = me.layers;
- for (id in _ref7) {
- layer = _ref7[id];
- layer.setView(me.viewBC);
- }
- if (me.symbolGroups != null) {
- _ref8 = me.symbolGroups;
- for (_i = 0, _len = _ref8.length; _i < _len; _i++) {
- sg = _ref8[_i];
- sg.onResize();
- }
- }
- };
+ var me;
+ me = this;
+ if (!(me.layers[layer_id] != null)) {
+ warn('could not find layer ' + layer_id);
+ return null;
+ }
+ return me.layers[layer_id];
+ };
- Kartograph.prototype.lonlat2xy = function(lonlat) {
- var a, me;
- me = this;
- if (lonlat.length === 2) {
- lonlat = new kartograph.LonLat(lonlat[0], lonlat[1]);
- }
- if (lonlat.length === 3) {
- lonlat = new kartograph.LonLat(lonlat[0], lonlat[1], lonlat[2]);
- }
- a = me.proj.project(lonlat.lon, lonlat.lat, lonlat.alt);
- return me.viewBC.project(me.viewAB.project(a));
- };
+ Kartograph.prototype.getLayerPath = function (layer_id, path_id) {
+ var layer, me;
+ me = this;
+ layer = me.getLayer(layer_id);
+ if (layer != null) {
+ if (__type(path_id) === 'object') {
+ return layer.getPaths(path_id)[0];
+ } else {
+ return layer.getPath(path_id);
+ }
+ }
+ return null;
+ };
- Kartograph.prototype.showZoomControls = function() {
- var me;
- me = this;
- me.zc = new PanAndZoomControl(me);
- return me;
- };
+ Kartograph.prototype.onLayerEvent = function (event, callback, layerId) {
+ var me;
+ me = this;
+ me.getLayer(layerId).on(event, callback);
+ return me;
+ };
- Kartograph.prototype.addSymbolGroup = function(symbolgroup) {
- var me, _ref4;
- me = this;
- if ((_ref4 = me.symbolGroups) == null) {
- me.symbolGroups = [];
- }
- return me.symbolGroups.push(symbolgroup);
- };
+ Kartograph.prototype.addMarker = function (marker) {
+ var me, xy;
+ me = this;
+ me.markers.push(marker);
+ xy = me.viewBC.project(me.viewAB.project(me.proj.project(marker.lonlat.lon, marker.lonlat.lat)));
+ return marker.render(xy[0], xy[1], me.container, me.paper);
+ };
- Kartograph.prototype.removeSymbols = function(index) {
- var me, sg, _i, _len, _ref4, _results;
- me = this;
- if (index != null) {
- return me.symbolGroups[index].remove();
- } else {
- _ref4 = me.symbolGroups;
- _results = [];
- for (_i = 0, _len = _ref4.length; _i < _len; _i++) {
- sg = _ref4[_i];
- _results.push(sg.remove());
- }
- return _results;
- }
- };
+ Kartograph.prototype.clearMarkers = function () {
+ var marker, me, _i, _len, _ref4;
+ me = this;
+ _ref4 = me.markers;
+ for (_i = 0, _len = _ref4.length; _i < _len; _i++) {
+ marker = _ref4[_i];
+ marker.clear();
+ }
+ return me.markers = [];
+ };
- Kartograph.prototype.clear = function() {
- var id, me, sg, _i, _len, _ref4;
- me = this;
- if (me.layers != null) {
- for (id in me.layers) {
- me.layers[id].remove();
- }
- me.layers = {};
- me.layerIds = [];
- }
- if (me.symbolGroups != null) {
- _ref4 = me.symbolGroups;
- for (_i = 0, _len = _ref4.length; _i < _len; _i++) {
- sg = _ref4[_i];
- sg.remove();
- }
- me.symbolGroups = [];
- }
- if (me.paper != null) {
- $(me.paper.canvas).remove();
- return me.paper = void 0;
- }
- };
+ Kartograph.prototype.fadeIn = function (opts) {
+ var dur, duration, id, layer_id, me, path, paths, _ref4, _ref5, _ref6, _results;
+ if (opts == null) {
+ opts = {};
+ }
+ me = this;
+ layer_id = (_ref4 = opts.layer) != null ? _ref4 : me.layerIds[me.layerIds.length - 1];
+ duration = (_ref5 = opts.duration) != null ? _ref5 : 500;
+ _ref6 = me.layers[layer_id].pathsById;
+ _results = [];
+ for (id in _ref6) {
+ paths = _ref6[id];
+ _results.push((function () {
+ var _i, _len, _results1;
+ _results1 = [];
+ for (_i = 0, _len = paths.length; _i < _len; _i++) {
+ path = paths[_i];
+ if (__type(duration) === "function") {
+ dur = duration(path.data);
+ } else {
+ dur = duration;
+ }
+ path.svgPath.attr('opacity', 0);
+ _results1.push(path.svgPath.animate({
+ opacity: 1
+ }, dur));
+ }
+ return _results1;
+ })());
+ }
+ return _results;
+ };
- Kartograph.prototype.loadCSS = function(url, callback) {
- /*
- loads a stylesheet
- */
-
- var me;
- me = this;
- if ($.browser.msie) {
- return $.ajax({
- url: url,
- dataType: 'text',
- success: function(resp) {
- me.styles = kartograph.parsecss(resp);
- return callback();
- },
- error: function(a, b, c) {
- return warn('error while loading ' + url, a, b, c);
- }
- });
- } else {
- $('body').append('<link rel="stylesheet" href="' + url + '" />');
- return callback();
- }
- };
+ /*
+ end of public API
+ */
- Kartograph.prototype.applyCSS = function(el, className) {
- /*
- applies pre-loaded css styles to
- raphael elements
- */
- var classes, k, me, p, props, sel, selectors, _i, _j, _len, _len1, _ref4, _ref5, _ref6, _ref7;
- me = this;
- if (!(me.styles != null)) {
- return el;
- }
- if ((_ref4 = me._pathTypes) == null) {
- me._pathTypes = ["path", "circle", "rectangle", "ellipse"];
- }
- if ((_ref5 = me._regardStyles) == null) {
- me._regardStyles = ["fill", "stroke", "fill-opacity", "stroke-width", "stroke-opacity"];
- }
- for (sel in me.styles) {
- p = sel;
- _ref6 = p.split(',');
- for (_i = 0, _len = _ref6.length; _i < _len; _i++) {
- selectors = _ref6[_i];
- p = selectors.split(' ');
- p = p[p.length - 1];
- p = p.split(':');
- if (p.length > 1) {
- continue;
- }
- p = p[0].split('.');
- classes = p.slice(1);
- if (classes.length > 0 && classes.indexOf(className) < 0) {
- continue;
- }
- p = p[0];
- if (me._pathTypes.indexOf(p) >= 0 && p !== el.type) {
- continue;
- }
- props = me.styles[sel];
- _ref7 = me._regardStyles;
- for (_j = 0, _len1 = _ref7.length; _j < _len1; _j++) {
- k = _ref7[_j];
- if (props[k] != null) {
- el.attr(k, props[k]);
- }
- }
- }
- }
- return el;
- };
+ Kartograph.prototype.loadCoastline = function () {
+ var me;
+ me = this;
+ return $.ajax({
+ url: 'coastline.json',
+ success: me.renderCoastline,
+ context: me
+ });
+ };
- Kartograph.prototype.style = function(layer, prop, value, duration, delay) {
- var me;
- me = this;
- layer = me.getLayer(layer);
- if (layer != null) {
- return layer.style(prop, value, duration, delay);
- }
- };
+ Kartograph.prototype.resize = function (w, h) {
+ /*
+ forces redraw of every layer
+ */
- return Kartograph;
+ var cnt, halign, id, layer, me, padding, sg, valign, vp, zoom, _i, _len, _ref4, _ref5, _ref6, _ref7, _ref8;
+ me = this;
+ cnt = me.container;
+ if (w == null) {
+ w = cnt.width();
+ }
+ if (h == null) {
+ h = cnt.height();
+ }
+ me.viewport = vp = new kartograph.BBox(0, 0, w, h);
+ if (me.paper != null) {
+ me.paper.setSize(vp.width, vp.height);
+ }
+ vp = me.viewport;
+ padding = (_ref4 = me.opts.padding) != null ? _ref4 : 0;
+ halign = (_ref5 = me.opts.halign) != null ? _ref5 : 'center';
+ valign = (_ref6 = me.opts.valign) != null ? _ref6 : 'center';
+ zoom = me.opts.zoom;
+ me.viewBC = new kartograph.View(me.viewAB.asBBox(), vp.width * zoom, vp.height * zoom, padding, halign, valign);
+ _ref7 = me.layers;
+ for (id in _ref7) {
+ layer = _ref7[id];
+ layer.setView(me.viewBC);
+ }
+ if (me.symbolGroups != null) {
+ _ref8 = me.symbolGroups;
+ for (_i = 0, _len = _ref8.length; _i < _len; _i++) {
+ sg = _ref8[_i];
+ sg.onResize();
+ }
+ }
+ };
- })();
+ Kartograph.prototype.lonlat2xy = function (lonlat) {
+ var a, me;
+ me = this;
+ if (lonlat.length === 2) {
+ lonlat = new kartograph.LonLat(lonlat[0], lonlat[1]);
+ }
+ if (lonlat.length === 3) {
+ lonlat = new kartograph.LonLat(lonlat[0], lonlat[1], lonlat[2]);
+ }
+ a = me.proj.project(lonlat.lon, lonlat.lat, lonlat.alt);
+ return me.viewBC.project(me.viewAB.project(a));
+ };
- kartograph.Kartograph = Kartograph;
+ Kartograph.prototype.showZoomControls = function () {
+ var me;
+ me = this;
+ me.zc = new PanAndZoomControl(me);
+ return me;
+ };
- kartograph.map = function(container, width, height) {
- /* short-hand constructor
- */
- return new Kartograph(container, width, height);
- };
+ Kartograph.prototype.addSymbolGroup = function (symbolgroup) {
+ var me, _ref4;
+ me = this;
+ if ((_ref4 = me.symbolGroups) == null) {
+ me.symbolGroups = [];
+ }
+ return me.symbolGroups.push(symbolgroup);
+ };
- kartograph.__mapCache = {};
+ Kartograph.prototype.removeSymbols = function (index) {
+ var me, sg, _i, _len, _ref4, _results;
+ me = this;
+ if (index != null) {
+ return me.symbolGroups[index].remove();
+ } else {
+ _ref4 = me.symbolGroups;
+ _results = [];
+ for (_i = 0, _len = _ref4.length; _i < _len; _i++) {
+ sg = _ref4[_i];
+ _results.push(sg.remove());
+ }
+ return _results;
+ }
+ };
+
+ Kartograph.prototype.clear = function () {
+ var id, me, sg, _i, _len, _ref4;
+ me = this;
+ if (me.layers != null) {
+ for (id in me.layers) {
+ me.layers[id].remove();
+ }
+ me.layers = {};
+ me.layerIds = [];
+ }
+ if (me.symbolGroups != null) {
+ _ref4 = me.symbolGroups;
+ for (_i = 0, _len = _ref4.length; _i < _len; _i++) {
+ sg = _ref4[_i];
+ sg.remove();
+ }
+ me.symbolGroups = [];
+ }
+ if (me.paper != null) {
+ $(me.paper.canvas).remove();
+ return me.paper = void 0;
+ }
+ };
- /*
- kartograph - a svg mapping library
- Copyright (C) 2011 Gregor Aisch
-
- This library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Lesser General Public
- License as published by the Free Software Foundation; either
- version 2.1 of the License, or (at your option) any later version.
-
- This library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public
- License along with this library. If not, see <http://www.gnu.org/licenses/>.
- */
+ Kartograph.prototype.loadCSS = function (url, callback) {
+ /*
+ loads a stylesheet
+ */
+
+ var me;
+ me = this;
+ if ($.browser.msie) {
+ return $.ajax({
+ url: url,
+ dataType: 'text',
+ success: function (resp) {
+ me.styles = kartograph.parsecss(resp);
+ return callback();
+ },
+ error: function (a, b, c) {
+ return warn('error while loading ' + url, a, b, c);
+ }
+ });
+ } else {
+ $('body').append('<link rel="stylesheet" href="' + url + '" />');
+ return callback();
+ }
+ };
+ Kartograph.prototype.applyCSS = function (el, className) {
+ /*
+ applies pre-loaded css styles to
+ raphael elements
+ */
- LonLat = (function() {
- /*
- represents a Point
- */
-
- function LonLat(lon, lat, alt) {
- if (alt == null) {
- alt = 0;
- }
- this.lon = Number(lon);
- this.lat = Number(lat);
- this.alt = Number(alt);
- }
+ var classes, k, me, p, props, sel, selectors, _i, _j, _len, _len1, _ref4, _ref5, _ref6, _ref7;
+ me = this;
+ if (!(me.styles != null)) {
+ return el;
+ }
+ if ((_ref4 = me._pathTypes) == null) {
+ me._pathTypes = ["path", "circle", "rectangle", "ellipse"];
+ }
+ if ((_ref5 = me._regardStyles) == null) {
+ me._regardStyles = ["fill", "stroke", "fill-opacity", "stroke-width", "stroke-opacity"];
+ }
+ for (sel in me.styles) {
+ p = sel;
+ _ref6 = p.split(',');
+ for (_i = 0, _len = _ref6.length; _i < _len; _i++) {
+ selectors = _ref6[_i];
+ p = selectors.split(' ');
+ p = p[p.length - 1];
+ p = p.split(':');
+ if (p.length > 1) {
+ continue;
+ }
+ p = p[0].split('.');
+ classes = p.slice(1);
+ if (classes.length > 0 && classes.indexOf(className) < 0) {
+ continue;
+ }
+ p = p[0];
+ if (me._pathTypes.indexOf(p) >= 0 && p !== el.type) {
+ continue;
+ }
+ props = me.styles[sel];
+ _ref7 = me._regardStyles;
+ for (_j = 0, _len1 = _ref7.length; _j < _len1; _j++) {
+ k = _ref7[_j];
+ if (props[k] != null) {
+ el.attr(k, props[k]);
+ }
+ }
+ }
+ }
+ return el;
+ };
+
+ Kartograph.prototype.style = function (layer, prop, value, duration, delay) {
+ var me;
+ me = this;
+ layer = me.getLayer(layer);
+ if (layer != null) {
+ return layer.style(prop, value, duration, delay);
+ }
+ };
- LonLat.prototype.distance = function(ll) {
- var R, a, c, dLat, dLon, deg2rad, lat1, lat2, me;
- me = this;
- R = 6371;
- deg2rad = Math.PI / 180;
- dLat = (ll.lat - me.lat) * deg2rad;
- dLon = (ll.lon - me.lon) * deg2rad;
- lat1 = me.lat * deg2rad;
- lat2 = ll.lat * deg2rad;
- a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.sin(dLon / 2) * Math.sin(dLon / 2) * Math.cos(lat1) * Math.cos(lat2);
- c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
- return R * c;
+ return Kartograph;
+
+ })();
+
+ kartograph.Kartograph = Kartograph;
+
+ kartograph.map = function (container, width, height) {
+ /* short-hand constructor
+ */
+ return new Kartograph(container, width, height);
};
- return LonLat;
+ kartograph.__mapCache = {};
- })();
+ /*
+ kartograph - a svg mapping library
+ Copyright (C) 2011 Gregor Aisch
- LatLon = (function(_super) {
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
- __extends(LatLon, _super);
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
- function LatLon(lat, lon, alt) {
- if (alt == null) {
- alt = 0;
- }
- LatLon.__super__.constructor.call(this, lon, lat, alt);
- }
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
- return LatLon;
-
- })(LonLat);
-
- kartograph.LonLat = LonLat;
-
- kartograph.LatLon = LatLon;
-
- /*
- kartograph - a svg mapping library
- Copyright (C) 2011,2012 Gregor Aisch
-
- This library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Lesser General Public
- License as published by the Free Software Foundation; either
- version 2.1 of the License, or (at your option) any later version.
-
- This library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public
- License along with this library. If not, see <http://www.gnu.org/licenses/>.
- */
-
-
- MapLayer = (function() {
-
- function MapLayer(layer_id, path_id, map, filter) {
- var me;
- me = this;
- me.id = layer_id;
- me.path_id = path_id;
- me.paper = map.paper;
- me.view = map.viewBC;
- me.map = map;
- me.filter = filter;
- }
- MapLayer.prototype.addPath = function(svg_path, titles) {
- var layerPath, me, _base2, _name, _ref4, _ref5, _ref6;
- me = this;
- if ((_ref4 = me.paths) == null) {
- me.paths = [];
- }
- layerPath = new MapLayerPath(svg_path, me.id, me.map, titles);
- if (__type(me.filter) === 'function') {
- if (me.filter(layerPath.data) === false) {
- layerPath.remove();
- return;
- }
- }
- me.paths.push(layerPath);
- if (me.path_id != null) {
- if ((_ref5 = me.pathsById) == null) {
- me.pathsById = {};
- }
- if ((_ref6 = (_base2 = me.pathsById)[_name = layerPath.data[me.path_id]]) == null) {
- _base2[_name] = [];
+ LonLat = (function () {
+ /*
+ represents a Point
+ */
+
+ function LonLat(lon, lat, alt) {
+ if (alt == null) {
+ alt = 0;
+ }
+ this.lon = Number(lon);
+ this.lat = Number(lat);
+ this.alt = Number(alt);
}
- return me.pathsById[layerPath.data[me.path_id]].push(layerPath);
- }
- };
- MapLayer.prototype.hasPath = function(id) {
- var me;
- me = this;
- return (me.pathsById != null) && (me.pathsById[id] != null);
- };
+ LonLat.prototype.distance = function (ll) {
+ var R, a, c, dLat, dLon, deg2rad, lat1, lat2, me;
+ me = this;
+ R = 6371;
+ deg2rad = Math.PI / 180;
+ dLat = (ll.lat - me.lat) * deg2rad;
+ dLon = (ll.lon - me.lon) * deg2rad;
+ lat1 = me.lat * deg2rad;
+ lat2 = ll.lat * deg2rad;
+ a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.sin(dLon / 2) * Math.sin(dLon / 2) * Math.cos(lat1) * Math.cos(lat2);
+ c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
+ return R * c;
+ };
- MapLayer.prototype.getPathsData = function() {
- /* returns a list of all shape data dictionaries
- */
-
- var me, path, pd, _i, _len, _ref4;
- me = this;
- pd = [];
- _ref4 = me.paths;
- for (_i = 0, _len = _ref4.length; _i < _len; _i++) {
- path = _ref4[_i];
- pd.push(path.data);
- }
- return pd;
- };
+ return LonLat;
- MapLayer.prototype.getPath = function(id) {
- var me;
- me = this;
- if (me.hasPath(id)) {
- return me.pathsById[id][0];
- }
- return null;
- };
+ })();
- MapLayer.prototype.getPaths = function(query) {
- var key, match, matches, me, path, _i, _len, _ref4;
- me = this;
- matches = [];
- if (__type(query) === 'object') {
- _ref4 = me.paths;
- for (_i = 0, _len = _ref4.length; _i < _len; _i++) {
- path = _ref4[_i];
- match = true;
- for (key in query) {
- match = match && path.data[key] === query[key];
- }
- if (match) {
- matches.push(path);
- }
+ LatLon = (function (_super) {
+
+ __extends(LatLon, _super);
+
+ function LatLon(lat, lon, alt) {
+ if (alt == null) {
+ alt = 0;
+ }
+ LatLon.__super__.constructor.call(this, lon, lat, alt);
}
- }
- return matches;
- };
- MapLayer.prototype.setView = function(view) {
- /*
- # after resizing of the map, each layer gets a new view
- */
-
- var me, path, _i, _len, _ref4;
- me = this;
- _ref4 = me.paths;
- for (_i = 0, _len = _ref4.length; _i < _len; _i++) {
- path = _ref4[_i];
- path.setView(view);
- }
- return me;
- };
+ return LatLon;
- MapLayer.prototype.remove = function() {
- /*
- removes every path
- */
-
- var me, path, _i, _len, _ref4, _results;
- me = this;
- _ref4 = me.paths;
- _results = [];
- for (_i = 0, _len = _ref4.length; _i < _len; _i++) {
- path = _ref4[_i];
- _results.push(path.remove());
- }
- return _results;
- };
+ })(LonLat);
- MapLayer.prototype.style = function(prop, value, duration, delay) {
- var anim, at, dly, dur, key, me, path, val, _i, _len, _ref4;
- me = this;
- if (__type(prop) === "object") {
- for (key in prop) {
- val = prop[key];
- me.style(key, val);
- }
- return me;
- }
- if (duration == null) {
- duration = 0;
- }
- if (delay == null) {
- delay = 0;
- }
- _ref4 = me.paths;
- for (_i = 0, _len = _ref4.length; _i < _len; _i++) {
- path = _ref4[_i];
- val = resolve(value, path.data);
- dur = resolve(duration, path.data);
- dly = resolve(delay, path.data);
- if (dur > 0) {
- at = {};
- at[prop] = val;
- anim = Raphael.animation(at, dur * 1000);
- path.svgPath.animate(anim.delay(dly * 1000));
- } else {
- path.svgPath.attr(prop, val);
+ kartograph.LonLat = LonLat;
+
+ kartograph.LatLon = LatLon;
+
+ /*
+ kartograph - a svg mapping library
+ Copyright (C) 2011,2012 Gregor Aisch
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+ MapLayer = (function () {
+
+ function MapLayer(layer_id, path_id, map, filter) {
+ var me;
+ me = this;
+ me.id = layer_id;
+ me.path_id = path_id;
+ me.paper = map.paper;
+ me.view = map.viewBC;
+ me.map = map;
+ me.filter = filter;
}
- }
- return me;
- };
- MapLayer.prototype.on = function(event, callback) {
- var EventContext, ctx, me, path, _i, _len, _ref4;
- me = this;
- EventContext = (function() {
+ MapLayer.prototype.addPath = function (svg_path, titles) {
+ var layerPath, me, _base2, _name, _ref4, _ref5, _ref6;
+ me = this;
+ if ((_ref4 = me.paths) == null) {
+ me.paths = [];
+ }
+ layerPath = new MapLayerPath(svg_path, me.id, me.map, titles);
+ if (__type(me.filter) === 'function') {
+ if (me.filter(layerPath.data) === false) {
+ layerPath.remove();
+ return;
+ }
+ }
+ me.paths.push(layerPath);
+ if (me.path_id != null) {
+ if ((_ref5 = me.pathsById) == null) {
+ me.pathsById = {};
+ }
+ if ((_ref6 = (_base2 = me.pathsById)[_name = layerPath.data[me.path_id]]) == null) {
+ _base2[_name] = [];
+ }
+ return me.pathsById[layerPath.data[me.path_id]].push(layerPath);
+ }
+ };
- function EventContext(type, cb, layer) {
- this.type = type;
- this.cb = cb;
- this.layer = layer;
- this.handle = __bind(this.handle, this);
+ MapLayer.prototype.hasPath = function (id) {
+ var me;
+ me = this;
+ return (me.pathsById != null) && (me.pathsById[id] != null);
+ };
- }
+ MapLayer.prototype.getPathsData = function () {
+ /* returns a list of all shape data dictionaries
+ */
+
+ var me, path, pd, _i, _len, _ref4;
+ me = this;
+ pd = [];
+ _ref4 = me.paths;
+ for (_i = 0, _len = _ref4.length; _i < _len; _i++) {
+ path = _ref4[_i];
+ pd.push(path.data);
+ }
+ return pd;
+ };
- EventContext.prototype.handle = function(e) {
- var path;
- me = this;
- path = me.layer.map.pathById[e.target.getAttribute('id')];
- return me.cb(path.data, path.svgPath, e);
+ MapLayer.prototype.getPath = function (id) {
+ var me;
+ me = this;
+ if (me.hasPath(id)) {
+ return me.pathsById[id][0];
+ }
+ return null;
};
- return EventContext;
+ MapLayer.prototype.getPaths = function (query) {
+ var key, match, matches, me, path, _i, _len, _ref4;
+ me = this;
+ matches = [];
+ if (__type(query) === 'object') {
+ _ref4 = me.paths;
+ for (_i = 0, _len = _ref4.length; _i < _len; _i++) {
+ path = _ref4[_i];
+ match = true;
+ for (key in query) {
+ match = match && path.data[key] === query[key];
+ }
+ if (match) {
+ matches.push(path);
+ }
+ }
+ }
+ return matches;
+ };
- })();
- ctx = new EventContext(event, callback, me);
- _ref4 = me.paths;
- for (_i = 0, _len = _ref4.length; _i < _len; _i++) {
- path = _ref4[_i];
- $(path.svgPath.node).bind(event, ctx.handle);
- }
- return me;
- };
+ MapLayer.prototype.setView = function (view) {
+ /*
+ # after resizing of the map, each layer gets a new view
+ */
+
+ var me, path, _i, _len, _ref4;
+ me = this;
+ _ref4 = me.paths;
+ for (_i = 0, _len = _ref4.length; _i < _len; _i++) {
+ path = _ref4[_i];
+ path.setView(view);
+ }
+ return me;
+ };
- MapLayer.prototype.tooltips = function(content, delay) {
- var me, path, setTooltip, tt, _i, _len, _ref4;
- me = this;
- setTooltip = function(path, tt) {
- var cfg;
- cfg = {
- position: {
- target: 'mouse',
- viewport: $(window),
- adjust: {
- x: 7,
- y: 7
- }
- },
- show: {
- delay: delay != null ? delay : 20
- },
- events: {
- show: function(evt, api) {
- return $('.qtip').filter(function() {
- return this !== api.elements.tooltip.get(0);
- }).hide();
- }
- },
- content: {}
- };
- if (tt != null) {
- if (typeof tt === "string") {
- cfg.content.text = tt;
- } else if ($.isArray(tt)) {
- cfg.content.title = tt[0];
- cfg.content.text = tt[1];
- }
- } else {
- cfg.content.text = 'n/a';
- }
- return $(path.svgPath.node).qtip(cfg);
- };
- _ref4 = me.paths;
- for (_i = 0, _len = _ref4.length; _i < _len; _i++) {
- path = _ref4[_i];
- tt = resolve(content, path.data);
- setTooltip(path, tt);
- }
- return me;
- };
+ MapLayer.prototype.remove = function () {
+ /*
+ removes every path
+ */
+
+ var me, path, _i, _len, _ref4, _results;
+ me = this;
+ _ref4 = me.paths;
+ _results = [];
+ for (_i = 0, _len = _ref4.length; _i < _len; _i++) {
+ path = _ref4[_i];
+ _results.push(path.remove());
+ }
+ return _results;
+ };
- MapLayer.prototype.sort = function(sortBy) {
- var lp, me, path, _i, _len, _ref4;
- me = this;
- me.paths.sort(function(a, b) {
- var av, bv, _ref4;
- av = sortBy(a.data);
- bv = sortBy(b.data);
- if (av === bv) {
- return 0;
- }
- return (_ref4 = av > bv) != null ? _ref4 : {
- 1: -1
- };
- });
- lp = false;
- _ref4 = me.paths;
- for (_i = 0, _len = _ref4.length; _i < _len; _i++) {
- path = _ref4[_i];
- if (lp) {
- path.svgPath.insertAfter(lp.svgPath);
- }
- lp = path;
- }
- return me;
- };
+ MapLayer.prototype.style = function (prop, value, duration, delay) {
+ var anim, at, dly, dur, key, me, path, val, _i, _len, _ref4;
+ me = this;
+ if (__type(prop) === "object") {
+ for (key in prop) {
+ val = prop[key];
+ me.style(key, val);
+ }
+ return me;
+ }
+ if (duration == null) {
+ duration = 0;
+ }
+ if (delay == null) {
+ delay = 0;
+ }
+ _ref4 = me.paths;
+ for (_i = 0, _len = _ref4.length; _i < _len; _i++) {
+ path = _ref4[_i];
+ val = resolve(value, path.data);
+ dur = resolve(duration, path.data);
+ dly = resolve(delay, path.data);
+ if (dur > 0) {
+ at = {};
+ at[prop] = val;
+ anim = Raphael.animation(at, dur * 1000);
+ path.svgPath.animate(anim.delay(dly * 1000));
+ } else {
+ path.svgPath.attr(prop, val);
+ }
+ }
+ return me;
+ };
- return MapLayer;
+ MapLayer.prototype.on = function (event, callback) {
+ var EventContext, ctx, me, path, _i, _len, _ref4;
+ me = this;
+ EventContext = (function () {
- })();
+ function EventContext(type, cb, layer) {
+ this.type = type;
+ this.cb = cb;
+ this.layer = layer;
+ this.handle = __bind(this.handle, this);
- resolve = function(prop, data) {
- if (__type(prop) === 'function') {
- return prop(data);
- }
- return prop;
- };
-
- map_layer_path_uid = 0;
-
- /*
- kartograph - a svg mapping library
- Copyright (C) 2011,2012 Gregor Aisch
-
- This library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Lesser General Public
- License as published by the Free Software Foundation; either
- version 2.1 of the License, or (at your option) any later version.
-
- This library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public
- License along with this library. If not, see <http://www.gnu.org/licenses/>.
- */
-
-
- MapLayerPath = (function() {
-
- function MapLayerPath(svg_path, layer_id, map, titles) {
- var attr, data, i, me, paper, path, title, uid, v, view, vn, _i, _ref4;
- me = this;
- paper = map.paper;
- view = map.viewBC;
- me.path = path = kartograph.geom.Path.fromSVG(svg_path);
- me.vpath = view.projectPath(path);
- me.svgPath = me.vpath.toSVG(paper);
- if (!(map.styles != null)) {
- me.svgPath.node.setAttribute('class', layer_id);
- } else {
- map.applyCSS(me.svgPath, layer_id);
- }
- uid = 'path_' + map_layer_path_uid++;
- me.svgPath.node.setAttribute('id', uid);
- map.pathById[uid] = me;
- data = {};
- for (i = _i = 0, _ref4 = svg_path.attributes.length - 1; 0 <= _ref4 ? _i <= _ref4 : _i >= _ref4; i = 0 <= _ref4 ? ++_i : --_i) {
- attr = svg_path.attributes[i];
- if (attr.name.substr(0, 5) === "data-") {
- v = attr.value;
- vn = Number(v);
- if (v.trim() !== "" && vn === v && !isNaN(vn)) {
- v = vn;
- }
- data[attr.name.substr(5)] = v;
- }
- }
- me.data = data;
- if (__type(titles) === 'string') {
- title = titles;
- } else if (__type(titles) === 'function') {
- title = titles(data);
- }
- if (title != null) {
- me.svgPath.attr('title', title);
- }
- }
+ }
- MapLayerPath.prototype.setView = function(view) {
- var me, path, path_str;
- me = this;
- path = view.projectPath(me.path);
- me.vpath = path;
- if (me.path.type === "path") {
- path_str = path.svgString();
- return me.svgPath.attr({
- path: path_str
- });
- } else if (me.path.type === "circle") {
- return me.svgPath.attr({
- cx: path.x,
- cy: path.y,
- r: path.r
- });
- }
- };
+ EventContext.prototype.handle = function (e) {
+ var path;
+ me = this;
+ path = me.layer.map.pathById[e.target.getAttribute('id')];
+ return me.cb(path.data, path.svgPath, e);
+ };
+
+ return EventContext;
+
+ })();
+ ctx = new EventContext(event, callback, me);
+ _ref4 = me.paths;
+ for (_i = 0, _len = _ref4.length; _i < _len; _i++) {
+ path = _ref4[_i];
+ $(path.svgPath.node).bind(event, ctx.handle);
+ }
+ return me;
+ };
+
+ MapLayer.prototype.tooltips = function (content, delay) {
+ var me, path, setTooltip, tt, _i, _len, _ref4;
+ me = this;
+ setTooltip = function (path, tt) {
+ var cfg;
+ cfg = {
+ position: {
+ target: 'mouse',
+ viewport: $(window),
+ adjust: {
+ x: 7,
+ y: 7
+ }
+ },
+ show: {
+ delay: delay != null ? delay : 20
+ },
+ events: {
+ show: function (evt, api) {
+ return $('.qtip').filter(function () {
+ return this !== api.elements.tooltip.get(0);
+ }).hide();
+ }
+ },
+ content: {}
+ };
+ if (tt != null) {
+ if (typeof tt === "string") {
+ cfg.content.text = tt;
+ } else if ($.isArray(tt)) {
+ cfg.content.title = tt[0];
+ cfg.content.text = tt[1];
+ }
+ } else {
+ cfg.content.text = 'n/a';
+ }
+ return $(path.svgPath.node).qtip(cfg);
+ };
+ _ref4 = me.paths;
+ for (_i = 0, _len = _ref4.length; _i < _len; _i++) {
+ path = _ref4[_i];
+ tt = resolve(content, path.data);
+ setTooltip(path, tt);
+ }
+ return me;
+ };
- MapLayerPath.prototype.remove = function() {
- var me;
- me = this;
- return me.svgPath.remove();
+ MapLayer.prototype.sort = function (sortBy) {
+ var lp, me, path, _i, _len, _ref4;
+ me = this;
+ me.paths.sort(function (a, b) {
+ var av, bv, _ref4;
+ av = sortBy(a.data);
+ bv = sortBy(b.data);
+ if (av === bv) {
+ return 0;
+ }
+ return (_ref4 = av > bv) != null ? _ref4 : {
+ 1: -1
+ };
+ });
+ lp = false;
+ _ref4 = me.paths;
+ for (_i = 0, _len = _ref4.length; _i < _len; _i++) {
+ path = _ref4[_i];
+ if (lp) {
+ path.svgPath.insertAfter(lp.svgPath);
+ }
+ lp = path;
+ }
+ return me;
+ };
+
+ return MapLayer;
+
+ })();
+
+ resolve = function (prop, data) {
+ if (__type(prop) === 'function') {
+ return prop(data);
+ }
+ return prop;
};
- return MapLayerPath;
-
- })();
-
- /*
- kartograph - a svg mapping library
- Copyright (C) 2011 Gregor Aisch
-
- This library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Lesser General Public
- License as published by the Free Software Foundation; either
- version 2.1 of the License, or (at your option) any later version.
-
- This library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public
- License along with this library. If not, see <http://www.gnu.org/licenses/>.
- */
-
-
- /*
- This is a reduced version of Danial Wachsstocks jQuery based CSS parser
- Everything is removed but the core css-to-object parsing
-
- jQuery based CSS parser
- documentation: http://youngisrael-stl.org/wordpress/2009/01/16/jquery-css-parser/
- Version: 1.3
- Copyright (c) 2011 Daniel Wachsstock
- MIT license:
- Permission is hereby granted, free of charge, to any person
- obtaining a copy of this software and associated documentation
- files (the "Software"), to deal in the Software without
- restriction, including without limitation the rights to use,
- copy, modify, merge, publish, distribute, sublicense, and/or sell
- copies of the Software, and to permit persons to whom the
- Software is furnished to do so, subject to the following
- conditions:
-
- The above copyright notice and this permission notice shall be
- included in all copies or substantial portions of the Software.
-
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
- OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
- NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
- HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
- WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
- FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
- OTHER DEALINGS IN THE SOFTWARE.
- */
-
-
- kartograph.parsecss = function(str, callback) {
- var css, k, props, ret, v, _i, _len, _ref4;
- ret = {};
- str = munge(str);
- _ref4 = str.split('`b%');
- for (_i = 0, _len = _ref4.length; _i < _len; _i++) {
- css = _ref4[_i];
- css = css.split('%b`');
- if (css.length < 2) {
- continue;
- }
- css[0] = restore(css[0]);
- props = parsedeclarations(css[1]);
- if (ret[css[0]] != null) {
- for (k in props) {
- v = props[k];
- ret[css[0]][k] = v;
+ map_layer_path_uid = 0;
+
+ /*
+ kartograph - a svg mapping library
+ Copyright (C) 2011,2012 Gregor Aisch
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+ MapLayerPath = (function () {
+
+ function MapLayerPath(svg_path, layer_id, map, titles) {
+ var attr, data, i, me, paper, path, title, uid, v, view, vn, _i, _ref4;
+ me = this;
+ paper = map.paper;
+ view = map.viewBC;
+ me.path = path = kartograph.geom.Path.fromSVG(svg_path);
+ me.vpath = view.projectPath(path);
+ me.svgPath = me.vpath.toSVG(paper);
+ if (!(map.styles != null)) {
+ me.svgPath.node.setAttribute('class', layer_id);
+ } else {
+ map.applyCSS(me.svgPath, layer_id);
+ }
+ uid = 'path_' + map_layer_path_uid++;
+ me.svgPath.node.setAttribute('id', uid);
+ map.pathById[uid] = me;
+ data = {};
+ for (i = _i = 0, _ref4 = svg_path.attributes.length - 1; 0 <= _ref4 ? _i <= _ref4 : _i >= _ref4; i = 0 <= _ref4 ? ++_i : --_i) {
+ attr = svg_path.attributes[i];
+ if (attr.name.substr(0, 5) === "data-") {
+ v = attr.value;
+ vn = Number(v);
+ if (v.trim() !== "" && vn === v && !isNaN(vn)) {
+ v = vn;
+ }
+ data[attr.name.substr(5)] = v;
+ }
+ }
+ me.data = data;
+ if (__type(titles) === 'string') {
+ title = titles;
+ } else if (__type(titles) === 'function') {
+ title = titles(data);
+ }
+ if (title != null) {
+ me.svgPath.attr('title', title);
+ }
}
- } else {
- ret[css[0]] = props;
- }
- }
- if (__type(callback) === 'function') {
- callback(ret);
- } else {
- return ret;
- }
- };
-
- munged = {};
-
- parsedeclarations = function(index) {
- var decl, parsed, str, _i, _len, _ref4;
- str = munged[index].replace(/^{|}$/g, '');
- str = munge(str);
- parsed = {};
- _ref4 = str.split(';');
- for (_i = 0, _len = _ref4.length; _i < _len; _i++) {
- decl = _ref4[_i];
- decl = decl.split(':');
- if (decl.length < 2) {
- continue;
- }
- parsed[restore(decl[0])] = restore(decl.slice(1).join(':'));
- }
- return parsed;
- };
-
- REbraces = /{[^{}]*}/;
-
- REfull = /\[[^\[\]]*\]|{[^{}]*}|\([^()]*\)|function(\s+\w+)?(\s*%b`\d+`b%){2}/;
-
- REcomment_string = /(?:\/\*(?:[^\*]|\*[^\/])*\*\/)|(\\.|"(?:[^\\\"]|\\.|\\\n)*"|'(?:[^\\\']|\\.|\\\n)*')/g;
-
- REmunged = /%\w`(\d+)`\w%/;
-
- uid = 0;
-
- munge = function(str, full) {
- var RE, match, replacement;
- str = str.replace(REcomment_string, function(s, string) {
- var replacement;
- if (!string) {
- return '';
- }
- replacement = '%s`' + (++uid) + '`s%';
- munged[uid] = string.replace(/^\\/, '');
- return replacement;
- });
- RE = full ? REfull : REbraces;
- while (match = RE.exec(str)) {
- replacement = '%b`' + (++uid) + '`b%';
- munged[uid] = match[0];
- str = str.replace(RE, replacement);
- }
- return str;
- };
- restore = function(str) {
- var match;
- if (!(str != null)) {
- return str;
- }
- while (match = REmunged.exec(str)) {
- str = str.replace(REmunged, munged[match[1]]);
- }
- return str.trim();
- };
-
- /*
- kartograph - a svg mapping library
- Copyright (C) 2011 Gregor Aisch
-
- This library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Lesser General Public
- License as published by the Free Software Foundation; either
- version 2.1 of the License, or (at your option) any later version.
-
- This library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public
- License along with this library. If not, see <http://www.gnu.org/licenses/>.
- */
-
-
- if ((_ref4 = kartograph.geom) == null) {
- kartograph.geom = {};
- }
-
- Path = (function() {
+ MapLayerPath.prototype.setView = function (view) {
+ var me, path, path_str;
+ me = this;
+ path = view.projectPath(me.path);
+ me.vpath = path;
+ if (me.path.type === "path") {
+ path_str = path.svgString();
+ return me.svgPath.attr({
+ path: path_str
+ });
+ } else if (me.path.type === "circle") {
+ return me.svgPath.attr({
+ cx: path.x,
+ cy: path.y,
+ r: path.r
+ });
+ }
+ };
+
+ MapLayerPath.prototype.remove = function () {
+ var me;
+ me = this;
+ return me.svgPath.remove();
+ };
+
+ return MapLayerPath;
+
+ })();
+
/*
- represents complex polygons (aka multi-polygons)
- */
-
- function Path(type, contours, closed) {
- var self;
- if (closed == null) {
- closed = true;
- }
- self = this;
- self.type = type;
- self.contours = contours;
- self.closed = closed;
- }
+ kartograph - a svg mapping library
+ Copyright (C) 2011 Gregor Aisch
- Path.prototype.clipToBBox = function(bbox) {
- throw "path clipping is not implemented yet";
- };
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
- Path.prototype.toSVG = function(paper) {
- /* translates this path to a SVG path string
- */
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
- var str;
- str = this.svgString();
- return paper.path(str);
- };
- Path.prototype.svgString = function() {
- var contour, fst, glue, me, str, x, y, _i, _j, _len, _len1, _ref5, _ref6;
- me = this;
- str = "";
- glue = me.closed ? "Z M" : "M";
- _ref5 = me.contours;
- for (_i = 0, _len = _ref5.length; _i < _len; _i++) {
- contour = _ref5[_i];
- fst = true;
- str += str === "" ? "M" : glue;
- for (_j = 0, _len1 = contour.length; _j < _len1; _j++) {
- _ref6 = contour[_j], x = _ref6[0], y = _ref6[1];
- if (!fst) {
- str += "L";
- }
- str += x + ',' + y;
- fst = false;
+ /*
+ This is a reduced version of Danial Wachsstocks jQuery based CSS parser
+ Everything is removed but the core css-to-object parsing
+
+ jQuery based CSS parser
+ documentation: http://youngisrael-stl.org/wordpress/2009/01/16/jquery-css-parser/
+ Version: 1.3
+ Copyright (c) 2011 Daniel Wachsstock
+ MIT license:
+ Permission is hereby granted, free of charge, to any person
+ obtaining a copy of this software and associated documentation
+ files (the "Software"), to deal in the Software without
+ restriction, including without limitation the rights to use,
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the
+ Software is furnished to do so, subject to the following
+ conditions:
+
+ The above copyright notice and this permission notice shall be
+ included in all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+
+ kartograph.parsecss = function (str, callback) {
+ var css, k, props, ret, v, _i, _len, _ref4;
+ ret = {};
+ str = munge(str);
+ _ref4 = str.split('`b%');
+ for (_i = 0, _len = _ref4.length; _i < _len; _i++) {
+ css = _ref4[_i];
+ css = css.split('%b`');
+ if (css.length < 2) {
+ continue;
+ }
+ css[0] = restore(css[0]);
+ props = parsedeclarations(css[1]);
+ if (ret[css[0]] != null) {
+ for (k in props) {
+ v = props[k];
+ ret[css[0]][k] = v;
+ }
+ } else {
+ ret[css[0]] = props;
+ }
+ }
+ if (__type(callback) === 'function') {
+ callback(ret);
+ } else {
+ return ret;
}
- }
- if (me.closed) {
- str += "Z";
- }
- return str;
};
- Path.prototype.area = function() {
- var area, cnt, i, me, _i, _j, _len, _ref5, _ref6;
- me = this;
- if (me.areas != null) {
- return me._area;
- }
- me.areas = [];
- me._area = 0;
- _ref5 = me.contours;
- for (_i = 0, _len = _ref5.length; _i < _len; _i++) {
- cnt = _ref5[_i];
- area = 0;
- for (i = _j = 0, _ref6 = cnt.length - 2; 0 <= _ref6 ? _j <= _ref6 : _j >= _ref6; i = 0 <= _ref6 ? ++_j : --_j) {
- area += cnt[i][0] * cnt[i + 1][1] - cnt[i + 1][0] * cnt[i][1];
+ munged = {};
+
+ parsedeclarations = function (index) {
+ var decl, parsed, str, _i, _len, _ref4;
+ str = munged[index].replace(/^{|}$/g, '');
+ str = munge(str);
+ parsed = {};
+ _ref4 = str.split(';');
+ for (_i = 0, _len = _ref4.length; _i < _len; _i++) {
+ decl = _ref4[_i];
+ decl = decl.split(':');
+ if (decl.length < 2) {
+ continue;
+ }
+ parsed[restore(decl[0])] = restore(decl.slice(1).join(':'));
}
- area *= .5;
- area = area;
- me.areas.push(area);
- me._area += area;
- }
- return me._area;
+ return parsed;
};
- Path.prototype.centroid = function() {
- var S, a, area, cnt, cnt_orig, cx, cy, diff, dx, dy, i, j, k, l, len, me, p0, p1, s, sp, total_len, w, x, x_, y, y_, _i, _j, _k, _l, _lengths, _m, _ref5, _ref6, _ref7, _ref8, _ref9;
- me = this;
- if (me._centroid != null) {
- return me._centroid;
- }
- area = me.area();
- cx = cy = 0;
- for (i = _i = 0, _ref5 = me.contours.length - 1; 0 <= _ref5 ? _i <= _ref5 : _i >= _ref5; i = 0 <= _ref5 ? ++_i : --_i) {
- cnt_orig = me.contours[i];
- cnt = [];
- l = cnt_orig.length;
- for (j = _j = 0, _ref6 = l - 1; 0 <= _ref6 ? _j <= _ref6 : _j >= _ref6; j = 0 <= _ref6 ? ++_j : --_j) {
- p0 = cnt_orig[j];
- p1 = cnt_orig[(j + 1) % l];
- diff = 0;
- cnt.push(p0);
- if (p0[0] === p1[0]) {
- diff = Math.abs(p0[1] - p1[1]);
- }
- if (p0[1] === p1[1]) {
- diff = Math.abs(p0[0] - p1[0]);
- }
- if (diff > 10) {
- S = Math.floor(diff * 2);
- for (s = _k = 1, _ref7 = S - 1; 1 <= _ref7 ? _k <= _ref7 : _k >= _ref7; s = 1 <= _ref7 ? ++_k : --_k) {
- sp = [p0[0] + s / S * (p1[0] - p0[0]), p0[1] + s / S * (p1[1] - p0[1])];
- cnt.push(sp);
- }
- }
- }
- a = me.areas[i];
- x = y = x_ = y_ = 0;
- l = cnt.length;
- _lengths = [];
- total_len = 0;
- for (j = _l = 0, _ref8 = l - 1; 0 <= _ref8 ? _l <= _ref8 : _l >= _ref8; j = 0 <= _ref8 ? ++_l : --_l) {
- p0 = cnt[j];
- p1 = cnt[(j + 1) % l];
- dx = p1[0] - p0[0];
- dy = p1[1] - p0[1];
- len = Math.sqrt(dx * dx + dy * dy);
- _lengths.push(len);
- total_len += len;
- }
- for (j = _m = 0, _ref9 = l - 1; 0 <= _ref9 ? _m <= _ref9 : _m >= _ref9; j = 0 <= _ref9 ? ++_m : --_m) {
- p0 = cnt[j];
- w = _lengths[j] / total_len;
- x += w * p0[0];
- y += w * p0[1];
+ REbraces = /{[^{}]*}/;
+
+ REfull = /\[[^\[\]]*\]|{[^{}]*}|\([^()]*\)|function(\s+\w+)?(\s*%b`\d+`b%){2}/;
+
+ REcomment_string = /(?:\/\*(?:[^\*]|\*[^\/])*\*\/)|(\\.|"(?:[^\\\"]|\\.|\\\n)*"|'(?:[^\\\']|\\.|\\\n)*')/g;
+
+ REmunged = /%\w`(\d+)`\w%/;
+
+ uid = 0;
+
+ munge = function (str, full) {
+ var RE, match, replacement;
+ str = str.replace(REcomment_string, function (s, string) {
+ var replacement;
+ if (!string) {
+ return '';
+ }
+ replacement = '%s`' + (++uid) + '`s%';
+ munged[uid] = string.replace(/^\\/, '');
+ return replacement;
+ });
+ RE = full ? REfull : REbraces;
+ while (match = RE.exec(str)) {
+ replacement = '%b`' + (++uid) + '`b%';
+ munged[uid] = match[0];
+ str = str.replace(RE, replacement);
}
- k = a / area;
- cx += x * k;
- cy += y * k;
- }
- me._centroid = [cx, cy];
- return me._centroid;
+ return str;
};
- Path.prototype.isInside = function(x, y) {
- var bbox, cnt, i, me, _i, _ref5;
- me = this;
- bbox = me._bbox;
- if (x < bbox[0] || x > bbox[2] || y < bbox[1] || y > bbox[3]) {
- return false;
- }
- for (i = _i = 0, _ref5 = me.contours.length - 1; 0 <= _ref5 ? _i <= _ref5 : _i >= _ref5; i = 0 <= _ref5 ? ++_i : --_i) {
- cnt = me.contours[i];
- if (__point_in_polygon(cnt, [x, y])) {
- return true;
+ restore = function (str) {
+ var match;
+ if (!(str != null)) {
+ return str;
}
- }
- return false;
+ while (match = REmunged.exec(str)) {
+ str = str.replace(REmunged, munged[match[1]]);
+ }
+ return str.trim();
};
- return Path;
+ /*
+ kartograph - a svg mapping library
+ Copyright (C) 2011 Gregor Aisch
- })();
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
- kartograph.geom.Path = Path;
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
- Circle = (function(_super) {
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
- __extends(Circle, _super);
- function Circle(x, y, r) {
- this.x = x;
- this.y = y;
- this.r = r;
- Circle.__super__.constructor.call(this, 'circle', null, true);
+ if ((_ref4 = kartograph.geom) == null) {
+ kartograph.geom = {};
}
- Circle.prototype.toSVG = function(paper) {
- var me;
- me = this;
- return paper.circle(me.x, me.y, me.r);
- };
+ Path = (function () {
+ /*
+ represents complex polygons (aka multi-polygons)
+ */
- Circle.prototype.centroid = function() {
- var me;
- me = this;
- return [me.x, me.y];
- };
+ function Path(type, contours, closed) {
+ var self;
+ if (closed == null) {
+ closed = true;
+ }
+ self = this;
+ self.type = type;
+ self.contours = contours;
+ self.closed = closed;
+ }
- Circle.prototype.area = function() {
- var me;
- me = this;
- return Math.PI * me.r * m.r;
- };
+ Path.prototype.clipToBBox = function (bbox) {
+ throw "path clipping is not implemented yet";
+ };
- return Circle;
+ Path.prototype.toSVG = function (paper) {
+ /* translates this path to a SVG path string
+ */
- })(Path);
+ var str;
+ str = this.svgString();
+ return paper.path(str);
+ };
- kartograph.geom.Circle = Circle;
+ Path.prototype.svgString = function () {
+ var contour, fst, glue, me, str, x, y, _i, _j, _len, _len1, _ref5, _ref6;
+ me = this;
+ str = "";
+ glue = me.closed ? "Z M" : "M";
+ _ref5 = me.contours;
+ for (_i = 0, _len = _ref5.length; _i < _len; _i++) {
+ contour = _ref5[_i];
+ fst = true;
+ str += str === "" ? "M" : glue;
+ for (_j = 0, _len1 = contour.length; _j < _len1; _j++) {
+ _ref6 = contour[_j], x = _ref6[0], y = _ref6[1];
+ if (!fst) {
+ str += "L";
+ }
+ str += x + ',' + y;
+ fst = false;
+ }
+ }
+ if (me.closed) {
+ str += "Z";
+ }
+ return str;
+ };
- Path.fromSVG = function(path) {
- /*
- loads a path from a SVG path string
- */
-
- var closed, cmd, contour, contours, cx, cy, path_data, path_str, r, res, sep, type, _i, _len;
- contours = [];
- type = path.nodeName;
- res = null;
- if (type === "path") {
- path_str = path.getAttribute('d').trim();
- path_data = Raphael.parsePathString(path_str);
- closed = path_data[path_data.length - 1] === "Z";
- sep = closed ? "Z M" : "M";
- contour = [];
- for (_i = 0, _len = path_data.length; _i < _len; _i++) {
- cmd = path_data[_i];
- if (cmd.length === 0) {
- continue;
+ Path.prototype.area = function () {
+ var area, cnt, i, me, _i, _j, _len, _ref5, _ref6;
+ me = this;
+ if (me.areas != null) {
+ return me._area;
+ }
+ me.areas = [];
+ me._area = 0;
+ _ref5 = me.contours;
+ for (_i = 0, _len = _ref5.length; _i < _len; _i++) {
+ cnt = _ref5[_i];
+ area = 0;
+ for (i = _j = 0, _ref6 = cnt.length - 2; 0 <= _ref6 ? _j <= _ref6 : _j >= _ref6; i = 0 <= _ref6 ? ++_j : --_j) {
+ area += cnt[i][0] * cnt[i + 1][1] - cnt[i + 1][0] * cnt[i][1];
+ }
+ area *= .5;
+ area = area;
+ me.areas.push(area);
+ me._area += area;
+ }
+ return me._area;
+ };
+
+ Path.prototype.centroid = function () {
+ var S, a, area, cnt, cnt_orig, cx, cy, diff, dx, dy, i, j, k, l, len, me, p0, p1, s, sp, total_len, w, x, x_, y, y_, _i, _j, _k, _l, _lengths, _m, _ref5, _ref6, _ref7, _ref8, _ref9;
+ me = this;
+ if (me._centroid != null) {
+ return me._centroid;
+ }
+ area = me.area();
+ cx = cy = 0;
+ for (i = _i = 0, _ref5 = me.contours.length - 1; 0 <= _ref5 ? _i <= _ref5 : _i >= _ref5; i = 0 <= _ref5 ? ++_i : --_i) {
+ cnt_orig = me.contours[i];
+ cnt = [];
+ l = cnt_orig.length;
+ for (j = _j = 0, _ref6 = l - 1; 0 <= _ref6 ? _j <= _ref6 : _j >= _ref6; j = 0 <= _ref6 ? ++_j : --_j) {
+ p0 = cnt_orig[j];
+ p1 = cnt_orig[(j + 1) % l];
+ diff = 0;
+ cnt.push(p0);
+ if (p0[0] === p1[0]) {
+ diff = Math.abs(p0[1] - p1[1]);
+ }
+ if (p0[1] === p1[1]) {
+ diff = Math.abs(p0[0] - p1[0]);
+ }
+ if (diff > 10) {
+ S = Math.floor(diff * 2);
+ for (s = _k = 1, _ref7 = S - 1; 1 <= _ref7 ? _k <= _ref7 : _k >= _ref7; s = 1 <= _ref7 ? ++_k : --_k) {
+ sp = [p0[0] + s / S * (p1[0] - p0[0]), p0[1] + s / S * (p1[1] - p0[1])];
+ cnt.push(sp);
+ }
+ }
+ }
+ a = me.areas[i];
+ x = y = x_ = y_ = 0;
+ l = cnt.length;
+ _lengths = [];
+ total_len = 0;
+ for (j = _l = 0, _ref8 = l - 1; 0 <= _ref8 ? _l <= _ref8 : _l >= _ref8; j = 0 <= _ref8 ? ++_l : --_l) {
+ p0 = cnt[j];
+ p1 = cnt[(j + 1) % l];
+ dx = p1[0] - p0[0];
+ dy = p1[1] - p0[1];
+ len = Math.sqrt(dx * dx + dy * dy);
+ _lengths.push(len);
+ total_len += len;
+ }
+ for (j = _m = 0, _ref9 = l - 1; 0 <= _ref9 ? _m <= _ref9 : _m >= _ref9; j = 0 <= _ref9 ? ++_m : --_m) {
+ p0 = cnt[j];
+ w = _lengths[j] / total_len;
+ x += w * p0[0];
+ y += w * p0[1];
+ }
+ k = a / area;
+ cx += x * k;
+ cy += y * k;
+ }
+ me._centroid = [cx, cy];
+ return me._centroid;
+ };
+
+ Path.prototype.isInside = function (x, y) {
+ var bbox, cnt, i, me, _i, _ref5;
+ me = this;
+ bbox = me._bbox;
+ if (x < bbox[0] || x > bbox[2] || y < bbox[1] || y > bbox[3]) {
+ return false;
+ }
+ for (i = _i = 0, _ref5 = me.contours.length - 1; 0 <= _ref5 ? _i <= _ref5 : _i >= _ref5; i = 0 <= _ref5 ? ++_i : --_i) {
+ cnt = me.contours[i];
+ if (__point_in_polygon(cnt, [x, y])) {
+ return true;
+ }
+ }
+ return false;
+ };
+
+ return Path;
+
+ })();
+
+ kartograph.geom.Path = Path;
+
+ Circle = (function (_super) {
+
+ __extends(Circle, _super);
+
+ function Circle(x, y, r) {
+ this.x = x;
+ this.y = y;
+ this.r = r;
+ Circle.__super__.constructor.call(this, 'circle', null, true);
}
- if (cmd[0] === "M") {
- if (contour.length > 2) {
- contours.push(contour);
- contour = [];
- }
- contour.push([cmd[1], cmd[2]]);
- } else if (cmd[0] === "L") {
- contour.push([cmd[1], cmd[2]]);
- } else if (cmd[0] === "Z") {
- if (contour.length > 2) {
- contours.push(contour);
+
+ Circle.prototype.toSVG = function (paper) {
+ var me;
+ me = this;
+ return paper.circle(me.x, me.y, me.r);
+ };
+
+ Circle.prototype.centroid = function () {
+ var me;
+ me = this;
+ return [me.x, me.y];
+ };
+
+ Circle.prototype.area = function () {
+ var me;
+ me = this;
+ return Math.PI * me.r * m.r;
+ };
+
+ return Circle;
+
+ })(Path);
+
+ kartograph.geom.Circle = Circle;
+
+ Path.fromSVG = function (path) {
+ /*
+ loads a path from a SVG path string
+ */
+
+ var closed, cmd, contour, contours, cx, cy, path_data, path_str, r, res, sep, type, _i, _len;
+ contours = [];
+ type = path.nodeName;
+ res = null;
+ if (type === "path") {
+ path_str = path.getAttribute('d').trim();
+ path_data = Raphael.parsePathString(path_str);
+ closed = path_data[path_data.length - 1] === "Z";
+ sep = closed ? "Z M" : "M";
contour = [];
- }
+ for (_i = 0, _len = path_data.length; _i < _len; _i++) {
+ cmd = path_data[_i];
+ if (cmd.length === 0) {
+ continue;
+ }
+ if (cmd[0] === "M") {
+ if (contour.length > 2) {
+ contours.push(contour);
+ contour = [];
+ }
+ contour.push([cmd[1], cmd[2]]);
+ } else if (cmd[0] === "L") {
+ contour.push([cmd[1], cmd[2]]);
+ } else if (cmd[0] === "Z") {
+ if (contour.length > 2) {
+ contours.push(contour);
+ contour = [];
+ }
+ }
+ }
+ if (contour.length > 2) {
+ contours.push(contour);
+ contour = [];
+ }
+ res = new kartograph.geom.Path(type, contours, closed);
+ } else if (type === "circle") {
+ cx = path.getAttribute("cx");
+ cy = path.getAttribute("cy");
+ r = path.getAttribute("r");
+ res = new kartograph.geom.Circle(cx, cy, r);
}
- }
- if (contour.length > 2) {
- contours.push(contour);
- contour = [];
- }
- res = new kartograph.geom.Path(type, contours, closed);
- } else if (type === "circle") {
- cx = path.getAttribute("cx");
- cy = path.getAttribute("cy");
- r = path.getAttribute("r");
- res = new kartograph.geom.Circle(cx, cy, r);
- }
- return res;
- };
+ return res;
+ };
- Line = (function() {
- /*
- represents simple lines
- */
+ Line = (function () {
+ /*
+ represents simple lines
+ */
- function Line(points) {
- this.points = points;
- }
+ function Line(points) {
+ this.points = points;
+ }
- Line.prototype.clipToBBox = function(bbox) {
- var clip, i, last_in, lines, p0x, p0y, p1x, p1y, pts, self, x0, x1, y0, y1, _i, _ref5, _ref6, _ref7, _ref8;
- self = this;
- clip = new kartograph.geom.clipping.CohenSutherland().clip;
- pts = [];
- lines = [];
- last_in = false;
- for (i = _i = 0, _ref5 = self.points.length - 2; 0 <= _ref5 ? _i <= _ref5 : _i >= _ref5; i = 0 <= _ref5 ? ++_i : --_i) {
- _ref6 = self.points[i], p0x = _ref6[0], p0y = _ref6[1];
- _ref7 = self.points[i + 1], p1x = _ref7[0], p1y = _ref7[1];
- try {
- _ref8 = clip(bbox, p0x, p0y, p1x, p1y), x0 = _ref8[0], y0 = _ref8[1], x1 = _ref8[2], y1 = _ref8[3];
- last_in = true;
- pts.push([x0, y0]);
- if (p1x !== x1 || p1y !== y0 || i === len(self.points) - 2) {
- pts.push([x1, y1]);
- }
- } catch (err) {
- if (last_in && pts.length > 1) {
- lines.push(new Line(pts));
+ Line.prototype.clipToBBox = function (bbox) {
+ var clip, i, last_in, lines, p0x, p0y, p1x, p1y, pts, self, x0, x1, y0, y1, _i, _ref5, _ref6, _ref7, _ref8;
+ self = this;
+ clip = new kartograph.geom.clipping.CohenSutherland().clip;
+ pts = [];
+ lines = [];
+ last_in = false;
+ for (i = _i = 0, _ref5 = self.points.length - 2; 0 <= _ref5 ? _i <= _ref5 : _i >= _ref5; i = 0 <= _ref5 ? ++_i : --_i) {
+ _ref6 = self.points[i], p0x = _ref6[0], p0y = _ref6[1];
+ _ref7 = self.points[i + 1], p1x = _ref7[0], p1y = _ref7[1];
+ try {
+ _ref8 = clip(bbox, p0x, p0y, p1x, p1y), x0 = _ref8[0], y0 = _ref8[1], x1 = _ref8[2], y1 = _ref8[3];
+ last_in = true;
+ pts.push([x0, y0]);
+ if (p1x !== x1 || p1y !== y0 || i === len(self.points) - 2) {
+ pts.push([x1, y1]);
+ }
+ } catch (err) {
+ if (last_in && pts.length > 1) {
+ lines.push(new Line(pts));
+ pts = [];
+ }
+ last_in = false;
+ }
+ }
+ if (pts.length > 1) {
+ lines.push(new Line(pts));
+ }
+ return lines;
+ };
+
+ Line.prototype.toSVG = function () {
+ var pts, self, x, y, _i, _len, _ref5, _ref6;
+ self = this;
pts = [];
- }
- last_in = false;
+ _ref5 = self.points;
+ for (_i = 0, _len = _ref5.length; _i < _len; _i++) {
+ _ref6 = _ref5[_i], x = _ref6[0], y = _ref6[1];
+ pts.push(x + ',' + y);
+ }
+ return 'M' + pts.join('L');
+ };
+
+ return Line;
+
+ })();
+
+ kartograph.geom.Line = Line;
+
+ __point_in_polygon = function (polygon, p) {
+ var angle, atan2, dtheta, i, n, pi, theta1, theta2, twopi, x1, x2, y1, y2, _i, _ref5;
+ pi = Math.PI;
+ atan2 = Math.atan2;
+ twopi = pi * 2;
+ n = polygon.length;
+ angle = 0;
+ for (i = _i = 0, _ref5 = n - 1; 0 <= _ref5 ? _i <= _ref5 : _i >= _ref5; i = 0 <= _ref5 ? ++_i : --_i) {
+ x1 = polygon[i][0] - p[0];
+ y1 = polygon[i][1] - p[1];
+ x2 = polygon[(i + 1) % n][0] - p[0];
+ y2 = polygon[(i + 1) % n][1] - p[1];
+ theta1 = atan2(y1, x1);
+ theta2 = atan2(y2, x2);
+ dtheta = theta2 - theta1;
+ while (dtheta > pi) {
+ dtheta -= twopi;
+ }
+ while (dtheta < -pi) {
+ dtheta += twopi;
+ }
+ angle += dtheta;
}
- }
- if (pts.length > 1) {
- lines.push(new Line(pts));
- }
- return lines;
+ return Math.abs(angle) >= pi;
};
- Line.prototype.toSVG = function() {
- var pts, self, x, y, _i, _len, _ref5, _ref6;
- self = this;
- pts = [];
- _ref5 = self.points;
- for (_i = 0, _len = _ref5.length; _i < _len; _i++) {
- _ref6 = _ref5[_i], x = _ref6[0], y = _ref6[1];
- pts.push(x + ',' + y);
- }
- return 'M' + pts.join('L');
- };
+ /*
+ kartograph - a svg mapping library
+ Copyright (C) 2011 Gregor Aisch
- return Line;
-
- })();
-
- kartograph.geom.Line = Line;
-
- __point_in_polygon = function(polygon, p) {
- var angle, atan2, dtheta, i, n, pi, theta1, theta2, twopi, x1, x2, y1, y2, _i, _ref5;
- pi = Math.PI;
- atan2 = Math.atan2;
- twopi = pi * 2;
- n = polygon.length;
- angle = 0;
- for (i = _i = 0, _ref5 = n - 1; 0 <= _ref5 ? _i <= _ref5 : _i >= _ref5; i = 0 <= _ref5 ? ++_i : --_i) {
- x1 = polygon[i][0] - p[0];
- y1 = polygon[i][1] - p[1];
- x2 = polygon[(i + 1) % n][0] - p[0];
- y2 = polygon[(i + 1) % n][1] - p[1];
- theta1 = atan2(y1, x1);
- theta2 = atan2(y2, x2);
- dtheta = theta2 - theta1;
- while (dtheta > pi) {
- dtheta -= twopi;
- }
- while (dtheta < -pi) {
- dtheta += twopi;
- }
- angle += dtheta;
- }
- return Math.abs(angle) >= pi;
- };
-
- /*
- kartograph - a svg mapping library
- Copyright (C) 2011 Gregor Aisch
-
- This library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Lesser General Public
- License as published by the Free Software Foundation; either
- version 2.1 of the License, or (at your option) any later version.
-
- This library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public
- License along with this library. If not, see <http://www.gnu.org/licenses/>.
- */
-
-
- __proj = kartograph.proj = {};
-
- Function.prototype.bind = function(scope) {
- var _function;
- _function = this;
- return function() {
- return _function.apply(scope, arguments);
- };
- };
-
- Proj = (function() {
-
- Proj.parameters = [];
-
- Proj.title = "Projection";
-
- function Proj(opts) {
- var me, _ref5, _ref6;
- me = this;
- me.lon0 = (_ref5 = opts.lon0) != null ? _ref5 : 0;
- me.lat0 = (_ref6 = opts.lat0) != null ? _ref6 : 0;
- me.PI = Math.PI;
- me.HALFPI = me.PI * .5;
- me.QUARTERPI = me.PI * .25;
- me.RAD = me.PI / 180;
- me.DEG = 180 / me.PI;
- me.lam0 = me.rad(this.lon0);
- me.phi0 = me.rad(this.lat0);
- me.minLat = -90;
- me.maxLat = 90;
- }
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
- Proj.prototype.rad = function(a) {
- return a * this.RAD;
- };
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
- Proj.prototype.deg = function(a) {
- return a * this.DEG;
- };
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
- Proj.prototype.plot = function(polygon, truncate) {
- var ignore, lat, lon, points, vis, x, y, _i, _len, _ref5, _ref6;
- if (truncate == null) {
- truncate = true;
- }
- points = [];
- ignore = true;
- for (_i = 0, _len = polygon.length; _i < _len; _i++) {
- _ref5 = polygon[_i], lon = _ref5[0], lat = _ref5[1];
- vis = this._visible(lon, lat);
- if (vis) {
- ignore = false;
- }
- _ref6 = this.project(lon, lat), x = _ref6[0], y = _ref6[1];
- if (!vis && truncate) {
- points.push(this._truncate(x, y));
- } else {
- points.push([x, y]);
- }
- }
- if (ignore) {
- return null;
- } else {
- return [points];
- }
- };
- Proj.prototype.sea = function() {
- var l0, lat, lon, o, p, s, _i, _j, _k, _l, _ref5, _ref6, _ref7, _ref8;
- s = this;
- p = s.project.bind(this);
- o = [];
- l0 = s.lon0;
- s.lon0 = 0;
- for (lon = _i = -180; _i <= 180; lon = ++_i) {
- o.push(p(lon, s.maxLat));
- }
- for (lat = _j = _ref5 = s.maxLat, _ref6 = s.minLat; _ref5 <= _ref6 ? _j <= _ref6 : _j >= _ref6; lat = _ref5 <= _ref6 ? ++_j : --_j) {
- o.push(p(180, lat));
- }
- for (lon = _k = 180; _k >= -180; lon = --_k) {
- o.push(p(lon, s.minLat));
- }
- for (lat = _l = _ref7 = s.minLat, _ref8 = s.maxLat; _ref7 <= _ref8 ? _l <= _ref8 : _l >= _ref8; lat = _ref7 <= _ref8 ? ++_l : --_l) {
- o.push(p(-180, lat));
- }
- s.lon0 = l0;
- return o;
- };
+ __proj = kartograph.proj = {};
- Proj.prototype.world_bbox = function() {
- var bbox, p, s, sea, _i, _len;
- p = this.project.bind(this);
- sea = this.sea();
- bbox = new kartograph.BBox();
- for (_i = 0, _len = sea.length; _i < _len; _i++) {
- s = sea[_i];
- bbox.update(s[0], s[1]);
- }
- return bbox;
+ Function.prototype.bind = function (scope) {
+ var _function;
+ _function = this;
+ return function () {
+ return _function.apply(scope, arguments);
+ };
};
- Proj.prototype.toString = function() {
- var me;
- me = this;
- return '[Proj: ' + me.name + ']';
- };
+ Proj = (function () {
- return Proj;
+ Proj.parameters = [];
- })();
+ Proj.title = "Projection";
- Proj.fromXML = function(xml) {
- /*
- reconstructs a projection from xml description
- */
-
- var attr, i, id, opts, proj, _i, _ref5;
- id = xml.getAttribute('id');
- opts = {};
- for (i = _i = 0, _ref5 = xml.attributes.length - 1; 0 <= _ref5 ? _i <= _ref5 : _i >= _ref5; i = 0 <= _ref5 ? ++_i : --_i) {
- attr = xml.attributes[i];
- if (attr.name !== "id") {
- opts[attr.name] = attr.value;
- }
- }
- proj = new kartograph.proj[id](opts);
- proj.name = id;
- return proj;
- };
+ function Proj(opts) {
+ var me, _ref5, _ref6;
+ me = this;
+ me.lon0 = (_ref5 = opts.lon0) != null ? _ref5 : 0;
+ me.lat0 = (_ref6 = opts.lat0) != null ? _ref6 : 0;
+ me.PI = Math.PI;
+ me.HALFPI = me.PI * .5;
+ me.QUARTERPI = me.PI * .25;
+ me.RAD = me.PI / 180;
+ me.DEG = 180 / me.PI;
+ me.lam0 = me.rad(this.lon0);
+ me.phi0 = me.rad(this.lat0);
+ me.minLat = -90;
+ me.maxLat = 90;
+ }
- kartograph.Proj = Proj;
+ Proj.prototype.rad = function (a) {
+ return a * this.RAD;
+ };
- Cylindrical = (function(_super) {
+ Proj.prototype.deg = function (a) {
+ return a * this.DEG;
+ };
- __extends(Cylindrical, _super);
+ Proj.prototype.plot = function (polygon, truncate) {
+ var ignore, lat, lon, points, vis, x, y, _i, _len, _ref5, _ref6;
+ if (truncate == null) {
+ truncate = true;
+ }
+ points = [];
+ ignore = true;
+ for (_i = 0, _len = polygon.length; _i < _len; _i++) {
+ _ref5 = polygon[_i], lon = _ref5[0], lat = _ref5[1];
+ vis = this._visible(lon, lat);
+ if (vis) {
+ ignore = false;
+ }
+ _ref6 = this.project(lon, lat), x = _ref6[0], y = _ref6[1];
+ if (!vis && truncate) {
+ points.push(this._truncate(x, y));
+ } else {
+ points.push([x, y]);
+ }
+ }
+ if (ignore) {
+ return null;
+ } else {
+ return [points];
+ }
+ };
- /*
- Base class for cylindrical projections
- */
+ Proj.prototype.sea = function () {
+ var l0, lat, lon, o, p, s, _i, _j, _k, _l, _ref5, _ref6, _ref7, _ref8;
+ s = this;
+ p = s.project.bind(this);
+ o = [];
+ l0 = s.lon0;
+ s.lon0 = 0;
+ for (lon = _i = -180; _i <= 180; lon = ++_i) {
+ o.push(p(lon, s.maxLat));
+ }
+ for (lat = _j = _ref5 = s.maxLat, _ref6 = s.minLat; _ref5 <= _ref6 ? _j <= _ref6 : _j >= _ref6; lat = _ref5 <= _ref6 ? ++_j : --_j) {
+ o.push(p(180, lat));
+ }
+ for (lon = _k = 180; _k >= -180; lon = --_k) {
+ o.push(p(lon, s.minLat));
+ }
+ for (lat = _l = _ref7 = s.minLat, _ref8 = s.maxLat; _ref7 <= _ref8 ? _l <= _ref8 : _l >= _ref8; lat = _ref7 <= _ref8 ? ++_l : --_l) {
+ o.push(p(-180, lat));
+ }
+ s.lon0 = l0;
+ return o;
+ };
+ Proj.prototype.world_bbox = function () {
+ var bbox, p, s, sea, _i, _len;
+ p = this.project.bind(this);
+ sea = this.sea();
+ bbox = new kartograph.BBox();
+ for (_i = 0, _len = sea.length; _i < _len; _i++) {
+ s = sea[_i];
+ bbox.update(s[0], s[1]);
+ }
+ return bbox;
+ };
- Cylindrical.parameters = ['lon0', 'flip'];
+ Proj.prototype.toString = function () {
+ var me;
+ me = this;
+ return '[Proj: ' + me.name + ']';
+ };
- Cylindrical.title = "Cylindrical Projection";
+ return Proj;
- function Cylindrical(opts) {
- var me, _ref5, _ref6;
- if (opts == null) {
- opts = {};
- }
- me = this;
- me.flip = Number((_ref5 = opts.flip) != null ? _ref5 : 0);
- if (me.flip === 1) {
- opts.lon0 = (_ref6 = -opts.lon0) != null ? _ref6 : 0;
- }
- Cylindrical.__super__.constructor.call(this, opts);
- }
+ })();
- Cylindrical.prototype._visible = function(lon, lat) {
- return true;
- };
+ Proj.fromXML = function (xml) {
+ /*
+ reconstructs a projection from xml description
+ */
- Cylindrical.prototype.clon = function(lon) {
- lon -= this.lon0;
- if (lon < -180) {
- lon += 360;
- } else if (lon > 180) {
- lon -= 360;
- }
- return lon;
+ var attr, i, id, opts, proj, _i, _ref5;
+ id = xml.getAttribute('id');
+ opts = {};
+ for (i = _i = 0, _ref5 = xml.attributes.length - 1; 0 <= _ref5 ? _i <= _ref5 : _i >= _ref5; i = 0 <= _ref5 ? ++_i : --_i) {
+ attr = xml.attributes[i];
+ if (attr.name !== "id") {
+ opts[attr.name] = attr.value;
+ }
+ }
+ proj = new kartograph.proj[id](opts);
+ proj.name = id;
+ return proj;
};
- Cylindrical.prototype.ll = function(lon, lat) {
- if (this.flip === 1) {
- return [-lon, -lat];
- } else {
- return [lon, lat];
- }
- };
+ kartograph.Proj = Proj;
- return Cylindrical;
+ Cylindrical = (function (_super) {
- })(Proj);
+ __extends(Cylindrical, _super);
- Equirectangular = (function(_super) {
+ /*
+ Base class for cylindrical projections
+ */
- __extends(Equirectangular, _super);
- /*
- Equirectangular Projection aka Lonlat aka Plate Carree
- */
+ Cylindrical.parameters = ['lon0', 'flip'];
+ Cylindrical.title = "Cylindrical Projection";
- function Equirectangular() {
- return Equirectangular.__super__.constructor.apply(this, arguments);
- }
+ function Cylindrical(opts) {
+ var me, _ref5, _ref6;
+ if (opts == null) {
+ opts = {};
+ }
+ me = this;
+ me.flip = Number((_ref5 = opts.flip) != null ? _ref5 : 0);
+ if (me.flip === 1) {
+ opts.lon0 = (_ref6 = -opts.lon0) != null ? _ref6 : 0;
+ }
+ Cylindrical.__super__.constructor.call(this, opts);
+ }
- Equirectangular.title = "Equirectangular Projection";
+ Cylindrical.prototype._visible = function (lon, lat) {
+ return true;
+ };
- Equirectangular.prototype.project = function(lon, lat) {
- var _ref5;
- _ref5 = this.ll(lon, lat), lon = _ref5[0], lat = _ref5[1];
- lon = this.clon(lon);
- return [lon * Math.cos(this.phi0) * 1000, lat * -1 * 1000];
- };
+ Cylindrical.prototype.clon = function (lon) {
+ lon -= this.lon0;
+ if (lon < -180) {
+ lon += 360;
+ } else if (lon > 180) {
+ lon -= 360;
+ }
+ return lon;
+ };
- return Equirectangular;
+ Cylindrical.prototype.ll = function (lon, lat) {
+ if (this.flip === 1) {
+ return [-lon, -lat];
+ } else {
+ return [lon, lat];
+ }
+ };
- })(Cylindrical);
+ return Cylindrical;
- __proj['lonlat'] = Equirectangular;
+ })(Proj);
- CEA = (function(_super) {
+ Equirectangular = (function (_super) {
- __extends(CEA, _super);
+ __extends(Equirectangular, _super);
- CEA.parameters = ['lon0', 'lat1', 'flip'];
+ /*
+ Equirectangular Projection aka Lonlat aka Plate Carree
+ */
- CEA.title = "Cylindrical Equal Area";
- function CEA(opts) {
- var _ref5;
- CEA.__super__.constructor.call(this, opts);
- this.lat1 = (_ref5 = opts.lat1) != null ? _ref5 : 0;
- this.phi1 = this.rad(this.lat1);
- }
+ function Equirectangular() {
+ return Equirectangular.__super__.constructor.apply(this, arguments);
+ }
- /*
- Cylindrical Equal Area Projection
- */
-
-
- CEA.prototype.project = function(lon, lat) {
- var lam, phi, x, y, _ref5;
- _ref5 = this.ll(lon, lat), lon = _ref5[0], lat = _ref5[1];
- lam = this.rad(this.clon(lon));
- phi = this.rad(lat * -1);
- x = lam * Math.cos(this.phi1);
- y = Math.sin(phi) / Math.cos(this.phi1);
- return [x * 1000, y * 1000];
- };
+ Equirectangular.title = "Equirectangular Projection";
- return CEA;
+ Equirectangular.prototype.project = function (lon, lat) {
+ var _ref5;
+ _ref5 = this.ll(lon, lat), lon = _ref5[0], lat = _ref5[1];
+ lon = this.clon(lon);
+ return [lon * Math.cos(this.phi0) * 1000, lat * -1 * 1000];
+ };
- })(Cylindrical);
+ return Equirectangular;
- __proj['cea'] = CEA;
+ })(Cylindrical);
- GallPeters = (function(_super) {
+ __proj['lonlat'] = Equirectangular;
- __extends(GallPeters, _super);
+ CEA = (function (_super) {
- /*
- Gall-Peters Projection
- */
+ __extends(CEA, _super);
+ CEA.parameters = ['lon0', 'lat1', 'flip'];
- GallPeters.title = "Gall-Peters Projection";
+ CEA.title = "Cylindrical Equal Area";
- GallPeters.parameters = ['lon0', 'flip'];
+ function CEA(opts) {
+ var _ref5;
+ CEA.__super__.constructor.call(this, opts);
+ this.lat1 = (_ref5 = opts.lat1) != null ? _ref5 : 0;
+ this.phi1 = this.rad(this.lat1);
+ }
- function GallPeters(opts) {
- opts.lat1 = 45;
- GallPeters.__super__.constructor.call(this, opts);
- }
+ /*
+ Cylindrical Equal Area Projection
+ */
- return GallPeters;
- })(CEA);
+ CEA.prototype.project = function (lon, lat) {
+ var lam, phi, x, y, _ref5;
+ _ref5 = this.ll(lon, lat), lon = _ref5[0], lat = _ref5[1];
+ lam = this.rad(this.clon(lon));
+ phi = this.rad(lat * -1);
+ x = lam * Math.cos(this.phi1);
+ y = Math.sin(phi) / Math.cos(this.phi1);
+ return [x * 1000, y * 1000];
+ };
- __proj['gallpeters'] = GallPeters;
+ return CEA;
- HoboDyer = (function(_super) {
+ })(Cylindrical);
- __extends(HoboDyer, _super);
+ __proj['cea'] = CEA;
- /*
- Hobo-Dyer Projection
- */
+ GallPeters = (function (_super) {
+ __extends(GallPeters, _super);
- HoboDyer.title = "Hobo-Dyer Projection";
+ /*
+ Gall-Peters Projection
+ */
- HoboDyer.parameters = ['lon0', 'flip'];
- function HoboDyer(opts) {
- opts.lat1 = 37.7;
- HoboDyer.__super__.constructor.call(this, opts);
- }
+ GallPeters.title = "Gall-Peters Projection";
- return HoboDyer;
+ GallPeters.parameters = ['lon0', 'flip'];
- })(CEA);
+ function GallPeters(opts) {
+ opts.lat1 = 45;
+ GallPeters.__super__.constructor.call(this, opts);
+ }
- __proj['hobodyer'] = HoboDyer;
+ return GallPeters;
- Behrmann = (function(_super) {
+ })(CEA);
- __extends(Behrmann, _super);
+ __proj['gallpeters'] = GallPeters;
- /*
- Behrmann Projection
- */
+ HoboDyer = (function (_super) {
+ __extends(HoboDyer, _super);
- Behrmann.title = "Behrmann Projection";
+ /*
+ Hobo-Dyer Projection
+ */
- Behrmann.parameters = ['lon0', 'flip'];
- function Behrmann(opts) {
- opts.lat1 = 30;
- Behrmann.__super__.constructor.call(this, opts);
- }
+ HoboDyer.title = "Hobo-Dyer Projection";
- return Behrmann;
+ HoboDyer.parameters = ['lon0', 'flip'];
- })(CEA);
+ function HoboDyer(opts) {
+ opts.lat1 = 37.7;
+ HoboDyer.__super__.constructor.call(this, opts);
+ }
- __proj['behrmann'] = Behrmann;
+ return HoboDyer;
- Balthasart = (function(_super) {
+ })(CEA);
- __extends(Balthasart, _super);
+ __proj['hobodyer'] = HoboDyer;
- /*
- Balthasart Projection
- */
+ Behrmann = (function (_super) {
+ __extends(Behrmann, _super);
- Balthasart.title = "Balthasart Projection";
+ /*
+ Behrmann Projection
+ */
- Balthasart.parameters = ['lon0', 'flip'];
- function Balthasart(opts) {
- opts.lat1 = 50;
- Balthasart.__super__.constructor.call(this, opts);
- }
+ Behrmann.title = "Behrmann Projection";
- return Balthasart;
+ Behrmann.parameters = ['lon0', 'flip'];
- })(CEA);
+ function Behrmann(opts) {
+ opts.lat1 = 30;
+ Behrmann.__super__.constructor.call(this, opts);
+ }
- __proj['balthasart'] = Balthasart;
+ return Behrmann;
- Mercator = (function(_super) {
+ })(CEA);
- __extends(Mercator, _super);
+ __proj['behrmann'] = Behrmann;
- /*
- # you're not really into maps..
- */
+ Balthasart = (function (_super) {
+ __extends(Balthasart, _super);
- Mercator.title = "Mercator Projection";
+ /*
+ Balthasart Projection
+ */
- function Mercator(opts) {
- Mercator.__super__.constructor.call(this, opts);
- this.minLat = -85;
- this.maxLat = 85;
- }
- Mercator.prototype.project = function(lon, lat) {
- var lam, math, phi, s, x, y, _ref5;
- s = this;
- _ref5 = s.ll(lon, lat), lon = _ref5[0], lat = _ref5[1];
- math = Math;
- lam = s.rad(s.clon(lon));
- phi = s.rad(lat * -1);
- x = lam * 1000;
- y = math.log((1 + math.sin(phi)) / math.cos(phi)) * 1000;
- return [x, y];
- };
+ Balthasart.title = "Balthasart Projection";
- return Mercator;
+ Balthasart.parameters = ['lon0', 'flip'];
- })(Cylindrical);
+ function Balthasart(opts) {
+ opts.lat1 = 50;
+ Balthasart.__super__.constructor.call(this, opts);
+ }
- __proj['mercator'] = Mercator;
+ return Balthasart;
- PseudoCylindrical = (function(_super) {
+ })(CEA);
- __extends(PseudoCylindrical, _super);
+ __proj['balthasart'] = Balthasart;
- /*
- Base class for pseudo cylindrical projections
- */
+ Mercator = (function (_super) {
+ __extends(Mercator, _super);
- function PseudoCylindrical() {
- return PseudoCylindrical.__super__.constructor.apply(this, arguments);
- }
+ /*
+ # you're not really into maps..
+ */
- PseudoCylindrical.title = "Pseudo-Cylindrical Projection";
- return PseudoCylindrical;
+ Mercator.title = "Mercator Projection";
- })(Cylindrical);
+ function Mercator(opts) {
+ Mercator.__super__.constructor.call(this, opts);
+ this.minLat = -85;
+ this.maxLat = 85;
+ }
- NaturalEarth = (function(_super) {
+ Mercator.prototype.project = function (lon, lat) {
+ var lam, math, phi, s, x, y, _ref5;
+ s = this;
+ _ref5 = s.ll(lon, lat), lon = _ref5[0], lat = _ref5[1];
+ math = Math;
+ lam = s.rad(s.clon(lon));
+ phi = s.rad(lat * -1);
+ x = lam * 1000;
+ y = math.log((1 + math.sin(phi)) / math.cos(phi)) * 1000;
+ return [x, y];
+ };
- __extends(NaturalEarth, _super);
+ return Mercator;
- /*
- Natural Earth Projection
- see here http://www.shadedrelief.com/NE_proj/
- */
-
-
- NaturalEarth.title = "Natural Earth Projection";
-
- function NaturalEarth(opts) {
- var s;
- NaturalEarth.__super__.constructor.call(this, opts);
- s = this;
- s.A0 = 0.8707;
- s.A1 = -0.131979;
- s.A2 = -0.013791;
- s.A3 = 0.003971;
- s.A4 = -0.001529;
- s.B0 = 1.007226;
- s.B1 = 0.015085;
- s.B2 = -0.044475;
- s.B3 = 0.028874;
- s.B4 = -0.005916;
- s.C0 = s.B0;
- s.C1 = 3 * s.B1;
- s.C2 = 7 * s.B2;
- s.C3 = 9 * s.B3;
- s.C4 = 11 * s.B4;
- s.EPS = 1e-11;
- s.MAX_Y = 0.8707 * 0.52 * Math.PI;
- return;
- }
+ })(Cylindrical);
- NaturalEarth.prototype.project = function(lon, lat) {
- var lplam, lpphi, phi2, phi4, s, x, y, _ref5;
- s = this;
- _ref5 = s.ll(lon, lat), lon = _ref5[0], lat = _ref5[1];
- lplam = s.rad(s.clon(lon));
- lpphi = s.rad(lat * -1);
- phi2 = lpphi * lpphi;
- phi4 = phi2 * phi2;
- x = lplam * (s.A0 + phi2 * (s.A1 + phi2 * (s.A2 + phi4 * phi2 * (s.A3 + phi2 * s.A4)))) * 180 + 500;
- y = lpphi * (s.B0 + phi2 * (s.B1 + phi4 * (s.B2 + s.B3 * phi2 + s.B4 * phi4))) * 180 + 270;
- return [x, y];
- };
+ __proj['mercator'] = Mercator;
- return NaturalEarth;
+ PseudoCylindrical = (function (_super) {
- })(PseudoCylindrical);
+ __extends(PseudoCylindrical, _super);
- __proj['naturalearth'] = NaturalEarth;
+ /*
+ Base class for pseudo cylindrical projections
+ */
- Robinson = (function(_super) {
- __extends(Robinson, _super);
+ function PseudoCylindrical() {
+ return PseudoCylindrical.__super__.constructor.apply(this, arguments);
+ }
- /*
- Robinson Projection
- */
-
-
- Robinson.title = "Robinson Projection";
-
- function Robinson(opts) {
- var s;
- Robinson.__super__.constructor.call(this, opts);
- s = this;
- s.X = [1, -5.67239e-12, -7.15511e-05, 3.11028e-06, 0.9986, -0.000482241, -2.4897e-05, -1.33094e-06, 0.9954, -0.000831031, -4.4861e-05, -9.86588e-07, 0.99, -0.00135363, -5.96598e-05, 3.67749e-06, 0.9822, -0.00167442, -4.4975e-06, -5.72394e-06, 0.973, -0.00214869, -9.03565e-05, 1.88767e-08, 0.96, -0.00305084, -9.00732e-05, 1.64869e-06, 0.9427, -0.00382792, -6.53428e-05, -2.61493e-06, 0.9216, -0.00467747, -0.000104566, 4.8122e-06, 0.8962, -0.00536222, -3.23834e-05, -5.43445e-06, 0.8679, -0.00609364, -0.0001139, 3.32521e-06, 0.835, -0.00698325, -6.40219e-05, 9.34582e-07, 0.7986, -0.00755337, -5.00038e-05, 9.35532e-07, 0.7597, -0.00798325, -3.59716e-05, -2.27604e-06, 0.7186, -0.00851366, -7.0112e-05, -8.63072e-06, 0.6732, -0.00986209, -0.000199572, 1.91978e-05, 0.6213, -0.010418, 8.83948e-05, 6.24031e-06, 0.5722, -0.00906601, 0.000181999, 6.24033e-06, 0.5322, 0, 0, 0];
- s.Y = [0, 0.0124, 3.72529e-10, 1.15484e-09, 0.062, 0.0124001, 1.76951e-08, -5.92321e-09, 0.124, 0.0123998, -7.09668e-08, 2.25753e-08, 0.186, 0.0124008, 2.66917e-07, -8.44523e-08, 0.248, 0.0123971, -9.99682e-07, 3.15569e-07, 0.31, 0.0124108, 3.73349e-06, -1.1779e-06, 0.372, 0.0123598, -1.3935e-05, 4.39588e-06, 0.434, 0.0125501, 5.20034e-05, -1.00051e-05, 0.4968, 0.0123198, -9.80735e-05, 9.22397e-06, 0.5571, 0.0120308, 4.02857e-05, -5.2901e-06, 0.6176, 0.0120369, -3.90662e-05, 7.36117e-07, 0.6769, 0.0117015, -2.80246e-05, -8.54283e-07, 0.7346, 0.0113572, -4.08389e-05, -5.18524e-07, 0.7903, 0.0109099, -4.86169e-05, -1.0718e-06, 0.8435, 0.0103433, -6.46934e-05, 5.36384e-09, 0.8936, 0.00969679, -6.46129e-05, -8.54894e-06, 0.9394, 0.00840949, -0.000192847, -4.21023e-06, 0.9761, 0.00616525, -0.000256001, -4.21021e-06, 1, 0, 0, 0];
- s.NODES = 18;
- s.FXC = 0.8487;
- s.FYC = 1.3523;
- s.C1 = 11.45915590261646417544;
- s.RC1 = 0.08726646259971647884;
- s.ONEEPS = 1.000001;
- s.EPS = 1e-8;
- return;
- }
+ PseudoCylindrical.title = "Pseudo-Cylindrical Projection";
+
+ return PseudoCylindrical;
+
+ })(Cylindrical);
+
+ NaturalEarth = (function (_super) {
+
+ __extends(NaturalEarth, _super);
+
+ /*
+ Natural Earth Projection
+ see here http://www.shadedrelief.com/NE_proj/
+ */
+
+
+ NaturalEarth.title = "Natural Earth Projection";
+
+ function NaturalEarth(opts) {
+ var s;
+ NaturalEarth.__super__.constructor.call(this, opts);
+ s = this;
+ s.A0 = 0.8707;
+ s.A1 = -0.131979;
+ s.A2 = -0.013791;
+ s.A3 = 0.003971;
+ s.A4 = -0.001529;
+ s.B0 = 1.007226;
+ s.B1 = 0.015085;
+ s.B2 = -0.044475;
+ s.B3 = 0.028874;
+ s.B4 = -0.005916;
+ s.C0 = s.B0;
+ s.C1 = 3 * s.B1;
+ s.C2 = 7 * s.B2;
+ s.C3 = 9 * s.B3;
+ s.C4 = 11 * s.B4;
+ s.EPS = 1e-11;
+ s.MAX_Y = 0.8707 * 0.52 * Math.PI;
+ return;
+ }
- Robinson.prototype._poly = function(arr, offs, z) {
- return arr[offs] + z * (arr[offs + 1] + z * (arr[offs + 2] + z * arr[offs + 3]));
- };
+ NaturalEarth.prototype.project = function (lon, lat) {
+ var lplam, lpphi, phi2, phi4, s, x, y, _ref5;
+ s = this;
+ _ref5 = s.ll(lon, lat), lon = _ref5[0], lat = _ref5[1];
+ lplam = s.rad(s.clon(lon));
+ lpphi = s.rad(lat * -1);
+ phi2 = lpphi * lpphi;
+ phi4 = phi2 * phi2;
+ x = lplam * (s.A0 + phi2 * (s.A1 + phi2 * (s.A2 + phi4 * phi2 * (s.A3 + phi2 * s.A4)))) * 180 + 500;
+ y = lpphi * (s.B0 + phi2 * (s.B1 + phi4 * (s.B2 + s.B3 * phi2 + s.B4 * phi4))) * 180 + 270;
+ return [x, y];
+ };
- Robinson.prototype.project = function(lon, lat) {
- var i, lplam, lpphi, phi, s, x, y, _ref5;
- s = this;
- _ref5 = s.ll(lon, lat), lon = _ref5[0], lat = _ref5[1];
- lon = s.clon(lon);
- lplam = s.rad(lon);
- lpphi = s.rad(lat * -1);
- phi = Math.abs(lpphi);
- i = Math.floor(phi * s.C1);
- if (i >= s.NODES) {
- i = s.NODES - 1;
- }
- phi = s.deg(phi - s.RC1 * i);
- i *= 4;
- x = 1000 * s._poly(s.X, i, phi) * s.FXC * lplam;
- y = 1000 * s._poly(s.Y, i, phi) * s.FYC;
- if (lpphi < 0.0) {
- y = -y;
- }
- return [x, y];
- };
+ return NaturalEarth;
- return Robinson;
+ })(PseudoCylindrical);
- })(PseudoCylindrical);
+ __proj['naturalearth'] = NaturalEarth;
- __proj['robinson'] = Robinson;
+ Robinson = (function (_super) {
- EckertIV = (function(_super) {
+ __extends(Robinson, _super);
- __extends(EckertIV, _super);
+ /*
+ Robinson Projection
+ */
- /*
- Eckert IV Projection
- */
-
-
- EckertIV.title = "Eckert IV Projection";
-
- function EckertIV(opts) {
- var me;
- EckertIV.__super__.constructor.call(this, opts);
- me = this;
- me.C_x = .42223820031577120149;
- me.C_y = 1.32650042817700232218;
- me.RC_y = .75386330736002178205;
- me.C_p = 3.57079632679489661922;
- me.RC_p = .28004957675577868795;
- me.EPS = 1e-7;
- me.NITER = 6;
- }
- EckertIV.prototype.project = function(lon, lat) {
- var V, c, i, lplam, lpphi, me, p, s, x, y, _ref5;
- me = this;
- _ref5 = me.ll(lon, lat), lon = _ref5[0], lat = _ref5[1];
- lplam = me.rad(me.clon(lon));
- lpphi = me.rad(lat * -1);
- p = me.C_p * Math.sin(lpphi);
- V = lpphi * lpphi;
- lpphi *= 0.895168 + V * (0.0218849 + V * 0.00826809);
- i = me.NITER;
- while (i > 0) {
- c = Math.cos(lpphi);
- s = Math.sin(lpphi);
- V = (lpphi + s * (c + 2) - p) / (1 + c * (c + 2) - s * s);
- lpphi -= V;
- if (Math.abs(V) < me.EPS) {
- break;
+ Robinson.title = "Robinson Projection";
+
+ function Robinson(opts) {
+ var s;
+ Robinson.__super__.constructor.call(this, opts);
+ s = this;
+ s.X = [1, -5.67239e-12, -7.15511e-05, 3.11028e-06, 0.9986, -0.000482241, -2.4897e-05, -1.33094e-06, 0.9954, -0.000831031, -4.4861e-05, -9.86588e-07, 0.99, -0.00135363, -5.96598e-05, 3.67749e-06, 0.9822, -0.00167442, -4.4975e-06, -5.72394e-06, 0.973, -0.00214869, -9.03565e-05, 1.88767e-08, 0.96, -0.00305084, -9.00732e-05, 1.64869e-06, 0.9427, -0.00382792, -6.53428e-05, -2.61493e-06, 0.9216, -0.00467747, -0.000104566, 4.8122e-06, 0.8962, -0.00536222, -3.23834e-05, -5.43445e-06, 0.8679, -0.00609364, -0.0001139, 3.32521e-06, 0.835, -0.00698325, -6.40219e-05, 9.34582e-07, 0.7986, -0.00755337, -5.00038e-05, 9.35532e-07, 0.7597, -0.00798325, -3.59716e-05, -2.27604e-06, 0.7186, -0.00851366, -7.0112e-05, -8.63072e-06, 0.6732, -0.00986209, -0.000199572, 1.91978e-05, 0.6213, -0.010418, 8.83948e-05, 6.24031e-06, 0.5722, -0.00906601, 0.000181999, 6.24033e-06, 0.5322, 0, 0, 0];
+ s.Y = [0, 0.0124, 3.72529e-10, 1.15484e-09, 0.062, 0.0124001, 1.76951e-08, -5.92321e-09, 0.124, 0.0123998, -7.09668e-08, 2.25753e-08, 0.186, 0.0124008, 2.66917e-07, -8.44523e-08, 0.248, 0.0123971, -9.99682e-07, 3.15569e-07, 0.31, 0.0124108, 3.73349e-06, -1.1779e-06, 0.372, 0.0123598, -1.3935e-05, 4.39588e-06, 0.434, 0.0125501, 5.20034e-05, -1.00051e-05, 0.4968, 0.0123198, -9.80735e-05, 9.22397e-06, 0.5571, 0.0120308, 4.02857e-05, -5.2901e-06, 0.6176, 0.0120369, -3.90662e-05, 7.36117e-07, 0.6769, 0.0117015, -2.80246e-05, -8.54283e-07, 0.7346, 0.0113572, -4.08389e-05, -5.18524e-07, 0.7903, 0.0109099, -4.86169e-05, -1.0718e-06, 0.8435, 0.0103433, -6.46934e-05, 5.36384e-09, 0.8936, 0.00969679, -6.46129e-05, -8.54894e-06, 0.9394, 0.00840949, -0.000192847, -4.21023e-06, 0.9761, 0.00616525, -0.000256001, -4.21021e-06, 1, 0, 0, 0];
+ s.NODES = 18;
+ s.FXC = 0.8487;
+ s.FYC = 1.3523;
+ s.C1 = 11.45915590261646417544;
+ s.RC1 = 0.08726646259971647884;
+ s.ONEEPS = 1.000001;
+ s.EPS = 1e-8;
+ return;
}
- i -= 1;
- }
- if (i === 0) {
- x = me.C_x * lplam;
- y = lpphi < 0 ? -me.C_y : me.C_y;
- } else {
- x = me.C_x * lplam * (1 + Math.cos(lpphi));
- y = me.C_y * Math.sin(lpphi);
- }
- return [x, y];
- };
- return EckertIV;
+ Robinson.prototype._poly = function (arr, offs, z) {
+ return arr[offs] + z * (arr[offs + 1] + z * (arr[offs + 2] + z * arr[offs + 3]));
+ };
- })(PseudoCylindrical);
+ Robinson.prototype.project = function (lon, lat) {
+ var i, lplam, lpphi, phi, s, x, y, _ref5;
+ s = this;
+ _ref5 = s.ll(lon, lat), lon = _ref5[0], lat = _ref5[1];
+ lon = s.clon(lon);
+ lplam = s.rad(lon);
+ lpphi = s.rad(lat * -1);
+ phi = Math.abs(lpphi);
+ i = Math.floor(phi * s.C1);
+ if (i >= s.NODES) {
+ i = s.NODES - 1;
+ }
+ phi = s.deg(phi - s.RC1 * i);
+ i *= 4;
+ x = 1000 * s._poly(s.X, i, phi) * s.FXC * lplam;
+ y = 1000 * s._poly(s.Y, i, phi) * s.FYC;
+ if (lpphi < 0.0) {
+ y = -y;
+ }
+ return [x, y];
+ };
- __proj['eckert4'] = EckertIV;
+ return Robinson;
- Sinusoidal = (function(_super) {
+ })(PseudoCylindrical);
- __extends(Sinusoidal, _super);
+ __proj['robinson'] = Robinson;
- /*
- Sinusoidal Projection
- */
+ EckertIV = (function (_super) {
+ __extends(EckertIV, _super);
- function Sinusoidal() {
- return Sinusoidal.__super__.constructor.apply(this, arguments);
- }
+ /*
+ Eckert IV Projection
+ */
- Sinusoidal.title = "Sinusoidal Projection";
-
- Sinusoidal.prototype.project = function(lon, lat) {
- var lam, me, phi, x, y, _ref5;
- me = this;
- _ref5 = me.ll(lon, lat), lon = _ref5[0], lat = _ref5[1];
- lam = me.rad(me.clon(lon));
- phi = me.rad(lat * -1);
- x = 1032 * lam * Math.cos(phi);
- y = 1032 * phi;
- return [x, y];
- };
- return Sinusoidal;
+ EckertIV.title = "Eckert IV Projection";
- })(PseudoCylindrical);
+ function EckertIV(opts) {
+ var me;
+ EckertIV.__super__.constructor.call(this, opts);
+ me = this;
+ me.C_x = .42223820031577120149;
+ me.C_y = 1.32650042817700232218;
+ me.RC_y = .75386330736002178205;
+ me.C_p = 3.57079632679489661922;
+ me.RC_p = .28004957675577868795;
+ me.EPS = 1e-7;
+ me.NITER = 6;
+ }
- __proj['sinusoidal'] = Sinusoidal;
+ EckertIV.prototype.project = function (lon, lat) {
+ var V, c, i, lplam, lpphi, me, p, s, x, y, _ref5;
+ me = this;
+ _ref5 = me.ll(lon, lat), lon = _ref5[0], lat = _ref5[1];
+ lplam = me.rad(me.clon(lon));
+ lpphi = me.rad(lat * -1);
+ p = me.C_p * Math.sin(lpphi);
+ V = lpphi * lpphi;
+ lpphi *= 0.895168 + V * (0.0218849 + V * 0.00826809);
+ i = me.NITER;
+ while (i > 0) {
+ c = Math.cos(lpphi);
+ s = Math.sin(lpphi);
+ V = (lpphi + s * (c + 2) - p) / (1 + c * (c + 2) - s * s);
+ lpphi -= V;
+ if (Math.abs(V) < me.EPS) {
+ break;
+ }
+ i -= 1;
+ }
+ if (i === 0) {
+ x = me.C_x * lplam;
+ y = lpphi < 0 ? -me.C_y : me.C_y;
+ } else {
+ x = me.C_x * lplam * (1 + Math.cos(lpphi));
+ y = me.C_y * Math.sin(lpphi);
+ }
+ return [x, y];
+ };
- Mollweide = (function(_super) {
+ return EckertIV;
- __extends(Mollweide, _super);
+ })(PseudoCylindrical);
- /*
- Mollweide Projection
- */
-
-
- Mollweide.title = "Mollweide Projection";
-
- function Mollweide(opts, p, cx, cy, cp) {
- var me, p2, r, sp;
- if (p == null) {
- p = 1.5707963267948966;
- }
- if (cx == null) {
- cx = null;
- }
- if (cy == null) {
- cy = null;
- }
- if (cp == null) {
- cp = null;
- }
- Mollweide.__super__.constructor.call(this, opts);
- me = this;
- me.MAX_ITER = 10;
- me.TOLERANCE = 1e-7;
- if (p != null) {
- p2 = p + p;
- sp = Math.sin(p);
- r = Math.sqrt(Math.PI * 2.0 * sp / (p2 + Math.sin(p2)));
- me.cx = 2 * r / Math.PI;
- me.cy = r / sp;
- me.cp = p2 + Math.sin(p2);
- } else if ((cx != null) && (cy != null) && (typeof cz !== "undefined" && cz !== null)) {
- me.cx = cx;
- me.cy = cy;
- me.cp = cp;
- } else {
- warn('kartograph.proj.Mollweide: either p or cx,cy,cp must be defined');
- }
- }
+ __proj['eckert4'] = EckertIV;
- Mollweide.prototype.project = function(lon, lat) {
- var abs, i, k, lam, math, me, phi, v, x, y, _ref5;
- me = this;
- _ref5 = me.ll(lon, lat), lon = _ref5[0], lat = _ref5[1];
- math = Math;
- abs = math.abs;
- lam = me.rad(me.clon(lon));
- phi = me.rad(lat);
- k = me.cp * math.sin(phi);
- i = me.MAX_ITER;
- while (i !== 0) {
- v = (phi + math.sin(phi) - k) / (1 + math.cos(phi));
- phi -= v;
- if (abs(v) < me.TOLERANCE) {
- break;
+ Sinusoidal = (function (_super) {
+
+ __extends(Sinusoidal, _super);
+
+ /*
+ Sinusoidal Projection
+ */
+
+
+ function Sinusoidal() {
+ return Sinusoidal.__super__.constructor.apply(this, arguments);
}
- i -= 1;
- }
- if (i === 0) {
- phi = phi >= 0 ? me.HALFPI : -me.HALFPI;
- } else {
- phi *= 0.5;
- }
- x = 1000 * me.cx * lam * math.cos(phi);
- y = 1000 * me.cy * math.sin(phi);
- return [x, y * -1];
- };
- return Mollweide;
+ Sinusoidal.title = "Sinusoidal Projection";
+
+ Sinusoidal.prototype.project = function (lon, lat) {
+ var lam, me, phi, x, y, _ref5;
+ me = this;
+ _ref5 = me.ll(lon, lat), lon = _ref5[0], lat = _ref5[1];
+ lam = me.rad(me.clon(lon));
+ phi = me.rad(lat * -1);
+ x = 1032 * lam * Math.cos(phi);
+ y = 1032 * phi;
+ return [x, y];
+ };
- })(PseudoCylindrical);
+ return Sinusoidal;
- __proj['mollweide'] = Mollweide;
+ })(PseudoCylindrical);
- WagnerIV = (function(_super) {
+ __proj['sinusoidal'] = Sinusoidal;
- __extends(WagnerIV, _super);
+ Mollweide = (function (_super) {
- /*
- Wagner IV Projection
- */
+ __extends(Mollweide, _super);
+ /*
+ Mollweide Projection
+ */
- WagnerIV.title = "Wagner IV Projection";
- function WagnerIV(opts) {
- WagnerIV.__super__.constructor.call(this, opts, 1.0471975511965976);
- }
+ Mollweide.title = "Mollweide Projection";
- return WagnerIV;
+ function Mollweide(opts, p, cx, cy, cp) {
+ var me, p2, r, sp;
+ if (p == null) {
+ p = 1.5707963267948966;
+ }
+ if (cx == null) {
+ cx = null;
+ }
+ if (cy == null) {
+ cy = null;
+ }
+ if (cp == null) {
+ cp = null;
+ }
+ Mollweide.__super__.constructor.call(this, opts);
+ me = this;
+ me.MAX_ITER = 10;
+ me.TOLERANCE = 1e-7;
+ if (p != null) {
+ p2 = p + p;
+ sp = Math.sin(p);
+ r = Math.sqrt(Math.PI * 2.0 * sp / (p2 + Math.sin(p2)));
+ me.cx = 2 * r / Math.PI;
+ me.cy = r / sp;
+ me.cp = p2 + Math.sin(p2);
+ } else if ((cx != null) && (cy != null) && (typeof cz !== "undefined" && cz !== null)) {
+ me.cx = cx;
+ me.cy = cy;
+ me.cp = cp;
+ } else {
+ warn('kartograph.proj.Mollweide: either p or cx,cy,cp must be defined');
+ }
+ }
- })(Mollweide);
+ Mollweide.prototype.project = function (lon, lat) {
+ var abs, i, k, lam, math, me, phi, v, x, y, _ref5;
+ me = this;
+ _ref5 = me.ll(lon, lat), lon = _ref5[0], lat = _ref5[1];
+ math = Math;
+ abs = math.abs;
+ lam = me.rad(me.clon(lon));
+ phi = me.rad(lat);
+ k = me.cp * math.sin(phi);
+ i = me.MAX_ITER;
+ while (i !== 0) {
+ v = (phi + math.sin(phi) - k) / (1 + math.cos(phi));
+ phi -= v;
+ if (abs(v) < me.TOLERANCE) {
+ break;
+ }
+ i -= 1;
+ }
+ if (i === 0) {
+ phi = phi >= 0 ? me.HALFPI : -me.HALFPI;
+ } else {
+ phi *= 0.5;
+ }
+ x = 1000 * me.cx * lam * math.cos(phi);
+ y = 1000 * me.cy * math.sin(phi);
+ return [x, y * -1];
+ };
- __proj['wagner4'] = WagnerIV;
+ return Mollweide;
- WagnerV = (function(_super) {
+ })(PseudoCylindrical);
- __extends(WagnerV, _super);
+ __proj['mollweide'] = Mollweide;
- /*
- Wagner V Projection
- */
+ WagnerIV = (function (_super) {
+ __extends(WagnerIV, _super);
- WagnerV.title = "Wagner V Projection";
+ /*
+ Wagner IV Projection
+ */
- function WagnerV(opts) {
- WagnerV.__super__.constructor.call(this, opts, null, 0.90977, 1.65014, 3.00896);
- }
- return WagnerV;
+ WagnerIV.title = "Wagner IV Projection";
- })(Mollweide);
+ function WagnerIV(opts) {
+ WagnerIV.__super__.constructor.call(this, opts, 1.0471975511965976);
+ }
- __proj['wagner5'] = WagnerV;
+ return WagnerIV;
- Loximuthal = (function(_super) {
- var maxLat, minLat;
+ })(Mollweide);
- __extends(Loximuthal, _super);
+ __proj['wagner4'] = WagnerIV;
- function Loximuthal() {
- return Loximuthal.__super__.constructor.apply(this, arguments);
- }
+ WagnerV = (function (_super) {
- minLat = -89;
-
- maxLat = 89;
-
- Loximuthal.parameters = ['lon0', 'lat0', 'flip'];
-
- Loximuthal.title = "Loximuthal Projection (equidistant)";
-
- Loximuthal.prototype.project = function(lon, lat) {
- var lam, math, me, phi, x, y, _ref5;
- me = this;
- _ref5 = me.ll(lon, lat), lon = _ref5[0], lat = _ref5[1];
- math = Math;
- lam = me.rad(me.clon(lon));
- phi = me.rad(lat);
- if (phi === me.phi0) {
- x = lam * math.cos(me.phi0);
- } else {
- x = lam * (phi - me.phi0) / (math.log(math.tan(me.QUARTERPI + phi * 0.5)) - math.log(math.tan(me.QUARTERPI + me.phi0 * 0.5)));
- }
- x *= 1000;
- y = 1000 * (phi - me.phi0);
- return [x, y * -1];
- };
+ __extends(WagnerV, _super);
- return Loximuthal;
+ /*
+ Wagner V Projection
+ */
- })(PseudoCylindrical);
- __proj['loximuthal'] = Loximuthal;
+ WagnerV.title = "Wagner V Projection";
- CantersModifiedSinusoidalI = (function(_super) {
- var C1, C3, C3x3, C5, C5x5;
+ function WagnerV(opts) {
+ WagnerV.__super__.constructor.call(this, opts, null, 0.90977, 1.65014, 3.00896);
+ }
- __extends(CantersModifiedSinusoidalI, _super);
+ return WagnerV;
- /*
- Canters, F. (2002) Small-scale Map projection Design. p. 218-219.
- Modified Sinusoidal, equal-area.
-
- implementation borrowed from
- http://cartography.oregonstate.edu/temp/AdaptiveProjection/src/projections/Canters1.js
- */
+ })(Mollweide);
+ __proj['wagner5'] = WagnerV;
- function CantersModifiedSinusoidalI() {
- return CantersModifiedSinusoidalI.__super__.constructor.apply(this, arguments);
- }
+ Loximuthal = (function (_super) {
+ var maxLat, minLat;
- CantersModifiedSinusoidalI.title = "Canters Modified Sinusoidal I";
+ __extends(Loximuthal, _super);
- CantersModifiedSinusoidalI.parameters = ['lon0'];
+ function Loximuthal() {
+ return Loximuthal.__super__.constructor.apply(this, arguments);
+ }
- C1 = 1.1966;
+ minLat = -89;
- C3 = -0.1290;
+ maxLat = 89;
- C3x3 = 3 * C3;
+ Loximuthal.parameters = ['lon0', 'lat0', 'flip'];
- C5 = -0.0076;
+ Loximuthal.title = "Loximuthal Projection (equidistant)";
- C5x5 = 5 * C5;
+ Loximuthal.prototype.project = function (lon, lat) {
+ var lam, math, me, phi, x, y, _ref5;
+ me = this;
+ _ref5 = me.ll(lon, lat), lon = _ref5[0], lat = _ref5[1];
+ math = Math;
+ lam = me.rad(me.clon(lon));
+ phi = me.rad(lat);
+ if (phi === me.phi0) {
+ x = lam * math.cos(me.phi0);
+ } else {
+ x = lam * (phi - me.phi0) / (math.log(math.tan(me.QUARTERPI + phi * 0.5)) - math.log(math.tan(me.QUARTERPI + me.phi0 * 0.5)));
+ }
+ x *= 1000;
+ y = 1000 * (phi - me.phi0);
+ return [x, y * -1];
+ };
- CantersModifiedSinusoidalI.prototype.project = function(lon, lat) {
- var me, x, y, y2, y4, _ref5;
- me = this;
- _ref5 = me.ll(lon, lat), lon = _ref5[0], lat = _ref5[1];
- lon = me.rad(me.clon(lon));
- lat = me.rad(lat);
- y2 = lat * lat;
- y4 = y2 * y2;
- x = 1000 * lon * Math.cos(lat) / (C1 + C3x3 * y2 + C5x5 * y4);
- y = 1000 * lat * (C1 + C3 * y2 + C5 * y4);
- return [x, y * -1];
- };
+ return Loximuthal;
- return CantersModifiedSinusoidalI;
+ })(PseudoCylindrical);
- })(PseudoCylindrical);
+ __proj['loximuthal'] = Loximuthal;
- __proj['canters1'] = CantersModifiedSinusoidalI;
+ CantersModifiedSinusoidalI = (function (_super) {
+ var C1, C3, C3x3, C5, C5x5;
- Hatano = (function(_super) {
- var CN, CS, EPS, FXC, FYCN, FYCS, NITER, ONETOL, RCN, RCS, RXC, RYCN, RYCS;
+ __extends(CantersModifiedSinusoidalI, _super);
- __extends(Hatano, _super);
+ /*
+ Canters, F. (2002) Small-scale Map projection Design. p. 218-219.
+ Modified Sinusoidal, equal-area.
- Hatano.title = "Hatano Projection";
+ implementation borrowed from
+ http://cartography.oregonstate.edu/temp/AdaptiveProjection/src/projections/Canters1.js
+ */
- NITER = 20;
- EPS = 1e-7;
+ function CantersModifiedSinusoidalI() {
+ return CantersModifiedSinusoidalI.__super__.constructor.apply(this, arguments);
+ }
- ONETOL = 1.000001;
+ CantersModifiedSinusoidalI.title = "Canters Modified Sinusoidal I";
- CN = 2.67595;
+ CantersModifiedSinusoidalI.parameters = ['lon0'];
- CS = 2.43763;
+ C1 = 1.1966;
- RCN = 0.37369906014686373063;
+ C3 = -0.1290;
- RCS = 0.41023453108141924738;
+ C3x3 = 3 * C3;
- FYCN = 1.75859;
+ C5 = -0.0076;
- FYCS = 1.93052;
+ C5x5 = 5 * C5;
- RYCN = 0.56863737426006061674;
+ CantersModifiedSinusoidalI.prototype.project = function (lon, lat) {
+ var me, x, y, y2, y4, _ref5;
+ me = this;
+ _ref5 = me.ll(lon, lat), lon = _ref5[0], lat = _ref5[1];
+ lon = me.rad(me.clon(lon));
+ lat = me.rad(lat);
+ y2 = lat * lat;
+ y4 = y2 * y2;
+ x = 1000 * lon * Math.cos(lat) / (C1 + C3x3 * y2 + C5x5 * y4);
+ y = 1000 * lat * (C1 + C3 * y2 + C5 * y4);
+ return [x, y * -1];
+ };
- RYCS = 0.51799515156538134803;
+ return CantersModifiedSinusoidalI;
- FXC = 0.85;
+ })(PseudoCylindrical);
- RXC = 1.17647058823529411764;
+ __proj['canters1'] = CantersModifiedSinusoidalI;
- function Hatano(opts) {
- Hatano.__super__.constructor.call(this, opts);
- }
+ Hatano = (function (_super) {
+ var CN, CS, EPS, FXC, FYCN, FYCS, NITER, ONETOL, RCN, RCS, RXC, RYCN, RYCS;
- Hatano.prototype.project = function(lon, lat) {
- var c, i, lam, me, phi, th1, x, y, _i, _ref5;
- me = this;
- _ref5 = me.ll(lon, lat), lon = _ref5[0], lat = _ref5[1];
- lam = me.rad(me.clon(lon));
- phi = me.rad(lat);
- c = Math.sin(phi) * (phi < 0.0 ? CS : CN);
- for (i = _i = NITER; _i >= 1; i = _i += -1) {
- th1 = (phi + Math.sin(phi) - c) / (1.0 + Math.cos(phi));
- phi -= th1;
- if (Math.abs(th1) < EPS) {
- break;
- }
- }
- x = 1000 * FXC * lam * Math.cos(phi *= 0.5);
- y = 1000 * Math.sin(phi) * (phi < 0.0 ? FYCS : FYCN);
- return [x, y * -1];
- };
+ __extends(Hatano, _super);
- return Hatano;
+ Hatano.title = "Hatano Projection";
- })(PseudoCylindrical);
+ NITER = 20;
- __proj['hatano'] = Hatano;
+ EPS = 1e-7;
- GoodeHomolosine = (function(_super) {
+ ONETOL = 1.000001;
- __extends(GoodeHomolosine, _super);
+ CN = 2.67595;
- GoodeHomolosine.title = "Goode Homolosine Projection";
+ CS = 2.43763;
- GoodeHomolosine.parameters = ['lon0'];
+ RCN = 0.37369906014686373063;
- function GoodeHomolosine(opts) {
- var me;
- GoodeHomolosine.__super__.constructor.call(this, opts);
- me = this;
- me.lat1 = 41.737;
- me.p1 = new Mollweide();
- me.p0 = new Sinusoidal();
- }
+ RCS = 0.41023453108141924738;
- GoodeHomolosine.prototype.project = function(lon, lat) {
- var me, _ref5;
- me = this;
- _ref5 = me.ll(lon, lat), lon = _ref5[0], lat = _ref5[1];
- lon = me.clon(lon);
- if (Math.abs(lat) > me.lat1) {
- return me.p1.project(lon, lat);
- } else {
- return me.p0.project(lon, lat);
- }
- };
+ FYCN = 1.75859;
- return GoodeHomolosine;
+ FYCS = 1.93052;
- })(PseudoCylindrical);
+ RYCN = 0.56863737426006061674;
- __proj['goodehomolosine'] = GoodeHomolosine;
+ RYCS = 0.51799515156538134803;
- Nicolosi = (function(_super) {
- var EPS;
+ FXC = 0.85;
- __extends(Nicolosi, _super);
+ RXC = 1.17647058823529411764;
- Nicolosi.title = "Nicolosi Globular Projection";
+ function Hatano(opts) {
+ Hatano.__super__.constructor.call(this, opts);
+ }
- Nicolosi.parameters = ['lon0'];
+ Hatano.prototype.project = function (lon, lat) {
+ var c, i, lam, me, phi, th1, x, y, _i, _ref5;
+ me = this;
+ _ref5 = me.ll(lon, lat), lon = _ref5[0], lat = _ref5[1];
+ lam = me.rad(me.clon(lon));
+ phi = me.rad(lat);
+ c = Math.sin(phi) * (phi < 0.0 ? CS : CN);
+ for (i = _i = NITER; _i >= 1; i = _i += -1) {
+ th1 = (phi + Math.sin(phi) - c) / (1.0 + Math.cos(phi));
+ phi -= th1;
+ if (Math.abs(th1) < EPS) {
+ break;
+ }
+ }
+ x = 1000 * FXC * lam * Math.cos(phi *= 0.5);
+ y = 1000 * Math.sin(phi) * (phi < 0.0 ? FYCS : FYCN);
+ return [x, y * -1];
+ };
- EPS = 1e-10;
+ return Hatano;
- function Nicolosi(opts) {
- Nicolosi.__super__.constructor.call(this, opts);
- this.r = this.HALFPI * 100;
- }
+ })(PseudoCylindrical);
- Nicolosi.prototype._visible = function(lon, lat) {
- var me;
- me = this;
- lon = me.clon(lon);
- return lon > -90 && lon < 90;
- };
+ __proj['hatano'] = Hatano;
- Nicolosi.prototype.project = function(lon, lat) {
- var c, d, lam, m, me, n, phi, r2, sp, tb, x, y, _ref5;
- me = this;
- _ref5 = me.ll(lon, lat), lon = _ref5[0], lat = _ref5[1];
- lam = me.rad(me.clon(lon));
- phi = me.rad(lat);
- if (Math.abs(lam) < EPS) {
- x = 0;
- y = phi;
- } else if (Math.abs(phi) < EPS) {
- x = lam;
- y = 0;
- } else if (Math.abs(Math.abs(lam) - me.HALFPI) < EPS) {
- x = lam * Math.cos(phi);
- y = me.HALFPI * Math.sin(phi);
- } else if (Math.abs(Math.abs(phi) - me.HALFPI) < EPS) {
- x = 0;
- y = phi;
- } else {
- tb = me.HALFPI / lam - lam / me.HALFPI;
- c = phi / me.HALFPI;
- sp = Math.sin(phi);
- d = (1 - c * c) / (sp - c);
- r2 = tb / d;
- r2 *= r2;
- m = (tb * sp / d - 0.5 * tb) / (1.0 + r2);
- n = (sp / r2 + 0.5 * d) / (1.0 + 1.0 / r2);
- x = Math.cos(phi);
- x = Math.sqrt(m * m + x * x / (1.0 + r2));
- x = me.HALFPI * (m + (lam < 0 ? -x : x));
- y = Math.sqrt(n * n - (sp * sp / r2 + d * sp - 1.0) / (1.0 + 1.0 / r2));
- y = me.HALFPI * (n + (phi < 0 ? y : -y));
- }
- return [x * 100, y * -100];
- };
+ GoodeHomolosine = (function (_super) {
- Nicolosi.prototype.sea = function() {
- var math, out, phi, r, _i;
- out = [];
- r = this.r;
- math = Math;
- for (phi = _i = 0; _i <= 360; phi = ++_i) {
- out.push([math.cos(this.rad(phi)) * r, math.sin(this.rad(phi)) * r]);
- }
- return out;
- };
+ __extends(GoodeHomolosine, _super);
- Nicolosi.prototype.world_bbox = function() {
- var r;
- r = this.r;
- return new kartograph.BBox(-r, -r, r * 2, r * 2);
- };
+ GoodeHomolosine.title = "Goode Homolosine Projection";
- return Nicolosi;
+ GoodeHomolosine.parameters = ['lon0'];
- })(PseudoCylindrical);
+ function GoodeHomolosine(opts) {
+ var me;
+ GoodeHomolosine.__super__.constructor.call(this, opts);
+ me = this;
+ me.lat1 = 41.737;
+ me.p1 = new Mollweide();
+ me.p0 = new Sinusoidal();
+ }
- __proj['nicolosi'] = Nicolosi;
+ GoodeHomolosine.prototype.project = function (lon, lat) {
+ var me, _ref5;
+ me = this;
+ _ref5 = me.ll(lon, lat), lon = _ref5[0], lat = _ref5[1];
+ lon = me.clon(lon);
+ if (Math.abs(lat) > me.lat1) {
+ return me.p1.project(lon, lat);
+ } else {
+ return me.p0.project(lon, lat);
+ }
+ };
- Azimuthal = (function(_super) {
+ return GoodeHomolosine;
- __extends(Azimuthal, _super);
+ })(PseudoCylindrical);
- /*
- Base class for azimuthal projections
- */
+ __proj['goodehomolosine'] = GoodeHomolosine;
+ Nicolosi = (function (_super) {
+ var EPS;
- Azimuthal.parameters = ['lon0', 'lat0'];
+ __extends(Nicolosi, _super);
- Azimuthal.title = "Azimuthal Projection";
+ Nicolosi.title = "Nicolosi Globular Projection";
- function Azimuthal(opts, rad) {
- var me;
- if (rad == null) {
- rad = 1000;
- }
- Azimuthal.__super__.constructor.call(this, opts);
- me = this;
- me.r = rad;
- me.elevation0 = me.to_elevation(me.lat0);
- me.azimuth0 = me.to_azimuth(me.lon0);
- }
+ Nicolosi.parameters = ['lon0'];
- Azimuthal.prototype.to_elevation = function(lat) {
- var me;
- me = this;
- return ((lat + 90) / 180) * me.PI - me.HALFPI;
- };
+ EPS = 1e-10;
- Azimuthal.prototype.to_azimuth = function(lon) {
- var me;
- me = this;
- return ((lon + 180) / 360) * me.PI * 2 - me.PI;
- };
+ function Nicolosi(opts) {
+ Nicolosi.__super__.constructor.call(this, opts);
+ this.r = this.HALFPI * 100;
+ }
- Azimuthal.prototype._visible = function(lon, lat) {
- var azimuth, cosc, elevation, math, me;
- me = this;
- math = Math;
- elevation = me.to_elevation(lat);
- azimuth = me.to_azimuth(lon);
- cosc = math.sin(elevation) * math.sin(me.elevation0) + math.cos(me.elevation0) * math.cos(elevation) * math.cos(azimuth - me.azimuth0);
- return cosc >= 0.0;
- };
+ Nicolosi.prototype._visible = function (lon, lat) {
+ var me;
+ me = this;
+ lon = me.clon(lon);
+ return lon > -90 && lon < 90;
+ };
- Azimuthal.prototype._truncate = function(x, y) {
- var math, r, theta, x1, y1;
- math = Math;
- r = this.r;
- theta = math.atan2(y - r, x - r);
- x1 = r + r * math.cos(theta);
- y1 = r + r * math.sin(theta);
- return [x1, y1];
- };
+ Nicolosi.prototype.project = function (lon, lat) {
+ var c, d, lam, m, me, n, phi, r2, sp, tb, x, y, _ref5;
+ me = this;
+ _ref5 = me.ll(lon, lat), lon = _ref5[0], lat = _ref5[1];
+ lam = me.rad(me.clon(lon));
+ phi = me.rad(lat);
+ if (Math.abs(lam) < EPS) {
+ x = 0;
+ y = phi;
+ } else if (Math.abs(phi) < EPS) {
+ x = lam;
+ y = 0;
+ } else if (Math.abs(Math.abs(lam) - me.HALFPI) < EPS) {
+ x = lam * Math.cos(phi);
+ y = me.HALFPI * Math.sin(phi);
+ } else if (Math.abs(Math.abs(phi) - me.HALFPI) < EPS) {
+ x = 0;
+ y = phi;
+ } else {
+ tb = me.HALFPI / lam - lam / me.HALFPI;
+ c = phi / me.HALFPI;
+ sp = Math.sin(phi);
+ d = (1 - c * c) / (sp - c);
+ r2 = tb / d;
+ r2 *= r2;
+ m = (tb * sp / d - 0.5 * tb) / (1.0 + r2);
+ n = (sp / r2 + 0.5 * d) / (1.0 + 1.0 / r2);
+ x = Math.cos(phi);
+ x = Math.sqrt(m * m + x * x / (1.0 + r2));
+ x = me.HALFPI * (m + (lam < 0 ? -x : x));
+ y = Math.sqrt(n * n - (sp * sp / r2 + d * sp - 1.0) / (1.0 + 1.0 / r2));
+ y = me.HALFPI * (n + (phi < 0 ? y : -y));
+ }
+ return [x * 100, y * -100];
+ };
- Azimuthal.prototype.sea = function() {
- var math, out, phi, r, _i;
- out = [];
- r = this.r;
- math = Math;
- for (phi = _i = 0; _i <= 360; phi = ++_i) {
- out.push([r + math.cos(this.rad(phi)) * r, r + math.sin(this.rad(phi)) * r]);
- }
- return out;
- };
+ Nicolosi.prototype.sea = function () {
+ var math, out, phi, r, _i;
+ out = [];
+ r = this.r;
+ math = Math;
+ for (phi = _i = 0; _i <= 360; phi = ++_i) {
+ out.push([math.cos(this.rad(phi)) * r, math.sin(this.rad(phi)) * r]);
+ }
+ return out;
+ };
- Azimuthal.prototype.world_bbox = function() {
- var r;
- r = this.r;
- return new kartograph.BBox(0, 0, r * 2, r * 2);
- };
+ Nicolosi.prototype.world_bbox = function () {
+ var r;
+ r = this.r;
+ return new kartograph.BBox(-r, -r, r * 2, r * 2);
+ };
- return Azimuthal;
+ return Nicolosi;
- })(Proj);
+ })(PseudoCylindrical);
- Orthographic = (function(_super) {
+ __proj['nicolosi'] = Nicolosi;
- __extends(Orthographic, _super);
+ Azimuthal = (function (_super) {
- /*
- Orthographic Azimuthal Projection
-
- implementation taken from http://www.mccarroll.net/snippets/svgworld/
- */
+ __extends(Azimuthal, _super);
+ /*
+ Base class for azimuthal projections
+ */
- function Orthographic() {
- return Orthographic.__super__.constructor.apply(this, arguments);
- }
- Orthographic.title = "Orthographic Projection";
-
- Orthographic.prototype.project = function(lon, lat) {
- var azimuth, elevation, math, me, x, xo, y, yo;
- me = this;
- math = Math;
- elevation = me.to_elevation(lat);
- azimuth = me.to_azimuth(lon);
- xo = me.r * math.cos(elevation) * math.sin(azimuth - me.azimuth0);
- yo = -me.r * (math.cos(me.elevation0) * math.sin(elevation) - math.sin(me.elevation0) * math.cos(elevation) * math.cos(azimuth - me.azimuth0));
- x = me.r + xo;
- y = me.r + yo;
- return [x, y];
- };
+ Azimuthal.parameters = ['lon0', 'lat0'];
- return Orthographic;
+ Azimuthal.title = "Azimuthal Projection";
- })(Azimuthal);
+ function Azimuthal(opts, rad) {
+ var me;
+ if (rad == null) {
+ rad = 1000;
+ }
+ Azimuthal.__super__.constructor.call(this, opts);
+ me = this;
+ me.r = rad;
+ me.elevation0 = me.to_elevation(me.lat0);
+ me.azimuth0 = me.to_azimuth(me.lon0);
+ }
- __proj['ortho'] = Orthographic;
+ Azimuthal.prototype.to_elevation = function (lat) {
+ var me;
+ me = this;
+ return ((lat + 90) / 180) * me.PI - me.HALFPI;
+ };
- LAEA = (function(_super) {
+ Azimuthal.prototype.to_azimuth = function (lon) {
+ var me;
+ me = this;
+ return ((lon + 180) / 360) * me.PI * 2 - me.PI;
+ };
- __extends(LAEA, _super);
+ Azimuthal.prototype._visible = function (lon, lat) {
+ var azimuth, cosc, elevation, math, me;
+ me = this;
+ math = Math;
+ elevation = me.to_elevation(lat);
+ azimuth = me.to_azimuth(lon);
+ cosc = math.sin(elevation) * math.sin(me.elevation0) + math.cos(me.elevation0) * math.cos(elevation) * math.cos(azimuth - me.azimuth0);
+ return cosc >= 0.0;
+ };
- /*
- Lambert Azimuthal Equal-Area Projection
-
- implementation taken from
- Snyder, Map projections - A working manual
- */
+ Azimuthal.prototype._truncate = function (x, y) {
+ var math, r, theta, x1, y1;
+ math = Math;
+ r = this.r;
+ theta = math.atan2(y - r, x - r);
+ x1 = r + r * math.cos(theta);
+ y1 = r + r * math.sin(theta);
+ return [x1, y1];
+ };
+ Azimuthal.prototype.sea = function () {
+ var math, out, phi, r, _i;
+ out = [];
+ r = this.r;
+ math = Math;
+ for (phi = _i = 0; _i <= 360; phi = ++_i) {
+ out.push([r + math.cos(this.rad(phi)) * r, r + math.sin(this.rad(phi)) * r]);
+ }
+ return out;
+ };
- LAEA.title = "Lambert Azimuthal Equal-Area Projection";
+ Azimuthal.prototype.world_bbox = function () {
+ var r;
+ r = this.r;
+ return new kartograph.BBox(0, 0, r * 2, r * 2);
+ };
- function LAEA(opts) {
- LAEA.__super__.constructor.call(this, opts);
- this.scale = Math.sqrt(2) * 0.5;
- }
+ return Azimuthal;
- LAEA.prototype.project = function(lon, lat) {
- var cos, k, lam, math, phi, sin, x, xo, y, yo;
- phi = this.rad(lat);
- lam = this.rad(lon);
- math = Math;
- sin = math.sin;
- cos = math.cos;
- if (false && math.abs(lon - this.lon0) === 180) {
- xo = this.r * 2;
- yo = 0;
- } else {
- k = math.pow(2 / (1 + sin(this.phi0) * sin(phi) + cos(this.phi0) * cos(phi) * cos(lam - this.lam0)), .5);
- k *= this.scale;
- xo = this.r * k * cos(phi) * sin(lam - this.lam0);
- yo = -this.r * k * (cos(this.phi0) * sin(phi) - sin(this.phi0) * cos(phi) * cos(lam - this.lam0));
- }
- x = this.r + xo;
- y = this.r + yo;
- return [x, y];
- };
+ })(Proj);
- return LAEA;
+ Orthographic = (function (_super) {
- })(Azimuthal);
+ __extends(Orthographic, _super);
- __proj['laea'] = LAEA;
+ /*
+ Orthographic Azimuthal Projection
- Stereographic = (function(_super) {
+ implementation taken from http://www.mccarroll.net/snippets/svgworld/
+ */
- __extends(Stereographic, _super);
- /*
- Stereographic projection
-
- implementation taken from
- Snyder, Map projections - A working manual
- */
+ function Orthographic() {
+ return Orthographic.__super__.constructor.apply(this, arguments);
+ }
+ Orthographic.title = "Orthographic Projection";
+
+ Orthographic.prototype.project = function (lon, lat) {
+ var azimuth, elevation, math, me, x, xo, y, yo;
+ me = this;
+ math = Math;
+ elevation = me.to_elevation(lat);
+ azimuth = me.to_azimuth(lon);
+ xo = me.r * math.cos(elevation) * math.sin(azimuth - me.azimuth0);
+ yo = -me.r * (math.cos(me.elevation0) * math.sin(elevation) - math.sin(me.elevation0) * math.cos(elevation) * math.cos(azimuth - me.azimuth0));
+ x = me.r + xo;
+ y = me.r + yo;
+ return [x, y];
+ };
- function Stereographic() {
- return Stereographic.__super__.constructor.apply(this, arguments);
- }
+ return Orthographic;
- Stereographic.title = "Stereographic Projection";
-
- Stereographic.prototype.project = function(lon, lat) {
- var cos, k, k0, lam, math, phi, sin, x, xo, y, yo;
- phi = this.rad(lat);
- lam = this.rad(lon);
- math = Math;
- sin = math.sin;
- cos = math.cos;
- k0 = 0.5;
- k = 2 * k0 / (1 + sin(this.phi0) * sin(phi) + cos(this.phi0) * cos(phi) * cos(lam - this.lam0));
- xo = this.r * k * cos(phi) * sin(lam - this.lam0);
- yo = -this.r * k * (cos(this.phi0) * sin(phi) - sin(this.phi0) * cos(phi) * cos(lam - this.lam0));
- x = this.r + xo;
- y = this.r + yo;
- return [x, y];
- };
+ })(Azimuthal);
- return Stereographic;
+ __proj['ortho'] = Orthographic;
- })(Azimuthal);
+ LAEA = (function (_super) {
- __proj['stereo'] = Stereographic;
+ __extends(LAEA, _super);
- Satellite = (function(_super) {
+ /*
+ Lambert Azimuthal Equal-Area Projection
- __extends(Satellite, _super);
+ implementation taken from
+ Snyder, Map projections - A working manual
+ */
- /*
- General perspective projection, aka Satellite projection
-
- implementation taken from
- Snyder, Map projections - A working manual
-
- up .. angle the camera is turned away from north (clockwise)
- tilt .. angle the camera is tilted
- */
-
-
- Satellite.parameters = ['lon0', 'lat0', 'tilt', 'dist', 'up'];
-
- Satellite.title = "Satellite Projection";
-
- function Satellite(opts) {
- var lat, lon, xmax, xmin, xy, _i, _j, _ref5, _ref6, _ref7;
- Satellite.__super__.constructor.call(this, {
- lon0: 0,
- lat0: 0
- });
- this.dist = (_ref5 = opts.dist) != null ? _ref5 : 3;
- this.up = this.rad((_ref6 = opts.up) != null ? _ref6 : 0);
- this.tilt = this.rad((_ref7 = opts.tilt) != null ? _ref7 : 0);
- this.scale = 1;
- xmin = Number.MAX_VALUE;
- xmax = Number.MAX_VALUE * -1;
- for (lat = _i = 0; _i <= 179; lat = ++_i) {
- for (lon = _j = 0; _j <= 360; lon = ++_j) {
- xy = this.project(lon - 180, lat - 90);
- xmin = Math.min(xy[0], xmin);
- xmax = Math.max(xy[0], xmax);
- }
- }
- this.scale = (this.r * 2) / (xmax - xmin);
- Satellite.__super__.constructor.call(this, opts);
- return;
- }
- Satellite.prototype.project = function(lon, lat, alt) {
- var A, H, cos, cos_c, cos_tilt, cos_up, k, lam, math, phi, r, ra, sin, sin_tilt, sin_up, x, xo, xt, y, yo, yt;
- if (alt == null) {
- alt = 0;
- }
- phi = this.rad(lat);
- lam = this.rad(lon);
- math = Math;
- sin = math.sin;
- cos = math.cos;
- r = this.r;
- ra = r * (alt + 6371) / 3671;
- cos_c = sin(this.phi0) * sin(phi) + cos(this.phi0) * cos(phi) * cos(lam - this.lam0);
- k = (this.dist - 1) / (this.dist - cos_c);
- k = (this.dist - 1) / (this.dist - cos_c);
- k *= this.scale;
- xo = ra * k * cos(phi) * sin(lam - this.lam0);
- yo = -ra * k * (cos(this.phi0) * sin(phi) - sin(this.phi0) * cos(phi) * cos(lam - this.lam0));
- cos_up = cos(this.up);
- sin_up = sin(this.up);
- cos_tilt = cos(this.tilt);
- sin_tilt = sin(this.tilt);
- H = ra * (this.dist - 1);
- A = ((yo * cos_up + xo * sin_up) * sin(this.tilt / H)) + cos_tilt;
- xt = (xo * cos_up - yo * sin_up) * cos(this.tilt / A);
- yt = (yo * cos_up + xo * sin_up) / A;
- x = r + xt;
- y = r + yt;
- return [x, y];
- };
+ LAEA.title = "Lambert Azimuthal Equal-Area Projection";
- Satellite.prototype._visible = function(lon, lat) {
- var azimuth, cosc, elevation, math;
- elevation = this.to_elevation(lat);
- azimuth = this.to_azimuth(lon);
- math = Math;
- cosc = math.sin(elevation) * math.sin(this.elevation0) + math.cos(this.elevation0) * math.cos(elevation) * math.cos(azimuth - this.azimuth0);
- return cosc >= (1.0 / this.dist);
- };
+ function LAEA(opts) {
+ LAEA.__super__.constructor.call(this, opts);
+ this.scale = Math.sqrt(2) * 0.5;
+ }
- Satellite.prototype.sea = function() {
- var math, out, phi, r, _i;
- out = [];
- r = this.r;
- math = Math;
- for (phi = _i = 0; _i <= 360; phi = ++_i) {
- out.push([r + math.cos(this.rad(phi)) * r, r + math.sin(this.rad(phi)) * r]);
- }
- return out;
- };
+ LAEA.prototype.project = function (lon, lat) {
+ var cos, k, lam, math, phi, sin, x, xo, y, yo;
+ phi = this.rad(lat);
+ lam = this.rad(lon);
+ math = Math;
+ sin = math.sin;
+ cos = math.cos;
+ if (false && math.abs(lon - this.lon0) === 180) {
+ xo = this.r * 2;
+ yo = 0;
+ } else {
+ k = math.pow(2 / (1 + sin(this.phi0) * sin(phi) + cos(this.phi0) * cos(phi) * cos(lam - this.lam0)), .5);
+ k *= this.scale;
+ xo = this.r * k * cos(phi) * sin(lam - this.lam0);
+ yo = -this.r * k * (cos(this.phi0) * sin(phi) - sin(this.phi0) * cos(phi) * cos(lam - this.lam0));
+ }
+ x = this.r + xo;
+ y = this.r + yo;
+ return [x, y];
+ };
- return Satellite;
+ return LAEA;
- })(Azimuthal);
+ })(Azimuthal);
- __proj['satellite'] = Satellite;
+ __proj['laea'] = LAEA;
- EquidistantAzimuthal = (function(_super) {
+ Stereographic = (function (_super) {
- __extends(EquidistantAzimuthal, _super);
+ __extends(Stereographic, _super);
- /*
- Equidistant projection
-
- implementation taken from
- Snyder, Map projections - A working manual
- */
+ /*
+ Stereographic projection
+ implementation taken from
+ Snyder, Map projections - A working manual
+ */
- function EquidistantAzimuthal() {
- return EquidistantAzimuthal.__super__.constructor.apply(this, arguments);
- }
- EquidistantAzimuthal.title = "Equidistant Azimuthal Projection";
-
- EquidistantAzimuthal.prototype.project = function(lon, lat) {
- var c, cos, cos_c, k, lam, math, me, phi, sin, x, xo, y, yo;
- me = this;
- phi = me.rad(lat);
- lam = me.rad(lon);
- math = Math;
- sin = math.sin;
- cos = math.cos;
- cos_c = sin(this.phi0) * sin(phi) + cos(this.phi0) * cos(phi) * cos(lam - this.lam0);
- c = math.acos(cos_c);
- k = 0.325 * c / sin(c);
- xo = this.r * k * cos(phi) * sin(lam - this.lam0);
- yo = -this.r * k * (cos(this.phi0) * sin(phi) - sin(this.phi0) * cos(phi) * cos(lam - this.lam0));
- x = this.r + xo;
- y = this.r + yo;
- return [x, y];
- };
+ function Stereographic() {
+ return Stereographic.__super__.constructor.apply(this, arguments);
+ }
- EquidistantAzimuthal.prototype._visible = function(lon, lat) {
- return true;
- };
+ Stereographic.title = "Stereographic Projection";
+
+ Stereographic.prototype.project = function (lon, lat) {
+ var cos, k, k0, lam, math, phi, sin, x, xo, y, yo;
+ phi = this.rad(lat);
+ lam = this.rad(lon);
+ math = Math;
+ sin = math.sin;
+ cos = math.cos;
+ k0 = 0.5;
+ k = 2 * k0 / (1 + sin(this.phi0) * sin(phi) + cos(this.phi0) * cos(phi) * cos(lam - this.lam0));
+ xo = this.r * k * cos(phi) * sin(lam - this.lam0);
+ yo = -this.r * k * (cos(this.phi0) * sin(phi) - sin(this.phi0) * cos(phi) * cos(lam - this.lam0));
+ x = this.r + xo;
+ y = this.r + yo;
+ return [x, y];
+ };
- return EquidistantAzimuthal;
+ return Stereographic;
- })(Azimuthal);
+ })(Azimuthal);
- __proj['equi'] = EquidistantAzimuthal;
+ __proj['stereo'] = Stereographic;
- Aitoff = (function(_super) {
- var COSPHI1;
+ Satellite = (function (_super) {
- __extends(Aitoff, _super);
+ __extends(Satellite, _super);
- /*
- Aitoff projection
-
- implementation taken from
- Snyder, Map projections - A working manual
- */
+ /*
+ General perspective projection, aka Satellite projection
+ implementation taken from
+ Snyder, Map projections - A working manual
- Aitoff.title = "Aitoff Projection";
+ up .. angle the camera is turned away from north (clockwise)
+ tilt .. angle the camera is tilted
+ */
- Aitoff.parameters = ['lon0'];
- COSPHI1 = 0.636619772367581343;
+ Satellite.parameters = ['lon0', 'lat0', 'tilt', 'dist', 'up'];
- function Aitoff(opts) {
- var me;
- me = this;
- opts.lat0 = 0;
- Aitoff.__super__.constructor.call(this, opts);
- me.lam0 = 0;
- }
+ Satellite.title = "Satellite Projection";
- Aitoff.prototype.project = function(lon, lat) {
- var c, d, lam, me, phi, x, y, _ref5;
- me = this;
- _ref5 = me.ll(lon, lat), lon = _ref5[0], lat = _ref5[1];
- lon = me.clon(lon);
- lam = me.rad(lon);
- phi = me.rad(lat);
- c = 0.5 * lam;
- d = Math.acos(Math.cos(phi) * Math.cos(c));
- if (d !== 0) {
- y = 1.0 / Math.sin(d);
- x = 2.0 * d * Math.cos(phi) * Math.sin(c) * y;
- y *= d * Math.sin(phi);
- } else {
- x = y = 0;
- }
- if (me.winkel) {
- x = (x + lam * COSPHI1) * 0.5;
- y = (y + phi) * 0.5;
- }
- return [x * 1000, y * -1000];
- };
+ function Satellite(opts) {
+ var lat, lon, xmax, xmin, xy, _i, _j, _ref5, _ref6, _ref7;
+ Satellite.__super__.constructor.call(this, {
+ lon0: 0,
+ lat0: 0
+ });
+ this.dist = (_ref5 = opts.dist) != null ? _ref5 : 3;
+ this.up = this.rad((_ref6 = opts.up) != null ? _ref6 : 0);
+ this.tilt = this.rad((_ref7 = opts.tilt) != null ? _ref7 : 0);
+ this.scale = 1;
+ xmin = Number.MAX_VALUE;
+ xmax = Number.MAX_VALUE * -1;
+ for (lat = _i = 0; _i <= 179; lat = ++_i) {
+ for (lon = _j = 0; _j <= 360; lon = ++_j) {
+ xy = this.project(lon - 180, lat - 90);
+ xmin = Math.min(xy[0], xmin);
+ xmax = Math.max(xy[0], xmax);
+ }
+ }
+ this.scale = (this.r * 2) / (xmax - xmin);
+ Satellite.__super__.constructor.call(this, opts);
+ return;
+ }
- Aitoff.prototype._visible = function(lon, lat) {
- return true;
- };
+ Satellite.prototype.project = function (lon, lat, alt) {
+ var A, H, cos, cos_c, cos_tilt, cos_up, k, lam, math, phi, r, ra, sin, sin_tilt, sin_up, x, xo, xt, y, yo, yt;
+ if (alt == null) {
+ alt = 0;
+ }
+ phi = this.rad(lat);
+ lam = this.rad(lon);
+ math = Math;
+ sin = math.sin;
+ cos = math.cos;
+ r = this.r;
+ ra = r * (alt + 6371) / 3671;
+ cos_c = sin(this.phi0) * sin(phi) + cos(this.phi0) * cos(phi) * cos(lam - this.lam0);
+ k = (this.dist - 1) / (this.dist - cos_c);
+ k = (this.dist - 1) / (this.dist - cos_c);
+ k *= this.scale;
+ xo = ra * k * cos(phi) * sin(lam - this.lam0);
+ yo = -ra * k * (cos(this.phi0) * sin(phi) - sin(this.phi0) * cos(phi) * cos(lam - this.lam0));
+ cos_up = cos(this.up);
+ sin_up = sin(this.up);
+ cos_tilt = cos(this.tilt);
+ sin_tilt = sin(this.tilt);
+ H = ra * (this.dist - 1);
+ A = ((yo * cos_up + xo * sin_up) * sin(this.tilt / H)) + cos_tilt;
+ xt = (xo * cos_up - yo * sin_up) * cos(this.tilt / A);
+ yt = (yo * cos_up + xo * sin_up) / A;
+ x = r + xt;
+ y = r + yt;
+ return [x, y];
+ };
- return Aitoff;
+ Satellite.prototype._visible = function (lon, lat) {
+ var azimuth, cosc, elevation, math;
+ elevation = this.to_elevation(lat);
+ azimuth = this.to_azimuth(lon);
+ math = Math;
+ cosc = math.sin(elevation) * math.sin(this.elevation0) + math.cos(this.elevation0) * math.cos(elevation) * math.cos(azimuth - this.azimuth0);
+ return cosc >= (1.0 / this.dist);
+ };
- })(PseudoCylindrical);
+ Satellite.prototype.sea = function () {
+ var math, out, phi, r, _i;
+ out = [];
+ r = this.r;
+ math = Math;
+ for (phi = _i = 0; _i <= 360; phi = ++_i) {
+ out.push([r + math.cos(this.rad(phi)) * r, r + math.sin(this.rad(phi)) * r]);
+ }
+ return out;
+ };
- __proj['aitoff'] = Aitoff;
+ return Satellite;
- Winkel3 = (function(_super) {
+ })(Azimuthal);
- __extends(Winkel3, _super);
+ __proj['satellite'] = Satellite;
- Winkel3.title = "Winkel Tripel Projection";
+ EquidistantAzimuthal = (function (_super) {
- function Winkel3(opts) {
- Winkel3.__super__.constructor.call(this, opts);
- this.winkel = true;
- }
+ __extends(EquidistantAzimuthal, _super);
- return Winkel3;
+ /*
+ Equidistant projection
- })(Aitoff);
+ implementation taken from
+ Snyder, Map projections - A working manual
+ */
- __proj['winkel3'] = Winkel3;
- Conic = (function(_super) {
+ function EquidistantAzimuthal() {
+ return EquidistantAzimuthal.__super__.constructor.apply(this, arguments);
+ }
- __extends(Conic, _super);
+ EquidistantAzimuthal.title = "Equidistant Azimuthal Projection";
+
+ EquidistantAzimuthal.prototype.project = function (lon, lat) {
+ var c, cos, cos_c, k, lam, math, me, phi, sin, x, xo, y, yo;
+ me = this;
+ phi = me.rad(lat);
+ lam = me.rad(lon);
+ math = Math;
+ sin = math.sin;
+ cos = math.cos;
+ cos_c = sin(this.phi0) * sin(phi) + cos(this.phi0) * cos(phi) * cos(lam - this.lam0);
+ c = math.acos(cos_c);
+ k = 0.325 * c / sin(c);
+ xo = this.r * k * cos(phi) * sin(lam - this.lam0);
+ yo = -this.r * k * (cos(this.phi0) * sin(phi) - sin(this.phi0) * cos(phi) * cos(lam - this.lam0));
+ x = this.r + xo;
+ y = this.r + yo;
+ return [x, y];
+ };
- Conic.title = "Conic Projection";
+ EquidistantAzimuthal.prototype._visible = function (lon, lat) {
+ return true;
+ };
- Conic.parameters = ['lon0', 'lat0', 'lat1', 'lat2'];
+ return EquidistantAzimuthal;
- function Conic(opts) {
- var self, _ref5, _ref6;
- self = this;
- Conic.__super__.constructor.call(this, opts);
- self.lat1 = (_ref5 = opts.lat1) != null ? _ref5 : 30;
- self.phi1 = self.rad(self.lat1);
- self.lat2 = (_ref6 = opts.lat2) != null ? _ref6 : 50;
- self.phi2 = self.rad(self.lat2);
- }
+ })(Azimuthal);
- Conic.prototype._visible = function(lon, lat) {
- var self;
- self = this;
- return lat > self.minLat && lat < self.maxLat;
- };
+ __proj['equi'] = EquidistantAzimuthal;
- Conic.prototype._truncate = function(x, y) {
- return [x, y];
- };
+ Aitoff = (function (_super) {
+ var COSPHI1;
- Conic.prototype.clon = function(lon) {
- lon -= this.lon0;
- if (lon < -180) {
- lon += 360;
- } else if (lon > 180) {
- lon -= 360;
- }
- return lon;
- };
+ __extends(Aitoff, _super);
- return Conic;
+ /*
+ Aitoff projection
- })(Proj);
+ implementation taken from
+ Snyder, Map projections - A working manual
+ */
- LCC = (function(_super) {
- __extends(LCC, _super);
+ Aitoff.title = "Aitoff Projection";
- /*
- Lambert Conformal Conic Projection (spherical)
- */
-
-
- LCC.title = "Lambert Conformal Conic Projection";
-
- function LCC(opts) {
- var abs, c, cos, cosphi, m, n, pow, secant, self, sin, sinphi, tan, _ref5;
- self = this;
- LCC.__super__.constructor.call(this, opts);
- m = Math;
- _ref5 = [m.sin, m.cos, m.abs, m.log, m.tan, m.pow], sin = _ref5[0], cos = _ref5[1], abs = _ref5[2], log = _ref5[3], tan = _ref5[4], pow = _ref5[5];
- self.n = n = sinphi = sin(self.phi1);
- cosphi = cos(self.phi1);
- secant = abs(self.phi1 - self.phi2) >= 1e-10;
- if (secant) {
- n = log(cosphi / cos(self.phi2)) / log(tan(self.QUARTERPI + 0.5 * self.phi2) / tan(self.QUARTERPI + 0.5 * self.phi1));
- }
- self.c = c = cosphi * pow(tan(self.QUARTERPI + .5 * self.phi1), n) / n;
- if (abs(abs(self.phi0) - self.HALFPI) < 1e-10) {
- self.rho0 = 0.0;
- } else {
- self.rho0 = c * pow(tan(self.QUARTERPI + .5 * self.phi0), -n);
- }
- self.minLat = -60;
- self.maxLat = 85;
- }
+ Aitoff.parameters = ['lon0'];
- LCC.prototype.project = function(lon, lat) {
- var abs, cos, lam, lam_, m, n, phi, pow, rho, self, sin, tan, x, y, _ref5;
- self = this;
- phi = self.rad(lat);
- lam = self.rad(self.clon(lon));
- m = Math;
- _ref5 = [m.sin, m.cos, m.abs, m.log, m.tan, m.pow], sin = _ref5[0], cos = _ref5[1], abs = _ref5[2], log = _ref5[3], tan = _ref5[4], pow = _ref5[5];
- n = self.n;
- if (abs(abs(phi) - self.HALFPI) < 1e-10) {
- rho = 0.0;
- } else {
- rho = self.c * pow(tan(self.QUARTERPI + 0.5 * phi), -n);
- }
- lam_ = lam * n;
- x = 1000 * rho * sin(lam_);
- y = 1000 * (self.rho0 - rho * cos(lam_));
- return [x, y * -1];
- };
+ COSPHI1 = 0.636619772367581343;
- return LCC;
+ function Aitoff(opts) {
+ var me;
+ me = this;
+ opts.lat0 = 0;
+ Aitoff.__super__.constructor.call(this, opts);
+ me.lam0 = 0;
+ }
- })(Conic);
+ Aitoff.prototype.project = function (lon, lat) {
+ var c, d, lam, me, phi, x, y, _ref5;
+ me = this;
+ _ref5 = me.ll(lon, lat), lon = _ref5[0], lat = _ref5[1];
+ lon = me.clon(lon);
+ lam = me.rad(lon);
+ phi = me.rad(lat);
+ c = 0.5 * lam;
+ d = Math.acos(Math.cos(phi) * Math.cos(c));
+ if (d !== 0) {
+ y = 1.0 / Math.sin(d);
+ x = 2.0 * d * Math.cos(phi) * Math.sin(c) * y;
+ y *= d * Math.sin(phi);
+ } else {
+ x = y = 0;
+ }
+ if (me.winkel) {
+ x = (x + lam * COSPHI1) * 0.5;
+ y = (y + phi) * 0.5;
+ }
+ return [x * 1000, y * -1000];
+ };
- __proj['lcc'] = LCC;
+ Aitoff.prototype._visible = function (lon, lat) {
+ return true;
+ };
- PseudoConic = (function(_super) {
+ return Aitoff;
- __extends(PseudoConic, _super);
+ })(PseudoCylindrical);
- function PseudoConic() {
- return PseudoConic.__super__.constructor.apply(this, arguments);
- }
+ __proj['aitoff'] = Aitoff;
- return PseudoConic;
-
- })(Conic);
-
- /*
- kartograph - a svg mapping library
- Copyright (C) 2011 Gregor Aisch
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more detailme.
-
- You should have received a copy of the GNU General Public License
- along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-
-
- View = (function() {
- /*
- 2D coordinate transfomation
- */
-
- function View(bbox, width, height, padding, halign, valign) {
- var me;
- me = this;
- me.bbox = bbox;
- me.width = width;
- me.padding = padding != null ? padding : 0;
- me.halign = halign != null ? halign : 'center';
- me.valign = valign != null ? valign : 'center';
- me.height = height;
- me.scale = Math.min((width - padding * 2) / bbox.width, (height - padding * 2) / bbox.height);
- }
+ Winkel3 = (function (_super) {
- View.prototype.project = function(x, y) {
- var bbox, h, me, s, w, xf, yf;
- if (!(y != null)) {
- y = x[1];
- x = x[0];
- }
- me = this;
- s = me.scale;
- bbox = me.bbox;
- h = me.height;
- w = me.width;
- xf = me.halign === "center" ? (w - bbox.width * s) * 0.5 : me.halign === "left" ? me.padding * s : w - (bbox.width - me.padding) * s;
- yf = me.valign === "center" ? (h - bbox.height * s) * 0.5 : me.valign === "top" ? me.padding * s : 0;
- x = (x - bbox.left) * s + xf;
- y = (y - bbox.top) * s + yf;
- return [x, y];
- };
+ __extends(Winkel3, _super);
- View.prototype.projectPath = function(path) {
- var bbox, cont, contours, me, new_path, pcont, r, x, y, _i, _j, _len, _len1, _ref5, _ref6, _ref7, _ref8;
- me = this;
- if (path.type === "path") {
- contours = [];
- bbox = [99999, 99999, -99999, -99999];
- _ref5 = path.contours;
- for (_i = 0, _len = _ref5.length; _i < _len; _i++) {
- pcont = _ref5[_i];
- cont = [];
- for (_j = 0, _len1 = pcont.length; _j < _len1; _j++) {
- _ref6 = pcont[_j], x = _ref6[0], y = _ref6[1];
- _ref7 = me.project(x, y), x = _ref7[0], y = _ref7[1];
- cont.push([x, y]);
- bbox[0] = Math.min(bbox[0], x);
- bbox[1] = Math.min(bbox[1], y);
- bbox[2] = Math.max(bbox[2], x);
- bbox[3] = Math.max(bbox[3], y);
- }
- contours.push(cont);
+ Winkel3.title = "Winkel Tripel Projection";
+
+ function Winkel3(opts) {
+ Winkel3.__super__.constructor.call(this, opts);
+ this.winkel = true;
}
- new_path = new kartograph.geom.Path(path.type, contours, path.closed);
- new_path._bbox = bbox;
- return new_path;
- } else if (path.type === "circle") {
- _ref8 = me.project(path.x, path.y), x = _ref8[0], y = _ref8[1];
- r = path.r * me.scale;
- return new kartograph.geom.Circle(x, y, r);
- }
- };
- View.prototype.asBBox = function() {
- var me;
- me = this;
- return new kartograph.BBox(0, 0, me.width, me.height);
- };
+ return Winkel3;
- return View;
+ })(Aitoff);
- })();
+ __proj['winkel3'] = Winkel3;
- View.fromXML = function(xml) {
- /*
- constructs a view from XML
- */
-
- var bbox, bbox_xml, h, pad, w;
- w = Number(xml.getAttribute('w'));
- h = Number(xml.getAttribute('h'));
- pad = Number(xml.getAttribute('padding'));
- bbox_xml = xml.getElementsByTagName('bbox')[0];
- bbox = BBox.fromXML(bbox_xml);
- return new kartograph.View(bbox, w, h, pad);
- };
-
- kartograph.View = View;
-
- /*
- kartograph - a svg mapping library
- Copyright (C) 2011 Gregor Aisch
-
- This library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Lesser General Public
- License as published by the Free Software Foundation; either
- version 2.1 of the License, or (at your option) any later version.
-
- This library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public
- License along with this library. If not, see <http://www.gnu.org/licenses/>.
- */
-
-
- kartograph.Kartograph.prototype.dotgrid = function(opts) {
- var anim, data, data_col, data_key, delay, dly, dotgrid, dotstyle, ds, dur, f, g, gridsize, id, layer, layer_id, me, path, pathData, paths, pd, row, size, sizes, x, y, _i, _j, _k, _l, _len, _len1, _len2, _len3, _m, _n, _ref10, _ref11, _ref12, _ref13, _ref14, _ref15, _ref16, _ref17, _ref5, _ref6, _ref7, _ref8, _ref9;
- me = this;
- layer_id = (_ref5 = opts.layer) != null ? _ref5 : me.layerIds[me.layerIds.length - 1];
- if (!me.layers.hasOwnProperty(layer_id)) {
- warn('dotgrid error: layer "' + layer_id + '" not found');
- return;
- }
- layer = me.layers[layer_id];
- data = opts.data;
- data_col = opts.value;
- data_key = opts.key;
- pathData = {};
- if ((data_key != null) && __type(data) === "array") {
- for (_i = 0, _len = data.length; _i < _len; _i++) {
- row = data[_i];
- id = row[data_key];
- pathData[String(id)] = row;
- }
- } else {
- for (id in data) {
- row = data[id];
- pathData[String(id)] = row;
- }
- }
- dotstyle = (_ref6 = opts.style) != null ? _ref6 : {
- fill: 'black',
- stroke: 'none'
- };
- sizes = opts.size;
- gridsize = (_ref7 = opts.gridsize) != null ? _ref7 : 15;
- dotgrid = (_ref8 = layer.dotgrid) != null ? _ref8 : layer.dotgrid = {
- gridsize: gridsize,
- grid: []
- };
- if (dotgrid.gridsize !== gridsize) {
- _ref9 = dotgrid.grid;
- for (_j = 0, _len1 = _ref9.length; _j < _len1; _j++) {
- g = _ref9[_j];
- if (g.shape != null) {
- g.shape.remove();
- g.shape = null;
+ Conic = (function (_super) {
+
+ __extends(Conic, _super);
+
+ Conic.title = "Conic Projection";
+
+ Conic.parameters = ['lon0', 'lat0', 'lat1', 'lat2'];
+
+ function Conic(opts) {
+ var self, _ref5, _ref6;
+ self = this;
+ Conic.__super__.constructor.call(this, opts);
+ self.lat1 = (_ref5 = opts.lat1) != null ? _ref5 : 30;
+ self.phi1 = self.rad(self.lat1);
+ self.lat2 = (_ref6 = opts.lat2) != null ? _ref6 : 50;
+ self.phi2 = self.rad(self.lat2);
}
- }
- }
- if (gridsize > 0) {
- if (dotgrid.grid.length === 0) {
- for (x = _k = 0, _ref10 = me.viewport.width; 0 <= _ref10 ? _k <= _ref10 : _k >= _ref10; x = _k += gridsize) {
- for (y = _l = 0, _ref11 = me.viewport.height; 0 <= _ref11 ? _l <= _ref11 : _l >= _ref11; y = _l += gridsize) {
- g = {
- x: x + (Math.random() - 0.5) * gridsize * 0.2,
- y: y + (Math.random() - 0.5) * gridsize * 0.2,
- pathid: false
- };
- f = false;
- _ref12 = layer.pathsById;
- for (id in _ref12) {
- paths = _ref12[id];
- for (_m = 0, _len2 = paths.length; _m < _len2; _m++) {
- path = paths[_m];
- if (path.vpath.isInside(g.x, g.y)) {
- f = true;
- pd = (_ref13 = pathData[id]) != null ? _ref13 : null;
- size = sizes(pd);
- g.pathid = id;
- g.shape = layer.paper.circle(g.x, g.y, 1);
- break;
- }
- }
- if (f) {
- break;
- }
+
+ Conic.prototype._visible = function (lon, lat) {
+ var self;
+ self = this;
+ return lat > self.minLat && lat < self.maxLat;
+ };
+
+ Conic.prototype._truncate = function (x, y) {
+ return [x, y];
+ };
+
+ Conic.prototype.clon = function (lon) {
+ lon -= this.lon0;
+ if (lon < -180) {
+ lon += 360;
+ } else if (lon > 180) {
+ lon -= 360;
}
- dotgrid.grid.push(g);
- }
- }
- }
- _ref14 = dotgrid.grid;
- for (_n = 0, _len3 = _ref14.length; _n < _len3; _n++) {
- g = _ref14[_n];
- if (g.pathid) {
- pd = (_ref15 = pathData[g.pathid]) != null ? _ref15 : null;
- size = sizes(pd);
- dur = (_ref16 = opts.duration) != null ? _ref16 : 0;
- delay = (_ref17 = opts.delay) != null ? _ref17 : 0;
- if (__type(delay) === "function") {
- dly = delay(pd);
- } else {
- dly = delay;
- }
- if (dur > 0 && Raphael.svg) {
- anim = Raphael.animation({
- r: size * 0.5
- }, dur);
- g.shape.animate(anim.delay(dly));
- } else {
- g.shape.attr({
- r: size * 0.5
- });
- }
- if (__type(dotstyle) === "function") {
- ds = dotstyle(pd);
- } else {
- ds = dotstyle;
- }
- g.shape.attr(ds);
- }
- }
- }
- };
-
- /*
- kartograph - a svg mapping library
- Copyright (C) 2011 Gregor Aisch
-
- This library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Lesser General Public
- License as published by the Free Software Foundation; either
- version 2.1 of the License, or (at your option) any later version.
-
- This library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public
- License along with this library. If not, see <http://www.gnu.org/licenses/>.
- */
-
-
- filter = (_ref5 = kartograph.filter) != null ? _ref5 : kartograph.filter = {};
-
- filter.__knownFilter = {};
-
- filter.__patternFills = 0;
-
- MapLayer.prototype.SVG = function(el, attr) {
- var key, val;
- if (typeof el === "string") {
- el = window.document.createElementNS("http://www.w3.org/2000/svg", el);
- }
- if (attr) {
- for (key in attr) {
- val = attr[key];
- el.setAttribute(key, val);
- }
- }
- return el;
- };
+ return lon;
+ };
- kartograph.Kartograph.prototype.addFilter = function(id, type, params) {
- var doc, fltr, me;
- if (params == null) {
- params = {};
- }
- me = this;
- doc = window.document;
- if (kartograph.filter[type] != null) {
- fltr = new kartograph.filter[type](params).getFilter(id);
- } else {
- throw 'unknown filter type ' + type;
- }
- return me.paper.defs.appendChild(fltr);
- };
-
- MapLayer.prototype.applyFilter = function(filter_id) {
- var me;
- me = this;
- return $('.' + me.id, me.paper.canvas).attr({
- filter: 'url(#' + filter_id + ')'
- });
- };
-
- MapLayer.prototype.applyTexture = function(url, filt, defCol) {
- var lp, me, _i, _len, _ref6, _results;
- if (filt == null) {
- filt = false;
- }
- if (defCol == null) {
- defCol = '#000';
- }
- me = this;
- filter.__patternFills += 1;
- _ref6 = me.paths;
- _results = [];
- for (_i = 0, _len = _ref6.length; _i < _len; _i++) {
- lp = _ref6[_i];
- if (!filt || filt(lp.data)) {
- _results.push(lp.svgPath.attr({
- fill: 'url(' + url + ')'
- }));
- } else {
- _results.push(lp.svgPath.attr('fill', defCol));
- }
- }
- return _results;
- };
+ return Conic;
- Filter = (function() {
- /* base class for all svg filter
- */
+ })(Proj);
- function Filter(params) {
- this.params = params != null ? params : {};
- }
+ LCC = (function (_super) {
- Filter.prototype.getFilter = function(id) {
- var fltr, me;
- me = this;
- fltr = me.SVG('filter', {
- id: id
- });
- me.buildFilter(fltr);
- return fltr;
- };
+ __extends(LCC, _super);
+
+ /*
+ Lambert Conformal Conic Projection (spherical)
+ */
- Filter.prototype._getFilter = function() {
- throw "not implemented";
- };
- Filter.prototype.SVG = function(el, attr) {
- var key, val;
- if (typeof el === "string") {
- el = window.document.createElementNS("http://www.w3.org/2000/svg", el);
- }
- if (attr) {
- for (key in attr) {
- val = attr[key];
- el.setAttribute(key, val);
+ LCC.title = "Lambert Conformal Conic Projection";
+
+ function LCC(opts) {
+ var abs, c, cos, cosphi, m, n, pow, secant, self, sin, sinphi, tan, _ref5;
+ self = this;
+ LCC.__super__.constructor.call(this, opts);
+ m = Math;
+ _ref5 = [m.sin, m.cos, m.abs, m.log, m.tan, m.pow], sin = _ref5[0], cos = _ref5[1], abs = _ref5[2], log = _ref5[3], tan = _ref5[4], pow = _ref5[5];
+ self.n = n = sinphi = sin(self.phi1);
+ cosphi = cos(self.phi1);
+ secant = abs(self.phi1 - self.phi2) >= 1e-10;
+ if (secant) {
+ n = log(cosphi / cos(self.phi2)) / log(tan(self.QUARTERPI + 0.5 * self.phi2) / tan(self.QUARTERPI + 0.5 * self.phi1));
+ }
+ self.c = c = cosphi * pow(tan(self.QUARTERPI + .5 * self.phi1), n) / n;
+ if (abs(abs(self.phi0) - self.HALFPI) < 1e-10) {
+ self.rho0 = 0.0;
+ } else {
+ self.rho0 = c * pow(tan(self.QUARTERPI + .5 * self.phi0), -n);
+ }
+ self.minLat = -60;
+ self.maxLat = 85;
}
- }
- return el;
- };
- return Filter;
+ LCC.prototype.project = function (lon, lat) {
+ var abs, cos, lam, lam_, m, n, phi, pow, rho, self, sin, tan, x, y, _ref5;
+ self = this;
+ phi = self.rad(lat);
+ lam = self.rad(self.clon(lon));
+ m = Math;
+ _ref5 = [m.sin, m.cos, m.abs, m.log, m.tan, m.pow], sin = _ref5[0], cos = _ref5[1], abs = _ref5[2], log = _ref5[3], tan = _ref5[4], pow = _ref5[5];
+ n = self.n;
+ if (abs(abs(phi) - self.HALFPI) < 1e-10) {
+ rho = 0.0;
+ } else {
+ rho = self.c * pow(tan(self.QUARTERPI + 0.5 * phi), -n);
+ }
+ lam_ = lam * n;
+ x = 1000 * rho * sin(lam_);
+ y = 1000 * (self.rho0 - rho * cos(lam_));
+ return [x, y * -1];
+ };
- })();
+ return LCC;
- BlurFilter = (function(_super) {
+ })(Conic);
- __extends(BlurFilter, _super);
+ __proj['lcc'] = LCC;
- function BlurFilter() {
- return BlurFilter.__super__.constructor.apply(this, arguments);
- }
+ PseudoConic = (function (_super) {
+
+ __extends(PseudoConic, _super);
- /* simple gaussian blur filter
- */
+ function PseudoConic() {
+ return PseudoConic.__super__.constructor.apply(this, arguments);
+ }
+ return PseudoConic;
- BlurFilter.prototype.buildFilter = function(fltr) {
- var SVG, blur, me;
- me = this;
- SVG = me.SVG;
- blur = SVG('feGaussianBlur', {
- stdDeviation: me.params.size || 4,
- result: 'blur'
- });
- return fltr.appendChild(blur);
- };
+ })(Conic);
+
+ /*
+ kartograph - a svg mapping library
+ Copyright (C) 2011 Gregor Aisch
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more detailme.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+ View = (function () {
+ /*
+ 2D coordinate transfomation
+ */
+
+ function View(bbox, width, height, padding, halign, valign) {
+ var me;
+ me = this;
+ me.bbox = bbox;
+ me.width = width;
+ me.padding = padding != null ? padding : 0;
+ me.halign = halign != null ? halign : 'center';
+ me.valign = valign != null ? valign : 'center';
+ me.height = height;
+ me.scale = Math.min((width - padding * 2) / bbox.width, (height - padding * 2) / bbox.height);
+ }
- return BlurFilter;
+ View.prototype.project = function (x, y) {
+ var bbox, h, me, s, w, xf, yf;
+ if (!(y != null)) {
+ y = x[1];
+ x = x[0];
+ }
+ me = this;
+ s = me.scale;
+ bbox = me.bbox;
+ h = me.height;
+ w = me.width;
+ xf = me.halign === "center" ? (w - bbox.width * s) * 0.5 : me.halign === "left" ? me.padding * s : w - (bbox.width - me.padding) * s;
+ yf = me.valign === "center" ? (h - bbox.height * s) * 0.5 : me.valign === "top" ? me.padding * s : 0;
+ x = (x - bbox.left) * s + xf;
+ y = (y - bbox.top) * s + yf;
+ return [x, y];
+ };
- })(Filter);
+ View.prototype.projectPath = function (path) {
+ var bbox, cont, contours, me, new_path, pcont, r, x, y, _i, _j, _len, _len1, _ref5, _ref6, _ref7, _ref8;
+ me = this;
+ if (path.type === "path") {
+ contours = [];
+ bbox = [99999, 99999, -99999, -99999];
+ _ref5 = path.contours;
+ for (_i = 0, _len = _ref5.length; _i < _len; _i++) {
+ pcont = _ref5[_i];
+ cont = [];
+ for (_j = 0, _len1 = pcont.length; _j < _len1; _j++) {
+ _ref6 = pcont[_j], x = _ref6[0], y = _ref6[1];
+ _ref7 = me.project(x, y), x = _ref7[0], y = _ref7[1];
+ cont.push([x, y]);
+ bbox[0] = Math.min(bbox[0], x);
+ bbox[1] = Math.min(bbox[1], y);
+ bbox[2] = Math.max(bbox[2], x);
+ bbox[3] = Math.max(bbox[3], y);
+ }
+ contours.push(cont);
+ }
+ new_path = new kartograph.geom.Path(path.type, contours, path.closed);
+ new_path._bbox = bbox;
+ return new_path;
+ } else if (path.type === "circle") {
+ _ref8 = me.project(path.x, path.y), x = _ref8[0], y = _ref8[1];
+ r = path.r * me.scale;
+ return new kartograph.geom.Circle(x, y, r);
+ }
+ };
- filter.blur = BlurFilter;
+ View.prototype.asBBox = function () {
+ var me;
+ me = this;
+ return new kartograph.BBox(0, 0, me.width, me.height);
+ };
- GlowFilter = (function(_super) {
+ return View;
- __extends(GlowFilter, _super);
+ })();
- function GlowFilter() {
- return GlowFilter.__super__.constructor.apply(this, arguments);
- }
+ View.fromXML = function (xml) {
+ /*
+ constructs a view from XML
+ */
- /* combined class for outer and inner glow filter
- */
-
-
- GlowFilter.prototype.buildFilter = function(fltr) {
- var alpha, blur, color, inner, knockout, me, rgb, strength, _ref10, _ref11, _ref6, _ref7, _ref8, _ref9;
- me = this;
- blur = (_ref6 = me.params.blur) != null ? _ref6 : 4;
- strength = (_ref7 = me.params.strength) != null ? _ref7 : 1;
- color = (_ref8 = me.params.color) != null ? _ref8 : '#D1BEB0';
- if (typeof color === 'string') {
- color = chroma.hex(color);
- }
- rgb = color.rgb;
- inner = (_ref9 = me.params.inner) != null ? _ref9 : false;
- knockout = (_ref10 = me.params.knockout) != null ? _ref10 : false;
- alpha = (_ref11 = me.params.alpha) != null ? _ref11 : 1;
- if (inner) {
- me.innerGlow(fltr, blur, strength, rgb, alpha, knockout);
- } else {
- me.outerGlow(fltr, blur, strength, rgb, alpha, knockout);
- }
+ var bbox, bbox_xml, h, pad, w;
+ w = Number(xml.getAttribute('w'));
+ h = Number(xml.getAttribute('h'));
+ pad = Number(xml.getAttribute('padding'));
+ bbox_xml = xml.getElementsByTagName('bbox')[0];
+ bbox = BBox.fromXML(bbox_xml);
+ return new kartograph.View(bbox, w, h, pad);
};
- GlowFilter.prototype.outerGlow = function(fltr, _blur, _strength, rgb, alpha, knockout) {
- var SVG, blur, comp, mat, me, merge, morph;
- me = this;
- SVG = me.SVG;
- mat = SVG('feColorMatrix', {
- "in": 'SourceGraphic',
- type: 'matrix',
- values: '0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0',
- result: 'mask'
- });
- fltr.appendChild(mat);
- if (_strength > 0) {
- morph = SVG('feMorphology', {
- "in": 'mask',
- radius: _strength,
- operator: 'dilate',
- result: 'mask'
- });
- fltr.appendChild(morph);
- }
- mat = SVG('feColorMatrix', {
- "in": 'mask',
- type: 'matrix',
- values: '0 0 0 0 ' + (rgb[0] / 255) + ' 0 0 0 0 ' + (rgb[1] / 255) + ' 0 0 0 0 ' + (rgb[2] / 255) + ' 0 0 0 1 0',
- result: 'r0'
- });
- fltr.appendChild(mat);
- blur = SVG('feGaussianBlur', {
- "in": 'r0',
- stdDeviation: _blur,
- result: 'r1'
- });
- fltr.appendChild(blur);
- comp = SVG('feComposite', {
- operator: 'out',
- "in": 'r1',
- in2: 'mask',
- result: 'comp'
- });
- fltr.appendChild(comp);
- merge = SVG('feMerge');
- if (!knockout) {
- merge.appendChild(SVG('feMergeNode', {
- 'in': 'SourceGraphic'
- }));
- }
- merge.appendChild(SVG('feMergeNode', {
- 'in': 'r1'
- }));
- return fltr.appendChild(merge);
- };
+ kartograph.View = View;
- GlowFilter.prototype.innerGlow = function(fltr, _blur, _strength, rgb, alpha, knockout) {
- var SVG, blur, comp, mat, me, merge, morph;
- me = this;
- SVG = me.SVG;
- log('innerglow');
- mat = SVG('feColorMatrix', {
- "in": 'SourceGraphic',
- type: 'matrix',
- values: '0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 500 0',
- result: 'mask'
- });
- fltr.appendChild(mat);
- morph = SVG('feMorphology', {
- "in": 'mask',
- radius: _strength,
- operator: 'erode',
- result: 'r1'
- });
- fltr.appendChild(morph);
- blur = SVG('feGaussianBlur', {
- "in": 'r1',
- stdDeviation: _blur,
- result: 'r2'
- });
- fltr.appendChild(blur);
- mat = SVG('feColorMatrix', {
- type: 'matrix',
- "in": 'r2',
- values: '1 0 0 0 ' + (rgb[0] / 255) + ' 0 1 0 0 ' + (rgb[1] / 255) + ' 0 0 1 0 ' + (rgb[2] / 255) + ' 0 0 0 -1 1',
- result: 'r3'
- });
- fltr.appendChild(mat);
- comp = SVG('feComposite', {
- operator: 'in',
- "in": 'r3',
- in2: 'mask',
- result: 'comp'
- });
- fltr.appendChild(comp);
- merge = SVG('feMerge');
- if (!knockout) {
- merge.appendChild(SVG('feMergeNode', {
- 'in': 'SourceGraphic'
- }));
- }
- merge.appendChild(SVG('feMergeNode', {
- 'in': 'comp'
- }));
- return fltr.appendChild(merge);
+ /*
+ kartograph - a svg mapping library
+ Copyright (C) 2011 Gregor Aisch
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+ kartograph.Kartograph.prototype.dotgrid = function (opts) {
+ var anim, data, data_col, data_key, delay, dly, dotgrid, dotstyle, ds, dur, f, g, gridsize, id, layer, layer_id, me, path, pathData, paths, pd, row, size, sizes, x, y, _i, _j, _k, _l, _len, _len1, _len2, _len3, _m, _n, _ref10, _ref11, _ref12, _ref13, _ref14, _ref15, _ref16, _ref17, _ref5, _ref6, _ref7, _ref8, _ref9;
+ me = this;
+ layer_id = (_ref5 = opts.layer) != null ? _ref5 : me.layerIds[me.layerIds.length - 1];
+ if (!me.layers.hasOwnProperty(layer_id)) {
+ warn('dotgrid error: layer "' + layer_id + '" not found');
+ return;
+ }
+ layer = me.layers[layer_id];
+ data = opts.data;
+ data_col = opts.value;
+ data_key = opts.key;
+ pathData = {};
+ if ((data_key != null) && __type(data) === "array") {
+ for (_i = 0, _len = data.length; _i < _len; _i++) {
+ row = data[_i];
+ id = row[data_key];
+ pathData[String(id)] = row;
+ }
+ } else {
+ for (id in data) {
+ row = data[id];
+ pathData[String(id)] = row;
+ }
+ }
+ dotstyle = (_ref6 = opts.style) != null ? _ref6 : {
+ fill: 'black',
+ stroke: 'none'
+ };
+ sizes = opts.size;
+ gridsize = (_ref7 = opts.gridsize) != null ? _ref7 : 15;
+ dotgrid = (_ref8 = layer.dotgrid) != null ? _ref8 : layer.dotgrid = {
+ gridsize: gridsize,
+ grid: []
+ };
+ if (dotgrid.gridsize !== gridsize) {
+ _ref9 = dotgrid.grid;
+ for (_j = 0, _len1 = _ref9.length; _j < _len1; _j++) {
+ g = _ref9[_j];
+ if (g.shape != null) {
+ g.shape.remove();
+ g.shape = null;
+ }
+ }
+ }
+ if (gridsize > 0) {
+ if (dotgrid.grid.length === 0) {
+ for (x = _k = 0, _ref10 = me.viewport.width; 0 <= _ref10 ? _k <= _ref10 : _k >= _ref10; x = _k += gridsize) {
+ for (y = _l = 0, _ref11 = me.viewport.height; 0 <= _ref11 ? _l <= _ref11 : _l >= _ref11; y = _l += gridsize) {
+ g = {
+ x: x + (Math.random() - 0.5) * gridsize * 0.2,
+ y: y + (Math.random() - 0.5) * gridsize * 0.2,
+ pathid: false
+ };
+ f = false;
+ _ref12 = layer.pathsById;
+ for (id in _ref12) {
+ paths = _ref12[id];
+ for (_m = 0, _len2 = paths.length; _m < _len2; _m++) {
+ path = paths[_m];
+ if (path.vpath.isInside(g.x, g.y)) {
+ f = true;
+ pd = (_ref13 = pathData[id]) != null ? _ref13 : null;
+ size = sizes(pd);
+ g.pathid = id;
+ g.shape = layer.paper.circle(g.x, g.y, 1);
+ break;
+ }
+ }
+ if (f) {
+ break;
+ }
+ }
+ dotgrid.grid.push(g);
+ }
+ }
+ }
+ _ref14 = dotgrid.grid;
+ for (_n = 0, _len3 = _ref14.length; _n < _len3; _n++) {
+ g = _ref14[_n];
+ if (g.pathid) {
+ pd = (_ref15 = pathData[g.pathid]) != null ? _ref15 : null;
+ size = sizes(pd);
+ dur = (_ref16 = opts.duration) != null ? _ref16 : 0;
+ delay = (_ref17 = opts.delay) != null ? _ref17 : 0;
+ if (__type(delay) === "function") {
+ dly = delay(pd);
+ } else {
+ dly = delay;
+ }
+ if (dur > 0 && Raphael.svg) {
+ anim = Raphael.animation({
+ r: size * 0.5
+ }, dur);
+ g.shape.animate(anim.delay(dly));
+ } else {
+ g.shape.attr({
+ r: size * 0.5
+ });
+ }
+ if (__type(dotstyle) === "function") {
+ ds = dotstyle(pd);
+ } else {
+ ds = dotstyle;
+ }
+ g.shape.attr(ds);
+ }
+ }
+ }
};
- return GlowFilter;
-
- })(Filter);
-
- filter.glow = GlowFilter;
-
- /*
- kartograph - a svg mapping library
- Copyright (C) 2011 Gregor Aisch
-
- This library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Lesser General Public
- License as published by the Free Software Foundation; either
- version 2.1 of the License, or (at your option) any later version.
-
- This library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public
- License along with this library. If not, see <http://www.gnu.org/licenses/>.
- */
-
-
- kartograph.Kartograph.prototype.addGeoPath = function(points, cmds, className) {
- var me, path, path_str;
- if (cmds == null) {
- cmds = [];
- }
- if (className == null) {
- className = '';
- }
- /* converts a set of
- */
-
- me = this;
- path_str = me.getGeoPathStr(points, cmds);
- path = me.paper.path(path_str);
- if (className !== '') {
- path.node.setAttribute('class', className);
- }
- return path;
- };
+ /*
+ kartograph - a svg mapping library
+ Copyright (C) 2011 Gregor Aisch
- kartograph.Kartograph.prototype.getGeoPathStr = function(points, cmds) {
- var cmd, i, me, path_str, pt, xy, _ref6;
- if (cmds == null) {
- cmds = [];
- }
- /* converts a set of
- */
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
- me = this;
- if (type(cmds) === 'string') {
- cmds = cmds.split("");
- }
- if (cmds.length === 0) {
- cmds.push('M');
- }
- path_str = '';
- for (i in points) {
- pt = points[i];
- cmd = (_ref6 = cmds[i]) != null ? _ref6 : 'L';
- xy = me.lonlat2xy(pt);
- if (isNaN(xy[0]) || isNaN(xy[1])) {
- continue;
- }
- path_str += cmd + xy[0] + ',' + xy[1];
- }
- return path_str;
- };
-
- kartograph.Kartograph.prototype.addGeoPolygon = function(points, className) {
- /* converts a set of
- */
-
- var cmds, i, me;
- me = this;
- cmds = ['M'];
- for (i in points) {
- cmds.push('L');
- }
- cmds.push('Z');
- return me.addGeoPath(points, cmds, className);
- };
-
- /*
- kartograph - a svg mapping library
- Copyright (C) 2011 Gregor Aisch
-
- This library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Lesser General Public
- License as published by the Free Software Foundation; either
- version 2.1 of the License, or (at your option) any later version.
-
- This library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public
- License along with this library. If not, see <http://www.gnu.org/licenses/>.
- */
-
-
- PanAndZoomControl = (function() {
-
- function PanAndZoomControl(map) {
- this.zoomOut = __bind(this.zoomOut, this);
-
- this.zoomIn = __bind(this.zoomIn, this);
-
- var c, div, mdown, me, mup, zc, zcm, zcp;
- me = this;
- me.map = map;
- c = map.container;
- div = function(className, childNodes) {
- var child, d, _i, _len;
- if (childNodes == null) {
- childNodes = [];
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+ filter = (_ref5 = kartograph.filter) != null ? _ref5 : kartograph.filter = {};
+
+ filter.__knownFilter = {};
+
+ filter.__patternFills = 0;
+
+ MapLayer.prototype.SVG = function (el, attr) {
+ var key, val;
+ if (typeof el === "string") {
+ el = window.document.createElementNS("http://www.w3.org/2000/svg", el);
}
- d = $('<div class="' + className + '" />');
- for (_i = 0, _len = childNodes.length; _i < _len; _i++) {
- child = childNodes[_i];
- d.append(child);
+ if (attr) {
+ for (key in attr) {
+ val = attr[key];
+ el.setAttribute(key, val);
+ }
}
- return d;
- };
- mdown = function(evt) {
- return $(evt.target).addClass('md');
- };
- mup = function(evt) {
- return $(evt.target).removeClass('md');
- };
- zcp = div('plus');
- zcp.mousedown(mdown);
- zcp.mouseup(mup);
- zcp.click(me.zoomIn);
- zcm = div('minus');
- zcm.mousedown(mdown);
- zcm.mouseup(mup);
- zcm.click(me.zoomOut);
- zc = div('zoom-control', [zcp, zcm]);
- c.append(zc);
- }
+ return el;
+ };
- PanAndZoomControl.prototype.zoomIn = function(evt) {
- var me;
- me = this;
- me.map.opts.zoom += 1;
- return me.map.resize();
+ kartograph.Kartograph.prototype.addFilter = function (id, type, params) {
+ var doc, fltr, me;
+ if (params == null) {
+ params = {};
+ }
+ me = this;
+ doc = window.document;
+ if (kartograph.filter[type] != null) {
+ fltr = new kartograph.filter[type](params).getFilter(id);
+ } else {
+ throw 'unknown filter type ' + type;
+ }
+ return me.paper.defs.appendChild(fltr);
};
- PanAndZoomControl.prototype.zoomOut = function(evt) {
- var me;
- me = this;
- me.map.opts.zoom -= 1;
- if (me.map.opts.zoom < 1) {
- me.map.opts.zoom = 1;
- }
- return me.map.resize();
+ MapLayer.prototype.applyFilter = function (filter_id) {
+ var me;
+ me = this;
+ return $('.' + me.id, me.paper.canvas).attr({
+ filter: 'url(#' + filter_id + ')'
+ });
};
- return PanAndZoomControl;
-
- })();
-
- /*
- kartograph - a svg mapping library
- Copyright (C) 2011 Gregor Aisch
-
- This library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Lesser General Public
- License as published by the Free Software Foundation; either
- version 2.1 of the License, or (at your option) any later version.
-
- This library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public
- License along with this library. If not, see <http://www.gnu.org/licenses/>.
- */
-
-
- Scale = (function() {
- /* scales map values to [0..1]
- */
-
- function Scale(domain, prop, filter) {
- var i, me, val, values,
- _this = this;
- if (domain == null) {
- domain = [0, 1];
- }
- if (prop == null) {
- prop = null;
- }
- if (filter == null) {
- filter = null;
- }
- this.rangedScale = __bind(this.rangedScale, this);
-
- this.scale = __bind(this.scale, this);
-
- me = this;
- values = [];
- for (i in domain) {
- if (__type(filter) === "function") {
- if (filter(domain[i]) === false) {
- continue;
- }
+ MapLayer.prototype.applyTexture = function (url, filt, defCol) {
+ var lp, me, _i, _len, _ref6, _results;
+ if (filt == null) {
+ filt = false;
}
- if (prop != null) {
- if (__type(prop) === "function") {
- val = prop(domain[i]);
- } else {
- val = domain[i][prop];
- }
- } else {
- val = domain[i];
+ if (defCol == null) {
+ defCol = '#000';
}
- if (!isNaN(val)) {
- values.push(val);
+ me = this;
+ filter.__patternFills += 1;
+ _ref6 = me.paths;
+ _results = [];
+ for (_i = 0, _len = _ref6.length; _i < _len; _i++) {
+ lp = _ref6[_i];
+ if (!filt || filt(lp.data)) {
+ _results.push(lp.svgPath.attr({
+ fill: 'url(' + url + ')'
+ }));
+ } else {
+ _results.push(lp.svgPath.attr('fill', defCol));
+ }
}
- }
- values = values.sort(function(a, b) {
- return a - b;
- });
- me.values = values;
- me._range = [0, 1];
- me.rangedScale.range = function(_r) {
- me._range = _r;
- return me.rangedScale;
- };
- }
-
- Scale.prototype.scale = function(x) {
- return x;
+ return _results;
};
- Scale.prototype.rangedScale = function(x) {
- var me, r;
- me = this;
- x = me.scale(x);
- r = me._range;
- return x * (r[1] - r[0]) + r[0];
- };
+ Filter = (function () {
+ /* base class for all svg filter
+ */
- return Scale;
+ function Filter(params) {
+ this.params = params != null ? params : {};
+ }
- })();
+ Filter.prototype.getFilter = function (id) {
+ var fltr, me;
+ me = this;
+ fltr = me.SVG('filter', {
+ id: id
+ });
+ me.buildFilter(fltr);
+ return fltr;
+ };
- LinearScale = (function(_super) {
+ Filter.prototype._getFilter = function () {
+ throw "not implemented";
+ };
- __extends(LinearScale, _super);
+ Filter.prototype.SVG = function (el, attr) {
+ var key, val;
+ if (typeof el === "string") {
+ el = window.document.createElementNS("http://www.w3.org/2000/svg", el);
+ }
+ if (attr) {
+ for (key in attr) {
+ val = attr[key];
+ el.setAttribute(key, val);
+ }
+ }
+ return el;
+ };
- function LinearScale() {
- this.scale = __bind(this.scale, this);
- return LinearScale.__super__.constructor.apply(this, arguments);
- }
+ return Filter;
- /* liniear scale
- */
+ })();
+ BlurFilter = (function (_super) {
- LinearScale.prototype.scale = function(x) {
- var me, vals;
- me = this;
- vals = me.values;
- return (x - vals[0]) / (vals[vals.length - 1] - vals[0]);
- };
+ __extends(BlurFilter, _super);
- return LinearScale;
+ function BlurFilter() {
+ return BlurFilter.__super__.constructor.apply(this, arguments);
+ }
- })(Scale);
+ /* simple gaussian blur filter
+ */
- LogScale = (function(_super) {
- __extends(LogScale, _super);
+ BlurFilter.prototype.buildFilter = function (fltr) {
+ var SVG, blur, me;
+ me = this;
+ SVG = me.SVG;
+ blur = SVG('feGaussianBlur', {
+ stdDeviation: me.params.size || 4,
+ result: 'blur'
+ });
+ return fltr.appendChild(blur);
+ };
- function LogScale() {
- this.scale = __bind(this.scale, this);
- return LogScale.__super__.constructor.apply(this, arguments);
- }
+ return BlurFilter;
- /* logatithmic scale
- */
+ })(Filter);
+ filter.blur = BlurFilter;
- LogScale.prototype.scale = function(x) {
- var me, vals;
- me = this;
- vals = me.values;
- log = Math.log;
- return (log(x) - log(vals[0])) / (log(vals[vals.length - 1]) - log(vals[0]));
- };
+ GlowFilter = (function (_super) {
- return LogScale;
+ __extends(GlowFilter, _super);
- })(Scale);
+ function GlowFilter() {
+ return GlowFilter.__super__.constructor.apply(this, arguments);
+ }
- SqrtScale = (function(_super) {
+ /* combined class for outer and inner glow filter
+ */
- __extends(SqrtScale, _super);
- function SqrtScale() {
- this.scale = __bind(this.scale, this);
- return SqrtScale.__super__.constructor.apply(this, arguments);
- }
+ GlowFilter.prototype.buildFilter = function (fltr) {
+ var alpha, blur, color, inner, knockout, me, rgb, strength, _ref10, _ref11, _ref6, _ref7, _ref8, _ref9;
+ me = this;
+ blur = (_ref6 = me.params.blur) != null ? _ref6 : 4;
+ strength = (_ref7 = me.params.strength) != null ? _ref7 : 1;
+ color = (_ref8 = me.params.color) != null ? _ref8 : '#D1BEB0';
+ if (typeof color === 'string') {
+ color = chroma.hex(color);
+ }
+ rgb = color.rgb;
+ inner = (_ref9 = me.params.inner) != null ? _ref9 : false;
+ knockout = (_ref10 = me.params.knockout) != null ? _ref10 : false;
+ alpha = (_ref11 = me.params.alpha) != null ? _ref11 : 1;
+ if (inner) {
+ me.innerGlow(fltr, blur, strength, rgb, alpha, knockout);
+ } else {
+ me.outerGlow(fltr, blur, strength, rgb, alpha, knockout);
+ }
+ };
- /* square root scale
- */
+ GlowFilter.prototype.outerGlow = function (fltr, _blur, _strength, rgb, alpha, knockout) {
+ var SVG, blur, comp, mat, me, merge, morph;
+ me = this;
+ SVG = me.SVG;
+ mat = SVG('feColorMatrix', {
+ "in": 'SourceGraphic',
+ type: 'matrix',
+ values: '0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0',
+ result: 'mask'
+ });
+ fltr.appendChild(mat);
+ if (_strength > 0) {
+ morph = SVG('feMorphology', {
+ "in": 'mask',
+ radius: _strength,
+ operator: 'dilate',
+ result: 'mask'
+ });
+ fltr.appendChild(morph);
+ }
+ mat = SVG('feColorMatrix', {
+ "in": 'mask',
+ type: 'matrix',
+ values: '0 0 0 0 ' + (rgb[0] / 255) + ' 0 0 0 0 ' + (rgb[1] / 255) + ' 0 0 0 0 ' + (rgb[2] / 255) + ' 0 0 0 1 0',
+ result: 'r0'
+ });
+ fltr.appendChild(mat);
+ blur = SVG('feGaussianBlur', {
+ "in": 'r0',
+ stdDeviation: _blur,
+ result: 'r1'
+ });
+ fltr.appendChild(blur);
+ comp = SVG('feComposite', {
+ operator: 'out',
+ "in": 'r1',
+ in2: 'mask',
+ result: 'comp'
+ });
+ fltr.appendChild(comp);
+ merge = SVG('feMerge');
+ if (!knockout) {
+ merge.appendChild(SVG('feMergeNode', {
+ 'in': 'SourceGraphic'
+ }));
+ }
+ merge.appendChild(SVG('feMergeNode', {
+ 'in': 'r1'
+ }));
+ return fltr.appendChild(merge);
+ };
+ GlowFilter.prototype.innerGlow = function (fltr, _blur, _strength, rgb, alpha, knockout) {
+ var SVG, blur, comp, mat, me, merge, morph;
+ me = this;
+ SVG = me.SVG;
+ log('innerglow');
+ mat = SVG('feColorMatrix', {
+ "in": 'SourceGraphic',
+ type: 'matrix',
+ values: '0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 500 0',
+ result: 'mask'
+ });
+ fltr.appendChild(mat);
+ morph = SVG('feMorphology', {
+ "in": 'mask',
+ radius: _strength,
+ operator: 'erode',
+ result: 'r1'
+ });
+ fltr.appendChild(morph);
+ blur = SVG('feGaussianBlur', {
+ "in": 'r1',
+ stdDeviation: _blur,
+ result: 'r2'
+ });
+ fltr.appendChild(blur);
+ mat = SVG('feColorMatrix', {
+ type: 'matrix',
+ "in": 'r2',
+ values: '1 0 0 0 ' + (rgb[0] / 255) + ' 0 1 0 0 ' + (rgb[1] / 255) + ' 0 0 1 0 ' + (rgb[2] / 255) + ' 0 0 0 -1 1',
+ result: 'r3'
+ });
+ fltr.appendChild(mat);
+ comp = SVG('feComposite', {
+ operator: 'in',
+ "in": 'r3',
+ in2: 'mask',
+ result: 'comp'
+ });
+ fltr.appendChild(comp);
+ merge = SVG('feMerge');
+ if (!knockout) {
+ merge.appendChild(SVG('feMergeNode', {
+ 'in': 'SourceGraphic'
+ }));
+ }
+ merge.appendChild(SVG('feMergeNode', {
+ 'in': 'comp'
+ }));
+ return fltr.appendChild(merge);
+ };
- SqrtScale.prototype.scale = function(x) {
- var me, vals;
- me = this;
- vals = me.values;
- return Math.sqrt((x - vals[0]) / (vals[vals.length - 1] - vals[0]));
- };
+ return GlowFilter;
- return SqrtScale;
+ })(Filter);
- })(Scale);
+ filter.glow = GlowFilter;
- QuantileScale = (function(_super) {
+ /*
+ kartograph - a svg mapping library
+ Copyright (C) 2011 Gregor Aisch
- __extends(QuantileScale, _super);
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
- function QuantileScale() {
- this.scale = __bind(this.scale, this);
- return QuantileScale.__super__.constructor.apply(this, arguments);
- }
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
- /* quantiles scale
- */
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
- QuantileScale.prototype.scale = function(x) {
- var i, k, me, nv, v, vals;
- me = this;
- vals = me.values;
- k = vals.length - 1;
- for (i in vals) {
- v = vals[Number(i)];
- nv = vals[Number(i) + 1];
- if (x === v) {
- return i / k;
+ kartograph.Kartograph.prototype.addGeoPath = function (points, cmds, className) {
+ var me, path, path_str;
+ if (cmds == null) {
+ cmds = [];
+ }
+ if (className == null) {
+ className = '';
}
- if (i < k && x > v && x < nv) {
- return i / k + (x - v) / (nv - v);
+ /* converts a set of
+ */
+
+ me = this;
+ path_str = me.getGeoPathStr(points, cmds);
+ path = me.paper.path(path_str);
+ if (className !== '') {
+ path.node.setAttribute('class', className);
}
- }
+ return path;
};
- return QuantileScale;
-
- })(Scale);
-
- kartograph.scale = {};
-
- kartograph.scale.identity = function(s) {
- return new Scale(domain, prop, filter).rangedScale;
- };
-
- kartograph.scale.linear = function(domain, prop, filter) {
- return new LinearScale(domain, prop, filter).rangedScale;
- };
-
- kartograph.scale.log = function(domain, prop, filter) {
- return new LogScale(domain, prop, filter).rangedScale;
- };
-
- kartograph.scale.sqrt = function(domain, prop, filter) {
- return new SqrtScale(domain, prop, filter).rangedScale;
- };
-
- kartograph.scale.quantile = function(domain, prop, filter) {
- return new QuantileScale(domain, prop, filter).rangedScale;
- };
-
- /*
- kartograph - a svg mapping library
- Copyright (C) 2011,2012 Gregor Aisch
-
- This library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Lesser General Public
- License as published by the Free Software Foundation; either
- version 2.1 of the License, or (at your option) any later version.
-
- This library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public
- License along with this library. If not, see <http://www.gnu.org/licenses/>.
- */
-
-
- Symbol = (function() {
- /* base class for all symbols
- */
-
- var me;
-
- me = null;
-
- function Symbol(opts) {
- me = this;
- me.location = opts.location;
- me.data = opts.data;
- me.map = opts.map;
- me.layers = opts.layers;
- me.key = opts.key;
- me.x = opts.x;
- me.y = opts.y;
- }
+ kartograph.Kartograph.prototype.getGeoPathStr = function (points, cmds) {
+ var cmd, i, me, path_str, pt, xy, _ref6;
+ if (cmds == null) {
+ cmds = [];
+ }
+ /* converts a set of
+ */
- Symbol.prototype.init = function() {
- return me;
+ me = this;
+ if (type(cmds) === 'string') {
+ cmds = cmds.split("");
+ }
+ if (cmds.length === 0) {
+ cmds.push('M');
+ }
+ path_str = '';
+ for (i in points) {
+ pt = points[i];
+ cmd = (_ref6 = cmds[i]) != null ? _ref6 : 'L';
+ xy = me.lonlat2xy(pt);
+ if (isNaN(xy[0]) || isNaN(xy[1])) {
+ continue;
+ }
+ path_str += cmd + xy[0] + ',' + xy[1];
+ }
+ return path_str;
};
- Symbol.prototype.overlaps = function(symbol) {
- return false;
- };
+ kartograph.Kartograph.prototype.addGeoPolygon = function (points, className) {
+ /* converts a set of
+ */
- Symbol.prototype.update = function(opts) {
- /* once the data has changed
- */
- return me;
+ var cmds, i, me;
+ me = this;
+ cmds = ['M'];
+ for (i in points) {
+ cmds.push('L');
+ }
+ cmds.push('Z');
+ return me.addGeoPath(points, cmds, className);
};
- Symbol.prototype.nodes = function() {
- return [];
- };
+ /*
+ kartograph - a svg mapping library
+ Copyright (C) 2011 Gregor Aisch
- Symbol.prototype.clear = function() {
- return me;
- };
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
- return Symbol;
-
- })();
-
- kartograph.Symbol = Symbol;
-
- /*
- kartograph - a svg mapping library
- Copyright (C) 2011,2012 Gregor Aisch
-
- This library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Lesser General Public
- License as published by the Free Software Foundation; either
- version 2.1 of the License, or (at your option) any later version.
-
- This library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public
- License along with this library. If not, see <http://www.gnu.org/licenses/>.
- */
-
-
- SymbolGroup = (function() {
- /* symbol groups
-
- Usage:
- new $K.SymbolGroup(options);
- map.addSymbols(options)
- */
-
- var me;
-
- me = null;
-
- function SymbolGroup(opts) {
- this._initTooltips = __bind(this._initTooltips, this);
-
- this._noverlap = __bind(this._noverlap, this);
-
- this._kMeans = __bind(this._kMeans, this);
-
- var SymbolType, d, i, id, l, layer, nid, optional, p, required, _i, _j, _k, _l, _len, _len1, _len2, _len3, _ref6, _ref7;
- me = this;
- required = ['data', 'location', 'type', 'map'];
- optional = ['filter', 'tooltip', 'click', 'delay', 'sortBy', 'clustering', 'aggregate', 'clusteringOpts', 'mouseenter', 'mouseleave'];
- for (_i = 0, _len = required.length; _i < _len; _i++) {
- p = required[_i];
- if (opts[p] != null) {
- me[p] = opts[p];
- } else {
- throw "SymbolGroup: missing argument '" + p + "'";
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+ PanAndZoomControl = (function () {
+
+ function PanAndZoomControl(map) {
+ this.zoomOut = __bind(this.zoomOut, this);
+
+ this.zoomIn = __bind(this.zoomIn, this);
+
+ var c, div, mdown, me, mup, zc, zcm, zcp;
+ me = this;
+ me.map = map;
+ c = map.container;
+ div = function (className, childNodes) {
+ var child, d, _i, _len;
+ if (childNodes == null) {
+ childNodes = [];
+ }
+ d = $('<div class="' + className + '" />');
+ for (_i = 0, _len = childNodes.length; _i < _len; _i++) {
+ child = childNodes[_i];
+ d.append(child);
+ }
+ return d;
+ };
+ mdown = function (evt) {
+ return $(evt.target).addClass('md');
+ };
+ mup = function (evt) {
+ return $(evt.target).removeClass('md');
+ };
+ zcp = div('plus');
+ zcp.mousedown(mdown);
+ zcp.mouseup(mup);
+ zcp.click(me.zoomIn);
+ zcm = div('minus');
+ zcm.mousedown(mdown);
+ zcm.mouseup(mup);
+ zcm.click(me.zoomOut);
+ zc = div('zoom-control', [zcp, zcm]);
+ c.append(zc);
}
- }
- for (_j = 0, _len1 = optional.length; _j < _len1; _j++) {
- p = optional[_j];
- if (opts[p] != null) {
- me[p] = opts[p];
+
+ PanAndZoomControl.prototype.zoomIn = function (evt) {
+ var me;
+ me = this;
+ me.map.opts.zoom += 1;
+ return me.map.resize();
+ };
+
+ PanAndZoomControl.prototype.zoomOut = function (evt) {
+ var me;
+ me = this;
+ me.map.opts.zoom -= 1;
+ if (me.map.opts.zoom < 1) {
+ me.map.opts.zoom = 1;
+ }
+ return me.map.resize();
+ };
+
+ return PanAndZoomControl;
+
+ })();
+
+ /*
+ kartograph - a svg mapping library
+ Copyright (C) 2011 Gregor Aisch
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+ Scale = (function () {
+ /* scales map values to [0..1]
+ */
+
+ function Scale(domain, prop, filter) {
+ var i, me, val, values,
+ _this = this;
+ if (domain == null) {
+ domain = [0, 1];
+ }
+ if (prop == null) {
+ prop = null;
+ }
+ if (filter == null) {
+ filter = null;
+ }
+ this.rangedScale = __bind(this.rangedScale, this);
+
+ this.scale = __bind(this.scale, this);
+
+ me = this;
+ values = [];
+ for (i in domain) {
+ if (__type(filter) === "function") {
+ if (filter(domain[i]) === false) {
+ continue;
+ }
+ }
+ if (prop != null) {
+ if (__type(prop) === "function") {
+ val = prop(domain[i]);
+ } else {
+ val = domain[i][prop];
+ }
+ } else {
+ val = domain[i];
+ }
+ if (!isNaN(val)) {
+ values.push(val);
+ }
+ }
+ values = values.sort(function (a, b) {
+ return a - b;
+ });
+ me.values = values;
+ me._range = [0, 1];
+ me.rangedScale.range = function (_r) {
+ me._range = _r;
+ return me.rangedScale;
+ };
}
- }
- SymbolType = me.type;
- if (!(SymbolType != null)) {
- warn('could not resolve symbol type', me.type);
- return;
- }
- _ref6 = SymbolType.props;
- for (_k = 0, _len2 = _ref6.length; _k < _len2; _k++) {
- p = _ref6[_k];
- if (opts[p] != null) {
- me[p] = opts[p];
+
+ Scale.prototype.scale = function (x) {
+ return x;
+ };
+
+ Scale.prototype.rangedScale = function (x) {
+ var me, r;
+ me = this;
+ x = me.scale(x);
+ r = me._range;
+ return x * (r[1] - r[0]) + r[0];
+ };
+
+ return Scale;
+
+ })();
+
+ LinearScale = (function (_super) {
+
+ __extends(LinearScale, _super);
+
+ function LinearScale() {
+ this.scale = __bind(this.scale, this);
+ return LinearScale.__super__.constructor.apply(this, arguments);
}
- }
- me.layers = {
- mapcanvas: me.map.paper
- };
- _ref7 = SymbolType.layers;
- for (_l = 0, _len3 = _ref7.length; _l < _len3; _l++) {
- l = _ref7[_l];
- nid = SymbolGroup._layerid++;
- id = 'sl_' + nid;
- if (l.type === 'svg') {
- layer = me.map.createSVGLayer(id);
- } else if (l.type === 'html') {
- layer = me.map.createHTMLLayer(id);
+
+ /* liniear scale
+ */
+
+
+ LinearScale.prototype.scale = function (x) {
+ var me, vals;
+ me = this;
+ vals = me.values;
+ return (x - vals[0]) / (vals[vals.length - 1] - vals[0]);
+ };
+
+ return LinearScale;
+
+ })(Scale);
+
+ LogScale = (function (_super) {
+
+ __extends(LogScale, _super);
+
+ function LogScale() {
+ this.scale = __bind(this.scale, this);
+ return LogScale.__super__.constructor.apply(this, arguments);
}
- me.layers[l.id] = layer;
- }
- me.symbols = [];
- for (i in me.data) {
- d = me.data[i];
- if (__type(me.filter) === "function") {
- if (me.filter(d, i)) {
- me.add(d, i);
- }
- } else {
- me.add(d, i);
+
+ /* logatithmic scale
+ */
+
+
+ LogScale.prototype.scale = function (x) {
+ var me, vals;
+ me = this;
+ vals = me.values;
+ log = Math.log;
+ return (log(x) - log(vals[0])) / (log(vals[vals.length - 1]) - log(vals[0]));
+ };
+
+ return LogScale;
+
+ })(Scale);
+
+ SqrtScale = (function (_super) {
+
+ __extends(SqrtScale, _super);
+
+ function SqrtScale() {
+ this.scale = __bind(this.scale, this);
+ return SqrtScale.__super__.constructor.apply(this, arguments);
}
- }
- me.layout();
- me.render();
- me.map.addSymbolGroup(me);
- }
- SymbolGroup.prototype.add = function(data, key) {
- /* adds a new symbol to this group
- */
-
- var SymbolType, ll, p, sprops, symbol, _i, _len, _ref6;
- me = this;
- SymbolType = me.type;
- ll = me._evaluate(me.location, data, key);
- if (__type(ll) === 'array') {
- ll = new kartograph.LonLat(ll[0], ll[1]);
- }
- sprops = {
- layers: me.layers,
- location: ll,
- data: data,
- key: key != null ? key : me.symbols.length,
- map: me.map
- };
- _ref6 = SymbolType.props;
- for (_i = 0, _len = _ref6.length; _i < _len; _i++) {
- p = _ref6[_i];
- if (me[p] != null) {
- sprops[p] = me._evaluate(me[p], data, key);
+ /* square root scale
+ */
+
+
+ SqrtScale.prototype.scale = function (x) {
+ var me, vals;
+ me = this;
+ vals = me.values;
+ return Math.sqrt((x - vals[0]) / (vals[vals.length - 1] - vals[0]));
+ };
+
+ return SqrtScale;
+
+ })(Scale);
+
+ QuantileScale = (function (_super) {
+
+ __extends(QuantileScale, _super);
+
+ function QuantileScale() {
+ this.scale = __bind(this.scale, this);
+ return QuantileScale.__super__.constructor.apply(this, arguments);
}
- }
- symbol = new SymbolType(sprops);
- me.symbols.push(symbol);
- return symbol;
+
+ /* quantiles scale
+ */
+
+
+ QuantileScale.prototype.scale = function (x) {
+ var i, k, me, nv, v, vals;
+ me = this;
+ vals = me.values;
+ k = vals.length - 1;
+ for (i in vals) {
+ v = vals[Number(i)];
+ nv = vals[Number(i) + 1];
+ if (x === v) {
+ return i / k;
+ }
+ if (i < k && x > v && x < nv) {
+ return i / k + (x - v) / (nv - v);
+ }
+ }
+ };
+
+ return QuantileScale;
+
+ })(Scale);
+
+ kartograph.scale = {};
+
+ kartograph.scale.identity = function (s) {
+ return new Scale(domain, prop, filter).rangedScale;
};
- SymbolGroup.prototype.layout = function() {
- var layer_id, ll, path, path_id, s, xy, _i, _len, _ref6, _ref7;
- _ref6 = me.symbols;
- for (_i = 0, _len = _ref6.length; _i < _len; _i++) {
- s = _ref6[_i];
- ll = s.location;
- if (__type(ll) === 'string') {
- _ref7 = ll.split('.'), layer_id = _ref7[0], path_id = _ref7[1];
- path = me.map.getLayerPath(layer_id, path_id);
- if (path != null) {
- xy = me.map.viewBC.project(path.path.centroid());
- } else {
- warn('could not find layer path ' + layer_id + '.' + path_id);
- continue;
- }
- } else {
- xy = me.map.lonlat2xy(ll);
- }
- s.x = xy[0];
- s.y = xy[1];
- }
- if (me.clustering === 'k-means') {
- me._kMeans();
- } else if (me.clustering === 'noverlap') {
- me._noverlap();
- }
- return me;
+ kartograph.scale.linear = function (domain, prop, filter) {
+ return new LinearScale(domain, prop, filter).rangedScale;
};
- SymbolGroup.prototype.render = function() {
- var node, s, sortBy, sortDir, _i, _j, _len, _len1, _ref6, _ref7, _ref8;
- me = this;
- if (me.sortBy) {
- sortDir = 'asc';
- if (__type(me.sortBy) === "string") {
- me.sortBy = me.sortBy.split(' ', 2);
- sortBy = me.sortBy[0];
- sortDir = (_ref6 = me.sortBy[1]) != null ? _ref6 : 'asc';
- }
- me.symbols = me.symbols.sort(function(a, b) {
- var m, va, vb;
- if (__type(me.sortBy) === "function") {
- va = me.sortBy(a.data, a);
- vb = me.sortBy(b.data, b);
- } else {
- va = a[sortBy];
- vb = b[sortBy];
- }
- if (va === vb) {
- return 0;
- }
- m = sortDir === 'asc' ? 1 : -1;
- if (va > vb) {
- return 1 * m;
- } else {
- return -1 * m;
- }
- });
- }
- _ref7 = me.symbols;
- for (_i = 0, _len = _ref7.length; _i < _len; _i++) {
- s = _ref7[_i];
- s.render();
- _ref8 = s.nodes();
- for (_j = 0, _len1 = _ref8.length; _j < _len1; _j++) {
- node = _ref8[_j];
- node.symbol = s;
- }
- }
- if (__type(me.tooltip) === "function") {
- me._initTooltips();
- }
- $.each(['click', 'mouseenter', 'mouseleave'], function(i, evt) {
- var _k, _len2, _ref9, _results;
- if (__type(me[evt]) === "function") {
- _ref9 = me.symbols;
- _results = [];
- for (_k = 0, _len2 = _ref9.length; _k < _len2; _k++) {
- s = _ref9[_k];
- _results.push((function() {
- var _l, _len3, _ref10, _results1,
- _this = this;
- _ref10 = s.nodes();
- _results1 = [];
- for (_l = 0, _len3 = _ref10.length; _l < _len3; _l++) {
- node = _ref10[_l];
- _results1.push($(node)[evt](function(e) {
- var tgt;
- tgt = e.target;
- while (!tgt.symbol) {
- tgt = $(tgt).parent().get(0);
- }
- e.stopPropagation();
- return me[evt](tgt.symbol.data, tgt.symbol, e);
- }));
- }
- return _results1;
- }).call(this));
- }
- return _results;
- }
- });
- return me;
+ kartograph.scale.log = function (domain, prop, filter) {
+ return new LogScale(domain, prop, filter).rangedScale;
};
- SymbolGroup.prototype.tooltips = function(cb) {
- me = this;
- me.tooltips = cb;
- me._initTooltips();
- return me;
+ kartograph.scale.sqrt = function (domain, prop, filter) {
+ return new SqrtScale(domain, prop, filter).rangedScale;
};
- SymbolGroup.prototype.remove = function(filter) {
- var id, kept, layer, s, _i, _len, _ref6, _ref7, _results;
- me = this;
- kept = [];
- _ref6 = me.symbols;
- for (_i = 0, _len = _ref6.length; _i < _len; _i++) {
- s = _ref6[_i];
- if ((filter != null) && !filter(s.data)) {
- kept.push(s);
- continue;
- }
- try {
- s.clear();
- } catch (error) {
- warn('error: symbolgroup.remove');
- }
- }
- if (!(filter != null)) {
- _ref7 = me.layers;
- _results = [];
- for (id in _ref7) {
- layer = _ref7[id];
- if (id !== "mapcanvas") {
- _results.push(layer.remove());
- } else {
- _results.push(void 0);
- }
- }
- return _results;
- } else {
- return me.symbols = kept;
- }
+ kartograph.scale.quantile = function (domain, prop, filter) {
+ return new QuantileScale(domain, prop, filter).rangedScale;
};
- SymbolGroup.prototype._evaluate = function(prop, data, key) {
- /* evaluates a property function or returns a static value
- */
+ /*
+ kartograph - a svg mapping library
+ Copyright (C) 2011,2012 Gregor Aisch
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
- var val;
- if (__type(prop) === 'function') {
- return val = prop(data, key);
- } else {
- return val = prop;
- }
- };
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
- SymbolGroup.prototype._kMeans = function() {
- /*
- layouts symbols in this group, eventually adds new 'grouped' symbols
- map.addSymbols({
- layout: "k-means",
- aggregate: function(data) {
- // compresses a list of data objects into a single one
- // typically you want to calculate the mean position, sum value or something here
- }
- })
- */
-
- var SymbolType, cluster, d, i, mean, means, out, p, s, size, sprops, _i, _j, _k, _l, _len, _len1, _len2, _len3, _ref6, _ref7, _ref8, _ref9;
- me = this;
- if ((_ref6 = me.osymbols) == null) {
- me.osymbols = me.symbols;
- }
- SymbolType = me.type;
- if (me.clusteringOpts != null) {
- size = me.clusteringOpts.size;
- }
- if (size == null) {
- size = 64;
- }
- cluster = kmeans().iterations(16).size(size);
- _ref7 = me.osymbols;
- for (_i = 0, _len = _ref7.length; _i < _len; _i++) {
- s = _ref7[_i];
- cluster.add({
- x: s.x,
- y: s.y
- });
- }
- means = cluster.means();
- out = [];
- for (_j = 0, _len1 = means.length; _j < _len1; _j++) {
- mean = means[_j];
- if (mean.size === 0) {
- continue;
- }
- d = [];
- _ref8 = mean.indices;
- for (_k = 0, _len2 = _ref8.length; _k < _len2; _k++) {
- i = _ref8[_k];
- d.push(me.osymbols[i].data);
- }
- d = me.aggregate(d);
- sprops = {
- layers: me.layers,
- location: false,
- data: d,
- map: me.map
- };
- _ref9 = SymbolType.props;
- for (_l = 0, _len3 = _ref9.length; _l < _len3; _l++) {
- p = _ref9[_l];
- if (me[p] != null) {
- sprops[p] = me._evaluate(me[p], d);
- }
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+ Symbol = (function () {
+ /* base class for all symbols
+ */
+
+ var me;
+
+ me = null;
+
+ function Symbol(opts) {
+ me = this;
+ me.location = opts.location;
+ me.data = opts.data;
+ me.map = opts.map;
+ me.layers = opts.layers;
+ me.key = opts.key;
+ me.x = opts.x;
+ me.y = opts.y;
}
- s = new SymbolType(sprops);
- s.x = mean.x;
- s.y = mean.y;
- out.push(s);
- }
- return me.symbols = out;
- };
- SymbolGroup.prototype._noverlap = function() {
- var SymbolType, b0, b1, d, dx, dy, i, intersects, iterations, l, l0, l1, maxRatio, out, p, q, r, r0, r1, rad0, rad1, s, s0, s1, sprops, symbols, t0, t1, tolerance, w, x, y, _i, _j, _k, _l, _len, _len1, _len2, _m, _n, _ref10, _ref11, _ref6, _ref7, _ref8, _ref9;
- me = this;
- if ((_ref6 = me.osymbols) == null) {
- me.osymbols = me.symbols;
- }
- iterations = 3;
- SymbolType = me.type;
- if (__indexOf.call(SymbolType.props, 'radius') < 0) {
- warn('noverlap layout only available for symbols with property "radius"');
- return;
- }
- symbols = me.osymbols.slice();
- if (me.clusteringOpts != null) {
- tolerance = me.clusteringOpts.tolerance;
- maxRatio = me.clusteringOpts.maxRatio;
- }
- if (tolerance == null) {
- tolerance = 0.05;
- }
- if (maxRatio == null) {
- maxRatio = 0.8;
- }
- for (i = _i = 0, _ref7 = iterations - 1; 0 <= _ref7 ? _i <= _ref7 : _i >= _ref7; i = 0 <= _ref7 ? ++_i : --_i) {
- symbols.sort(function(a, b) {
- return b.radius - a.radius;
- });
- l = symbols.length;
- out = [];
- for (p = _j = 0, _ref8 = l - 3; 0 <= _ref8 ? _j <= _ref8 : _j >= _ref8; p = 0 <= _ref8 ? ++_j : --_j) {
- s0 = symbols[p];
- if (!s0) {
- continue;
- }
- rad0 = s0.radius * (1 - tolerance);
- l0 = s0.x - rad0;
- r0 = s0.x + rad0;
- t0 = s0.y - rad0;
- b0 = s0.y + rad0;
- intersects = [];
- for (q = _k = _ref9 = p + 1, _ref10 = l - 2; _ref9 <= _ref10 ? _k <= _ref10 : _k >= _ref10; q = _ref9 <= _ref10 ? ++_k : --_k) {
- s1 = symbols[q];
- if (!s1) {
- continue;
- }
- rad1 = s1.radius;
- l1 = s1.x - rad1;
- r1 = s1.x + rad1;
- t1 = s1.y - rad1;
- b1 = s1.y + rad1;
- if (rad1 / s0.radius < maxRatio) {
- if (!(r0 < l1 || r1 < l0) && !(b0 < t1 || b1 < t0)) {
- dx = s1.x - s0.x;
- dy = s1.y - s0.y;
- if (dx * dx + dy * dy < (rad0 + rad1) * (rad0 + rad1)) {
- intersects.push(q);
+ Symbol.prototype.init = function () {
+ return me;
+ };
+
+ Symbol.prototype.overlaps = function (symbol) {
+ return false;
+ };
+
+ Symbol.prototype.update = function (opts) {
+ /* once the data has changed
+ */
+ return me;
+ };
+
+ Symbol.prototype.nodes = function () {
+ return [];
+ };
+
+ Symbol.prototype.clear = function () {
+ return me;
+ };
+
+ return Symbol;
+
+ })();
+
+ kartograph.Symbol = Symbol;
+
+ /*
+ kartograph - a svg mapping library
+ Copyright (C) 2011,2012 Gregor Aisch
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+ SymbolGroup = (function () {
+ /* symbol groups
+
+ Usage:
+ new $K.SymbolGroup(options);
+ map.addSymbols(options)
+ */
+
+ var me;
+
+ me = null;
+
+ function SymbolGroup(opts) {
+ this._initTooltips = __bind(this._initTooltips, this);
+
+ this._noverlap = __bind(this._noverlap, this);
+
+ this._kMeans = __bind(this._kMeans, this);
+
+ var SymbolType, d, i, id, l, layer, nid, optional, p, required, _i, _j, _k, _l, _len, _len1, _len2, _len3, _ref6, _ref7;
+ me = this;
+ required = ['data', 'location', 'type', 'map'];
+ optional = ['filter', 'tooltip', 'click', 'delay', 'sortBy', 'clustering', 'aggregate', 'clusteringOpts', 'mouseenter', 'mouseleave'];
+ for (_i = 0, _len = required.length; _i < _len; _i++) {
+ p = required[_i];
+ if (opts[p] != null) {
+ me[p] = opts[p];
+ } else {
+ throw "SymbolGroup: missing argument '" + p + "'";
}
- }
- }
- }
- if (intersects.length > 0) {
- d = [s0.data];
- r = s0.radius * s0.radius;
- for (_l = 0, _len = intersects.length; _l < _len; _l++) {
- i = intersects[_l];
- d.push(symbols[i].data);
- r += symbols[i].radius * symbols[i].radius;
- }
- d = me.aggregate(d);
- sprops = {
- layers: me.layers,
- location: false,
- data: d,
- map: me.map
+ }
+ for (_j = 0, _len1 = optional.length; _j < _len1; _j++) {
+ p = optional[_j];
+ if (opts[p] != null) {
+ me[p] = opts[p];
+ }
+ }
+ SymbolType = me.type;
+ if (!(SymbolType != null)) {
+ warn('could not resolve symbol type', me.type);
+ return;
+ }
+ _ref6 = SymbolType.props;
+ for (_k = 0, _len2 = _ref6.length; _k < _len2; _k++) {
+ p = _ref6[_k];
+ if (opts[p] != null) {
+ me[p] = opts[p];
+ }
+ }
+ me.layers = {
+ mapcanvas: me.map.paper
};
- _ref11 = SymbolType.props;
- for (_m = 0, _len1 = _ref11.length; _m < _len1; _m++) {
- p = _ref11[_m];
- if (me[p] != null) {
- sprops[p] = me._evaluate(me[p], d);
- }
- }
- s = new SymbolType(sprops);
- w = s0.radius * s0.radius / r;
- x = s0.x * w;
- y = s0.y * w;
- for (_n = 0, _len2 = intersects.length; _n < _len2; _n++) {
- i = intersects[_n];
- s1 = symbols[i];
- w = s1.radius * s1.radius / r;
- x += s1.x * w;
- y += s1.y * w;
- symbols[i] = void 0;
- }
- s.x = x;
- s.y = y;
- symbols[p] = void 0;
- out.push(s);
- } else {
- out.push(s0);
- }
+ _ref7 = SymbolType.layers;
+ for (_l = 0, _len3 = _ref7.length; _l < _len3; _l++) {
+ l = _ref7[_l];
+ nid = SymbolGroup._layerid++;
+ id = 'sl_' + nid;
+ if (l.type === 'svg') {
+ layer = me.map.createSVGLayer(id);
+ } else if (l.type === 'html') {
+ layer = me.map.createHTMLLayer(id);
+ }
+ me.layers[l.id] = layer;
+ }
+ me.symbols = [];
+ for (i in me.data) {
+ d = me.data[i];
+ if (__type(me.filter) === "function") {
+ if (me.filter(d, i)) {
+ me.add(d, i);
+ }
+ } else {
+ me.add(d, i);
+ }
+ }
+ me.layout();
+ me.render();
+ me.map.addSymbolGroup(me);
}
- symbols = out;
- }
- return me.symbols = symbols;
- };
- SymbolGroup.prototype._initTooltips = function() {
- var cfg, node, s, tooltips, tt, _i, _j, _len, _len1, _ref6, _ref7;
- me = this;
- tooltips = me.tooltip;
- _ref6 = me.symbols;
- for (_i = 0, _len = _ref6.length; _i < _len; _i++) {
- s = _ref6[_i];
- cfg = {
- position: {
- target: 'mouse',
- viewport: $(window),
- adjust: {
- x: 7,
- y: 7
- }
- },
- show: {
- delay: 20
- },
- content: {},
- events: {
- show: function(evt, api) {
- return $('.qtip').filter(function() {
- return this !== api.elements.tooltip.get(0);
- }).hide();
- }
- }
- };
- tt = tooltips(s.data, s.key);
- if (__type(tt) === "string") {
- cfg.content.text = tt;
- } else if (__type(tt) === "array") {
- cfg.content.title = tt[0];
- cfg.content.text = tt[1];
- }
- _ref7 = s.nodes();
- for (_j = 0, _len1 = _ref7.length; _j < _len1; _j++) {
- node = _ref7[_j];
- $(node).qtip(cfg);
- }
- }
- };
+ SymbolGroup.prototype.add = function (data, key) {
+ /* adds a new symbol to this group
+ */
- SymbolGroup.prototype.onResize = function() {
- var s, _i, _len, _ref6;
- me = this;
- me.layout();
- _ref6 = me.symbols;
- for (_i = 0, _len = _ref6.length; _i < _len; _i++) {
- s = _ref6[_i];
- s.update();
- }
- };
+ var SymbolType, ll, p, sprops, symbol, _i, _len, _ref6;
+ me = this;
+ SymbolType = me.type;
+ ll = me._evaluate(me.location, data, key);
+ if (__type(ll) === 'array') {
+ ll = new kartograph.LonLat(ll[0], ll[1]);
+ }
+ sprops = {
+ layers: me.layers,
+ location: ll,
+ data: data,
+ key: key != null ? key : me.symbols.length,
+ map: me.map
+ };
+ _ref6 = SymbolType.props;
+ for (_i = 0, _len = _ref6.length; _i < _len; _i++) {
+ p = _ref6[_i];
+ if (me[p] != null) {
+ sprops[p] = me._evaluate(me[p], data, key);
+ }
+ }
+ symbol = new SymbolType(sprops);
+ me.symbols.push(symbol);
+ return symbol;
+ };
- SymbolGroup.prototype.update = function(opts, duration, easing) {
- var p, s, _i, _j, _len, _len1, _ref6, _ref7;
- me = this;
- if (!(opts != null)) {
- opts = {};
- }
- _ref6 = me.symbols;
- for (_i = 0, _len = _ref6.length; _i < _len; _i++) {
- s = _ref6[_i];
- _ref7 = me.type.props;
- for (_j = 0, _len1 = _ref7.length; _j < _len1; _j++) {
- p = _ref7[_j];
- if (opts[p] != null) {
- s[p] = me._evaluate(opts[p], s.data);
- } else if (me[p] != null) {
- s[p] = me._evaluate(me[p], s.data);
- }
- }
- s.update(duration, easing);
- }
- return me;
- };
+ SymbolGroup.prototype.layout = function () {
+ var layer_id, ll, path, path_id, s, xy, _i, _len, _ref6, _ref7;
+ _ref6 = me.symbols;
+ for (_i = 0, _len = _ref6.length; _i < _len; _i++) {
+ s = _ref6[_i];
+ ll = s.location;
+ if (__type(ll) === 'string') {
+ _ref7 = ll.split('.'), layer_id = _ref7[0], path_id = _ref7[1];
+ path = me.map.getLayerPath(layer_id, path_id);
+ if (path != null) {
+ xy = me.map.viewBC.project(path.path.centroid());
+ } else {
+ warn('could not find layer path ' + layer_id + '.' + path_id);
+ continue;
+ }
+ } else {
+ xy = me.map.lonlat2xy(ll);
+ }
+ s.x = xy[0];
+ s.y = xy[1];
+ }
+ if (me.clustering === 'k-means') {
+ me._kMeans();
+ } else if (me.clustering === 'noverlap') {
+ me._noverlap();
+ }
+ return me;
+ };
- return SymbolGroup;
+ SymbolGroup.prototype.render = function () {
+ var node, s, sortBy, sortDir, _i, _j, _len, _len1, _ref6, _ref7, _ref8;
+ me = this;
+ if (me.sortBy) {
+ sortDir = 'asc';
+ if (__type(me.sortBy) === "string") {
+ me.sortBy = me.sortBy.split(' ', 2);
+ sortBy = me.sortBy[0];
+ sortDir = (_ref6 = me.sortBy[1]) != null ? _ref6 : 'asc';
+ }
+ me.symbols = me.symbols.sort(function (a, b) {
+ var m, va, vb;
+ if (__type(me.sortBy) === "function") {
+ va = me.sortBy(a.data, a);
+ vb = me.sortBy(b.data, b);
+ } else {
+ va = a[sortBy];
+ vb = b[sortBy];
+ }
+ if (va === vb) {
+ return 0;
+ }
+ m = sortDir === 'asc' ? 1 : -1;
+ if (va > vb) {
+ return 1 * m;
+ } else {
+ return -1 * m;
+ }
+ });
+ }
+ _ref7 = me.symbols;
+ for (_i = 0, _len = _ref7.length; _i < _len; _i++) {
+ s = _ref7[_i];
+ s.render();
+ _ref8 = s.nodes();
+ for (_j = 0, _len1 = _ref8.length; _j < _len1; _j++) {
+ node = _ref8[_j];
+ node.symbol = s;
+ }
+ }
+ if (__type(me.tooltip) === "function") {
+ me._initTooltips();
+ }
+ $.each(['click', 'mouseenter', 'mouseleave'], function (i, evt) {
+ var _k, _len2, _ref9, _results;
+ if (__type(me[evt]) === "function") {
+ _ref9 = me.symbols;
+ _results = [];
+ for (_k = 0, _len2 = _ref9.length; _k < _len2; _k++) {
+ s = _ref9[_k];
+ _results.push((function () {
+ var _l, _len3, _ref10, _results1,
+ _this = this;
+ _ref10 = s.nodes();
+ _results1 = [];
+ for (_l = 0, _len3 = _ref10.length; _l < _len3; _l++) {
+ node = _ref10[_l];
+ _results1.push($(node)[evt](function (e) {
+ var tgt;
+ tgt = e.target;
+ while (!tgt.symbol) {
+ tgt = $(tgt).parent().get(0);
+ }
+ e.stopPropagation();
+ return me[evt](tgt.symbol.data, tgt.symbol, e);
+ }));
+ }
+ return _results1;
+ }).call(this));
+ }
+ return _results;
+ }
+ });
+ return me;
+ };
- })();
+ SymbolGroup.prototype.tooltips = function (cb) {
+ me = this;
+ me.tooltips = cb;
+ me._initTooltips();
+ return me;
+ };
- SymbolGroup._layerid = 0;
+ SymbolGroup.prototype.remove = function (filter) {
+ var id, kept, layer, s, _i, _len, _ref6, _ref7, _results;
+ me = this;
+ kept = [];
+ _ref6 = me.symbols;
+ for (_i = 0, _len = _ref6.length; _i < _len; _i++) {
+ s = _ref6[_i];
+ if ((filter != null) && !filter(s.data)) {
+ kept.push(s);
+ continue;
+ }
+ try {
+ s.clear();
+ } catch (error) {
+ warn('error: symbolgroup.remove');
+ }
+ }
+ if (!(filter != null)) {
+ _ref7 = me.layers;
+ _results = [];
+ for (id in _ref7) {
+ layer = _ref7[id];
+ if (id !== "mapcanvas") {
+ _results.push(layer.remove());
+ } else {
+ _results.push(void 0);
+ }
+ }
+ return _results;
+ } else {
+ return me.symbols = kept;
+ }
+ };
- kartograph.SymbolGroup = SymbolGroup;
+ SymbolGroup.prototype._evaluate = function (prop, data, key) {
+ /* evaluates a property function or returns a static value
+ */
- kartograph.Kartograph.prototype.addSymbols = function(opts) {
- opts.map = this;
- return new SymbolGroup(opts);
- };
+ var val;
+ if (__type(prop) === 'function') {
+ return val = prop(data, key);
+ } else {
+ return val = prop;
+ }
+ };
-
-/*
- Copyright (c) 2010, SimpleGeo and Stamen Design
- All rights reserved.
-
- Redistribution and use in source and binary forms, with or without
- modification, are permitted provided that the following conditions are met:
- * Redistributions of source code must retain the above copyright
- notice, this list of conditions and the following disclaimer.
- * Redistributions in binary form must reproduce the above copyright
- notice, this list of conditions and the following disclaimer in the
- documentation and/or other materials provided with the distribution.
- * Neither the name of SimpleGeo nor the
- names of its contributors may be used to endorse or promote products
- derived from this software without specific prior written permission.
-
- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
- ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
- DISCLAIMED. IN NO EVENT SHALL SIMPLEGEO BE LIABLE FOR ANY
- DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
- SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-*/
+ SymbolGroup.prototype._kMeans = function () {
+ /*
+ layouts symbols in this group, eventually adds new 'grouped' symbols
+ map.addSymbols({
+ layout: "k-means",
+ aggregate: function(data) {
+ // compresses a list of data objects into a single one
+ // typically you want to calculate the mean position, sum value or something here
+ }
+ })
+ */
+
+ var SymbolType, cluster, d, i, mean, means, out, p, s, size, sprops, _i, _j, _k, _l, _len, _len1, _len2, _len3, _ref6, _ref7, _ref8, _ref9;
+ me = this;
+ if ((_ref6 = me.osymbols) == null) {
+ me.osymbols = me.symbols;
+ }
+ SymbolType = me.type;
+ if (me.clusteringOpts != null) {
+ size = me.clusteringOpts.size;
+ }
+ if (size == null) {
+ size = 64;
+ }
+ cluster = kmeans().iterations(16).size(size);
+ _ref7 = me.osymbols;
+ for (_i = 0, _len = _ref7.length; _i < _len; _i++) {
+ s = _ref7[_i];
+ cluster.add({
+ x: s.x,
+ y: s.y
+ });
+ }
+ means = cluster.means();
+ out = [];
+ for (_j = 0, _len1 = means.length; _j < _len1; _j++) {
+ mean = means[_j];
+ if (mean.size === 0) {
+ continue;
+ }
+ d = [];
+ _ref8 = mean.indices;
+ for (_k = 0, _len2 = _ref8.length; _k < _len2; _k++) {
+ i = _ref8[_k];
+ d.push(me.osymbols[i].data);
+ }
+ d = me.aggregate(d);
+ sprops = {
+ layers: me.layers,
+ location: false,
+ data: d,
+ map: me.map
+ };
+ _ref9 = SymbolType.props;
+ for (_l = 0, _len3 = _ref9.length; _l < _len3; _l++) {
+ p = _ref9[_l];
+ if (me[p] != null) {
+ sprops[p] = me._evaluate(me[p], d);
+ }
+ }
+ s = new SymbolType(sprops);
+ s.x = mean.x;
+ s.y = mean.y;
+ out.push(s);
+ }
+ return me.symbols = out;
+ };
-// k-means clustering
-function kmeans() {
- var kmeans = {},
- points = [],
- iterations = 1,
- size = 1;
-
- kmeans.size = function(x) {
- if (!arguments.length) return size;
- size = x;
- return kmeans;
- };
-
- kmeans.iterations = function(x) {
- if (!arguments.length) return iterations;
- iterations = x;
- return kmeans;
- };
-
- kmeans.add = function(x) {
- points.push(x);
- return kmeans;
- };
-
- kmeans.means = function() {
- var means = [],
- seen = {},
- n = Math.min(size, points.length);
-
- // Initialize k random (unique!) means.
- for (var i = 0, m = 2 * n; i < m; i++) {
- var p = points[~~(Math.random() * points.length)], id = p.x + "/" + p.y;
- if (!(id in seen)) {
- seen[id] = 1;
- if (means.push({x: p.x, y: p.y}) >= n) break;
- }
- }
- n = means.length;
-
- // For each iteration, create a kd-tree of the current means.
- for (var j = 0; j < iterations; j++) {
- var kd = kdtree().points(means);
-
- // Clear the state.
- for (var i = 0; i < n; i++) {
- var mean = means[i];
- mean.sumX = 0;
- mean.sumY = 0;
- mean.size = 0;
- mean.points = [];
- mean.indices = [];
- }
-
- // Find the mean closest to each point.
- for (var i = 0; i < points.length; i++) {
- var point = points[i], mean = kd.find(point);
- mean.sumX += point.x;
- mean.sumY += point.y;
- mean.size++;
- mean.points.push(point);
- mean.indices.push(i);
- }
-
- // Compute the new means.
- for (var i = 0; i < n; i++) {
- var mean = means[i];
- if (!mean.size) continue; // overlapping mean
- mean.x = mean.sumX / mean.size;
- mean.y = mean.sumY / mean.size;
- }
- }
+ SymbolGroup.prototype._noverlap = function () {
+ var SymbolType, b0, b1, d, dx, dy, i, intersects, iterations, l, l0, l1, maxRatio, out, p, q, r, r0, r1, rad0, rad1, s, s0, s1, sprops, symbols, t0, t1, tolerance, w, x, y, _i, _j, _k, _l, _len, _len1, _len2, _m, _n, _ref10, _ref11, _ref6, _ref7, _ref8, _ref9;
+ me = this;
+ if ((_ref6 = me.osymbols) == null) {
+ me.osymbols = me.symbols;
+ }
+ iterations = 3;
+ SymbolType = me.type;
+ if (__indexOf.call(SymbolType.props, 'radius') < 0) {
+ warn('noverlap layout only available for symbols with property "radius"');
+ return;
+ }
+ symbols = me.osymbols.slice();
+ if (me.clusteringOpts != null) {
+ tolerance = me.clusteringOpts.tolerance;
+ maxRatio = me.clusteringOpts.maxRatio;
+ }
+ if (tolerance == null) {
+ tolerance = 0.05;
+ }
+ if (maxRatio == null) {
+ maxRatio = 0.8;
+ }
+ for (i = _i = 0, _ref7 = iterations - 1; 0 <= _ref7 ? _i <= _ref7 : _i >= _ref7; i = 0 <= _ref7 ? ++_i : --_i) {
+ symbols.sort(function (a, b) {
+ return b.radius - a.radius;
+ });
+ l = symbols.length;
+ out = [];
+ for (p = _j = 0, _ref8 = l - 3; 0 <= _ref8 ? _j <= _ref8 : _j >= _ref8; p = 0 <= _ref8 ? ++_j : --_j) {
+ s0 = symbols[p];
+ if (!s0) {
+ continue;
+ }
+ rad0 = s0.radius * (1 - tolerance);
+ l0 = s0.x - rad0;
+ r0 = s0.x + rad0;
+ t0 = s0.y - rad0;
+ b0 = s0.y + rad0;
+ intersects = [];
+ for (q = _k = _ref9 = p + 1, _ref10 = l - 2; _ref9 <= _ref10 ? _k <= _ref10 : _k >= _ref10; q = _ref9 <= _ref10 ? ++_k : --_k) {
+ s1 = symbols[q];
+ if (!s1) {
+ continue;
+ }
+ rad1 = s1.radius;
+ l1 = s1.x - rad1;
+ r1 = s1.x + rad1;
+ t1 = s1.y - rad1;
+ b1 = s1.y + rad1;
+ if (rad1 / s0.radius < maxRatio) {
+ if (!(r0 < l1 || r1 < l0) && !(b0 < t1 || b1 < t0)) {
+ dx = s1.x - s0.x;
+ dy = s1.y - s0.y;
+ if (dx * dx + dy * dy < (rad0 + rad1) * (rad0 + rad1)) {
+ intersects.push(q);
+ }
+ }
+ }
+ }
+ if (intersects.length > 0) {
+ d = [s0.data];
+ r = s0.radius * s0.radius;
+ for (_l = 0, _len = intersects.length; _l < _len; _l++) {
+ i = intersects[_l];
+ d.push(symbols[i].data);
+ r += symbols[i].radius * symbols[i].radius;
+ }
+ d = me.aggregate(d);
+ sprops = {
+ layers: me.layers,
+ location: false,
+ data: d,
+ map: me.map
+ };
+ _ref11 = SymbolType.props;
+ for (_m = 0, _len1 = _ref11.length; _m < _len1; _m++) {
+ p = _ref11[_m];
+ if (me[p] != null) {
+ sprops[p] = me._evaluate(me[p], d);
+ }
+ }
+ s = new SymbolType(sprops);
+ w = s0.radius * s0.radius / r;
+ x = s0.x * w;
+ y = s0.y * w;
+ for (_n = 0, _len2 = intersects.length; _n < _len2; _n++) {
+ i = intersects[_n];
+ s1 = symbols[i];
+ w = s1.radius * s1.radius / r;
+ x += s1.x * w;
+ y += s1.y * w;
+ symbols[i] = void 0;
+ }
+ s.x = x;
+ s.y = y;
+ symbols[p] = void 0;
+ out.push(s);
+ } else {
+ out.push(s0);
+ }
+ }
+ symbols = out;
+ }
+ return me.symbols = symbols;
+ };
+
+ SymbolGroup.prototype._initTooltips = function () {
+ var cfg, node, s, tooltips, tt, _i, _j, _len, _len1, _ref6, _ref7;
+ me = this;
+ tooltips = me.tooltip;
+ _ref6 = me.symbols;
+ for (_i = 0, _len = _ref6.length; _i < _len; _i++) {
+ s = _ref6[_i];
+ cfg = {
+ position: {
+ target: 'mouse',
+ viewport: $(window),
+ adjust: {
+ x: 7,
+ y: 7
+ }
+ },
+ show: {
+ delay: 20
+ },
+ content: {},
+ events: {
+ show: function (evt, api) {
+ return $('.qtip').filter(function () {
+ return this !== api.elements.tooltip.get(0);
+ }).hide();
+ }
+ }
+ };
+ tt = tooltips(s.data, s.key);
+ if (__type(tt) === "string") {
+ cfg.content.text = tt;
+ } else if (__type(tt) === "array") {
+ cfg.content.title = tt[0];
+ cfg.content.text = tt[1];
+ }
+ _ref7 = s.nodes();
+ for (_j = 0, _len1 = _ref7.length; _j < _len1; _j++) {
+ node = _ref7[_j];
+ $(node).qtip(cfg);
+ }
+ }
+ };
- return means;
- };
+ SymbolGroup.prototype.onResize = function () {
+ var s, _i, _len, _ref6;
+ me = this;
+ me.layout();
+ _ref6 = me.symbols;
+ for (_i = 0, _len = _ref6.length; _i < _len; _i++) {
+ s = _ref6[_i];
+ s.update();
+ }
+ };
- return kmeans;
-}
+ SymbolGroup.prototype.update = function (opts, duration, easing) {
+ var p, s, _i, _j, _len, _len1, _ref6, _ref7;
+ me = this;
+ if (!(opts != null)) {
+ opts = {};
+ }
+ _ref6 = me.symbols;
+ for (_i = 0, _len = _ref6.length; _i < _len; _i++) {
+ s = _ref6[_i];
+ _ref7 = me.type.props;
+ for (_j = 0, _len1 = _ref7.length; _j < _len1; _j++) {
+ p = _ref7[_j];
+ if (opts[p] != null) {
+ s[p] = me._evaluate(opts[p], s.data);
+ } else if (me[p] != null) {
+ s[p] = me._evaluate(me[p], s.data);
+ }
+ }
+ s.update(duration, easing);
+ }
+ return me;
+ };
-// kd-tree
-function kdtree() {
- var kdtree = {},
- axes = ["x", "y"],
- root,
- points = [];
-
- kdtree.axes = function(x) {
- if (!arguments.length) return axes;
- axes = x;
- return kdtree;
- };
-
- kdtree.points = function(x) {
- if (!arguments.length) return points;
- points = x;
- root = null;
- return kdtree;
- };
-
- kdtree.find = function(x) {
- return find(kdtree.root(), x, root).point;
- };
-
- kdtree.root = function(x) {
- return root || (root = node(points, 0));
- };
-
- function node(points, depth) {
- if (!points.length) return;
- var axis = axes[depth % axes.length], median = points.length >> 1;
- points.sort(order(axis)); // could use random sample to speed up here
- return {
- axis: axis,
- point: points[median],
- left: node(points.slice(0, median), depth + 1),
- right: node(points.slice(median + 1), depth + 1)
- };
- }
+ return SymbolGroup;
- function distance(a, b) {
- var sum = 0;
- for (var i = 0; i < axes.length; i++) {
- var axis = axes[i], d = a[axis] - b[axis];
- sum += d * d;
- }
- return sum;
- }
-
- function order(axis) {
- return function(a, b) {
- a = a[axis];
- b = b[axis];
- return a < b ? -1 : a > b ? 1 : 0;
+ })();
+
+ SymbolGroup._layerid = 0;
+
+ kartograph.SymbolGroup = SymbolGroup;
+
+ kartograph.Kartograph.prototype.addSymbols = function (opts) {
+ opts.map = this;
+ return new SymbolGroup(opts);
};
- }
-
- function find(node, point, best) {
- if (distance(node.point, point) < distance(best.point, point)) best = node;
- if (node.left) best = find(node.left, point, best);
- if (node.right) {
- var d = node.point[node.axis] - point[node.axis];
- if (d * d < distance(best.point, point)) best = find(node.right, point, best);
- }
- return best;
- }
- return kdtree;
-}
-;
+ /*
+ Copyright (c) 2010, SimpleGeo and Stamen Design
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ * Neither the name of SimpleGeo nor the
+ names of its contributors may be used to endorse or promote products
+ derived from this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL SIMPLEGEO BE LIABLE FOR ANY
+ DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// k-means clustering
+ function kmeans() {
+ var kmeans = {},
+ points = [],
+ iterations = 1,
+ size = 1;
+
+ kmeans.size = function (x) {
+ if (!arguments.length) return size;
+ size = x;
+ return kmeans;
+ };
+
+ kmeans.iterations = function (x) {
+ if (!arguments.length) return iterations;
+ iterations = x;
+ return kmeans;
+ };
- kartograph.dorlingLayout = function(symbolgroup, iterations) {
- var A, B, apply, d, ds, dx, dy, f, i, j, nodes, r, rd, rs, _i;
- if (iterations == null) {
- iterations = 40;
+ kmeans.add = function (x) {
+ points.push(x);
+ return kmeans;
+ };
+
+ kmeans.means = function () {
+ var means = [],
+ seen = {},
+ n = Math.min(size, points.length);
+
+ // Initialize k random (unique!) means.
+ for (var i = 0, m = 2 * n; i < m; i++) {
+ var p = points[~~(Math.random() * points.length)], id = p.x + "/" + p.y;
+ if (!(id in seen)) {
+ seen[id] = 1;
+ if (means.push({x: p.x, y: p.y}) >= n) break;
+ }
+ }
+ n = means.length;
+
+ // For each iteration, create a kd-tree of the current means.
+ for (var j = 0; j < iterations; j++) {
+ var kd = kdtree().points(means);
+
+ // Clear the state.
+ for (var i = 0; i < n; i++) {
+ var mean = means[i];
+ mean.sumX = 0;
+ mean.sumY = 0;
+ mean.size = 0;
+ mean.points = [];
+ mean.indices = [];
+ }
+
+ // Find the mean closest to each point.
+ for (var i = 0; i < points.length; i++) {
+ var point = points[i], mean = kd.find(point);
+ mean.sumX += point.x;
+ mean.sumY += point.y;
+ mean.size++;
+ mean.points.push(point);
+ mean.indices.push(i);
+ }
+
+ // Compute the new means.
+ for (var i = 0; i < n; i++) {
+ var mean = means[i];
+ if (!mean.size) continue; // overlapping mean
+ mean.x = mean.sumX / mean.size;
+ mean.y = mean.sumY / mean.size;
+ }
+ }
+
+ return means;
+ };
+
+ return kmeans;
}
- nodes = [];
- $.each(symbolgroup.symbols, function(i, s) {
- return nodes.push({
- i: i,
- x: s.path.attrs.cx,
- y: s.path.attrs.cy,
- r: s.path.attrs.r
- });
- });
- nodes.sort(function(a, b) {
- return b.r - a.r;
- });
- apply = function() {
- var n, _i, _len;
- for (_i = 0, _len = nodes.length; _i < _len; _i++) {
- n = nodes[_i];
- symbolgroup.symbols[n.i].path.attr({
- cx: n.x,
- cy: n.y
- });
- }
- };
- for (r = _i = 1; 1 <= iterations ? _i <= iterations : _i >= iterations; r = 1 <= iterations ? ++_i : --_i) {
- for (i in nodes) {
- for (j in nodes) {
- if (j > i) {
- A = nodes[i];
- B = nodes[j];
- if (A.x + A.r < B.x - B.r || A.x - A.r > B.x + B.r) {
- continue;
- }
- if (A.y + A.r < B.y - B.r || A.y - A.r > B.y + B.r) {
- continue;
- }
- dx = A.x - B.x;
- dy = A.y - B.y;
- ds = dx * dx + dy * dy;
- rd = A.r + B.r;
- rs = rd * rd;
- if (ds < rs) {
- d = Math.sqrt(ds);
- f = 10 / d;
- A.x += dx * f * (1 - (A.r / rd));
- A.y += dy * f * (1 - (A.r / rd));
- B.x -= dx * f * (1 - (B.r / rd));
- B.y -= dy * f * (1 - (B.r / rd));
- }
- }
+
+// kd-tree
+ function kdtree() {
+ var kdtree = {},
+ axes = ["x", "y"],
+ root,
+ points = [];
+
+ kdtree.axes = function (x) {
+ if (!arguments.length) return axes;
+ axes = x;
+ return kdtree;
+ };
+
+ kdtree.points = function (x) {
+ if (!arguments.length) return points;
+ points = x;
+ root = null;
+ return kdtree;
+ };
+
+ kdtree.find = function (x) {
+ return find(kdtree.root(), x, root).point;
+ };
+
+ kdtree.root = function (x) {
+ return root || (root = node(points, 0));
+ };
+
+ function node(points, depth) {
+ if (!points.length) return;
+ var axis = axes[depth % axes.length], median = points.length >> 1;
+ points.sort(order(axis)); // could use random sample to speed up here
+ return {
+ axis: axis,
+ point: points[median],
+ left: node(points.slice(0, median), depth + 1),
+ right: node(points.slice(median + 1), depth + 1)
+ };
}
- }
- }
- return apply();
- };
-
- /*
- kartograph - a svg mapping library
- Copyright (C) 2011,2012 Gregor Aisch
-
- This library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Lesser General Public
- License as published by the Free Software Foundation; either
- version 2.1 of the License, or (at your option) any later version.
-
- This library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public
- License along with this library. If not, see <http://www.gnu.org/licenses/>.
- */
-
-
- Bubble = (function(_super) {
-
- __extends(Bubble, _super);
-
- function Bubble(opts) {
- this.nodes = __bind(this.nodes, this);
-
- this.clear = __bind(this.clear, this);
-
- this.update = __bind(this.update, this);
-
- this.render = __bind(this.render, this);
-
- this.overlaps = __bind(this.overlaps, this);
-
- var me, _ref6, _ref7;
- me = this;
- Bubble.__super__.constructor.call(this, opts);
- me.radius = (_ref6 = opts.radius) != null ? _ref6 : 4;
- me.style = opts.style;
- me.attrs = opts.attrs;
- me.title = opts.title;
- me["class"] = (_ref7 = opts["class"]) != null ? _ref7 : 'bubble';
- }
- Bubble.prototype.overlaps = function(bubble) {
- var dx, dy, me, r1, r2, x1, x2, y1, y2, _ref6, _ref7;
- me = this;
- _ref6 = [me.x, me.y, me.radius], x1 = _ref6[0], y1 = _ref6[1], r1 = _ref6[2];
- _ref7 = [bubble.x, bubble.y, bubble.radius], x2 = _ref7[0], y2 = _ref7[1], r2 = _ref7[2];
- if (x1 - r1 > x2 + r2 || x1 + r1 < x2 - r2 || y1 - r1 > y2 + r2 || y1 + r1 < y2 - r2) {
- return false;
- }
- dx = x1 - x2;
- dy = y1 - y2;
- if (dx * dx + dy * dy > (r1 + r2) * (r1 + r2)) {
- return false;
- }
- return true;
- };
+ function distance(a, b) {
+ var sum = 0;
+ for (var i = 0; i < axes.length; i++) {
+ var axis = axes[i], d = a[axis] - b[axis];
+ sum += d * d;
+ }
+ return sum;
+ }
- Bubble.prototype.render = function(layers) {
- var me;
- me = this;
- if (!(me.path != null)) {
- me.path = me.layers.mapcanvas.circle(me.x, me.y, me.radius);
- }
- me.update();
- me.map.applyCSS(me.path);
- return me;
- };
+ function order(axis) {
+ return function (a, b) {
+ a = a[axis];
+ b = b[axis];
+ return a < b ? -1 : a > b ? 1 : 0;
+ };
+ }
+
+ function find(node, point, best) {
+ if (distance(node.point, point) < distance(best.point, point)) best = node;
+ if (node.left) best = find(node.left, point, best);
+ if (node.right) {
+ var d = node.point[node.axis] - point[node.axis];
+ if (d * d < distance(best.point, point)) best = find(node.right, point, best);
+ }
+ return best;
+ }
+
+ return kdtree;
+ }
+ ;
- Bubble.prototype.update = function(duration, easing) {
- var attrs, me, path;
- if (duration == null) {
- duration = false;
- }
- if (easing == null) {
- easing = 'expo-out';
- }
- me = this;
- path = me.path;
- attrs = {
- cx: me.x,
- cy: me.y,
- r: me.radius
- };
- if (me.attrs != null) {
- attrs = $.extend(attrs, me.attrs);
- }
- if (!duration) {
- path.attr(attrs);
- } else {
- path.animate(attrs, duration, easing);
- }
- if (path.node != null) {
- if (me.style != null) {
- path.node.setAttribute('style', me.style);
+
+ kartograph.dorlingLayout = function (symbolgroup, iterations) {
+ var A, B, apply, d, ds, dx, dy, f, i, j, nodes, r, rd, rs, _i;
+ if (iterations == null) {
+ iterations = 40;
}
- if (me["class"] != null) {
- path.node.setAttribute('class', me["class"]);
+ nodes = [];
+ $.each(symbolgroup.symbols, function (i, s) {
+ return nodes.push({
+ i: i,
+ x: s.path.attrs.cx,
+ y: s.path.attrs.cy,
+ r: s.path.attrs.r
+ });
+ });
+ nodes.sort(function (a, b) {
+ return b.r - a.r;
+ });
+ apply = function () {
+ var n, _i, _len;
+ for (_i = 0, _len = nodes.length; _i < _len; _i++) {
+ n = nodes[_i];
+ symbolgroup.symbols[n.i].path.attr({
+ cx: n.x,
+ cy: n.y
+ });
+ }
+ };
+ for (r = _i = 1; 1 <= iterations ? _i <= iterations : _i >= iterations; r = 1 <= iterations ? ++_i : --_i) {
+ for (i in nodes) {
+ for (j in nodes) {
+ if (j > i) {
+ A = nodes[i];
+ B = nodes[j];
+ if (A.x + A.r < B.x - B.r || A.x - A.r > B.x + B.r) {
+ continue;
+ }
+ if (A.y + A.r < B.y - B.r || A.y - A.r > B.y + B.r) {
+ continue;
+ }
+ dx = A.x - B.x;
+ dy = A.y - B.y;
+ ds = dx * dx + dy * dy;
+ rd = A.r + B.r;
+ rs = rd * rd;
+ if (ds < rs) {
+ d = Math.sqrt(ds);
+ f = 10 / d;
+ A.x += dx * f * (1 - (A.r / rd));
+ A.y += dy * f * (1 - (A.r / rd));
+ B.x -= dx * f * (1 - (B.r / rd));
+ B.y -= dy * f * (1 - (B.r / rd));
+ }
+ }
+ }
+ }
}
- }
- if (me.title != null) {
- path.attr('title', me.title);
- }
- return me;
+ return apply();
};
- Bubble.prototype.clear = function() {
- var me;
- me = this;
- me.path.remove();
- return me;
- };
+ /*
+ kartograph - a svg mapping library
+ Copyright (C) 2011,2012 Gregor Aisch
- Bubble.prototype.nodes = function() {
- var me;
- me = this;
- return [me.path.node];
- };
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
- return Bubble;
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
- })(Symbol);
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
- Bubble.props = ['radius', 'style', 'class', 'title', 'attrs'];
- Bubble.layers = [];
+ Bubble = (function (_super) {
- kartograph.Bubble = Bubble;
+ __extends(Bubble, _super);
- /*
- kartograph - a svg mapping library
- Copyright (C) 2011,2012 Gregor Aisch
-
- This library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Lesser General Public
- License as published by the Free Software Foundation; either
- version 2.1 of the License, or (at your option) any later version.
-
- This library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public
- License along with this library. If not, see <http://www.gnu.org/licenses/>.
- */
+ function Bubble(opts) {
+ this.nodes = __bind(this.nodes, this);
+ this.clear = __bind(this.clear, this);
- Icon = (function(_super) {
+ this.update = __bind(this.update, this);
- __extends(Icon, _super);
+ this.render = __bind(this.render, this);
- function Icon(opts) {
- var me, _ref10, _ref6, _ref7, _ref8, _ref9;
- me = this;
- Icon.__super__.constructor.call(this, opts);
- me.icon = (_ref6 = opts.icon) != null ? _ref6 : '';
- me.offset = (_ref7 = opts.offset) != null ? _ref7 : [0, 0];
- me.iconsize = (_ref8 = opts.iconsize) != null ? _ref8 : [10, 10];
- me["class"] = (_ref9 = opts["class"]) != null ? _ref9 : '';
- me.title = (_ref10 = opts.title) != null ? _ref10 : '';
- }
+ this.overlaps = __bind(this.overlaps, this);
- Icon.prototype.render = function(layers) {
- var cont, me;
- me = this;
- cont = me.map.container;
- me.img = $('<img />');
- me.img.attr({
- src: me.icon,
- title: me.title,
- alt: me.title,
- width: me.iconsize[0],
- height: me.iconsize[1]
- });
- me.img.addClass(me["class"]);
- me.img.css({
- position: 'absolute',
- 'z-index': 1000,
- cursor: 'pointer'
- });
- me.img[0].symbol = me;
- cont.append(me.img);
- return me.update();
- };
+ var me, _ref6, _ref7;
+ me = this;
+ Bubble.__super__.constructor.call(this, opts);
+ me.radius = (_ref6 = opts.radius) != null ? _ref6 : 4;
+ me.style = opts.style;
+ me.attrs = opts.attrs;
+ me.title = opts.title;
+ me["class"] = (_ref7 = opts["class"]) != null ? _ref7 : 'bubble';
+ }
- Icon.prototype.update = function() {
- var me;
- me = this;
- return me.img.css({
- left: (me.x + me.offset[0]) + 'px',
- top: (me.y + me.offset[1]) + 'px'
- });
- };
+ Bubble.prototype.overlaps = function (bubble) {
+ var dx, dy, me, r1, r2, x1, x2, y1, y2, _ref6, _ref7;
+ me = this;
+ _ref6 = [me.x, me.y, me.radius], x1 = _ref6[0], y1 = _ref6[1], r1 = _ref6[2];
+ _ref7 = [bubble.x, bubble.y, bubble.radius], x2 = _ref7[0], y2 = _ref7[1], r2 = _ref7[2];
+ if (x1 - r1 > x2 + r2 || x1 + r1 < x2 - r2 || y1 - r1 > y2 + r2 || y1 + r1 < y2 - r2) {
+ return false;
+ }
+ dx = x1 - x2;
+ dy = y1 - y2;
+ if (dx * dx + dy * dy > (r1 + r2) * (r1 + r2)) {
+ return false;
+ }
+ return true;
+ };
- Icon.prototype.clear = function() {
- var me;
- me = this;
- me.img.remove();
- return me;
- };
+ Bubble.prototype.render = function (layers) {
+ var me;
+ me = this;
+ if (!(me.path != null)) {
+ me.path = me.layers.mapcanvas.circle(me.x, me.y, me.radius);
+ }
+ me.update();
+ me.map.applyCSS(me.path);
+ return me;
+ };
- Icon.prototype.nodes = function() {
- var me;
- me = this;
- return [me.img];
- };
+ Bubble.prototype.update = function (duration, easing) {
+ var attrs, me, path;
+ if (duration == null) {
+ duration = false;
+ }
+ if (easing == null) {
+ easing = 'expo-out';
+ }
+ me = this;
+ path = me.path;
+ attrs = {
+ cx: me.x,
+ cy: me.y,
+ r: me.radius
+ };
+ if (me.attrs != null) {
+ attrs = $.extend(attrs, me.attrs);
+ }
+ if (!duration) {
+ path.attr(attrs);
+ } else {
+ path.animate(attrs, duration, easing);
+ }
+ if (path.node != null) {
+ if (me.style != null) {
+ path.node.setAttribute('style', me.style);
+ }
+ if (me["class"] != null) {
+ path.node.setAttribute('class', me["class"]);
+ }
+ }
+ if (me.title != null) {
+ path.attr('title', me.title);
+ }
+ return me;
+ };
- return Icon;
+ Bubble.prototype.clear = function () {
+ var me;
+ me = this;
+ me.path.remove();
+ return me;
+ };
- })(kartograph.Symbol);
+ Bubble.prototype.nodes = function () {
+ var me;
+ me = this;
+ return [me.path.node];
+ };
- Icon.props = ['icon', 'offset', 'class', 'title', 'iconsize'];
+ return Bubble;
- Icon.layers = [];
+ })(Symbol);
- kartograph.Icon = Icon;
+ Bubble.props = ['radius', 'style', 'class', 'title', 'attrs'];
- /*
- kartograph - a svg mapping library
- Copyright (C) 2011,2012 Gregor Aisch
-
- This library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Lesser General Public
- License as published by the Free Software Foundation; either
- version 2.1 of the License, or (at your option) any later version.
-
- This library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public
- License along with this library. If not, see <http://www.gnu.org/licenses/>.
- */
+ Bubble.layers = [];
+ kartograph.Bubble = Bubble;
- SvgLabel = (function(_super) {
+ /*
+ kartograph - a svg mapping library
+ Copyright (C) 2011,2012 Gregor Aisch
- __extends(SvgLabel, _super);
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
- function SvgLabel(opts) {
- var me, _ref6, _ref7, _ref8, _ref9;
- me = this;
- SvgLabel.__super__.constructor.call(this, opts);
- me.text = (_ref6 = opts.text) != null ? _ref6 : '';
- me.style = (_ref7 = opts.style) != null ? _ref7 : '';
- me["class"] = (_ref8 = opts["class"]) != null ? _ref8 : '';
- me.offset = (_ref9 = opts.offset) != null ? _ref9 : [0, 0];
- }
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
- SvgLabel.prototype.render = function(layers) {
- var lbl, me;
- me = this;
- me.lbl = lbl = me.layers.mapcanvas.text(me.x, me.y, me.text);
- me.update();
- return me;
- };
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
- SvgLabel.prototype.update = function() {
- var me;
- me = this;
- me.lbl.attr({
- x: me.x + me.offset[0],
- y: me.y + me.offset[1]
- });
- me.lbl.node.setAttribute('style', me.style);
- return me.lbl.node.setAttribute('class', me["class"]);
- };
- SvgLabel.prototype.clear = function() {
- var me;
- me = this;
- me.lbl.remove();
- return me;
- };
+ Icon = (function (_super) {
- SvgLabel.prototype.nodes = function() {
- var me;
- me = this;
- return [me.lbl.node];
- };
+ __extends(Icon, _super);
- return SvgLabel;
+ function Icon(opts) {
+ var me, _ref10, _ref6, _ref7, _ref8, _ref9;
+ me = this;
+ Icon.__super__.constructor.call(this, opts);
+ me.icon = (_ref6 = opts.icon) != null ? _ref6 : '';
+ me.offset = (_ref7 = opts.offset) != null ? _ref7 : [0, 0];
+ me.iconsize = (_ref8 = opts.iconsize) != null ? _ref8 : [10, 10];
+ me["class"] = (_ref9 = opts["class"]) != null ? _ref9 : '';
+ me.title = (_ref10 = opts.title) != null ? _ref10 : '';
+ }
- })(kartograph.Symbol);
+ Icon.prototype.render = function (layers) {
+ var cont, me;
+ me = this;
+ cont = me.map.container;
+ me.img = $('<img />');
+ me.img.attr({
+ src: me.icon,
+ title: me.title,
+ alt: me.title,
+ width: me.iconsize[0],
+ height: me.iconsize[1]
+ });
+ me.img.addClass(me["class"]);
+ me.img.css({
+ position: 'absolute',
+ 'z-index': 1000,
+ cursor: 'pointer'
+ });
+ me.img[0].symbol = me;
+ cont.append(me.img);
+ return me.update();
+ };
- SvgLabel.props = ['text', 'style', 'class', 'offset'];
+ Icon.prototype.update = function () {
+ var me;
+ me = this;
+ return me.img.css({
+ left: (me.x + me.offset[0]) + 'px',
+ top: (me.y + me.offset[1]) + 'px'
+ });
+ };
- SvgLabel.layers = [];
+ Icon.prototype.clear = function () {
+ var me;
+ me = this;
+ me.img.remove();
+ return me;
+ };
- kartograph.Label = SvgLabel;
+ Icon.prototype.nodes = function () {
+ var me;
+ me = this;
+ return [me.img];
+ };
- HtmlLabel = (function(_super) {
+ return Icon;
- __extends(HtmlLabel, _super);
+ })(kartograph.Symbol);
- function HtmlLabel(opts) {
- var me, _ref6, _ref7, _ref8;
- me = this;
- HtmlLabel.__super__.constructor.call(this, opts);
- me.text = (_ref6 = opts.text) != null ? _ref6 : '';
- me.style = (_ref7 = opts.style) != null ? _ref7 : '';
- me["class"] = (_ref8 = opts["class"]) != null ? _ref8 : '';
- }
+ Icon.props = ['icon', 'offset', 'class', 'title', 'iconsize'];
- HtmlLabel.prototype.render = function(layers) {
- var l, lbl, me;
- me = this;
- l = $('<div>' + me.text + '</div>');
- l.css({
- width: '50px',
- position: 'absolute',
- left: '-25px',
- 'text-align': 'center'
- });
- me.lbl = lbl = $('<div class="label" />');
- lbl.append(l);
- me.layers.lbl.append(lbl);
- l.css({
- height: l.height() + 'px',
- top: (l.height() * -.4) + 'px'
- });
- me.update();
- return me;
- };
+ Icon.layers = [];
- HtmlLabel.prototype.update = function() {
- var me;
- me = this;
- return me.lbl.css({
- position: 'absolute',
- left: me.x + 'px',
- top: me.y + 'px'
- });
- };
+ kartograph.Icon = Icon;
- HtmlLabel.prototype.clear = function() {
- var me;
- me = this;
- me.lbl.remove();
- return me;
- };
+ /*
+ kartograph - a svg mapping library
+ Copyright (C) 2011,2012 Gregor Aisch
- HtmlLabel.prototype.nodes = function() {
- var me;
- me = this;
- return [me.lbl[0]];
- };
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
- return HtmlLabel;
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
- })(kartograph.Symbol);
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
- HtmlLabel.props = ['text', 'style', 'class'];
- HtmlLabel.layers = [
- {
- id: 'lbl',
- type: 'html'
- }
- ];
+ SvgLabel = (function (_super) {
- kartograph.HtmlLabel = HtmlLabel;
+ __extends(SvgLabel, _super);
- /*
- kartograph - a svg mapping library
- Copyright (C) 2011,2012 Gregor Aisch
-
- This library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Lesser General Public
- License as published by the Free Software Foundation; either
- version 2.1 of the License, or (at your option) any later version.
-
- This library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public
- License along with this library. If not, see <http://www.gnu.org/licenses/>.
- */
+ function SvgLabel(opts) {
+ var me, _ref6, _ref7, _ref8, _ref9;
+ me = this;
+ SvgLabel.__super__.constructor.call(this, opts);
+ me.text = (_ref6 = opts.text) != null ? _ref6 : '';
+ me.style = (_ref7 = opts.style) != null ? _ref7 : '';
+ me["class"] = (_ref8 = opts["class"]) != null ? _ref8 : '';
+ me.offset = (_ref9 = opts.offset) != null ? _ref9 : [0, 0];
+ }
+ SvgLabel.prototype.render = function (layers) {
+ var lbl, me;
+ me = this;
+ me.lbl = lbl = me.layers.mapcanvas.text(me.x, me.y, me.text);
+ me.update();
+ return me;
+ };
- LabeledBubble = (function(_super) {
+ SvgLabel.prototype.update = function () {
+ var me;
+ me = this;
+ me.lbl.attr({
+ x: me.x + me.offset[0],
+ y: me.y + me.offset[1]
+ });
+ me.lbl.node.setAttribute('style', me.style);
+ return me.lbl.node.setAttribute('class', me["class"]);
+ };
- __extends(LabeledBubble, _super);
+ SvgLabel.prototype.clear = function () {
+ var me;
+ me = this;
+ me.lbl.remove();
+ return me;
+ };
- function LabeledBubble(opts) {
- this.nodes = __bind(this.nodes, this);
+ SvgLabel.prototype.nodes = function () {
+ var me;
+ me = this;
+ return [me.lbl.node];
+ };
- this.clear = __bind(this.clear, this);
+ return SvgLabel;
- this.update = __bind(this.update, this);
+ })(kartograph.Symbol);
- this.render = __bind(this.render, this);
+ SvgLabel.props = ['text', 'style', 'class', 'offset'];
- var me, _ref6, _ref7;
- me = this;
- LabeledBubble.__super__.constructor.call(this, opts);
- me.labelattrs = (_ref6 = opts.labelattrs) != null ? _ref6 : {};
- me.buffer = opts.buffer;
- me.center = (_ref7 = opts.center) != null ? _ref7 : true;
- }
+ SvgLabel.layers = [];
- LabeledBubble.prototype.render = function(layers) {
- var me;
- me = this;
- if ((me.title != null) && String(me.title).trim() !== '') {
- if (me.buffer) {
- me.bufferlabel = me.layers.mapcanvas.text(me.x, me.y, me.title);
- }
- me.label = me.layers.mapcanvas.text(me.x, me.y, me.title);
- }
- LabeledBubble.__super__.render.call(this, layers);
- return me;
- };
+ kartograph.Label = SvgLabel;
+
+ HtmlLabel = (function (_super) {
- LabeledBubble.prototype.update = function(duration, easing) {
- var attrs, me, vp, x, y;
- if (duration == null) {
- duration = false;
- }
- if (easing == null) {
- easing = 'expo-out';
- }
- me = this;
- LabeledBubble.__super__.update.call(this, duration, easing);
- if (me.label != null) {
- vp = me.map.viewport;
- attrs = $.extend({}, me.labelattrs);
- x = me.x;
- y = me.y;
- if (me.center) {
- y -= 0;
- } else if (x > vp.width * 0.5) {
- attrs['text-anchor'] = 'end';
- x -= me.radius + 5;
- } else if (x < vp.width * 0.5) {
- attrs['text-anchor'] = 'start';
- x += me.radius + 5;
+ __extends(HtmlLabel, _super);
+
+ function HtmlLabel(opts) {
+ var me, _ref6, _ref7, _ref8;
+ me = this;
+ HtmlLabel.__super__.constructor.call(this, opts);
+ me.text = (_ref6 = opts.text) != null ? _ref6 : '';
+ me.style = (_ref7 = opts.style) != null ? _ref7 : '';
+ me["class"] = (_ref8 = opts["class"]) != null ? _ref8 : '';
}
- attrs['x'] = x;
- attrs['y'] = y;
- if (me.buffer) {
- me.bufferlabel.attr(attrs);
- me.bufferlabel.attr({
- stroke: '#fff',
- fill: '#fff',
- 'stroke-linejoin': 'round',
- 'stroke-linecap': 'round',
- 'stroke-width': 6
- });
+
+ HtmlLabel.prototype.render = function (layers) {
+ var l, lbl, me;
+ me = this;
+ l = $('<div>' + me.text + '</div>');
+ l.css({
+ width: '50px',
+ position: 'absolute',
+ left: '-25px',
+ 'text-align': 'center'
+ });
+ me.lbl = lbl = $('<div class="label" />');
+ lbl.append(l);
+ me.layers.lbl.append(lbl);
+ l.css({
+ height: l.height() + 'px',
+ top: (l.height() * -.4) + 'px'
+ });
+ me.update();
+ return me;
+ };
+
+ HtmlLabel.prototype.update = function () {
+ var me;
+ me = this;
+ return me.lbl.css({
+ position: 'absolute',
+ left: me.x + 'px',
+ top: me.y + 'px'
+ });
+ };
+
+ HtmlLabel.prototype.clear = function () {
+ var me;
+ me = this;
+ me.lbl.remove();
+ return me;
+ };
+
+ HtmlLabel.prototype.nodes = function () {
+ var me;
+ me = this;
+ return [me.lbl[0]];
+ };
+
+ return HtmlLabel;
+
+ })(kartograph.Symbol);
+
+ HtmlLabel.props = ['text', 'style', 'class'];
+
+ HtmlLabel.layers = [
+ {
+ id: 'lbl',
+ type: 'html'
}
- me.label.attr(attrs);
- me.label.toFront();
- }
- return me;
- };
+ ];
- LabeledBubble.prototype.clear = function() {
- var me;
- me = this;
- return LabeledBubble.__super__.clear.apply(this, arguments);
- };
+ kartograph.HtmlLabel = HtmlLabel;
- LabeledBubble.prototype.nodes = function() {
- var me, nodes;
- me = this;
- nodes = LabeledBubble.__super__.nodes.apply(this, arguments);
- if (me.label) {
- nodes.push(me.label.node);
- }
- if (me.bufferlabel) {
- nodes.push(me.bufferlabel.node);
- }
- return nodes;
- };
+ /*
+ kartograph - a svg mapping library
+ Copyright (C) 2011,2012 Gregor Aisch
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
- return LabeledBubble;
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
- })(Bubble);
- LabeledBubble.props = ['radius', 'style', 'class', 'title', 'labelattrs', 'buffer', 'center', 'attrs'];
+ LabeledBubble = (function (_super) {
- LabeledBubble.layers = [];
+ __extends(LabeledBubble, _super);
- kartograph.LabeledBubble = LabeledBubble;
+ function LabeledBubble(opts) {
+ this.nodes = __bind(this.nodes, this);
- /*
- kartograph - a svg mapping library
- Copyright (C) 2011,2012 Gregor Aisch
-
- This library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Lesser General Public
- License as published by the Free Software Foundation; either
- version 2.1 of the License, or (at your option) any later version.
-
- This library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public
- License along with this library. If not, see <http://www.gnu.org/licenses/>.
- */
+ this.clear = __bind(this.clear, this);
+ this.update = __bind(this.update, this);
+
+ this.render = __bind(this.render, this);
+
+ var me, _ref6, _ref7;
+ me = this;
+ LabeledBubble.__super__.constructor.call(this, opts);
+ me.labelattrs = (_ref6 = opts.labelattrs) != null ? _ref6 : {};
+ me.buffer = opts.buffer;
+ me.center = (_ref7 = opts.center) != null ? _ref7 : true;
+ }
+
+ LabeledBubble.prototype.render = function (layers) {
+ var me;
+ me = this;
+ if ((me.title != null) && String(me.title).trim() !== '') {
+ if (me.buffer) {
+ me.bufferlabel = me.layers.mapcanvas.text(me.x, me.y, me.title);
+ }
+ me.label = me.layers.mapcanvas.text(me.x, me.y, me.title);
+ }
+ LabeledBubble.__super__.render.call(this, layers);
+ return me;
+ };
- PieChart = (function(_super) {
- var me;
+ LabeledBubble.prototype.update = function (duration, easing) {
+ var attrs, me, vp, x, y;
+ if (duration == null) {
+ duration = false;
+ }
+ if (easing == null) {
+ easing = 'expo-out';
+ }
+ me = this;
+ LabeledBubble.__super__.update.call(this, duration, easing);
+ if (me.label != null) {
+ vp = me.map.viewport;
+ attrs = $.extend({}, me.labelattrs);
+ x = me.x;
+ y = me.y;
+ if (me.center) {
+ y -= 0;
+ } else if (x > vp.width * 0.5) {
+ attrs['text-anchor'] = 'end';
+ x -= me.radius + 5;
+ } else if (x < vp.width * 0.5) {
+ attrs['text-anchor'] = 'start';
+ x += me.radius + 5;
+ }
+ attrs['x'] = x;
+ attrs['y'] = y;
+ if (me.buffer) {
+ me.bufferlabel.attr(attrs);
+ me.bufferlabel.attr({
+ stroke: '#fff',
+ fill: '#fff',
+ 'stroke-linejoin': 'round',
+ 'stroke-linecap': 'round',
+ 'stroke-width': 6
+ });
+ }
+ me.label.attr(attrs);
+ me.label.toFront();
+ }
+ return me;
+ };
- __extends(PieChart, _super);
+ LabeledBubble.prototype.clear = function () {
+ var me;
+ me = this;
+ return LabeledBubble.__super__.clear.apply(this, arguments);
+ };
+
+ LabeledBubble.prototype.nodes = function () {
+ var me, nodes;
+ me = this;
+ nodes = LabeledBubble.__super__.nodes.apply(this, arguments);
+ if (me.label) {
+ nodes.push(me.label.node);
+ }
+ if (me.bufferlabel) {
+ nodes.push(me.bufferlabel.node);
+ }
+ return nodes;
+ };
+
+ return LabeledBubble;
+
+ })(Bubble);
+
+ LabeledBubble.props = ['radius', 'style', 'class', 'title', 'labelattrs', 'buffer', 'center', 'attrs'];
+
+ LabeledBubble.layers = [];
+
+ kartograph.LabeledBubble = LabeledBubble;
/*
- usage:
- new SymbolMap({
- map: map,
- radius: 10
- data: [25,75],
- colors: ['red', 'blue'],
- titles: ['red pie', 'blue pie']
- })
- */
-
-
- me = null;
-
- function PieChart(opts) {
- var _base2, _ref10, _ref11, _ref12, _ref13, _ref14, _ref6, _ref7, _ref8, _ref9;
- me = this;
- PieChart.__super__.constructor.call(this, opts);
- me.radius = (_ref6 = opts.radius) != null ? _ref6 : 4;
- me.styles = (_ref7 = opts.styles) != null ? _ref7 : '';
- me.colors = (_ref8 = opts.colors) != null ? _ref8 : ['#3cc', '#c3c', '#33c', '#cc3'];
- me.titles = (_ref9 = opts.titles) != null ? _ref9 : ['', '', '', '', ''];
- me.values = (_ref10 = opts.values) != null ? _ref10 : [];
- me.border = (_ref11 = opts.border) != null ? _ref11 : false;
- me.borderWidth = (_ref12 = opts.borderWidth) != null ? _ref12 : 2;
- me["class"] = (_ref13 = opts["class"]) != null ? _ref13 : 'piechart';
- if ((_ref14 = (_base2 = Raphael.fn).pieChart) == null) {
- _base2.pieChart = drawPieChart;
- }
- }
+ kartograph - a svg mapping library
+ Copyright (C) 2011,2012 Gregor Aisch
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+ PieChart = (function (_super) {
+ var me;
+
+ __extends(PieChart, _super);
+
+ /*
+ usage:
+ new SymbolMap({
+ map: map,
+ radius: 10
+ data: [25,75],
+ colors: ['red', 'blue'],
+ titles: ['red pie', 'blue pie']
+ })
+ */
+
+
+ me = null;
+
+ function PieChart(opts) {
+ var _base2, _ref10, _ref11, _ref12, _ref13, _ref14, _ref6, _ref7, _ref8, _ref9;
+ me = this;
+ PieChart.__super__.constructor.call(this, opts);
+ me.radius = (_ref6 = opts.radius) != null ? _ref6 : 4;
+ me.styles = (_ref7 = opts.styles) != null ? _ref7 : '';
+ me.colors = (_ref8 = opts.colors) != null ? _ref8 : ['#3cc', '#c3c', '#33c', '#cc3'];
+ me.titles = (_ref9 = opts.titles) != null ? _ref9 : ['', '', '', '', ''];
+ me.values = (_ref10 = opts.values) != null ? _ref10 : [];
+ me.border = (_ref11 = opts.border) != null ? _ref11 : false;
+ me.borderWidth = (_ref12 = opts.borderWidth) != null ? _ref12 : 2;
+ me["class"] = (_ref13 = opts["class"]) != null ? _ref13 : 'piechart';
+ if ((_ref14 = (_base2 = Raphael.fn).pieChart) == null) {
+ _base2.pieChart = drawPieChart;
+ }
+ }
- PieChart.prototype.overlaps = function(bubble) {
- var dx, dy, r1, r2, x1, x2, y1, y2, _ref6, _ref7;
- _ref6 = [me.x, me.y, me.radius], x1 = _ref6[0], y1 = _ref6[1], r1 = _ref6[2];
- _ref7 = [bubble.x, bubble.y, bubble.radius], x2 = _ref7[0], y2 = _ref7[1], r2 = _ref7[2];
- if (x1 - r1 > x2 + r2 || x1 + r1 < x2 - r2 || y1 - r1 > y2 + r2 || y1 + r1 < y2 - r2) {
- return false;
- }
- dx = x1 - x2;
- dy = y1 - y2;
- if (dx * dx + dy * dy > (r1 + r2) * (r1 + r2)) {
- return false;
- }
- return true;
- };
+ PieChart.prototype.overlaps = function (bubble) {
+ var dx, dy, r1, r2, x1, x2, y1, y2, _ref6, _ref7;
+ _ref6 = [me.x, me.y, me.radius], x1 = _ref6[0], y1 = _ref6[1], r1 = _ref6[2];
+ _ref7 = [bubble.x, bubble.y, bubble.radius], x2 = _ref7[0], y2 = _ref7[1], r2 = _ref7[2];
+ if (x1 - r1 > x2 + r2 || x1 + r1 < x2 - r2 || y1 - r1 > y2 + r2 || y1 + r1 < y2 - r2) {
+ return false;
+ }
+ dx = x1 - x2;
+ dy = y1 - y2;
+ if (dx * dx + dy * dy > (r1 + r2) * (r1 + r2)) {
+ return false;
+ }
+ return true;
+ };
- PieChart.prototype.render = function(layers) {
- var bg;
- me = this;
- if (me.border != null) {
- bg = me.layers.mapcanvas.circle(me.x, me.y, me.radius + me.borderWidth).attr({
- stroke: 'none',
- fill: me.border
- });
- }
- me.chart = me.layers.mapcanvas.pieChart(me.x, me.y, me.radius, me.values, me.titles, me.colors, "none");
- me.chart.push(bg);
- return me;
- };
+ PieChart.prototype.render = function (layers) {
+ var bg;
+ me = this;
+ if (me.border != null) {
+ bg = me.layers.mapcanvas.circle(me.x, me.y, me.radius + me.borderWidth).attr({
+ stroke: 'none',
+ fill: me.border
+ });
+ }
+ me.chart = me.layers.mapcanvas.pieChart(me.x, me.y, me.radius, me.values, me.titles, me.colors, "none");
+ me.chart.push(bg);
+ return me;
+ };
- PieChart.prototype.update = function(opts) {
- var path;
- return;
- me.path.attr({
- x: me.x,
- y: me.y,
- r: me.radius
- });
- path = me.path;
- path.node.setAttribute('style', me.styles[0]);
- path.node.setAttribute('class', me["class"]);
- if (me.title != null) {
- path.attr('title', me.titles[0]);
- }
- return me;
- };
+ PieChart.prototype.update = function (opts) {
+ var path;
+ return;
+ me.path.attr({
+ x: me.x,
+ y: me.y,
+ r: me.radius
+ });
+ path = me.path;
+ path.node.setAttribute('style', me.styles[0]);
+ path.node.setAttribute('class', me["class"]);
+ if (me.title != null) {
+ path.attr('title', me.titles[0]);
+ }
+ return me;
+ };
- PieChart.prototype.clear = function() {
- var p, _i, _len, _ref6;
- me = this;
- _ref6 = me.chart;
- for (_i = 0, _len = _ref6.length; _i < _len; _i++) {
- p = _ref6[_i];
- p.remove();
- }
- return me;
- };
+ PieChart.prototype.clear = function () {
+ var p, _i, _len, _ref6;
+ me = this;
+ _ref6 = me.chart;
+ for (_i = 0, _len = _ref6.length; _i < _len; _i++) {
+ p = _ref6[_i];
+ p.remove();
+ }
+ return me;
+ };
- PieChart.prototype.nodes = function() {
- var el, _i, _len, _ref6, _results;
- _ref6 = me.chart;
- _results = [];
- for (_i = 0, _len = _ref6.length; _i < _len; _i++) {
- el = _ref6[_i];
- _results.push(el.node);
- }
- return _results;
- };
+ PieChart.prototype.nodes = function () {
+ var el, _i, _len, _ref6, _results;
+ _ref6 = me.chart;
+ _results = [];
+ for (_i = 0, _len = _ref6.length; _i < _len; _i++) {
+ el = _ref6[_i];
+ _results.push(el.node);
+ }
+ return _results;
+ };
- return PieChart;
+ return PieChart;
- })(Symbol);
+ })(Symbol);
- PieChart.props = ['radius', 'values', 'styles', 'class', 'titles', 'colors', 'border', 'borderWidth'];
+ PieChart.props = ['radius', 'values', 'styles', 'class', 'titles', 'colors', 'border', 'borderWidth'];
- PieChart.layers = [];
+ PieChart.layers = [];
- kartograph.PieChart = PieChart;
+ kartograph.PieChart = PieChart;
- /*
- pie chart extension for RaphaelJS
- */
+ /*
+ pie chart extension for RaphaelJS
+ */
- drawPieChart = function(cx, cy, r, values, labels, colors, stroke) {
- var angle, chart, i, paper, process, rad, sector, total, v, _i, _len;
- if (isNaN(cx) || isNaN(cy) || isNaN(r)) {
- return [];
- }
- paper = this;
- rad = Math.PI / 180;
- chart = paper.set();
- sector = function(cx, cy, r, startAngle, endAngle, params) {
- var x1, x2, y1, y2;
- x1 = cx + r * Math.cos(-startAngle * rad);
- x2 = cx + r * Math.cos(-endAngle * rad);
- y1 = cy + r * Math.sin(-startAngle * rad);
- y2 = cy + r * Math.sin(-endAngle * rad);
- return paper.path(["M", cx, cy, "L", x1, y1, "A", r, r, 0, +(endAngle - startAngle > 180), 0, x2, y2, "z"]).attr(params);
- };
- angle = -270;
- total = 0;
- process = function(j) {
- var angleplus, color, delta, ms, p, popangle, value;
- value = values[j];
- angleplus = 360 * value / total;
- popangle = angle + (angleplus * 0.5);
- color = colors[j];
- ms = 500;
- delta = 30;
- p = sector(cx, cy, r, angle, angle + angleplus, {
- fill: color,
- stroke: stroke,
- 'stroke-width': 1
- });
- p.mouseover(function() {
- p.stop().animate({
- transform: "s1.1 1.1 " + cx + " " + cy
- }, ms, "elastic");
- });
- p.mouseout(function() {
- p.stop().animate({
- transform: ""
- }, ms, "elastic");
- });
- angle += angleplus;
- chart.push(p);
- };
- for (_i = 0, _len = values.length; _i < _len; _i++) {
- v = values[_i];
- total += v;
- }
- for (i in values) {
- process(i);
- }
- return chart;
- };
-
- /*
- kartograph - a svg mapping library
- Copyright (C) 2011,2012 Gregor Aisch
-
- This library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Lesser General Public
- License as published by the Free Software Foundation; either
- version 2.1 of the License, or (at your option) any later version.
-
- This library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public
- License along with this library. If not, see <http://www.gnu.org/licenses/>.
- */
-
-
-
-
-drawStackedBars = function (cx, cy, w, h, values, labels, colors, stroke) {
- var paper = this,
- chart = this.set();
- function bar(x, y, w, h, params) {
- return paper.rect(x,y,w,h).attr(params);
- }
- var yo = 0,
- total = 0,
+ drawPieChart = function (cx, cy, r, values, labels, colors, stroke) {
+ var angle, chart, i, paper, process, rad, sector, total, v, _i, _len;
+ if (isNaN(cx) || isNaN(cy) || isNaN(r)) {
+ return [];
+ }
+ paper = this;
+ rad = Math.PI / 180;
+ chart = paper.set();
+ sector = function (cx, cy, r, startAngle, endAngle, params) {
+ var x1, x2, y1, y2;
+ x1 = cx + r * Math.cos(-startAngle * rad);
+ x2 = cx + r * Math.cos(-endAngle * rad);
+ y1 = cy + r * Math.sin(-startAngle * rad);
+ y2 = cy + r * Math.sin(-endAngle * rad);
+ return paper.path(["M", cx, cy, "L", x1, y1, "A", r, r, 0, +(endAngle - startAngle > 180), 0, x2, y2, "z"]).attr(params);
+ };
+ angle = -270;
+ total = 0;
process = function (j) {
- var value = values[j],
- bh = h * value / total,
- x = cx - w*0.5,
- y = cy + h*0.5 - yo,
- bw = w,
- color = colors[j],
- ms = 500,
- delta = 30,
- p = bar(x, y-bh, bw, bh, {fill: color, stroke: stroke, "stroke-width": 1});
-
- yo += bh;
-
+ var angleplus, color, delta, ms, p, popangle, value;
+ value = values[j];
+ angleplus = 360 * value / total;
+ popangle = angle + (angleplus * 0.5);
+ color = colors[j];
+ ms = 500;
+ delta = 30;
+ p = sector(cx, cy, r, angle, angle + angleplus, {
+ fill: color,
+ stroke: stroke,
+ 'stroke-width': 1
+ });
p.mouseover(function () {
- p.stop().animate({transform: "s1.1 1.1 " + cx + " " + cy}, ms, "elastic");
- }).mouseout(function () {
- p.stop().animate({transform: ""}, ms, "elastic");
-
+ p.stop().animate({
+ transform: "s1.1 1.1 " + cx + " " + cy
+ }, ms, "elastic");
+ });
+ p.mouseout(function () {
+ p.stop().animate({
+ transform: ""
+ }, ms, "elastic");
});
+ angle += angleplus;
chart.push(p);
};
- for (var i = 0, ii = values.length; i < ii; i++) {
- total += values[i];
- }
- for (i = 0; i < ii; i++) {
- process(i);
- }
- return chart;
-};
+ for (_i = 0, _len = values.length; _i < _len; _i++) {
+ v = values[_i];
+ total += v;
+ }
+ for (i in values) {
+ process(i);
+ }
+ return chart;
+ };
-;
+ /*
+ kartograph - a svg mapping library
+ Copyright (C) 2011,2012 Gregor Aisch
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
- StackedBarChart = (function(_super) {
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
- __extends(StackedBarChart, _super);
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
- /*
- usage:
- new SymbolMap({
- map: map,
- radius: 10
- data: [25,75],
- colors: ['red', 'blue'],
- titles: ['red pie', 'blue pie']
- })
- */
-
-
- function StackedBarChart(opts) {
- var me, _base2, _ref10, _ref11, _ref12, _ref13, _ref6, _ref7, _ref8, _ref9;
- me = this;
- StackedBarChart.__super__.constructor.call(this, opts);
- me.styles = (_ref6 = opts.styles) != null ? _ref6 : '';
- me.colors = (_ref7 = opts.colors) != null ? _ref7 : [];
- me.titles = (_ref8 = opts.titles) != null ? _ref8 : ['', '', '', '', ''];
- me.values = (_ref9 = opts.values) != null ? _ref9 : [];
- me.width = (_ref10 = opts.width) != null ? _ref10 : 17;
- me.height = (_ref11 = opts.height) != null ? _ref11 : 30;
- me["class"] = (_ref12 = opts["class"]) != null ? _ref12 : 'barchart';
- if ((_ref13 = (_base2 = Raphael.fn).drawStackedBarChart) == null) {
- _base2.drawStackedBarChart = drawStackedBars;
- }
- }
- StackedBarChart.prototype.overlaps = function(bubble) {
- var dx, dy, me, r1, r2, x1, x2, y1, y2, _ref6, _ref7;
- me = this;
- _ref6 = [me.x, me.y, me.radius], x1 = _ref6[0], y1 = _ref6[1], r1 = _ref6[2];
- _ref7 = [bubble.x, bubble.y, bubble.radius], x2 = _ref7[0], y2 = _ref7[1], r2 = _ref7[2];
- if (x1 - r1 > x2 + r2 || x1 + r1 < x2 - r2 || y1 - r1 > y2 + r2 || y1 + r1 < y2 - r2) {
- return false;
- }
- dx = x1 - x2;
- dy = y1 - y2;
- if (dx * dx + dy * dy > (r1 + r2) * (r1 + r2)) {
- return false;
- }
- return true;
- };
+ drawStackedBars = function (cx, cy, w, h, values, labels, colors, stroke) {
+ var paper = this,
+ chart = this.set();
- StackedBarChart.prototype.render = function(layers) {
- var bg, h, me, w, x, y;
- me = this;
- w = me.width;
- h = me.height;
- x = me.x;
- y = me.y;
- bg = me.layers.mapcanvas.rect(x - w * 0.5 - 2, y - h * 0.5 - 2, w + 4, h + 4).attr({
- stroke: 'none',
- fill: '#fff'
- });
- me.chart = me.layers.mapcanvas.drawStackedBarChart(me.x, me.y, me.width, me.height, me.values, me.titles, me.colors, "none");
- me.chart.push(bg);
- return me;
- };
+ function bar(x, y, w, h, params) {
+ return paper.rect(x, y, w, h).attr(params);
+ }
- StackedBarChart.prototype.update = function() {
- var me, path;
- me = this;
- return;
- me.path.attr({
- x: me.x,
- y: me.y,
- r: me.radius
- });
- path = me.path;
- path.node.setAttribute('style', me.styles[0]);
- path.node.setAttribute('class', me["class"]);
- if (me.title != null) {
- path.attr('title', me.titles[0]);
- }
- return me;
+ var yo = 0,
+ total = 0,
+ process = function (j) {
+ var value = values[j],
+ bh = h * value / total,
+ x = cx - w * 0.5,
+ y = cy + h * 0.5 - yo,
+ bw = w,
+ color = colors[j],
+ ms = 500,
+ delta = 30,
+ p = bar(x, y - bh, bw, bh, {fill: color, stroke: stroke, "stroke-width": 1});
+
+ yo += bh;
+
+ p.mouseover(function () {
+ p.stop().animate({transform: "s1.1 1.1 " + cx + " " + cy}, ms, "elastic");
+ }).mouseout(function () {
+ p.stop().animate({transform: ""}, ms, "elastic");
+
+ });
+ chart.push(p);
+ };
+ for (var i = 0, ii = values.length; i < ii; i++) {
+ total += values[i];
+ }
+ for (i = 0; i < ii; i++) {
+ process(i);
+ }
+ return chart;
};
- StackedBarChart.prototype.clear = function() {
- var me, p, _i, _len, _ref6;
- me = this;
- _ref6 = me.chart;
- for (_i = 0, _len = _ref6.length; _i < _len; _i++) {
- p = _ref6[_i];
- p.remove();
- }
- me.chart = [];
- return me;
- };
+ ;
- StackedBarChart.prototype.nodes = function() {
- var el, me, _i, _len, _ref6, _results;
- me = this;
- _ref6 = me.chart;
- _results = [];
- for (_i = 0, _len = _ref6.length; _i < _len; _i++) {
- el = _ref6[_i];
- _results.push(el.node);
- }
- return _results;
- };
- return StackedBarChart;
+ StackedBarChart = (function (_super) {
+
+ __extends(StackedBarChart, _super);
+
+ /*
+ usage:
+ new SymbolMap({
+ map: map,
+ radius: 10
+ data: [25,75],
+ colors: ['red', 'blue'],
+ titles: ['red pie', 'blue pie']
+ })
+ */
+
+
+ function StackedBarChart(opts) {
+ var me, _base2, _ref10, _ref11, _ref12, _ref13, _ref6, _ref7, _ref8, _ref9;
+ me = this;
+ StackedBarChart.__super__.constructor.call(this, opts);
+ me.styles = (_ref6 = opts.styles) != null ? _ref6 : '';
+ me.colors = (_ref7 = opts.colors) != null ? _ref7 : [];
+ me.titles = (_ref8 = opts.titles) != null ? _ref8 : ['', '', '', '', ''];
+ me.values = (_ref9 = opts.values) != null ? _ref9 : [];
+ me.width = (_ref10 = opts.width) != null ? _ref10 : 17;
+ me.height = (_ref11 = opts.height) != null ? _ref11 : 30;
+ me["class"] = (_ref12 = opts["class"]) != null ? _ref12 : 'barchart';
+ if ((_ref13 = (_base2 = Raphael.fn).drawStackedBarChart) == null) {
+ _base2.drawStackedBarChart = drawStackedBars;
+ }
+ }
+
+ StackedBarChart.prototype.overlaps = function (bubble) {
+ var dx, dy, me, r1, r2, x1, x2, y1, y2, _ref6, _ref7;
+ me = this;
+ _ref6 = [me.x, me.y, me.radius], x1 = _ref6[0], y1 = _ref6[1], r1 = _ref6[2];
+ _ref7 = [bubble.x, bubble.y, bubble.radius], x2 = _ref7[0], y2 = _ref7[1], r2 = _ref7[2];
+ if (x1 - r1 > x2 + r2 || x1 + r1 < x2 - r2 || y1 - r1 > y2 + r2 || y1 + r1 < y2 - r2) {
+ return false;
+ }
+ dx = x1 - x2;
+ dy = y1 - y2;
+ if (dx * dx + dy * dy > (r1 + r2) * (r1 + r2)) {
+ return false;
+ }
+ return true;
+ };
+
+ StackedBarChart.prototype.render = function (layers) {
+ var bg, h, me, w, x, y;
+ me = this;
+ w = me.width;
+ h = me.height;
+ x = me.x;
+ y = me.y;
+ bg = me.layers.mapcanvas.rect(x - w * 0.5 - 2, y - h * 0.5 - 2, w + 4, h + 4).attr({
+ stroke: 'none',
+ fill: '#fff'
+ });
+ me.chart = me.layers.mapcanvas.drawStackedBarChart(me.x, me.y, me.width, me.height, me.values, me.titles, me.colors, "none");
+ me.chart.push(bg);
+ return me;
+ };
+
+ StackedBarChart.prototype.update = function () {
+ var me, path;
+ me = this;
+ return;
+ me.path.attr({
+ x: me.x,
+ y: me.y,
+ r: me.radius
+ });
+ path = me.path;
+ path.node.setAttribute('style', me.styles[0]);
+ path.node.setAttribute('class', me["class"]);
+ if (me.title != null) {
+ path.attr('title', me.titles[0]);
+ }
+ return me;
+ };
+
+ StackedBarChart.prototype.clear = function () {
+ var me, p, _i, _len, _ref6;
+ me = this;
+ _ref6 = me.chart;
+ for (_i = 0, _len = _ref6.length; _i < _len; _i++) {
+ p = _ref6[_i];
+ p.remove();
+ }
+ me.chart = [];
+ return me;
+ };
+
+ StackedBarChart.prototype.nodes = function () {
+ var el, me, _i, _len, _ref6, _results;
+ me = this;
+ _ref6 = me.chart;
+ _results = [];
+ for (_i = 0, _len = _ref6.length; _i < _len; _i++) {
+ el = _ref6[_i];
+ _results.push(el.node);
+ }
+ return _results;
+ };
+
+ return StackedBarChart;
- })(kartograph.Symbol);
+ })(kartograph.Symbol);
- StackedBarChart.props = ['values', 'styles', 'class', 'titles', 'colors', 'width', 'height'];
+ StackedBarChart.props = ['values', 'styles', 'class', 'titles', 'colors', 'width', 'height'];
- StackedBarChart.layers = [];
+ StackedBarChart.layers = [];
- kartograph.StackedBarChart = StackedBarChart;
+ kartograph.StackedBarChart = StackedBarChart;
}).call(this);
diff --git a/plugins/UserCountryMap/js/vendor/kartograph.min.js b/plugins/UserCountryMap/js/vendor/kartograph.min.js
index 2a6e8ac636..1f3a863c4a 100644
--- a/plugins/UserCountryMap/js/vendor/kartograph.min.js
+++ b/plugins/UserCountryMap/js/vendor/kartograph.min.js
@@ -15,7 +15,1673 @@
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
-*/
-(function(){function bS(){function h(a,b,c){f(a.point,b)<f(c.point,b)&&(c=a),a.left&&(c=h(a.left,b,c));if(a.right){var d=a.point[a.axis]-b[a.axis];d*d<f(c.point,b)&&(c=h(a.right,b,c))}return c}function g(a){return function(b,c){b=b[a],c=c[a];return b<c?-1:b>c?1:0}}function f(a,c){var d=0;for(var e=0;e<b.length;e++){var f=b[e],g=a[f]-c[f];d+=g*g}return d}function e(a,c){if(!!a.length){var d=b[c%b.length],f=a.length>>1;a.sort(g(d));return{axis:d,point:a[f],left:e(a.slice(0,f),c+1),right:e(a.slice(f+1),c+1)}}}var a={},b=["x","y"],c,d=[];a.axes=function(c){if(!arguments.length)return b;b=c;return a},a.points=function(b){if(!arguments.length)return d;d=b,c=null;return a},a.find=function(b){return h(a.root(),b,c).point},a.root=function(a){return c||(c=e(d,0))};return a}function bR(){var a={},b=[],c=1,d=1;a.size=function(b){if(!arguments.length)return d;d=b;return a},a.iterations=function(b){if(!arguments.length)return c;c=b;return a},a.add=function(c){b.push(c);return a},a.means=function(){var a=[],e={},f=Math.min(d,b.length);for(var g=0,h=2*f;g<h;g++){var i=b[~~(Math.random()*b.length)],j=i.x+"/"+i.y;if(!(j in e)){e[j]=1;if(a.push({x:i.x,y:i.y})>=f)break}}f=a.length;for(var k=0;k<c;k++){var l=bS().points(a);for(var g=0;g<f;g++){var m=a[g];m.sumX=0,m.sumY=0,m.size=0,m.points=[],m.indices=[]}for(var g=0;g<b.length;g++){var n=b[g],m=l.find(n);m.sumX+=n.x,m.sumY+=n.y,m.size++,m.points.push(n),m.indices.push(g)}for(var g=0;g<f;g++){var m=a[g];if(!m.size)continue;m.x=m.sumX/m.size,m.y=m.sumY/m.size}}return a};return a}var a,b,c,d,e,f,g,h,i,j,k,l,n,o,p,q,r,t,u,v,w,x,y,z,A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,$,_,ba,bb,bc,bd,be,bf,bg,bh,bi,bj,bk,bl,bm,bn,bo,bp,bq,br,bs,bt,bu,bv,bw,bx,by,bz,bA,bB,bC,bD,bE,bF,bG,bH,bI,bJ,bK,bL,bM,bN={}.hasOwnProperty,bO=function(a,b){function d(){this.constructor=a}for(var c in b)bN.call(b,c)&&(a[c]=b[c]);d.prototype=b.prototype,a.prototype=new d,a.__super__=b.prototype;return a},bP=function(a,b){return function(){return a.apply(b,arguments)}},bQ=[].indexOf||function(a){for(var b=0,c=this.length;b<c;b++)if(b in this&&this[b]===a)return b;return-1};bz=typeof exports!="undefined"&&exports!==null?exports:this,br=bz.$K=window.Kartograph=(bH=bz.Kartograph)!=null?bH:bz.Kartograph={},br.version="0.5.2",a=bz.jQuery,br.__verbose=!1,bB=function(a){try{return console.warn.apply(console,arguments)}catch(b){try{return opera.postError.apply(opera,arguments)}catch(b){return alert(Array.prototype.join.call(arguments," "))}}},bs=function(a){if(br.__verbose)try{return console.debug.apply(console,arguments)}catch(b){try{return opera.postError.apply(opera,arguments)}catch(b){return alert(Array.prototype.join.call(arguments," "))}}},(bI=(bF=String.prototype).trim)==null&&(bF.trim=function(){return this.replace(/^\s+|\s+$/g,"")}),Array.prototype.indexOf||(Array.prototype.indexOf=function(a){"use strict";if(this==null)throw new TypeError;var b=Object(this),c=b.length>>>0;if(c===0)return-1;var d=0;arguments.length>0&&(d=Number(arguments[1]),d!=d?d=0:d!=0&&d!=Infinity&&d!=-Infinity&&(d=(d>0||-1)*Math.floor(Math.abs(d))));if(d>=c)return-1;var e=d>=0?d:Math.max(c-Math.abs(d),0);for(;e<c;e++)if(e in b&&b[e]===a)return e;return-1}),bE=function(){var a,b,c,d,e;a={},e="Boolean Number String Function Array Date RegExp Undefined Null".split(" ");for(c=0,d=e.length;c<d;c++)b=e[c],a["[object "+b+"]"]=b.toLowerCase();return function(b){var c;c=Object.prototype.toString.call(b);return a[c]||"object"}}(),d=function(){function a(a,b,c,d){var e;a==null&&(a=0),b==null&&(b=0),c==null&&(c=null),d==null&&(d=null),e=this,c===null?(e.xmin=Number.MAX_VALUE,e.xmax=Number.MAX_VALUE*-1):(e.xmin=e.left=a,e.xmax=e.right=a+c,e.width=c),d===null?(e.ymin=Number.MAX_VALUE,e.ymax=Number.MAX_VALUE*-1):(e.ymin=e.top=b,e.ymax=e.bottom=d+b,e.height=d);return}a.prototype.update=function(a,b){var c;b==null&&(b=a[1],a=a[0]),c=this,c.xmin=Math.min(c.xmin,a),c.ymin=Math.min(c.ymin,b),c.xmax=Math.max(c.xmax,a),c.ymax=Math.max(c.ymax,b),c.left=c.xmin,c.top=c.ymin,c.right=c.xmax,c.bottom=c.ymax,c.width=c.xmax-c.xmin,c.height=c.ymax-c.ymin;return this},a.prototype.intersects=function(a){return a.left<s.right&&a.right>s.left&&a.top<s.bottom&&a.bottom>s.top},a.prototype.inside=function(a,b){var c;c=this;return a>=c.left&&a<=c.right&&b>=c.top&&b<=c.bottom},a.prototype.join=function(a){var b;b=this,b.update(a.left,a.top),b.update(a.right,a.bottom);return this};return a}(),d.fromXML=function(a){var b,c,d,e;d=Number(a.getAttribute("x")),e=Number(a.getAttribute("y")),c=Number(a.getAttribute("w")),b=Number(a.getAttribute("h"));return new br.BBox(d,e,c,b)},br.BBox=d,(bJ=br.geom)==null&&(br.geom={}),(bK=(bG=br.geom).clipping)==null&&(bG.clipping={}),l=function(){function f(){}var a,b,c,d,e;b=0,c=1,d=2,a=4,e=8,f.prototype.compute_out_code=function(a,b,c){var d,e;e=this,d=e.INSIDE,b<a.left?d|=e.LEFT:b>a.right&&(d|=e.RIGHT),c<a.top?d|=e.TOP:c>a.bottom&&(d|=e.BOTTOM);return d},f.prototype.clip=function(a,b,c,d,e){var f,g,h,i,j,k,l;j=this,g=j.compute_out_code(a,b,c),h=j.compute_out_code(a,d,e),f=False;while(True){if(!(g|h)){f=True;break}if(g&h)break;i=code===0?h:g,i&j.TOP?(k=b+(d-b)*(a.top-c)/(e-c),l=a.top):i&j.BOTTOM?(k=b+(d-b)*(a.bottom-c)/(e-c),l=a.bottom):i&j.RIGHT?(l=c+(e-c)*(a.right-b)/(d-b),k=a.right):i&j.LEFT&&(l=c+(e-c)*(a.left-b)/(d-b),k=a.left),i===g?(b=k,c=l,g=j.compute_out_code(a,b,c)):(d=k,e=l,h=j.compute_out_code(a,d,e))}return f?[b,c,d,e]:null};return f}(),br.geom.clipping.CohenSutherland=l,B=function(){function b(b,c,d){var e,f;f=this,f.container=e=a(b),c==null&&(c=e.width()),d==null&&(d=e.height()),d===0&&(d="auto"),f.size={h:d,w:c},f.markers=[],f.pathById={},f.container.addClass("kartograph")}b.prototype.createSVGLayer=function(b){var c,d,e,f,g,h,i,j;f=this,(j=f._layerCnt)==null&&(f._layerCnt=0),e=f._layerCnt++,i=f.viewport,d=f.container,g=Raphael(d[0],i.width,i.height),h=a(g.canvas),h.css({position:"absolute",top:"0px",left:"0px","z-index":e+5}),d.css("position")==="static"&&d.css({position:"relative",height:i.height+"px"}),h.addClass(b),c=a("desc",g.canvas).text(),a("desc",g.canvas).text(c.replace("with ","with kartograph "+br.version+" and "));return g},b.prototype.createHTMLLayer=function(b){var c,d,e,f,g,h;f=this,g=f.viewport,c=f.container,(h=f._layerCnt)==null&&(f._layerCnt=0),e=f._layerCnt++,d=a('<div class="layer '+b+'" />'),d.css({position:"absolute",top:"0px",left:"0px",width:g.width+"px",height:g.height+"px","z-index":e+5}),c.append(d);return d},b.prototype.loadMap=function(b,c,d){var e,f,g,h;f=this,e=a.Deferred(),f.clear(),f.opts=d!=null?d:{},(h=(g=f.opts).zoom)==null&&(g.zoom=1),f.mapLoadCallback=c,f._loadMapDeferred=e,f._lastMapUrl=b,f.cacheMaps&&br.__mapCache[b]!=null?f._mapLoaded(br.__mapCache[b]):a.ajax({url:b,dataType:"text",success:f._mapLoaded,context:f,error:function(a,b,c){return bB(a,b,c)}});return e.promise()},b.prototype.setMap=function(a,b){var c,d,e;c=this,c.opts=b!=null?b:{},(e=(d=c.opts).zoom)==null&&(d.zoom=1),c._lastMapUrl="string",c._mapLoaded(a)},b.prototype._mapLoaded=function(b){var c,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s;h=this,h.cacheMaps&&((o=br.__mapCache)==null&&(br.__mapCache={}),br.__mapCache[h._lastMapUrl]=b);try{b=a(b)}catch(t){bB("something went horribly wrong while parsing svg"),h._loadMapDeferred.reject("could not parse svg");return}h.svgSrc=b,c=a("view",b),bs("got svg src",h.svgSrc),h.paper==null&&(m=h.size.w,f=h.size.h,f==="auto"&&(j=c.attr("w")/c.attr("h"),f=m/j),h.viewport=new d(0,0,m,f)),l=h.viewport,bs("got viewport",h.viewport),h.viewAB=e=br.View.fromXML(c[0]),bs("got first view",h.viewAB),i=(p=h.opts.padding)!=null?p:0,g=(q=h.opts.halign)!=null?q:"center",k=(r=h.opts.valign)!=null?r:"center",bs("got alignment",g,k),n=(s=h.opts.zoom)!=null?s:1,h.viewBC=new br.View(h.viewAB.asBBox(),l.width*n,l.height*n,i,g,k),bs("got second view",h.viewBC),h.proj=br.Proj.fromXML(a("proj",c)[0]),bs("got projection",h.proj),h.mapLoadCallback!=null&&h.mapLoadCallback(h),h._loadMapDeferred!=null&&h._loadMapDeferred.resolve(h)},b.prototype.addLayer=function(b,c){var d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w;c==null&&(c={}),i=this,(u=i.layerIds)==null&&(i.layerIds=[]),(v=i.layers)==null&&(i.layers={}),i.paper==null&&(i.paper=i.createSVGLayer()),l=b,bE(c)==="object"?(h=c.name,j=c.key,o=c.title):c={},h==null&&(h=l),m=a("#"+l,i.svgSrc);if(m.length!==0){g=new L(h,j,i,c.filter),d=a("*",m[0]);for(q=0,s=d.length;q<s;q++)n=d[q],g.addPath(n,o);g.paths.length>0&&(i.layers[h]=g,i.layerIds.push(h)),e=["click","mouseenter","mouseleave","dblclick","mousedown","mouseup","mouseover","mouseout"];for(r=0,t=e.length;r<t;r++)f=e[r],bE(c[f])==="function"&&g.on(f,c[f]);if(c.styles!=null){w=c.styles;for(k in w)p=w[k],g.style(k,p)}c.tooltips!=null&&g.tooltips(c.tooltips);return i}},b.prototype.getLayer=function(a){var b;b=this;if(b.layers[a]==null){bB("could not find layer "+a);return null}return b.layers[a]},b.prototype.getLayerPath=function(a,b){var c,d;d=this,c=d.getLayer(a);if(c!=null)return bE(b)==="object"?c.getPaths(b)[0]:c.getPath(b);return null},b.prototype.onLayerEvent=function(a,b,c){var d;d=this,d.getLayer(c).on(a,b);return d},b.prototype.addMarker=function(a){var b,c;b=this,b.markers.push(a),c=b.viewBC.project(b.viewAB.project(b.proj.project(a.lonlat.lon,a.lonlat.lat)));return a.render(c[0],c[1],b.container,b.paper)},b.prototype.clearMarkers=function(){var a,b,c,d,e;b=this,e=b.markers;for(c=0,d=e.length;c<d;c++)a=e[c],a.clear();return b.markers=[]},b.prototype.fadeIn=function(a){var b,c,d,e,f,g,h,i,j,k,l;a==null&&(a={}),f=this,e=(i=a.layer)!=null?i:f.layerIds[f.layerIds.length-1],c=(j=a.duration)!=null?j:500,k=f.layers[e].pathsById,l=[];for(d in k)h=k[d],l.push(function(){var a,d,e;e=[];for(a=0,d=h.length;a<d;a++)g=h[a],bE(c)==="function"?b=c(g.data):b=c,g.svgPath.attr("opacity",0),e.push(g.svgPath.animate({opacity:1},b));return e}());return l},b.prototype.loadCoastline=function(){var b;b=this;return a.ajax({url:"coastline.json",success:b.renderCoastline,context:b})},b.prototype.resize=function(a,b){var c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s;g=this,c=g.container,a==null&&(a=c.width()),b==null&&(b=c.height()),g.viewport=k=new br.BBox(0,0,a,b),g.paper!=null&&g.paper.setSize(k.width,k.height),k=g.viewport,h=(o=g.opts.padding)!=null?o:0,d=(p=g.opts.halign)!=null?p:"center",j=(q=g.opts.valign)!=null?q:"center",l=g.opts.zoom,g.viewBC=new br.View(g.viewAB.asBBox(),k.width*l,k.height*l,h,d,j),r=g.layers;for(e in r)f=r[e],f.setView(g.viewBC);if(g.symbolGroups!=null){s=g.symbolGroups;for(m=0,n=s.length;m<n;m++)i=s[m],i.onResize()}},b.prototype.lonlat2xy=function(a){var b,c;c=this,a.length===2&&(a=new br.LonLat(a[0],a[1])),a.length===3&&(a=new br.LonLat(a[0],a[1],a[2])),b=c.proj.project(a.lon,a.lat,a.alt);return c.viewBC.project(c.viewAB.project(b))},b.prototype.showZoomControls=function(){var a;a=this,a.zc=new S(a);return a},b.prototype.addSymbolGroup=function(a){var b,c;b=this,(c=b.symbolGroups)==null&&(b.symbolGroups=[]);return b.symbolGroups.push(a)},b.prototype.removeSymbols=function(a){var b,c,d,e,f,g;b=this;if(a!=null)return b.symbolGroups[a].remove();f=b.symbolGroups,g=[];for(d=0,e=f.length;d<e;d++)c=f[d],g.push(c.remove());return g},b.prototype.clear=function(){var b,c,d,e,f,g;c=this;if(c.layers!=null){for(b in c.layers)c.layers[b].remove();c.layers={},c.layerIds=[]}if(c.symbolGroups!=null){g=c.symbolGroups;for(e=0,f=g.length;e<f;e++)d=g[e],d.remove();c.symbolGroups=[]}if(c.paper!=null){a(c.paper.canvas).remove();return c.paper=void 0}},b.prototype.loadCSS=function(b,c){var d;d=this;if(a.browser.msie)return a.ajax({url:b,dataType:"text",success:function(a){d.styles=br.parsecss(a);return c()},error:function(a,c,d){return bB("error while loading "+b,a,c,d)}});a("body").append('<link rel="stylesheet" href="'+b+'" />');return c()},b.prototype.applyCSS=function(a,b){var c,d,e,f,g,h,i,j,k,l,m,n,o,p,q;e=this;if(e.styles==null)return a;(n=e._pathTypes)==null&&(e._pathTypes=["path","circle","rectangle","ellipse"]),(o=e._regardStyles)==null&&(e._regardStyles=["fill","stroke","fill-opacity","stroke-width","stroke-opacity"]);for(h in e.styles){f=h,p=f.split(",");for(j=0,l=p.length;j<l;j++){i=p[j],f=i.split(" "),f=f[f.length-1],f=f.split(":");if(f.length>1)continue;f=f[0].split("."),c=f.slice(1);if(c.length>0&&c.indexOf(b)<0)continue;f=f[0];if(e._pathTypes.indexOf(f)>=0&&f!==a.type)continue;g=e.styles[h],q=e._regardStyles;for(k=0,m=q.length;k<m;k++)d=q[k],g[d]!=null&&a.attr(d,g[d])}}return a},b.prototype.style=function(a,b,c,d,e){var f;f=this,a=f.getLayer(a);if(a!=null)return a.style(b,c,d,e)};return b}(),br.Kartograph=B,br.map=function(a,b,c){return new B(a,b,c)},br.__mapCache={},J=function(){function a(a,b,c){c==null&&(c=0),this.lon=Number(a),this.lat=Number(b),this.alt=Number(c)}a.prototype.distance=function(a){var b,c,d,e,f,g,h,i,j;j=this,b=6371,g=Math.PI/180,e=(a.lat-j.lat)*g,f=(a.lon-j.lon)*g,h=j.lat*g,i=a.lat*g,c=Math.sin(e/2)*Math.sin(e/2)+Math.sin(f/2)*Math.sin(f/2)*Math.cos(h)*Math.cos(i),d=2*Math.atan2(Math.sqrt(c),Math.sqrt(1-c));return b*d};return a}(),F=function(a){function b(a,c,d){d==null&&(d=0),b.__super__.constructor.call(this,c,a,d)}bO(b,a);return b}(J),br.LonLat=J,br.LatLon=F,L=function(){function b(a,b,c,d){var e;e=this,e.id=a,e.path_id=b,e.paper=c.paper,e.view=c.viewBC,e.map=c,e.filter=d}b.prototype.addPath=function(a,b){var c,d,e,f,g,h,i;d=this,(g=d.paths)==null&&(d.paths=[]),c=new M(a,d.id,d.map,b);if(bE(d.filter)==="function"&&d.filter(c.data)===!1)c.remove();else{d.paths.push(c);if(d.path_id!=null){(h=d.pathsById)==null&&(d.pathsById={}),(i=(e=d.pathsById)[f=c.data[d.path_id]])==null&&(e[f]=[]);return d.pathsById[c.data[d.path_id]].push(c)}}},b.prototype.hasPath=function(a){var b;b=this;return b.pathsById!=null&&b.pathsById[a]!=null},b.prototype.getPathsData=function(){var a,b,c,d,e,f;a=this,c=[],f=a.paths;for(d=0,e=f.length;d<e;d++)b=f[d],c.push(b.data);return c},b.prototype.getPath=function(a){var b;b=this;return b.hasPath(a)?b.pathsById[a][0]:null},b.prototype.getPaths=function(a){var b,c,d,e,f,g,h,i;e=this,d=[];if(bE(a)==="object"){i=e.paths;for(g=0,h=i.length;g<h;g++){f=i[g],c=!0;for(b in a)c=c&&f.data[b]===a[b];c&&d.push(f)}}return d},b.prototype.setView=function(a){var b,c,d,e,f;b=this,f=b.paths;for(d=0,e=f.length;d<e;d++)c=f[d],c.setView(a);return b},b.prototype.remove=function(){var a,b,c,d,e,f;a=this,e=a.paths,f=[];for(c=0,d=e.length;c<d;c++)b=e[c],f.push(b.remove());return f},b.prototype.style=function(a,b,c,d){var e,f,g,h,i,j,k,l,m,n,o;j=this;if(bE(a)==="object"){for(i in a)l=a[i],j.style(i,l);return j}c==null&&(c=0),d==null&&(d=0),o=j.paths;for(m=0,n=o.length;m<n;m++)k=o[m],l=bx(b,k.data),h=bx(c,k.data),g=bx(d,k.data),h>0?(f={},f[a]=l,e=Raphael.animation(f,h*1e3),k.svgPath.animate(e.delay(g*1e3))):k.svgPath.attr(a,l);return j},b.prototype.on=function(b,c){var d,e,f,g,h,i,j;f=this,d=function(){function a(a,b,c){this.type=a,this.cb=b,this.layer=c,this.handle=bP(this.handle,this)}a.prototype.handle=function(a){var b;f=this,b=f.layer.map.pathById[a.target.getAttribute("id")];return f.cb(b.data,b.svgPath,a)};return a}(),e=new d(b,c,f),j=f.paths;for(h=0,i=j.length;h<i;h++)g=j[h],a(g.svgPath.node).bind(b,e.handle);return f},b.prototype.tooltips=function(b,c){var d,e,f,g,h,i,j;d=this,f=function(b,d){var e;e={position:{target:"mouse",viewport:a(window),adjust:{x:7,y:7}},show:{delay:c!=null?c:20},events:{show:function(b,c){return a(".qtip").filter(function(){return this!==c.elements.tooltip.get(0)}).hide()}},content:{}},d!=null?typeof d=="string"?e.content.text=d:a.isArray(d)&&(e.content.title=d[0],e.content.text=d[1]):e.content.text="n/a";return a(b.svgPath.node).qtip(e)},j=d.paths;for(h=0,i=j.length;h<i;h++)e=j[h],g=bx(b,e.data),f(e,g);return d},b.prototype.sort=function(a){var b,c,d,e,f,g;c=this,c.paths.sort(function(b,c){var d,e,f;d=a(b.data),e=a(c.data);return d===e?0:(f=d>e)!=null?f:{1:-1}}),b=!1,g=c.paths;for(e=0,f=g.length;e<f;e++)d=g[e],b&&d.svgPath.insertAfter(b.svgPath),b=d;return c};return b}(),bx=function(a,b){return bE(a)==="function"?a(b):a},bt=0,M=function(){function a(a,b,c,d){var e,f,g,h,i,j,k,l,m,n,o,p,q;h=this,i=c.paper,n=c.viewBC,h.path=j=br.geom.Path.fromSVG(a),h.vpath=n.projectPath(j),h.svgPath=h.vpath.toSVG(i),c.styles==null?h.svgPath.node.setAttribute("class",b):c.applyCSS(h.svgPath,b),l="path_"+bt++,h.svgPath.node.setAttribute("id",l),c.pathById[l]=h,f={};for(g=p=0,q=a.attributes.length-1;0<=q?p<=q:p>=q;g=0<=q?++p:--p)e=a.attributes[g],e.name.substr(0,5)==="data-"&&(m=e.value,o=Number(m),m.trim()!==""&&o===m&&!isNaN(o)&&(m=o),f[e.name.substr(5)]=m);h.data=f,bE(d)==="string"?k=d:bE(d)==="function"&&(k=d(f)),k!=null&&h.svgPath.attr("title",k)}a.prototype.setView=function(a){var b,c,d;b=this,c=a.projectPath(b.path),b.vpath=c;if(b.path.type==="path"){d=c.svgString();return b.svgPath.attr({path:d})}if(b.path.type==="circle")return b.svgPath.attr({cx:c.x,cy:c.y,r:c.r})},a.prototype.remove=function(){var a;a=this;return a.svgPath.remove()};return a}(),br.parsecss=function(a,b){var c,d,e,f,g,h,i,j;f={},a=bu(a),j=a.split("`b%");for(h=0,i=j.length;h<i;h++){c=j[h],c=c.split("%b`");if(c.length<2)continue;c[0]=by(c[0]),e=bw(c[1]);if(f[c[0]]!=null)for(d in e)g=e[d],f[c[0]][d]=g;else f[c[0]]=e}if(bE(b)==="function")b(f);else return f},bv={},bw=function(a){var b,c,d,e,f,g;d=bv[a].replace(/^{|}$/g,""),d=bu(d),c={},g=d.split(";");for(e=0,f=g.length;e<f;e++){b=g[e],b=b.split(":");if(b.length<2)continue;c[by(b[0])]=by(b.slice(1).join(":"))}return c},Z=/{[^{}]*}/,_=/\[[^\[\]]*\]|{[^{}]*}|\([^()]*\)|function(\s+\w+)?(\s*%b`\d+`b%){2}/,$=/(?:\/\*(?:[^\*]|\*[^\/])*\*\/)|(\\.|"(?:[^\\\"]|\\.|\\\n)*"|'(?:[^\\\']|\\.|\\\n)*')/g,ba=/%\w`(\d+)`\w%/,bA=0,bu=function(a,b){var c,d,e;a=a.replace($,function(a,b){var c;if(!b)return"";c="%s`"+ ++bA+"`s%",bv[bA]=b.replace(/^\\/,"");return c}),c=b?_:Z;while(d=c.exec(a))e="%b`"+ ++bA+"`b%",bv[bA]=d[0],a=a.replace(c,e);return a},by=function(a){var b;if(a==null)return a;while(b=ba.exec(a))a=a.replace(ba,bv[b[1]]);return a.trim()},(bL=br.geom)==null&&(br.geom={}),T=function(){function a(a,b,c){var d;c==null&&(c=!0),d=this,d.type=a,d.contours=b,d.closed=c}a.prototype.clipToBBox=function(a){throw"path clipping is not implemented yet"},a.prototype.toSVG=function(a){var b;b=this.svgString();return a.path(b)},a.prototype.svgString=function(){var a,b,c,d,e,f,g,h,i,j,k,l,m;d=this,e="",c=d.closed?"Z M":"M",l=d.contours;for(h=0,j=l.length;h<j;h++){a=l[h],b=!0,e+=e===""?"M":c;for(i=0,k=a.length;i<k;i++)m=a[i],f=m[0],g=m[1],b||(e+="L"),e+=f+","+g,b=!1}d.closed&&(e+="Z");return e},a.prototype.area=function(){var a,b,c,d,e,f,g,h,i;d=this;if(d.areas!=null)return d._area;d.areas=[],d._area=0,h=d.contours;for(e=0,g=h.length;e<g;e++){b=h[e],a=0;for(c=f=0,i=b.length-2;0<=i?f<=i:f>=i;c=0<=i?++f:--f)a+=b[c][0]*b[c+1][1]-b[c+1][0]*b[c][1];a*=.5,a=a,d.areas.push(a),d._area+=a}return d._area},a.prototype.centroid=function(){var a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z,A,B,C,D,E,F,G,H,I,J,K;p=this;if(p._centroid!=null)return p._centroid;c=p.area(),f=g=0;for(k=A=0,G=p.contours.length-1;0<=G?A<=G:A>=G;k=0<=G?++A:--A){e=p.contours[k],d=[],n=e.length;for(l=B=0,H=n-1;0<=H?B<=H:B>=H;l=0<=H?++B:--B){q=e[l],r=e[(l+1)%n],h=0,d.push(q),q[0]===r[0]&&(h=Math.abs(q[1]-r[1])),q[1]===r[1]&&(h=Math.abs(q[0]-r[0]));if(h>10){a=Math.floor(h*2);for(s=C=1,I=a-1;1<=I?C<=I:C>=I;s=1<=I?++C:--C)t=[q[0]+s/a*(r[0]-q[0]),q[1]+s/a*(r[1]-q[1])],d.push(t)}}b=p.areas[k],w=y=x=z=0,n=d.length,E=[],u=0;for(l=D=0,J=n-1;0<=J?D<=J:D>=J;l=0<=J?++D:--D)q=d[l],r=d[(l+1)%n],i=r[0]-q[0],j=r[1]-q[1],o=Math.sqrt(i*i+j*j),E.push(o),u+=o;for(l=F=0,K=n-1;0<=K?F<=K:F>=K;l=0<=K?++F:--F)q=d[l],v=E[l]/u,w+=v*q[0],y+=v*q[1];m=b/c,f+=w*m,g+=y*m}p._centroid=[f,g];return p._centroid},a.prototype.isInside=function(a,b){var c,d,e,f,g,h;f=this,c=f._bbox;if(a<c[0]||a>c[2]||b<c[1]||b>c[3])return!1;for(e=g=0,h=f.contours.length-1;0<=h?g<=h:g>=h;e=0<=h?++g:--g){d=f.contours[e];if(bC(d,[a,b]))return!0}return!1};return a}(),br.geom.Path=T,k=function(a){function b(a,c,d){this.x=a,this.y=c,this.r=d,b.__super__.constructor.call(this,"circle",null,!0)}bO(b,a),b.prototype.toSVG=function(a){var b;b=this;return a.circle(b.x,b.y,b.r)},b.prototype.centroid=function(){var a;a=this;return[a.x,a.y]},b.prototype.area=function(){var a;a=this;return Math.PI*a.r*m.r};return b}(T),br.geom.Circle=k,T.fromSVG=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o;e=[],m=a.nodeName,k=null;if(m==="path"){i=a.getAttribute("d").trim(),h=Raphael.parsePathString(i),b=h[h.length-1]==="Z",l=b?"Z M":"M",d=[];for(n=0,o=h.length;n<o;n++){c=h[n];if(c.length===0)continue;c[0]==="M"?(d.length>2&&(e.push(d),d=[]),d.push([c[1],c[2]])):c[0]==="L"?d.push([c[1],c[2]]):c[0]==="Z"&&d.length>2&&(e.push(d),d=[])}d.length>2&&(e.push(d),d=[]),k=new br.geom.Path(m,e,b)}else m==="circle"&&(f=a.getAttribute("cx"),g=a.getAttribute("cy"),j=a.getAttribute("r"),k=new br.geom.Circle(f,g,j));return k},G=function(){function a(a){this.points=a}a.prototype.clipToBBox=function(b){var c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u;l=this,c=(new br.geom.clipping.CohenSutherland).clip,k=[],f=[],e=!1;for(d=q=0,r=l.points.length-2;0<=r?q<=r:q>=r;d=0<=r?++q:--q){s=l.points[d],g=s[0],h=s[1],t=l.points[d+1],i=t[0],j=t[1];try{u=c(b,g,h,i,j),m=u[0],o=u[1],n=u[2],p=u[3],e=!0,k.push([m,o]),(i!==n||j!==o||d===len(l.points)-2)&&k.push([n,p])}catch(v){e&&k.length>1&&(f.push(new a(k)),k=[]),e=!1}}k.length>1&&f.push(new a(k));return f},a.prototype.toSVG=function(){var a,b,c,d,e,f,g,h;b=this,a=[],g=b.points;for(e=0,f=g.length;e<f;e++)h=g[e],c=h[0],d=h[1],a.push(c+","+d);return"M"+a.join("L")};return a}(),br.geom.Line=G,bC=function(a,b){var c,d,e,f,g,h,i,j,k,l,m,n,o,p,q;h=Math.PI,d=Math.atan2,k=h*2,g=a.length,c=0;for(f=p=0,q=g-1;0<=q?p<=q:p>=q;f=0<=q?++p:--p){l=a[f][0]-b[0],n=a[f][1]-b[1],m=a[(f+1)%g][0]-b[0],o=a[(f+1)%g][1]-b[1],i=d(n,l),j=d(o,m),e=j-i;while(e>h)e-=k;while(e<-h)e+=k;c+=e}return Math.abs(c)>=h},bD=br.proj={},Function.prototype.bind=function(a){var b;b=this;return function(){return b.apply(a,arguments)}},V=function(){function a(a){var b,c,d;b=this,b.lon0=(c=a.lon0)!=null?c:0,b.lat0=(d=a.lat0)!=null?d:0,b.PI=Math.PI,b.HALFPI=b.PI*.5,b.QUARTERPI=b.PI*.25,b.RAD=b.PI/180,b.DEG=180/b.PI,b.lam0=b.rad(this.lon0),b.phi0=b.rad(this.lat0),b.minLat=-90,b.maxLat=90}a.parameters=[],a.title="Projection",a.prototype.rad=function(a){return a*this.RAD},a.prototype.deg=function(a){return a*this.DEG},a.prototype.plot=function(a,b){var c,d,e,f,g,h,i,j,k,l,m;b==null&&(b=!0),f=[],c=!0;for(j=0,k=a.length;j<k;j++)l=a[j],e=l[0],d=l[1],g=this._visible(e,d),g&&(c=!1),m=this.project(e,d),h=m[0],i=m[1],!g&&b?f.push(this._truncate(h,i)):f.push([h,i]);return c?null:[f]},a.prototype.sea=function(){var a,b,c,d,e,f,g,h,i,j,k,l,m,n;f=this,e=f.project.bind(this),d=[],a=f.lon0,f.lon0=0;for(c=g=-180;g<=180;c=++g)d.push(e(c,f.maxLat));for(b=h=k=f.maxLat,l=f.minLat;k<=l?h<=l:h>=l;b=k<=l?++h:--h)d.push(e(180,b));for(c=i=180;i>=-180;c=--i)d.push(e(c,f.minLat));for(b=j=m=f.minLat,n=f.maxLat;m<=n?j<=n:j>=n;b=m<=n?++j:--j)d.push(e(-180,b));f.lon0=a;return d},a.prototype.world_bbox=function(){var a,b,c,d,e,f;b=this.project.bind(this),d=this.sea(),a=new br.BBox;for(e=0,f=d.length;e<f;e++)c=d[e],a.update(c[0],c[1]);return a},a.prototype.toString=function(){var a;a=this;return"[Proj: "+a.name+"]"};return a}(),V.fromXML=function(a){var b,c,d,e,f,g,h;d=a.getAttribute("id"),e={};for(c=g=0,h=a.attributes.length-1;0<=h?g<=h:g>=h;c=0<=h?++g:--g)b=a.attributes[c],b.name!=="id"&&(e[b.name]=b.value);f=new br.proj[d](e),f.name=d;return f},br.Proj=V,o=function(a){function b(a){var c,d,e;a==null&&(a={}),c=this,c.flip=Number((d=a.flip)!=null?d:0),c.flip===1&&(a.lon0=(e=-a.lon0)!=null?e:0),b.__super__.constructor.call(this,a)}bO(b,a),b.parameters=["lon0","flip"],b.title="Cylindrical Projection",b.prototype._visible=function(a,b){return!0},b.prototype.clon=function(a){a-=this.lon0,a<-180?a+=360:a>180&&(a-=360);return a},b.prototype.ll=function(a,b){return this.flip===1?[-a,-b]:[a,b]};return b}(V),r=function(a){function b(){return b.__super__.constructor.apply(this,arguments)}bO(b,a),b.title="Equirectangular Projection",b.prototype.project=function(a,b){var c;c=this.ll(a,b),a=c[0],b=c[1],a=this.clon(a);return[a*Math.cos(this.phi0)*1e3,b*-1*1e3]};return b}(o),bD.lonlat=r,i=function(a){function b(a){var c;b.__super__.constructor.call(this,a),this.lat1=(c=a.lat1)!=null?c:0,this.phi1=this.rad(this.lat1)}bO(b,a),b.parameters=["lon0","lat1","flip"],b.title="Cylindrical Equal Area",b.prototype.project=function(a,b){var c,d,e,f,g;g=this.ll(a,b),a=g[0],b=g[1],c=this.rad(this.clon(a)),d=this.rad(b*-1),e=c*Math.cos(this.phi1),f=Math.sin(d)/Math.cos(this.phi1);return[e*1e3,f*1e3]};return b}(o),bD.cea=i,u=function(a){function b(a){a.lat1=45,b.__super__.constructor.call(this,a)}bO(b,a),b.title="Gall-Peters Projection",b.parameters=["lon0","flip"];return b}(i),bD.gallpeters=u,y=function(a){function b(a){a.lat1=37.7,b.__super__.constructor.call(this,a)}bO(b,a),b.title="Hobo-Dyer Projection",b.parameters=["lon0","flip"];return b}(i),bD.hobodyer=y,f=function(a){function b(a){a.lat1=30,b.__super__.constructor.call(this,a)}bO(b,a),b.title="Behrmann Projection",b.parameters=["lon0","flip"];return b}(i),bD.behrmann=f,e=function(a){function b(a){a.lat1=50,b.__super__.constructor.call(this,a)}bO(b,a),b.title="Balthasart Projection",b.parameters=["lon0","flip"];return b}(i),bD.balthasart=e,N=function(a){function b(a){b.__super__.constructor.call(this,a),this.minLat=-85,this.maxLat=85}bO(b,a),b.title="Mercator Projection",b.prototype.project=function(a,b){var c,d,e,f,g,h,i;f=this,i=f.ll(a,b),a=i[0],b=i[1],d=Math,c=f.rad(f.clon(a)),e=f.rad(b*-1),g=c*1e3,h=d.log((1+d.sin(e))/d.cos(e))*1e3;return[g,h]};return b}(o),bD.mercator=N,X=function(a){function b(){return b.__super__.constructor.apply(this,arguments)}bO(b,a),b.title="Pseudo-Cylindrical Projection";return b}(o),P=function(a){function b(a){var c;b.__super__.constructor.call(this,a),c=this,c.A0=.8707,c.A1=-0.131979,c.A2=-0.013791,c.A3=.003971,c.A4=-0.001529,c.B0=1.007226,c.B1=.015085,c.B2=-0.044475,c.B3=.028874,c.B4=-0.005916,c.C0=c.B0,c.C1=3*c.B1,c.C2=7*c.B2,c.C3=9*c.B3,c.C4=11*c.B4,c.EPS=1e-11,c.MAX_Y=.8707*.52*Math.PI;return}bO(b,a),b.title="Natural Earth Projection",b.prototype.project=function(a,b){var c,d,e,f,g,h,i,j;g=this,j=g.ll(a,b),a=j[0],b=j[1],c=g.rad(g.clon(a)),d=g.rad(b*-1),e=d*d,f=e*e,h=c*(g.A0+e*(g.A1+e*(g.A2+f*e*(g.A3+e*g.A4))))*180+500,i=d*(g.B0+e*(g.B1+f*(g.B2+g.B3*e+g.B4*f)))*180+270;return[h,i]};return b}(X),bD.naturalearth=P,bb=function(a){function b(a){var c;b.__super__.constructor.call(this,a),c=this,c.X=[1,-5.67239e-12,-0.0000715511,311028e-11,.9986,-0.000482241,-0.000024897,-0.00000133094,.9954,-0.000831031,-0.000044861,-9.86588e-7,.99,-0.00135363,-0.0000596598,367749e-11,.9822,-0.00167442,-0.0000044975,-0.00000572394,.973,-0.00214869,-0.0000903565,1.88767e-8,.96,-0.00305084,-0.0000900732,164869e-11,.9427,-0.00382792,-0.0000653428,-0.00000261493,.9216,-0.00467747,-0.000104566,48122e-10,.8962,-0.00536222,-0.0000323834,-0.00000543445,.8679,-0.00609364,-0.0001139,332521e-11,.835,-0.00698325,-0.0000640219,9.34582e-7,.7986,-0.00755337,-0.0000500038,9.35532e-7,.7597,-0.00798325,-0.0000359716,-0.00000227604,.7186,-0.00851366,-0.000070112,-0.00000863072,.6732,-0.00986209,-0.000199572,191978e-10,.6213,-0.010418,883948e-10,624031e-11,.5722,-0.00906601,181999e-9,624033e-11,.5322,0,0,0],c.Y=[0,.0124,3.72529e-10,1.15484e-9,.062,.0124001,1.76951e-8,-5.92321e-9,.124,.0123998,-7.09668e-8,2.25753e-8,.186,.0124008,2.66917e-7,-8.44523e-8,.248,.0123971,-9.99682e-7,3.15569e-7,.31,.0124108,373349e-11,-0.0000011779,.372,.0123598,-0.000013935,439588e-11,.434,.0125501,520034e-10,-0.0000100051,.4968,.0123198,-0.0000980735,922397e-11,.5571,.0120308,402857e-10,-0.0000052901,.6176,.0120369,-0.0000390662,7.36117e-7,.6769,.0117015,-0.0000280246,-8.54283e-7,.7346,.0113572,-0.0000408389,-5.18524e-7,.7903,.0109099,-0.0000486169,-0.0000010718,.8435,.0103433,-0.0000646934,5.36384e-9,.8936,.00969679,-0.0000646129,-0.00000854894,.9394,.00840949,-0.000192847,-0.00000421023,.9761,.00616525,-0.000256001,-0.00000421021,1,0,0,0],c.NODES=18,c.FXC=.8487,c.FYC=1.3523,c.C1=11.459155902616464,c.RC1=.08726646259971647,c.ONEEPS=1.000001,c.EPS=1e-8;return}bO(b,a),b.title="Robinson Projection",b.prototype._poly=function(a,b,c){return a[b]+c*(a[b+1]+c*(a[b+2]+c*a[b+3]))},b.prototype.project=function(a,b){var c,d,e,f,g,h,i,j;g=this,j=g.ll(a,b),a=j[0],b=j[1],a=g.clon(a),d=g.rad(a),e=g.rad(b*-1),f=Math.abs(e),c=Math.floor(f*g.C1),c>=g.NODES&&(c=g.NODES-1),f=g.deg(f-g.RC1*c),c*=4,h=1e3*g._poly(g.X,c,f)*g.FXC*d,i=1e3*g._poly(g.Y,c,f)*g.FYC,e<0&&(i=-i);return[h,i]};return b}(X),bD.robinson=bb,p=function(a){function b(a){var c;b.__super__.constructor.call(this,a),c=this,c.C_x=.4222382003157712,c.C_y=1.3265004281770023,c.RC_y=.7538633073600218,c.C_p=3.5707963267948966,c.RC_p=.2800495767557787,c.EPS=1e-7,c.NITER=6}bO(b,a),b.title="Eckert IV Projection",b.prototype.project=function(a,b){var c,d,e,f,g,h,i,j,k,l,m;h=this,m=h.ll(a,b),a=m[0],b=m[1],f=h.rad(h.clon(a)),g=h.rad(b*-1),i=h.C_p*Math.sin(g),c=g*g,g*=.895168+c*(.0218849+c*.00826809),e=h.NITER;while(e>0){d=Math.cos(g),j=Math.sin(g),c=(g+j*(d+2)-i)/(1+d*(d+2)-j*j),g-=c;if(Math.abs(c)<h.EPS)break;e-=1}e===0?(k=h.C_x*f,l=g<0?-h.C_y:h.C_y):(k=h.C_x*f*(1+Math.cos(g)),l=h.C_y*Math.sin(g));return[k,l]};return b}(X),bD.eckert4=p,be=function(a){function b(){return b.__super__.constructor.apply(this,arguments)}bO(b,a),b.title="Sinusoidal Projection",b.prototype.project=function(a,b){var c,d,e,f,g,h;d=this,h=d.ll(a,b),a=h[0],b=h[1],c=d.rad(d.clon(a)),e=d.rad(b*-1),f=1032*c*Math.cos(e),g=1032*e;return[f,g]};return b}(X),bD.sinusoidal=be,O=function(a){function b(a,c,d,e,f){var g,h,i,j;c==null&&(c=1.5707963267948966),d==null&&(d=null),e==null&&(e=null),f==null&&(f=null),b.__super__.constructor.call(this,a),g=this,g.MAX_ITER=10,g.TOLERANCE=1e-7,c!=null?(h=c+c,j=Math.sin(c),i=Math.sqrt(Math.PI*2*j/(h+Math.sin(h))),g.cx=2*i/Math.PI,g.cy=i/j,g.cp=h+Math.sin(h)):d!=null&&e!=null&&typeof cz!="undefined"&&cz!==null?(g.cx=d,g.cy=e,g.cp=f):bB("kartograph.proj.Mollweide: either p or cx,cy,cp must be defined")}bO(b,a),b.title="Mollweide Projection",b.prototype.project=function(a,b){var c,d,e,f,g,h,i,j,k,l,m;h=this,m=h.ll(a,b),a=m[0],b=m[1],g=Math,c=g.abs,f=h.rad(h.clon(a)),i=h.rad(b),e=h.cp*g.sin(i),d=h.MAX_ITER;while(d!==0){j=(i+g.sin(i)-e)/(1+g.cos(i)),i-=j;if(c(j)<h.TOLERANCE)break;d-=1}d===0?i=i>=0?h.HALFPI:-h.HALFPI:i*=.5,k=1e3*h.cx*f*g.cos(i),l=1e3*h.cy*g.sin(i);return[k,l*-1]};return b}(X),bD.mollweide=O,bm=function(a){function b(a){b.__super__.constructor.call(this,a,1.0471975511965976)}bO(b,a),b.title="Wagner IV Projection";return b}(O),bD.wagner4=bm,bn=function(a){function b(a){b.__super__.constructor.call(this,a,null,.90977,1.65014,3.00896)}bO(b,a),b.title="Wagner V Projection";return b}(O),bD.wagner5=bn,K=function(a){function d(){return d.__super__.constructor.apply(this,arguments)}var b,c;bO(d,a),c=-89,b=89,d.parameters=["lon0","lat0","flip"],d.title="Loximuthal Projection (equidistant)",d.prototype.project=function(a,b){var c,d,e,f,g,h,i;e=this,i=e.ll(a,b),a=i[0],b=i[1],d=Math,c=e.rad(e.clon(a)),f=e.rad(b),f===e.phi0?g=c*d.cos(e.phi0):g=c*(f-e.phi0)/(d.log(d.tan(e.QUARTERPI+f*.5))-d.log(d.tan(e.QUARTERPI+e.phi0*.5))),g*=1e3,h=1e3*(f-e.phi0);return[g,h*-1]};return d}(X),bD.loximuthal=K,j=function(a){function g(){return g.__super__.constructor.apply(this,arguments)}var b,c,d,e,f;bO(g,a),g.title="Canters Modified Sinusoidal I",g.parameters=["lon0"],b=1.1966,c=-0.129,d=3*c,e=-0.0076,f=5*e,g.prototype.project=function(a,g){var h,i,j,k,l,m;h=this,m=h.ll(a,g),a=m[0],g=m[1],a=h.rad(h.clon(a)),g=h.rad(g),k=g*g,l=k*k,i=1e3*a*Math.cos(g)/(b+d*k+f*l),j=1e3*g*(b+c*k+e*l);return[i,j*-1]};return g}(X),bD.canters1=j,x=function(a){function o(a){o.__super__.constructor.call(this,a)}var b,c,d,e,f,g,h,i,j,k,l,m,n;bO(o,a),o.title="Hatano Projection",h=20,d=1e-7,i=1.000001,b=2.67595,c=2.43763,j=.3736990601468637,k=.4102345310814193,f=1.75859,g=1.93052,m=.5686373742600607,n=.5179951515653813,e=.85,l=1.1764705882352942,o.prototype.project=function(a,i){var j,k,l,m,n,
-o,p,q,r,s;m=this,s=m.ll(a,i),a=s[0],i=s[1],l=m.rad(m.clon(a)),n=m.rad(i),j=Math.sin(n)*(n<0?c:b);for(k=r=h;r>=1;k=r+=-1){o=(n+Math.sin(n)-j)/(1+Math.cos(n)),n-=o;if(Math.abs(o)<d)break}p=1e3*e*l*Math.cos(n*=.5),q=1e3*Math.sin(n)*(n<0?g:f);return[p,q*-1]};return o}(X),bD.hatano=x,w=function(a){function b(a){var c;b.__super__.constructor.call(this,a),c=this,c.lat1=41.737,c.p1=new O,c.p0=new be}bO(b,a),b.title="Goode Homolosine Projection",b.parameters=["lon0"],b.prototype.project=function(a,b){var c,d;c=this,d=c.ll(a,b),a=d[0],b=d[1],a=c.clon(a);return Math.abs(b)>c.lat1?c.p1.project(a,b):c.p0.project(a,b)};return b}(X),bD.goodehomolosine=w,Q=function(a){function c(a){c.__super__.constructor.call(this,a),this.r=this.HALFPI*100}var b;bO(c,a),c.title="Nicolosi Globular Projection",c.parameters=["lon0"],b=1e-10,c.prototype._visible=function(a,b){var c;c=this,a=c.clon(a);return a>-90&&a<90},c.prototype.project=function(a,c){var d,e,f,g,h,i,j,k,l,m,n,o,p;h=this,p=h.ll(a,c),a=p[0],c=p[1],f=h.rad(h.clon(a)),j=h.rad(c),Math.abs(f)<b?(n=0,o=j):Math.abs(j)<b?(n=f,o=0):Math.abs(Math.abs(f)-h.HALFPI)<b?(n=f*Math.cos(j),o=h.HALFPI*Math.sin(j)):Math.abs(Math.abs(j)-h.HALFPI)<b?(n=0,o=j):(m=h.HALFPI/f-f/h.HALFPI,d=j/h.HALFPI,l=Math.sin(j),e=(1-d*d)/(l-d),k=m/e,k*=k,g=(m*l/e-.5*m)/(1+k),i=(l/k+.5*e)/(1+1/k),n=Math.cos(j),n=Math.sqrt(g*g+n*n/(1+k)),n=h.HALFPI*(g+(f<0?-n:n)),o=Math.sqrt(i*i-(l*l/k+e*l-1)/(1+1/k)),o=h.HALFPI*(i+(j<0?o:-o)));return[n*100,o*-100]},c.prototype.sea=function(){var a,b,c,d,e;b=[],d=this.r,a=Math;for(c=e=0;e<=360;c=++e)b.push([a.cos(this.rad(c))*d,a.sin(this.rad(c))*d]);return b},c.prototype.world_bbox=function(){var a;a=this.r;return new br.BBox(-a,-a,a*2,a*2)};return c}(X),bD.nicolosi=Q,c=function(a){function b(a,c){var d;c==null&&(c=1e3),b.__super__.constructor.call(this,a),d=this,d.r=c,d.elevation0=d.to_elevation(d.lat0),d.azimuth0=d.to_azimuth(d.lon0)}bO(b,a),b.parameters=["lon0","lat0"],b.title="Azimuthal Projection",b.prototype.to_elevation=function(a){var b;b=this;return(a+90)/180*b.PI-b.HALFPI},b.prototype.to_azimuth=function(a){var b;b=this;return(a+180)/360*b.PI*2-b.PI},b.prototype._visible=function(a,b){var c,d,e,f,g;g=this,f=Math,e=g.to_elevation(b),c=g.to_azimuth(a),d=f.sin(e)*f.sin(g.elevation0)+f.cos(g.elevation0)*f.cos(e)*f.cos(c-g.azimuth0);return d>=0},b.prototype._truncate=function(a,b){var c,d,e,f,g;c=Math,d=this.r,e=c.atan2(b-d,a-d),f=d+d*c.cos(e),g=d+d*c.sin(e);return[f,g]},b.prototype.sea=function(){var a,b,c,d,e;b=[],d=this.r,a=Math;for(c=e=0;e<=360;c=++e)b.push([d+a.cos(this.rad(c))*d,d+a.sin(this.rad(c))*d]);return b},b.prototype.world_bbox=function(){var a;a=this.r;return new br.BBox(0,0,a*2,a*2)};return b}(V),R=function(a){function b(){return b.__super__.constructor.apply(this,arguments)}bO(b,a),b.title="Orthographic Projection",b.prototype.project=function(a,b){var c,d,e,f,g,h,i,j;f=this,e=Math,d=f.to_elevation(b),c=f.to_azimuth(a),h=f.r*e.cos(d)*e.sin(c-f.azimuth0),j=-f.r*(e.cos(f.elevation0)*e.sin(d)-e.sin(f.elevation0)*e.cos(d)*e.cos(c-f.azimuth0)),g=f.r+h,i=f.r+j;return[g,i]};return b}(c),bD.ortho=R,C=function(a){function b(a){b.__super__.constructor.call(this,a),this.scale=Math.sqrt(2)*.5}bO(b,a),b.title="Lambert Azimuthal Equal-Area Projection",b.prototype.project=function(a,b){var c,d,e,f,g,h,i,j,k,l;g=this.rad(b),e=this.rad(a),f=Math,h=f.sin,c=f.cos,d=f.pow(2/(1+h(this.phi0)*h(g)+c(this.phi0)*c(g)*c(e-this.lam0)),.5),d*=this.scale,j=this.r*d*c(g)*h(e-this.lam0),l=-this.r*d*(c(this.phi0)*h(g)-h(this.phi0)*c(g)*c(e-this.lam0)),i=this.r+j,k=this.r+l;return[i,k]};return b}(c),bD.laea=C,bh=function(a){function b(){return b.__super__.constructor.apply(this,arguments)}bO(b,a),b.title="Stereographic Projection",b.prototype.project=function(a,b){var c,d,e,f,g,h,i,j,k,l,m;h=this.rad(b),f=this.rad(a),g=Math,i=g.sin,c=g.cos,e=.5,d=2*e/(1+i(this.phi0)*i(h)+c(this.phi0)*c(h)*c(f-this.lam0)),k=this.r*d*c(h)*i(f-this.lam0),m=-this.r*d*(c(this.phi0)*i(h)-i(this.phi0)*c(h)*c(f-this.lam0)),j=this.r+k,l=this.r+m;return[j,l]};return b}(c),bD.stereo=bh,bc=function(a){function b(a){var c,d,e,f,g,h,i,j,k,l;b.__super__.constructor.call(this,{lon0:0,lat0:0}),this.dist=(j=a.dist)!=null?j:3,this.up=this.rad((k=a.up)!=null?k:0),this.tilt=this.rad((l=a.tilt)!=null?l:0),this.scale=1,f=Number.MAX_VALUE,e=Number.MAX_VALUE*-1;for(c=h=0;h<=179;c=++h)for(d=i=0;i<=360;d=++i)g=this.project(d-180,c-90),f=Math.min(g[0],f),e=Math.max(g[0],e);this.scale=this.r*2/(e-f),b.__super__.constructor.call(this,a);return}bO(b,a),b.parameters=["lon0","lat0","tilt","dist","up"],b.title="Satellite Projection",b.prototype.project=function(a,b,c){var d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x;c==null&&(c=0),m=this.rad(b),k=this.rad(a),l=Math,p=l.sin,f=l.cos,n=this.r,o=n*(c+6371)/3671,g=p(this.phi0)*p(m)+f(this.phi0)*f(m)*f(k-this.lam0),j=(this.dist-1)/(this.dist-g),j=(this.dist-1)/(this.dist-g),j*=this.scale,t=o*j*f(m)*p(k-this.lam0),w=-o*j*(f(this.phi0)*p(m)-p(this.phi0)*f(m)*f(k-this.lam0)),i=f(this.up),r=p(this.up),h=f(this.tilt),q=p(this.tilt),e=o*(this.dist-1),d=(w*i+t*r)*p(this.tilt/e)+h,u=(t*i-w*r)*f(this.tilt/d),x=(w*i+t*r)/d,s=n+u,v=n+x;return[s,v]},b.prototype._visible=function(a,b){var c,d,e,f;e=this.to_elevation(b),c=this.to_azimuth(a),f=Math,d=f.sin(e)*f.sin(this.elevation0)+f.cos(this.elevation0)*f.cos(e)*f.cos(c-this.azimuth0);return d>=1/this.dist},b.prototype.sea=function(){var a,b,c,d,e;b=[],d=this.r,a=Math;for(c=e=0;e<=360;c=++e)b.push([d+a.cos(this.rad(c))*d,d+a.sin(this.rad(c))*d]);return b};return b}(c),bD.satellite=bc,q=function(a){function b(){return b.__super__.constructor.apply(this,arguments)}bO(b,a),b.title="Equidistant Azimuthal Projection",b.prototype.project=function(a,b){var c,d,e,f,g,h,i,j,k,l,m,n,o;i=this,j=i.rad(b),g=i.rad(a),h=Math,k=h.sin,d=h.cos,e=k(this.phi0)*k(j)+d(this.phi0)*d(j)*d(g-this.lam0),c=h.acos(e),f=.325*c/k(c),m=this.r*f*d(j)*k(g-this.lam0),o=-this.r*f*(d(this.phi0)*k(j)-k(this.phi0)*d(j)*d(g-this.lam0)),l=this.r+m,n=this.r+o;return[l,n]},b.prototype._visible=function(a,b){return!0};return b}(c),bD.equi=q,b=function(a){function c(a){var b;b=this,a.lat0=0,c.__super__.constructor.call(this,a),b.lam0=0}var b;bO(c,a),c.title="Aitoff Projection",c.parameters=["lon0"],b=.6366197723675814,c.prototype.project=function(a,c){var d,e,f,g,h,i,j,k;g=this,k=g.ll(a,c),a=k[0],c=k[1],a=g.clon(a),f=g.rad(a),h=g.rad(c),d=.5*f,e=Math.acos(Math.cos(h)*Math.cos(d)),e!==0?(j=1/Math.sin(e),i=2*e*Math.cos(h)*Math.sin(d)*j,j*=e*Math.sin(h)):i=j=0,g.winkel&&(i=(i+f*b)*.5,j=(j+h)*.5);return[i*1e3,j*-1e3]},c.prototype._visible=function(a,b){return!0};return c}(X),bD.aitoff=b,bo=function(a){function b(a){b.__super__.constructor.call(this,a),this.winkel=!0}bO(b,a),b.title="Winkel Tripel Projection";return b}(b),bD.winkel3=bo,n=function(a){function b(a){var c,d,e;c=this,b.__super__.constructor.call(this,a),c.lat1=(d=a.lat1)!=null?d:30,c.phi1=c.rad(c.lat1),c.lat2=(e=a.lat2)!=null?e:50,c.phi2=c.rad(c.lat2)}bO(b,a),b.title="Conic Projection",b.parameters=["lon0","lat0","lat1","lat2"],b.prototype._visible=function(a,b){var c;c=this;return b>c.minLat&&b<c.maxLat},b.prototype._truncate=function(a,b){return[a,b]},b.prototype.clon=function(a){a-=this.lon0,a<-180?a+=360:a>180&&(a-=360);return a};return b}(V),D=function(a){function b(a){var c,d,e,f,g,h,i,j,k,l,m,n,o;k=this,b.__super__.constructor.call(this,a),g=Math,o=[g.sin,g.cos,g.abs,g.log,g.tan,g.pow],l=o[0],e=o[1],c=o[2],bs=o[3],n=o[4],i=o[5],k.n=h=m=l(k.phi1),f=e(k.phi1),j=c(k.phi1-k.phi2)>=1e-10,j&&(h=bs(f/e(k.phi2))/bs(n(k.QUARTERPI+.5*k.phi2)/n(k.QUARTERPI+.5*k.phi1))),k.c=d=f*i(n(k.QUARTERPI+.5*k.phi1),h)/h,c(c(k.phi0)-k.HALFPI)<1e-10?k.rho0=0:k.rho0=d*i(n(k.QUARTERPI+.5*k.phi0),-h),k.minLat=-60,k.maxLat=85}bO(b,a),b.title="Lambert Conformal Conic Projection",b.prototype.project=function(a,b){var c,d,e,f,g,h,i,j,k,l,m,n,o,p,q;l=this,i=l.rad(b),e=l.rad(l.clon(a)),g=Math,q=[g.sin,g.cos,g.abs,g.log,g.tan,g.pow],m=q[0],d=q[1],c=q[2],bs=q[3],n=q[4],j=q[5],h=l.n,c(c(i)-l.HALFPI)<1e-10?k=0:k=l.c*j(n(l.QUARTERPI+.5*i),-h),f=e*h,o=1e3*k*m(f),p=1e3*(l.rho0-k*d(f));return[o,p*-1]};return b}(n),bD.lcc=D,W=function(a){function b(){return b.__super__.constructor.apply(this,arguments)}bO(b,a);return b}(n),bl=function(){function a(a,b,c,d,e,f){var g;g=this,g.bbox=a,g.width=b,g.padding=d!=null?d:0,g.halign=e!=null?e:"center",g.valign=f!=null?f:"center",g.height=c,g.scale=Math.min((b-d*2)/a.width,(c-d*2)/a.height)}a.prototype.project=function(a,b){var c,d,e,f,g,h,i;b==null&&(b=a[1],a=a[0]),e=this,f=e.scale,c=e.bbox,d=e.height,g=e.width,h=e.halign==="center"?(g-c.width*f)*.5:e.halign==="left"?e.padding*f:g-(c.width-e.padding)*f,i=e.valign==="center"?(d-c.height*f)*.5:e.valign==="top"?e.padding*f:0,a=(a-c.left)*f+h,b=(b-c.top)*f+i;return[a,b]},a.prototype.projectPath=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r;e=this;if(a.type==="path"){d=[],b=[99999,99999,-99999,-99999],o=a.contours;for(k=0,m=o.length;k<m;k++){g=o[k],c=[];for(l=0,n=g.length;l<n;l++)p=g[l],i=p[0],j=p[1],q=e.project(i,j),i=q[0],j=q[1],c.push([i,j]),b[0]=Math.min(b[0],i),b[1]=Math.min(b[1],j),b[2]=Math.max(b[2],i),b[3]=Math.max(b[3],j);d.push(c)}f=new br.geom.Path(a.type,d,a.closed),f._bbox=b;return f}if(a.type==="circle"){r=e.project(a.x,a.y),i=r[0],j=r[1],h=a.r*e.scale;return new br.geom.Circle(i,j,h)}},a.prototype.asBBox=function(){var a;a=this;return new br.BBox(0,0,a.width,a.height)};return a}(),bl.fromXML=function(a){var b,c,e,f,g;g=Number(a.getAttribute("w")),e=Number(a.getAttribute("h")),f=Number(a.getAttribute("padding")),c=a.getElementsByTagName("bbox")[0],b=d.fromXML(c);return new br.View(b,g,e,f)},br.View=bl,br.Kartograph.prototype.dotgrid=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z,A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X;r=this,q=(T=a.layer)!=null?T:r.layerIds[r.layerIds.length-1];if(!r.layers.hasOwnProperty(q))bB('dotgrid error: layer "'+q+'" not found');else{p=r.layers[q],c=a.data,d=a.value,e=a.key,t={};if(e!=null&&bE(c)==="array")for(B=0,F=c.length;B<F;B++)w=c[B],o=w[e],t[String(o)]=w;else for(o in c)w=c[o],t[String(o)]=w;i=(U=a.style)!=null?U:{fill:"black",stroke:"none"},y=a.size,n=(V=a.gridsize)!=null?V:15,h=(W=p.dotgrid)!=null?W:p.dotgrid={gridsize:n,grid:[]};if(h.gridsize!==n){X=h.grid;for(C=0,G=X.length;C<G;C++)m=X[C],m.shape!=null&&(m.shape.remove(),m.shape=null)}if(n>0){if(h.grid.length===0)for(z=D=0,L=r.viewport.width;0<=L?D<=L:D>=L;z=D+=n)for(A=E=0,M=r.viewport.height;0<=M?E<=M:E>=M;A=E+=n){m={x:z+(Math.random()-.5)*n*.2,y:A+(Math.random()-.5)*n*.2,pathid:!1},l=!1,N=p.pathsById;for(o in N){u=N[o];for(J=0,H=u.length;J<H;J++){s=u[J];if(s.vpath.isInside(m.x,m.y)){l=!0,v=(O=t[o])!=null?O:null,x=y(v),m.pathid=o,m.shape=p.paper.circle(m.x,m.y,1);break}}if(l)break}h.grid.push(m)}P=h.grid;for(K=0,I=P.length;K<I;K++)m=P[K],m.pathid&&(v=(Q=t[m.pathid])!=null?Q:null,x=y(v),k=(R=a.duration)!=null?R:0,f=(S=a.delay)!=null?S:0,bE(f)==="function"?g=f(v):g=f,k>0&&Raphael.svg?(b=Raphael.animation({r:x*.5},k),m.shape.animate(b.delay(g))):m.shape.attr({r:x*.5}),bE(i)==="function"?j=i(v):j=i,m.shape.attr(j))}}},bq=(bM=br.filter)!=null?bM:br.filter={},bq.__knownFilter={},bq.__patternFills=0,L.prototype.SVG=function(a,b){var c,d;typeof a=="string"&&(a=window.document.createElementNS("http://www.w3.org/2000/svg",a));if(b)for(c in b)d=b[c],a.setAttribute(c,d);return a},br.Kartograph.prototype.addFilter=function(a,b,c){var d,e,f;c==null&&(c={}),f=this,d=window.document;if(br.filter[b]!=null)e=(new br.filter[b](c)).getFilter(a);else throw"unknown filter type "+b;return f.paper.defs.appendChild(e)},L.prototype.applyFilter=function(b){var c;c=this;return a("."+c.id,c.paper.canvas).attr({filter:"url(#"+b+")"})},L.prototype.applyTexture=function(a,b,c){var d,e,f,g,h,i;b==null&&(b=!1),c==null&&(c="#000"),e=this,bq.__patternFills+=1,h=e.paths,i=[];for(f=0,g=h.length;f<g;f++)d=h[f],!b||b(d.data)?i.push(d.svgPath.attr({fill:"url("+a+")"})):i.push(d.svgPath.attr("fill",c));return i},t=function(){function a(a){this.params=a!=null?a:{}}a.prototype.getFilter=function(a){var b,c;c=this,b=c.SVG("filter",{id:a}),c.buildFilter(b);return b},a.prototype._getFilter=function(){throw"not implemented"},a.prototype.SVG=function(a,b){var c,d;typeof a=="string"&&(a=window.document.createElementNS("http://www.w3.org/2000/svg",a));if(b)for(c in b)d=b[c],a.setAttribute(c,d);return a};return a}(),g=function(a){function b(){return b.__super__.constructor.apply(this,arguments)}bO(b,a),b.prototype.buildFilter=function(a){var b,c,d;d=this,b=d.SVG,c=b("feGaussianBlur",{stdDeviation:d.params.size||4,result:"blur"});return a.appendChild(c)};return b}(t),bq.blur=g,v=function(a){function b(){return b.__super__.constructor.apply(this,arguments)}bO(b,a),b.prototype.buildFilter=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o;g=this,c=(l=g.params.blur)!=null?l:4,i=(m=g.params.strength)!=null?m:1,d=(n=g.params.color)!=null?n:"#D1BEB0",typeof d=="string"&&(d=chroma.hex(d)),h=d.rgb,e=(o=g.params.inner)!=null?o:!1,f=(j=g.params.knockout)!=null?j:!1,b=(k=g.params.alpha)!=null?k:1,e?g.innerGlow(a,c,i,h,b,f):g.outerGlow(a,c,i,h,b,f)},b.prototype.outerGlow=function(a,b,c,d,e,f){var g,h,i,j,k,l,m;k=this,g=k.SVG,j=g("feColorMatrix",{"in":"SourceGraphic",type:"matrix",values:"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0",result:"mask"}),a.appendChild(j),c>0&&(m=g("feMorphology",{"in":"mask",radius:c,operator:"dilate",result:"mask"}),a.appendChild(m)),j=g("feColorMatrix",{"in":"mask",type:"matrix",values:"0 0 0 0 "+d[0]/255+" 0 0 0 0 "+d[1]/255+" 0 0 0 0 "+d[2]/255+" 0 0 0 1 0",result:"r0"}),a.appendChild(j),h=g("feGaussianBlur",{"in":"r0",stdDeviation:b,result:"r1"}),a.appendChild(h),i=g("feComposite",{operator:"out","in":"r1",in2:"mask",result:"comp"}),a.appendChild(i),l=g("feMerge"),f||l.appendChild(g("feMergeNode",{"in":"SourceGraphic"})),l.appendChild(g("feMergeNode",{"in":"r1"}));return a.appendChild(l)},b.prototype.innerGlow=function(a,b,c,d,e,f){var g,h,i,j,k,l,m;k=this,g=k.SVG,bs("innerglow"),j=g("feColorMatrix",{"in":"SourceGraphic",type:"matrix",values:"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 500 0",result:"mask"}),a.appendChild(j),m=g("feMorphology",{"in":"mask",radius:c,operator:"erode",result:"r1"}),a.appendChild(m),h=g("feGaussianBlur",{"in":"r1",stdDeviation:b,result:"r2"}),a.appendChild(h),j=g("feColorMatrix",{type:"matrix","in":"r2",values:"1 0 0 0 "+d[0]/255+" 0 1 0 0 "+d[1]/255+" 0 0 1 0 "+d[2]/255+" 0 0 0 -1 1",result:"r3"}),a.appendChild(j),i=g("feComposite",{operator:"in","in":"r3",in2:"mask",result:"comp"}),a.appendChild(i),l=g("feMerge"),f||l.appendChild(g("feMergeNode",{"in":"SourceGraphic"})),l.appendChild(g("feMergeNode",{"in":"comp"}));return a.appendChild(l)};return b}(t),bq.glow=v,br.Kartograph.prototype.addGeoPath=function(a,b,c){var d,e,f;b==null&&(b=[]),c==null&&(c=""),d=this,f=d.getGeoPathStr(a,b),e=d.paper.path(f),c!==""&&e.node.setAttribute("class",c);return e},br.Kartograph.prototype.getGeoPathStr=function(a,b){var c,d,e,f,g,h,i;b==null&&(b=[]),e=this,type(b)==="string"&&(b=b.split("")),b.length===0&&b.push("M"),f="";for(d in a){g=a[d],c=(i=b[d])!=null?i:"L",h=e.lonlat2xy(g);if(isNaN(h[0])||isNaN(h[1]))continue;f+=c+h[0]+","+h[1]}return f},br.Kartograph.prototype.addGeoPolygon=function(a,b){var c,d,e;e=this,c=["M"];for(d in a)c.push("L");c.push("Z");return e.addGeoPath(a,c,b)},S=function(){function b(b){this.zoomOut=bP(this.zoomOut,this),this.zoomIn=bP(this.zoomIn,this);var c,d,e,f,g,h,i,j;f=this,f.map=b,c=b.container,d=function(b,c){var d,e,f,g;c==null&&(c=[]),e=a('<div class="'+b+'" />');for(f=0,g=c.length;f<g;f++)d=c[f],e.append(d);return e},e=function(b){return a(b.target).addClass("md")},g=function(b){return a(b.target).removeClass("md")},j=d("plus"),j.mousedown(e),j.mouseup(g),j.click(f.zoomIn),i=d("minus"),i.mousedown(e),i.mouseup(g),i.click(f.zoomOut),h=d("zoom-control",[j,i]),c.append(h)}b.prototype.zoomIn=function(a){var b;b=this,b.map.opts.zoom+=1;return b.map.resize()},b.prototype.zoomOut=function(a){var b;b=this,b.map.opts.zoom-=1,b.map.opts.zoom<1&&(b.map.opts.zoom=1);return b.map.resize()};return b}(),bd=function(){function a(a,b,c){var d,e,f,g,h=this;a==null&&(a=[0,1]),b==null&&(b=null),c==null&&(c=null),this.rangedScale=bP(this.rangedScale,this),this.scale=bP(this.scale,this),e=this,g=[];for(d in a){if(bE(c)==="function"&&c(a[d])===!1)continue;b!=null?bE(b)==="function"?f=b(a[d]):f=a[d][b]:f=a[d],isNaN(f)||g.push(f)}g=g.sort(function(a,b){return a-b}),e.values=g,e._range=[0,1],e.rangedScale.range=function(a){e._range=a;return e.rangedScale}}a.prototype.scale=function(a){return a},a.prototype.rangedScale=function(a){var b,c;b=this,a=b.scale(a),c=b._range;return a*(c[1]-c[0])+c[0]};return a}(),H=function(a){function b(){this.scale=bP(this.scale,this);return b.__super__.constructor.apply(this,arguments)}bO(b,a),b.prototype.scale=function(a){var b,c;b=this,c=b.values;return(a-c[0])/(c[c.length-1]-c[0])};return b}(bd),I=function(a){function b(){this.scale=bP(this.scale,this);return b.__super__.constructor.apply(this,arguments)}bO(b,a),b.prototype.scale=function(a){var b,c;b=this,c=b.values,bs=Math.log;return(bs(a)-bs(c[0]))/(bs(c[c.length-1])-bs(c[0]))};return b}(bd),bf=function(a){function b(){this.scale=bP(this.scale,this);return b.__super__.constructor.apply(this,arguments)}bO(b,a),b.prototype.scale=function(a){var b,c;b=this,c=b.values;return Math.sqrt((a-c[0])/(c[c.length-1]-c[0]))};return b}(bd),Y=function(a){function b(){this.scale=bP(this.scale,this);return b.__super__.constructor.apply(this,arguments)}bO(b,a),b.prototype.scale=function(a){var b,c,d,e,f,g;d=this,g=d.values,c=g.length-1;for(b in g){f=g[Number(b)],e=g[Number(b)+1];if(a===f)return b/c;if(b<c&&a>f&&a<e)return b/c+(a-f)/(e-f)}};return b}(bd),br.scale={},br.scale.identity=function(a){return(new bd(domain,prop,bq)).rangedScale},br.scale.linear=function(a,b,c){return(new H(a,b,c)).rangedScale},br.scale.log=function(a,b,c){return(new I(a,b,c)).rangedScale},br.scale.sqrt=function(a,b,c){return(new bf(a,b,c)).rangedScale},br.scale.quantile=function(a,b,c){return(new Y(a,b,c)).rangedScale},bj=function(){function b(b){a=this,a.location=b.location,a.data=b.data,a.map=b.map,a.layers=b.layers,a.key=b.key,a.x=b.x,a.y=b.y}var a;a=null,b.prototype.init=function(){return a},b.prototype.overlaps=function(a){return!1},b.prototype.update=function(b){return a},b.prototype.nodes=function(){return[]},b.prototype.clear=function(){return a};return b}(),br.Symbol=bj,bk=function(){function c(a){this._initTooltips=bP(this._initTooltips,this),this._noverlap=bP(this._noverlap,this),this._kMeans=bP(this._kMeans,this);var d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w;b=this,m=["data","location","type","map"],k=["filter","tooltip","click","delay","sortBy","clustering","aggregate","clusteringOpts","mouseenter","mouseleave"];for(n=0,r=m.length;n<r;n++){l=m[n];if(a[l]!=null)b[l]=a[l];else throw"SymbolGroup: missing argument '"+l+"'"}for(o=0,s=k.length;o<s;o++)l=k[o],a[l]!=null&&(b[l]=a[l]);d=b.type;if(d==null)bB("could not resolve symbol type",b.type);else{v=d.props;for(p=0,t=v.length;p<t;p++)l=v[p],a[l]!=null&&(b[l]=a[l]);b.layers={mapcanvas:b.map.paper},w=d.layers;for(q=0,u=w.length;q<u;q++)h=w[q],j=c._layerid++,g="sl_"+j,h.type==="svg"?i=b.map.createSVGLayer(g):h.type==="html"&&(i=b.map.createHTMLLayer(g)),b.layers[h.id]=i;b.symbols=[];for(f in b.data)e=b.data[f],bE(b.filter)==="function"?b.filter(e,f)&&b.add(e,f):b.add(e,f);b.layout(),b.render(),b.map.addSymbolGroup(b)}}var b;b=null,c.prototype.add=function(a,c){var d,e,f,g,h,i,j,k;b=this,d=b.type,e=b._evaluate(b.location,a,c),bE(e)==="array"&&(e=new br.LonLat(e[0],e[1])),g={layers:b.layers,location:e,data:a,key:c!=null?c:b.symbols.length,map:b.map},k=d.props;for(i=0,j=k.length;i<j;i++)f=k[i],b[f]!=null&&(g[f]=b._evaluate(b[f],a,c));h=new d(g),b.symbols.push(h);return h},c.prototype.layout=function(){var a,c,d,e,f,g,h,i,j,k;j=b.symbols;for(h=0,i=j.length;h<i;h++){f=j[h],c=f.location;if(bE(c)==="string"){k=c.split("."),a=k[0],e=k[1],d=b.map.getLayerPath(a,e);if(d!=null)g=b.map.viewBC.project(d.path.centroid());else{bB("could not find layer path "+a+"."+e);continue}}else g=b.map.lonlat2xy(c);f.x=g[0],f.y=g[1]}b.clustering==="k-means"?b._kMeans():b.clustering==="noverlap"&&b._noverlap();return b},c.prototype.render=function(){var c,d,e,f,g,h,i,j,k,l,m;b=this,b.sortBy&&(f="asc",bE(b.sortBy)==="string"&&(b.sortBy=b.sortBy.split(" ",2),e=b.sortBy[0],f=(k=b.sortBy[1])!=null?k:"asc"),b.symbols=b.symbols.sort(function(a,c){var d,g,h;bE(b.sortBy)==="function"?(g=b.sortBy(a.data,a),h=b.sortBy(c.data,c)):(g=a[e],h=c[e]);if(g===h)return 0;d=f==="asc"?1:-1;return g>h?1*d:-1*d})),l=b.symbols;for(g=0,i=l.length;g<i;g++){d=l[g],d.render(),m=d.nodes();for(h=0,j=m.length;h<j;h++)c=m[h],c.symbol=d}bE(b.tooltip)==="function"&&b._initTooltips(),a.each(["click","mouseenter","mouseleave"],function(e,f){var g,h,i,j;if(bE(b[f])==="function"){i=b.symbols,j=[];for(g=0,h=i.length;g<h;g++)d=i[g],j.push(function(){var e,g,h,i,j=this;h=d.nodes(),i=[];for(e=0,g=h.length;e<g;e++)c=h[e],i.push(a(c)[f](function(c){var d;d=c.target;while(!d.symbol)d=a(d).parent().get(0);c.stopPropagation();return b[f](d.symbol.data,d.symbol,c)}));return i}.call(this));return j}});return b},c.prototype.tooltips=function(a){b=this,b.tooltips=a,b._initTooltips();return b},c.prototype.remove=function(a){var c,d,e,f,g,h,i,j,k;b=this,d=[],i=b.symbols;for(g=0,h=i.length;g<h;g++){f=i[g];if(a!=null&&!a(f.data)){d.push(f);continue}try{f.clear()}catch(l){bB("error: symbolgroup.remove")}}if(a==null){j=b.layers,k=[];for(c in j)e=j[c],c!=="mapcanvas"?k.push(e.remove()):k.push(void 0);return k}return b.symbols=d},c.prototype._evaluate=function(a,b,c){var d;return bE(a)==="function"?d=a(b,c):d=a},c.prototype._kMeans=function(){var a,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x;b=this,(u=b.osymbols)==null&&(b.osymbols=b.symbols),a=b.type,b.clusteringOpts!=null&&(k=b.clusteringOpts.size),k==null&&(k=64),c=bR().iterations(16).size(k),v=b.osymbols;for(m=0,q=v.length;m<q;m++)j=v[m],c.add({x:j.x,y:j.y});g=c.means(),h=[];for(n=0,r=g.length;n<r;n++){f=g[n];if(f.size===0)continue;d=[],w=f.indices;for(o=0,s=w.length;o<s;o++)e=w[o],d.push(b.osymbols[e].data);d=b.aggregate(d),l={layers:b.layers,location:!1,data:d,map:b.map},x=a.props;for(p=0,t=x.length;p<t;p++)i=x[p],b[i]!=null&&(l[i]=b._evaluate(b[i],d));j=new a(l),j.x=f.x,j.y=f.y,h.push(j)}return b.symbols=h},c.prototype._noverlap=function(){var a,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z,A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V;b=this,(S=b.osymbols)==null&&(b.osymbols=b.symbols),j=3,a=b.type;if(bQ.call(a.props,"radius")<0)bB('noverlap layout only available for symbols with property "radius"');else{A=b.osymbols.slice(),b.clusteringOpts!=null&&(D=b.clusteringOpts.tolerance,n=b.clusteringOpts.maxRatio),D==null&&(D=.05),n==null&&(n=.8);for(h=H=0,T=j-1;0<=T?H<=T:H>=T;h=0<=T?++H:--H){A.sort(function(a,b){return b.radius-a.radius}),k=A.length,o=[];for(p=I=0,U=k-3;0<=U?I<=U:I>=U;p=0<=U?++I:--I){x=A[p];if(!x)continue;u=x.radius*(1-D),l=x.x-u,s=x.x+u,B=x.y-u,c=x.y+u,i=[];for(q=J=V=p+1,Q=k-2;V<=Q?J<=Q:J>=Q;q=V<=Q?++J:--J){y=A[q];if(!y)continue;v=y.radius,m=y.x-v,t=y.x+v,C=y.y-v,d=y.y+v,v/x.radius<n&&!(s<m||t<l)&&!(c<C||d<B)&&(f=y.x-x.x,g=y.y-x.y,f*f+g*g<(u+v)*(u+v)&&i.push(q))}if(i.length>0){e=[x.data],r=x.radius*x.radius;for(K=0,L=i.length;K<L;K++)h=i[K],e.push(A[h].data),r+=A[h].radius*A[h].radius;e=b.aggregate(e),z={layers:b.layers,location:!1,data:e,map:b.map},R=a.props;for(O=0,M=R.length;O<M;O++)p=R[O],b[p]!=null&&(z[p]=b._evaluate(b[p],e));w=new a(z),E=x.radius*x.radius/r,F=x.x*E,G=x.y*E;for(P=0,N=i.length;P<N;P++)h=i[P],y=A[h],E=y.radius*y.radius/r,F+=y.x*E,G+=y.y*E,A[h]=void 0;w.x=F,w.y=G,A[p]=void 0,o.push(w)}else o.push(x)}A=o}return b.symbols=A}},c.prototype._initTooltips=function(){var c,d,e,f,g,h,i,j,k,l,m;b=this,f=b.tooltip,l=b.symbols;for(h=0,j=l.length;h<j;h++){e=l[h],c={position:{target:"mouse",viewport:a(window),adjust:{x:7,y:7}},show:{delay:20},content:{},events:{show:function(b,c){return a(".qtip").filter(function(){return this!==c.elements.tooltip.get(0)}).hide()}}},g=f(e.data,e.key),bE(g)==="string"?c.content.text=g:bE(g)==="array"&&(c.content.title=g[0],c.content.text=g[1]),m=e.nodes();for(i=0,k=m.length;i<k;i++)d=m[i],a(d).qtip(c)}},c.prototype.onResize=function(){var a,c,d,e;b=this,b.layout(),e=b.symbols;for(c=0,d=e.length;c<d;c++)a=e[c],a.update()},c.prototype.update=function(a,c,d){var e,f,g,h,i,j,k,l;b=this,a==null&&(a={}),k=b.symbols;for(g=0,i=k.length;g<i;g++){f=k[g],l=b.type.props;for(h=0,j=l.length;h<j;h++)e=l[h],a[e]!=null?f[e]=b._evaluate(a[e],f.data):b[e]!=null&&(f[e]=b._evaluate(b[e],f.data));f.update(c,d)}return b};return c}(),bk._layerid=0,br.SymbolGroup=bk,br.Kartograph.prototype.addSymbols=function(a){a.map=this;return new bk(a)},br.dorlingLayout=function(b,c){var d,e,f,g,h,i,j,k,l,m,n,o,p,q,r;c==null&&(c=40),n=[],a.each(b.symbols,function(a,b){return n.push({i:a,x:b.path.attrs.cx,y:b.path.attrs.cy,r:b.path.attrs.r})}),n.sort(function(a,b){return b.r-a.r}),f=function(){var a,c,d;for(c=0,d=n.length;c<d;c++)a=n[c],b.symbols[a.i].path.attr({cx:a.x,cy:a.y})};for(o=r=1;1<=c?r<=c:r>=c;o=1<=c?++r:--r)for(l in n)for(m in n)if(m>l){d=n[l],e=n[m];if(d.x+d.r<e.x-e.r||d.x-d.r>e.x+e.r)continue;if(d.y+d.r<e.y-e.r||d.y-d.r>e.y+e.r)continue;i=d.x-e.x,j=d.y-e.y,h=i*i+j*j,p=d.r+e.r,q=p*p,h<q&&(g=Math.sqrt(h),k=10/g,d.x+=i*k*(1-d.r/p),d.y+=j*k*(1-d.r/p),e.x-=i*k*(1-e.r/p),e.y-=j*k*(1-e.r/p))}return f()},h=function(b){function c(a){this.nodes=bP(this.nodes,this),this.clear=bP(this.clear,this),this.update=bP(this.update,this),this.render=bP(this.render,this),this.overlaps=bP(this.overlaps,this);var b,d,e;b=this,c.__super__.constructor.call(this,a),b.radius=(d=a.radius)!=null?d:4,b.style=a.style,b.attrs=a.attrs,b.title=a.title,b["class"]=(e=a["class"])!=null?e:"bubble"}bO(c,b),c.prototype.overlaps=function(a){var b,c,d,e,f,g,h,i,j,k,l;d=this,k=[d.x,d.y,d.radius],g=k[0],i=k[1],e=k[2],l=[a.x,a.y,a.radius],h=l[0],j=l[1],f=l[2];if(g-e>h+f||g+e<h-f||i-e>j+f||i+e<j-f)return!1;b=g-h,c=i-j;return b*b+c*c>(e+f)*(e+f)?!1:!0},c.prototype.render=function(a){var b;b=this,b.path==null&&(b.path=b.layers.mapcanvas.circle(b.x,b.y,b.radius)),b.update(),b.map.applyCSS(b.path);return b},c.prototype.update=function(b,c){var d,e,f;b==null&&(b=!1),c==null&&(c="expo-out"),e=this,f=e.path,d={cx:e.x,cy:e.y,r:e.radius},e.attrs!=null&&(d=a.extend(d,e.attrs)),b?f.animate(d,b,c):f.attr(d),f.node!=null&&(e.style!=null&&f.node.setAttribute("style",e.style),e["class"]!=null&&f.node.setAttribute("class",e["class"])),e.title!=null&&f.attr("title",e.title);return e},c.prototype.clear=function(){var a;a=this,a.path.remove();return a},c.prototype.nodes=function(){var a;a=this;return[a.path.node]};return c}(bj),h.props=["radius","style","class","title","attrs"],h.layers=[],br.Bubble=h,A=function(b){function c(a){var b,d,e,f,g,h;b=this,c.__super__.constructor.call(this,a),b.icon=(e=a.icon)!=null?e:"",b.offset=(f=a.offset)!=null?f:[0,0],b.iconsize=(g=a.iconsize)!=null?g:[10,10],b["class"]=(h=a["class"])!=null?h:"",b.title=(d=a.title)!=null?d:""}bO(c,b),c.prototype.render=function(b){var c,d;d=this,c=d.map.container,d.img=a("<img />"),d.img.attr({src:d.icon,title:d.title,alt:d.title,width:d.iconsize[0],height:d.iconsize[1]}),d.img.addClass(d["class"]),d.img.css({position:"absolute","z-index":1e3,cursor:"pointer"}),d.img[0].symbol=d,c.append(d.img);return d.update()},c.prototype.update=function(){var a;a=this;return a.img.css({left:a.x+a.offset[0]+"px",top:a.y+a.offset[1]+"px"})},c.prototype.clear=function(){var a;a=this,a.img.remove();return a},c.prototype.nodes=function(){var a;a=this;return[a.img]};return c}(br.Symbol),A.props=["icon","offset","class","title","iconsize"],A.layers=[],br.Icon=A,bi=function(a){function b(a){var c,d,e,f,g;c=this,b.__super__.constructor.call(this,a),c.text=(d=a.text)!=null?d:"",c.style=(e=a.style)!=null?e:"",c["class"]=(f=a["class"])!=null?f:"",c.offset=(g=a.offset)!=null?g:[0,0]}bO(b,a),b.prototype.render=function(a){var b,c;c=this,c.lbl=b=c.layers.mapcanvas.text(c.x,c.y,c.text),c.update();return c},b.prototype.update=function(){var a;a=this,a.lbl.attr({x:a.x+a.offset[0],y:a.y+a.offset[1]}),a.lbl.node.setAttribute("style",a.style);return a.lbl.node.setAttribute("class",a["class"])},b.prototype.clear=function(){var a;a=this,a.lbl.remove();return a},b.prototype.nodes=function(){var a;a=this;return[a.lbl.node]};return b}(br.Symbol),bi.props=["text","style","class","offset"],bi.layers=[],br.Label=bi,z=function(b){function c(a){var b,d,e,f;b=this,c.__super__.constructor.call(this,a),b.text=(d=a.text)!=null?d:"",b.style=(e=a.style)!=null?e:"",b["class"]=(f=a["class"])!=null?f:""}bO(c,b),c.prototype.render=function(b){var c,d,e;e=this,c=a("<div>"+e.text+"</div>"),c.css({width:"50px",position:"absolute",left:"-25px","text-align":"center"}),e.lbl=d=a('<div class="label" />'),d.append(c),e.layers.lbl.append(d),c.css({height:c.height()+"px",top:c.height()*-0.4+"px"}),e.update();return e},c.prototype.update=function(){var a;a=this;return a.lbl.css({position:"absolute",left:a.x+"px",top:a.y+"px"})},c.prototype.clear=function(){var a;a=this,a.lbl.remove();return a},c.prototype.nodes=function(){var a;a=this;return[a.lbl[0]]};return c}(br.Symbol),z.props=["text","style","class"],z.layers=[{id:"lbl",type:"html"}],br.HtmlLabel=z,E=function(b){function c(a){this.nodes=bP(this.nodes,this),this.clear=bP(this.clear,this),this.update=bP(this.update,this),this.render=bP(this.render,this);var b,d,e;b=this,c.__super__.constructor.call(this,a),b.labelattrs=(d=a.labelattrs)!=null?d:{},b.buffer=a.buffer,b.center=(e=a.center)!=null?e:!0}bO(c,b),c.prototype.render=function(a){var b;b=this,b.title!=null&&String(b.title).trim()!==""&&(b.buffer&&(b.bufferlabel=b.layers.mapcanvas.text(b.x,b.y,b.title)),b.label=b.layers.mapcanvas.text(b.x,b.y,b.title)),c.__super__.render.call(this,a);return b},c.prototype.update=function(b,d){var e,f,g,h,i;b==null&&(b=!1),d==null&&(d="expo-out"),f=this,c.__super__.update.call(this,b,d),f.label!=null&&(g=f.map.viewport,e=a.extend({},f.labelattrs),h=f.x,i=f.y,f.center?i-=0:h>g.width*.5?(e["text-anchor"]="end",h-=f.radius+5):h<g.width*.5&&(e["text-anchor"]="start",h+=f.radius+5),e.x=h,e.y=i,f.buffer&&(f.bufferlabel.attr(e),f.bufferlabel.attr({stroke:"#fff",fill:"#fff","stroke-linejoin":"round","stroke-linecap":"round","stroke-width":6})),f.label.attr(e),f.label.toFront());return f},c.prototype.clear=function(){var a;a=this;return c.__super__.clear.apply(this,arguments)},c.prototype.nodes=function(){var a,b;a=this,b=c.__super__.nodes.apply(this,arguments),a.label&&b.push(a.label.node),a.bufferlabel&&b.push(a.bufferlabel.node);return b};return c}(h),E.props=["radius","style","class","title","labelattrs","buffer","center","attrs"],E.layers=[],br.LabeledBubble=E,U=function(a){function c(a){var d,e,f,g,h,i,j,k,l,m;b=this,c.__super__.constructor.call(this,a),b.radius=(j=a.radius)!=null?j:4,b.styles=(k=a.styles)!=null?k:"",b.colors=(l=a.colors)!=null?l:["#3cc","#c3c","#33c","#cc3"],b.titles=(m=a.titles)!=null?m:["","","","",""],b.values=(e=a.values)!=null?e:[],b.border=(f=a.border)!=null?f:!1,b.borderWidth=(g=a.borderWidth)!=null?g:2,b["class"]=(h=a["class"])!=null?h:"piechart",(i=(d=Raphael.fn).pieChart)==null&&(d.pieChart=bp)}var b;bO(c,a),b=null,c.prototype.overlaps=function(a){var c,d,e,f,g,h,i,j,k,l;k=[b.x,b.y,b.radius],g=k[0],i=k[1],e=k[2],l=[a.x,a.y,a.radius],h=l[0],j=l[1],f=l[2];if(g-e>h+f||g+e<h-f||i-e>j+f||i+e<j-f)return!1;c=g-h,d=i-j;return c*c+d*d>(e+f)*(e+f)?!1:!0},c.prototype.render=function(a){var c;b=this,b.border!=null&&(c=b.layers.mapcanvas.circle(b.x,b.y,b.radius+b.borderWidth).attr({stroke:"none",fill:b.border})),b.chart=b.layers.mapcanvas.pieChart(b.x,b.y,b.radius,b.values,b.titles,b.colors,"none"),b.chart.push(c);return b},c.prototype.update=function(a){var c;return},c.prototype.clear=function(){var a,c,d,e;b=this,e=b.chart;for(c=0,d=e.length;c<d;c++)a=e[c],a.remove();return b},c.prototype.nodes=function(){var a,c,d,e,f;e=b.chart,f=[];for(c=0,d=e.length;c<d;c++)a=e[c],f.push(a.node);return f};return c}(bj),U.props=["radius","values","styles","class","titles","colors","border","borderWidth"],U.layers=[],br.PieChart=U,bp=function(a,b,c,d,e,f,g){var h,i,j,k,l,m,n,o,p,q,r;if(isNaN(a)||isNaN(b)||isNaN(c))return[];k=this,m=Math.PI/180,i=k.set(),n=function(a,b,c,d,e,f){var g,h,i,j;g=a+c*Math.cos(-d*m),h=a+c*Math.cos(-e*m),i=b+c*Math.sin(-d*m),j=b+c*Math.sin(-e*m);return k.path(["M",a,b,"L",g,i,"A",c,c,0,+(e-d>180),0,h,j,"z"]).attr(f)},h=-270,o=0,l=function(e){var j,k,l,m,p,q,r;r=d[e],j=360*r/o,q=h+j*.5,k=f[e],m=500,l=30,p=n(a,b,c,h,h+j,{fill:k,stroke:g,"stroke-width":1}),p.mouseover(function(){p.stop().animate({transform:"s1.1 1.1 "+a+" "+b},m,"elastic")}),p.mouseout(function(){p.stop().animate({transform:""},m,"elastic")}),h+=j,i.push(p
-)};for(q=0,r=d.length;q<r;q++)p=d[q],o+=p;for(j in d)l(j);return i},drawStackedBars=function(a,b,c,d,e,f,g,h){function k(a,b,c,d,e){return i.rect(a,b,c,d).attr(e)}var i=this,j=this.set(),l=0,m=0,n=function(f){var i=e[f],n=d*i/m,o=a-c*.5,p=b+d*.5-l,q=c,r=g[f],s=500,t=30,u=k(o,p-n,q,n,{fill:r,stroke:h,"stroke-width":1});l+=n,u.mouseover(function(){u.stop().animate({transform:"s1.1 1.1 "+a+" "+b},s,"elastic")}).mouseout(function(){u.stop().animate({transform:""},s,"elastic")}),j.push(u)};for(var o=0,p=e.length;o<p;o++)m+=e[o];for(o=0;o<p;o++)n(o);return j},bg=function(a){function b(a){var c,d,e,f,g,h,i,j,k,l;c=this,b.__super__.constructor.call(this,a),c.styles=(i=a.styles)!=null?i:"",c.colors=(j=a.colors)!=null?j:[],c.titles=(k=a.titles)!=null?k:["","","","",""],c.values=(l=a.values)!=null?l:[],c.width=(e=a.width)!=null?e:17,c.height=(f=a.height)!=null?f:30,c["class"]=(g=a["class"])!=null?g:"barchart",(h=(d=Raphael.fn).drawStackedBarChart)==null&&(d.drawStackedBarChart=drawStackedBars)}bO(b,a),b.prototype.overlaps=function(a){var b,c,d,e,f,g,h,i,j,k,l;d=this,k=[d.x,d.y,d.radius],g=k[0],i=k[1],e=k[2],l=[a.x,a.y,a.radius],h=l[0],j=l[1],f=l[2];if(g-e>h+f||g+e<h-f||i-e>j+f||i+e<j-f)return!1;b=g-h,c=i-j;return b*b+c*c>(e+f)*(e+f)?!1:!0},b.prototype.render=function(a){var b,c,d,e,f,g;d=this,e=d.width,c=d.height,f=d.x,g=d.y,b=d.layers.mapcanvas.rect(f-e*.5-2,g-c*.5-2,e+4,c+4).attr({stroke:"none",fill:"#fff"}),d.chart=d.layers.mapcanvas.drawStackedBarChart(d.x,d.y,d.width,d.height,d.values,d.titles,d.colors,"none"),d.chart.push(b);return d},b.prototype.update=function(){var a,b;a=this;return},b.prototype.clear=function(){var a,b,c,d,e;a=this,e=a.chart;for(c=0,d=e.length;c<d;c++)b=e[c],b.remove();a.chart=[];return a},b.prototype.nodes=function(){var a,b,c,d,e,f;b=this,e=b.chart,f=[];for(c=0,d=e.length;c<d;c++)a=e[c],f.push(a.node);return f};return b}(br.Symbol),bg.props=["values","styles","class","titles","colors","width","height"],bg.layers=[],br.StackedBarChart=bg}).call(this);
+ */
+(function () {
+ function bS() {
+ function h(a, b, c) {
+ f(a.point, b) < f(c.point, b) && (c = a), a.left && (c = h(a.left, b, c));
+ if (a.right) {
+ var d = a.point[a.axis] - b[a.axis];
+ d * d < f(c.point, b) && (c = h(a.right, b, c))
+ }
+ return c
+ }
+
+ function g(a) {
+ return function (b, c) {
+ b = b[a], c = c[a];
+ return b < c ? -1 : b > c ? 1 : 0
+ }
+ }
+
+ function f(a, c) {
+ var d = 0;
+ for (var e = 0; e < b.length; e++) {
+ var f = b[e], g = a[f] - c[f];
+ d += g * g
+ }
+ return d
+ }
+
+ function e(a, c) {
+ if (!!a.length) {
+ var d = b[c % b.length], f = a.length >> 1;
+ a.sort(g(d));
+ return{axis: d, point: a[f], left: e(a.slice(0, f), c + 1), right: e(a.slice(f + 1), c + 1)}
+ }
+ }
+
+ var a = {}, b = ["x", "y"], c, d = [];
+ a.axes = function (c) {
+ if (!arguments.length)return b;
+ b = c;
+ return a
+ }, a.points = function (b) {
+ if (!arguments.length)return d;
+ d = b, c = null;
+ return a
+ }, a.find = function (b) {return h(a.root(), b, c).point}, a.root = function (a) {return c || (c = e(d, 0))};
+ return a
+ }
+
+ function bR() {
+ var a = {}, b = [], c = 1, d = 1;
+ a.size = function (b) {
+ if (!arguments.length)return d;
+ d = b;
+ return a
+ }, a.iterations = function (b) {
+ if (!arguments.length)return c;
+ c = b;
+ return a
+ }, a.add = function (c) {
+ b.push(c);
+ return a
+ }, a.means = function () {
+ var a = [], e = {}, f = Math.min(d, b.length);
+ for (var g = 0, h = 2 * f; g < h; g++) {
+ var i = b[~~(Math.random() * b.length)], j = i.x + "/" + i.y;
+ if (!(j in e)) {
+ e[j] = 1;
+ if (a.push({x: i.x, y: i.y}) >= f)break
+ }
+ }
+ f = a.length;
+ for (var k = 0; k < c; k++) {
+ var l = bS().points(a);
+ for (var g = 0; g < f; g++) {
+ var m = a[g];
+ m.sumX = 0, m.sumY = 0, m.size = 0, m.points = [], m.indices = []
+ }
+ for (var g = 0; g < b.length; g++) {
+ var n = b[g], m = l.find(n);
+ m.sumX += n.x, m.sumY += n.y, m.size++, m.points.push(n), m.indices.push(g)
+ }
+ for (var g = 0; g < f; g++) {
+ var m = a[g];
+ if (!m.size)continue;
+ m.x = m.sumX / m.size, m.y = m.sumY / m.size
+ }
+ }
+ return a
+ };
+ return a
+ }
+
+ var a, b, c, d, e, f, g, h, i, j, k, l, n, o, p, q, r, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, $, _, ba, bb, bc, bd, be, bf, bg, bh, bi, bj, bk, bl, bm, bn, bo, bp, bq, br, bs, bt, bu, bv, bw, bx, by, bz, bA, bB, bC, bD, bE, bF, bG, bH, bI, bJ, bK, bL, bM, bN = {}.hasOwnProperty, bO = function (a, b) {
+ function d() {this.constructor = a}
+
+ for (var c in b)bN.call(b, c) && (a[c] = b[c]);
+ d.prototype = b.prototype, a.prototype = new d, a.__super__ = b.prototype;
+ return a
+ }, bP = function (a, b) {return function () {return a.apply(b, arguments)}}, bQ = [].indexOf || function (a) {
+ for (var b = 0, c = this.length; b < c; b++)if (b in this && this[b] === a)return b;
+ return-1
+ };
+ bz = typeof exports != "undefined" && exports !== null ? exports : this, br = bz.$K = window.Kartograph = (bH = bz.Kartograph) != null ? bH : bz.Kartograph = {}, br.version = "0.5.2", a = bz.jQuery, br.__verbose = !1, bB = function (a) {try {return console.warn.apply(console, arguments)} catch (b) {try {return opera.postError.apply(opera, arguments)} catch (b) {return alert(Array.prototype.join.call(arguments, " "))}}}, bs = function (a) {if (br.__verbose)try {return console.debug.apply(console, arguments)} catch (b) {try {return opera.postError.apply(opera, arguments)} catch (b) {return alert(Array.prototype.join.call(arguments, " "))}}}, (bI = (bF = String.prototype).trim) == null && (bF.trim = function () {return this.replace(/^\s+|\s+$/g, "")}), Array.prototype.indexOf || (Array.prototype.indexOf = function (a) {
+ "use strict";
+ if (this == null)throw new TypeError;
+ var b = Object(this), c = b.length >>> 0;
+ if (c === 0)return-1;
+ var d = 0;
+ arguments.length > 0 && (d = Number(arguments[1]), d != d ? d = 0 : d != 0 && d != Infinity && d != -Infinity && (d = (d > 0 || -1) * Math.floor(Math.abs(d))));
+ if (d >= c)return-1;
+ var e = d >= 0 ? d : Math.max(c - Math.abs(d), 0);
+ for (; e < c; e++)if (e in b && b[e] === a)return e;
+ return-1
+ }), bE = function () {
+ var a, b, c, d, e;
+ a = {}, e = "Boolean Number String Function Array Date RegExp Undefined Null".split(" ");
+ for (c = 0, d = e.length; c < d; c++)b = e[c], a["[object " + b + "]"] = b.toLowerCase();
+ return function (b) {
+ var c;
+ c = Object.prototype.toString.call(b);
+ return a[c] || "object"
+ }
+ }(), d = function () {
+ function a(a, b, c, d) {
+ var e;
+ a == null && (a = 0), b == null && (b = 0), c == null && (c = null), d == null && (d = null), e = this, c === null ? (e.xmin = Number.MAX_VALUE, e.xmax = Number.MAX_VALUE * -1) : (e.xmin = e.left = a, e.xmax = e.right = a + c, e.width = c), d === null ? (e.ymin = Number.MAX_VALUE, e.ymax = Number.MAX_VALUE * -1) : (e.ymin = e.top = b, e.ymax = e.bottom = d + b, e.height = d);
+ return
+ }
+
+ a.prototype.update = function (a, b) {
+ var c;
+ b == null && (b = a[1], a = a[0]), c = this, c.xmin = Math.min(c.xmin, a), c.ymin = Math.min(c.ymin, b), c.xmax = Math.max(c.xmax, a), c.ymax = Math.max(c.ymax, b), c.left = c.xmin, c.top = c.ymin, c.right = c.xmax, c.bottom = c.ymax, c.width = c.xmax - c.xmin, c.height = c.ymax - c.ymin;
+ return this
+ }, a.prototype.intersects = function (a) {return a.left < s.right && a.right > s.left && a.top < s.bottom && a.bottom > s.top}, a.prototype.inside = function (a, b) {
+ var c;
+ c = this;
+ return a >= c.left && a <= c.right && b >= c.top && b <= c.bottom
+ }, a.prototype.join = function (a) {
+ var b;
+ b = this, b.update(a.left, a.top), b.update(a.right, a.bottom);
+ return this
+ };
+ return a
+ }(), d.fromXML = function (a) {
+ var b, c, d, e;
+ d = Number(a.getAttribute("x")), e = Number(a.getAttribute("y")), c = Number(a.getAttribute("w")), b = Number(a.getAttribute("h"));
+ return new br.BBox(d, e, c, b)
+ }, br.BBox = d, (bJ = br.geom) == null && (br.geom = {}), (bK = (bG = br.geom).clipping) == null && (bG.clipping = {}), l = function () {
+ function f() {}
+
+ var a, b, c, d, e;
+ b = 0, c = 1, d = 2, a = 4, e = 8, f.prototype.compute_out_code = function (a, b, c) {
+ var d, e;
+ e = this, d = e.INSIDE, b < a.left ? d |= e.LEFT : b > a.right && (d |= e.RIGHT), c < a.top ? d |= e.TOP : c > a.bottom && (d |= e.BOTTOM);
+ return d
+ }, f.prototype.clip = function (a, b, c, d, e) {
+ var f, g, h, i, j, k, l;
+ j = this, g = j.compute_out_code(a, b, c), h = j.compute_out_code(a, d, e), f = False;
+ while (True) {
+ if (!(g | h)) {
+ f = True;
+ break
+ }
+ if (g & h)break;
+ i = code === 0 ? h : g, i & j.TOP ? (k = b + (d - b) * (a.top - c) / (e - c), l = a.top) : i & j.BOTTOM ? (k = b + (d - b) * (a.bottom - c) / (e - c), l = a.bottom) : i & j.RIGHT ? (l = c + (e - c) * (a.right - b) / (d - b), k = a.right) : i & j.LEFT && (l = c + (e - c) * (a.left - b) / (d - b), k = a.left), i === g ? (b = k, c = l, g = j.compute_out_code(a, b, c)) : (d = k, e = l, h = j.compute_out_code(a, d, e))
+ }
+ return f ? [b, c, d, e] : null
+ };
+ return f
+ }(), br.geom.clipping.CohenSutherland = l, B = function () {
+ function b(b, c, d) {
+ var e, f;
+ f = this, f.container = e = a(b), c == null && (c = e.width()), d == null && (d = e.height()), d === 0 && (d = "auto"), f.size = {h: d, w: c}, f.markers = [], f.pathById = {}, f.container.addClass("kartograph")
+ }
+
+ b.prototype.createSVGLayer = function (b) {
+ var c, d, e, f, g, h, i, j;
+ f = this, (j = f._layerCnt) == null && (f._layerCnt = 0), e = f._layerCnt++, i = f.viewport, d = f.container, g = Raphael(d[0], i.width, i.height), h = a(g.canvas), h.css({position: "absolute", top: "0px", left: "0px", "z-index": e + 5}), d.css("position") === "static" && d.css({position: "relative", height: i.height + "px"}), h.addClass(b), c = a("desc", g.canvas).text(), a("desc", g.canvas).text(c.replace("with ", "with kartograph " + br.version + " and "));
+ return g
+ }, b.prototype.createHTMLLayer = function (b) {
+ var c, d, e, f, g, h;
+ f = this, g = f.viewport, c = f.container, (h = f._layerCnt) == null && (f._layerCnt = 0), e = f._layerCnt++, d = a('<div class="layer ' + b + '" />'), d.css({position: "absolute", top: "0px", left: "0px", width: g.width + "px", height: g.height + "px", "z-index": e + 5}), c.append(d);
+ return d
+ }, b.prototype.loadMap = function (b, c, d) {
+ var e, f, g, h;
+ f = this, e = a.Deferred(), f.clear(), f.opts = d != null ? d : {}, (h = (g = f.opts).zoom) == null && (g.zoom = 1), f.mapLoadCallback = c, f._loadMapDeferred = e, f._lastMapUrl = b, f.cacheMaps && br.__mapCache[b] != null ? f._mapLoaded(br.__mapCache[b]) : a.ajax({url: b, dataType: "text", success: f._mapLoaded, context: f, error: function (a, b, c) {return bB(a, b, c)}});
+ return e.promise()
+ }, b.prototype.setMap = function (a, b) {
+ var c, d, e;
+ c = this, c.opts = b != null ? b : {}, (e = (d = c.opts).zoom) == null && (d.zoom = 1), c._lastMapUrl = "string", c._mapLoaded(a)
+ }, b.prototype._mapLoaded = function (b) {
+ var c, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s;
+ h = this, h.cacheMaps && ((o = br.__mapCache) == null && (br.__mapCache = {}), br.__mapCache[h._lastMapUrl] = b);
+ try {b = a(b)} catch (t) {
+ bB("something went horribly wrong while parsing svg"), h._loadMapDeferred.reject("could not parse svg");
+ return
+ }
+ h.svgSrc = b, c = a("view", b), bs("got svg src", h.svgSrc), h.paper == null && (m = h.size.w, f = h.size.h, f === "auto" && (j = c.attr("w") / c.attr("h"), f = m / j), h.viewport = new d(0, 0, m, f)), l = h.viewport, bs("got viewport", h.viewport), h.viewAB = e = br.View.fromXML(c[0]), bs("got first view", h.viewAB), i = (p = h.opts.padding) != null ? p : 0, g = (q = h.opts.halign) != null ? q : "center", k = (r = h.opts.valign) != null ? r : "center", bs("got alignment", g, k), n = (s = h.opts.zoom) != null ? s : 1, h.viewBC = new br.View(h.viewAB.asBBox(), l.width * n, l.height * n, i, g, k), bs("got second view", h.viewBC), h.proj = br.Proj.fromXML(a("proj", c)[0]), bs("got projection", h.proj), h.mapLoadCallback != null && h.mapLoadCallback(h), h._loadMapDeferred != null && h._loadMapDeferred.resolve(h)
+ }, b.prototype.addLayer = function (b, c) {
+ var d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w;
+ c == null && (c = {}), i = this, (u = i.layerIds) == null && (i.layerIds = []), (v = i.layers) == null && (i.layers = {}), i.paper == null && (i.paper = i.createSVGLayer()), l = b, bE(c) === "object" ? (h = c.name, j = c.key, o = c.title) : c = {}, h == null && (h = l), m = a("#" + l, i.svgSrc);
+ if (m.length !== 0) {
+ g = new L(h, j, i, c.filter), d = a("*", m[0]);
+ for (q = 0, s = d.length; q < s; q++)n = d[q], g.addPath(n, o);
+ g.paths.length > 0 && (i.layers[h] = g, i.layerIds.push(h)), e = ["click", "mouseenter", "mouseleave", "dblclick", "mousedown", "mouseup", "mouseover", "mouseout"];
+ for (r = 0, t = e.length; r < t; r++)f = e[r], bE(c[f]) === "function" && g.on(f, c[f]);
+ if (c.styles != null) {
+ w = c.styles;
+ for (k in w)p = w[k], g.style(k, p)
+ }
+ c.tooltips != null && g.tooltips(c.tooltips);
+ return i
+ }
+ }, b.prototype.getLayer = function (a) {
+ var b;
+ b = this;
+ if (b.layers[a] == null) {
+ bB("could not find layer " + a);
+ return null
+ }
+ return b.layers[a]
+ }, b.prototype.getLayerPath = function (a, b) {
+ var c, d;
+ d = this, c = d.getLayer(a);
+ if (c != null)return bE(b) === "object" ? c.getPaths(b)[0] : c.getPath(b);
+ return null
+ }, b.prototype.onLayerEvent = function (a, b, c) {
+ var d;
+ d = this, d.getLayer(c).on(a, b);
+ return d
+ }, b.prototype.addMarker = function (a) {
+ var b, c;
+ b = this, b.markers.push(a), c = b.viewBC.project(b.viewAB.project(b.proj.project(a.lonlat.lon, a.lonlat.lat)));
+ return a.render(c[0], c[1], b.container, b.paper)
+ }, b.prototype.clearMarkers = function () {
+ var a, b, c, d, e;
+ b = this, e = b.markers;
+ for (c = 0, d = e.length; c < d; c++)a = e[c], a.clear();
+ return b.markers = []
+ }, b.prototype.fadeIn = function (a) {
+ var b, c, d, e, f, g, h, i, j, k, l;
+ a == null && (a = {}), f = this, e = (i = a.layer) != null ? i : f.layerIds[f.layerIds.length - 1], c = (j = a.duration) != null ? j : 500, k = f.layers[e].pathsById, l = [];
+ for (d in k)h = k[d], l.push(function () {
+ var a, d, e;
+ e = [];
+ for (a = 0, d = h.length; a < d; a++)g = h[a], bE(c) === "function" ? b = c(g.data) : b = c, g.svgPath.attr("opacity", 0), e.push(g.svgPath.animate({opacity: 1}, b));
+ return e
+ }());
+ return l
+ }, b.prototype.loadCoastline = function () {
+ var b;
+ b = this;
+ return a.ajax({url: "coastline.json", success: b.renderCoastline, context: b})
+ }, b.prototype.resize = function (a, b) {
+ var c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s;
+ g = this, c = g.container, a == null && (a = c.width()), b == null && (b = c.height()), g.viewport = k = new br.BBox(0, 0, a, b), g.paper != null && g.paper.setSize(k.width, k.height), k = g.viewport, h = (o = g.opts.padding) != null ? o : 0, d = (p = g.opts.halign) != null ? p : "center", j = (q = g.opts.valign) != null ? q : "center", l = g.opts.zoom, g.viewBC = new br.View(g.viewAB.asBBox(), k.width * l, k.height * l, h, d, j), r = g.layers;
+ for (e in r)f = r[e], f.setView(g.viewBC);
+ if (g.symbolGroups != null) {
+ s = g.symbolGroups;
+ for (m = 0, n = s.length; m < n; m++)i = s[m], i.onResize()
+ }
+ }, b.prototype.lonlat2xy = function (a) {
+ var b, c;
+ c = this, a.length === 2 && (a = new br.LonLat(a[0], a[1])), a.length === 3 && (a = new br.LonLat(a[0], a[1], a[2])), b = c.proj.project(a.lon, a.lat, a.alt);
+ return c.viewBC.project(c.viewAB.project(b))
+ }, b.prototype.showZoomControls = function () {
+ var a;
+ a = this, a.zc = new S(a);
+ return a
+ }, b.prototype.addSymbolGroup = function (a) {
+ var b, c;
+ b = this, (c = b.symbolGroups) == null && (b.symbolGroups = []);
+ return b.symbolGroups.push(a)
+ }, b.prototype.removeSymbols = function (a) {
+ var b, c, d, e, f, g;
+ b = this;
+ if (a != null)return b.symbolGroups[a].remove();
+ f = b.symbolGroups, g = [];
+ for (d = 0, e = f.length; d < e; d++)c = f[d], g.push(c.remove());
+ return g
+ }, b.prototype.clear = function () {
+ var b, c, d, e, f, g;
+ c = this;
+ if (c.layers != null) {
+ for (b in c.layers)c.layers[b].remove();
+ c.layers = {}, c.layerIds = []
+ }
+ if (c.symbolGroups != null) {
+ g = c.symbolGroups;
+ for (e = 0, f = g.length; e < f; e++)d = g[e], d.remove();
+ c.symbolGroups = []
+ }
+ if (c.paper != null) {
+ a(c.paper.canvas).remove();
+ return c.paper = void 0
+ }
+ }, b.prototype.loadCSS = function (b, c) {
+ var d;
+ d = this;
+ if (a.browser.msie)return a.ajax({url: b, dataType: "text", success: function (a) {
+ d.styles = br.parsecss(a);
+ return c()
+ }, error: function (a, c, d) {return bB("error while loading " + b, a, c, d)}});
+ a("body").append('<link rel="stylesheet" href="' + b + '" />');
+ return c()
+ }, b.prototype.applyCSS = function (a, b) {
+ var c, d, e, f, g, h, i, j, k, l, m, n, o, p, q;
+ e = this;
+ if (e.styles == null)return a;
+ (n = e._pathTypes) == null && (e._pathTypes = ["path", "circle", "rectangle", "ellipse"]), (o = e._regardStyles) == null && (e._regardStyles = ["fill", "stroke", "fill-opacity", "stroke-width", "stroke-opacity"]);
+ for (h in e.styles) {
+ f = h, p = f.split(",");
+ for (j = 0, l = p.length; j < l; j++) {
+ i = p[j], f = i.split(" "), f = f[f.length - 1], f = f.split(":");
+ if (f.length > 1)continue;
+ f = f[0].split("."), c = f.slice(1);
+ if (c.length > 0 && c.indexOf(b) < 0)continue;
+ f = f[0];
+ if (e._pathTypes.indexOf(f) >= 0 && f !== a.type)continue;
+ g = e.styles[h], q = e._regardStyles;
+ for (k = 0, m = q.length; k < m; k++)d = q[k], g[d] != null && a.attr(d, g[d])
+ }
+ }
+ return a
+ }, b.prototype.style = function (a, b, c, d, e) {
+ var f;
+ f = this, a = f.getLayer(a);
+ if (a != null)return a.style(b, c, d, e)
+ };
+ return b
+ }(), br.Kartograph = B, br.map = function (a, b, c) {return new B(a, b, c)}, br.__mapCache = {}, J = function () {
+ function a(a, b, c) {c == null && (c = 0), this.lon = Number(a), this.lat = Number(b), this.alt = Number(c)}
+
+ a.prototype.distance = function (a) {
+ var b, c, d, e, f, g, h, i, j;
+ j = this, b = 6371, g = Math.PI / 180, e = (a.lat - j.lat) * g, f = (a.lon - j.lon) * g, h = j.lat * g, i = a.lat * g, c = Math.sin(e / 2) * Math.sin(e / 2) + Math.sin(f / 2) * Math.sin(f / 2) * Math.cos(h) * Math.cos(i), d = 2 * Math.atan2(Math.sqrt(c), Math.sqrt(1 - c));
+ return b * d
+ };
+ return a
+ }(), F = function (a) {
+ function b(a, c, d) {d == null && (d = 0), b.__super__.constructor.call(this, c, a, d)}
+
+ bO(b, a);
+ return b
+ }(J), br.LonLat = J, br.LatLon = F, L = function () {
+ function b(a, b, c, d) {
+ var e;
+ e = this, e.id = a, e.path_id = b, e.paper = c.paper, e.view = c.viewBC, e.map = c, e.filter = d
+ }
+
+ b.prototype.addPath = function (a, b) {
+ var c, d, e, f, g, h, i;
+ d = this, (g = d.paths) == null && (d.paths = []), c = new M(a, d.id, d.map, b);
+ if (bE(d.filter) === "function" && d.filter(c.data) === !1)c.remove(); else {
+ d.paths.push(c);
+ if (d.path_id != null) {
+ (h = d.pathsById) == null && (d.pathsById = {}), (i = (e = d.pathsById)[f = c.data[d.path_id]]) == null && (e[f] = []);
+ return d.pathsById[c.data[d.path_id]].push(c)
+ }
+ }
+ }, b.prototype.hasPath = function (a) {
+ var b;
+ b = this;
+ return b.pathsById != null && b.pathsById[a] != null
+ }, b.prototype.getPathsData = function () {
+ var a, b, c, d, e, f;
+ a = this, c = [], f = a.paths;
+ for (d = 0, e = f.length; d < e; d++)b = f[d], c.push(b.data);
+ return c
+ }, b.prototype.getPath = function (a) {
+ var b;
+ b = this;
+ return b.hasPath(a) ? b.pathsById[a][0] : null
+ }, b.prototype.getPaths = function (a) {
+ var b, c, d, e, f, g, h, i;
+ e = this, d = [];
+ if (bE(a) === "object") {
+ i = e.paths;
+ for (g = 0, h = i.length; g < h; g++) {
+ f = i[g], c = !0;
+ for (b in a)c = c && f.data[b] === a[b];
+ c && d.push(f)
+ }
+ }
+ return d
+ }, b.prototype.setView = function (a) {
+ var b, c, d, e, f;
+ b = this, f = b.paths;
+ for (d = 0, e = f.length; d < e; d++)c = f[d], c.setView(a);
+ return b
+ }, b.prototype.remove = function () {
+ var a, b, c, d, e, f;
+ a = this, e = a.paths, f = [];
+ for (c = 0, d = e.length; c < d; c++)b = e[c], f.push(b.remove());
+ return f
+ }, b.prototype.style = function (a, b, c, d) {
+ var e, f, g, h, i, j, k, l, m, n, o;
+ j = this;
+ if (bE(a) === "object") {
+ for (i in a)l = a[i], j.style(i, l);
+ return j
+ }
+ c == null && (c = 0), d == null && (d = 0), o = j.paths;
+ for (m = 0, n = o.length; m < n; m++)k = o[m], l = bx(b, k.data), h = bx(c, k.data), g = bx(d, k.data), h > 0 ? (f = {}, f[a] = l, e = Raphael.animation(f, h * 1e3), k.svgPath.animate(e.delay(g * 1e3))) : k.svgPath.attr(a, l);
+ return j
+ }, b.prototype.on = function (b, c) {
+ var d, e, f, g, h, i, j;
+ f = this, d = function () {
+ function a(a, b, c) {this.type = a, this.cb = b, this.layer = c, this.handle = bP(this.handle, this)}
+
+ a.prototype.handle = function (a) {
+ var b;
+ f = this, b = f.layer.map.pathById[a.target.getAttribute("id")];
+ return f.cb(b.data, b.svgPath, a)
+ };
+ return a
+ }(), e = new d(b, c, f), j = f.paths;
+ for (h = 0, i = j.length; h < i; h++)g = j[h], a(g.svgPath.node).bind(b, e.handle);
+ return f
+ }, b.prototype.tooltips = function (b, c) {
+ var d, e, f, g, h, i, j;
+ d = this, f = function (b, d) {
+ var e;
+ e = {position: {target: "mouse", viewport: a(window), adjust: {x: 7, y: 7}}, show: {delay: c != null ? c : 20}, events: {show: function (b, c) {return a(".qtip").filter(function () {return this !== c.elements.tooltip.get(0)}).hide()}}, content: {}}, d != null ? typeof d == "string" ? e.content.text = d : a.isArray(d) && (e.content.title = d[0], e.content.text = d[1]) : e.content.text = "n/a";
+ return a(b.svgPath.node).qtip(e)
+ }, j = d.paths;
+ for (h = 0, i = j.length; h < i; h++)e = j[h], g = bx(b, e.data), f(e, g);
+ return d
+ }, b.prototype.sort = function (a) {
+ var b, c, d, e, f, g;
+ c = this, c.paths.sort(function (b, c) {
+ var d, e, f;
+ d = a(b.data), e = a(c.data);
+ return d === e ? 0 : (f = d > e) != null ? f : {1: -1}
+ }), b = !1, g = c.paths;
+ for (e = 0, f = g.length; e < f; e++)d = g[e], b && d.svgPath.insertAfter(b.svgPath), b = d;
+ return c
+ };
+ return b
+ }(), bx = function (a, b) {return bE(a) === "function" ? a(b) : a}, bt = 0, M = function () {
+ function a(a, b, c, d) {
+ var e, f, g, h, i, j, k, l, m, n, o, p, q;
+ h = this, i = c.paper, n = c.viewBC, h.path = j = br.geom.Path.fromSVG(a), h.vpath = n.projectPath(j), h.svgPath = h.vpath.toSVG(i), c.styles == null ? h.svgPath.node.setAttribute("class", b) : c.applyCSS(h.svgPath, b), l = "path_" + bt++, h.svgPath.node.setAttribute("id", l), c.pathById[l] = h, f = {};
+ for (g = p = 0, q = a.attributes.length - 1; 0 <= q ? p <= q : p >= q; g = 0 <= q ? ++p : --p)e = a.attributes[g], e.name.substr(0, 5) === "data-" && (m = e.value, o = Number(m), m.trim() !== "" && o === m && !isNaN(o) && (m = o), f[e.name.substr(5)] = m);
+ h.data = f, bE(d) === "string" ? k = d : bE(d) === "function" && (k = d(f)), k != null && h.svgPath.attr("title", k)
+ }
+
+ a.prototype.setView = function (a) {
+ var b, c, d;
+ b = this, c = a.projectPath(b.path), b.vpath = c;
+ if (b.path.type === "path") {
+ d = c.svgString();
+ return b.svgPath.attr({path: d})
+ }
+ if (b.path.type === "circle")return b.svgPath.attr({cx: c.x, cy: c.y, r: c.r})
+ }, a.prototype.remove = function () {
+ var a;
+ a = this;
+ return a.svgPath.remove()
+ };
+ return a
+ }(), br.parsecss = function (a, b) {
+ var c, d, e, f, g, h, i, j;
+ f = {}, a = bu(a), j = a.split("`b%");
+ for (h = 0, i = j.length; h < i; h++) {
+ c = j[h], c = c.split("%b`");
+ if (c.length < 2)continue;
+ c[0] = by(c[0]), e = bw(c[1]);
+ if (f[c[0]] != null)for (d in e)g = e[d], f[c[0]][d] = g; else f[c[0]] = e
+ }
+ if (bE(b) === "function")b(f); else return f
+ }, bv = {}, bw = function (a) {
+ var b, c, d, e, f, g;
+ d = bv[a].replace(/^{|}$/g, ""), d = bu(d), c = {}, g = d.split(";");
+ for (e = 0, f = g.length; e < f; e++) {
+ b = g[e], b = b.split(":");
+ if (b.length < 2)continue;
+ c[by(b[0])] = by(b.slice(1).join(":"))
+ }
+ return c
+ }, Z = /{[^{}]*}/, _ = /\[[^\[\]]*\]|{[^{}]*}|\([^()]*\)|function(\s+\w+)?(\s*%b`\d+`b%){2}/, $ = /(?:\/\*(?:[^\*]|\*[^\/])*\*\/)|(\\.|"(?:[^\\\"]|\\.|\\\n)*"|'(?:[^\\\']|\\.|\\\n)*')/g, ba = /%\w`(\d+)`\w%/, bA = 0, bu = function (a, b) {
+ var c, d, e;
+ a = a.replace($, function (a, b) {
+ var c;
+ if (!b)return"";
+ c = "%s`" + ++bA + "`s%", bv[bA] = b.replace(/^\\/, "");
+ return c
+ }), c = b ? _ : Z;
+ while (d = c.exec(a))e = "%b`" + ++bA + "`b%", bv[bA] = d[0], a = a.replace(c, e);
+ return a
+ }, by = function (a) {
+ var b;
+ if (a == null)return a;
+ while (b = ba.exec(a))a = a.replace(ba, bv[b[1]]);
+ return a.trim()
+ }, (bL = br.geom) == null && (br.geom = {}), T = function () {
+ function a(a, b, c) {
+ var d;
+ c == null && (c = !0), d = this, d.type = a, d.contours = b, d.closed = c
+ }
+
+ a.prototype.clipToBBox = function (a) {throw"path clipping is not implemented yet"}, a.prototype.toSVG = function (a) {
+ var b;
+ b = this.svgString();
+ return a.path(b)
+ }, a.prototype.svgString = function () {
+ var a, b, c, d, e, f, g, h, i, j, k, l, m;
+ d = this, e = "", c = d.closed ? "Z M" : "M", l = d.contours;
+ for (h = 0, j = l.length; h < j; h++) {
+ a = l[h], b = !0, e += e === "" ? "M" : c;
+ for (i = 0, k = a.length; i < k; i++)m = a[i], f = m[0], g = m[1], b || (e += "L"), e += f + "," + g, b = !1
+ }
+ d.closed && (e += "Z");
+ return e
+ }, a.prototype.area = function () {
+ var a, b, c, d, e, f, g, h, i;
+ d = this;
+ if (d.areas != null)return d._area;
+ d.areas = [], d._area = 0, h = d.contours;
+ for (e = 0, g = h.length; e < g; e++) {
+ b = h[e], a = 0;
+ for (c = f = 0, i = b.length - 2; 0 <= i ? f <= i : f >= i; c = 0 <= i ? ++f : --f)a += b[c][0] * b[c + 1][1] - b[c + 1][0] * b[c][1];
+ a *= .5, a = a, d.areas.push(a), d._area += a
+ }
+ return d._area
+ }, a.prototype.centroid = function () {
+ var a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K;
+ p = this;
+ if (p._centroid != null)return p._centroid;
+ c = p.area(), f = g = 0;
+ for (k = A = 0, G = p.contours.length - 1; 0 <= G ? A <= G : A >= G; k = 0 <= G ? ++A : --A) {
+ e = p.contours[k], d = [], n = e.length;
+ for (l = B = 0, H = n - 1; 0 <= H ? B <= H : B >= H; l = 0 <= H ? ++B : --B) {
+ q = e[l], r = e[(l + 1) % n], h = 0, d.push(q), q[0] === r[0] && (h = Math.abs(q[1] - r[1])), q[1] === r[1] && (h = Math.abs(q[0] - r[0]));
+ if (h > 10) {
+ a = Math.floor(h * 2);
+ for (s = C = 1, I = a - 1; 1 <= I ? C <= I : C >= I; s = 1 <= I ? ++C : --C)t = [q[0] + s / a * (r[0] - q[0]), q[1] + s / a * (r[1] - q[1])], d.push(t)
+ }
+ }
+ b = p.areas[k], w = y = x = z = 0, n = d.length, E = [], u = 0;
+ for (l = D = 0, J = n - 1; 0 <= J ? D <= J : D >= J; l = 0 <= J ? ++D : --D)q = d[l], r = d[(l + 1) % n], i = r[0] - q[0], j = r[1] - q[1], o = Math.sqrt(i * i + j * j), E.push(o), u += o;
+ for (l = F = 0, K = n - 1; 0 <= K ? F <= K : F >= K; l = 0 <= K ? ++F : --F)q = d[l], v = E[l] / u, w += v * q[0], y += v * q[1];
+ m = b / c, f += w * m, g += y * m
+ }
+ p._centroid = [f, g];
+ return p._centroid
+ }, a.prototype.isInside = function (a, b) {
+ var c, d, e, f, g, h;
+ f = this, c = f._bbox;
+ if (a < c[0] || a > c[2] || b < c[1] || b > c[3])return!1;
+ for (e = g = 0, h = f.contours.length - 1; 0 <= h ? g <= h : g >= h; e = 0 <= h ? ++g : --g) {
+ d = f.contours[e];
+ if (bC(d, [a, b]))return!0
+ }
+ return!1
+ };
+ return a
+ }(), br.geom.Path = T, k = function (a) {
+ function b(a, c, d) {this.x = a, this.y = c, this.r = d, b.__super__.constructor.call(this, "circle", null, !0)}
+
+ bO(b, a), b.prototype.toSVG = function (a) {
+ var b;
+ b = this;
+ return a.circle(b.x, b.y, b.r)
+ }, b.prototype.centroid = function () {
+ var a;
+ a = this;
+ return[a.x, a.y]
+ }, b.prototype.area = function () {
+ var a;
+ a = this;
+ return Math.PI * a.r * m.r
+ };
+ return b
+ }(T), br.geom.Circle = k, T.fromSVG = function (a) {
+ var b, c, d, e, f, g, h, i, j, k, l, m, n, o;
+ e = [], m = a.nodeName, k = null;
+ if (m === "path") {
+ i = a.getAttribute("d").trim(), h = Raphael.parsePathString(i), b = h[h.length - 1] === "Z", l = b ? "Z M" : "M", d = [];
+ for (n = 0, o = h.length; n < o; n++) {
+ c = h[n];
+ if (c.length === 0)continue;
+ c[0] === "M" ? (d.length > 2 && (e.push(d), d = []), d.push([c[1], c[2]])) : c[0] === "L" ? d.push([c[1], c[2]]) : c[0] === "Z" && d.length > 2 && (e.push(d), d = [])
+ }
+ d.length > 2 && (e.push(d), d = []), k = new br.geom.Path(m, e, b)
+ } else m === "circle" && (f = a.getAttribute("cx"), g = a.getAttribute("cy"), j = a.getAttribute("r"), k = new br.geom.Circle(f, g, j));
+ return k
+ }, G = function () {
+ function a(a) {this.points = a}
+
+ a.prototype.clipToBBox = function (b) {
+ var c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u;
+ l = this, c = (new br.geom.clipping.CohenSutherland).clip, k = [], f = [], e = !1;
+ for (d = q = 0, r = l.points.length - 2; 0 <= r ? q <= r : q >= r; d = 0 <= r ? ++q : --q) {
+ s = l.points[d], g = s[0], h = s[1], t = l.points[d + 1], i = t[0], j = t[1];
+ try {u = c(b, g, h, i, j), m = u[0], o = u[1], n = u[2], p = u[3], e = !0, k.push([m, o]), (i !== n || j !== o || d === len(l.points) - 2) && k.push([n, p])} catch (v) {e && k.length > 1 && (f.push(new a(k)), k = []), e = !1}
+ }
+ k.length > 1 && f.push(new a(k));
+ return f
+ }, a.prototype.toSVG = function () {
+ var a, b, c, d, e, f, g, h;
+ b = this, a = [], g = b.points;
+ for (e = 0, f = g.length; e < f; e++)h = g[e], c = h[0], d = h[1], a.push(c + "," + d);
+ return"M" + a.join("L")
+ };
+ return a
+ }(), br.geom.Line = G, bC = function (a, b) {
+ var c, d, e, f, g, h, i, j, k, l, m, n, o, p, q;
+ h = Math.PI, d = Math.atan2, k = h * 2, g = a.length, c = 0;
+ for (f = p = 0, q = g - 1; 0 <= q ? p <= q : p >= q; f = 0 <= q ? ++p : --p) {
+ l = a[f][0] - b[0], n = a[f][1] - b[1], m = a[(f + 1) % g][0] - b[0], o = a[(f + 1) % g][1] - b[1], i = d(n, l), j = d(o, m), e = j - i;
+ while (e > h)e -= k;
+ while (e < -h)e += k;
+ c += e
+ }
+ return Math.abs(c) >= h
+ }, bD = br.proj = {}, Function.prototype.bind = function (a) {
+ var b;
+ b = this;
+ return function () {return b.apply(a, arguments)}
+ }, V = function () {
+ function a(a) {
+ var b, c, d;
+ b = this, b.lon0 = (c = a.lon0) != null ? c : 0, b.lat0 = (d = a.lat0) != null ? d : 0, b.PI = Math.PI, b.HALFPI = b.PI * .5, b.QUARTERPI = b.PI * .25, b.RAD = b.PI / 180, b.DEG = 180 / b.PI, b.lam0 = b.rad(this.lon0), b.phi0 = b.rad(this.lat0), b.minLat = -90, b.maxLat = 90
+ }
+
+ a.parameters = [], a.title = "Projection", a.prototype.rad = function (a) {return a * this.RAD}, a.prototype.deg = function (a) {return a * this.DEG}, a.prototype.plot = function (a, b) {
+ var c, d, e, f, g, h, i, j, k, l, m;
+ b == null && (b = !0), f = [], c = !0;
+ for (j = 0, k = a.length; j < k; j++)l = a[j], e = l[0], d = l[1], g = this._visible(e, d), g && (c = !1), m = this.project(e, d), h = m[0], i = m[1], !g && b ? f.push(this._truncate(h, i)) : f.push([h, i]);
+ return c ? null : [f]
+ }, a.prototype.sea = function () {
+ var a, b, c, d, e, f, g, h, i, j, k, l, m, n;
+ f = this, e = f.project.bind(this), d = [], a = f.lon0, f.lon0 = 0;
+ for (c = g = -180; g <= 180; c = ++g)d.push(e(c, f.maxLat));
+ for (b = h = k = f.maxLat, l = f.minLat; k <= l ? h <= l : h >= l; b = k <= l ? ++h : --h)d.push(e(180, b));
+ for (c = i = 180; i >= -180; c = --i)d.push(e(c, f.minLat));
+ for (b = j = m = f.minLat, n = f.maxLat; m <= n ? j <= n : j >= n; b = m <= n ? ++j : --j)d.push(e(-180, b));
+ f.lon0 = a;
+ return d
+ }, a.prototype.world_bbox = function () {
+ var a, b, c, d, e, f;
+ b = this.project.bind(this), d = this.sea(), a = new br.BBox;
+ for (e = 0, f = d.length; e < f; e++)c = d[e], a.update(c[0], c[1]);
+ return a
+ }, a.prototype.toString = function () {
+ var a;
+ a = this;
+ return"[Proj: " + a.name + "]"
+ };
+ return a
+ }(), V.fromXML = function (a) {
+ var b, c, d, e, f, g, h;
+ d = a.getAttribute("id"), e = {};
+ for (c = g = 0, h = a.attributes.length - 1; 0 <= h ? g <= h : g >= h; c = 0 <= h ? ++g : --g)b = a.attributes[c], b.name !== "id" && (e[b.name] = b.value);
+ f = new br.proj[d](e), f.name = d;
+ return f
+ }, br.Proj = V, o = function (a) {
+ function b(a) {
+ var c, d, e;
+ a == null && (a = {}), c = this, c.flip = Number((d = a.flip) != null ? d : 0), c.flip === 1 && (a.lon0 = (e = -a.lon0) != null ? e : 0), b.__super__.constructor.call(this, a)
+ }
+
+ bO(b, a), b.parameters = ["lon0", "flip"], b.title = "Cylindrical Projection", b.prototype._visible = function (a, b) {return!0}, b.prototype.clon = function (a) {
+ a -= this.lon0, a < -180 ? a += 360 : a > 180 && (a -= 360);
+ return a
+ }, b.prototype.ll = function (a, b) {return this.flip === 1 ? [-a, -b] : [a, b]};
+ return b
+ }(V), r = function (a) {
+ function b() {return b.__super__.constructor.apply(this, arguments)}
+
+ bO(b, a), b.title = "Equirectangular Projection", b.prototype.project = function (a, b) {
+ var c;
+ c = this.ll(a, b), a = c[0], b = c[1], a = this.clon(a);
+ return[a * Math.cos(this.phi0) * 1e3, b * -1 * 1e3]
+ };
+ return b
+ }(o), bD.lonlat = r, i = function (a) {
+ function b(a) {
+ var c;
+ b.__super__.constructor.call(this, a), this.lat1 = (c = a.lat1) != null ? c : 0, this.phi1 = this.rad(this.lat1)
+ }
+
+ bO(b, a), b.parameters = ["lon0", "lat1", "flip"], b.title = "Cylindrical Equal Area", b.prototype.project = function (a, b) {
+ var c, d, e, f, g;
+ g = this.ll(a, b), a = g[0], b = g[1], c = this.rad(this.clon(a)), d = this.rad(b * -1), e = c * Math.cos(this.phi1), f = Math.sin(d) / Math.cos(this.phi1);
+ return[e * 1e3, f * 1e3]
+ };
+ return b
+ }(o), bD.cea = i, u = function (a) {
+ function b(a) {a.lat1 = 45, b.__super__.constructor.call(this, a)}
+
+ bO(b, a), b.title = "Gall-Peters Projection", b.parameters = ["lon0", "flip"];
+ return b
+ }(i), bD.gallpeters = u, y = function (a) {
+ function b(a) {a.lat1 = 37.7, b.__super__.constructor.call(this, a)}
+
+ bO(b, a), b.title = "Hobo-Dyer Projection", b.parameters = ["lon0", "flip"];
+ return b
+ }(i), bD.hobodyer = y, f = function (a) {
+ function b(a) {a.lat1 = 30, b.__super__.constructor.call(this, a)}
+
+ bO(b, a), b.title = "Behrmann Projection", b.parameters = ["lon0", "flip"];
+ return b
+ }(i), bD.behrmann = f, e = function (a) {
+ function b(a) {a.lat1 = 50, b.__super__.constructor.call(this, a)}
+
+ bO(b, a), b.title = "Balthasart Projection", b.parameters = ["lon0", "flip"];
+ return b
+ }(i), bD.balthasart = e, N = function (a) {
+ function b(a) {b.__super__.constructor.call(this, a), this.minLat = -85, this.maxLat = 85}
+
+ bO(b, a), b.title = "Mercator Projection", b.prototype.project = function (a, b) {
+ var c, d, e, f, g, h, i;
+ f = this, i = f.ll(a, b), a = i[0], b = i[1], d = Math, c = f.rad(f.clon(a)), e = f.rad(b * -1), g = c * 1e3, h = d.log((1 + d.sin(e)) / d.cos(e)) * 1e3;
+ return[g, h]
+ };
+ return b
+ }(o), bD.mercator = N, X = function (a) {
+ function b() {return b.__super__.constructor.apply(this, arguments)}
+
+ bO(b, a), b.title = "Pseudo-Cylindrical Projection";
+ return b
+ }(o), P = function (a) {
+ function b(a) {
+ var c;
+ b.__super__.constructor.call(this, a), c = this, c.A0 = .8707, c.A1 = -0.131979, c.A2 = -0.013791, c.A3 = .003971, c.A4 = -0.001529, c.B0 = 1.007226, c.B1 = .015085, c.B2 = -0.044475, c.B3 = .028874, c.B4 = -0.005916, c.C0 = c.B0, c.C1 = 3 * c.B1, c.C2 = 7 * c.B2, c.C3 = 9 * c.B3, c.C4 = 11 * c.B4, c.EPS = 1e-11, c.MAX_Y = .8707 * .52 * Math.PI;
+ return
+ }
+
+ bO(b, a), b.title = "Natural Earth Projection", b.prototype.project = function (a, b) {
+ var c, d, e, f, g, h, i, j;
+ g = this, j = g.ll(a, b), a = j[0], b = j[1], c = g.rad(g.clon(a)), d = g.rad(b * -1), e = d * d, f = e * e, h = c * (g.A0 + e * (g.A1 + e * (g.A2 + f * e * (g.A3 + e * g.A4)))) * 180 + 500, i = d * (g.B0 + e * (g.B1 + f * (g.B2 + g.B3 * e + g.B4 * f))) * 180 + 270;
+ return[h, i]
+ };
+ return b
+ }(X), bD.naturalearth = P, bb = function (a) {
+ function b(a) {
+ var c;
+ b.__super__.constructor.call(this, a), c = this, c.X = [1, -5.67239e-12, -0.0000715511, 311028e-11, .9986, -0.000482241, -0.000024897, -0.00000133094, .9954, -0.000831031, -0.000044861, -9.86588e-7, .99, -0.00135363, -0.0000596598, 367749e-11, .9822, -0.00167442, -0.0000044975, -0.00000572394, .973, -0.00214869, -0.0000903565, 1.88767e-8, .96, -0.00305084, -0.0000900732, 164869e-11, .9427, -0.00382792, -0.0000653428, -0.00000261493, .9216, -0.00467747, -0.000104566, 48122e-10, .8962, -0.00536222, -0.0000323834, -0.00000543445, .8679, -0.00609364, -0.0001139, 332521e-11, .835, -0.00698325, -0.0000640219, 9.34582e-7, .7986, -0.00755337, -0.0000500038, 9.35532e-7, .7597, -0.00798325, -0.0000359716, -0.00000227604, .7186, -0.00851366, -0.000070112, -0.00000863072, .6732, -0.00986209, -0.000199572, 191978e-10, .6213, -0.010418, 883948e-10, 624031e-11, .5722, -0.00906601, 181999e-9, 624033e-11, .5322, 0, 0, 0], c.Y = [0, .0124, 3.72529e-10, 1.15484e-9, .062, .0124001, 1.76951e-8, -5.92321e-9, .124, .0123998, -7.09668e-8, 2.25753e-8, .186, .0124008, 2.66917e-7, -8.44523e-8, .248, .0123971, -9.99682e-7, 3.15569e-7, .31, .0124108, 373349e-11, -0.0000011779, .372, .0123598, -0.000013935, 439588e-11, .434, .0125501, 520034e-10, -0.0000100051, .4968, .0123198, -0.0000980735, 922397e-11, .5571, .0120308, 402857e-10, -0.0000052901, .6176, .0120369, -0.0000390662, 7.36117e-7, .6769, .0117015, -0.0000280246, -8.54283e-7, .7346, .0113572, -0.0000408389, -5.18524e-7, .7903, .0109099, -0.0000486169, -0.0000010718, .8435, .0103433, -0.0000646934, 5.36384e-9, .8936, .00969679, -0.0000646129, -0.00000854894, .9394, .00840949, -0.000192847, -0.00000421023, .9761, .00616525, -0.000256001, -0.00000421021, 1, 0, 0, 0], c.NODES = 18, c.FXC = .8487, c.FYC = 1.3523, c.C1 = 11.459155902616464, c.RC1 = .08726646259971647, c.ONEEPS = 1.000001, c.EPS = 1e-8;
+ return
+ }
+
+ bO(b, a), b.title = "Robinson Projection", b.prototype._poly = function (a, b, c) {return a[b] + c * (a[b + 1] + c * (a[b + 2] + c * a[b + 3]))}, b.prototype.project = function (a, b) {
+ var c, d, e, f, g, h, i, j;
+ g = this, j = g.ll(a, b), a = j[0], b = j[1], a = g.clon(a), d = g.rad(a), e = g.rad(b * -1), f = Math.abs(e), c = Math.floor(f * g.C1), c >= g.NODES && (c = g.NODES - 1), f = g.deg(f - g.RC1 * c), c *= 4, h = 1e3 * g._poly(g.X, c, f) * g.FXC * d, i = 1e3 * g._poly(g.Y, c, f) * g.FYC, e < 0 && (i = -i);
+ return[h, i]
+ };
+ return b
+ }(X), bD.robinson = bb, p = function (a) {
+ function b(a) {
+ var c;
+ b.__super__.constructor.call(this, a), c = this, c.C_x = .4222382003157712, c.C_y = 1.3265004281770023, c.RC_y = .7538633073600218, c.C_p = 3.5707963267948966, c.RC_p = .2800495767557787, c.EPS = 1e-7, c.NITER = 6
+ }
+
+ bO(b, a), b.title = "Eckert IV Projection", b.prototype.project = function (a, b) {
+ var c, d, e, f, g, h, i, j, k, l, m;
+ h = this, m = h.ll(a, b), a = m[0], b = m[1], f = h.rad(h.clon(a)), g = h.rad(b * -1), i = h.C_p * Math.sin(g), c = g * g, g *= .895168 + c * (.0218849 + c * .00826809), e = h.NITER;
+ while (e > 0) {
+ d = Math.cos(g), j = Math.sin(g), c = (g + j * (d + 2) - i) / (1 + d * (d + 2) - j * j), g -= c;
+ if (Math.abs(c) < h.EPS)break;
+ e -= 1
+ }
+ e === 0 ? (k = h.C_x * f, l = g < 0 ? -h.C_y : h.C_y) : (k = h.C_x * f * (1 + Math.cos(g)), l = h.C_y * Math.sin(g));
+ return[k, l]
+ };
+ return b
+ }(X), bD.eckert4 = p, be = function (a) {
+ function b() {return b.__super__.constructor.apply(this, arguments)}
+
+ bO(b, a), b.title = "Sinusoidal Projection", b.prototype.project = function (a, b) {
+ var c, d, e, f, g, h;
+ d = this, h = d.ll(a, b), a = h[0], b = h[1], c = d.rad(d.clon(a)), e = d.rad(b * -1), f = 1032 * c * Math.cos(e), g = 1032 * e;
+ return[f, g]
+ };
+ return b
+ }(X), bD.sinusoidal = be, O = function (a) {
+ function b(a, c, d, e, f) {
+ var g, h, i, j;
+ c == null && (c = 1.5707963267948966), d == null && (d = null), e == null && (e = null), f == null && (f = null), b.__super__.constructor.call(this, a), g = this, g.MAX_ITER = 10, g.TOLERANCE = 1e-7, c != null ? (h = c + c, j = Math.sin(c), i = Math.sqrt(Math.PI * 2 * j / (h + Math.sin(h))), g.cx = 2 * i / Math.PI, g.cy = i / j, g.cp = h + Math.sin(h)) : d != null && e != null && typeof cz != "undefined" && cz !== null ? (g.cx = d, g.cy = e, g.cp = f) : bB("kartograph.proj.Mollweide: either p or cx,cy,cp must be defined")
+ }
+
+ bO(b, a), b.title = "Mollweide Projection", b.prototype.project = function (a, b) {
+ var c, d, e, f, g, h, i, j, k, l, m;
+ h = this, m = h.ll(a, b), a = m[0], b = m[1], g = Math, c = g.abs, f = h.rad(h.clon(a)), i = h.rad(b), e = h.cp * g.sin(i), d = h.MAX_ITER;
+ while (d !== 0) {
+ j = (i + g.sin(i) - e) / (1 + g.cos(i)), i -= j;
+ if (c(j) < h.TOLERANCE)break;
+ d -= 1
+ }
+ d === 0 ? i = i >= 0 ? h.HALFPI : -h.HALFPI : i *= .5, k = 1e3 * h.cx * f * g.cos(i), l = 1e3 * h.cy * g.sin(i);
+ return[k, l * -1]
+ };
+ return b
+ }(X), bD.mollweide = O, bm = function (a) {
+ function b(a) {b.__super__.constructor.call(this, a, 1.0471975511965976)}
+
+ bO(b, a), b.title = "Wagner IV Projection";
+ return b
+ }(O), bD.wagner4 = bm, bn = function (a) {
+ function b(a) {b.__super__.constructor.call(this, a, null, .90977, 1.65014, 3.00896)}
+
+ bO(b, a), b.title = "Wagner V Projection";
+ return b
+ }(O), bD.wagner5 = bn, K = function (a) {
+ function d() {return d.__super__.constructor.apply(this, arguments)}
+
+ var b, c;
+ bO(d, a), c = -89, b = 89, d.parameters = ["lon0", "lat0", "flip"], d.title = "Loximuthal Projection (equidistant)", d.prototype.project = function (a, b) {
+ var c, d, e, f, g, h, i;
+ e = this, i = e.ll(a, b), a = i[0], b = i[1], d = Math, c = e.rad(e.clon(a)), f = e.rad(b), f === e.phi0 ? g = c * d.cos(e.phi0) : g = c * (f - e.phi0) / (d.log(d.tan(e.QUARTERPI + f * .5)) - d.log(d.tan(e.QUARTERPI + e.phi0 * .5))), g *= 1e3, h = 1e3 * (f - e.phi0);
+ return[g, h * -1]
+ };
+ return d
+ }(X), bD.loximuthal = K, j = function (a) {
+ function g() {return g.__super__.constructor.apply(this, arguments)}
+
+ var b, c, d, e, f;
+ bO(g, a), g.title = "Canters Modified Sinusoidal I", g.parameters = ["lon0"], b = 1.1966, c = -0.129, d = 3 * c, e = -0.0076, f = 5 * e, g.prototype.project = function (a, g) {
+ var h, i, j, k, l, m;
+ h = this, m = h.ll(a, g), a = m[0], g = m[1], a = h.rad(h.clon(a)), g = h.rad(g), k = g * g, l = k * k, i = 1e3 * a * Math.cos(g) / (b + d * k + f * l), j = 1e3 * g * (b + c * k + e * l);
+ return[i, j * -1]
+ };
+ return g
+ }(X), bD.canters1 = j, x = function (a) {
+ function o(a) {o.__super__.constructor.call(this, a)}
+
+ var b, c, d, e, f, g, h, i, j, k, l, m, n;
+ bO(o, a), o.title = "Hatano Projection", h = 20, d = 1e-7, i = 1.000001, b = 2.67595, c = 2.43763, j = .3736990601468637, k = .4102345310814193, f = 1.75859, g = 1.93052, m = .5686373742600607, n = .5179951515653813, e = .85, l = 1.1764705882352942, o.prototype.project = function (a, i) {
+ var j, k, l, m, n,
+ o, p, q, r, s;
+ m = this, s = m.ll(a, i), a = s[0], i = s[1], l = m.rad(m.clon(a)), n = m.rad(i), j = Math.sin(n) * (n < 0 ? c : b);
+ for (k = r = h; r >= 1; k = r += -1) {
+ o = (n + Math.sin(n) - j) / (1 + Math.cos(n)), n -= o;
+ if (Math.abs(o) < d)break
+ }
+ p = 1e3 * e * l * Math.cos(n *= .5), q = 1e3 * Math.sin(n) * (n < 0 ? g : f);
+ return[p, q * -1]
+ };
+ return o
+ }(X), bD.hatano = x, w = function (a) {
+ function b(a) {
+ var c;
+ b.__super__.constructor.call(this, a), c = this, c.lat1 = 41.737, c.p1 = new O, c.p0 = new be
+ }
+
+ bO(b, a), b.title = "Goode Homolosine Projection", b.parameters = ["lon0"], b.prototype.project = function (a, b) {
+ var c, d;
+ c = this, d = c.ll(a, b), a = d[0], b = d[1], a = c.clon(a);
+ return Math.abs(b) > c.lat1 ? c.p1.project(a, b) : c.p0.project(a, b)
+ };
+ return b
+ }(X), bD.goodehomolosine = w, Q = function (a) {
+ function c(a) {c.__super__.constructor.call(this, a), this.r = this.HALFPI * 100}
+
+ var b;
+ bO(c, a), c.title = "Nicolosi Globular Projection", c.parameters = ["lon0"], b = 1e-10, c.prototype._visible = function (a, b) {
+ var c;
+ c = this, a = c.clon(a);
+ return a > -90 && a < 90
+ }, c.prototype.project = function (a, c) {
+ var d, e, f, g, h, i, j, k, l, m, n, o, p;
+ h = this, p = h.ll(a, c), a = p[0], c = p[1], f = h.rad(h.clon(a)), j = h.rad(c), Math.abs(f) < b ? (n = 0, o = j) : Math.abs(j) < b ? (n = f, o = 0) : Math.abs(Math.abs(f) - h.HALFPI) < b ? (n = f * Math.cos(j), o = h.HALFPI * Math.sin(j)) : Math.abs(Math.abs(j) - h.HALFPI) < b ? (n = 0, o = j) : (m = h.HALFPI / f - f / h.HALFPI, d = j / h.HALFPI, l = Math.sin(j), e = (1 - d * d) / (l - d), k = m / e, k *= k, g = (m * l / e - .5 * m) / (1 + k), i = (l / k + .5 * e) / (1 + 1 / k), n = Math.cos(j), n = Math.sqrt(g * g + n * n / (1 + k)), n = h.HALFPI * (g + (f < 0 ? -n : n)), o = Math.sqrt(i * i - (l * l / k + e * l - 1) / (1 + 1 / k)), o = h.HALFPI * (i + (j < 0 ? o : -o)));
+ return[n * 100, o * -100]
+ }, c.prototype.sea = function () {
+ var a, b, c, d, e;
+ b = [], d = this.r, a = Math;
+ for (c = e = 0; e <= 360; c = ++e)b.push([a.cos(this.rad(c)) * d, a.sin(this.rad(c)) * d]);
+ return b
+ }, c.prototype.world_bbox = function () {
+ var a;
+ a = this.r;
+ return new br.BBox(-a, -a, a * 2, a * 2)
+ };
+ return c
+ }(X), bD.nicolosi = Q, c = function (a) {
+ function b(a, c) {
+ var d;
+ c == null && (c = 1e3), b.__super__.constructor.call(this, a), d = this, d.r = c, d.elevation0 = d.to_elevation(d.lat0), d.azimuth0 = d.to_azimuth(d.lon0)
+ }
+
+ bO(b, a), b.parameters = ["lon0", "lat0"], b.title = "Azimuthal Projection", b.prototype.to_elevation = function (a) {
+ var b;
+ b = this;
+ return(a + 90) / 180 * b.PI - b.HALFPI
+ }, b.prototype.to_azimuth = function (a) {
+ var b;
+ b = this;
+ return(a + 180) / 360 * b.PI * 2 - b.PI
+ }, b.prototype._visible = function (a, b) {
+ var c, d, e, f, g;
+ g = this, f = Math, e = g.to_elevation(b), c = g.to_azimuth(a), d = f.sin(e) * f.sin(g.elevation0) + f.cos(g.elevation0) * f.cos(e) * f.cos(c - g.azimuth0);
+ return d >= 0
+ }, b.prototype._truncate = function (a, b) {
+ var c, d, e, f, g;
+ c = Math, d = this.r, e = c.atan2(b - d, a - d), f = d + d * c.cos(e), g = d + d * c.sin(e);
+ return[f, g]
+ }, b.prototype.sea = function () {
+ var a, b, c, d, e;
+ b = [], d = this.r, a = Math;
+ for (c = e = 0; e <= 360; c = ++e)b.push([d + a.cos(this.rad(c)) * d, d + a.sin(this.rad(c)) * d]);
+ return b
+ }, b.prototype.world_bbox = function () {
+ var a;
+ a = this.r;
+ return new br.BBox(0, 0, a * 2, a * 2)
+ };
+ return b
+ }(V), R = function (a) {
+ function b() {return b.__super__.constructor.apply(this, arguments)}
+
+ bO(b, a), b.title = "Orthographic Projection", b.prototype.project = function (a, b) {
+ var c, d, e, f, g, h, i, j;
+ f = this, e = Math, d = f.to_elevation(b), c = f.to_azimuth(a), h = f.r * e.cos(d) * e.sin(c - f.azimuth0), j = -f.r * (e.cos(f.elevation0) * e.sin(d) - e.sin(f.elevation0) * e.cos(d) * e.cos(c - f.azimuth0)), g = f.r + h, i = f.r + j;
+ return[g, i]
+ };
+ return b
+ }(c), bD.ortho = R, C = function (a) {
+ function b(a) {b.__super__.constructor.call(this, a), this.scale = Math.sqrt(2) * .5}
+
+ bO(b, a), b.title = "Lambert Azimuthal Equal-Area Projection", b.prototype.project = function (a, b) {
+ var c, d, e, f, g, h, i, j, k, l;
+ g = this.rad(b), e = this.rad(a), f = Math, h = f.sin, c = f.cos, d = f.pow(2 / (1 + h(this.phi0) * h(g) + c(this.phi0) * c(g) * c(e - this.lam0)), .5), d *= this.scale, j = this.r * d * c(g) * h(e - this.lam0), l = -this.r * d * (c(this.phi0) * h(g) - h(this.phi0) * c(g) * c(e - this.lam0)), i = this.r + j, k = this.r + l;
+ return[i, k]
+ };
+ return b
+ }(c), bD.laea = C, bh = function (a) {
+ function b() {return b.__super__.constructor.apply(this, arguments)}
+
+ bO(b, a), b.title = "Stereographic Projection", b.prototype.project = function (a, b) {
+ var c, d, e, f, g, h, i, j, k, l, m;
+ h = this.rad(b), f = this.rad(a), g = Math, i = g.sin, c = g.cos, e = .5, d = 2 * e / (1 + i(this.phi0) * i(h) + c(this.phi0) * c(h) * c(f - this.lam0)), k = this.r * d * c(h) * i(f - this.lam0), m = -this.r * d * (c(this.phi0) * i(h) - i(this.phi0) * c(h) * c(f - this.lam0)), j = this.r + k, l = this.r + m;
+ return[j, l]
+ };
+ return b
+ }(c), bD.stereo = bh, bc = function (a) {
+ function b(a) {
+ var c, d, e, f, g, h, i, j, k, l;
+ b.__super__.constructor.call(this, {lon0: 0, lat0: 0}), this.dist = (j = a.dist) != null ? j : 3, this.up = this.rad((k = a.up) != null ? k : 0), this.tilt = this.rad((l = a.tilt) != null ? l : 0), this.scale = 1, f = Number.MAX_VALUE, e = Number.MAX_VALUE * -1;
+ for (c = h = 0; h <= 179; c = ++h)for (d = i = 0; i <= 360; d = ++i)g = this.project(d - 180, c - 90), f = Math.min(g[0], f), e = Math.max(g[0], e);
+ this.scale = this.r * 2 / (e - f), b.__super__.constructor.call(this, a);
+ return
+ }
+
+ bO(b, a), b.parameters = ["lon0", "lat0", "tilt", "dist", "up"], b.title = "Satellite Projection", b.prototype.project = function (a, b, c) {
+ var d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x;
+ c == null && (c = 0), m = this.rad(b), k = this.rad(a), l = Math, p = l.sin, f = l.cos, n = this.r, o = n * (c + 6371) / 3671, g = p(this.phi0) * p(m) + f(this.phi0) * f(m) * f(k - this.lam0), j = (this.dist - 1) / (this.dist - g), j = (this.dist - 1) / (this.dist - g), j *= this.scale, t = o * j * f(m) * p(k - this.lam0), w = -o * j * (f(this.phi0) * p(m) - p(this.phi0) * f(m) * f(k - this.lam0)), i = f(this.up), r = p(this.up), h = f(this.tilt), q = p(this.tilt), e = o * (this.dist - 1), d = (w * i + t * r) * p(this.tilt / e) + h, u = (t * i - w * r) * f(this.tilt / d), x = (w * i + t * r) / d, s = n + u, v = n + x;
+ return[s, v]
+ }, b.prototype._visible = function (a, b) {
+ var c, d, e, f;
+ e = this.to_elevation(b), c = this.to_azimuth(a), f = Math, d = f.sin(e) * f.sin(this.elevation0) + f.cos(this.elevation0) * f.cos(e) * f.cos(c - this.azimuth0);
+ return d >= 1 / this.dist
+ }, b.prototype.sea = function () {
+ var a, b, c, d, e;
+ b = [], d = this.r, a = Math;
+ for (c = e = 0; e <= 360; c = ++e)b.push([d + a.cos(this.rad(c)) * d, d + a.sin(this.rad(c)) * d]);
+ return b
+ };
+ return b
+ }(c), bD.satellite = bc, q = function (a) {
+ function b() {return b.__super__.constructor.apply(this, arguments)}
+
+ bO(b, a), b.title = "Equidistant Azimuthal Projection", b.prototype.project = function (a, b) {
+ var c, d, e, f, g, h, i, j, k, l, m, n, o;
+ i = this, j = i.rad(b), g = i.rad(a), h = Math, k = h.sin, d = h.cos, e = k(this.phi0) * k(j) + d(this.phi0) * d(j) * d(g - this.lam0), c = h.acos(e), f = .325 * c / k(c), m = this.r * f * d(j) * k(g - this.lam0), o = -this.r * f * (d(this.phi0) * k(j) - k(this.phi0) * d(j) * d(g - this.lam0)), l = this.r + m, n = this.r + o;
+ return[l, n]
+ }, b.prototype._visible = function (a, b) {return!0};
+ return b
+ }(c), bD.equi = q, b = function (a) {
+ function c(a) {
+ var b;
+ b = this, a.lat0 = 0, c.__super__.constructor.call(this, a), b.lam0 = 0
+ }
+
+ var b;
+ bO(c, a), c.title = "Aitoff Projection", c.parameters = ["lon0"], b = .6366197723675814, c.prototype.project = function (a, c) {
+ var d, e, f, g, h, i, j, k;
+ g = this, k = g.ll(a, c), a = k[0], c = k[1], a = g.clon(a), f = g.rad(a), h = g.rad(c), d = .5 * f, e = Math.acos(Math.cos(h) * Math.cos(d)), e !== 0 ? (j = 1 / Math.sin(e), i = 2 * e * Math.cos(h) * Math.sin(d) * j, j *= e * Math.sin(h)) : i = j = 0, g.winkel && (i = (i + f * b) * .5, j = (j + h) * .5);
+ return[i * 1e3, j * -1e3]
+ }, c.prototype._visible = function (a, b) {return!0};
+ return c
+ }(X), bD.aitoff = b, bo = function (a) {
+ function b(a) {b.__super__.constructor.call(this, a), this.winkel = !0}
+
+ bO(b, a), b.title = "Winkel Tripel Projection";
+ return b
+ }(b), bD.winkel3 = bo, n = function (a) {
+ function b(a) {
+ var c, d, e;
+ c = this, b.__super__.constructor.call(this, a), c.lat1 = (d = a.lat1) != null ? d : 30, c.phi1 = c.rad(c.lat1), c.lat2 = (e = a.lat2) != null ? e : 50, c.phi2 = c.rad(c.lat2)
+ }
+
+ bO(b, a), b.title = "Conic Projection", b.parameters = ["lon0", "lat0", "lat1", "lat2"], b.prototype._visible = function (a, b) {
+ var c;
+ c = this;
+ return b > c.minLat && b < c.maxLat
+ }, b.prototype._truncate = function (a, b) {return[a, b]}, b.prototype.clon = function (a) {
+ a -= this.lon0, a < -180 ? a += 360 : a > 180 && (a -= 360);
+ return a
+ };
+ return b
+ }(V), D = function (a) {
+ function b(a) {
+ var c, d, e, f, g, h, i, j, k, l, m, n, o;
+ k = this, b.__super__.constructor.call(this, a), g = Math, o = [g.sin, g.cos, g.abs, g.log, g.tan, g.pow], l = o[0], e = o[1], c = o[2], bs = o[3], n = o[4], i = o[5], k.n = h = m = l(k.phi1), f = e(k.phi1), j = c(k.phi1 - k.phi2) >= 1e-10, j && (h = bs(f / e(k.phi2)) / bs(n(k.QUARTERPI + .5 * k.phi2) / n(k.QUARTERPI + .5 * k.phi1))), k.c = d = f * i(n(k.QUARTERPI + .5 * k.phi1), h) / h, c(c(k.phi0) - k.HALFPI) < 1e-10 ? k.rho0 = 0 : k.rho0 = d * i(n(k.QUARTERPI + .5 * k.phi0), -h), k.minLat = -60, k.maxLat = 85
+ }
+
+ bO(b, a), b.title = "Lambert Conformal Conic Projection", b.prototype.project = function (a, b) {
+ var c, d, e, f, g, h, i, j, k, l, m, n, o, p, q;
+ l = this, i = l.rad(b), e = l.rad(l.clon(a)), g = Math, q = [g.sin, g.cos, g.abs, g.log, g.tan, g.pow], m = q[0], d = q[1], c = q[2], bs = q[3], n = q[4], j = q[5], h = l.n, c(c(i) - l.HALFPI) < 1e-10 ? k = 0 : k = l.c * j(n(l.QUARTERPI + .5 * i), -h), f = e * h, o = 1e3 * k * m(f), p = 1e3 * (l.rho0 - k * d(f));
+ return[o, p * -1]
+ };
+ return b
+ }(n), bD.lcc = D, W = function (a) {
+ function b() {return b.__super__.constructor.apply(this, arguments)}
+
+ bO(b, a);
+ return b
+ }(n), bl = function () {
+ function a(a, b, c, d, e, f) {
+ var g;
+ g = this, g.bbox = a, g.width = b, g.padding = d != null ? d : 0, g.halign = e != null ? e : "center", g.valign = f != null ? f : "center", g.height = c, g.scale = Math.min((b - d * 2) / a.width, (c - d * 2) / a.height)
+ }
+
+ a.prototype.project = function (a, b) {
+ var c, d, e, f, g, h, i;
+ b == null && (b = a[1], a = a[0]), e = this, f = e.scale, c = e.bbox, d = e.height, g = e.width, h = e.halign === "center" ? (g - c.width * f) * .5 : e.halign === "left" ? e.padding * f : g - (c.width - e.padding) * f, i = e.valign === "center" ? (d - c.height * f) * .5 : e.valign === "top" ? e.padding * f : 0, a = (a - c.left) * f + h, b = (b - c.top) * f + i;
+ return[a, b]
+ }, a.prototype.projectPath = function (a) {
+ var b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r;
+ e = this;
+ if (a.type === "path") {
+ d = [], b = [99999, 99999, -99999, -99999], o = a.contours;
+ for (k = 0, m = o.length; k < m; k++) {
+ g = o[k], c = [];
+ for (l = 0, n = g.length; l < n; l++)p = g[l], i = p[0], j = p[1], q = e.project(i, j), i = q[0], j = q[1], c.push([i, j]), b[0] = Math.min(b[0], i), b[1] = Math.min(b[1], j), b[2] = Math.max(b[2], i), b[3] = Math.max(b[3], j);
+ d.push(c)
+ }
+ f = new br.geom.Path(a.type, d, a.closed), f._bbox = b;
+ return f
+ }
+ if (a.type === "circle") {
+ r = e.project(a.x, a.y), i = r[0], j = r[1], h = a.r * e.scale;
+ return new br.geom.Circle(i, j, h)
+ }
+ }, a.prototype.asBBox = function () {
+ var a;
+ a = this;
+ return new br.BBox(0, 0, a.width, a.height)
+ };
+ return a
+ }(), bl.fromXML = function (a) {
+ var b, c, e, f, g;
+ g = Number(a.getAttribute("w")), e = Number(a.getAttribute("h")), f = Number(a.getAttribute("padding")), c = a.getElementsByTagName("bbox")[0], b = d.fromXML(c);
+ return new br.View(b, g, e, f)
+ }, br.View = bl, br.Kartograph.prototype.dotgrid = function (a) {
+ var b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X;
+ r = this, q = (T = a.layer) != null ? T : r.layerIds[r.layerIds.length - 1];
+ if (!r.layers.hasOwnProperty(q))bB('dotgrid error: layer "' + q + '" not found'); else {
+ p = r.layers[q], c = a.data, d = a.value, e = a.key, t = {};
+ if (e != null && bE(c) === "array")for (B = 0, F = c.length; B < F; B++)w = c[B], o = w[e], t[String(o)] = w; else for (o in c)w = c[o], t[String(o)] = w;
+ i = (U = a.style) != null ? U : {fill: "black", stroke: "none"}, y = a.size, n = (V = a.gridsize) != null ? V : 15, h = (W = p.dotgrid) != null ? W : p.dotgrid = {gridsize: n, grid: []};
+ if (h.gridsize !== n) {
+ X = h.grid;
+ for (C = 0, G = X.length; C < G; C++)m = X[C], m.shape != null && (m.shape.remove(), m.shape = null)
+ }
+ if (n > 0) {
+ if (h.grid.length === 0)for (z = D = 0, L = r.viewport.width; 0 <= L ? D <= L : D >= L; z = D += n)for (A = E = 0, M = r.viewport.height; 0 <= M ? E <= M : E >= M; A = E += n) {
+ m = {x: z + (Math.random() - .5) * n * .2, y: A + (Math.random() - .5) * n * .2, pathid: !1}, l = !1, N = p.pathsById;
+ for (o in N) {
+ u = N[o];
+ for (J = 0, H = u.length; J < H; J++) {
+ s = u[J];
+ if (s.vpath.isInside(m.x, m.y)) {
+ l = !0, v = (O = t[o]) != null ? O : null, x = y(v), m.pathid = o, m.shape = p.paper.circle(m.x, m.y, 1);
+ break
+ }
+ }
+ if (l)break
+ }
+ h.grid.push(m)
+ }
+ P = h.grid;
+ for (K = 0, I = P.length; K < I; K++)m = P[K], m.pathid && (v = (Q = t[m.pathid]) != null ? Q : null, x = y(v), k = (R = a.duration) != null ? R : 0, f = (S = a.delay) != null ? S : 0, bE(f) === "function" ? g = f(v) : g = f, k > 0 && Raphael.svg ? (b = Raphael.animation({r: x * .5}, k), m.shape.animate(b.delay(g))) : m.shape.attr({r: x * .5}), bE(i) === "function" ? j = i(v) : j = i, m.shape.attr(j))
+ }
+ }
+ }, bq = (bM = br.filter) != null ? bM : br.filter = {}, bq.__knownFilter = {}, bq.__patternFills = 0, L.prototype.SVG = function (a, b) {
+ var c, d;
+ typeof a == "string" && (a = window.document.createElementNS("http://www.w3.org/2000/svg", a));
+ if (b)for (c in b)d = b[c], a.setAttribute(c, d);
+ return a
+ }, br.Kartograph.prototype.addFilter = function (a, b, c) {
+ var d, e, f;
+ c == null && (c = {}), f = this, d = window.document;
+ if (br.filter[b] != null)e = (new br.filter[b](c)).getFilter(a); else throw"unknown filter type " + b;
+ return f.paper.defs.appendChild(e)
+ }, L.prototype.applyFilter = function (b) {
+ var c;
+ c = this;
+ return a("." + c.id, c.paper.canvas).attr({filter: "url(#" + b + ")"})
+ }, L.prototype.applyTexture = function (a, b, c) {
+ var d, e, f, g, h, i;
+ b == null && (b = !1), c == null && (c = "#000"), e = this, bq.__patternFills += 1, h = e.paths, i = [];
+ for (f = 0, g = h.length; f < g; f++)d = h[f], !b || b(d.data) ? i.push(d.svgPath.attr({fill: "url(" + a + ")"})) : i.push(d.svgPath.attr("fill", c));
+ return i
+ }, t = function () {
+ function a(a) {this.params = a != null ? a : {}}
+
+ a.prototype.getFilter = function (a) {
+ var b, c;
+ c = this, b = c.SVG("filter", {id: a}), c.buildFilter(b);
+ return b
+ }, a.prototype._getFilter = function () {throw"not implemented"}, a.prototype.SVG = function (a, b) {
+ var c, d;
+ typeof a == "string" && (a = window.document.createElementNS("http://www.w3.org/2000/svg", a));
+ if (b)for (c in b)d = b[c], a.setAttribute(c, d);
+ return a
+ };
+ return a
+ }(), g = function (a) {
+ function b() {return b.__super__.constructor.apply(this, arguments)}
+
+ bO(b, a), b.prototype.buildFilter = function (a) {
+ var b, c, d;
+ d = this, b = d.SVG, c = b("feGaussianBlur", {stdDeviation: d.params.size || 4, result: "blur"});
+ return a.appendChild(c)
+ };
+ return b
+ }(t), bq.blur = g, v = function (a) {
+ function b() {return b.__super__.constructor.apply(this, arguments)}
+
+ bO(b, a), b.prototype.buildFilter = function (a) {
+ var b, c, d, e, f, g, h, i, j, k, l, m, n, o;
+ g = this, c = (l = g.params.blur) != null ? l : 4, i = (m = g.params.strength) != null ? m : 1, d = (n = g.params.color) != null ? n : "#D1BEB0", typeof d == "string" && (d = chroma.hex(d)), h = d.rgb, e = (o = g.params.inner) != null ? o : !1, f = (j = g.params.knockout) != null ? j : !1, b = (k = g.params.alpha) != null ? k : 1, e ? g.innerGlow(a, c, i, h, b, f) : g.outerGlow(a, c, i, h, b, f)
+ }, b.prototype.outerGlow = function (a, b, c, d, e, f) {
+ var g, h, i, j, k, l, m;
+ k = this, g = k.SVG, j = g("feColorMatrix", {"in": "SourceGraphic", type: "matrix", values: "0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0", result: "mask"}), a.appendChild(j), c > 0 && (m = g("feMorphology", {"in": "mask", radius: c, operator: "dilate", result: "mask"}), a.appendChild(m)), j = g("feColorMatrix", {"in": "mask", type: "matrix", values: "0 0 0 0 " + d[0] / 255 + " 0 0 0 0 " + d[1] / 255 + " 0 0 0 0 " + d[2] / 255 + " 0 0 0 1 0", result: "r0"}), a.appendChild(j), h = g("feGaussianBlur", {"in": "r0", stdDeviation: b, result: "r1"}), a.appendChild(h), i = g("feComposite", {operator: "out", "in": "r1", in2: "mask", result: "comp"}), a.appendChild(i), l = g("feMerge"), f || l.appendChild(g("feMergeNode", {"in": "SourceGraphic"})), l.appendChild(g("feMergeNode", {"in": "r1"}));
+ return a.appendChild(l)
+ }, b.prototype.innerGlow = function (a, b, c, d, e, f) {
+ var g, h, i, j, k, l, m;
+ k = this, g = k.SVG, bs("innerglow"), j = g("feColorMatrix", {"in": "SourceGraphic", type: "matrix", values: "0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 500 0", result: "mask"}), a.appendChild(j), m = g("feMorphology", {"in": "mask", radius: c, operator: "erode", result: "r1"}), a.appendChild(m), h = g("feGaussianBlur", {"in": "r1", stdDeviation: b, result: "r2"}), a.appendChild(h), j = g("feColorMatrix", {type: "matrix", "in": "r2", values: "1 0 0 0 " + d[0] / 255 + " 0 1 0 0 " + d[1] / 255 + " 0 0 1 0 " + d[2] / 255 + " 0 0 0 -1 1", result: "r3"}), a.appendChild(j), i = g("feComposite", {operator: "in", "in": "r3", in2: "mask", result: "comp"}), a.appendChild(i), l = g("feMerge"), f || l.appendChild(g("feMergeNode", {"in": "SourceGraphic"})), l.appendChild(g("feMergeNode", {"in": "comp"}));
+ return a.appendChild(l)
+ };
+ return b
+ }(t), bq.glow = v, br.Kartograph.prototype.addGeoPath = function (a, b, c) {
+ var d, e, f;
+ b == null && (b = []), c == null && (c = ""), d = this, f = d.getGeoPathStr(a, b), e = d.paper.path(f), c !== "" && e.node.setAttribute("class", c);
+ return e
+ }, br.Kartograph.prototype.getGeoPathStr = function (a, b) {
+ var c, d, e, f, g, h, i;
+ b == null && (b = []), e = this, type(b) === "string" && (b = b.split("")), b.length === 0 && b.push("M"), f = "";
+ for (d in a) {
+ g = a[d], c = (i = b[d]) != null ? i : "L", h = e.lonlat2xy(g);
+ if (isNaN(h[0]) || isNaN(h[1]))continue;
+ f += c + h[0] + "," + h[1]
+ }
+ return f
+ }, br.Kartograph.prototype.addGeoPolygon = function (a, b) {
+ var c, d, e;
+ e = this, c = ["M"];
+ for (d in a)c.push("L");
+ c.push("Z");
+ return e.addGeoPath(a, c, b)
+ }, S = function () {
+ function b(b) {
+ this.zoomOut = bP(this.zoomOut, this), this.zoomIn = bP(this.zoomIn, this);
+ var c, d, e, f, g, h, i, j;
+ f = this, f.map = b, c = b.container, d = function (b, c) {
+ var d, e, f, g;
+ c == null && (c = []), e = a('<div class="' + b + '" />');
+ for (f = 0, g = c.length; f < g; f++)d = c[f], e.append(d);
+ return e
+ }, e = function (b) {return a(b.target).addClass("md")}, g = function (b) {return a(b.target).removeClass("md")}, j = d("plus"), j.mousedown(e), j.mouseup(g), j.click(f.zoomIn), i = d("minus"), i.mousedown(e), i.mouseup(g), i.click(f.zoomOut), h = d("zoom-control", [j, i]), c.append(h)
+ }
+
+ b.prototype.zoomIn = function (a) {
+ var b;
+ b = this, b.map.opts.zoom += 1;
+ return b.map.resize()
+ }, b.prototype.zoomOut = function (a) {
+ var b;
+ b = this, b.map.opts.zoom -= 1, b.map.opts.zoom < 1 && (b.map.opts.zoom = 1);
+ return b.map.resize()
+ };
+ return b
+ }(), bd = function () {
+ function a(a, b, c) {
+ var d, e, f, g, h = this;
+ a == null && (a = [0, 1]), b == null && (b = null), c == null && (c = null), this.rangedScale = bP(this.rangedScale, this), this.scale = bP(this.scale, this), e = this, g = [];
+ for (d in a) {
+ if (bE(c) === "function" && c(a[d]) === !1)continue;
+ b != null ? bE(b) === "function" ? f = b(a[d]) : f = a[d][b] : f = a[d], isNaN(f) || g.push(f)
+ }
+ g = g.sort(function (a, b) {return a - b}), e.values = g, e._range = [0, 1], e.rangedScale.range = function (a) {
+ e._range = a;
+ return e.rangedScale
+ }
+ }
+
+ a.prototype.scale = function (a) {return a}, a.prototype.rangedScale = function (a) {
+ var b, c;
+ b = this, a = b.scale(a), c = b._range;
+ return a * (c[1] - c[0]) + c[0]
+ };
+ return a
+ }(), H = function (a) {
+ function b() {
+ this.scale = bP(this.scale, this);
+ return b.__super__.constructor.apply(this, arguments)
+ }
+
+ bO(b, a), b.prototype.scale = function (a) {
+ var b, c;
+ b = this, c = b.values;
+ return(a - c[0]) / (c[c.length - 1] - c[0])
+ };
+ return b
+ }(bd), I = function (a) {
+ function b() {
+ this.scale = bP(this.scale, this);
+ return b.__super__.constructor.apply(this, arguments)
+ }
+
+ bO(b, a), b.prototype.scale = function (a) {
+ var b, c;
+ b = this, c = b.values, bs = Math.log;
+ return(bs(a) - bs(c[0])) / (bs(c[c.length - 1]) - bs(c[0]))
+ };
+ return b
+ }(bd), bf = function (a) {
+ function b() {
+ this.scale = bP(this.scale, this);
+ return b.__super__.constructor.apply(this, arguments)
+ }
+
+ bO(b, a), b.prototype.scale = function (a) {
+ var b, c;
+ b = this, c = b.values;
+ return Math.sqrt((a - c[0]) / (c[c.length - 1] - c[0]))
+ };
+ return b
+ }(bd), Y = function (a) {
+ function b() {
+ this.scale = bP(this.scale, this);
+ return b.__super__.constructor.apply(this, arguments)
+ }
+
+ bO(b, a), b.prototype.scale = function (a) {
+ var b, c, d, e, f, g;
+ d = this, g = d.values, c = g.length - 1;
+ for (b in g) {
+ f = g[Number(b)], e = g[Number(b) + 1];
+ if (a === f)return b / c;
+ if (b < c && a > f && a < e)return b / c + (a - f) / (e - f)
+ }
+ };
+ return b
+ }(bd), br.scale = {}, br.scale.identity = function (a) {return(new bd(domain, prop, bq)).rangedScale}, br.scale.linear = function (a, b, c) {return(new H(a, b, c)).rangedScale}, br.scale.log = function (a, b, c) {return(new I(a, b, c)).rangedScale}, br.scale.sqrt = function (a, b, c) {return(new bf(a, b, c)).rangedScale}, br.scale.quantile = function (a, b, c) {return(new Y(a, b, c)).rangedScale}, bj = function () {
+ function b(b) {a = this, a.location = b.location, a.data = b.data, a.map = b.map, a.layers = b.layers, a.key = b.key, a.x = b.x, a.y = b.y}
+
+ var a;
+ a = null, b.prototype.init = function () {return a}, b.prototype.overlaps = function (a) {return!1}, b.prototype.update = function (b) {return a}, b.prototype.nodes = function () {return[]}, b.prototype.clear = function () {return a};
+ return b
+ }(), br.Symbol = bj, bk = function () {
+ function c(a) {
+ this._initTooltips = bP(this._initTooltips, this), this._noverlap = bP(this._noverlap, this), this._kMeans = bP(this._kMeans, this);
+ var d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w;
+ b = this, m = ["data", "location", "type", "map"], k = ["filter", "tooltip", "click", "delay", "sortBy", "clustering", "aggregate", "clusteringOpts", "mouseenter", "mouseleave"];
+ for (n = 0, r = m.length; n < r; n++) {
+ l = m[n];
+ if (a[l] != null)b[l] = a[l]; else throw"SymbolGroup: missing argument '" + l + "'"
+ }
+ for (o = 0, s = k.length; o < s; o++)l = k[o], a[l] != null && (b[l] = a[l]);
+ d = b.type;
+ if (d == null)bB("could not resolve symbol type", b.type); else {
+ v = d.props;
+ for (p = 0, t = v.length; p < t; p++)l = v[p], a[l] != null && (b[l] = a[l]);
+ b.layers = {mapcanvas: b.map.paper}, w = d.layers;
+ for (q = 0, u = w.length; q < u; q++)h = w[q], j = c._layerid++, g = "sl_" + j, h.type === "svg" ? i = b.map.createSVGLayer(g) : h.type === "html" && (i = b.map.createHTMLLayer(g)), b.layers[h.id] = i;
+ b.symbols = [];
+ for (f in b.data)e = b.data[f], bE(b.filter) === "function" ? b.filter(e, f) && b.add(e, f) : b.add(e, f);
+ b.layout(), b.render(), b.map.addSymbolGroup(b)
+ }
+ }
+
+ var b;
+ b = null, c.prototype.add = function (a, c) {
+ var d, e, f, g, h, i, j, k;
+ b = this, d = b.type, e = b._evaluate(b.location, a, c), bE(e) === "array" && (e = new br.LonLat(e[0], e[1])), g = {layers: b.layers, location: e, data: a, key: c != null ? c : b.symbols.length, map: b.map}, k = d.props;
+ for (i = 0, j = k.length; i < j; i++)f = k[i], b[f] != null && (g[f] = b._evaluate(b[f], a, c));
+ h = new d(g), b.symbols.push(h);
+ return h
+ }, c.prototype.layout = function () {
+ var a, c, d, e, f, g, h, i, j, k;
+ j = b.symbols;
+ for (h = 0, i = j.length; h < i; h++) {
+ f = j[h], c = f.location;
+ if (bE(c) === "string") {
+ k = c.split("."), a = k[0], e = k[1], d = b.map.getLayerPath(a, e);
+ if (d != null)g = b.map.viewBC.project(d.path.centroid()); else {
+ bB("could not find layer path " + a + "." + e);
+ continue
+ }
+ } else g = b.map.lonlat2xy(c);
+ f.x = g[0], f.y = g[1]
+ }
+ b.clustering === "k-means" ? b._kMeans() : b.clustering === "noverlap" && b._noverlap();
+ return b
+ }, c.prototype.render = function () {
+ var c, d, e, f, g, h, i, j, k, l, m;
+ b = this, b.sortBy && (f = "asc", bE(b.sortBy) === "string" && (b.sortBy = b.sortBy.split(" ", 2), e = b.sortBy[0], f = (k = b.sortBy[1]) != null ? k : "asc"), b.symbols = b.symbols.sort(function (a, c) {
+ var d, g, h;
+ bE(b.sortBy) === "function" ? (g = b.sortBy(a.data, a), h = b.sortBy(c.data, c)) : (g = a[e], h = c[e]);
+ if (g === h)return 0;
+ d = f === "asc" ? 1 : -1;
+ return g > h ? 1 * d : -1 * d
+ })), l = b.symbols;
+ for (g = 0, i = l.length; g < i; g++) {
+ d = l[g], d.render(), m = d.nodes();
+ for (h = 0, j = m.length; h < j; h++)c = m[h], c.symbol = d
+ }
+ bE(b.tooltip) === "function" && b._initTooltips(), a.each(["click", "mouseenter", "mouseleave"], function (e, f) {
+ var g, h, i, j;
+ if (bE(b[f]) === "function") {
+ i = b.symbols, j = [];
+ for (g = 0, h = i.length; g < h; g++)d = i[g], j.push(function () {
+ var e, g, h, i, j = this;
+ h = d.nodes(), i = [];
+ for (e = 0, g = h.length; e < g; e++)c = h[e], i.push(a(c)[f](function (c) {
+ var d;
+ d = c.target;
+ while (!d.symbol)d = a(d).parent().get(0);
+ c.stopPropagation();
+ return b[f](d.symbol.data, d.symbol, c)
+ }));
+ return i
+ }.call(this));
+ return j
+ }
+ });
+ return b
+ }, c.prototype.tooltips = function (a) {
+ b = this, b.tooltips = a, b._initTooltips();
+ return b
+ }, c.prototype.remove = function (a) {
+ var c, d, e, f, g, h, i, j, k;
+ b = this, d = [], i = b.symbols;
+ for (g = 0, h = i.length; g < h; g++) {
+ f = i[g];
+ if (a != null && !a(f.data)) {
+ d.push(f);
+ continue
+ }
+ try {f.clear()} catch (l) {bB("error: symbolgroup.remove")}
+ }
+ if (a == null) {
+ j = b.layers, k = [];
+ for (c in j)e = j[c], c !== "mapcanvas" ? k.push(e.remove()) : k.push(void 0);
+ return k
+ }
+ return b.symbols = d
+ }, c.prototype._evaluate = function (a, b, c) {
+ var d;
+ return bE(a) === "function" ? d = a(b, c) : d = a
+ }, c.prototype._kMeans = function () {
+ var a, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x;
+ b = this, (u = b.osymbols) == null && (b.osymbols = b.symbols), a = b.type, b.clusteringOpts != null && (k = b.clusteringOpts.size), k == null && (k = 64), c = bR().iterations(16).size(k), v = b.osymbols;
+ for (m = 0, q = v.length; m < q; m++)j = v[m], c.add({x: j.x, y: j.y});
+ g = c.means(), h = [];
+ for (n = 0, r = g.length; n < r; n++) {
+ f = g[n];
+ if (f.size === 0)continue;
+ d = [], w = f.indices;
+ for (o = 0, s = w.length; o < s; o++)e = w[o], d.push(b.osymbols[e].data);
+ d = b.aggregate(d), l = {layers: b.layers, location: !1, data: d, map: b.map}, x = a.props;
+ for (p = 0, t = x.length; p < t; p++)i = x[p], b[i] != null && (l[i] = b._evaluate(b[i], d));
+ j = new a(l), j.x = f.x, j.y = f.y, h.push(j)
+ }
+ return b.symbols = h
+ }, c.prototype._noverlap = function () {
+ var a, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V;
+ b = this, (S = b.osymbols) == null && (b.osymbols = b.symbols), j = 3, a = b.type;
+ if (bQ.call(a.props, "radius") < 0)bB('noverlap layout only available for symbols with property "radius"'); else {
+ A = b.osymbols.slice(), b.clusteringOpts != null && (D = b.clusteringOpts.tolerance, n = b.clusteringOpts.maxRatio), D == null && (D = .05), n == null && (n = .8);
+ for (h = H = 0, T = j - 1; 0 <= T ? H <= T : H >= T; h = 0 <= T ? ++H : --H) {
+ A.sort(function (a, b) {return b.radius - a.radius}), k = A.length, o = [];
+ for (p = I = 0, U = k - 3; 0 <= U ? I <= U : I >= U; p = 0 <= U ? ++I : --I) {
+ x = A[p];
+ if (!x)continue;
+ u = x.radius * (1 - D), l = x.x - u, s = x.x + u, B = x.y - u, c = x.y + u, i = [];
+ for (q = J = V = p + 1, Q = k - 2; V <= Q ? J <= Q : J >= Q; q = V <= Q ? ++J : --J) {
+ y = A[q];
+ if (!y)continue;
+ v = y.radius, m = y.x - v, t = y.x + v, C = y.y - v, d = y.y + v, v / x.radius < n && !(s < m || t < l) && !(c < C || d < B) && (f = y.x - x.x, g = y.y - x.y, f * f + g * g < (u + v) * (u + v) && i.push(q))
+ }
+ if (i.length > 0) {
+ e = [x.data], r = x.radius * x.radius;
+ for (K = 0, L = i.length; K < L; K++)h = i[K], e.push(A[h].data), r += A[h].radius * A[h].radius;
+ e = b.aggregate(e), z = {layers: b.layers, location: !1, data: e, map: b.map}, R = a.props;
+ for (O = 0, M = R.length; O < M; O++)p = R[O], b[p] != null && (z[p] = b._evaluate(b[p], e));
+ w = new a(z), E = x.radius * x.radius / r, F = x.x * E, G = x.y * E;
+ for (P = 0, N = i.length; P < N; P++)h = i[P], y = A[h], E = y.radius * y.radius / r, F += y.x * E, G += y.y * E, A[h] = void 0;
+ w.x = F, w.y = G, A[p] = void 0, o.push(w)
+ } else o.push(x)
+ }
+ A = o
+ }
+ return b.symbols = A
+ }
+ }, c.prototype._initTooltips = function () {
+ var c, d, e, f, g, h, i, j, k, l, m;
+ b = this, f = b.tooltip, l = b.symbols;
+ for (h = 0, j = l.length; h < j; h++) {
+ e = l[h], c = {position: {target: "mouse", viewport: a(window), adjust: {x: 7, y: 7}}, show: {delay: 20}, content: {}, events: {show: function (b, c) {return a(".qtip").filter(function () {return this !== c.elements.tooltip.get(0)}).hide()}}}, g = f(e.data, e.key), bE(g) === "string" ? c.content.text = g : bE(g) === "array" && (c.content.title = g[0], c.content.text = g[1]), m = e.nodes();
+ for (i = 0, k = m.length; i < k; i++)d = m[i], a(d).qtip(c)
+ }
+ }, c.prototype.onResize = function () {
+ var a, c, d, e;
+ b = this, b.layout(), e = b.symbols;
+ for (c = 0, d = e.length; c < d; c++)a = e[c], a.update()
+ }, c.prototype.update = function (a, c, d) {
+ var e, f, g, h, i, j, k, l;
+ b = this, a == null && (a = {}), k = b.symbols;
+ for (g = 0, i = k.length; g < i; g++) {
+ f = k[g], l = b.type.props;
+ for (h = 0, j = l.length; h < j; h++)e = l[h], a[e] != null ? f[e] = b._evaluate(a[e], f.data) : b[e] != null && (f[e] = b._evaluate(b[e], f.data));
+ f.update(c, d)
+ }
+ return b
+ };
+ return c
+ }(), bk._layerid = 0, br.SymbolGroup = bk, br.Kartograph.prototype.addSymbols = function (a) {
+ a.map = this;
+ return new bk(a)
+ }, br.dorlingLayout = function (b, c) {
+ var d, e, f, g, h, i, j, k, l, m, n, o, p, q, r;
+ c == null && (c = 40), n = [], a.each(b.symbols, function (a, b) {return n.push({i: a, x: b.path.attrs.cx, y: b.path.attrs.cy, r: b.path.attrs.r})}), n.sort(function (a, b) {return b.r - a.r}), f = function () {
+ var a, c, d;
+ for (c = 0, d = n.length; c < d; c++)a = n[c], b.symbols[a.i].path.attr({cx: a.x, cy: a.y})
+ };
+ for (o = r = 1; 1 <= c ? r <= c : r >= c; o = 1 <= c ? ++r : --r)for (l in n)for (m in n)if (m > l) {
+ d = n[l], e = n[m];
+ if (d.x + d.r < e.x - e.r || d.x - d.r > e.x + e.r)continue;
+ if (d.y + d.r < e.y - e.r || d.y - d.r > e.y + e.r)continue;
+ i = d.x - e.x, j = d.y - e.y, h = i * i + j * j, p = d.r + e.r, q = p * p, h < q && (g = Math.sqrt(h), k = 10 / g, d.x += i * k * (1 - d.r / p), d.y += j * k * (1 - d.r / p), e.x -= i * k * (1 - e.r / p), e.y -= j * k * (1 - e.r / p))
+ }
+ return f()
+ }, h = function (b) {
+ function c(a) {
+ this.nodes = bP(this.nodes, this), this.clear = bP(this.clear, this), this.update = bP(this.update, this), this.render = bP(this.render, this), this.overlaps = bP(this.overlaps, this);
+ var b, d, e;
+ b = this, c.__super__.constructor.call(this, a), b.radius = (d = a.radius) != null ? d : 4, b.style = a.style, b.attrs = a.attrs, b.title = a.title, b["class"] = (e = a["class"]) != null ? e : "bubble"
+ }
+
+ bO(c, b), c.prototype.overlaps = function (a) {
+ var b, c, d, e, f, g, h, i, j, k, l;
+ d = this, k = [d.x, d.y, d.radius], g = k[0], i = k[1], e = k[2], l = [a.x, a.y, a.radius], h = l[0], j = l[1], f = l[2];
+ if (g - e > h + f || g + e < h - f || i - e > j + f || i + e < j - f)return!1;
+ b = g - h, c = i - j;
+ return b * b + c * c > (e + f) * (e + f) ? !1 : !0
+ }, c.prototype.render = function (a) {
+ var b;
+ b = this, b.path == null && (b.path = b.layers.mapcanvas.circle(b.x, b.y, b.radius)), b.update(), b.map.applyCSS(b.path);
+ return b
+ }, c.prototype.update = function (b, c) {
+ var d, e, f;
+ b == null && (b = !1), c == null && (c = "expo-out"), e = this, f = e.path, d = {cx: e.x, cy: e.y, r: e.radius}, e.attrs != null && (d = a.extend(d, e.attrs)), b ? f.animate(d, b, c) : f.attr(d), f.node != null && (e.style != null && f.node.setAttribute("style", e.style), e["class"] != null && f.node.setAttribute("class", e["class"])), e.title != null && f.attr("title", e.title);
+ return e
+ }, c.prototype.clear = function () {
+ var a;
+ a = this, a.path.remove();
+ return a
+ }, c.prototype.nodes = function () {
+ var a;
+ a = this;
+ return[a.path.node]
+ };
+ return c
+ }(bj), h.props = ["radius", "style", "class", "title", "attrs"], h.layers = [], br.Bubble = h, A = function (b) {
+ function c(a) {
+ var b, d, e, f, g, h;
+ b = this, c.__super__.constructor.call(this, a), b.icon = (e = a.icon) != null ? e : "", b.offset = (f = a.offset) != null ? f : [0, 0], b.iconsize = (g = a.iconsize) != null ? g : [10, 10], b["class"] = (h = a["class"]) != null ? h : "", b.title = (d = a.title) != null ? d : ""
+ }
+
+ bO(c, b), c.prototype.render = function (b) {
+ var c, d;
+ d = this, c = d.map.container, d.img = a("<img />"), d.img.attr({src: d.icon, title: d.title, alt: d.title, width: d.iconsize[0], height: d.iconsize[1]}), d.img.addClass(d["class"]), d.img.css({position: "absolute", "z-index": 1e3, cursor: "pointer"}), d.img[0].symbol = d, c.append(d.img);
+ return d.update()
+ }, c.prototype.update = function () {
+ var a;
+ a = this;
+ return a.img.css({left: a.x + a.offset[0] + "px", top: a.y + a.offset[1] + "px"})
+ }, c.prototype.clear = function () {
+ var a;
+ a = this, a.img.remove();
+ return a
+ }, c.prototype.nodes = function () {
+ var a;
+ a = this;
+ return[a.img]
+ };
+ return c
+ }(br.Symbol), A.props = ["icon", "offset", "class", "title", "iconsize"], A.layers = [], br.Icon = A, bi = function (a) {
+ function b(a) {
+ var c, d, e, f, g;
+ c = this, b.__super__.constructor.call(this, a), c.text = (d = a.text) != null ? d : "", c.style = (e = a.style) != null ? e : "", c["class"] = (f = a["class"]) != null ? f : "", c.offset = (g = a.offset) != null ? g : [0, 0]
+ }
+
+ bO(b, a), b.prototype.render = function (a) {
+ var b, c;
+ c = this, c.lbl = b = c.layers.mapcanvas.text(c.x, c.y, c.text), c.update();
+ return c
+ }, b.prototype.update = function () {
+ var a;
+ a = this, a.lbl.attr({x: a.x + a.offset[0], y: a.y + a.offset[1]}), a.lbl.node.setAttribute("style", a.style);
+ return a.lbl.node.setAttribute("class", a["class"])
+ }, b.prototype.clear = function () {
+ var a;
+ a = this, a.lbl.remove();
+ return a
+ }, b.prototype.nodes = function () {
+ var a;
+ a = this;
+ return[a.lbl.node]
+ };
+ return b
+ }(br.Symbol), bi.props = ["text", "style", "class", "offset"], bi.layers = [], br.Label = bi, z = function (b) {
+ function c(a) {
+ var b, d, e, f;
+ b = this, c.__super__.constructor.call(this, a), b.text = (d = a.text) != null ? d : "", b.style = (e = a.style) != null ? e : "", b["class"] = (f = a["class"]) != null ? f : ""
+ }
+
+ bO(c, b), c.prototype.render = function (b) {
+ var c, d, e;
+ e = this, c = a("<div>" + e.text + "</div>"), c.css({width: "50px", position: "absolute", left: "-25px", "text-align": "center"}), e.lbl = d = a('<div class="label" />'), d.append(c), e.layers.lbl.append(d), c.css({height: c.height() + "px", top: c.height() * -0.4 + "px"}), e.update();
+ return e
+ }, c.prototype.update = function () {
+ var a;
+ a = this;
+ return a.lbl.css({position: "absolute", left: a.x + "px", top: a.y + "px"})
+ }, c.prototype.clear = function () {
+ var a;
+ a = this, a.lbl.remove();
+ return a
+ }, c.prototype.nodes = function () {
+ var a;
+ a = this;
+ return[a.lbl[0]]
+ };
+ return c
+ }(br.Symbol), z.props = ["text", "style", "class"], z.layers = [
+ {id: "lbl", type: "html"}
+ ], br.HtmlLabel = z, E = function (b) {
+ function c(a) {
+ this.nodes = bP(this.nodes, this), this.clear = bP(this.clear, this), this.update = bP(this.update, this), this.render = bP(this.render, this);
+ var b, d, e;
+ b = this, c.__super__.constructor.call(this, a), b.labelattrs = (d = a.labelattrs) != null ? d : {}, b.buffer = a.buffer, b.center = (e = a.center) != null ? e : !0
+ }
+
+ bO(c, b), c.prototype.render = function (a) {
+ var b;
+ b = this, b.title != null && String(b.title).trim() !== "" && (b.buffer && (b.bufferlabel = b.layers.mapcanvas.text(b.x, b.y, b.title)), b.label = b.layers.mapcanvas.text(b.x, b.y, b.title)), c.__super__.render.call(this, a);
+ return b
+ }, c.prototype.update = function (b, d) {
+ var e, f, g, h, i;
+ b == null && (b = !1), d == null && (d = "expo-out"), f = this, c.__super__.update.call(this, b, d), f.label != null && (g = f.map.viewport, e = a.extend({}, f.labelattrs), h = f.x, i = f.y, f.center ? i -= 0 : h > g.width * .5 ? (e["text-anchor"] = "end", h -= f.radius + 5) : h < g.width * .5 && (e["text-anchor"] = "start", h += f.radius + 5), e.x = h, e.y = i, f.buffer && (f.bufferlabel.attr(e), f.bufferlabel.attr({stroke: "#fff", fill: "#fff", "stroke-linejoin": "round", "stroke-linecap": "round", "stroke-width": 6})), f.label.attr(e), f.label.toFront());
+ return f
+ }, c.prototype.clear = function () {
+ var a;
+ a = this;
+ return c.__super__.clear.apply(this, arguments)
+ }, c.prototype.nodes = function () {
+ var a, b;
+ a = this, b = c.__super__.nodes.apply(this, arguments), a.label && b.push(a.label.node), a.bufferlabel && b.push(a.bufferlabel.node);
+ return b
+ };
+ return c
+ }(h), E.props = ["radius", "style", "class", "title", "labelattrs", "buffer", "center", "attrs"], E.layers = [], br.LabeledBubble = E, U = function (a) {
+ function c(a) {
+ var d, e, f, g, h, i, j, k, l, m;
+ b = this, c.__super__.constructor.call(this, a), b.radius = (j = a.radius) != null ? j : 4, b.styles = (k = a.styles) != null ? k : "", b.colors = (l = a.colors) != null ? l : ["#3cc", "#c3c", "#33c", "#cc3"], b.titles = (m = a.titles) != null ? m : ["", "", "", "", ""], b.values = (e = a.values) != null ? e : [], b.border = (f = a.border) != null ? f : !1, b.borderWidth = (g = a.borderWidth) != null ? g : 2, b["class"] = (h = a["class"]) != null ? h : "piechart", (i = (d = Raphael.fn).pieChart) == null && (d.pieChart = bp)
+ }
+
+ var b;
+ bO(c, a), b = null, c.prototype.overlaps = function (a) {
+ var c, d, e, f, g, h, i, j, k, l;
+ k = [b.x, b.y, b.radius], g = k[0], i = k[1], e = k[2], l = [a.x, a.y, a.radius], h = l[0], j = l[1], f = l[2];
+ if (g - e > h + f || g + e < h - f || i - e > j + f || i + e < j - f)return!1;
+ c = g - h, d = i - j;
+ return c * c + d * d > (e + f) * (e + f) ? !1 : !0
+ }, c.prototype.render = function (a) {
+ var c;
+ b = this, b.border != null && (c = b.layers.mapcanvas.circle(b.x, b.y, b.radius + b.borderWidth).attr({stroke: "none", fill: b.border})), b.chart = b.layers.mapcanvas.pieChart(b.x, b.y, b.radius, b.values, b.titles, b.colors, "none"), b.chart.push(c);
+ return b
+ }, c.prototype.update = function (a) {
+ var c;
+ return
+ }, c.prototype.clear = function () {
+ var a, c, d, e;
+ b = this, e = b.chart;
+ for (c = 0, d = e.length; c < d; c++)a = e[c], a.remove();
+ return b
+ }, c.prototype.nodes = function () {
+ var a, c, d, e, f;
+ e = b.chart, f = [];
+ for (c = 0, d = e.length; c < d; c++)a = e[c], f.push(a.node);
+ return f
+ };
+ return c
+ }(bj), U.props = ["radius", "values", "styles", "class", "titles", "colors", "border", "borderWidth"], U.layers = [], br.PieChart = U, bp = function (a, b, c, d, e, f, g) {
+ var h, i, j, k, l, m, n, o, p, q, r;
+ if (isNaN(a) || isNaN(b) || isNaN(c))return[];
+ k = this, m = Math.PI / 180, i = k.set(), n = function (a, b, c, d, e, f) {
+ var g, h, i, j;
+ g = a + c * Math.cos(-d * m), h = a + c * Math.cos(-e * m), i = b + c * Math.sin(-d * m), j = b + c * Math.sin(-e * m);
+ return k.path(["M", a, b, "L", g, i, "A", c, c, 0, +(e - d > 180), 0, h, j, "z"]).attr(f)
+ }, h = -270, o = 0, l = function (e) {
+ var j, k, l, m, p, q, r;
+ r = d[e], j = 360 * r / o, q = h + j * .5, k = f[e], m = 500, l = 30, p = n(a, b, c, h, h + j, {fill: k, stroke: g, "stroke-width": 1}), p.mouseover(function () {p.stop().animate({transform: "s1.1 1.1 " + a + " " + b}, m, "elastic")}), p.mouseout(function () {p.stop().animate({transform: ""}, m, "elastic")}), h += j, i.push(p
+ )
+ };
+ for (q = 0, r = d.length; q < r; q++)p = d[q], o += p;
+ for (j in d)l(j);
+ return i
+ }, drawStackedBars = function (a, b, c, d, e, f, g, h) {
+ function k(a, b, c, d, e) {return i.rect(a, b, c, d).attr(e)}
+
+ var i = this, j = this.set(), l = 0, m = 0, n = function (f) {
+ var i = e[f], n = d * i / m, o = a - c * .5, p = b + d * .5 - l, q = c, r = g[f], s = 500, t = 30, u = k(o, p - n, q, n, {fill: r, stroke: h, "stroke-width": 1});
+ l += n, u.mouseover(function () {u.stop().animate({transform: "s1.1 1.1 " + a + " " + b}, s, "elastic")}).mouseout(function () {u.stop().animate({transform: ""}, s, "elastic")}), j.push(u)
+ };
+ for (var o = 0, p = e.length; o < p; o++)m += e[o];
+ for (o = 0; o < p; o++)n(o);
+ return j
+ }, bg = function (a) {
+ function b(a) {
+ var c, d, e, f, g, h, i, j, k, l;
+ c = this, b.__super__.constructor.call(this, a), c.styles = (i = a.styles) != null ? i : "", c.colors = (j = a.colors) != null ? j : [], c.titles = (k = a.titles) != null ? k : ["", "", "", "", ""], c.values = (l = a.values) != null ? l : [], c.width = (e = a.width) != null ? e : 17, c.height = (f = a.height) != null ? f : 30, c["class"] = (g = a["class"]) != null ? g : "barchart", (h = (d = Raphael.fn).drawStackedBarChart) == null && (d.drawStackedBarChart = drawStackedBars)
+ }
+
+ bO(b, a), b.prototype.overlaps = function (a) {
+ var b, c, d, e, f, g, h, i, j, k, l;
+ d = this, k = [d.x, d.y, d.radius], g = k[0], i = k[1], e = k[2], l = [a.x, a.y, a.radius], h = l[0], j = l[1], f = l[2];
+ if (g - e > h + f || g + e < h - f || i - e > j + f || i + e < j - f)return!1;
+ b = g - h, c = i - j;
+ return b * b + c * c > (e + f) * (e + f) ? !1 : !0
+ }, b.prototype.render = function (a) {
+ var b, c, d, e, f, g;
+ d = this, e = d.width, c = d.height, f = d.x, g = d.y, b = d.layers.mapcanvas.rect(f - e * .5 - 2, g - c * .5 - 2, e + 4, c + 4).attr({stroke: "none", fill: "#fff"}), d.chart = d.layers.mapcanvas.drawStackedBarChart(d.x, d.y, d.width, d.height, d.values, d.titles, d.colors, "none"), d.chart.push(b);
+ return d
+ }, b.prototype.update = function () {
+ var a, b;
+ a = this;
+ return
+ }, b.prototype.clear = function () {
+ var a, b, c, d, e;
+ a = this, e = a.chart;
+ for (c = 0, d = e.length; c < d; c++)b = e[c], b.remove();
+ a.chart = [];
+ return a
+ }, b.prototype.nodes = function () {
+ var a, b, c, d, e, f;
+ b = this, e = b.chart, f = [];
+ for (c = 0, d = e.length; c < d; c++)a = e[c], f.push(a.node);
+ return f
+ };
+ return b
+ }(br.Symbol), bg.props = ["values", "styles", "class", "titles", "colors", "width", "height"], bg.layers = [], br.StackedBarChart = bg
+}).call(this);
diff --git a/plugins/UserCountryMap/js/vendor/kmeans.js b/plugins/UserCountryMap/js/vendor/kmeans.js
index b6204a3522..f4a0d9ca4c 100644
--- a/plugins/UserCountryMap/js/vendor/kmeans.js
+++ b/plugins/UserCountryMap/js/vendor/kmeans.js
@@ -1,145 +1,145 @@
// k-means clustering
function kmeans() {
- var kmeans = {},
- points = [],
- iterations = 1,
- size = 1;
-
- kmeans.size = function(x) {
- if (!arguments.length) return size;
- size = x;
- return kmeans;
- };
+ var kmeans = {},
+ points = [],
+ iterations = 1,
+ size = 1;
+
+ kmeans.size = function (x) {
+ if (!arguments.length) return size;
+ size = x;
+ return kmeans;
+ };
- kmeans.iterations = function(x) {
- if (!arguments.length) return iterations;
- iterations = x;
- return kmeans;
- };
+ kmeans.iterations = function (x) {
+ if (!arguments.length) return iterations;
+ iterations = x;
+ return kmeans;
+ };
- kmeans.add = function(x) {
- points.push(x);
- return kmeans;
- };
-
- kmeans.means = function() {
- var means = [],
- seen = {},
- n = Math.min(size, points.length);
-
- // Initialize k random (unique!) means.
- for (var i = 0, m = 2 * n; i < m; i++) {
- var p = points[~~(Math.random() * points.length)], id = p.x + "/" + p.y;
- if (!(id in seen)) {
- seen[id] = 1;
- if (means.push({x: p.x, y: p.y}) >= n) break;
- }
- }
- n = means.length;
-
- // For each iteration, create a kd-tree of the current means.
- for (var j = 0; j < iterations; j++) {
- var kd = kdtree().points(means);
-
- // Clear the state.
- for (var i = 0; i < n; i++) {
- var mean = means[i];
- mean.sumX = 0;
- mean.sumY = 0;
- mean.size = 0;
- mean.points = [];
- }
-
- // Find the mean closest to each point.
- for (var i = 0; i < points.length; i++) {
- var point = points[i], mean = kd.find(point);
- mean.sumX += point.x;
- mean.sumY += point.y;
- mean.size++;
- mean.points.push(point);
- }
-
- // Compute the new means.
- for (var i = 0; i < n; i++) {
- var mean = means[i];
- if (!mean.size) continue; // overlapping mean
- mean.x = mean.sumX / mean.size;
- mean.y = mean.sumY / mean.size;
- }
- }
+ kmeans.add = function (x) {
+ points.push(x);
+ return kmeans;
+ };
- return means;
- };
+ kmeans.means = function () {
+ var means = [],
+ seen = {},
+ n = Math.min(size, points.length);
+
+ // Initialize k random (unique!) means.
+ for (var i = 0, m = 2 * n; i < m; i++) {
+ var p = points[~~(Math.random() * points.length)], id = p.x + "/" + p.y;
+ if (!(id in seen)) {
+ seen[id] = 1;
+ if (means.push({x: p.x, y: p.y}) >= n) break;
+ }
+ }
+ n = means.length;
+
+ // For each iteration, create a kd-tree of the current means.
+ for (var j = 0; j < iterations; j++) {
+ var kd = kdtree().points(means);
+
+ // Clear the state.
+ for (var i = 0; i < n; i++) {
+ var mean = means[i];
+ mean.sumX = 0;
+ mean.sumY = 0;
+ mean.size = 0;
+ mean.points = [];
+ }
+
+ // Find the mean closest to each point.
+ for (var i = 0; i < points.length; i++) {
+ var point = points[i], mean = kd.find(point);
+ mean.sumX += point.x;
+ mean.sumY += point.y;
+ mean.size++;
+ mean.points.push(point);
+ }
+
+ // Compute the new means.
+ for (var i = 0; i < n; i++) {
+ var mean = means[i];
+ if (!mean.size) continue; // overlapping mean
+ mean.x = mean.sumX / mean.size;
+ mean.y = mean.sumY / mean.size;
+ }
+ }
+
+ return means;
+ };
- return kmeans;
+ return kmeans;
}
// kd-tree
function kdtree() {
- var kdtree = {},
- axes = ["x", "y"],
- root,
- points = [];
-
- kdtree.axes = function(x) {
- if (!arguments.length) return axes;
- axes = x;
- return kdtree;
- };
+ var kdtree = {},
+ axes = ["x", "y"],
+ root,
+ points = [];
+
+ kdtree.axes = function (x) {
+ if (!arguments.length) return axes;
+ axes = x;
+ return kdtree;
+ };
- kdtree.points = function(x) {
- if (!arguments.length) return points;
- points = x;
- root = null;
- return kdtree;
- };
-
- kdtree.find = function(x) {
- return find(kdtree.root(), x, root).point;
- };
-
- kdtree.root = function(x) {
- return root || (root = node(points, 0));
- };
-
- function node(points, depth) {
- if (!points.length) return;
- var axis = axes[depth % axes.length], median = points.length >> 1;
- points.sort(order(axis)); // could use random sample to speed up here
- return {
- axis: axis,
- point: points[median],
- left: node(points.slice(0, median), depth + 1),
- right: node(points.slice(median + 1), depth + 1)
+ kdtree.points = function (x) {
+ if (!arguments.length) return points;
+ points = x;
+ root = null;
+ return kdtree;
};
- }
- function distance(a, b) {
- var sum = 0;
- for (var i = 0; i < axes.length; i++) {
- var axis = axes[i], d = a[axis] - b[axis];
- sum += d * d;
- }
- return sum;
- }
-
- function order(axis) {
- return function(a, b) {
- a = a[axis];
- b = b[axis];
- return a < b ? -1 : a > b ? 1 : 0;
+ kdtree.find = function (x) {
+ return find(kdtree.root(), x, root).point;
+ };
+
+ kdtree.root = function (x) {
+ return root || (root = node(points, 0));
};
- }
-
- function find(node, point, best) {
- if (distance(node.point, point) < distance(best.point, point)) best = node;
- if (node.left) best = find(node.left, point, best);
- if (node.right) {
- var d = node.point[node.axis] - point[node.axis];
- if (d * d < distance(best.point, point)) best = find(node.right, point, best);
+
+ function node(points, depth) {
+ if (!points.length) return;
+ var axis = axes[depth % axes.length], median = points.length >> 1;
+ points.sort(order(axis)); // could use random sample to speed up here
+ return {
+ axis: axis,
+ point: points[median],
+ left: node(points.slice(0, median), depth + 1),
+ right: node(points.slice(median + 1), depth + 1)
+ };
+ }
+
+ function distance(a, b) {
+ var sum = 0;
+ for (var i = 0; i < axes.length; i++) {
+ var axis = axes[i], d = a[axis] - b[axis];
+ sum += d * d;
+ }
+ return sum;
+ }
+
+ function order(axis) {
+ return function (a, b) {
+ a = a[axis];
+ b = b[axis];
+ return a < b ? -1 : a > b ? 1 : 0;
+ };
}
- return best;
- }
- return kdtree;
+ function find(node, point, best) {
+ if (distance(node.point, point) < distance(best.point, point)) best = node;
+ if (node.left) best = find(node.left, point, best);
+ if (node.right) {
+ var d = node.point[node.axis] - point[node.axis];
+ if (d * d < distance(best.point, point)) best = find(node.right, point, best);
+ }
+ return best;
+ }
+
+ return kdtree;
}
diff --git a/plugins/UserCountryMap/js/vendor/raphael-min.js b/plugins/UserCountryMap/js/vendor/raphael-min.js
index eeb59152af..118d7f93f2 100644
--- a/plugins/UserCountryMap/js/vendor/raphael-min.js
+++ b/plugins/UserCountryMap/js/vendor/raphael-min.js
@@ -7,4 +7,2217 @@
// │ Licensed under the MIT (http://raphaeljs.com/license.html) license.│ \\
// └────────────────────────────────────────────────────────────────────┘ \\
-(function(a){var b="0.3.4",c="hasOwnProperty",d=/[\.\/]/,e="*",f=function(){},g=function(a,b){return a-b},h,i,j={n:{}},k=function(a,b){var c=j,d=i,e=Array.prototype.slice.call(arguments,2),f=k.listeners(a),l=0,m=!1,n,o=[],p={},q=[],r=h,s=[];h=a,i=0;for(var t=0,u=f.length;t<u;t++)"zIndex"in f[t]&&(o.push(f[t].zIndex),f[t].zIndex<0&&(p[f[t].zIndex]=f[t]));o.sort(g);while(o[l]<0){n=p[o[l++]],q.push(n.apply(b,e));if(i){i=d;return q}}for(t=0;t<u;t++){n=f[t];if("zIndex"in n)if(n.zIndex==o[l]){q.push(n.apply(b,e));if(i)break;do{l++,n=p[o[l]],n&&q.push(n.apply(b,e));if(i)break}while(n)}else p[n.zIndex]=n;else{q.push(n.apply(b,e));if(i)break}}i=d,h=r;return q.length?q:null};k.listeners=function(a){var b=a.split(d),c=j,f,g,h,i,k,l,m,n,o=[c],p=[];for(i=0,k=b.length;i<k;i++){n=[];for(l=0,m=o.length;l<m;l++){c=o[l].n,g=[c[b[i]],c[e]],h=2;while(h--)f=g[h],f&&(n.push(f),p=p.concat(f.f||[]))}o=n}return p},k.on=function(a,b){var c=a.split(d),e=j;for(var g=0,h=c.length;g<h;g++)e=e.n,!e[c[g]]&&(e[c[g]]={n:{}}),e=e[c[g]];e.f=e.f||[];for(g=0,h=e.f.length;g<h;g++)if(e.f[g]==b)return f;e.f.push(b);return function(a){+a==+a&&(b.zIndex=+a)}},k.stop=function(){i=1},k.nt=function(a){if(a)return(new RegExp("(?:\\.|\\/|^)"+a+"(?:\\.|\\/|$)")).test(h);return h},k.off=k.unbind=function(a,b){var f=a.split(d),g,h,i,k,l,m,n,o=[j];for(k=0,l=f.length;k<l;k++)for(m=0;m<o.length;m+=i.length-2){i=[m,1],g=o[m].n;if(f[k]!=e)g[f[k]]&&i.push(g[f[k]]);else for(h in g)g[c](h)&&i.push(g[h]);o.splice.apply(o,i)}for(k=0,l=o.length;k<l;k++){g=o[k];while(g.n){if(b){if(g.f){for(m=0,n=g.f.length;m<n;m++)if(g.f[m]==b){g.f.splice(m,1);break}!g.f.length&&delete g.f}for(h in g.n)if(g.n[c](h)&&g.n[h].f){var p=g.n[h].f;for(m=0,n=p.length;m<n;m++)if(p[m]==b){p.splice(m,1);break}!p.length&&delete g.n[h].f}}else{delete g.f;for(h in g.n)g.n[c](h)&&g.n[h].f&&delete g.n[h].f}g=g.n}}},k.once=function(a,b){var c=function(){var d=b.apply(this,arguments);k.unbind(a,c);return d};return k.on(a,c)},k.version=b,k.toString=function(){return"You are running Eve "+b},typeof module!="undefined"&&module.exports?module.exports=k:typeof define!="undefined"?define("eve",[],function(){return k}):a.eve=k})(this),function(){function cF(a){for(var b=0;b<cy.length;b++)cy[b].el.paper==a&&cy.splice(b--,1)}function cE(b,d,e,f,h,i){e=Q(e);var j,k,l,m=[],o,p,q,t=b.ms,u={},v={},w={};if(f)for(y=0,z=cy.length;y<z;y++){var x=cy[y];if(x.el.id==d.id&&x.anim==b){x.percent!=e?(cy.splice(y,1),l=1):k=x,d.attr(x.totalOrigin);break}}else f=+v;for(var y=0,z=b.percents.length;y<z;y++){if(b.percents[y]==e||b.percents[y]>f*b.top){e=b.percents[y],p=b.percents[y-1]||0,t=t/b.top*(e-p),o=b.percents[y+1],j=b.anim[e];break}f&&d.attr(b.anim[b.percents[y]])}if(!!j){if(!k){for(var A in j)if(j[g](A))if(U[g](A)||d.paper.customAttributes[g](A)){u[A]=d.attr(A),u[A]==null&&(u[A]=T[A]),v[A]=j[A];switch(U[A]){case C:w[A]=(v[A]-u[A])/t;break;case"colour":u[A]=a.getRGB(u[A]);var B=a.getRGB(v[A]);w[A]={r:(B.r-u[A].r)/t,g:(B.g-u[A].g)/t,b:(B.b-u[A].b)/t};break;case"path":var D=bR(u[A],v[A]),E=D[1];u[A]=D[0],w[A]=[];for(y=0,z=u[A].length;y<z;y++){w[A][y]=[0];for(var F=1,G=u[A][y].length;F<G;F++)w[A][y][F]=(E[y][F]-u[A][y][F])/t}break;case"transform":var H=d._,I=ca(H[A],v[A]);if(I){u[A]=I.from,v[A]=I.to,w[A]=[],w[A].real=!0;for(y=0,z=u[A].length;y<z;y++){w[A][y]=[u[A][y][0]];for(F=1,G=u[A][y].length;F<G;F++)w[A][y][F]=(v[A][y][F]-u[A][y][F])/t}}else{var J=d.matrix||new cb,K={_:{transform:H.transform},getBBox:function(){return d.getBBox(1)}};u[A]=[J.a,J.b,J.c,J.d,J.e,J.f],b$(K,v[A]),v[A]=K._.transform,w[A]=[(K.matrix.a-J.a)/t,(K.matrix.b-J.b)/t,(K.matrix.c-J.c)/t,(K.matrix.d-J.d)/t,(K.matrix.e-J.e)/t,(K.matrix.f-J.f)/t]}break;case"csv":var L=r(j[A])[s](c),M=r(u[A])[s](c);if(A=="clip-rect"){u[A]=M,w[A]=[],y=M.length;while(y--)w[A][y]=(L[y]-u[A][y])/t}v[A]=L;break;default:L=[][n](j[A]),M=[][n](u[A]),w[A]=[],y=d.paper.customAttributes[A].length;while(y--)w[A][y]=((L[y]||0)-(M[y]||0))/t}}var O=j.easing,P=a.easing_formulas[O];if(!P){P=r(O).match(N);if(P&&P.length==5){var R=P;P=function(a){return cC(a,+R[1],+R[2],+R[3],+R[4],t)}}else P=bf}q=j.start||b.start||+(new Date),x={anim:b,percent:e,timestamp:q,start:q+(b.del||0),status:0,initstatus:f||0,stop:!1,ms:t,easing:P,from:u,diff:w,to:v,el:d,callback:j.callback,prev:p,next:o,repeat:i||b.times,origin:d.attr(),totalOrigin:h},cy.push(x);if(f&&!k&&!l){x.stop=!0,x.start=new Date-t*f;if(cy.length==1)return cA()}l&&(x.start=new Date-x.ms*f),cy.length==1&&cz(cA)}else k.initstatus=f,k.start=new Date-k.ms*f;eve("raphael.anim.start."+d.id,d,b)}}function cD(a,b){var c=[],d={};this.ms=b,this.times=1;if(a){for(var e in a)a[g](e)&&(d[Q(e)]=a[e],c.push(Q(e)));c.sort(bd)}this.anim=d,this.top=c[c.length-1],this.percents=c}function cC(a,b,c,d,e,f){function o(a,b){var c,d,e,f,j,k;for(e=a,k=0;k<8;k++){f=m(e)-a;if(z(f)<b)return e;j=(3*i*e+2*h)*e+g;if(z(j)<1e-6)break;e=e-f/j}c=0,d=1,e=a;if(e<c)return c;if(e>d)return d;while(c<d){f=m(e);if(z(f-a)<b)return e;a>f?c=e:d=e,e=(d-c)/2+c}return e}function n(a,b){var c=o(a,b);return((l*c+k)*c+j)*c}function m(a){return((i*a+h)*a+g)*a}var g=3*b,h=3*(d-b)-g,i=1-g-h,j=3*c,k=3*(e-c)-j,l=1-j-k;return n(a,1/(200*f))}function cq(){return this.x+q+this.y+q+this.width+" × "+this.height}function cp(){return this.x+q+this.y}function cb(a,b,c,d,e,f){a!=null?(this.a=+a,this.b=+b,this.c=+c,this.d=+d,this.e=+e,this.f=+f):(this.a=1,this.b=0,this.c=0,this.d=1,this.e=0,this.f=0)}function bH(b,c,d){b=a._path2curve(b),c=a._path2curve(c);var e,f,g,h,i,j,k,l,m,n,o=d?0:[];for(var p=0,q=b.length;p<q;p++){var r=b[p];if(r[0]=="M")e=i=r[1],f=j=r[2];else{r[0]=="C"?(m=[e,f].concat(r.slice(1)),e=m[6],f=m[7]):(m=[e,f,e,f,i,j,i,j],e=i,f=j);for(var s=0,t=c.length;s<t;s++){var u=c[s];if(u[0]=="M")g=k=u[1],h=l=u[2];else{u[0]=="C"?(n=[g,h].concat(u.slice(1)),g=n[6],h=n[7]):(n=[g,h,g,h,k,l,k,l],g=k,h=l);var v=bG(m,n,d);if(d)o+=v;else{for(var w=0,x=v.length;w<x;w++)v[w].segment1=p,v[w].segment2=s,v[w].bez1=m,v[w].bez2=n;o=o.concat(v)}}}}}return o}function bG(b,c,d){var e=a.bezierBBox(b),f=a.bezierBBox(c);if(!a.isBBoxIntersect(e,f))return d?0:[];var g=bB.apply(0,b),h=bB.apply(0,c),i=~~(g/5),j=~~(h/5),k=[],l=[],m={},n=d?0:[];for(var o=0;o<i+1;o++){var p=a.findDotsAtSegment.apply(a,b.concat(o/i));k.push({x:p.x,y:p.y,t:o/i})}for(o=0;o<j+1;o++)p=a.findDotsAtSegment.apply(a,c.concat(o/j)),l.push({x:p.x,y:p.y,t:o/j});for(o=0;o<i;o++)for(var q=0;q<j;q++){var r=k[o],s=k[o+1],t=l[q],u=l[q+1],v=z(s.x-r.x)<.001?"y":"x",w=z(u.x-t.x)<.001?"y":"x",x=bD(r.x,r.y,s.x,s.y,t.x,t.y,u.x,u.y);if(x){if(m[x.x.toFixed(4)]==x.y.toFixed(4))continue;m[x.x.toFixed(4)]=x.y.toFixed(4);var y=r.t+z((x[v]-r[v])/(s[v]-r[v]))*(s.t-r.t),A=t.t+z((x[w]-t[w])/(u[w]-t[w]))*(u.t-t.t);y>=0&&y<=1&&A>=0&&A<=1&&(d?n++:n.push({x:x.x,y:x.y,t1:y,t2:A}))}}return n}function bF(a,b){return bG(a,b,1)}function bE(a,b){return bG(a,b)}function bD(a,b,c,d,e,f,g,h){if(!(x(a,c)<y(e,g)||y(a,c)>x(e,g)||x(b,d)<y(f,h)||y(b,d)>x(f,h))){var i=(a*d-b*c)*(e-g)-(a-c)*(e*h-f*g),j=(a*d-b*c)*(f-h)-(b-d)*(e*h-f*g),k=(a-c)*(f-h)-(b-d)*(e-g);if(!k)return;var l=i/k,m=j/k,n=+l.toFixed(2),o=+m.toFixed(2);if(n<+y(a,c).toFixed(2)||n>+x(a,c).toFixed(2)||n<+y(e,g).toFixed(2)||n>+x(e,g).toFixed(2)||o<+y(b,d).toFixed(2)||o>+x(b,d).toFixed(2)||o<+y(f,h).toFixed(2)||o>+x(f,h).toFixed(2))return;return{x:l,y:m}}}function bC(a,b,c,d,e,f,g,h,i){if(!(i<0||bB(a,b,c,d,e,f,g,h)<i)){var j=1,k=j/2,l=j-k,m,n=.01;m=bB(a,b,c,d,e,f,g,h,l);while(z(m-i)>n)k/=2,l+=(m<i?1:-1)*k,m=bB(a,b,c,d,e,f,g,h,l);return l}}function bB(a,b,c,d,e,f,g,h,i){i==null&&(i=1),i=i>1?1:i<0?0:i;var j=i/2,k=12,l=[-0.1252,.1252,-0.3678,.3678,-0.5873,.5873,-0.7699,.7699,-0.9041,.9041,-0.9816,.9816],m=[.2491,.2491,.2335,.2335,.2032,.2032,.1601,.1601,.1069,.1069,.0472,.0472],n=0;for(var o=0;o<k;o++){var p=j*l[o]+j,q=bA(p,a,c,e,g),r=bA(p,b,d,f,h),s=q*q+r*r;n+=m[o]*w.sqrt(s)}return j*n}function bA(a,b,c,d,e){var f=-3*b+9*c-9*d+3*e,g=a*f+6*b-12*c+6*d;return a*g-3*b+3*c}function by(a,b){var c=[];for(var d=0,e=a.length;e-2*!b>d;d+=2){var f=[{x:+a[d-2],y:+a[d-1]},{x:+a[d],y:+a[d+1]},{x:+a[d+2],y:+a[d+3]},{x:+a[d+4],y:+a[d+5]}];b?d?e-4==d?f[3]={x:+a[0],y:+a[1]}:e-2==d&&(f[2]={x:+a[0],y:+a[1]},f[3]={x:+a[2],y:+a[3]}):f[0]={x:+a[e-2],y:+a[e-1]}:e-4==d?f[3]=f[2]:d||(f[0]={x:+a[d],y:+a[d+1]}),c.push(["C",(-f[0].x+6*f[1].x+f[2].x)/6,(-f[0].y+6*f[1].y+f[2].y)/6,(f[1].x+6*f[2].x-f[3].x)/6,(f[1].y+6*f[2].y-f[3].y)/6,f[2].x,f[2].y])}return c}function bx(){return this.hex}function bv(a,b,c){function d(){var e=Array.prototype.slice.call(arguments,0),f=e.join("␀"),h=d.cache=d.cache||{},i=d.count=d.count||[];if(h[g](f)){bu(i,f);return c?c(h[f]):h[f]}i.length>=1e3&&delete h[i.shift()],i.push(f),h[f]=a[m](b,e);return c?c(h[f]):h[f]}return d}function bu(a,b){for(var c=0,d=a.length;c<d;c++)if(a[c]===b)return a.push(a.splice(c,1)[0])}function bm(a){if(Object(a)!==a)return a;var b=new a.constructor;for(var c in a)a[g](c)&&(b[c]=bm(a[c]));return b}function a(c){if(a.is(c,"function"))return b?c():eve.on("raphael.DOMload",c);if(a.is(c,E))return a._engine.create[m](a,c.splice(0,3+a.is(c[0],C))).add(c);var d=Array.prototype.slice.call(arguments,0);if(a.is(d[d.length-1],"function")){var e=d.pop();return b?e.call(a._engine.create[m](a,d)):eve.on("raphael.DOMload",function(){e.call(a._engine.create[m](a,d))})}return a._engine.create[m](a,arguments)}a.version="2.1.0",a.eve=eve;var b,c=/[, ]+/,d={circle:1,rect:1,path:1,ellipse:1,text:1,image:1},e=/\{(\d+)\}/g,f="prototype",g="hasOwnProperty",h={doc:document,win:window},i={was:Object.prototype[g].call(h.win,"Raphael"),is:h.win.Raphael},j=function(){this.ca=this.customAttributes={}},k,l="appendChild",m="apply",n="concat",o="createTouch"in h.doc,p="",q=" ",r=String,s="split",t="click dblclick mousedown mousemove mouseout mouseover mouseup touchstart touchmove touchend touchcancel"[s](q),u={mousedown:"touchstart",mousemove:"touchmove",mouseup:"touchend"},v=r.prototype.toLowerCase,w=Math,x=w.max,y=w.min,z=w.abs,A=w.pow,B=w.PI,C="number",D="string",E="array",F="toString",G="fill",H=Object.prototype.toString,I={},J="push",K=a._ISURL=/^url\(['"]?([^\)]+?)['"]?\)$/i,L=/^\s*((#[a-f\d]{6})|(#[a-f\d]{3})|rgba?\(\s*([\d\.]+%?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+%?(?:\s*,\s*[\d\.]+%?)?)\s*\)|hsba?\(\s*([\d\.]+(?:deg|\xb0|%)?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+(?:%?\s*,\s*[\d\.]+)?)%?\s*\)|hsla?\(\s*([\d\.]+(?:deg|\xb0|%)?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+(?:%?\s*,\s*[\d\.]+)?)%?\s*\))\s*$/i,M={NaN:1,Infinity:1,"-Infinity":1},N=/^(?:cubic-)?bezier\(([^,]+),([^,]+),([^,]+),([^\)]+)\)/,O=w.round,P="setAttribute",Q=parseFloat,R=parseInt,S=r.prototype.toUpperCase,T=a._availableAttrs={"arrow-end":"none","arrow-start":"none",blur:0,"clip-rect":"0 0 1e9 1e9",cursor:"default",cx:0,cy:0,fill:"#fff","fill-opacity":1,font:'10px "Arial"',"font-family":'"Arial"',"font-size":"10","font-style":"normal","font-weight":400,gradient:0,height:0,href:"http://raphaeljs.com/","letter-spacing":0,opacity:1,path:"M0,0",r:0,rx:0,ry:0,src:"",stroke:"#000","stroke-dasharray":"","stroke-linecap":"butt","stroke-linejoin":"butt","stroke-miterlimit":0,"stroke-opacity":1,"stroke-width":1,target:"_blank","text-anchor":"middle",title:"Raphael",transform:"",width:0,x:0,y:0},U=a._availableAnimAttrs={blur:C,"clip-rect":"csv",cx:C,cy:C,fill:"colour","fill-opacity":C,"font-size":C,height:C,opacity:C,path:"path",r:C,rx:C,ry:C,stroke:"colour","stroke-opacity":C,"stroke-width":C,transform:"transform",width:C,x:C,y:C},V=/[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]/g,W=/[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*,[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*/,X={hs:1,rg:1},Y=/,?([achlmqrstvxz]),?/gi,Z=/([achlmrqstvz])[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029,]*((-?\d*\.?\d*(?:e[\-+]?\d+)?[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*,?[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*)+)/ig,$=/([rstm])[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029,]*((-?\d*\.?\d*(?:e[\-+]?\d+)?[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*,?[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*)+)/ig,_=/(-?\d*\.?\d*(?:e[\-+]?\d+)?)[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*,?[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*/ig,ba=a._radial_gradient=/^r(?:\(([^,]+?)[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*,[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*([^\)]+?)\))?/,bb={},bc=function(a,b){return a.key-b.key},bd=function(a,b){return Q(a)-Q(b)},be=function(){},bf=function(a){return a},bg=a._rectPath=function(a,b,c,d,e){if(e)return[["M",a+e,b],["l",c-e*2,0],["a",e,e,0,0,1,e,e],["l",0,d-e*2],["a",e,e,0,0,1,-e,e],["l",e*2-c,0],["a",e,e,0,0,1,-e,-e],["l",0,e*2-d],["a",e,e,0,0,1,e,-e],["z"]];return[["M",a,b],["l",c,0],["l",0,d],["l",-c,0],["z"]]},bh=function(a,b,c,d){d==null&&(d=c);return[["M",a,b],["m",0,-d],["a",c,d,0,1,1,0,2*d],["a",c,d,0,1,1,0,-2*d],["z"]]},bi=a._getPath={path:function(a){return a.attr("path")},circle:function(a){var b=a.attrs;return bh(b.cx,b.cy,b.r)},ellipse:function(a){var b=a.attrs;return bh(b.cx,b.cy,b.rx,b.ry)},rect:function(a){var b=a.attrs;return bg(b.x,b.y,b.width,b.height,b.r)},image:function(a){var b=a.attrs;return bg(b.x,b.y,b.width,b.height)},text:function(a){var b=a._getBBox();return bg(b.x,b.y,b.width,b.height)}},bj=a.mapPath=function(a,b){if(!b)return a;var c,d,e,f,g,h,i;a=bR(a);for(e=0,g=a.length;e<g;e++){i=a[e];for(f=1,h=i.length;f<h;f+=2)c=b.x(i[f],i[f+1]),d=b.y(i[f],i[f+1]),i[f]=c,i[f+1]=d}return a};a._g=h,a.type=h.win.SVGAngle||h.doc.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure","1.1")?"SVG":"VML";if(a.type=="VML"){var bk=h.doc.createElement("div"),bl;bk.innerHTML='<v:shape adj="1"/>',bl=bk.firstChild,bl.style.behavior="url(#default#VML)";if(!bl||typeof bl.adj!="object")return a.type=p;bk=null}a.svg=!(a.vml=a.type=="VML"),a._Paper=j,a.fn=k=j.prototype=a.prototype,a._id=0,a._oid=0,a.is=function(a,b){b=v.call(b);if(b=="finite")return!M[g](+a);if(b=="array")return a instanceof Array;return b=="null"&&a===null||b==typeof a&&a!==null||b=="object"&&a===Object(a)||b=="array"&&Array.isArray&&Array.isArray(a)||H.call(a).slice(8,-1).toLowerCase()==b},a.angle=function(b,c,d,e,f,g){if(f==null){var h=b-d,i=c-e;if(!h&&!i)return 0;return(180+w.atan2(-i,-h)*180/B+360)%360}return a.angle(b,c,f,g)-a.angle(d,e,f,g)},a.rad=function(a){return a%360*B/180},a.deg=function(a){return a*180/B%360},a.snapTo=function(b,c,d){d=a.is(d,"finite")?d:10;if(a.is(b,E)){var e=b.length;while(e--)if(z(b[e]-c)<=d)return b[e]}else{b=+b;var f=c%b;if(f<d)return c-f;if(f>b-d)return c-f+b}return c};var bn=a.createUUID=function(a,b){return function(){return"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(a,b).toUpperCase()}}(/[xy]/g,function(a){var b=w.random()*16|0,c=a=="x"?b:b&3|8;return c.toString(16)});a.setWindow=function(b){eve("raphael.setWindow",a,h.win,b),h.win=b,h.doc=h.win.document,a._engine.initWin&&a._engine.initWin(h.win)};var bo=function(b){if(a.vml){var c=/^\s+|\s+$/g,d;try{var e=new ActiveXObject("htmlfile");e.write("<body>"),e.close(),d=e.body}catch(f){d=createPopup().document.body}var g=d.createTextRange();bo=bv(function(a){try{d.style.color=r(a).replace(c,p);var b=g.queryCommandValue("ForeColor");b=(b&255)<<16|b&65280|(b&16711680)>>>16;return"#"+("000000"+b.toString(16)).slice(-6)}catch(e){return"none"}})}else{var i=h.doc.createElement("i");i.title="Raphaël Colour Picker",i.style.display="none",h.doc.body.appendChild(i),bo=bv(function(a){i.style.color=a;return h.doc.defaultView.getComputedStyle(i,p).getPropertyValue("color")})}return bo(b)},bp=function(){return"hsb("+[this.h,this.s,this.b]+")"},bq=function(){return"hsl("+[this.h,this.s,this.l]+")"},br=function(){return this.hex},bs=function(b,c,d){c==null&&a.is(b,"object")&&"r"in b&&"g"in b&&"b"in b&&(d=b.b,c=b.g,b=b.r);if(c==null&&a.is(b,D)){var e=a.getRGB(b);b=e.r,c=e.g,d=e.b}if(b>1||c>1||d>1)b/=255,c/=255,d/=255;return[b,c,d]},bt=function(b,c,d,e){b*=255,c*=255,d*=255;var f={r:b,g:c,b:d,hex:a.rgb(b,c,d),toString:br};a.is(e,"finite")&&(f.opacity=e);return f};a.color=function(b){var c;a.is(b,"object")&&"h"in b&&"s"in b&&"b"in b?(c=a.hsb2rgb(b),b.r=c.r,b.g=c.g,b.b=c.b,b.hex=c.hex):a.is(b,"object")&&"h"in b&&"s"in b&&"l"in b?(c=a.hsl2rgb(b),b.r=c.r,b.g=c.g,b.b=c.b,b.hex=c.hex):(a.is(b,"string")&&(b=a.getRGB(b)),a.is(b,"object")&&"r"in b&&"g"in b&&"b"in b?(c=a.rgb2hsl(b),b.h=c.h,b.s=c.s,b.l=c.l,c=a.rgb2hsb(b),b.v=c.b):(b={hex:"none"},b.r=b.g=b.b=b.h=b.s=b.v=b.l=-1)),b.toString=br;return b},a.hsb2rgb=function(a,b,c,d){this.is(a,"object")&&"h"in a&&"s"in a&&"b"in a&&(c=a.b,b=a.s,a=a.h,d=a.o),a*=360;var e,f,g,h,i;a=a%360/60,i=c*b,h=i*(1-z(a%2-1)),e=f=g=c-i,a=~~a,e+=[i,h,0,0,h,i][a],f+=[h,i,i,h,0,0][a],g+=[0,0,h,i,i,h][a];return bt(e,f,g,d)},a.hsl2rgb=function(a,b,c,d){this.is(a,"object")&&"h"in a&&"s"in a&&"l"in a&&(c=a.l,b=a.s,a=a.h);if(a>1||b>1||c>1)a/=360,b/=100,c/=100;a*=360;var e,f,g,h,i;a=a%360/60,i=2*b*(c<.5?c:1-c),h=i*(1-z(a%2-1)),e=f=g=c-i/2,a=~~a,e+=[i,h,0,0,h,i][a],f+=[h,i,i,h,0,0][a],g+=[0,0,h,i,i,h][a];return bt(e,f,g,d)},a.rgb2hsb=function(a,b,c){c=bs(a,b,c),a=c[0],b=c[1],c=c[2];var d,e,f,g;f=x(a,b,c),g=f-y(a,b,c),d=g==0?null:f==a?(b-c)/g:f==b?(c-a)/g+2:(a-b)/g+4,d=(d+360)%6*60/360,e=g==0?0:g/f;return{h:d,s:e,b:f,toString:bp}},a.rgb2hsl=function(a,b,c){c=bs(a,b,c),a=c[0],b=c[1],c=c[2];var d,e,f,g,h,i;g=x(a,b,c),h=y(a,b,c),i=g-h,d=i==0?null:g==a?(b-c)/i:g==b?(c-a)/i+2:(a-b)/i+4,d=(d+360)%6*60/360,f=(g+h)/2,e=i==0?0:f<.5?i/(2*f):i/(2-2*f);return{h:d,s:e,l:f,toString:bq}},a._path2string=function(){return this.join(",").replace(Y,"$1")};var bw=a._preload=function(a,b){var c=h.doc.createElement("img");c.style.cssText="position:absolute;left:-9999em;top:-9999em",c.onload=function(){b.call(this),this.onload=null,h.doc.body.removeChild(this)},c.onerror=function(){h.doc.body.removeChild(this)},h.doc.body.appendChild(c),c.src=a};a.getRGB=bv(function(b){if(!b||!!((b=r(b)).indexOf("-")+1))return{r:-1,g:-1,b:-1,hex:"none",error:1,toString:bx};if(b=="none")return{r:-1,g:-1,b:-1,hex:"none",toString:bx};!X[g](b.toLowerCase().substring(0,2))&&b.charAt()!="#"&&(b=bo(b));var c,d,e,f,h,i,j,k=b.match(L);if(k){k[2]&&(f=R(k[2].substring(5),16),e=R(k[2].substring(3,5),16),d=R(k[2].substring(1,3),16)),k[3]&&(f=R((i=k[3].charAt(3))+i,16),e=R((i=k[3].charAt(2))+i,16),d=R((i=k[3].charAt(1))+i,16)),k[4]&&(j=k[4][s](W),d=Q(j[0]),j[0].slice(-1)=="%"&&(d*=2.55),e=Q(j[1]),j[1].slice(-1)=="%"&&(e*=2.55),f=Q(j[2]),j[2].slice(-1)=="%"&&(f*=2.55),k[1].toLowerCase().slice(0,4)=="rgba"&&(h=Q(j[3])),j[3]&&j[3].slice(-1)=="%"&&(h/=100));if(k[5]){j=k[5][s](W),d=Q(j[0]),j[0].slice(-1)=="%"&&(d*=2.55),e=Q(j[1]),j[1].slice(-1)=="%"&&(e*=2.55),f=Q(j[2]),j[2].slice(-1)=="%"&&(f*=2.55),(j[0].slice(-3)=="deg"||j[0].slice(-1)=="°")&&(d/=360),k[1].toLowerCase().slice(0,4)=="hsba"&&(h=Q(j[3])),j[3]&&j[3].slice(-1)=="%"&&(h/=100);return a.hsb2rgb(d,e,f,h)}if(k[6]){j=k[6][s](W),d=Q(j[0]),j[0].slice(-1)=="%"&&(d*=2.55),e=Q(j[1]),j[1].slice(-1)=="%"&&(e*=2.55),f=Q(j[2]),j[2].slice(-1)=="%"&&(f*=2.55),(j[0].slice(-3)=="deg"||j[0].slice(-1)=="°")&&(d/=360),k[1].toLowerCase().slice(0,4)=="hsla"&&(h=Q(j[3])),j[3]&&j[3].slice(-1)=="%"&&(h/=100);return a.hsl2rgb(d,e,f,h)}k={r:d,g:e,b:f,toString:bx},k.hex="#"+(16777216|f|e<<8|d<<16).toString(16).slice(1),a.is(h,"finite")&&(k.opacity=h);return k}return{r:-1,g:-1,b:-1,hex:"none",error:1,toString:bx}},a),a.hsb=bv(function(b,c,d){return a.hsb2rgb(b,c,d).hex}),a.hsl=bv(function(b,c,d){return a.hsl2rgb(b,c,d).hex}),a.rgb=bv(function(a,b,c){return"#"+(16777216|c|b<<8|a<<16).toString(16).slice(1)}),a.getColor=function(a){var b=this.getColor.start=this.getColor.start||{h:0,s:1,b:a||.75},c=this.hsb2rgb(b.h,b.s,b.b);b.h+=.075,b.h>1&&(b.h=0,b.s-=.2,b.s<=0&&(this.getColor.start={h:0,s:1,b:b.b}));return c.hex},a.getColor.reset=function(){delete this.start},a.parsePathString=function(b){if(!b)return null;var c=bz(b);if(c.arr)return bJ(c.arr);var d={a:7,c:6,h:1,l:2,m:2,r:4,q:4,s:4,t:2,v:1,z:0},e=[];a.is(b,E)&&a.is(b[0],E)&&(e=bJ(b)),e.length||r(b).replace(Z,function(a,b,c){var f=[],g=b.toLowerCase();c.replace(_,function(a,b){b&&f.push(+b)}),g=="m"&&f.length>2&&(e.push([b][n](f.splice(0,2))),g="l",b=b=="m"?"l":"L");if(g=="r")e.push([b][n](f));else while(f.length>=d[g]){e.push([b][n](f.splice(0,d[g])));if(!d[g])break}}),e.toString=a._path2string,c.arr=bJ(e);return e},a.parseTransformString=bv(function(b){if(!b)return null;var c={r:3,s:4,t:2,m:6},d=[];a.is(b,E)&&a.is(b[0],E)&&(d=bJ(b)),d.length||r(b).replace($,function(a,b,c){var e=[],f=v.call(b);c.replace(_,function(a,b){b&&e.push(+b)}),d.push([b][n](e))}),d.toString=a._path2string;return d});var bz=function(a){var b=bz.ps=bz.ps||{};b[a]?b[a].sleep=100:b[a]={sleep:100},setTimeout(function(){for(var c in b)b[g](c)&&c!=a&&(b[c].sleep--,!b[c].sleep&&delete b[c])});return b[a]};a.findDotsAtSegment=function(a,b,c,d,e,f,g,h,i){var j=1-i,k=A(j,3),l=A(j,2),m=i*i,n=m*i,o=k*a+l*3*i*c+j*3*i*i*e+n*g,p=k*b+l*3*i*d+j*3*i*i*f+n*h,q=a+2*i*(c-a)+m*(e-2*c+a),r=b+2*i*(d-b)+m*(f-2*d+b),s=c+2*i*(e-c)+m*(g-2*e+c),t=d+2*i*(f-d)+m*(h-2*f+d),u=j*a+i*c,v=j*b+i*d,x=j*e+i*g,y=j*f+i*h,z=90-w.atan2(q-s,r-t)*180/B;(q>s||r<t)&&(z+=180);return{x:o,y:p,m:{x:q,y:r},n:{x:s,y:t},start:{x:u,y:v},end:{x:x,y:y},alpha:z}},a.bezierBBox=function(b,c,d,e,f,g,h,i){a.is(b,"array")||(b=[b,c,d,e,f,g,h,i]);var j=bQ.apply(null,b);return{x:j.min.x,y:j.min.y,x2:j.max.x,y2:j.max.y,width:j.max.x-j.min.x,height:j.max.y-j.min.y}},a.isPointInsideBBox=function(a,b,c){return b>=a.x&&b<=a.x2&&c>=a.y&&c<=a.y2},a.isBBoxIntersect=function(b,c){var d=a.isPointInsideBBox;return d(c,b.x,b.y)||d(c,b.x2,b.y)||d(c,b.x,b.y2)||d(c,b.x2,b.y2)||d(b,c.x,c.y)||d(b,c.x2,c.y)||d(b,c.x,c.y2)||d(b,c.x2,c.y2)||(b.x<c.x2&&b.x>c.x||c.x<b.x2&&c.x>b.x)&&(b.y<c.y2&&b.y>c.y||c.y<b.y2&&c.y>b.y)},a.pathIntersection=function(a,b){return bH(a,b)},a.pathIntersectionNumber=function(a,b){return bH(a,b,1)},a.isPointInsidePath=function(b,c,d){var e=a.pathBBox(b);return a.isPointInsideBBox(e,c,d)&&bH(b,[["M",c,d],["H",e.x2+10]],1)%2==1},a._removedFactory=function(a){return function(){eve("raphael.log",null,"Raphaël: you are calling to method “"+a+"” of removed object",a)}};var bI=a.pathBBox=function(a){var b=bz(a);if(b.bbox)return b.bbox;if(!a)return{x:0,y:0,width:0,height:0,x2:0,y2:0};a=bR(a);var c=0,d=0,e=[],f=[],g;for(var h=0,i=a.length;h<i;h++){g=a[h];if(g[0]=="M")c=g[1],d=g[2],e.push(c),f.push(d);else{var j=bQ(c,d,g[1],g[2],g[3],g[4],g[5],g[6]);e=e[n](j.min.x,j.max.x),f=f[n](j.min.y,j.max.y),c=g[5],d=g[6]}}var k=y[m](0,e),l=y[m](0,f),o=x[m](0,e),p=x[m](0,f),q={x:k,y:l,x2:o,y2:p,width:o-k,height:p-l};b.bbox=bm(q);return q},bJ=function(b){var c=bm(b);c.toString=a._path2string;return c},bK=a._pathToRelative=function(b){var c=bz(b);if(c.rel)return bJ(c.rel);if(!a.is(b,E)||!a.is(b&&b[0],E))b=a.parsePathString(b);var d=[],e=0,f=0,g=0,h=0,i=0;b[0][0]=="M"&&(e=b[0][1],f=b[0][2],g=e,h=f,i++,d.push(["M",e,f]));for(var j=i,k=b.length;j<k;j++){var l=d[j]=[],m=b[j];if(m[0]!=v.call(m[0])){l[0]=v.call(m[0]);switch(l[0]){case"a":l[1]=m[1],l[2]=m[2],l[3]=m[3],l[4]=m[4],l[5]=m[5],l[6]=+(m[6]-e).toFixed(3),l[7]=+(m[7]-f).toFixed(3);break;case"v":l[1]=+(m[1]-f).toFixed(3);break;case"m":g=m[1],h=m[2];default:for(var n=1,o=m.length;n<o;n++)l[n]=+(m[n]-(n%2?e:f)).toFixed(3)}}else{l=d[j]=[],m[0]=="m"&&(g=m[1]+e,h=m[2]+f);for(var p=0,q=m.length;p<q;p++)d[j][p]=m[p]}var r=d[j].length;switch(d[j][0]){case"z":e=g,f=h;break;case"h":e+=+d[j][r-1];break;case"v":f+=+d[j][r-1];break;default:e+=+d[j][r-2],f+=+d[j][r-1]}}d.toString=a._path2string,c.rel=bJ(d);return d},bL=a._pathToAbsolute=function(b){var c=bz(b);if(c.abs)return bJ(c.abs);if(!a.is(b,E)||!a.is(b&&b[0],E))b=a.parsePathString(b);if(!b||!b.length)return[["M",0,0]];var d=[],e=0,f=0,g=0,h=0,i=0;b[0][0]=="M"&&(e=+b[0][1],f=+b[0][2],g=e,h=f,i++,d[0]=["M",e,f]);var j=b.length==3&&b[0][0]=="M"&&b[1][0].toUpperCase()=="R"&&b[2][0].toUpperCase()=="Z";for(var k,l,m=i,o=b.length;m<o;m++){d.push(k=[]),l=b[m];if(l[0]!=S.call(l[0])){k[0]=S.call(l[0]);switch(k[0]){case"A":k[1]=l[1],k[2]=l[2],k[3]=l[3],k[4]=l[4],k[5]=l[5],k[6]=+(l[6]+e),k[7]=+(l[7]+f);break;case"V":k[1]=+l[1]+f;break;case"H":k[1]=+l[1]+e;break;case"R":var p=[e,f][n](l.slice(1));for(var q=2,r=p.length;q<r;q++)p[q]=+p[q]+e,p[++q]=+p[q]+f;d.pop(),d=d[n](by(p,j));break;case"M":g=+l[1]+e,h=+l[2]+f;default:for(q=1,r=l.length;q<r;q++)k[q]=+l[q]+(q%2?e:f)}}else if(l[0]=="R")p=[e,f][n](l.slice(1)),d.pop(),d=d[n](by(p,j)),k=["R"][n](l.slice(-2));else for(var s=0,t=l.length;s<t;s++)k[s]=l[s];switch(k[0]){case"Z":e=g,f=h;break;case"H":e=k[1];break;case"V":f=k[1];break;case"M":g=k[k.length-2],h=k[k.length-1];default:e=k[k.length-2],f=k[k.length-1]}}d.toString=a._path2string,c.abs=bJ(d);return d},bM=function(a,b,c,d){return[a,b,c,d,c,d]},bN=function(a,b,c,d,e,f){var g=1/3,h=2/3;return[g*a+h*c,g*b+h*d,g*e+h*c,g*f+h*d,e,f]},bO=function(a,b,c,d,e,f,g,h,i,j){var k=B*120/180,l=B/180*(+e||0),m=[],o,p=bv(function(a,b,c){var d=a*w.cos(c)-b*w.sin(c),e=a*w.sin(c)+b*w.cos(c);return{x:d,y:e}});if(!j){o=p(a,b,-l),a=o.x,b=o.y,o=p(h,i,-l),h=o.x,i=o.y;var q=w.cos(B/180*e),r=w.sin(B/180*e),t=(a-h)/2,u=(b-i)/2,v=t*t/(c*c)+u*u/(d*d);v>1&&(v=w.sqrt(v),c=v*c,d=v*d);var x=c*c,y=d*d,A=(f==g?-1:1)*w.sqrt(z((x*y-x*u*u-y*t*t)/(x*u*u+y*t*t))),C=A*c*u/d+(a+h)/2,D=A*-d*t/c+(b+i)/2,E=w.asin(((b-D)/d).toFixed(9)),F=w.asin(((i-D)/d).toFixed(9));E=a<C?B-E:E,F=h<C?B-F:F,E<0&&(E=B*2+E),F<0&&(F=B*2+F),g&&E>F&&(E=E-B*2),!g&&F>E&&(F=F-B*2)}else E=j[0],F=j[1],C=j[2],D=j[3];var G=F-E;if(z(G)>k){var H=F,I=h,J=i;F=E+k*(g&&F>E?1:-1),h=C+c*w.cos(F),i=D+d*w.sin(F),m=bO(h,i,c,d,e,0,g,I,J,[F,H,C,D])}G=F-E;var K=w.cos(E),L=w.sin(E),M=w.cos(F),N=w.sin(F),O=w.tan(G/4),P=4/3*c*O,Q=4/3*d*O,R=[a,b],S=[a+P*L,b-Q*K],T=[h+P*N,i-Q*M],U=[h,i];S[0]=2*R[0]-S[0],S[1]=2*R[1]-S[1];if(j)return[S,T,U][n](m);m=[S,T,U][n](m).join()[s](",");var V=[];for(var W=0,X=m.length;W<X;W++)V[W]=W%2?p(m[W-1],m[W],l).y:p(m[W],m[W+1],l).x;return V},bP=function(a,b,c,d,e,f,g,h,i){var j=1-i;return{x:A(j,3)*a+A(j,2)*3*i*c+j*3*i*i*e+A(i,3)*g,y:A(j,3)*b+A(j,2)*3*i*d+j*3*i*i*f+A(i,3)*h}},bQ=bv(function(a,b,c,d,e,f,g,h){var i=e-2*c+a-(g-2*e+c),j=2*(c-a)-2*(e-c),k=a-c,l=(-j+w.sqrt(j*j-4*i*k))/2/i,n=(-j-w.sqrt(j*j-4*i*k))/2/i,o=[b,h],p=[a,g],q;z(l)>"1e12"&&(l=.5),z(n)>"1e12"&&(n=.5),l>0&&l<1&&(q=bP(a,b,c,d,e,f,g,h,l),p.push(q.x),o.push(q.y)),n>0&&n<1&&(q=bP(a,b,c,d,e,f,g,h,n),p.push(q.x),o.push(q.y)),i=f-2*d+b-(h-2*f+d),j=2*(d-b)-2*(f-d),k=b-d,l=(-j+w.sqrt(j*j-4*i*k))/2/i,n=(-j-w.sqrt(j*j-4*i*k))/2/i,z(l)>"1e12"&&(l=.5),z(n)>"1e12"&&(n=.5),l>0&&l<1&&(q=bP(a,b,c,d,e,f,g,h,l),p.push(q.x),o.push(q.y)),n>0&&n<1&&(q=bP(a,b,c,d,e,f,g,h,n),p.push(q.x),o.push(q.y));return{min:{x:y[m](0,p),y:y[m](0,o)},max:{x:x[m](0,p),y:x[m](0,o)}}}),bR=a._path2curve=bv(function(a,b){var c=!b&&bz(a);if(!b&&c.curve)return bJ(c.curve);var d=bL(a),e=b&&bL(b),f={x:0,y:0,bx:0,by:0,X:0,Y:0,qx:null,qy:null},g={x:0,y:0,bx:0,by:0,X:0,Y:0,qx:null,qy:null},h=function(a,b){var c,d;if(!a)return["C",b.x,b.y,b.x,b.y,b.x,b.y];!(a[0]in{T:1,Q:1})&&(b.qx=b.qy=null);switch(a[0]){case"M":b.X=a[1],b.Y=a[2];break;case"A":a=["C"][n](bO[m](0,[b.x,b.y][n](a.slice(1))));break;case"S":c=b.x+(b.x-(b.bx||b.x)),d=b.y+(b.y-(b.by||b.y)),a=["C",c,d][n](a.slice(1));break;case"T":b.qx=b.x+(b.x-(b.qx||b.x)),b.qy=b.y+(b.y-(b.qy||b.y)),a=["C"][n](bN(b.x,b.y,b.qx,b.qy,a[1],a[2]));break;case"Q":b.qx=a[1],b.qy=a[2],a=["C"][n](bN(b.x,b.y,a[1],a[2],a[3],a[4]));break;case"L":a=["C"][n](bM(b.x,b.y,a[1],a[2]));break;case"H":a=["C"][n](bM(b.x,b.y,a[1],b.y));break;case"V":a=["C"][n](bM(b.x,b.y,b.x,a[1]));break;case"Z":a=["C"][n](bM(b.x,b.y,b.X,b.Y))}return a},i=function(a,b){if(a[b].length>7){a[b].shift();var c=a[b];while(c.length)a.splice(b++,0,["C"][n](c.splice(0,6)));a.splice(b,1),l=x(d.length,e&&e.length||0)}},j=function(a,b,c,f,g){a&&b&&a[g][0]=="M"&&b[g][0]!="M"&&(b.splice(g,0,["M",f.x,f.y]),c.bx=0,c.by=0,c.x=a[g][1],c.y=a[g][2],l=x(d.length,e&&e.length||0))};for(var k=0,l=x(d.length,e&&e.length||0);k<l;k++){d[k]=h(d[k],f),i(d,k),e&&(e[k]=h(e[k],g)),e&&i(e,k),j(d,e,f,g,k),j(e,d,g,f,k);var o=d[k],p=e&&e[k],q=o.length,r=e&&p.length;f.x=o[q-2],f.y=o[q-1],f.bx=Q(o[q-4])||f.x,f.by=Q(o[q-3])||f.y,g.bx=e&&(Q(p[r-4])||g.x),g.by=e&&(Q(p[r-3])||g.y),g.x=e&&p[r-2],g.y=e&&p[r-1]}e||(c.curve=bJ(d));return e?[d,e]:d},null,bJ),bS=a._parseDots=bv(function(b){var c=[];for(var d=0,e=b.length;d<e;d++){var f={},g=b[d].match(/^([^:]*):?([\d\.]*)/);f.color=a.getRGB(g[1]);if(f.color.error)return null;f.color=f.color.hex,g[2]&&(f.offset=g[2]+"%"),c.push(f)}for(d=1,e=c.length-1;d<e;d++)if(!c[d].offset){var h=Q(c[d-1].offset||0),i=0;for(var j=d+1;j<e;j++)if(c[j].offset){i=c[j].offset;break}i||(i=100,j=e),i=Q(i);var k=(i-h)/(j-d+1);for(;d<j;d++)h+=k,c[d].offset=h+"%"}return c}),bT=a._tear=function(a,b){a==b.top&&(b.top=a.prev),a==b.bottom&&(b.bottom=a.next),a.next&&(a.next.prev=a.prev),a.prev&&(a.prev.next=a.next)},bU=a._tofront=function(a,b){b.top!==a&&(bT(a,b),a.next=null,a.prev=b.top,b.top.next=a,b.top=a)},bV=a._toback=function(a,b){b.bottom!==a&&(bT(a,b),a.next=b.bottom,a.prev=null,b.bottom.prev=a,b.bottom=a)},bW=a._insertafter=function(a,b,c){bT(a,c),b==c.top&&(c.top=a),b.next&&(b.next.prev=a),a.next=b.next,a.prev=b,b.next=a},bX=a._insertbefore=function(a,b,c){bT(a,c),b==c.bottom&&(c.bottom=a),b.prev&&(b.prev.next=a),a.prev=b.prev,b.prev=a,a.next=b},bY=a.toMatrix=function(a,b){var c=bI(a),d={_:{transform:p},getBBox:function(){return c}};b$(d,b);return d.matrix},bZ=a.transformPath=function(a,b){return bj(a,bY(a,b))},b$=a._extractTransform=function(b,c){if(c==null)return b._.transform;c=r(c).replace(/\.{3}|\u2026/g,b._.transform||p);var d=a.parseTransformString(c),e=0,f=0,g=0,h=1,i=1,j=b._,k=new cb;j.transform=d||[];if(d)for(var l=0,m=d.length;l<m;l++){var n=d[l],o=n.length,q=r(n[0]).toLowerCase(),s=n[0]!=q,t=s?k.invert():0,u,v,w,x,y;q=="t"&&o==3?s?(u=t.x(0,0),v=t.y(0,0),w=t.x(n[1],n[2]),x=t.y(n[1],n[2]),k.translate(w-u,x-v)):k.translate(n[1],n[2]):q=="r"?o==2?(y=y||b.getBBox(1),k.rotate(n[1],y.x+y.width/2,y.y+y.height/2),e+=n[1]):o==4&&(s?(w=t.x(n[2],n[3]),x=t.y(n[2],n[3]),k.rotate(n[1],w,x)):k.rotate(n[1],n[2],n[3]),e+=n[1]):q=="s"?o==2||o==3?(y=y||b.getBBox(1),k.scale(n[1],n[o-1],y.x+y.width/2,y.y+y.height/2),h*=n[1],i*=n[o-1]):o==5&&(s?(w=t.x(n[3],n[4]),x=t.y(n[3],n[4]),k.scale(n[1],n[2],w,x)):k.scale(n[1],n[2],n[3],n[4]),h*=n[1],i*=n[2]):q=="m"&&o==7&&k.add(n[1],n[2],n[3],n[4],n[5],n[6]),j.dirtyT=1,b.matrix=k}b.matrix=k,j.sx=h,j.sy=i,j.deg=e,j.dx=f=k.e,j.dy=g=k.f,h==1&&i==1&&!e&&j.bbox?(j.bbox.x+=+f,j.bbox.y+=+g):j.dirtyT=1},b_=function(a){var b=a[0];switch(b.toLowerCase()){case"t":return[b,0,0];case"m":return[b,1,0,0,1,0,0];case"r":return a.length==4?[b,0,a[2],a[3]]:[b,0];case"s":return a.length==5?[b,1,1,a[3],a[4]]:a.length==3?[b,1,1]:[b,1]}},ca=a._equaliseTransform=function(b,c){c=r(c).replace(/\.{3}|\u2026/g,b),b=a.parseTransformString(b)||[],c=a.parseTransformString(c)||[];var d=x(b.length,c.length),e=[],f=[],g=0,h,i,j,k;for(;g<d;g++){j=b[g]||b_(c[g]),k=c[g]||b_(j);if(j[0]!=k[0]||j[0].toLowerCase()=="r"&&(j[2]!=k[2]||j[3]!=k[3])||j[0].toLowerCase()=="s"&&(j[3]!=k[3]||j[4]!=k[4]))return;e[g]=[],f[g]=[];for(h=0,i=x(j.length,k.length);h<i;h++)h in j&&(e[g][h]=j[h]),h in k&&(f[g][h]=k[h])}return{from:e,to:f}};a._getContainer=function(b,c,d,e){var f;f=e==null&&!a.is(b,"object")?h.doc.getElementById(b):b;if(f!=null){if(f.tagName)return c==null?{container:f,width:f.style.pixelWidth||f.offsetWidth,height:f.style.pixelHeight||f.offsetHeight}:{container:f,width:c,height:d};return{container:1,x:b,y:c,width:d,height:e}}},a.pathToRelative=bK,a._engine={},a.path2curve=bR,a.matrix=function(a,b,c,d,e,f){return new cb(a,b,c,d,e,f)},function(b){function d(a){var b=w.sqrt(c(a));a[0]&&(a[0]/=b),a[1]&&(a[1]/=b)}function c(a){return a[0]*a[0]+a[1]*a[1]}b.add=function(a,b,c,d,e,f){var g=[[],[],[]],h=[[this.a,this.c,this.e],[this.b,this.d,this.f],[0,0,1]],i=[[a,c,e],[b,d,f],[0,0,1]],j,k,l,m;a&&a instanceof cb&&(i=[[a.a,a.c,a.e],[a.b,a.d,a.f],[0,0,1]]);for(j=0;j<3;j++)for(k=0;k<3;k++){m=0;for(l=0;l<3;l++)m+=h[j][l]*i[l][k];g[j][k]=m}this.a=g[0][0],this.b=g[1][0],this.c=g[0][1],this.d=g[1][1],this.e=g[0][2],this.f=g[1][2]},b.invert=function(){var a=this,b=a.a*a.d-a.b*a.c;return new cb(a.d/b,-a.b/b,-a.c/b,a.a/b,(a.c*a.f-a.d*a.e)/b,(a.b*a.e-a.a*a.f)/b)},b.clone=function(){return new cb(this.a,this.b,this.c,this.d,this.e,this.f)},b.translate=function(a,b){this.add(1,0,0,1,a,b)},b.scale=function(a,b,c,d){b==null&&(b=a),(c||d)&&this.add(1,0,0,1,c,d),this.add(a,0,0,b,0,0),(c||d)&&this.add(1,0,0,1,-c,-d)},b.rotate=function(b,c,d){b=a.rad(b),c=c||0,d=d||0;var e=+w.cos(b).toFixed(9),f=+w.sin(b).toFixed(9);this.add(e,f,-f,e,c,d),this.add(1,0,0,1,-c,-d)},b.x=function(a,b){return a*this.a+b*this.c+this.e},b.y=function(a,b){return a*this.b+b*this.d+this.f},b.get=function(a){return+this[r.fromCharCode(97+a)].toFixed(4)},b.toString=function(){return a.svg?"matrix("+[this.get(0),this.get(1),this.get(2),this.get(3),this.get(4),this.get(5)].join()+")":[this.get(0),this.get(2),this.get(1),this.get(3),0,0].join()},b.toFilter=function(){return"progid:DXImageTransform.Microsoft.Matrix(M11="+this.get(0)+", M12="+this.get(2)+", M21="+this.get(1)+", M22="+this.get(3)+", Dx="+this.get(4)+", Dy="+this.get(5)+", sizingmethod='auto expand')"},b.offset=function(){return[this.e.toFixed(4),this.f.toFixed(4)]},b.split=function(){var b={};b.dx=this.e,b.dy=this.f;var e=[[this.a,this.c],[this.b,this.d]];b.scalex=w.sqrt(c(e[0])),d(e[0]),b.shear=e[0][0]*e[1][0]+e[0][1]*e[1][1],e[1]=[e[1][0]-e[0][0]*b.shear,e[1][1]-e[0][1]*b.shear],b.scaley=w.sqrt(c(e[1])),d(e[1]),b.shear/=b.scaley;var f=-e[0][1],g=e[1][1];g<0?(b.rotate=a.deg(w.acos(g)),f<0&&(b.rotate=360-b.rotate)):b.rotate=a.deg(w.asin(f)),b.isSimple=!+b.shear.toFixed(9)&&(b.scalex.toFixed(9)==b.scaley.toFixed(9)||!b.rotate),b.isSuperSimple=!+b.shear.toFixed(9)&&b.scalex.toFixed(9)==b.scaley.toFixed(9)&&!b.rotate,b.noRotation=!+b.shear.toFixed(9)&&!b.rotate;return b},b.toTransformString=function(a){var b=a||this[s]();if(b.isSimple){b.scalex=+b.scalex.toFixed(4),b.scaley=+b.scaley.toFixed(4),b.rotate=+b.rotate.toFixed(4);return(b.dx||b.dy?"t"+[b.dx,b.dy]:p)+(b.scalex!=1||b.scaley!=1?"s"+[b.scalex,b.scaley,0,0]:p)+(b.rotate?"r"+[b.rotate,0,0]:p)}return"m"+[this.get(0),this.get(1),this.get(2),this.get(3),this.get(4),this.get(5)]}}(cb.prototype);var cc=navigator.userAgent.match(/Version\/(.*?)\s/)||navigator.userAgent.match(/Chrome\/(\d+)/);navigator.vendor=="Apple Computer, Inc."&&(cc&&cc[1]<4||navigator.platform.slice(0,2)=="iP")||navigator.vendor=="Google Inc."&&cc&&cc[1]<8?k.safari=function(){var a=this.rect(-99,-99,this.width+99,this.height+99).attr({stroke:"none"});setTimeout(function(){a.remove()})}:k.safari=be;var cd=function(){this.returnValue=!1},ce=function(){return this.originalEvent.preventDefault()},cf=function(){this.cancelBubble=!0},cg=function(){return this.originalEvent.stopPropagation()},ch=function(){if(h.doc.addEventListener)return function(a,b,c,d){var e=o&&u[b]?u[b]:b,f=function(e){var f=h.doc.documentElement.scrollTop||h.doc.body.scrollTop,i=h.doc.documentElement.scrollLeft||h.doc.body.scrollLeft,j=e.clientX+i,k=e.clientY+f;if(o&&u[g](b))for(var l=0,m=e.targetTouches&&e.targetTouches.length;l<m;l++)if(e.targetTouches[l].target==a){var n=e;e=e.targetTouches[l],e.originalEvent=n,e.preventDefault=ce,e.stopPropagation=cg;break}return c.call(d,e,j,k)};a.addEventListener(e,f,!1);return function(){a.removeEventListener(e,f,!1);return!0}};if(h.doc.attachEvent)return function(a,b,c,d){var e=function(a){a=a||h.win.event;var b=h.doc.documentElement.scrollTop||h.doc.body.scrollTop,e=h.doc.documentElement.scrollLeft||h.doc.body.scrollLeft,f=a.clientX+e,g=a.clientY+b;a.preventDefault=a.preventDefault||cd,a.stopPropagation=a.stopPropagation||cf;return c.call(d,a,f,g)};a.attachEvent("on"+b,e);var f=function(){a.detachEvent("on"+b,e);return!0};return f}}(),ci=[],cj=function(a){var b=a.clientX,c=a.clientY,d=h.doc.documentElement.scrollTop||h.doc.body.scrollTop,e=h.doc.documentElement.scrollLeft||h.doc.body.scrollLeft,f,g=ci.length;while(g--){f=ci[g];if(o){var i=a.touches.length,j;while(i--){j=a.touches[i];if(j.identifier==f.el._drag.id){b=j.clientX,c=j.clientY,(a.originalEvent?a.originalEvent:a).preventDefault();break}}}else a.preventDefault();var k=f.el.node,l,m=k.nextSibling,n=k.parentNode,p=k.style.display;h.win.opera&&n.removeChild(k),k.style.display="none",l=f.el.paper.getElementByPoint(b,c),k.style.display=p,h.win.opera&&(m?n.insertBefore(k,m):n.appendChild(k)),l&&eve("raphael.drag.over."+f.el.id,f.el,l),b+=e,c+=d,eve("raphael.drag.move."+f.el.id,f.move_scope||f.el,b-f.el._drag.x,c-f.el._drag.y,b,c,a)}},ck=function(b){a.unmousemove(cj).unmouseup(ck);var c=ci.length,d;while(c--)d=ci[c],d.el._drag={},eve("raphael.drag.end."+d.el.id,d.end_scope||d.start_scope||d.move_scope||d.el,b);ci=[]},cl=a.el={};for(var cm=t.length;cm--;)(function(b){a[b]=cl[b]=function(c,d){a.is(c,"function")&&(this.events=this.events||[],this.events.push({name:b,f:c,unbind:ch(this.shape||this.node||h.doc,b,c,d||this)}));return this},a["un"+b]=cl["un"+b]=function(a){var c=this.events||[],d=c.length;while(d--)if(c[d].name==b&&c[d].f==a){c[d].unbind(),c.splice(d,1),!c.length&&delete this.events;return this}return this}})(t[cm]);cl.data=function(b,c){var d=bb[this.id]=bb[this.id]||{};if(arguments.length==1){if(a.is(b,"object")){for(var e in b)b[g](e)&&this.data(e,b[e]);return this}eve("raphael.data.get."+this.id,this,d[b],b);return d[b]}d[b]=c,eve("raphael.data.set."+this.id,this,c,b);return this},cl.removeData=function(a){a==null?bb[this.id]={}:bb[this.id]&&delete bb[this.id][a];return this},cl.hover=function(a,b,c,d){return this.mouseover(a,c).mouseout(b,d||c)},cl.unhover=function(a,b){return this.unmouseover(a).unmouseout(b)};var cn=[];cl.drag=function(b,c,d,e,f,g){function i(i){(i.originalEvent||i).preventDefault();var j=h.doc.documentElement.scrollTop||h.doc.body.scrollTop,k=h.doc.documentElement.scrollLeft||h.doc.body.scrollLeft;this._drag.x=i.clientX+k,this._drag.y=i.clientY+j,this._drag.id=i.identifier,!ci.length&&a.mousemove(cj).mouseup(ck),ci.push({el:this,move_scope:e,start_scope:f,end_scope:g}),c&&eve.on("raphael.drag.start."+this.id,c),b&&eve.on("raphael.drag.move."+this.id,b),d&&eve.on("raphael.drag.end."+this.id,d),eve("raphael.drag.start."+this.id,f||e||this,i.clientX+k,i.clientY+j,i)}this._drag={},cn.push({el:this,start:i}),this.mousedown(i);return this},cl.onDragOver=function(a){a?eve.on("raphael.drag.over."+this.id,a):eve.unbind("raphael.drag.over."+this.id)},cl.undrag=function(){var b=cn.length;while(b--)cn[b].el==this&&(this.unmousedown(cn[b].start),cn.splice(b,1),eve.unbind("raphael.drag.*."+this.id));!cn.length&&a.unmousemove(cj).unmouseup(ck)},k.circle=function(b,c,d){var e=a._engine.circle(this,b||0,c||0,d||0);this.__set__&&this.__set__.push(e);return e},k.rect=function(b,c,d,e,f){var g=a._engine.rect(this,b||0,c||0,d||0,e||0,f||0);this.__set__&&this.__set__.push(g);return g},k.ellipse=function(b,c,d,e){var f=a._engine.ellipse(this,b||0,c||0,d||0,e||0);this.__set__&&this.__set__.push(f);return f},k.path=function(b){b&&!a.is(b,D)&&!a.is(b[0],E)&&(b+=p);var c=a._engine.path(a.format[m](a,arguments),this);this.__set__&&this.__set__.push(c);return c},k.image=function(b,c,d,e,f){var g=a._engine.image(this,b||"about:blank",c||0,d||0,e||0,f||0);this.__set__&&this.__set__.push(g);return g},k.text=function(b,c,d){var e=a._engine.text(this,b||0,c||0,r(d));this.__set__&&this.__set__.push(e);return e},k.set=function(b){!a.is(b,"array")&&(b=Array.prototype.splice.call(arguments,0,arguments.length));var c=new cG(b);this.__set__&&this.__set__.push(c);return c},k.setStart=function(a){this.__set__=a||this.set()},k.setFinish=function(a){var b=this.__set__;delete this.__set__;return b},k.setSize=function(b,c){return a._engine.setSize.call(this,b,c)},k.setViewBox=function(b,c,d,e,f){return a._engine.setViewBox.call(this,b,c,d,e,f)},k.top=k.bottom=null,k.raphael=a;var co=function(a){var b=a.getBoundingClientRect(),c=a.ownerDocument,d=c.body,e=c.documentElement,f=e.clientTop||d.clientTop||0,g=e.clientLeft||d.clientLeft||0,i=b.top+(h.win.pageYOffset||e.scrollTop||d.scrollTop)-f,j=b.left+(h.win.pageXOffset||e.scrollLeft||d.scrollLeft)-g;return{y:i,x:j}};k.getElementByPoint=function(a,b){var c=this,d=c.canvas,e=h.doc.elementFromPoint(a,b);if(h.win.opera&&e.tagName=="svg"){var f=co(d),g=d.createSVGRect();g.x=a-f.x,g.y=b-f.y,g.width=g.height=1;var i=d.getIntersectionList(g,null);i.length&&(e=i[i.length-1])}if(!e)return null;while(e.parentNode&&e!=d.parentNode&&!e.raphael)e=e.parentNode;e==c.canvas.parentNode&&(e=d),e=e&&e.raphael?c.getById(e.raphaelid):null;return e},k.getById=function(a){var b=this.bottom;while(b){if(b.id==a)return b;b=b.next}return null},k.forEach=function(a,b){var c=this.bottom;while(c){if(a.call(b,c)===!1)return this;c=c.next}return this},k.getElementsByPoint=function(a,b){var c=this.set();this.forEach(function(d){d.isPointInside(a,b)&&c.push(d)});return c},cl.isPointInside=function(b,c){var d=this.realPath=this.realPath||bi[this.type](this);return a.isPointInsidePath(d,b,c)},cl.getBBox=function(a){if(this.removed)return{};var b=this._;if(a){if(b.dirty||!b.bboxwt)this.realPath=bi[this.type](this),b.bboxwt=bI(this.realPath),b.bboxwt.toString=cq,b.dirty=0;return b.bboxwt}if(b.dirty||b.dirtyT||!b.bbox){if(b.dirty||!this.realPath)b.bboxwt=0,this.realPath=bi[this.type](this);b.bbox=bI(bj(this.realPath,this.matrix)),b.bbox.toString=cq,b.dirty=b.dirtyT=0}return b.bbox},cl.clone=function(){if(this.removed)return null;var a=this.paper[this.type]().attr(this.attr());this.__set__&&this.__set__.push(a);return a},cl.glow=function(a){if(this.type=="text")return null;a=a||{};var b={width:(a.width||10)+(+this.attr("stroke-width")||1),fill:a.fill||!1,opacity:a.opacity||.5,offsetx:a.offsetx||0,offsety:a.offsety||0,color:a.color||"#000"},c=b.width/2,d=this.paper,e=d.set(),f=this.realPath||bi[this.type](this);f=this.matrix?bj(f,this.matrix):f;for(var g=1;g<c+1;g++)e.push(d.path(f).attr({stroke:b.color,fill:b.fill?b.color:"none","stroke-linejoin":"round","stroke-linecap":"round","stroke-width":+(b.width/c*g).toFixed(3),opacity:+(b.opacity/c).toFixed(3)}));return e.insertBefore(this).translate(b.offsetx,b.offsety)};var cr={},cs=function(b,c,d,e,f,g,h,i,j){return j==null?bB(b,c,d,e,f,g,h,i):a.findDotsAtSegment(b,c,d,e,f,g,h,i,bC(b,c,d,e,f,g,h,i,j))},ct=function(b,c){return function(d,e,f){d=bR(d);var g,h,i,j,k="",l={},m,n=0;for(var o=0,p=d.length;o<p;o++){i=d[o];if(i[0]=="M")g=+i[1],h=+i[2];else{j=cs(g,h,i[1],i[2],i[3],i[4],i[5],i[6]);if(n+j>e){if(c&&!l.start){m=cs(g,h,i[1],i[2],i[3],i[4],i[5],i[6],e-n),k+=["C"+m.start.x,m.start.y,m.m.x,m.m.y,m.x,m.y];if(f)return k;l.start=k,k=["M"+m.x,m.y+"C"+m.n.x,m.n.y,m.end.x,m.end.y,i[5],i[6]].join(),n+=j,g=+i[5],h=+i[6];continue}if(!b&&!c){m=cs(g,h,i[1],i[2],i[3],i[4],i[5],i[6],e-n);return{x:m.x,y:m.y,alpha:m.alpha}}}n+=j,g=+i[5],h=+i[6]}k+=i.shift()+i}l.end=k,m=b?n:c?l:a.findDotsAtSegment(g,h,i[0],i[1],i[2],i[3],i[4],i[5],1),m.alpha&&(m={x:m.x,y:m.y,alpha:m.alpha});return m}},cu=ct(1),cv=ct(),cw=ct(0,1);a.getTotalLength=cu,a.getPointAtLength=cv,a.getSubpath=function(a,b,c){if(this.getTotalLength(a)-c<1e-6)return cw(a,b).end;var d=cw(a,c,1);return b?cw(d,b).end:d},cl.getTotalLength=function(){if(this.type=="path"){if(this.node.getTotalLength)return this.node.getTotalLength();return cu(this.attrs.path)}},cl.getPointAtLength=function(a){if(this.type=="path")return cv(this.attrs.path,a)},cl.getSubpath=function(b,c){if(this.type=="path")return a.getSubpath(this.attrs.path,b,c)};var cx=a.easing_formulas={linear:function(a){return a},"<":function(a){return A(a,1.7)},">":function(a){return A(a,.48)},"<>":function(a){var b=.48-a/1.04,c=w.sqrt(.1734+b*b),d=c-b,e=A(z(d),1/3)*(d<0?-1:1),f=-c-b,g=A(z(f),1/3)*(f<0?-1:1),h=e+g+.5;return(1-h)*3*h*h+h*h*h},backIn:function(a){var b=1.70158;return a*a*((b+1)*a-b)},backOut:function(a){a=a-1;var b=1.70158;return a*a*((b+1)*a+b)+1},elastic:function(a){if(a==!!a)return a;return A(2,-10*a)*w.sin((a-.075)*2*B/.3)+1},bounce:function(a){var b=7.5625,c=2.75,d;a<1/c?d=b*a*a:a<2/c?(a-=1.5/c,d=b*a*a+.75):a<2.5/c?(a-=2.25/c,d=b*a*a+.9375):(a-=2.625/c,d=b*a*a+.984375);return d}};cx.easeIn=cx["ease-in"]=cx["<"],cx.easeOut=cx["ease-out"]=cx[">"],cx.easeInOut=cx["ease-in-out"]=cx["<>"],cx["back-in"]=cx.backIn,cx["back-out"]=cx.backOut;var cy=[],cz=window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.oRequestAnimationFrame||window.msRequestAnimationFrame||function(a){setTimeout(a,16)},cA=function(){var b=+(new Date),c=0;for(;c<cy.length;c++){var d=cy[c];if(d.el.removed||d.paused)continue;var e=b-d.start,f=d.ms,h=d.easing,i=d.from,j=d.diff,k=d.to,l=d.t,m=d.el,o={},p,r={},s;d.initstatus?(e=(d.initstatus*d.anim.top-d.prev)/(d.percent-d.prev)*f,d.status=d.initstatus,delete d.initstatus,d.stop&&cy.splice(c--,1)):d.status=(d.prev+(d.percent-d.prev)*(e/f))/d.anim.top;if(e<0)continue;if(e<f){var t=h(e/f);for(var u in i)if(i[g](u)){switch(U[u]){case C:p=+i[u]+t*f*j[u];break;case"colour":p="rgb("+[cB(O(i[u].r+t*f*j[u].r)),cB(O(i[u].g+t*f*j[u].g)),cB(O(i[u].b+t*f*j[u].b))].join(",")+")";break;case"path":p=[];for(var v=0,w=i[u].length;v<w;v++){p[v]=[i[u][v][0]];for(var x=1,y=i[u][v].length;x<y;x++)p[v][x]=+i[u][v][x]+t*f*j[u][v][x];p[v]=p[v].join(q)}p=p.join(q);break;case"transform":if(j[u].real){p=[];for(v=0,w=i[u].length;v<w;v++){p[v]=[i[u][v][0]];for(x=1,y=i[u][v].length;x<y;x++)p[v][x]=i[u][v][x]+t*f*j[u][v][x]}}else{var z=function(a){return+i[u][a]+t*f*j[u][a]};p=[["m",z(0),z(1),z(2),z(3),z(4),z(5)]]}break;case"csv":if(u=="clip-rect"){p=[],v=4;while(v--)p[v]=+i[u][v]+t*f*j[u][v]}break;default:var A=[][n](i[u]);p=[],v=m.paper.customAttributes[u].length;while(v--)p[v]=+A[v]+t*f*j[u][v]}o[u]=p}m.attr(o),function(a,b,c){setTimeout(function(){eve("raphael.anim.frame."+a,b,c)})}(m.id,m,d.anim)}else{(function(b,c,d){setTimeout(function(){eve("raphael.anim.frame."+c.id,c,d),eve("raphael.anim.finish."+c.id,c,d),a.is(b,"function")&&b.call(c)})})(d.callback,m,d.anim),m.attr(k),cy.splice(c--,1);if(d.repeat>1&&!d.next){for(s in k)k[g](s)&&(r[s]=d.totalOrigin[s]);d.el.attr(r),cE(d.anim,d.el,d.anim.percents[0],null,d.totalOrigin,d.repeat-1)}d.next&&!d.stop&&cE(d.anim,d.el,d.next,null,d.totalOrigin,d.repeat)}}a.svg&&m&&m.paper&&m.paper.safari(),cy.length&&cz(cA)},cB=function(a){return a>255?255:a<0?0:a};cl.animateWith=function(b,c,d,e,f,g){var h=this;if(h.removed){g&&g.call(h);return h}var i=d instanceof cD?d:a.animation(d,e,f,g),j,k;cE(i,h,i.percents[0],null,h.attr());for(var l=0,m=cy.length;l<m;l++)if(cy[l].anim==c&&cy[l].el==b){cy[m-1].start=cy[l].start;break}return h},cl.onAnimation=function(a){a?eve.on("raphael.anim.frame."+this.id,a):eve.unbind("raphael.anim.frame."+this.id);return this},cD.prototype.delay=function(a){var b=new cD(this.anim,this.ms);b.times=this.times,b.del=+a||0;return b},cD.prototype.repeat=function(a){var b=new cD(this.anim,this.ms);b.del=this.del,b.times=w.floor(x(a,0))||1;return b},a.animation=function(b,c,d,e){if(b instanceof cD)return b;if(a.is(d,"function")||!d)e=e||d||null,d=null;b=Object(b),c=+c||0;var f={},h,i;for(i in b)b[g](i)&&Q(i)!=i&&Q(i)+"%"!=i&&(h=!0,f[i]=b[i]);if(!h)return new cD(b,c);d&&(f.easing=d),e&&(f.callback=e);return new cD({100:f},c)},cl.animate=function(b,c,d,e){var f=this;if(f.removed){e&&e.call(f);return f}var g=b instanceof cD?b:a.animation(b,c,d,e);cE(g,f,g.percents[0],null,f.attr());return f},cl.setTime=function(a,b){a&&b!=null&&this.status(a,y(b,a.ms)/a.ms);return this},cl.status=function(a,b){var c=[],d=0,e,f;if(b!=null){cE(a,this,-1,y(b,1));return this}e=cy.length;for(;d<e;d++){f=cy[d];if(f.el.id==this.id&&(!a||f.anim==a)){if(a)return f.status;c.push({anim:f.anim,status:f.status})}}if(a)return 0;return c},cl.pause=function(a){for(var b=0;b<cy.length;b++)cy[b].el.id==this.id&&(!a||cy[b].anim==a)&&eve("raphael.anim.pause."+this.id,this,cy[b].anim)!==!1&&(cy[b].paused=!0);return this},cl.resume=function(a){for(var b=0;b<cy.length;b++)if(cy[b].el.id==this.id&&(!a||cy[b].anim==a)){var c=cy[b];eve("raphael.anim.resume."+this.id,this,c.anim)!==!1&&(delete c.paused,this.status(c.anim,c.status))}return this},cl.stop=function(a){for(var b=0;b<cy.length;b++)cy[b].el.id==this.id&&(!a||cy[b].anim==a)&&eve("raphael.anim.stop."+this.id,this,cy[b].anim)!==!1&&cy.splice(b--,1);return this},eve.on("raphael.remove",cF),eve.on("raphael.clear",cF),cl.toString=function(){return"Raphaël’s object"};var cG=function(a){this.items=[],this.length=0,this.type="set";if(a)for(var b=0,c=a.length;b<c;b++)a[b]&&(a[b].constructor==cl.constructor||a[b].constructor==cG)&&(this[this.items.length]=this.items[this.items.length]=a[b],this.length++)},cH=cG.prototype;cH.push=function(){var a,b;for(var c=0,d=arguments.length;c<d;c++)a=arguments[c],a&&(a.constructor==cl.constructor||a.constructor==cG)&&(b=this.items.length,this[b]=this.items[b]=a,this.length++);return this},cH.pop=function(){this.length&&delete this[this.length--];return this.items.pop()},cH.forEach=function(a,b){for(var c=0,d=this.items.length;c<d;c++)if(a.call(b,this.items[c],c)===!1)return this;return this};for(var cI in cl)cl[g](cI)&&(cH[cI]=function(a){return function(){var b=arguments;return this.forEach(function(c){c[a][m](c,b)})}}(cI));cH.attr=function(b,c){if(b&&a.is(b,E)&&a.is(b[0],"object"))for(var d=0,e=b.length;d<e;d++)this.items[d].attr(b[d]);else for(var f=0,g=this.items.length;f<g;f++)this.items[f].attr(b,c);return this},cH.clear=function(){while(this.length)this.pop()},cH.splice=function(a,b,c){a=a<0?x(this.length+a,0):a,b=x(0,y(this.length-a,b));var d=[],e=[],f=[],g;for(g=2;g<arguments.length;g++)f.push(arguments[g]);for(g=0;g<b;g++)e.push(this[a+g]);for(;g<this.length-a;g++)d.push(this[a+g]);var h=f.length;for(g=0;g<h+d.length;g++)this.items[a+g]=this[a+g]=g<h?f[g]:d[g-h];g=this.items.length=this.length-=b-h;while(this[g])delete this[g++];return new cG(e)},cH.exclude=function(a){for(var b=0,c=this.length;b<c;b++)if(this[b]==a){this.splice(b,1);return!0}},cH.animate=function(b,c,d,e){(a.is(d,"function")||!d)&&(e=d||null);var f=this.items.length,g=f,h,i=this,j;if(!f)return this;e&&(j=function(){!--f&&e.call(i)}),d=a.is(d,D)?d:j;var k=a.animation(b,c,d,j);h=this.items[--g].animate(k);while(g--)this.items[g]&&!this.items[g].removed&&this.items[g].animateWith(h,k,k);return this},cH.insertAfter=function(a){var b=this.items.length;while(b--)this.items[b].insertAfter(a);return this},cH.getBBox=function(){var a=[],b=[],c=[],d=[];for(var e=this.items.length;e--;)if(!this.items[e].removed){var f=this.items[e].getBBox();a.push(f.x),b.push(f.y),c.push(f.x+f.width),d.push(f.y+f.height)}a=y[m](0,a),b=y[m](0,b),c=x[m](0,c),d=x[m](0,d);return{x:a,y:b,x2:c,y2:d,width:c-a,height:d-b}},cH.clone=function(a){a=new cG;for(var b=0,c=this.items.length;b<c;b++)a.push(this.items[b].clone());return a},cH.toString=function(){return"Raphaël‘s set"},a.registerFont=function(a){if(!a.face)return a;this.fonts=this.fonts||{};var b={w:a.w,face:{},glyphs:{}},c=a.face["font-family"];for(var d in a.face)a.face[g](d)&&(b.face[d]=a.face[d]);this.fonts[c]?this.fonts[c].push(b):this.fonts[c]=[b];if(!a.svg){b.face["units-per-em"]=R(a.face["units-per-em"],10);for(var e in a.glyphs)if(a.glyphs[g](e)){var f=a.glyphs[e];b.glyphs[e]={w:f.w,k:{},d:f.d&&"M"+f.d.replace(/[mlcxtrv]/g,function(a){return{l:"L",c:"C",x:"z",t:"m",r:"l",v:"c"}[a]||"M"})+"z"};if(f.k)for(var h in f.k)f[g](h)&&(b.glyphs[e].k[h]=f.k[h])}}return a},k.getFont=function(b,c,d,e){e=e||"normal",d=d||"normal",c=+c||{normal:400,bold:700,lighter:300,bolder:800}[c]||400;if(!!a.fonts){var f=a.fonts[b];if(!f){var h=new RegExp("(^|\\s)"+b.replace(/[^\w\d\s+!~.:_-]/g,p)+"(\\s|$)","i");for(var i in a.fonts)if(a.fonts[g](i)&&h.test(i)){f=a.fonts[i];break}}var j;if(f)for(var k=0,l=f.length;k<l;k++){j=f[k];if(j.face["font-weight"]==c&&(j.face["font-style"]==d||!j.face["font-style"])&&j.face["font-stretch"]==e)break}return j}},k.print=function(b,d,e,f,g,h,i){h=h||"middle",i=x(y(i||0,1),-1);var j=r(e)[s](p),k=0,l=0,m=p,n;a.is(f,e)&&(f=this.getFont(f));if(f){n=(g||16)/f.face["units-per-em"];var o=f.face.bbox[s](c),q=+o[0],t=o[3]-o[1],u=0,v=+o[1]+(h=="baseline"?t+ +f.face.descent:t/2);for(var w=0,z=j.length;w<z;w++){if(j[w]=="\n")k=0,B=0,l=0,u+=t;else{var A=l&&f.glyphs[j[w-1]]||{},B=f.glyphs[j[w]];k+=l?(A.w||f.w)+(A.k&&A.k[j[w]]||0)+f.w*i:0,l=1}B&&B.d&&(m+=a.transformPath(B.d,["t",k*n,u*n,"s",n,n,q,v,"t",(b-q)/n,(d-v)/n]))}}return this.path(m).attr({fill:"#000",stroke:"none"})},k.add=function(b){if(a.is(b,"array")){var c=this.set(),e=0,f=b.length,h;for(;e<f;e++)h=b[e]||{},d[g](h.type)&&c.push(this[h.type]().attr(h))}return c},a.format=function(b,c){var d=a.is(c,E)?[0][n](c):arguments;b&&a.is(b,D)&&d.length-1&&(b=b.replace(e,function(a,b){return d[++b]==null?p:d[b]}));return b||p},a.fullfill=function(){var a=/\{([^\}]+)\}/g,b=/(?:(?:^|\.)(.+?)(?=\[|\.|$|\()|\[('|")(.+?)\2\])(\(\))?/g,c=function(a,c,d){var e=d;c.replace(b,function(a,b,c,d,f){b=b||d,e&&(b in e&&(e=e[b]),typeof e=="function"&&f&&(e=e()))}),e=(e==null||e==d?a:e)+"";return e};return function(b,d){return String(b).replace(a,function(a,b){return c(a,b,d)})}}(),a.ninja=function(){i.was?h.win.Raphael=i.is:delete Raphael;return a},a.st=cH,function(b,c,d){function e(){/in/.test(b.readyState)?setTimeout(e,9):a.eve("raphael.DOMload")}b.readyState==null&&b.addEventListener&&(b.addEventListener(c,d=function(){b.removeEventListener(c,d,!1),b.readyState="complete"},!1),b.readyState="loading"),e()}(document,"DOMContentLoaded"),i.was?h.win.Raphael=a:Raphael=a,eve.on("raphael.DOMload",function(){b=!0})}(),window.Raphael.svg&&function(a){var b="hasOwnProperty",c=String,d=parseFloat,e=parseInt,f=Math,g=f.max,h=f.abs,i=f.pow,j=/[, ]+/,k=a.eve,l="",m=" ",n="http://www.w3.org/1999/xlink",o={block:"M5,0 0,2.5 5,5z",classic:"M5,0 0,2.5 5,5 3.5,3 3.5,2z",diamond:"M2.5,0 5,2.5 2.5,5 0,2.5z",open:"M6,1 1,3.5 6,6",oval:"M2.5,0A2.5,2.5,0,0,1,2.5,5 2.5,2.5,0,0,1,2.5,0z"},p={};a.toString=function(){return"Your browser supports SVG.\nYou are running Raphaël "+this.version};var q=function(d,e){if(e){typeof d=="string"&&(d=q(d));for(var f in e)e[b](f)&&(f.substring(0,6)=="xlink:"?d.setAttributeNS(n,f.substring(6),c(e[f])):d.setAttribute(f,c(e[f])))}else d=a._g.doc.createElementNS("http://www.w3.org/2000/svg",d),d.style&&(d.style.webkitTapHighlightColor="rgba(0,0,0,0)");return d},r=function(b,e){var j="linear",k=b.id+e,m=.5,n=.5,o=b.node,p=b.paper,r=o.style,s=a._g.doc.getElementById(k);if(!s){e=c(e).replace(a._radial_gradient,function(a,b,c){j="radial";if(b&&c){m=d(b),n=d(c);var e=(n>.5)*2-1;i(m-.5,2)+i(n-.5,2)>.25&&(n=f.sqrt(.25-i(m-.5,2))*e+.5)&&n!=.5&&(n=n.toFixed(5)-1e-5*e)}return l}),e=e.split(/\s*\-\s*/);if(j=="linear"){var t=e.shift();t=-d(t);if(isNaN(t))return null;var u=[0,0,f.cos(a.rad(t)),f.sin(a.rad(t))],v=1/(g(h(u[2]),h(u[3]))||1);u[2]*=v,u[3]*=v,u[2]<0&&(u[0]=-u[2],u[2]=0),u[3]<0&&(u[1]=-u[3],u[3]=0)}var w=a._parseDots(e);if(!w)return null;k=k.replace(/[\(\)\s,\xb0#]/g,"_"),b.gradient&&k!=b.gradient.id&&(p.defs.removeChild(b.gradient),delete b.gradient);if(!b.gradient){s=q(j+"Gradient",{id:k}),b.gradient=s,q(s,j=="radial"?{fx:m,fy:n}:{x1:u[0],y1:u[1],x2:u[2],y2:u[3],gradientTransform:b.matrix.invert()}),p.defs.appendChild(s);for(var x=0,y=w.length;x<y;x++)s.appendChild(q("stop",{offset:w[x].offset?w[x].offset:x?"100%":"0%","stop-color":w[x].color||"#fff"}))}}q(o,{fill:"url(#"+k+")",opacity:1,"fill-opacity":1}),r.fill=l,r.opacity=1,r.fillOpacity=1;return 1},s=function(a){var b=a.getBBox(1);q(a.pattern,{patternTransform:a.matrix.invert()+" translate("+b.x+","+b.y+")"})},t=function(d,e,f){if(d.type=="path"){var g=c(e).toLowerCase().split("-"),h=d.paper,i=f?"end":"start",j=d.node,k=d.attrs,m=k["stroke-width"],n=g.length,r="classic",s,t,u,v,w,x=3,y=3,z=5;while(n--)switch(g[n]){case"block":case"classic":case"oval":case"diamond":case"open":case"none":r=g[n];break;case"wide":y=5;break;case"narrow":y=2;break;case"long":x=5;break;case"short":x=2}r=="open"?(x+=2,y+=2,z+=2,u=1,v=f?4:1,w={fill:"none",stroke:k.stroke}):(v=u=x/2,w={fill:k.stroke,stroke:"none"}),d._.arrows?f?(d._.arrows.endPath&&p[d._.arrows.endPath]--,d._.arrows.endMarker&&p[d._.arrows.endMarker]--):(d._.arrows.startPath&&p[d._.arrows.startPath]--,d._.arrows.startMarker&&p[d._.arrows.startMarker]--):d._.arrows={};if(r!="none"){var A="raphael-marker-"+r,B="raphael-marker-"+i+r+x+y;a._g.doc.getElementById(A)?p[A]++:(h.defs.appendChild(q(q("path"),{"stroke-linecap":"round",d:o[r],id:A})),p[A]=1);var C=a._g.doc.getElementById(B),D;C?(p[B]++,D=C.getElementsByTagName("use")[0]):(C=q(q("marker"),{id:B,markerHeight:y,markerWidth:x,orient:"auto",refX:v,refY:y/2}),D=q(q("use"),{"xlink:href":"#"+A,transform:(f?"rotate(180 "+x/2+" "+y/2+") ":l)+"scale("+x/z+","+y/z+")","stroke-width":(1/((x/z+y/z)/2)).toFixed(4)}),C.appendChild(D),h.defs.appendChild(C),p[B]=1),q(D,w);var F=u*(r!="diamond"&&r!="oval");f?(s=d._.arrows.startdx*m||0,t=a.getTotalLength(k.path)-F*m):(s=F*m,t=a.getTotalLength(k.path)-(d._.arrows.enddx*m||0)),w={},w["marker-"+i]="url(#"+B+")";if(t||s)w.d=Raphael.getSubpath(k.path,s,t);q(j,w),d._.arrows[i+"Path"]=A,d._.arrows[i+"Marker"]=B,d._.arrows[i+"dx"]=F,d._.arrows[i+"Type"]=r,d._.arrows[i+"String"]=e}else f?(s=d._.arrows.startdx*m||0,t=a.getTotalLength(k.path)-s):(s=0,t=a.getTotalLength(k.path)-(d._.arrows.enddx*m||0)),d._.arrows[i+"Path"]&&q(j,{d:Raphael.getSubpath(k.path,s,t)}),delete d._.arrows[i+"Path"],delete d._.arrows[i+"Marker"],delete d._.arrows[i+"dx"],delete d._.arrows[i+"Type"],delete d._.arrows[i+"String"];for(w in p)if(p[b](w)&&!p[w]){var G=a._g.doc.getElementById(w);G&&G.parentNode.removeChild(G)}}},u={"":[0],none:[0],"-":[3,1],".":[1,1],"-.":[3,1,1,1],"-..":[3,1,1,1,1,1],". ":[1,3],"- ":[4,3],"--":[8,3],"- .":[4,3,1,3],"--.":[8,3,1,3],"--..":[8,3,1,3,1,3]},v=function(a,b,d){b=u[c(b).toLowerCase()];if(b){var e=a.attrs["stroke-width"]||"1",f={round:e,square:e,butt:0}[a.attrs["stroke-linecap"]||d["stroke-linecap"]]||0,g=[],h=b.length;while(h--)g[h]=b[h]*e+(h%2?1:-1)*f;q(a.node,{"stroke-dasharray":g.join(",")})}},w=function(d,f){var i=d.node,k=d.attrs,m=i.style.visibility;i.style.visibility="hidden";for(var o in f)if(f[b](o)){if(!a._availableAttrs[b](o))continue;var p=f[o];k[o]=p;switch(o){case"blur":d.blur(p);break;case"href":case"title":case"target":var u=i.parentNode;if(u.tagName.toLowerCase()!="a"){var w=q("a");u.insertBefore(w,i),w.appendChild(i),u=w}o=="target"?u.setAttributeNS(n,"show",p=="blank"?"new":p):u.setAttributeNS(n,o,p);break;case"cursor":i.style.cursor=p;break;case"transform":d.transform(p);break;case"arrow-start":t(d,p);break;case"arrow-end":t(d,p,1);break;case"clip-rect":var x=c(p).split(j);if(x.length==4){d.clip&&d.clip.parentNode.parentNode.removeChild(d.clip.parentNode);var z=q("clipPath"),A=q("rect");z.id=a.createUUID(),q(A,{x:x[0],y:x[1],width:x[2],height:x[3]}),z.appendChild(A),d.paper.defs.appendChild(z),q(i,{"clip-path":"url(#"+z.id+")"}),d.clip=A}if(!p){var B=i.getAttribute("clip-path");if(B){var C=a._g.doc.getElementById(B.replace(/(^url\(#|\)$)/g,l));C&&C.parentNode.removeChild(C),q(i,{"clip-path":l}),delete d.clip}}break;case"path":d.type=="path"&&(q(i,{d:p?k.path=a._pathToAbsolute(p):"M0,0"}),d._.dirty=1,d._.arrows&&("startString"in d._.arrows&&t(d,d._.arrows.startString),"endString"in d._.arrows&&t(d,d._.arrows.endString,1)));break;case"width":i.setAttribute(o,p),d._.dirty=1;if(k.fx)o="x",p=k.x;else break;case"x":k.fx&&(p=-k.x-(k.width||0));case"rx":if(o=="rx"&&d.type=="rect")break;case"cx":i.setAttribute(o,p),d.pattern&&s(d),d._.dirty=1;break;case"height":i.setAttribute(o,p),d._.dirty=1;if(k.fy)o="y",p=k.y;else break;case"y":k.fy&&(p=-k.y-(k.height||0));case"ry":if(o=="ry"&&d.type=="rect")break;case"cy":i.setAttribute(o,p),d.pattern&&s(d),d._.dirty=1;break;case"r":d.type=="rect"?q(i,{rx:p,ry:p}):i.setAttribute(o,p),d._.dirty=1;break;case"src":d.type=="image"&&i.setAttributeNS(n,"href",p);break;case"stroke-width":if(d._.sx!=1||d._.sy!=1)p/=g(h(d._.sx),h(d._.sy))||1;d.paper._vbSize&&(p*=d.paper._vbSize),i.setAttribute(o,p),k["stroke-dasharray"]&&v(d,k["stroke-dasharray"],f),d._.arrows&&("startString"in d._.arrows&&t(d,d._.arrows.startString),"endString"in d._.arrows&&t(d,d._.arrows.endString,1));break;case"stroke-dasharray":v(d,p,f);break;case"fill":var D=c(p).match(a._ISURL);if(D){z=q("pattern");var F=q("image");z.id=a.createUUID(),q(z,{x:0,y:0,patternUnits:"userSpaceOnUse",height:1,width:1}),q(F,{x:0,y:0,"xlink:href":D[1]}),z.appendChild(F),function(b){a._preload(D[1],function(){var a=this.offsetWidth,c=this.offsetHeight;q(b,{width:a,height:c}),q(F,{width:a,height:c}),d.paper.safari()})}(z),d.paper.defs.appendChild(z),q(i,{fill:"url(#"+z.id+")"}),d.pattern=z,d.pattern&&s(d);break}var G=a.getRGB(p);if(!G.error)delete f.gradient,delete k.gradient,!a.is(k.opacity,"undefined")&&a.is(f.opacity,"undefined")&&q(i,{opacity:k.opacity}),!a.is(k["fill-opacity"],"undefined")&&a.is(f["fill-opacity"],"undefined")&&q(i,{"fill-opacity":k["fill-opacity"]});else if((d.type=="circle"||d.type=="ellipse"||c(p).charAt()!="r")&&r(d,p)){if("opacity"in k||"fill-opacity"in k){var H=a._g.doc.getElementById(i.getAttribute("fill").replace(/^url\(#|\)$/g,l));if(H){var I=H.getElementsByTagName("stop");q(I[I.length-1],{"stop-opacity":("opacity"in k?k.opacity:1)*("fill-opacity"in k?k["fill-opacity"]:1)})}}k.gradient=p,k.fill="none";break}G[b]("opacity")&&q(i,{"fill-opacity":G.opacity>1?G.opacity/100:G.opacity});case"stroke":G=a.getRGB(p),i.setAttribute(o,G.hex),o=="stroke"&&G[b]("opacity")&&q(i,{"stroke-opacity":G.opacity>1?G.opacity/100:G.opacity}),o=="stroke"&&d._.arrows&&("startString"in d._.arrows&&t(d,d._.arrows.startString),"endString"in d._.arrows&&t(d,d._.arrows.endString,1));break;case"gradient":(d.type=="circle"||d.type=="ellipse"||c(p).charAt()!="r")&&r(d,p);break;case"opacity":k.gradient&&!k[b]("stroke-opacity")&&q(i,{"stroke-opacity":p>1?p/100:p});case"fill-opacity":if(k.gradient){H=a._g.doc.getElementById(i.getAttribute("fill").replace(/^url\(#|\)$/g,l)),H&&(I=H.getElementsByTagName("stop"),q(I[I.length-1],{"stop-opacity":p}));break};default:o=="font-size"&&(p=e(p,10)+"px");var J=o.replace(/(\-.)/g,function(a){return a.substring(1).toUpperCase()});i.style[J]=p,d._.dirty=1,i.setAttribute(o,p)}}y(d,f),i.style.visibility=m},x=1.2,y=function(d,f){if(d.type=="text"&&!!(f[b]("text")||f[b]("font")||f[b]("font-size")||f[b]("x")||f[b]("y"))){var g=d.attrs,h=d.node,i=h.firstChild?e(a._g.doc.defaultView.getComputedStyle(h.firstChild,l).getPropertyValue("font-size"),10):10;if(f[b]("text")){g.text=f.text;while(h.firstChild)h.removeChild(h.firstChild);var j=c(f.text).split("\n"),k=[],m;for(var n=0,o=j.length;n<o;n++)m=q("tspan"),n&&q(m,{dy:i*x,x:g.x}),m.appendChild(a._g.doc.createTextNode(j[n])),h.appendChild(m),k[n]=m}else{k=h.getElementsByTagName("tspan");for(n=0,o=k.length;n<o;n++)n?q(k[n],{dy:i*x,x:g.x}):q(k[0],{dy:0})}q(h,{x:g.x,y:g.y}),d._.dirty=1;var p=d._getBBox(),r=g.y-(p.y+p.height/2);r&&a.is(r,"finite")&&q(k[0],{dy:r})}},z=function(b,c){var d=0,e=0;this[0]=this.node=b,b.raphael=!0,this.id=a._oid++,b.raphaelid=this.id,this.matrix=a.matrix(),this.realPath=null,this.paper=c,this.attrs=this.attrs||{},this._={transform:[],sx:1,sy:1,deg:0,dx:0,dy:0,dirty:1},!c.bottom&&(c.bottom=this),this.prev=c.top,c.top&&(c.top.next=this),c.top=this,this.next=null},A=a.el;z.prototype=A,A.constructor=z,a._engine.path=function(a,b){var c=q("path");b.canvas&&b.canvas.appendChild(c);var d=new z(c,b);d.type="path",w(d,{fill:"none",stroke:"#000",path:a});return d},A.rotate=function(a,b,e){if(this.removed)return this;a=c(a).split(j),a.length-1&&(b=d(a[1]),e=d(a[2])),a=d(a[0]),e==null&&(b=e);if(b==null||e==null){var f=this.getBBox(1);b=f.x+f.width/2,e=f.y+f.height/2}this.transform(this._.transform.concat([["r",a,b,e]]));return this},A.scale=function(a,b,e,f){if(this.removed)return this;a=c(a).split(j),a.length-1&&(b=d(a[1]),e=d(a[2]),f=d(a[3])),a=d(a[0]),b==null&&(b=a),f==null&&(e=f);if(e==null||f==null)var g=this.getBBox(1);e=e==null?g.x+g.width/2:e,f=f==null?g.y+g.height/2:f,this.transform(this._.transform.concat([["s",a,b,e,f]]));return this},A.translate=function(a,b){if(this.removed)return this;a=c(a).split(j),a.length-1&&(b=d(a[1])),a=d(a[0])||0,b=+b||0,this.transform(this._.transform.concat([["t",a,b]]));return this},A.transform=function(c){var d=this._;if(c==null)return d.transform;a._extractTransform(this,c),this.clip&&q(this.clip,{transform:this.matrix.invert()}),this.pattern&&s(this),this.node&&q(this.node,{transform:this.matrix});if(d.sx!=1||d.sy!=1){var e=this.attrs[b]("stroke-width")?this.attrs["stroke-width"]:1;this.attr({"stroke-width":e})}return this},A.hide=function(){!this.removed&&this.paper.safari(this.node.style.display="none");return this},A.show=function(){!this.removed&&this.paper.safari(this.node.style.display="");return this},A.remove=function(){if(!this.removed&&!!this.node.parentNode){var b=this.paper;b.__set__&&b.__set__.exclude(this),k.unbind("raphael.*.*."+this.id),this.gradient&&b.defs.removeChild(this.gradient),a._tear(this,b),this.node.parentNode.tagName.toLowerCase()=="a"?this.node.parentNode.parentNode.removeChild(this.node.parentNode):this.node.parentNode.removeChild(this.node);for(var c in this)this[c]=typeof this[c]=="function"?a._removedFactory(c):null;this.removed=!0}},A._getBBox=function(){if(this.node.style.display=="none"){this.show();var a=!0}var b={};try{b=this.node.getBBox()}catch(c){}finally{b=b||{}}a&&this.hide();return b},A.attr=function(c,d){if(this.removed)return this;if(c==null){var e={};for(var f in this.attrs)this.attrs[b](f)&&(e[f]=this.attrs[f]);e.gradient&&e.fill=="none"&&(e.fill=e.gradient)&&delete e.gradient,e.transform=this._.transform;return e}if(d==null&&a.is(c,"string")){if(c=="fill"&&this.attrs.fill=="none"&&this.attrs.gradient)return this.attrs.gradient;if(c=="transform")return this._.transform;var g=c.split(j),h={};for(var i=0,l=g.length;i<l;i++)c=g[i],c in this.attrs?h[c]=this.attrs[c]:a.is(this.paper.customAttributes[c],"function")?h[c]=this.paper.customAttributes[c].def:h[c]=a._availableAttrs[c];return l-1?h:h[g[0]]}if(d==null&&a.is(c,"array")){h={};for(i=0,l=c.length;i<l;i++)h[c[i]]=this.attr(c[i]);return h}if(d!=null){var m={};m[c]=d}else c!=null&&a.is(c,"object")&&(m=c);for(var n in m)k("raphael.attr."+n+"."+this.id,this,m[n]);for(n in this.paper.customAttributes)if(this.paper.customAttributes[b](n)&&m[b](n)&&a.is(this.paper.customAttributes[n],"function")){var o=this.paper.customAttributes[n].apply(this,[].concat(m[n]));this.attrs[n]=m[n];for(var p in o)o[b](p)&&(m[p]=o[p])}w(this,m);return this},A.toFront=function(){if(this.removed)return this;this.node.parentNode.tagName.toLowerCase()=="a"?this.node.parentNode.parentNode.appendChild(this.node.parentNode):this.node.parentNode.appendChild(this.node);var b=this.paper;b.top!=this&&a._tofront(this,b);return this},A.toBack=function(){if(this.removed)return this;var b=this.node.parentNode;b.tagName.toLowerCase()=="a"?b.parentNode.insertBefore(this.node.parentNode,this.node.parentNode.parentNode.firstChild):b.firstChild!=this.node&&b.insertBefore(this.node,this.node.parentNode.firstChild),a._toback(this,this.paper);var c=this.paper;return this},A.insertAfter=function(b){if(this.removed)return this;var c=b.node||b[b.length-1].node;c.nextSibling?c.parentNode.insertBefore(this.node,c.nextSibling):c.parentNode.appendChild(this.node),a._insertafter(this,b,this.paper);return this},A.insertBefore=function(b){if(this.removed)return this;var c=b.node||b[0].node;c.parentNode.insertBefore(this.node,c),a._insertbefore(this,b,this.paper);return this},A.blur=function(b){var c=this;if(+b!==0){var d=q("filter"),e=q("feGaussianBlur");c.attrs.blur=b,d.id=a.createUUID(),q(e,{stdDeviation:+b||1.5}),d.appendChild(e),c.paper.defs.appendChild(d),c._blur=d,q(c.node,{filter:"url(#"+d.id+")"})}else c._blur&&(c._blur.parentNode.removeChild(c._blur),delete c._blur,delete c.attrs.blur),c.node.removeAttribute("filter")},a._engine.circle=function(a,b,c,d){var e=q("circle");a.canvas&&a.canvas.appendChild(e);var f=new z(e,a);f.attrs={cx:b,cy:c,r:d,fill:"none",stroke:"#000"},f.type="circle",q(e,f.attrs);return f},a._engine.rect=function(a,b,c,d,e,f){var g=q("rect");a.canvas&&a.canvas.appendChild(g);var h=new z(g,a);h.attrs={x:b,y:c,width:d,height:e,r:f||0,rx:f||0,ry:f||0,fill:"none",stroke:"#000"},h.type="rect",q(g,h.attrs);return h},a._engine.ellipse=function(a,b,c,d,e){var f=q("ellipse");a.canvas&&a.canvas.appendChild(f);var g=new z(f,a);g.attrs={cx:b,cy:c,rx:d,ry:e,fill:"none",stroke:"#000"},g.type="ellipse",q(f,g.attrs);return g},a._engine.image=function(a,b,c,d,e,f){var g=q("image");q(g,{x:c,y:d,width:e,height:f,preserveAspectRatio:"none"}),g.setAttributeNS(n,"href",b),a.canvas&&a.canvas.appendChild(g);var h=new z(g,a);h.attrs={x:c,y:d,width:e,height:f,src:b},h.type="image";return h},a._engine.text=function(b,c,d,e){var f=q("text");b.canvas&&b.canvas.appendChild(f);var g=new z(f,b);g.attrs={x:c,y:d,"text-anchor":"middle",text:e,font:a._availableAttrs.font,stroke:"none",fill:"#000"},g.type="text",w(g,g.attrs);return g},a._engine.setSize=function(a,b){this.width=a||this.width,this.height=b||this.height,this.canvas.setAttribute("width",this.width),this.canvas.setAttribute("height",this.height),this._viewBox&&this.setViewBox.apply(this,this._viewBox);return this},a._engine.create=function(){var b=a._getContainer.apply(0,arguments),c=b&&b.container,d=b.x,e=b.y,f=b.width,g=b.height;if(!c)throw new Error("SVG container not found.");var h=q("svg"),i="overflow:hidden;",j;d=d||0,e=e||0,f=f||512,g=g||342,q(h,{height:g,version:1.1,width:f,xmlns:"http://www.w3.org/2000/svg"}),c==1?(h.style.cssText=i+"position:absolute;left:"+d+"px;top:"+e+"px",a._g.doc.body.appendChild(h),j=1):(h.style.cssText=i+"position:relative",c.firstChild?c.insertBefore(h,c.firstChild):c.appendChild(h)),c=new a._Paper,c.width=f,c.height=g,c.canvas=h,c.clear(),c._left=c._top=0,j&&(c.renderfix=function(){}),c.renderfix();return c},a._engine.setViewBox=function(a,b,c,d,e){k("raphael.setViewBox",this,this._viewBox,[a,b,c,d,e]);var f=g(c/this.width,d/this.height),h=this.top,i=e?"meet":"xMinYMin",j,l;a==null?(this._vbSize&&(f=1),delete this._vbSize,j="0 0 "+this.width+m+this.height):(this._vbSize=f,j=a+m+b+m+c+m+d),q(this.canvas,{viewBox:j,preserveAspectRatio:i});while(f&&h)l="stroke-width"in h.attrs?h.attrs["stroke-width"]:1,h.attr({"stroke-width":l}),h._.dirty=1,h._.dirtyT=1,h=h.prev;this._viewBox=[a,b,c,d,!!e];return this},a.prototype.renderfix=function(){var a=this.canvas,b=a.style,c;try{c=a.getScreenCTM()||a.createSVGMatrix()}catch(d){c=a.createSVGMatrix()}var e=-c.e%1,f=-c.f%1;if(e||f)e&&(this._left=(this._left+e)%1,b.left=this._left+"px"),f&&(this._top=(this._top+f)%1,b.top=this._top+"px")},a.prototype.clear=function(){a.eve("raphael.clear",this);var b=this.canvas;while(b.firstChild)b.removeChild(b.firstChild);this.bottom=this.top=null,(this.desc=q("desc")).appendChild(a._g.doc.createTextNode("Created with Raphaël "+a.version)),b.appendChild(this.desc),b.appendChild(this.defs=q("defs"))},a.prototype.remove=function(){k("raphael.remove",this),this.canvas.parentNode&&this.canvas.parentNode.removeChild(this.canvas);for(var b in this)this[b]=typeof this[b]=="function"?a._removedFactory(b):null};var B=a.st;for(var C in A)A[b](C)&&!B[b](C)&&(B[C]=function(a){return function(){var b=arguments;return this.forEach(function(c){c[a].apply(c,b)})}}(C))}(window.Raphael),window.Raphael.vml&&function(a){var b="hasOwnProperty",c=String,d=parseFloat,e=Math,f=e.round,g=e.max,h=e.min,i=e.abs,j="fill",k=/[, ]+/,l=a.eve,m=" progid:DXImageTransform.Microsoft",n=" ",o="",p={M:"m",L:"l",C:"c",Z:"x",m:"t",l:"r",c:"v",z:"x"},q=/([clmz]),?([^clmz]*)/gi,r=/ progid:\S+Blur\([^\)]+\)/g,s=/-?[^,\s-]+/g,t="position:absolute;left:0;top:0;width:1px;height:1px",u=21600,v={path:1,rect:1,image:1},w={circle:1,ellipse:1},x=function(b){var d=/[ahqstv]/ig,e=a._pathToAbsolute;c(b).match(d)&&(e=a._path2curve),d=/[clmz]/g;if(e==a._pathToAbsolute&&!c(b).match(d)){var g=c(b).replace(q,function(a,b,c){var d=[],e=b.toLowerCase()=="m",g=p[b];c.replace(s,function(a){e&&d.length==2&&(g+=d+p[b=="m"?"l":"L"],d=[]),d.push(f(a*u))});return g+d});return g}var h=e(b),i,j;g=[];for(var k=0,l=h.length;k<l;k++){i=h[k],j=h[k][0].toLowerCase(),j=="z"&&(j="x");for(var m=1,r=i.length;m<r;m++)j+=f(i[m]*u)+(m!=r-1?",":o);g.push(j)}return g.join(n)},y=function(b,c,d){var e=a.matrix();e.rotate(-b,.5,.5);return{dx:e.x(c,d),dy:e.y(c,d)}},z=function(a,b,c,d,e,f){var g=a._,h=a.matrix,k=g.fillpos,l=a.node,m=l.style,o=1,p="",q,r=u/b,s=u/c;m.visibility="hidden";if(!!b&&!!c){l.coordsize=i(r)+n+i(s),m.rotation=f*(b*c<0?-1:1);if(f){var t=y(f,d,e);d=t.dx,e=t.dy}b<0&&(p+="x"),c<0&&(p+=" y")&&(o=-1),m.flip=p,l.coordorigin=d*-r+n+e*-s;if(k||g.fillsize){var v=l.getElementsByTagName(j);v=v&&v[0],l.removeChild(v),k&&(t=y(f,h.x(k[0],k[1]),h.y(k[0],k[1])),v.position=t.dx*o+n+t.dy*o),g.fillsize&&(v.size=g.fillsize[0]*i(b)+n+g.fillsize[1]*i(c)),l.appendChild(v)}m.visibility="visible"}};a.toString=function(){return"Your browser doesn’t support SVG. Falling down to VML.\nYou are running Raphaël "+this.version};var A=function(a,b,d){var e=c(b).toLowerCase().split("-"),f=d?"end":"start",g=e.length,h="classic",i="medium",j="medium";while(g--)switch(e[g]){case"block":case"classic":case"oval":case"diamond":case"open":case"none":h=e[g];break;case"wide":case"narrow":j=e[g];break;case"long":case"short":i=e[g]}var k=a.node.getElementsByTagName("stroke")[0];k[f+"arrow"]=h,k[f+"arrowlength"]=i,k[f+"arrowwidth"]=j},B=function(e,i){e.attrs=e.attrs||{};var l=e.node,m=e.attrs,p=l.style,q,r=v[e.type]&&(i.x!=m.x||i.y!=m.y||i.width!=m.width||i.height!=m.height||i.cx!=m.cx||i.cy!=m.cy||i.rx!=m.rx||i.ry!=m.ry||i.r!=m.r),s=w[e.type]&&(m.cx!=i.cx||m.cy!=i.cy||m.r!=i.r||m.rx!=i.rx||m.ry!=i.ry),t=e;for(var y in i)i[b](y)&&(m[y]=i[y]);r&&(m.path=a._getPath[e.type](e),e._.dirty=1),i.href&&(l.href=i.href),i.title&&(l.title=i.title),i.target&&(l.target=i.target),i.cursor&&(p.cursor=i.cursor),"blur"in i&&e.blur(i.blur);if(i.path&&e.type=="path"||r)l.path=x(~c(m.path).toLowerCase().indexOf("r")?a._pathToAbsolute(m.path):m.path),e.type=="image"&&(e._.fillpos=[m.x,m.y],e._.fillsize=[m.width,m.height],z(e,1,1,0,0,0));"transform"in i&&e.transform(i.transform);if(s){var B=+m.cx,D=+m.cy,E=+m.rx||+m.r||0,G=+m.ry||+m.r||0;l.path=a.format("ar{0},{1},{2},{3},{4},{1},{4},{1}x",f((B-E)*u),f((D-G)*u),f((B+E)*u),f((D+G)*u),f(B*u))}if("clip-rect"in i){var H=c(i["clip-rect"]).split(k);if(H.length==4){H[2]=+H[2]+ +H[0],H[3]=+H[3]+ +H[1];var I=l.clipRect||a._g.doc.createElement("div"),J=I.style;J.clip=a.format("rect({1}px {2}px {3}px {0}px)",H),l.clipRect||(J.position="absolute",J.top=0,J.left=0,J.width=e.paper.width+"px",J.height=e.paper.height+"px",l.parentNode.insertBefore(I,l),I.appendChild(l),l.clipRect=I)}i["clip-rect"]||l.clipRect&&(l.clipRect.style.clip="auto")}if(e.textpath){var K=e.textpath.style;i.font&&(K.font=i.font),i["font-family"]&&(K.fontFamily='"'+i["font-family"].split(",")[0].replace(/^['"]+|['"]+$/g,o)+'"'),i["font-size"]&&(K.fontSize=i["font-size"]),i["font-weight"]&&(K.fontWeight=i["font-weight"]),i["font-style"]&&(K.fontStyle=i["font-style"])}"arrow-start"in i&&A(t,i["arrow-start"]),"arrow-end"in i&&A(t,i["arrow-end"],1);if(i.opacity!=null||i["stroke-width"]!=null||i.fill!=null||i.src!=null||i.stroke!=null||i["stroke-width"]!=null||i["stroke-opacity"]!=null||i["fill-opacity"]!=null||i["stroke-dasharray"]!=null||i["stroke-miterlimit"]!=null||i["stroke-linejoin"]!=null||i["stroke-linecap"]!=null){var L=l.getElementsByTagName(j),M=!1;L=L&&L[0],!L&&(M=L=F(j)),e.type=="image"&&i.src&&(L.src=i.src),i.fill&&(L.on=!0);if(L.on==null||i.fill=="none"||i.fill===null)L.on=!1;if(L.on&&i.fill){var N=c(i.fill).match(a._ISURL);if(N){L.parentNode==l&&l.removeChild(L),L.rotate=!0,L.src=N[1],L.type="tile";var O=e.getBBox(1);L.position=O.x+n+O.y,e._.fillpos=[O.x,O.y],a._preload(N[1],function(){e._.fillsize=[this.offsetWidth,this.offsetHeight]})}else L.color=a.getRGB(i.fill).hex,L.src=o,L.type="solid",a.getRGB(i.fill).error&&(t.type in{circle:1,ellipse:1}||c(i.fill).charAt()!="r")&&C(t,i.fill,L)&&(m.fill="none",m.gradient=i.fill,L.rotate=!1)}if("fill-opacity"in i||"opacity"in i){var P=((+m["fill-opacity"]+1||2)-1)*((+m.opacity+1||2)-1)*((+a.getRGB(i.fill).o+1||2)-1);P=h(g(P,0),1),L.opacity=P,L.src&&(L.color="none")}l.appendChild(L);var Q=l.getElementsByTagName("stroke")&&l.getElementsByTagName("stroke")[0],T=!1;!Q&&(T=Q=F("stroke"));if(i.stroke&&i.stroke!="none"||i["stroke-width"]||i["stroke-opacity"]!=null||i["stroke-dasharray"]||i["stroke-miterlimit"]||i["stroke-linejoin"]||i["stroke-linecap"])Q.on=!0;(i.stroke=="none"||i.stroke===null||Q.on==null||i.stroke==0||i["stroke-width"]==0)&&(Q.on=!1);var U=a.getRGB(i.stroke);Q.on&&i.stroke&&(Q.color=U.hex),P=((+m["stroke-opacity"]+1||2)-1)*((+m.opacity+1||2)-1)*((+U.o+1||2)-1);var V=(d(i["stroke-width"])||1)*.75;P=h(g(P,0),1),i["stroke-width"]==null&&(V=m["stroke-width"]),i["stroke-width"]&&(Q.weight=V),V&&V<1&&(P*=V)&&(Q.weight=1),Q.opacity=P,i["stroke-linejoin"]&&(Q.joinstyle=i["stroke-linejoin"]||"miter"),Q.miterlimit=i["stroke-miterlimit"]||8,i["stroke-linecap"]&&(Q.endcap=i["stroke-linecap"]=="butt"?"flat":i["stroke-linecap"]=="square"?"square":"round");if(i["stroke-dasharray"]){var W={"-":"shortdash",".":"shortdot","-.":"shortdashdot","-..":"shortdashdotdot",". ":"dot","- ":"dash","--":"longdash","- .":"dashdot","--.":"longdashdot","--..":"longdashdotdot"};Q.dashstyle=W[b](i["stroke-dasharray"])?W[i["stroke-dasharray"]]:o}T&&l.appendChild(Q)}if(t.type=="text"){t.paper.canvas.style.display=o;var X=t.paper.span,Y=100,Z=m.font&&m.font.match(/\d+(?:\.\d*)?(?=px)/);p=X.style,m.font&&(p.font=m.font),m["font-family"]&&(p.fontFamily=m["font-family"]),m["font-weight"]&&(p.fontWeight=m["font-weight"]),m["font-style"]&&(p.fontStyle=m["font-style"]),Z=d(m["font-size"]||Z&&Z[0])||10,p.fontSize=Z*Y+"px",t.textpath.string&&(X.innerHTML=c(t.textpath.string).replace(/</g,"&#60;").replace(/&/g,"&#38;").replace(/\n/g,"<br>"));var $=X.getBoundingClientRect();t.W=m.w=($.right-$.left)/Y,t.H=m.h=($.bottom-$.top)/Y,t.X=m.x,t.Y=m.y+t.H/2,("x"in i||"y"in i)&&(t.path.v=a.format("m{0},{1}l{2},{1}",f(m.x*u),f(m.y*u),f(m.x*u)+1));var _=["x","y","text","font","font-family","font-weight","font-style","font-size"];for(var ba=0,bb=_.length;ba<bb;ba++)if(_[ba]in i){t._.dirty=1;break}switch(m["text-anchor"]){case"start":t.textpath.style["v-text-align"]="left",t.bbx=t.W/2;break;case"end":t.textpath.style["v-text-align"]="right",t.bbx=-t.W/2;break;default:t.textpath.style["v-text-align"]="center",t.bbx=0}t.textpath.style["v-text-kern"]=!0}},C=function(b,f,g){b.attrs=b.attrs||{};var h=b.attrs,i=Math.pow,j,k,l="linear",m=".5 .5";b.attrs.gradient=f,f=c(f).replace(a._radial_gradient,function(a,b,c){l="radial",b&&c&&(b=d(b),c=d(c),i(b-.5,2)+i(c-.5,2)>.25&&(c=e.sqrt(.25-i(b-.5,2))*((c>.5)*2-1)+.5),m=b+n+c);return o}),f=f.split(/\s*\-\s*/);if(l=="linear"){var p=f.shift();p=-d(p);if(isNaN(p))return null}var q=a._parseDots(f);if(!q)return null;b=b.shape||b.node;if(q.length){b.removeChild(g),g.on=!0,g.method="none",g.color=q[0].color,g.color2=q[q.length-1].color;var r=[];for(var s=0,t=q.length;s<t;s++)q[s].offset&&r.push(q[s].offset+n+q[s].color);g.colors=r.length?r.join():"0% "+g.color,l=="radial"?(g.type="gradientTitle",g.focus="100%",g.focussize="0 0",g.focusposition=m,g.angle=0):(g.type="gradient",g.angle=(270-p)%360),b.appendChild(g)}return 1},D=function(b,c){this[0]=this.node=b,b.raphael=!0,this.id=a._oid++,b.raphaelid=this.id,this.X=0,this.Y=0,this.attrs={},this.paper=c,this.matrix=a.matrix(),this._={transform:[],sx:1,sy:1,dx:0,dy:0,deg:0,dirty:1,dirtyT:1},!c.bottom&&(c.bottom=this),this.prev=c.top,c.top&&(c.top.next=this),c.top=this,this.next=null},E=a.el;D.prototype=E,E.constructor=D,E.transform=function(b){if(b==null)return this._.transform;var d=this.paper._viewBoxShift,e=d?"s"+[d.scale,d.scale]+"-1-1t"+[d.dx,d.dy]:o,f;d&&(f=b=c(b).replace(/\.{3}|\u2026/g,this._.transform||o)),a._extractTransform(this,e+b);var g=this.matrix.clone(),h=this.skew,i=this.node,j,k=~c(this.attrs.fill).indexOf("-"),l=!c(this.attrs.fill).indexOf("url(");g.translate(-0.5,-0.5);if(l||k||this.type=="image"){h.matrix="1 0 0 1",h.offset="0 0",j=g.split();if(k&&j.noRotation||!j.isSimple){i.style.filter=g.toFilter();var m=this.getBBox(),p=this.getBBox(1),q=m.x-p.x,r=m.y-p.y;i.coordorigin=q*-u+n+r*-u,z(this,1,1,q,r,0)}else i.style.filter=o,z(this,j.scalex,j.scaley,j.dx,j.dy,j.rotate)}else i.style.filter=o,h.matrix=c(g),h.offset=g.offset();f&&(this._.transform=f);return this},E.rotate=function(a,b,e){if(this.removed)return this;if(a!=null){a=c(a).split(k),a.length-1&&(b=d(a[1]),e=d(a[2])),a=d(a[0]),e==null&&(b=e);if(b==null||e==null){var f=this.getBBox(1);b=f.x+f.width/2,e=f.y+f.height/2}this._.dirtyT=1,this.transform(this._.transform.concat([["r",a,b,e]]));return this}},E.translate=function(a,b){if(this.removed)return this;a=c(a).split(k),a.length-1&&(b=d(a[1])),a=d(a[0])||0,b=+b||0,this._.bbox&&(this._.bbox.x+=a,this._.bbox.y+=b),this.transform(this._.transform.concat([["t",a,b]]));return this},E.scale=function(a,b,e,f){if(this.removed)return this;a=c(a).split(k),a.length-1&&(b=d(a[1]),e=d(a[2]),f=d(a[3]),isNaN(e)&&(e=null),isNaN(f)&&(f=null)),a=d(a[0]),b==null&&(b=a),f==null&&(e=f);if(e==null||f==null)var g=this.getBBox(1);e=e==null?g.x+g.width/2:e,f=f==null?g.y+g.height/2:f,this.transform(this._.transform.concat([["s",a,b,e,f]])),this._.dirtyT=1;return this},E.hide=function(){!this.removed&&(this.node.style.display="none");return this},E.show=function(){!this.removed&&(this.node.style.display=o);return this},E._getBBox=function(){if(this.removed)return{};return{x:this.X+(this.bbx||0)-this.W/2,y:this.Y-this.H,width:this.W,height:this.H}},E.remove=function(){if(!this.removed&&!!this.node.parentNode){this.paper.__set__&&this.paper.__set__.exclude(this),a.eve.unbind("raphael.*.*."+this.id),a._tear(this,this.paper),this.node.parentNode.removeChild(this.node),this.shape&&this.shape.parentNode.removeChild(this.shape);for(var b in this)this[b]=typeof this[b]=="function"?a._removedFactory(b):null;this.removed=!0}},E.attr=function(c,d){if(this.removed)return this;if(c==null){var e={};for(var f in this.attrs)this.attrs[b](f)&&(e[f]=this.attrs[f]);e.gradient&&e.fill=="none"&&(e.fill=e.gradient)&&delete e.gradient,e.transform=this._.transform;return e}if(d==null&&a.is(c,"string")){if(c==j&&this.attrs.fill=="none"&&this.attrs.gradient)return this.attrs.gradient;var g=c.split(k),h={};for(var i=0,m=g.length;i<m;i++)c=g[i],c in this.attrs?h[c]=this.attrs[c]:a.is(this.paper.customAttributes[c],"function")?h[c]=this.paper.customAttributes[c].def:h[c]=a._availableAttrs[c];return m-1?h:h[g[0]]}if(this.attrs&&d==null&&a.is(c,"array")){h={};for(i=0,m=c.length;i<m;i++)h[c[i]]=this.attr(c[i]);return h}var n;d!=null&&(n={},n[c]=d),d==null&&a.is(c,"object")&&(n=c);for(var o in n)l("raphael.attr."+o+"."+this.id,this,n[o]);if(n){for(o in this.paper.customAttributes)if(this.paper.customAttributes[b](o)&&n[b](o)&&a.is(this.paper.customAttributes[o],"function")){var p=this.paper.customAttributes[o].apply(this,[].concat(n[o]));this.attrs[o]=n[o];for(var q in p)p[b](q)&&(n[q]=p[q])}n.text&&this.type=="text"&&(this.textpath.string=n.text),B(this,n)}return this},E.toFront=function(){!this.removed&&this.node.parentNode.appendChild(this.node),this.paper&&this.paper.top!=this&&a._tofront(this,this.paper);return this},E.toBack=function(){if(this.removed)return this;this.node.parentNode.firstChild!=this.node&&(this.node.parentNode.insertBefore(this.node,this.node.parentNode.firstChild),a._toback(this,this.paper));return this},E.insertAfter=function(b){if(this.removed)return this;b.constructor==a.st.constructor&&(b=b[b.length-1]),b.node.nextSibling?b.node.parentNode.insertBefore(this.node,b.node.nextSibling):b.node.parentNode.appendChild(this.node),a._insertafter(this,b,this.paper);return this},E.insertBefore=function(b){if(this.removed)return this;b.constructor==a.st.constructor&&(b=b[0]),b.node.parentNode.insertBefore(this.node,b.node),a._insertbefore(this,b,this.paper);return this},E.blur=function(b){var c=this.node.runtimeStyle,d=c.filter;d=d.replace(r,o),+b!==0?(this.attrs.blur=b,c.filter=d+n+m+".Blur(pixelradius="+(+b||1.5)+")",c.margin=a.format("-{0}px 0 0 -{0}px",f(+b||1.5))):(c.filter=d,c.margin=0,delete this.attrs.blur)},a._engine.path=function(a,b){var c=F("shape");c.style.cssText=t,c.coordsize=u+n+u,c.coordorigin=b.coordorigin;var d=new D(c,b),e={fill:"none",stroke:"#000"};a&&(e.path=a),d.type="path",d.path=[],d.Path=o,B(d,e),b.canvas.appendChild(c);var f=F("skew");f.on=!0,c.appendChild(f),d.skew=f,d.transform(o);return d},a._engine.rect=function(b,c,d,e,f,g){var h=a._rectPath(c,d,e,f,g),i=b.path(h),j=i.attrs;i.X=j.x=c,i.Y=j.y=d,i.W=j.width=e,i.H=j.height=f,j.r=g,j.path=h,i.type="rect";return i},a._engine.ellipse=function(a,b,c,d,e){var f=a.path(),g=f.attrs;f.X=b-d,f.Y=c-e,f.W=d*2,f.H=e*2,f.type="ellipse",B(f,{cx:b,cy:c,rx:d,ry:e});return f},a._engine.circle=function(a,b,c,d){var e=a.path(),f=e.attrs;e.X=b-d,e.Y=c-d,e.W=e.H=d*2,e.type="circle",B(e,{cx:b,cy:c,r:d});return e},a._engine.image=function(b,c,d,e,f,g){var h=a._rectPath(d,e,f,g),i=b.path(h).attr({stroke:"none"}),k=i.attrs,l=i.node,m=l.getElementsByTagName(j)[0];k.src=c,i.X=k.x=d,i.Y=k.y=e,i.W=k.width=f,i.H=k.height=g,k.path=h,i.type="image",m.parentNode==l&&l.removeChild(m),m.rotate=!0,m.src=c,m.type="tile",i._.fillpos=[d,e],i._.fillsize=[f,g],l.appendChild(m),z(i,1,1,0,0,0);return i},a._engine.text=function(b,d,e,g){var h=F("shape"),i=F("path"),j=F("textpath");d=d||0,e=e||0,g=g||"",i.v=a.format("m{0},{1}l{2},{1}",f(d*u),f(e*u),f(d*u)+1),i.textpathok=!0,j.string=c(g),j.on=!0,h.style.cssText=t,h.coordsize=u+n+u,h.coordorigin="0 0";var k=new D(h,b),l={fill:"#000",stroke:"none",font:a._availableAttrs.font,text:g};k.shape=h,k.path=i,k.textpath=j,k.type="text",k.attrs.text=c(g),k.attrs.x=d,k.attrs.y=e,k.attrs.w=1,k.attrs.h=1,B(k,l),h.appendChild(j),h.appendChild(i),b.canvas.appendChild(h);var m=F("skew");m.on=!0,h.appendChild(m),k.skew=m,k.transform(o);return k},a._engine.setSize=function(b,c){var d=this.canvas.style;this.width=b,this.height=c,b==+b&&(b+="px"),c==+c&&(c+="px"),d.width=b,d.height=c,d.clip="rect(0 "+b+" "+c+" 0)",this._viewBox&&a._engine.setViewBox.apply(this,this._viewBox);return this},a._engine.setViewBox=function(b,c,d,e,f){a.eve("raphael.setViewBox",this,this._viewBox,[b,c,d,e,f]);var h=this.width,i=this.height,j=1/g(d/h,e/i),k,l;f&&(k=i/e,l=h/d,d*k<h&&(b-=(h-d*k)/2/k),e*l<i&&(c-=(i-e*l)/2/l)),this._viewBox=[b,c,d,e,!!f],this._viewBoxShift={dx:-b,dy:-c,scale:j},this.forEach(function(a){a.transform("...")});return this};var F;a._engine.initWin=function(a){var b=a.document;b.createStyleSheet().addRule(".rvml","behavior:url(#default#VML)");try{!b.namespaces.rvml&&b.namespaces.add("rvml","urn:schemas-microsoft-com:vml"),F=function(a){return b.createElement("<rvml:"+a+' class="rvml">')}}catch(c){F=function(a){return b.createElement("<"+a+' xmlns="urn:schemas-microsoft.com:vml" class="rvml">')}}},a._engine.initWin(a._g.win),a._engine.create=function(){var b=a._getContainer.apply(0,arguments),c=b.container,d=b.height,e,f=b.width,g=b.x,h=b.y;if(!c)throw new Error("VML container not found.");var i=new a._Paper,j=i.canvas=a._g.doc.createElement("div"),k=j.style;g=g||0,h=h||0,f=f||512,d=d||342,i.width=f,i.height=d,f==+f&&(f+="px"),d==+d&&(d+="px"),i.coordsize=u*1e3+n+u*1e3,i.coordorigin="0 0",i.span=a._g.doc.createElement("span"),i.span.style.cssText="position:absolute;left:-9999em;top:-9999em;padding:0;margin:0;line-height:1;",j.appendChild(i.span),k.cssText=a.format("top:0;left:0;width:{0};height:{1};display:inline-block;position:relative;clip:rect(0 {0} {1} 0);overflow:hidden",f,d),c==1?(a._g.doc.body.appendChild(j),k.left=g+"px",k.top=h+"px",k.position="absolute"):c.firstChild?c.insertBefore(j,c.firstChild):c.appendChild(j),i.renderfix=function(){};return i},a.prototype.clear=function(){a.eve("raphael.clear",this),this.canvas.innerHTML=o,this.span=a._g.doc.createElement("span"),this.span.style.cssText="position:absolute;left:-9999em;top:-9999em;padding:0;margin:0;line-height:1;display:inline;",this.canvas.appendChild(this.span),this.bottom=this.top=null},a.prototype.remove=function(){a.eve("raphael.remove",this),this.canvas.parentNode.removeChild(this.canvas);for(var b in this)this[b]=typeof this[b]=="function"?a._removedFactory(b):null;return!0};var G=a.st;for(var H in E)E[b](H)&&!G[b](H)&&(G[H]=function(a){return function(){var b=arguments;return this.forEach(function(c){c[a].apply(c,b)})}}(H))}(window.Raphael); \ No newline at end of file
+(function (a) {
+ var b = "0.3.4", c = "hasOwnProperty", d = /[\.\/]/, e = "*", f = function () {}, g = function (a, b) {return a - b}, h, i, j = {n: {}}, k = function (a, b) {
+ var c = j, d = i, e = Array.prototype.slice.call(arguments, 2), f = k.listeners(a), l = 0, m = !1, n, o = [], p = {}, q = [], r = h, s = [];
+ h = a, i = 0;
+ for (var t = 0, u = f.length; t < u; t++)"zIndex"in f[t] && (o.push(f[t].zIndex), f[t].zIndex < 0 && (p[f[t].zIndex] = f[t]));
+ o.sort(g);
+ while (o[l] < 0) {
+ n = p[o[l++]], q.push(n.apply(b, e));
+ if (i) {
+ i = d;
+ return q
+ }
+ }
+ for (t = 0; t < u; t++) {
+ n = f[t];
+ if ("zIndex"in n)if (n.zIndex == o[l]) {
+ q.push(n.apply(b, e));
+ if (i)break;
+ do {
+ l++, n = p[o[l]], n && q.push(n.apply(b, e));
+ if (i)break
+ } while (n)
+ } else p[n.zIndex] = n; else {
+ q.push(n.apply(b, e));
+ if (i)break
+ }
+ }
+ i = d, h = r;
+ return q.length ? q : null
+ };
+ k.listeners = function (a) {
+ var b = a.split(d), c = j, f, g, h, i, k, l, m, n, o = [c], p = [];
+ for (i = 0, k = b.length; i < k; i++) {
+ n = [];
+ for (l = 0, m = o.length; l < m; l++) {
+ c = o[l].n, g = [c[b[i]], c[e]], h = 2;
+ while (h--)f = g[h], f && (n.push(f), p = p.concat(f.f || []))
+ }
+ o = n
+ }
+ return p
+ }, k.on = function (a, b) {
+ var c = a.split(d), e = j;
+ for (var g = 0, h = c.length; g < h; g++)e = e.n, !e[c[g]] && (e[c[g]] = {n: {}}), e = e[c[g]];
+ e.f = e.f || [];
+ for (g = 0, h = e.f.length; g < h; g++)if (e.f[g] == b)return f;
+ e.f.push(b);
+ return function (a) {+a == +a && (b.zIndex = +a)}
+ }, k.stop = function () {i = 1}, k.nt = function (a) {
+ if (a)return(new RegExp("(?:\\.|\\/|^)" + a + "(?:\\.|\\/|$)")).test(h);
+ return h
+ }, k.off = k.unbind = function (a, b) {
+ var f = a.split(d), g, h, i, k, l, m, n, o = [j];
+ for (k = 0, l = f.length; k < l; k++)for (m = 0; m < o.length; m += i.length - 2) {
+ i = [m, 1], g = o[m].n;
+ if (f[k] != e)g[f[k]] && i.push(g[f[k]]); else for (h in g)g[c](h) && i.push(g[h]);
+ o.splice.apply(o, i)
+ }
+ for (k = 0, l = o.length; k < l; k++) {
+ g = o[k];
+ while (g.n) {
+ if (b) {
+ if (g.f) {
+ for (m = 0, n = g.f.length; m < n; m++)if (g.f[m] == b) {
+ g.f.splice(m, 1);
+ break
+ }
+ !g.f.length && delete g.f
+ }
+ for (h in g.n)if (g.n[c](h) && g.n[h].f) {
+ var p = g.n[h].f;
+ for (m = 0, n = p.length; m < n; m++)if (p[m] == b) {
+ p.splice(m, 1);
+ break
+ }
+ !p.length && delete g.n[h].f
+ }
+ } else {
+ delete g.f;
+ for (h in g.n)g.n[c](h) && g.n[h].f && delete g.n[h].f
+ }
+ g = g.n
+ }
+ }
+ }, k.once = function (a, b) {
+ var c = function () {
+ var d = b.apply(this, arguments);
+ k.unbind(a, c);
+ return d
+ };
+ return k.on(a, c)
+ }, k.version = b, k.toString = function () {return"You are running Eve " + b}, typeof module != "undefined" && module.exports ? module.exports = k : typeof define != "undefined" ? define("eve", [], function () {return k}) : a.eve = k
+})(this), function () {
+ function cF(a) {for (var b = 0; b < cy.length; b++)cy[b].el.paper == a && cy.splice(b--, 1)}
+
+ function cE(b, d, e, f, h, i) {
+ e = Q(e);
+ var j, k, l, m = [], o, p, q, t = b.ms, u = {}, v = {}, w = {};
+ if (f)for (y = 0, z = cy.length; y < z; y++) {
+ var x = cy[y];
+ if (x.el.id == d.id && x.anim == b) {
+ x.percent != e ? (cy.splice(y, 1), l = 1) : k = x, d.attr(x.totalOrigin);
+ break
+ }
+ } else f = +v;
+ for (var y = 0, z = b.percents.length; y < z; y++) {
+ if (b.percents[y] == e || b.percents[y] > f * b.top) {
+ e = b.percents[y], p = b.percents[y - 1] || 0, t = t / b.top * (e - p), o = b.percents[y + 1], j = b.anim[e];
+ break
+ }
+ f && d.attr(b.anim[b.percents[y]])
+ }
+ if (!!j) {
+ if (!k) {
+ for (var A in j)if (j[g](A))if (U[g](A) || d.paper.customAttributes[g](A)) {
+ u[A] = d.attr(A), u[A] == null && (u[A] = T[A]), v[A] = j[A];
+ switch (U[A]) {
+ case C:
+ w[A] = (v[A] - u[A]) / t;
+ break;
+ case"colour":
+ u[A] = a.getRGB(u[A]);
+ var B = a.getRGB(v[A]);
+ w[A] = {r: (B.r - u[A].r) / t, g: (B.g - u[A].g) / t, b: (B.b - u[A].b) / t};
+ break;
+ case"path":
+ var D = bR(u[A], v[A]), E = D[1];
+ u[A] = D[0], w[A] = [];
+ for (y = 0, z = u[A].length; y < z; y++) {
+ w[A][y] = [0];
+ for (var F = 1, G = u[A][y].length; F < G; F++)w[A][y][F] = (E[y][F] - u[A][y][F]) / t
+ }
+ break;
+ case"transform":
+ var H = d._, I = ca(H[A], v[A]);
+ if (I) {
+ u[A] = I.from, v[A] = I.to, w[A] = [], w[A].real = !0;
+ for (y = 0, z = u[A].length; y < z; y++) {
+ w[A][y] = [u[A][y][0]];
+ for (F = 1, G = u[A][y].length; F < G; F++)w[A][y][F] = (v[A][y][F] - u[A][y][F]) / t
+ }
+ } else {
+ var J = d.matrix || new cb, K = {_: {transform: H.transform}, getBBox: function () {return d.getBBox(1)}};
+ u[A] = [J.a, J.b, J.c, J.d, J.e, J.f], b$(K, v[A]), v[A] = K._.transform, w[A] = [(K.matrix.a - J.a) / t, (K.matrix.b - J.b) / t, (K.matrix.c - J.c) / t, (K.matrix.d - J.d) / t, (K.matrix.e - J.e) / t, (K.matrix.f - J.f) / t]
+ }
+ break;
+ case"csv":
+ var L = r(j[A])[s](c), M = r(u[A])[s](c);
+ if (A == "clip-rect") {
+ u[A] = M, w[A] = [], y = M.length;
+ while (y--)w[A][y] = (L[y] - u[A][y]) / t
+ }
+ v[A] = L;
+ break;
+ default:
+ L = [][n](j[A]), M = [][n](u[A]), w[A] = [], y = d.paper.customAttributes[A].length;
+ while (y--)w[A][y] = ((L[y] || 0) - (M[y] || 0)) / t
+ }
+ }
+ var O = j.easing, P = a.easing_formulas[O];
+ if (!P) {
+ P = r(O).match(N);
+ if (P && P.length == 5) {
+ var R = P;
+ P = function (a) {return cC(a, +R[1], +R[2], +R[3], +R[4], t)}
+ } else P = bf
+ }
+ q = j.start || b.start || +(new Date), x = {anim: b, percent: e, timestamp: q, start: q + (b.del || 0), status: 0, initstatus: f || 0, stop: !1, ms: t, easing: P, from: u, diff: w, to: v, el: d, callback: j.callback, prev: p, next: o, repeat: i || b.times, origin: d.attr(), totalOrigin: h}, cy.push(x);
+ if (f && !k && !l) {
+ x.stop = !0, x.start = new Date - t * f;
+ if (cy.length == 1)return cA()
+ }
+ l && (x.start = new Date - x.ms * f), cy.length == 1 && cz(cA)
+ } else k.initstatus = f, k.start = new Date - k.ms * f;
+ eve("raphael.anim.start." + d.id, d, b)
+ }
+ }
+
+ function cD(a, b) {
+ var c = [], d = {};
+ this.ms = b, this.times = 1;
+ if (a) {
+ for (var e in a)a[g](e) && (d[Q(e)] = a[e], c.push(Q(e)));
+ c.sort(bd)
+ }
+ this.anim = d, this.top = c[c.length - 1], this.percents = c
+ }
+
+ function cC(a, b, c, d, e, f) {
+ function o(a, b) {
+ var c, d, e, f, j, k;
+ for (e = a, k = 0; k < 8; k++) {
+ f = m(e) - a;
+ if (z(f) < b)return e;
+ j = (3 * i * e + 2 * h) * e + g;
+ if (z(j) < 1e-6)break;
+ e = e - f / j
+ }
+ c = 0, d = 1, e = a;
+ if (e < c)return c;
+ if (e > d)return d;
+ while (c < d) {
+ f = m(e);
+ if (z(f - a) < b)return e;
+ a > f ? c = e : d = e, e = (d - c) / 2 + c
+ }
+ return e
+ }
+
+ function n(a, b) {
+ var c = o(a, b);
+ return((l * c + k) * c + j) * c
+ }
+
+ function m(a) {return((i * a + h) * a + g) * a}
+
+ var g = 3 * b, h = 3 * (d - b) - g, i = 1 - g - h, j = 3 * c, k = 3 * (e - c) - j, l = 1 - j - k;
+ return n(a, 1 / (200 * f))
+ }
+
+ function cq() {return this.x + q + this.y + q + this.width + " × " + this.height}
+
+ function cp() {return this.x + q + this.y}
+
+ function cb(a, b, c, d, e, f) {a != null ? (this.a = +a, this.b = +b, this.c = +c, this.d = +d, this.e = +e, this.f = +f) : (this.a = 1, this.b = 0, this.c = 0, this.d = 1, this.e = 0, this.f = 0)}
+
+ function bH(b, c, d) {
+ b = a._path2curve(b), c = a._path2curve(c);
+ var e, f, g, h, i, j, k, l, m, n, o = d ? 0 : [];
+ for (var p = 0, q = b.length; p < q; p++) {
+ var r = b[p];
+ if (r[0] == "M")e = i = r[1], f = j = r[2]; else {
+ r[0] == "C" ? (m = [e, f].concat(r.slice(1)), e = m[6], f = m[7]) : (m = [e, f, e, f, i, j, i, j], e = i, f = j);
+ for (var s = 0, t = c.length; s < t; s++) {
+ var u = c[s];
+ if (u[0] == "M")g = k = u[1], h = l = u[2]; else {
+ u[0] == "C" ? (n = [g, h].concat(u.slice(1)), g = n[6], h = n[7]) : (n = [g, h, g, h, k, l, k, l], g = k, h = l);
+ var v = bG(m, n, d);
+ if (d)o += v; else {
+ for (var w = 0, x = v.length; w < x; w++)v[w].segment1 = p, v[w].segment2 = s, v[w].bez1 = m, v[w].bez2 = n;
+ o = o.concat(v)
+ }
+ }
+ }
+ }
+ }
+ return o
+ }
+
+ function bG(b, c, d) {
+ var e = a.bezierBBox(b), f = a.bezierBBox(c);
+ if (!a.isBBoxIntersect(e, f))return d ? 0 : [];
+ var g = bB.apply(0, b), h = bB.apply(0, c), i = ~~(g / 5), j = ~~(h / 5), k = [], l = [], m = {}, n = d ? 0 : [];
+ for (var o = 0; o < i + 1; o++) {
+ var p = a.findDotsAtSegment.apply(a, b.concat(o / i));
+ k.push({x: p.x, y: p.y, t: o / i})
+ }
+ for (o = 0; o < j + 1; o++)p = a.findDotsAtSegment.apply(a, c.concat(o / j)), l.push({x: p.x, y: p.y, t: o / j});
+ for (o = 0; o < i; o++)for (var q = 0; q < j; q++) {
+ var r = k[o], s = k[o + 1], t = l[q], u = l[q + 1], v = z(s.x - r.x) < .001 ? "y" : "x", w = z(u.x - t.x) < .001 ? "y" : "x", x = bD(r.x, r.y, s.x, s.y, t.x, t.y, u.x, u.y);
+ if (x) {
+ if (m[x.x.toFixed(4)] == x.y.toFixed(4))continue;
+ m[x.x.toFixed(4)] = x.y.toFixed(4);
+ var y = r.t + z((x[v] - r[v]) / (s[v] - r[v])) * (s.t - r.t), A = t.t + z((x[w] - t[w]) / (u[w] - t[w])) * (u.t - t.t);
+ y >= 0 && y <= 1 && A >= 0 && A <= 1 && (d ? n++ : n.push({x: x.x, y: x.y, t1: y, t2: A}))
+ }
+ }
+ return n
+ }
+
+ function bF(a, b) {return bG(a, b, 1)}
+
+ function bE(a, b) {return bG(a, b)}
+
+ function bD(a, b, c, d, e, f, g, h) {
+ if (!(x(a, c) < y(e, g) || y(a, c) > x(e, g) || x(b, d) < y(f, h) || y(b, d) > x(f, h))) {
+ var i = (a * d - b * c) * (e - g) - (a - c) * (e * h - f * g), j = (a * d - b * c) * (f - h) - (b - d) * (e * h - f * g), k = (a - c) * (f - h) - (b - d) * (e - g);
+ if (!k)return;
+ var l = i / k, m = j / k, n = +l.toFixed(2), o = +m.toFixed(2);
+ if (n < +y(a, c).toFixed(2) || n > +x(a, c).toFixed(2) || n < +y(e, g).toFixed(2) || n > +x(e, g).toFixed(2) || o < +y(b, d).toFixed(2) || o > +x(b, d).toFixed(2) || o < +y(f, h).toFixed(2) || o > +x(f, h).toFixed(2))return;
+ return{x: l, y: m}
+ }
+ }
+
+ function bC(a, b, c, d, e, f, g, h, i) {
+ if (!(i < 0 || bB(a, b, c, d, e, f, g, h) < i)) {
+ var j = 1, k = j / 2, l = j - k, m, n = .01;
+ m = bB(a, b, c, d, e, f, g, h, l);
+ while (z(m - i) > n)k /= 2, l += (m < i ? 1 : -1) * k, m = bB(a, b, c, d, e, f, g, h, l);
+ return l
+ }
+ }
+
+ function bB(a, b, c, d, e, f, g, h, i) {
+ i == null && (i = 1), i = i > 1 ? 1 : i < 0 ? 0 : i;
+ var j = i / 2, k = 12, l = [-0.1252, .1252, -0.3678, .3678, -0.5873, .5873, -0.7699, .7699, -0.9041, .9041, -0.9816, .9816], m = [.2491, .2491, .2335, .2335, .2032, .2032, .1601, .1601, .1069, .1069, .0472, .0472], n = 0;
+ for (var o = 0; o < k; o++) {
+ var p = j * l[o] + j, q = bA(p, a, c, e, g), r = bA(p, b, d, f, h), s = q * q + r * r;
+ n += m[o] * w.sqrt(s)
+ }
+ return j * n
+ }
+
+ function bA(a, b, c, d, e) {
+ var f = -3 * b + 9 * c - 9 * d + 3 * e, g = a * f + 6 * b - 12 * c + 6 * d;
+ return a * g - 3 * b + 3 * c
+ }
+
+ function by(a, b) {
+ var c = [];
+ for (var d = 0, e = a.length; e - 2 * !b > d; d += 2) {
+ var f = [
+ {x: +a[d - 2], y: +a[d - 1]},
+ {x: +a[d], y: +a[d + 1]},
+ {x: +a[d + 2], y: +a[d + 3]},
+ {x: +a[d + 4], y: +a[d + 5]}
+ ];
+ b ? d ? e - 4 == d ? f[3] = {x: +a[0], y: +a[1]} : e - 2 == d && (f[2] = {x: +a[0], y: +a[1]}, f[3] = {x: +a[2], y: +a[3]}) : f[0] = {x: +a[e - 2], y: +a[e - 1]} : e - 4 == d ? f[3] = f[2] : d || (f[0] = {x: +a[d], y: +a[d + 1]}), c.push(["C", (-f[0].x + 6 * f[1].x + f[2].x) / 6, (-f[0].y + 6 * f[1].y + f[2].y) / 6, (f[1].x + 6 * f[2].x - f[3].x) / 6, (f[1].y + 6 * f[2].y - f[3].y) / 6, f[2].x, f[2].y])
+ }
+ return c
+ }
+
+ function bx() {return this.hex}
+
+ function bv(a, b, c) {
+ function d() {
+ var e = Array.prototype.slice.call(arguments, 0), f = e.join("␀"), h = d.cache = d.cache || {}, i = d.count = d.count || [];
+ if (h[g](f)) {
+ bu(i, f);
+ return c ? c(h[f]) : h[f]
+ }
+ i.length >= 1e3 && delete h[i.shift()], i.push(f), h[f] = a[m](b, e);
+ return c ? c(h[f]) : h[f]
+ }
+
+ return d
+ }
+
+ function bu(a, b) {for (var c = 0, d = a.length; c < d; c++)if (a[c] === b)return a.push(a.splice(c, 1)[0])}
+
+ function bm(a) {
+ if (Object(a) !== a)return a;
+ var b = new a.constructor;
+ for (var c in a)a[g](c) && (b[c] = bm(a[c]));
+ return b
+ }
+
+ function a(c) {
+ if (a.is(c, "function"))return b ? c() : eve.on("raphael.DOMload", c);
+ if (a.is(c, E))return a._engine.create[m](a, c.splice(0, 3 + a.is(c[0], C))).add(c);
+ var d = Array.prototype.slice.call(arguments, 0);
+ if (a.is(d[d.length - 1], "function")) {
+ var e = d.pop();
+ return b ? e.call(a._engine.create[m](a, d)) : eve.on("raphael.DOMload", function () {e.call(a._engine.create[m](a, d))})
+ }
+ return a._engine.create[m](a, arguments)
+ }
+
+ a.version = "2.1.0", a.eve = eve;
+ var b, c = /[, ]+/, d = {circle: 1, rect: 1, path: 1, ellipse: 1, text: 1, image: 1}, e = /\{(\d+)\}/g, f = "prototype", g = "hasOwnProperty", h = {doc: document, win: window}, i = {was: Object.prototype[g].call(h.win, "Raphael"), is: h.win.Raphael}, j = function () {this.ca = this.customAttributes = {}}, k, l = "appendChild", m = "apply", n = "concat", o = "createTouch"in h.doc, p = "", q = " ", r = String, s = "split", t = "click dblclick mousedown mousemove mouseout mouseover mouseup touchstart touchmove touchend touchcancel"[s](q), u = {mousedown: "touchstart", mousemove: "touchmove", mouseup: "touchend"}, v = r.prototype.toLowerCase, w = Math, x = w.max, y = w.min, z = w.abs, A = w.pow, B = w.PI, C = "number", D = "string", E = "array", F = "toString", G = "fill", H = Object.prototype.toString, I = {}, J = "push", K = a._ISURL = /^url\(['"]?([^\)]+?)['"]?\)$/i, L = /^\s*((#[a-f\d]{6})|(#[a-f\d]{3})|rgba?\(\s*([\d\.]+%?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+%?(?:\s*,\s*[\d\.]+%?)?)\s*\)|hsba?\(\s*([\d\.]+(?:deg|\xb0|%)?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+(?:%?\s*,\s*[\d\.]+)?)%?\s*\)|hsla?\(\s*([\d\.]+(?:deg|\xb0|%)?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+(?:%?\s*,\s*[\d\.]+)?)%?\s*\))\s*$/i, M = {NaN: 1, Infinity: 1, "-Infinity": 1}, N = /^(?:cubic-)?bezier\(([^,]+),([^,]+),([^,]+),([^\)]+)\)/, O = w.round, P = "setAttribute", Q = parseFloat, R = parseInt, S = r.prototype.toUpperCase, T = a._availableAttrs = {"arrow-end": "none", "arrow-start": "none", blur: 0, "clip-rect": "0 0 1e9 1e9", cursor: "default", cx: 0, cy: 0, fill: "#fff", "fill-opacity": 1, font: '10px "Arial"', "font-family": '"Arial"', "font-size": "10", "font-style": "normal", "font-weight": 400, gradient: 0, height: 0, href: "http://raphaeljs.com/", "letter-spacing": 0, opacity: 1, path: "M0,0", r: 0, rx: 0, ry: 0, src: "", stroke: "#000", "stroke-dasharray": "", "stroke-linecap": "butt", "stroke-linejoin": "butt", "stroke-miterlimit": 0, "stroke-opacity": 1, "stroke-width": 1, target: "_blank", "text-anchor": "middle", title: "Raphael", transform: "", width: 0, x: 0, y: 0}, U = a._availableAnimAttrs = {blur: C, "clip-rect": "csv", cx: C, cy: C, fill: "colour", "fill-opacity": C, "font-size": C, height: C, opacity: C, path: "path", r: C, rx: C, ry: C, stroke: "colour", "stroke-opacity": C, "stroke-width": C, transform: "transform", width: C, x: C, y: C}, V = /[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]/g, W = /[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*,[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*/, X = {hs: 1, rg: 1}, Y = /,?([achlmqrstvxz]),?/gi, Z = /([achlmrqstvz])[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029,]*((-?\d*\.?\d*(?:e[\-+]?\d+)?[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*,?[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*)+)/ig, $ = /([rstm])[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029,]*((-?\d*\.?\d*(?:e[\-+]?\d+)?[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*,?[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*)+)/ig, _ = /(-?\d*\.?\d*(?:e[\-+]?\d+)?)[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*,?[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*/ig, ba = a._radial_gradient = /^r(?:\(([^,]+?)[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*,[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*([^\)]+?)\))?/, bb = {}, bc = function (a, b) {return a.key - b.key}, bd = function (a, b) {return Q(a) - Q(b)}, be = function () {}, bf = function (a) {return a}, bg = a._rectPath = function (a, b, c, d, e) {
+ if (e)return[
+ ["M", a + e, b],
+ ["l", c - e * 2, 0],
+ ["a", e, e, 0, 0, 1, e, e],
+ ["l", 0, d - e * 2],
+ ["a", e, e, 0, 0, 1, -e, e],
+ ["l", e * 2 - c, 0],
+ ["a", e, e, 0, 0, 1, -e, -e],
+ ["l", 0, e * 2 - d],
+ ["a", e, e, 0, 0, 1, e, -e],
+ ["z"]
+ ];
+ return[
+ ["M", a, b],
+ ["l", c, 0],
+ ["l", 0, d],
+ ["l", -c, 0],
+ ["z"]
+ ]
+ }, bh = function (a, b, c, d) {
+ d == null && (d = c);
+ return[
+ ["M", a, b],
+ ["m", 0, -d],
+ ["a", c, d, 0, 1, 1, 0, 2 * d],
+ ["a", c, d, 0, 1, 1, 0, -2 * d],
+ ["z"]
+ ]
+ }, bi = a._getPath = {path: function (a) {return a.attr("path")}, circle: function (a) {
+ var b = a.attrs;
+ return bh(b.cx, b.cy, b.r)
+ }, ellipse: function (a) {
+ var b = a.attrs;
+ return bh(b.cx, b.cy, b.rx, b.ry)
+ }, rect: function (a) {
+ var b = a.attrs;
+ return bg(b.x, b.y, b.width, b.height, b.r)
+ }, image: function (a) {
+ var b = a.attrs;
+ return bg(b.x, b.y, b.width, b.height)
+ }, text: function (a) {
+ var b = a._getBBox();
+ return bg(b.x, b.y, b.width, b.height)
+ }}, bj = a.mapPath = function (a, b) {
+ if (!b)return a;
+ var c, d, e, f, g, h, i;
+ a = bR(a);
+ for (e = 0, g = a.length; e < g; e++) {
+ i = a[e];
+ for (f = 1, h = i.length; f < h; f += 2)c = b.x(i[f], i[f + 1]), d = b.y(i[f], i[f + 1]), i[f] = c, i[f + 1] = d
+ }
+ return a
+ };
+ a._g = h, a.type = h.win.SVGAngle || h.doc.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure", "1.1") ? "SVG" : "VML";
+ if (a.type == "VML") {
+ var bk = h.doc.createElement("div"), bl;
+ bk.innerHTML = '<v:shape adj="1"/>', bl = bk.firstChild, bl.style.behavior = "url(#default#VML)";
+ if (!bl || typeof bl.adj != "object")return a.type = p;
+ bk = null
+ }
+ a.svg = !(a.vml = a.type == "VML"), a._Paper = j, a.fn = k = j.prototype = a.prototype, a._id = 0, a._oid = 0, a.is = function (a, b) {
+ b = v.call(b);
+ if (b == "finite")return!M[g](+a);
+ if (b == "array")return a instanceof Array;
+ return b == "null" && a === null || b == typeof a && a !== null || b == "object" && a === Object(a) || b == "array" && Array.isArray && Array.isArray(a) || H.call(a).slice(8, -1).toLowerCase() == b
+ }, a.angle = function (b, c, d, e, f, g) {
+ if (f == null) {
+ var h = b - d, i = c - e;
+ if (!h && !i)return 0;
+ return(180 + w.atan2(-i, -h) * 180 / B + 360) % 360
+ }
+ return a.angle(b, c, f, g) - a.angle(d, e, f, g)
+ }, a.rad = function (a) {return a % 360 * B / 180}, a.deg = function (a) {return a * 180 / B % 360}, a.snapTo = function (b, c, d) {
+ d = a.is(d, "finite") ? d : 10;
+ if (a.is(b, E)) {
+ var e = b.length;
+ while (e--)if (z(b[e] - c) <= d)return b[e]
+ } else {
+ b = +b;
+ var f = c % b;
+ if (f < d)return c - f;
+ if (f > b - d)return c - f + b
+ }
+ return c
+ };
+ var bn = a.createUUID = function (a, b) {return function () {return"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(a, b).toUpperCase()}}(/[xy]/g, function (a) {
+ var b = w.random() * 16 | 0, c = a == "x" ? b : b & 3 | 8;
+ return c.toString(16)
+ });
+ a.setWindow = function (b) {eve("raphael.setWindow", a, h.win, b), h.win = b, h.doc = h.win.document, a._engine.initWin && a._engine.initWin(h.win)};
+ var bo = function (b) {
+ if (a.vml) {
+ var c = /^\s+|\s+$/g, d;
+ try {
+ var e = new ActiveXObject("htmlfile");
+ e.write("<body>"), e.close(), d = e.body
+ } catch (f) {d = createPopup().document.body}
+ var g = d.createTextRange();
+ bo = bv(function (a) {
+ try {
+ d.style.color = r(a).replace(c, p);
+ var b = g.queryCommandValue("ForeColor");
+ b = (b & 255) << 16 | b & 65280 | (b & 16711680) >>> 16;
+ return"#" + ("000000" + b.toString(16)).slice(-6)
+ } catch (e) {return"none"}
+ })
+ } else {
+ var i = h.doc.createElement("i");
+ i.title = "Raphaël Colour Picker", i.style.display = "none", h.doc.body.appendChild(i), bo = bv(function (a) {
+ i.style.color = a;
+ return h.doc.defaultView.getComputedStyle(i, p).getPropertyValue("color")
+ })
+ }
+ return bo(b)
+ }, bp = function () {return"hsb(" + [this.h, this.s, this.b] + ")"}, bq = function () {return"hsl(" + [this.h, this.s, this.l] + ")"}, br = function () {return this.hex}, bs = function (b, c, d) {
+ c == null && a.is(b, "object") && "r"in b && "g"in b && "b"in b && (d = b.b, c = b.g, b = b.r);
+ if (c == null && a.is(b, D)) {
+ var e = a.getRGB(b);
+ b = e.r, c = e.g, d = e.b
+ }
+ if (b > 1 || c > 1 || d > 1)b /= 255, c /= 255, d /= 255;
+ return[b, c, d]
+ }, bt = function (b, c, d, e) {
+ b *= 255, c *= 255, d *= 255;
+ var f = {r: b, g: c, b: d, hex: a.rgb(b, c, d), toString: br};
+ a.is(e, "finite") && (f.opacity = e);
+ return f
+ };
+ a.color = function (b) {
+ var c;
+ a.is(b, "object") && "h"in b && "s"in b && "b"in b ? (c = a.hsb2rgb(b), b.r = c.r, b.g = c.g, b.b = c.b, b.hex = c.hex) : a.is(b, "object") && "h"in b && "s"in b && "l"in b ? (c = a.hsl2rgb(b), b.r = c.r, b.g = c.g, b.b = c.b, b.hex = c.hex) : (a.is(b, "string") && (b = a.getRGB(b)), a.is(b, "object") && "r"in b && "g"in b && "b"in b ? (c = a.rgb2hsl(b), b.h = c.h, b.s = c.s, b.l = c.l, c = a.rgb2hsb(b), b.v = c.b) : (b = {hex: "none"}, b.r = b.g = b.b = b.h = b.s = b.v = b.l = -1)), b.toString = br;
+ return b
+ }, a.hsb2rgb = function (a, b, c, d) {
+ this.is(a, "object") && "h"in a && "s"in a && "b"in a && (c = a.b, b = a.s, a = a.h, d = a.o), a *= 360;
+ var e, f, g, h, i;
+ a = a % 360 / 60, i = c * b, h = i * (1 - z(a % 2 - 1)), e = f = g = c - i, a = ~~a, e += [i, h, 0, 0, h, i][a], f += [h, i, i, h, 0, 0][a], g += [0, 0, h, i, i, h][a];
+ return bt(e, f, g, d)
+ }, a.hsl2rgb = function (a, b, c, d) {
+ this.is(a, "object") && "h"in a && "s"in a && "l"in a && (c = a.l, b = a.s, a = a.h);
+ if (a > 1 || b > 1 || c > 1)a /= 360, b /= 100, c /= 100;
+ a *= 360;
+ var e, f, g, h, i;
+ a = a % 360 / 60, i = 2 * b * (c < .5 ? c : 1 - c), h = i * (1 - z(a % 2 - 1)), e = f = g = c - i / 2, a = ~~a, e += [i, h, 0, 0, h, i][a], f += [h, i, i, h, 0, 0][a], g += [0, 0, h, i, i, h][a];
+ return bt(e, f, g, d)
+ }, a.rgb2hsb = function (a, b, c) {
+ c = bs(a, b, c), a = c[0], b = c[1], c = c[2];
+ var d, e, f, g;
+ f = x(a, b, c), g = f - y(a, b, c), d = g == 0 ? null : f == a ? (b - c) / g : f == b ? (c - a) / g + 2 : (a - b) / g + 4, d = (d + 360) % 6 * 60 / 360, e = g == 0 ? 0 : g / f;
+ return{h: d, s: e, b: f, toString: bp}
+ }, a.rgb2hsl = function (a, b, c) {
+ c = bs(a, b, c), a = c[0], b = c[1], c = c[2];
+ var d, e, f, g, h, i;
+ g = x(a, b, c), h = y(a, b, c), i = g - h, d = i == 0 ? null : g == a ? (b - c) / i : g == b ? (c - a) / i + 2 : (a - b) / i + 4, d = (d + 360) % 6 * 60 / 360, f = (g + h) / 2, e = i == 0 ? 0 : f < .5 ? i / (2 * f) : i / (2 - 2 * f);
+ return{h: d, s: e, l: f, toString: bq}
+ }, a._path2string = function () {return this.join(",").replace(Y, "$1")};
+ var bw = a._preload = function (a, b) {
+ var c = h.doc.createElement("img");
+ c.style.cssText = "position:absolute;left:-9999em;top:-9999em", c.onload = function () {b.call(this), this.onload = null, h.doc.body.removeChild(this)}, c.onerror = function () {h.doc.body.removeChild(this)}, h.doc.body.appendChild(c), c.src = a
+ };
+ a.getRGB = bv(function (b) {
+ if (!b || !!((b = r(b)).indexOf("-") + 1))return{r: -1, g: -1, b: -1, hex: "none", error: 1, toString: bx};
+ if (b == "none")return{r: -1, g: -1, b: -1, hex: "none", toString: bx};
+ !X[g](b.toLowerCase().substring(0, 2)) && b.charAt() != "#" && (b = bo(b));
+ var c, d, e, f, h, i, j, k = b.match(L);
+ if (k) {
+ k[2] && (f = R(k[2].substring(5), 16), e = R(k[2].substring(3, 5), 16), d = R(k[2].substring(1, 3), 16)), k[3] && (f = R((i = k[3].charAt(3)) + i, 16), e = R((i = k[3].charAt(2)) + i, 16), d = R((i = k[3].charAt(1)) + i, 16)), k[4] && (j = k[4][s](W), d = Q(j[0]), j[0].slice(-1) == "%" && (d *= 2.55), e = Q(j[1]), j[1].slice(-1) == "%" && (e *= 2.55), f = Q(j[2]), j[2].slice(-1) == "%" && (f *= 2.55), k[1].toLowerCase().slice(0, 4) == "rgba" && (h = Q(j[3])), j[3] && j[3].slice(-1) == "%" && (h /= 100));
+ if (k[5]) {
+ j = k[5][s](W), d = Q(j[0]), j[0].slice(-1) == "%" && (d *= 2.55), e = Q(j[1]), j[1].slice(-1) == "%" && (e *= 2.55), f = Q(j[2]), j[2].slice(-1) == "%" && (f *= 2.55), (j[0].slice(-3) == "deg" || j[0].slice(-1) == "°") && (d /= 360), k[1].toLowerCase().slice(0, 4) == "hsba" && (h = Q(j[3])), j[3] && j[3].slice(-1) == "%" && (h /= 100);
+ return a.hsb2rgb(d, e, f, h)
+ }
+ if (k[6]) {
+ j = k[6][s](W), d = Q(j[0]), j[0].slice(-1) == "%" && (d *= 2.55), e = Q(j[1]), j[1].slice(-1) == "%" && (e *= 2.55), f = Q(j[2]), j[2].slice(-1) == "%" && (f *= 2.55), (j[0].slice(-3) == "deg" || j[0].slice(-1) == "°") && (d /= 360), k[1].toLowerCase().slice(0, 4) == "hsla" && (h = Q(j[3])), j[3] && j[3].slice(-1) == "%" && (h /= 100);
+ return a.hsl2rgb(d, e, f, h)
+ }
+ k = {r: d, g: e, b: f, toString: bx}, k.hex = "#" + (16777216 | f | e << 8 | d << 16).toString(16).slice(1), a.is(h, "finite") && (k.opacity = h);
+ return k
+ }
+ return{r: -1, g: -1, b: -1, hex: "none", error: 1, toString: bx}
+ }, a), a.hsb = bv(function (b, c, d) {return a.hsb2rgb(b, c, d).hex}), a.hsl = bv(function (b, c, d) {return a.hsl2rgb(b, c, d).hex}), a.rgb = bv(function (a, b, c) {return"#" + (16777216 | c | b << 8 | a << 16).toString(16).slice(1)}), a.getColor = function (a) {
+ var b = this.getColor.start = this.getColor.start || {h: 0, s: 1, b: a || .75}, c = this.hsb2rgb(b.h, b.s, b.b);
+ b.h += .075, b.h > 1 && (b.h = 0, b.s -= .2, b.s <= 0 && (this.getColor.start = {h: 0, s: 1, b: b.b}));
+ return c.hex
+ }, a.getColor.reset = function () {delete this.start}, a.parsePathString = function (b) {
+ if (!b)return null;
+ var c = bz(b);
+ if (c.arr)return bJ(c.arr);
+ var d = {a: 7, c: 6, h: 1, l: 2, m: 2, r: 4, q: 4, s: 4, t: 2, v: 1, z: 0}, e = [];
+ a.is(b, E) && a.is(b[0], E) && (e = bJ(b)), e.length || r(b).replace(Z, function (a, b, c) {
+ var f = [], g = b.toLowerCase();
+ c.replace(_, function (a, b) {b && f.push(+b)}), g == "m" && f.length > 2 && (e.push([b][n](f.splice(0, 2))), g = "l", b = b == "m" ? "l" : "L");
+ if (g == "r")e.push([b][n](f)); else while (f.length >= d[g]) {
+ e.push([b][n](f.splice(0, d[g])));
+ if (!d[g])break
+ }
+ }), e.toString = a._path2string, c.arr = bJ(e);
+ return e
+ }, a.parseTransformString = bv(function (b) {
+ if (!b)return null;
+ var c = {r: 3, s: 4, t: 2, m: 6}, d = [];
+ a.is(b, E) && a.is(b[0], E) && (d = bJ(b)), d.length || r(b).replace($, function (a, b, c) {
+ var e = [], f = v.call(b);
+ c.replace(_, function (a, b) {b && e.push(+b)}), d.push([b][n](e))
+ }), d.toString = a._path2string;
+ return d
+ });
+ var bz = function (a) {
+ var b = bz.ps = bz.ps || {};
+ b[a] ? b[a].sleep = 100 : b[a] = {sleep: 100}, setTimeout(function () {for (var c in b)b[g](c) && c != a && (b[c].sleep--, !b[c].sleep && delete b[c])});
+ return b[a]
+ };
+ a.findDotsAtSegment = function (a, b, c, d, e, f, g, h, i) {
+ var j = 1 - i, k = A(j, 3), l = A(j, 2), m = i * i, n = m * i, o = k * a + l * 3 * i * c + j * 3 * i * i * e + n * g, p = k * b + l * 3 * i * d + j * 3 * i * i * f + n * h, q = a + 2 * i * (c - a) + m * (e - 2 * c + a), r = b + 2 * i * (d - b) + m * (f - 2 * d + b), s = c + 2 * i * (e - c) + m * (g - 2 * e + c), t = d + 2 * i * (f - d) + m * (h - 2 * f + d), u = j * a + i * c, v = j * b + i * d, x = j * e + i * g, y = j * f + i * h, z = 90 - w.atan2(q - s, r - t) * 180 / B;
+ (q > s || r < t) && (z += 180);
+ return{x: o, y: p, m: {x: q, y: r}, n: {x: s, y: t}, start: {x: u, y: v}, end: {x: x, y: y}, alpha: z}
+ }, a.bezierBBox = function (b, c, d, e, f, g, h, i) {
+ a.is(b, "array") || (b = [b, c, d, e, f, g, h, i]);
+ var j = bQ.apply(null, b);
+ return{x: j.min.x, y: j.min.y, x2: j.max.x, y2: j.max.y, width: j.max.x - j.min.x, height: j.max.y - j.min.y}
+ }, a.isPointInsideBBox = function (a, b, c) {return b >= a.x && b <= a.x2 && c >= a.y && c <= a.y2}, a.isBBoxIntersect = function (b, c) {
+ var d = a.isPointInsideBBox;
+ return d(c, b.x, b.y) || d(c, b.x2, b.y) || d(c, b.x, b.y2) || d(c, b.x2, b.y2) || d(b, c.x, c.y) || d(b, c.x2, c.y) || d(b, c.x, c.y2) || d(b, c.x2, c.y2) || (b.x < c.x2 && b.x > c.x || c.x < b.x2 && c.x > b.x) && (b.y < c.y2 && b.y > c.y || c.y < b.y2 && c.y > b.y)
+ }, a.pathIntersection = function (a, b) {return bH(a, b)}, a.pathIntersectionNumber = function (a, b) {return bH(a, b, 1)}, a.isPointInsidePath = function (b, c, d) {
+ var e = a.pathBBox(b);
+ return a.isPointInsideBBox(e, c, d) && bH(b, [
+ ["M", c, d],
+ ["H", e.x2 + 10]
+ ], 1) % 2 == 1
+ }, a._removedFactory = function (a) {return function () {eve("raphael.log", null, "Raphaël: you are calling to method “" + a + "” of removed object", a)}};
+ var bI = a.pathBBox = function (a) {
+ var b = bz(a);
+ if (b.bbox)return b.bbox;
+ if (!a)return{x: 0, y: 0, width: 0, height: 0, x2: 0, y2: 0};
+ a = bR(a);
+ var c = 0, d = 0, e = [], f = [], g;
+ for (var h = 0, i = a.length; h < i; h++) {
+ g = a[h];
+ if (g[0] == "M")c = g[1], d = g[2], e.push(c), f.push(d); else {
+ var j = bQ(c, d, g[1], g[2], g[3], g[4], g[5], g[6]);
+ e = e[n](j.min.x, j.max.x), f = f[n](j.min.y, j.max.y), c = g[5], d = g[6]
+ }
+ }
+ var k = y[m](0, e), l = y[m](0, f), o = x[m](0, e), p = x[m](0, f), q = {x: k, y: l, x2: o, y2: p, width: o - k, height: p - l};
+ b.bbox = bm(q);
+ return q
+ }, bJ = function (b) {
+ var c = bm(b);
+ c.toString = a._path2string;
+ return c
+ }, bK = a._pathToRelative = function (b) {
+ var c = bz(b);
+ if (c.rel)return bJ(c.rel);
+ if (!a.is(b, E) || !a.is(b && b[0], E))b = a.parsePathString(b);
+ var d = [], e = 0, f = 0, g = 0, h = 0, i = 0;
+ b[0][0] == "M" && (e = b[0][1], f = b[0][2], g = e, h = f, i++, d.push(["M", e, f]));
+ for (var j = i, k = b.length; j < k; j++) {
+ var l = d[j] = [], m = b[j];
+ if (m[0] != v.call(m[0])) {
+ l[0] = v.call(m[0]);
+ switch (l[0]) {
+ case"a":
+ l[1] = m[1], l[2] = m[2], l[3] = m[3], l[4] = m[4], l[5] = m[5], l[6] = +(m[6] - e).toFixed(3), l[7] = +(m[7] - f).toFixed(3);
+ break;
+ case"v":
+ l[1] = +(m[1] - f).toFixed(3);
+ break;
+ case"m":
+ g = m[1], h = m[2];
+ default:
+ for (var n = 1, o = m.length; n < o; n++)l[n] = +(m[n] - (n % 2 ? e : f)).toFixed(3)
+ }
+ } else {
+ l = d[j] = [], m[0] == "m" && (g = m[1] + e, h = m[2] + f);
+ for (var p = 0, q = m.length; p < q; p++)d[j][p] = m[p]
+ }
+ var r = d[j].length;
+ switch (d[j][0]) {
+ case"z":
+ e = g, f = h;
+ break;
+ case"h":
+ e += +d[j][r - 1];
+ break;
+ case"v":
+ f += +d[j][r - 1];
+ break;
+ default:
+ e += +d[j][r - 2], f += +d[j][r - 1]
+ }
+ }
+ d.toString = a._path2string, c.rel = bJ(d);
+ return d
+ }, bL = a._pathToAbsolute = function (b) {
+ var c = bz(b);
+ if (c.abs)return bJ(c.abs);
+ if (!a.is(b, E) || !a.is(b && b[0], E))b = a.parsePathString(b);
+ if (!b || !b.length)return[
+ ["M", 0, 0]
+ ];
+ var d = [], e = 0, f = 0, g = 0, h = 0, i = 0;
+ b[0][0] == "M" && (e = +b[0][1], f = +b[0][2], g = e, h = f, i++, d[0] = ["M", e, f]);
+ var j = b.length == 3 && b[0][0] == "M" && b[1][0].toUpperCase() == "R" && b[2][0].toUpperCase() == "Z";
+ for (var k, l, m = i, o = b.length; m < o; m++) {
+ d.push(k = []), l = b[m];
+ if (l[0] != S.call(l[0])) {
+ k[0] = S.call(l[0]);
+ switch (k[0]) {
+ case"A":
+ k[1] = l[1], k[2] = l[2], k[3] = l[3], k[4] = l[4], k[5] = l[5], k[6] = +(l[6] + e), k[7] = +(l[7] + f);
+ break;
+ case"V":
+ k[1] = +l[1] + f;
+ break;
+ case"H":
+ k[1] = +l[1] + e;
+ break;
+ case"R":
+ var p = [e, f][n](l.slice(1));
+ for (var q = 2, r = p.length; q < r; q++)p[q] = +p[q] + e, p[++q] = +p[q] + f;
+ d.pop(), d = d[n](by(p, j));
+ break;
+ case"M":
+ g = +l[1] + e, h = +l[2] + f;
+ default:
+ for (q = 1, r = l.length; q < r; q++)k[q] = +l[q] + (q % 2 ? e : f)
+ }
+ } else if (l[0] == "R")p = [e, f][n](l.slice(1)), d.pop(), d = d[n](by(p, j)), k = ["R"][n](l.slice(-2)); else for (var s = 0, t = l.length; s < t; s++)k[s] = l[s];
+ switch (k[0]) {
+ case"Z":
+ e = g, f = h;
+ break;
+ case"H":
+ e = k[1];
+ break;
+ case"V":
+ f = k[1];
+ break;
+ case"M":
+ g = k[k.length - 2], h = k[k.length - 1];
+ default:
+ e = k[k.length - 2], f = k[k.length - 1]
+ }
+ }
+ d.toString = a._path2string, c.abs = bJ(d);
+ return d
+ }, bM = function (a, b, c, d) {return[a, b, c, d, c, d]}, bN = function (a, b, c, d, e, f) {
+ var g = 1 / 3, h = 2 / 3;
+ return[g * a + h * c, g * b + h * d, g * e + h * c, g * f + h * d, e, f]
+ }, bO = function (a, b, c, d, e, f, g, h, i, j) {
+ var k = B * 120 / 180, l = B / 180 * (+e || 0), m = [], o, p = bv(function (a, b, c) {
+ var d = a * w.cos(c) - b * w.sin(c), e = a * w.sin(c) + b * w.cos(c);
+ return{x: d, y: e}
+ });
+ if (!j) {
+ o = p(a, b, -l), a = o.x, b = o.y, o = p(h, i, -l), h = o.x, i = o.y;
+ var q = w.cos(B / 180 * e), r = w.sin(B / 180 * e), t = (a - h) / 2, u = (b - i) / 2, v = t * t / (c * c) + u * u / (d * d);
+ v > 1 && (v = w.sqrt(v), c = v * c, d = v * d);
+ var x = c * c, y = d * d, A = (f == g ? -1 : 1) * w.sqrt(z((x * y - x * u * u - y * t * t) / (x * u * u + y * t * t))), C = A * c * u / d + (a + h) / 2, D = A * -d * t / c + (b + i) / 2, E = w.asin(((b - D) / d).toFixed(9)), F = w.asin(((i - D) / d).toFixed(9));
+ E = a < C ? B - E : E, F = h < C ? B - F : F, E < 0 && (E = B * 2 + E), F < 0 && (F = B * 2 + F), g && E > F && (E = E - B * 2), !g && F > E && (F = F - B * 2)
+ } else E = j[0], F = j[1], C = j[2], D = j[3];
+ var G = F - E;
+ if (z(G) > k) {
+ var H = F, I = h, J = i;
+ F = E + k * (g && F > E ? 1 : -1), h = C + c * w.cos(F), i = D + d * w.sin(F), m = bO(h, i, c, d, e, 0, g, I, J, [F, H, C, D])
+ }
+ G = F - E;
+ var K = w.cos(E), L = w.sin(E), M = w.cos(F), N = w.sin(F), O = w.tan(G / 4), P = 4 / 3 * c * O, Q = 4 / 3 * d * O, R = [a, b], S = [a + P * L, b - Q * K], T = [h + P * N, i - Q * M], U = [h, i];
+ S[0] = 2 * R[0] - S[0], S[1] = 2 * R[1] - S[1];
+ if (j)return[S, T, U][n](m);
+ m = [S, T, U][n](m).join()[s](",");
+ var V = [];
+ for (var W = 0, X = m.length; W < X; W++)V[W] = W % 2 ? p(m[W - 1], m[W], l).y : p(m[W], m[W + 1], l).x;
+ return V
+ }, bP = function (a, b, c, d, e, f, g, h, i) {
+ var j = 1 - i;
+ return{x: A(j, 3) * a + A(j, 2) * 3 * i * c + j * 3 * i * i * e + A(i, 3) * g, y: A(j, 3) * b + A(j, 2) * 3 * i * d + j * 3 * i * i * f + A(i, 3) * h}
+ }, bQ = bv(function (a, b, c, d, e, f, g, h) {
+ var i = e - 2 * c + a - (g - 2 * e + c), j = 2 * (c - a) - 2 * (e - c), k = a - c, l = (-j + w.sqrt(j * j - 4 * i * k)) / 2 / i, n = (-j - w.sqrt(j * j - 4 * i * k)) / 2 / i, o = [b, h], p = [a, g], q;
+ z(l) > "1e12" && (l = .5), z(n) > "1e12" && (n = .5), l > 0 && l < 1 && (q = bP(a, b, c, d, e, f, g, h, l), p.push(q.x), o.push(q.y)), n > 0 && n < 1 && (q = bP(a, b, c, d, e, f, g, h, n), p.push(q.x), o.push(q.y)), i = f - 2 * d + b - (h - 2 * f + d), j = 2 * (d - b) - 2 * (f - d), k = b - d, l = (-j + w.sqrt(j * j - 4 * i * k)) / 2 / i, n = (-j - w.sqrt(j * j - 4 * i * k)) / 2 / i, z(l) > "1e12" && (l = .5), z(n) > "1e12" && (n = .5), l > 0 && l < 1 && (q = bP(a, b, c, d, e, f, g, h, l), p.push(q.x), o.push(q.y)), n > 0 && n < 1 && (q = bP(a, b, c, d, e, f, g, h, n), p.push(q.x), o.push(q.y));
+ return{min: {x: y[m](0, p), y: y[m](0, o)}, max: {x: x[m](0, p), y: x[m](0, o)}}
+ }), bR = a._path2curve = bv(function (a, b) {
+ var c = !b && bz(a);
+ if (!b && c.curve)return bJ(c.curve);
+ var d = bL(a), e = b && bL(b), f = {x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0, qx: null, qy: null}, g = {x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0, qx: null, qy: null}, h = function (a, b) {
+ var c, d;
+ if (!a)return["C", b.x, b.y, b.x, b.y, b.x, b.y];
+ !(a[0]in{T: 1, Q: 1}) && (b.qx = b.qy = null);
+ switch (a[0]) {
+ case"M":
+ b.X = a[1], b.Y = a[2];
+ break;
+ case"A":
+ a = ["C"][n](bO[m](0, [b.x, b.y][n](a.slice(1))));
+ break;
+ case"S":
+ c = b.x + (b.x - (b.bx || b.x)), d = b.y + (b.y - (b.by || b.y)), a = ["C", c, d][n](a.slice(1));
+ break;
+ case"T":
+ b.qx = b.x + (b.x - (b.qx || b.x)), b.qy = b.y + (b.y - (b.qy || b.y)), a = ["C"][n](bN(b.x, b.y, b.qx, b.qy, a[1], a[2]));
+ break;
+ case"Q":
+ b.qx = a[1], b.qy = a[2], a = ["C"][n](bN(b.x, b.y, a[1], a[2], a[3], a[4]));
+ break;
+ case"L":
+ a = ["C"][n](bM(b.x, b.y, a[1], a[2]));
+ break;
+ case"H":
+ a = ["C"][n](bM(b.x, b.y, a[1], b.y));
+ break;
+ case"V":
+ a = ["C"][n](bM(b.x, b.y, b.x, a[1]));
+ break;
+ case"Z":
+ a = ["C"][n](bM(b.x, b.y, b.X, b.Y))
+ }
+ return a
+ }, i = function (a, b) {
+ if (a[b].length > 7) {
+ a[b].shift();
+ var c = a[b];
+ while (c.length)a.splice(b++, 0, ["C"][n](c.splice(0, 6)));
+ a.splice(b, 1), l = x(d.length, e && e.length || 0)
+ }
+ }, j = function (a, b, c, f, g) {a && b && a[g][0] == "M" && b[g][0] != "M" && (b.splice(g, 0, ["M", f.x, f.y]), c.bx = 0, c.by = 0, c.x = a[g][1], c.y = a[g][2], l = x(d.length, e && e.length || 0))};
+ for (var k = 0, l = x(d.length, e && e.length || 0); k < l; k++) {
+ d[k] = h(d[k], f), i(d, k), e && (e[k] = h(e[k], g)), e && i(e, k), j(d, e, f, g, k), j(e, d, g, f, k);
+ var o = d[k], p = e && e[k], q = o.length, r = e && p.length;
+ f.x = o[q - 2], f.y = o[q - 1], f.bx = Q(o[q - 4]) || f.x, f.by = Q(o[q - 3]) || f.y, g.bx = e && (Q(p[r - 4]) || g.x), g.by = e && (Q(p[r - 3]) || g.y), g.x = e && p[r - 2], g.y = e && p[r - 1]
+ }
+ e || (c.curve = bJ(d));
+ return e ? [d, e] : d
+ }, null, bJ), bS = a._parseDots = bv(function (b) {
+ var c = [];
+ for (var d = 0, e = b.length; d < e; d++) {
+ var f = {}, g = b[d].match(/^([^:]*):?([\d\.]*)/);
+ f.color = a.getRGB(g[1]);
+ if (f.color.error)return null;
+ f.color = f.color.hex, g[2] && (f.offset = g[2] + "%"), c.push(f)
+ }
+ for (d = 1, e = c.length - 1; d < e; d++)if (!c[d].offset) {
+ var h = Q(c[d - 1].offset || 0), i = 0;
+ for (var j = d + 1; j < e; j++)if (c[j].offset) {
+ i = c[j].offset;
+ break
+ }
+ i || (i = 100, j = e), i = Q(i);
+ var k = (i - h) / (j - d + 1);
+ for (; d < j; d++)h += k, c[d].offset = h + "%"
+ }
+ return c
+ }), bT = a._tear = function (a, b) {a == b.top && (b.top = a.prev), a == b.bottom && (b.bottom = a.next), a.next && (a.next.prev = a.prev), a.prev && (a.prev.next = a.next)}, bU = a._tofront = function (a, b) {b.top !== a && (bT(a, b), a.next = null, a.prev = b.top, b.top.next = a, b.top = a)}, bV = a._toback = function (a, b) {b.bottom !== a && (bT(a, b), a.next = b.bottom, a.prev = null, b.bottom.prev = a, b.bottom = a)}, bW = a._insertafter = function (a, b, c) {bT(a, c), b == c.top && (c.top = a), b.next && (b.next.prev = a), a.next = b.next, a.prev = b, b.next = a}, bX = a._insertbefore = function (a, b, c) {bT(a, c), b == c.bottom && (c.bottom = a), b.prev && (b.prev.next = a), a.prev = b.prev, b.prev = a, a.next = b}, bY = a.toMatrix = function (a, b) {
+ var c = bI(a), d = {_: {transform: p}, getBBox: function () {return c}};
+ b$(d, b);
+ return d.matrix
+ }, bZ = a.transformPath = function (a, b) {return bj(a, bY(a, b))}, b$ = a._extractTransform = function (b, c) {
+ if (c == null)return b._.transform;
+ c = r(c).replace(/\.{3}|\u2026/g, b._.transform || p);
+ var d = a.parseTransformString(c), e = 0, f = 0, g = 0, h = 1, i = 1, j = b._, k = new cb;
+ j.transform = d || [];
+ if (d)for (var l = 0, m = d.length; l < m; l++) {
+ var n = d[l], o = n.length, q = r(n[0]).toLowerCase(), s = n[0] != q, t = s ? k.invert() : 0, u, v, w, x, y;
+ q == "t" && o == 3 ? s ? (u = t.x(0, 0), v = t.y(0, 0), w = t.x(n[1], n[2]), x = t.y(n[1], n[2]), k.translate(w - u, x - v)) : k.translate(n[1], n[2]) : q == "r" ? o == 2 ? (y = y || b.getBBox(1), k.rotate(n[1], y.x + y.width / 2, y.y + y.height / 2), e += n[1]) : o == 4 && (s ? (w = t.x(n[2], n[3]), x = t.y(n[2], n[3]), k.rotate(n[1], w, x)) : k.rotate(n[1], n[2], n[3]), e += n[1]) : q == "s" ? o == 2 || o == 3 ? (y = y || b.getBBox(1), k.scale(n[1], n[o - 1], y.x + y.width / 2, y.y + y.height / 2), h *= n[1], i *= n[o - 1]) : o == 5 && (s ? (w = t.x(n[3], n[4]), x = t.y(n[3], n[4]), k.scale(n[1], n[2], w, x)) : k.scale(n[1], n[2], n[3], n[4]), h *= n[1], i *= n[2]) : q == "m" && o == 7 && k.add(n[1], n[2], n[3], n[4], n[5], n[6]), j.dirtyT = 1, b.matrix = k
+ }
+ b.matrix = k, j.sx = h, j.sy = i, j.deg = e, j.dx = f = k.e, j.dy = g = k.f, h == 1 && i == 1 && !e && j.bbox ? (j.bbox.x += +f, j.bbox.y += +g) : j.dirtyT = 1
+ }, b_ = function (a) {
+ var b = a[0];
+ switch (b.toLowerCase()) {
+ case"t":
+ return[b, 0, 0];
+ case"m":
+ return[b, 1, 0, 0, 1, 0, 0];
+ case"r":
+ return a.length == 4 ? [b, 0, a[2], a[3]] : [b, 0];
+ case"s":
+ return a.length == 5 ? [b, 1, 1, a[3], a[4]] : a.length == 3 ? [b, 1, 1] : [b, 1]
+ }
+ }, ca = a._equaliseTransform = function (b, c) {
+ c = r(c).replace(/\.{3}|\u2026/g, b), b = a.parseTransformString(b) || [], c = a.parseTransformString(c) || [];
+ var d = x(b.length, c.length), e = [], f = [], g = 0, h, i, j, k;
+ for (; g < d; g++) {
+ j = b[g] || b_(c[g]), k = c[g] || b_(j);
+ if (j[0] != k[0] || j[0].toLowerCase() == "r" && (j[2] != k[2] || j[3] != k[3]) || j[0].toLowerCase() == "s" && (j[3] != k[3] || j[4] != k[4]))return;
+ e[g] = [], f[g] = [];
+ for (h = 0, i = x(j.length, k.length); h < i; h++)h in j && (e[g][h] = j[h]), h in k && (f[g][h] = k[h])
+ }
+ return{from: e, to: f}
+ };
+ a._getContainer = function (b, c, d, e) {
+ var f;
+ f = e == null && !a.is(b, "object") ? h.doc.getElementById(b) : b;
+ if (f != null) {
+ if (f.tagName)return c == null ? {container: f, width: f.style.pixelWidth || f.offsetWidth, height: f.style.pixelHeight || f.offsetHeight} : {container: f, width: c, height: d};
+ return{container: 1, x: b, y: c, width: d, height: e}
+ }
+ }, a.pathToRelative = bK, a._engine = {}, a.path2curve = bR, a.matrix = function (a, b, c, d, e, f) {return new cb(a, b, c, d, e, f)}, function (b) {
+ function d(a) {
+ var b = w.sqrt(c(a));
+ a[0] && (a[0] /= b), a[1] && (a[1] /= b)
+ }
+
+ function c(a) {return a[0] * a[0] + a[1] * a[1]}
+
+ b.add = function (a, b, c, d, e, f) {
+ var g = [
+ [],
+ [],
+ []
+ ], h = [
+ [this.a, this.c, this.e],
+ [this.b, this.d, this.f],
+ [0, 0, 1]
+ ], i = [
+ [a, c, e],
+ [b, d, f],
+ [0, 0, 1]
+ ], j, k, l, m;
+ a && a instanceof cb && (i = [
+ [a.a, a.c, a.e],
+ [a.b, a.d, a.f],
+ [0, 0, 1]
+ ]);
+ for (j = 0; j < 3; j++)for (k = 0; k < 3; k++) {
+ m = 0;
+ for (l = 0; l < 3; l++)m += h[j][l] * i[l][k];
+ g[j][k] = m
+ }
+ this.a = g[0][0], this.b = g[1][0], this.c = g[0][1], this.d = g[1][1], this.e = g[0][2], this.f = g[1][2]
+ }, b.invert = function () {
+ var a = this, b = a.a * a.d - a.b * a.c;
+ return new cb(a.d / b, -a.b / b, -a.c / b, a.a / b, (a.c * a.f - a.d * a.e) / b, (a.b * a.e - a.a * a.f) / b)
+ }, b.clone = function () {return new cb(this.a, this.b, this.c, this.d, this.e, this.f)}, b.translate = function (a, b) {this.add(1, 0, 0, 1, a, b)}, b.scale = function (a, b, c, d) {b == null && (b = a), (c || d) && this.add(1, 0, 0, 1, c, d), this.add(a, 0, 0, b, 0, 0), (c || d) && this.add(1, 0, 0, 1, -c, -d)}, b.rotate = function (b, c, d) {
+ b = a.rad(b), c = c || 0, d = d || 0;
+ var e = +w.cos(b).toFixed(9), f = +w.sin(b).toFixed(9);
+ this.add(e, f, -f, e, c, d), this.add(1, 0, 0, 1, -c, -d)
+ }, b.x = function (a, b) {return a * this.a + b * this.c + this.e}, b.y = function (a, b) {return a * this.b + b * this.d + this.f}, b.get = function (a) {return+this[r.fromCharCode(97 + a)].toFixed(4)}, b.toString = function () {return a.svg ? "matrix(" + [this.get(0), this.get(1), this.get(2), this.get(3), this.get(4), this.get(5)].join() + ")" : [this.get(0), this.get(2), this.get(1), this.get(3), 0, 0].join()}, b.toFilter = function () {return"progid:DXImageTransform.Microsoft.Matrix(M11=" + this.get(0) + ", M12=" + this.get(2) + ", M21=" + this.get(1) + ", M22=" + this.get(3) + ", Dx=" + this.get(4) + ", Dy=" + this.get(5) + ", sizingmethod='auto expand')"}, b.offset = function () {return[this.e.toFixed(4), this.f.toFixed(4)]}, b.split = function () {
+ var b = {};
+ b.dx = this.e, b.dy = this.f;
+ var e = [
+ [this.a, this.c],
+ [this.b, this.d]
+ ];
+ b.scalex = w.sqrt(c(e[0])), d(e[0]), b.shear = e[0][0] * e[1][0] + e[0][1] * e[1][1], e[1] = [e[1][0] - e[0][0] * b.shear, e[1][1] - e[0][1] * b.shear], b.scaley = w.sqrt(c(e[1])), d(e[1]), b.shear /= b.scaley;
+ var f = -e[0][1], g = e[1][1];
+ g < 0 ? (b.rotate = a.deg(w.acos(g)), f < 0 && (b.rotate = 360 - b.rotate)) : b.rotate = a.deg(w.asin(f)), b.isSimple = !+b.shear.toFixed(9) && (b.scalex.toFixed(9) == b.scaley.toFixed(9) || !b.rotate), b.isSuperSimple = !+b.shear.toFixed(9) && b.scalex.toFixed(9) == b.scaley.toFixed(9) && !b.rotate, b.noRotation = !+b.shear.toFixed(9) && !b.rotate;
+ return b
+ }, b.toTransformString = function (a) {
+ var b = a || this[s]();
+ if (b.isSimple) {
+ b.scalex = +b.scalex.toFixed(4), b.scaley = +b.scaley.toFixed(4), b.rotate = +b.rotate.toFixed(4);
+ return(b.dx || b.dy ? "t" + [b.dx, b.dy] : p) + (b.scalex != 1 || b.scaley != 1 ? "s" + [b.scalex, b.scaley, 0, 0] : p) + (b.rotate ? "r" + [b.rotate, 0, 0] : p)
+ }
+ return"m" + [this.get(0), this.get(1), this.get(2), this.get(3), this.get(4), this.get(5)]
+ }
+ }(cb.prototype);
+ var cc = navigator.userAgent.match(/Version\/(.*?)\s/) || navigator.userAgent.match(/Chrome\/(\d+)/);
+ navigator.vendor == "Apple Computer, Inc." && (cc && cc[1] < 4 || navigator.platform.slice(0, 2) == "iP") || navigator.vendor == "Google Inc." && cc && cc[1] < 8 ? k.safari = function () {
+ var a = this.rect(-99, -99, this.width + 99, this.height + 99).attr({stroke: "none"});
+ setTimeout(function () {a.remove()})
+ } : k.safari = be;
+ var cd = function () {this.returnValue = !1}, ce = function () {return this.originalEvent.preventDefault()}, cf = function () {this.cancelBubble = !0}, cg = function () {return this.originalEvent.stopPropagation()}, ch = function () {
+ if (h.doc.addEventListener)return function (a, b, c, d) {
+ var e = o && u[b] ? u[b] : b, f = function (e) {
+ var f = h.doc.documentElement.scrollTop || h.doc.body.scrollTop, i = h.doc.documentElement.scrollLeft || h.doc.body.scrollLeft, j = e.clientX + i, k = e.clientY + f;
+ if (o && u[g](b))for (var l = 0, m = e.targetTouches && e.targetTouches.length; l < m; l++)if (e.targetTouches[l].target == a) {
+ var n = e;
+ e = e.targetTouches[l], e.originalEvent = n, e.preventDefault = ce, e.stopPropagation = cg;
+ break
+ }
+ return c.call(d, e, j, k)
+ };
+ a.addEventListener(e, f, !1);
+ return function () {
+ a.removeEventListener(e, f, !1);
+ return!0
+ }
+ };
+ if (h.doc.attachEvent)return function (a, b, c, d) {
+ var e = function (a) {
+ a = a || h.win.event;
+ var b = h.doc.documentElement.scrollTop || h.doc.body.scrollTop, e = h.doc.documentElement.scrollLeft || h.doc.body.scrollLeft, f = a.clientX + e, g = a.clientY + b;
+ a.preventDefault = a.preventDefault || cd, a.stopPropagation = a.stopPropagation || cf;
+ return c.call(d, a, f, g)
+ };
+ a.attachEvent("on" + b, e);
+ var f = function () {
+ a.detachEvent("on" + b, e);
+ return!0
+ };
+ return f
+ }
+ }(), ci = [], cj = function (a) {
+ var b = a.clientX, c = a.clientY, d = h.doc.documentElement.scrollTop || h.doc.body.scrollTop, e = h.doc.documentElement.scrollLeft || h.doc.body.scrollLeft, f, g = ci.length;
+ while (g--) {
+ f = ci[g];
+ if (o) {
+ var i = a.touches.length, j;
+ while (i--) {
+ j = a.touches[i];
+ if (j.identifier == f.el._drag.id) {
+ b = j.clientX, c = j.clientY, (a.originalEvent ? a.originalEvent : a).preventDefault();
+ break
+ }
+ }
+ } else a.preventDefault();
+ var k = f.el.node, l, m = k.nextSibling, n = k.parentNode, p = k.style.display;
+ h.win.opera && n.removeChild(k), k.style.display = "none", l = f.el.paper.getElementByPoint(b, c), k.style.display = p, h.win.opera && (m ? n.insertBefore(k, m) : n.appendChild(k)), l && eve("raphael.drag.over." + f.el.id, f.el, l), b += e, c += d, eve("raphael.drag.move." + f.el.id, f.move_scope || f.el, b - f.el._drag.x, c - f.el._drag.y, b, c, a)
+ }
+ }, ck = function (b) {
+ a.unmousemove(cj).unmouseup(ck);
+ var c = ci.length, d;
+ while (c--)d = ci[c], d.el._drag = {}, eve("raphael.drag.end." + d.el.id, d.end_scope || d.start_scope || d.move_scope || d.el, b);
+ ci = []
+ }, cl = a.el = {};
+ for (var cm = t.length; cm--;)(function (b) {
+ a[b] = cl[b] = function (c, d) {
+ a.is(c, "function") && (this.events = this.events || [], this.events.push({name: b, f: c, unbind: ch(this.shape || this.node || h.doc, b, c, d || this)}));
+ return this
+ }, a["un" + b] = cl["un" + b] = function (a) {
+ var c = this.events || [], d = c.length;
+ while (d--)if (c[d].name == b && c[d].f == a) {
+ c[d].unbind(), c.splice(d, 1), !c.length && delete this.events;
+ return this
+ }
+ return this
+ }
+ })(t[cm]);
+ cl.data = function (b, c) {
+ var d = bb[this.id] = bb[this.id] || {};
+ if (arguments.length == 1) {
+ if (a.is(b, "object")) {
+ for (var e in b)b[g](e) && this.data(e, b[e]);
+ return this
+ }
+ eve("raphael.data.get." + this.id, this, d[b], b);
+ return d[b]
+ }
+ d[b] = c, eve("raphael.data.set." + this.id, this, c, b);
+ return this
+ }, cl.removeData = function (a) {
+ a == null ? bb[this.id] = {} : bb[this.id] && delete bb[this.id][a];
+ return this
+ }, cl.hover = function (a, b, c, d) {return this.mouseover(a, c).mouseout(b, d || c)}, cl.unhover = function (a, b) {return this.unmouseover(a).unmouseout(b)};
+ var cn = [];
+ cl.drag = function (b, c, d, e, f, g) {
+ function i(i) {
+ (i.originalEvent || i).preventDefault();
+ var j = h.doc.documentElement.scrollTop || h.doc.body.scrollTop, k = h.doc.documentElement.scrollLeft || h.doc.body.scrollLeft;
+ this._drag.x = i.clientX + k, this._drag.y = i.clientY + j, this._drag.id = i.identifier, !ci.length && a.mousemove(cj).mouseup(ck), ci.push({el: this, move_scope: e, start_scope: f, end_scope: g}), c && eve.on("raphael.drag.start." + this.id, c), b && eve.on("raphael.drag.move." + this.id, b), d && eve.on("raphael.drag.end." + this.id, d), eve("raphael.drag.start." + this.id, f || e || this, i.clientX + k, i.clientY + j, i)
+ }
+
+ this._drag = {}, cn.push({el: this, start: i}), this.mousedown(i);
+ return this
+ }, cl.onDragOver = function (a) {a ? eve.on("raphael.drag.over." + this.id, a) : eve.unbind("raphael.drag.over." + this.id)}, cl.undrag = function () {
+ var b = cn.length;
+ while (b--)cn[b].el == this && (this.unmousedown(cn[b].start), cn.splice(b, 1), eve.unbind("raphael.drag.*." + this.id));
+ !cn.length && a.unmousemove(cj).unmouseup(ck)
+ }, k.circle = function (b, c, d) {
+ var e = a._engine.circle(this, b || 0, c || 0, d || 0);
+ this.__set__ && this.__set__.push(e);
+ return e
+ }, k.rect = function (b, c, d, e, f) {
+ var g = a._engine.rect(this, b || 0, c || 0, d || 0, e || 0, f || 0);
+ this.__set__ && this.__set__.push(g);
+ return g
+ }, k.ellipse = function (b, c, d, e) {
+ var f = a._engine.ellipse(this, b || 0, c || 0, d || 0, e || 0);
+ this.__set__ && this.__set__.push(f);
+ return f
+ }, k.path = function (b) {
+ b && !a.is(b, D) && !a.is(b[0], E) && (b += p);
+ var c = a._engine.path(a.format[m](a, arguments), this);
+ this.__set__ && this.__set__.push(c);
+ return c
+ }, k.image = function (b, c, d, e, f) {
+ var g = a._engine.image(this, b || "about:blank", c || 0, d || 0, e || 0, f || 0);
+ this.__set__ && this.__set__.push(g);
+ return g
+ }, k.text = function (b, c, d) {
+ var e = a._engine.text(this, b || 0, c || 0, r(d));
+ this.__set__ && this.__set__.push(e);
+ return e
+ }, k.set = function (b) {
+ !a.is(b, "array") && (b = Array.prototype.splice.call(arguments, 0, arguments.length));
+ var c = new cG(b);
+ this.__set__ && this.__set__.push(c);
+ return c
+ }, k.setStart = function (a) {this.__set__ = a || this.set()}, k.setFinish = function (a) {
+ var b = this.__set__;
+ delete this.__set__;
+ return b
+ }, k.setSize = function (b, c) {return a._engine.setSize.call(this, b, c)}, k.setViewBox = function (b, c, d, e, f) {return a._engine.setViewBox.call(this, b, c, d, e, f)}, k.top = k.bottom = null, k.raphael = a;
+ var co = function (a) {
+ var b = a.getBoundingClientRect(), c = a.ownerDocument, d = c.body, e = c.documentElement, f = e.clientTop || d.clientTop || 0, g = e.clientLeft || d.clientLeft || 0, i = b.top + (h.win.pageYOffset || e.scrollTop || d.scrollTop) - f, j = b.left + (h.win.pageXOffset || e.scrollLeft || d.scrollLeft) - g;
+ return{y: i, x: j}
+ };
+ k.getElementByPoint = function (a, b) {
+ var c = this, d = c.canvas, e = h.doc.elementFromPoint(a, b);
+ if (h.win.opera && e.tagName == "svg") {
+ var f = co(d), g = d.createSVGRect();
+ g.x = a - f.x, g.y = b - f.y, g.width = g.height = 1;
+ var i = d.getIntersectionList(g, null);
+ i.length && (e = i[i.length - 1])
+ }
+ if (!e)return null;
+ while (e.parentNode && e != d.parentNode && !e.raphael)e = e.parentNode;
+ e == c.canvas.parentNode && (e = d), e = e && e.raphael ? c.getById(e.raphaelid) : null;
+ return e
+ }, k.getById = function (a) {
+ var b = this.bottom;
+ while (b) {
+ if (b.id == a)return b;
+ b = b.next
+ }
+ return null
+ }, k.forEach = function (a, b) {
+ var c = this.bottom;
+ while (c) {
+ if (a.call(b, c) === !1)return this;
+ c = c.next
+ }
+ return this
+ }, k.getElementsByPoint = function (a, b) {
+ var c = this.set();
+ this.forEach(function (d) {d.isPointInside(a, b) && c.push(d)});
+ return c
+ }, cl.isPointInside = function (b, c) {
+ var d = this.realPath = this.realPath || bi[this.type](this);
+ return a.isPointInsidePath(d, b, c)
+ }, cl.getBBox = function (a) {
+ if (this.removed)return{};
+ var b = this._;
+ if (a) {
+ if (b.dirty || !b.bboxwt)this.realPath = bi[this.type](this), b.bboxwt = bI(this.realPath), b.bboxwt.toString = cq, b.dirty = 0;
+ return b.bboxwt
+ }
+ if (b.dirty || b.dirtyT || !b.bbox) {
+ if (b.dirty || !this.realPath)b.bboxwt = 0, this.realPath = bi[this.type](this);
+ b.bbox = bI(bj(this.realPath, this.matrix)), b.bbox.toString = cq, b.dirty = b.dirtyT = 0
+ }
+ return b.bbox
+ }, cl.clone = function () {
+ if (this.removed)return null;
+ var a = this.paper[this.type]().attr(this.attr());
+ this.__set__ && this.__set__.push(a);
+ return a
+ }, cl.glow = function (a) {
+ if (this.type == "text")return null;
+ a = a || {};
+ var b = {width: (a.width || 10) + (+this.attr("stroke-width") || 1), fill: a.fill || !1, opacity: a.opacity || .5, offsetx: a.offsetx || 0, offsety: a.offsety || 0, color: a.color || "#000"}, c = b.width / 2, d = this.paper, e = d.set(), f = this.realPath || bi[this.type](this);
+ f = this.matrix ? bj(f, this.matrix) : f;
+ for (var g = 1; g < c + 1; g++)e.push(d.path(f).attr({stroke: b.color, fill: b.fill ? b.color : "none", "stroke-linejoin": "round", "stroke-linecap": "round", "stroke-width": +(b.width / c * g).toFixed(3), opacity: +(b.opacity / c).toFixed(3)}));
+ return e.insertBefore(this).translate(b.offsetx, b.offsety)
+ };
+ var cr = {}, cs = function (b, c, d, e, f, g, h, i, j) {return j == null ? bB(b, c, d, e, f, g, h, i) : a.findDotsAtSegment(b, c, d, e, f, g, h, i, bC(b, c, d, e, f, g, h, i, j))}, ct = function (b, c) {
+ return function (d, e, f) {
+ d = bR(d);
+ var g, h, i, j, k = "", l = {}, m, n = 0;
+ for (var o = 0, p = d.length; o < p; o++) {
+ i = d[o];
+ if (i[0] == "M")g = +i[1], h = +i[2]; else {
+ j = cs(g, h, i[1], i[2], i[3], i[4], i[5], i[6]);
+ if (n + j > e) {
+ if (c && !l.start) {
+ m = cs(g, h, i[1], i[2], i[3], i[4], i[5], i[6], e - n), k += ["C" + m.start.x, m.start.y, m.m.x, m.m.y, m.x, m.y];
+ if (f)return k;
+ l.start = k, k = ["M" + m.x, m.y + "C" + m.n.x, m.n.y, m.end.x, m.end.y, i[5], i[6]].join(), n += j, g = +i[5], h = +i[6];
+ continue
+ }
+ if (!b && !c) {
+ m = cs(g, h, i[1], i[2], i[3], i[4], i[5], i[6], e - n);
+ return{x: m.x, y: m.y, alpha: m.alpha}
+ }
+ }
+ n += j, g = +i[5], h = +i[6]
+ }
+ k += i.shift() + i
+ }
+ l.end = k, m = b ? n : c ? l : a.findDotsAtSegment(g, h, i[0], i[1], i[2], i[3], i[4], i[5], 1), m.alpha && (m = {x: m.x, y: m.y, alpha: m.alpha});
+ return m
+ }
+ }, cu = ct(1), cv = ct(), cw = ct(0, 1);
+ a.getTotalLength = cu, a.getPointAtLength = cv, a.getSubpath = function (a, b, c) {
+ if (this.getTotalLength(a) - c < 1e-6)return cw(a, b).end;
+ var d = cw(a, c, 1);
+ return b ? cw(d, b).end : d
+ }, cl.getTotalLength = function () {
+ if (this.type == "path") {
+ if (this.node.getTotalLength)return this.node.getTotalLength();
+ return cu(this.attrs.path)
+ }
+ }, cl.getPointAtLength = function (a) {if (this.type == "path")return cv(this.attrs.path, a)}, cl.getSubpath = function (b, c) {if (this.type == "path")return a.getSubpath(this.attrs.path, b, c)};
+ var cx = a.easing_formulas = {linear: function (a) {return a}, "<": function (a) {return A(a, 1.7)}, ">": function (a) {return A(a, .48)}, "<>": function (a) {
+ var b = .48 - a / 1.04, c = w.sqrt(.1734 + b * b), d = c - b, e = A(z(d), 1 / 3) * (d < 0 ? -1 : 1), f = -c - b, g = A(z(f), 1 / 3) * (f < 0 ? -1 : 1), h = e + g + .5;
+ return(1 - h) * 3 * h * h + h * h * h
+ }, backIn: function (a) {
+ var b = 1.70158;
+ return a * a * ((b + 1) * a - b)
+ }, backOut: function (a) {
+ a = a - 1;
+ var b = 1.70158;
+ return a * a * ((b + 1) * a + b) + 1
+ }, elastic: function (a) {
+ if (a == !!a)return a;
+ return A(2, -10 * a) * w.sin((a - .075) * 2 * B / .3) + 1
+ }, bounce: function (a) {
+ var b = 7.5625, c = 2.75, d;
+ a < 1 / c ? d = b * a * a : a < 2 / c ? (a -= 1.5 / c, d = b * a * a + .75) : a < 2.5 / c ? (a -= 2.25 / c, d = b * a * a + .9375) : (a -= 2.625 / c, d = b * a * a + .984375);
+ return d
+ }};
+ cx.easeIn = cx["ease-in"] = cx["<"], cx.easeOut = cx["ease-out"] = cx[">"], cx.easeInOut = cx["ease-in-out"] = cx["<>"], cx["back-in"] = cx.backIn, cx["back-out"] = cx.backOut;
+ var cy = [], cz = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function (a) {setTimeout(a, 16)}, cA = function () {
+ var b = +(new Date), c = 0;
+ for (; c < cy.length; c++) {
+ var d = cy[c];
+ if (d.el.removed || d.paused)continue;
+ var e = b - d.start, f = d.ms, h = d.easing, i = d.from, j = d.diff, k = d.to, l = d.t, m = d.el, o = {}, p, r = {}, s;
+ d.initstatus ? (e = (d.initstatus * d.anim.top - d.prev) / (d.percent - d.prev) * f, d.status = d.initstatus, delete d.initstatus, d.stop && cy.splice(c--, 1)) : d.status = (d.prev + (d.percent - d.prev) * (e / f)) / d.anim.top;
+ if (e < 0)continue;
+ if (e < f) {
+ var t = h(e / f);
+ for (var u in i)if (i[g](u)) {
+ switch (U[u]) {
+ case C:
+ p = +i[u] + t * f * j[u];
+ break;
+ case"colour":
+ p = "rgb(" + [cB(O(i[u].r + t * f * j[u].r)), cB(O(i[u].g + t * f * j[u].g)), cB(O(i[u].b + t * f * j[u].b))].join(",") + ")";
+ break;
+ case"path":
+ p = [];
+ for (var v = 0, w = i[u].length; v < w; v++) {
+ p[v] = [i[u][v][0]];
+ for (var x = 1, y = i[u][v].length; x < y; x++)p[v][x] = +i[u][v][x] + t * f * j[u][v][x];
+ p[v] = p[v].join(q)
+ }
+ p = p.join(q);
+ break;
+ case"transform":
+ if (j[u].real) {
+ p = [];
+ for (v = 0, w = i[u].length; v < w; v++) {
+ p[v] = [i[u][v][0]];
+ for (x = 1, y = i[u][v].length; x < y; x++)p[v][x] = i[u][v][x] + t * f * j[u][v][x]
+ }
+ } else {
+ var z = function (a) {return+i[u][a] + t * f * j[u][a]};
+ p = [
+ ["m", z(0), z(1), z(2), z(3), z(4), z(5)]
+ ]
+ }
+ break;
+ case"csv":
+ if (u == "clip-rect") {
+ p = [], v = 4;
+ while (v--)p[v] = +i[u][v] + t * f * j[u][v]
+ }
+ break;
+ default:
+ var A = [][n](i[u]);
+ p = [], v = m.paper.customAttributes[u].length;
+ while (v--)p[v] = +A[v] + t * f * j[u][v]
+ }
+ o[u] = p
+ }
+ m.attr(o), function (a, b, c) {setTimeout(function () {eve("raphael.anim.frame." + a, b, c)})}(m.id, m, d.anim)
+ } else {
+ (function (b, c, d) {setTimeout(function () {eve("raphael.anim.frame." + c.id, c, d), eve("raphael.anim.finish." + c.id, c, d), a.is(b, "function") && b.call(c)})})(d.callback, m, d.anim), m.attr(k), cy.splice(c--, 1);
+ if (d.repeat > 1 && !d.next) {
+ for (s in k)k[g](s) && (r[s] = d.totalOrigin[s]);
+ d.el.attr(r), cE(d.anim, d.el, d.anim.percents[0], null, d.totalOrigin, d.repeat - 1)
+ }
+ d.next && !d.stop && cE(d.anim, d.el, d.next, null, d.totalOrigin, d.repeat)
+ }
+ }
+ a.svg && m && m.paper && m.paper.safari(), cy.length && cz(cA)
+ }, cB = function (a) {return a > 255 ? 255 : a < 0 ? 0 : a};
+ cl.animateWith = function (b, c, d, e, f, g) {
+ var h = this;
+ if (h.removed) {
+ g && g.call(h);
+ return h
+ }
+ var i = d instanceof cD ? d : a.animation(d, e, f, g), j, k;
+ cE(i, h, i.percents[0], null, h.attr());
+ for (var l = 0, m = cy.length; l < m; l++)if (cy[l].anim == c && cy[l].el == b) {
+ cy[m - 1].start = cy[l].start;
+ break
+ }
+ return h
+ }, cl.onAnimation = function (a) {
+ a ? eve.on("raphael.anim.frame." + this.id, a) : eve.unbind("raphael.anim.frame." + this.id);
+ return this
+ }, cD.prototype.delay = function (a) {
+ var b = new cD(this.anim, this.ms);
+ b.times = this.times, b.del = +a || 0;
+ return b
+ }, cD.prototype.repeat = function (a) {
+ var b = new cD(this.anim, this.ms);
+ b.del = this.del, b.times = w.floor(x(a, 0)) || 1;
+ return b
+ }, a.animation = function (b, c, d, e) {
+ if (b instanceof cD)return b;
+ if (a.is(d, "function") || !d)e = e || d || null, d = null;
+ b = Object(b), c = +c || 0;
+ var f = {}, h, i;
+ for (i in b)b[g](i) && Q(i) != i && Q(i) + "%" != i && (h = !0, f[i] = b[i]);
+ if (!h)return new cD(b, c);
+ d && (f.easing = d), e && (f.callback = e);
+ return new cD({100: f}, c)
+ }, cl.animate = function (b, c, d, e) {
+ var f = this;
+ if (f.removed) {
+ e && e.call(f);
+ return f
+ }
+ var g = b instanceof cD ? b : a.animation(b, c, d, e);
+ cE(g, f, g.percents[0], null, f.attr());
+ return f
+ }, cl.setTime = function (a, b) {
+ a && b != null && this.status(a, y(b, a.ms) / a.ms);
+ return this
+ }, cl.status = function (a, b) {
+ var c = [], d = 0, e, f;
+ if (b != null) {
+ cE(a, this, -1, y(b, 1));
+ return this
+ }
+ e = cy.length;
+ for (; d < e; d++) {
+ f = cy[d];
+ if (f.el.id == this.id && (!a || f.anim == a)) {
+ if (a)return f.status;
+ c.push({anim: f.anim, status: f.status})
+ }
+ }
+ if (a)return 0;
+ return c
+ }, cl.pause = function (a) {
+ for (var b = 0; b < cy.length; b++)cy[b].el.id == this.id && (!a || cy[b].anim == a) && eve("raphael.anim.pause." + this.id, this, cy[b].anim) !== !1 && (cy[b].paused = !0);
+ return this
+ }, cl.resume = function (a) {
+ for (var b = 0; b < cy.length; b++)if (cy[b].el.id == this.id && (!a || cy[b].anim == a)) {
+ var c = cy[b];
+ eve("raphael.anim.resume." + this.id, this, c.anim) !== !1 && (delete c.paused, this.status(c.anim, c.status))
+ }
+ return this
+ }, cl.stop = function (a) {
+ for (var b = 0; b < cy.length; b++)cy[b].el.id == this.id && (!a || cy[b].anim == a) && eve("raphael.anim.stop." + this.id, this, cy[b].anim) !== !1 && cy.splice(b--, 1);
+ return this
+ }, eve.on("raphael.remove", cF), eve.on("raphael.clear", cF), cl.toString = function () {return"Raphaël’s object"};
+ var cG = function (a) {
+ this.items = [], this.length = 0, this.type = "set";
+ if (a)for (var b = 0, c = a.length; b < c; b++)a[b] && (a[b].constructor == cl.constructor || a[b].constructor == cG) && (this[this.items.length] = this.items[this.items.length] = a[b], this.length++)
+ }, cH = cG.prototype;
+ cH.push = function () {
+ var a, b;
+ for (var c = 0, d = arguments.length; c < d; c++)a = arguments[c], a && (a.constructor == cl.constructor || a.constructor == cG) && (b = this.items.length, this[b] = this.items[b] = a, this.length++);
+ return this
+ }, cH.pop = function () {
+ this.length && delete this[this.length--];
+ return this.items.pop()
+ }, cH.forEach = function (a, b) {
+ for (var c = 0, d = this.items.length; c < d; c++)if (a.call(b, this.items[c], c) === !1)return this;
+ return this
+ };
+ for (var cI in cl)cl[g](cI) && (cH[cI] = function (a) {
+ return function () {
+ var b = arguments;
+ return this.forEach(function (c) {c[a][m](c, b)})
+ }
+ }(cI));
+ cH.attr = function (b, c) {
+ if (b && a.is(b, E) && a.is(b[0], "object"))for (var d = 0, e = b.length; d < e; d++)this.items[d].attr(b[d]); else for (var f = 0, g = this.items.length; f < g; f++)this.items[f].attr(b, c);
+ return this
+ }, cH.clear = function () {while (this.length)this.pop()}, cH.splice = function (a, b, c) {
+ a = a < 0 ? x(this.length + a, 0) : a, b = x(0, y(this.length - a, b));
+ var d = [], e = [], f = [], g;
+ for (g = 2; g < arguments.length; g++)f.push(arguments[g]);
+ for (g = 0; g < b; g++)e.push(this[a + g]);
+ for (; g < this.length - a; g++)d.push(this[a + g]);
+ var h = f.length;
+ for (g = 0; g < h + d.length; g++)this.items[a + g] = this[a + g] = g < h ? f[g] : d[g - h];
+ g = this.items.length = this.length -= b - h;
+ while (this[g])delete this[g++];
+ return new cG(e)
+ }, cH.exclude = function (a) {
+ for (var b = 0, c = this.length; b < c; b++)if (this[b] == a) {
+ this.splice(b, 1);
+ return!0
+ }
+ }, cH.animate = function (b, c, d, e) {
+ (a.is(d, "function") || !d) && (e = d || null);
+ var f = this.items.length, g = f, h, i = this, j;
+ if (!f)return this;
+ e && (j = function () {!--f && e.call(i)}), d = a.is(d, D) ? d : j;
+ var k = a.animation(b, c, d, j);
+ h = this.items[--g].animate(k);
+ while (g--)this.items[g] && !this.items[g].removed && this.items[g].animateWith(h, k, k);
+ return this
+ }, cH.insertAfter = function (a) {
+ var b = this.items.length;
+ while (b--)this.items[b].insertAfter(a);
+ return this
+ }, cH.getBBox = function () {
+ var a = [], b = [], c = [], d = [];
+ for (var e = this.items.length; e--;)if (!this.items[e].removed) {
+ var f = this.items[e].getBBox();
+ a.push(f.x), b.push(f.y), c.push(f.x + f.width), d.push(f.y + f.height)
+ }
+ a = y[m](0, a), b = y[m](0, b), c = x[m](0, c), d = x[m](0, d);
+ return{x: a, y: b, x2: c, y2: d, width: c - a, height: d - b}
+ }, cH.clone = function (a) {
+ a = new cG;
+ for (var b = 0, c = this.items.length; b < c; b++)a.push(this.items[b].clone());
+ return a
+ }, cH.toString = function () {return"Raphaël‘s set"}, a.registerFont = function (a) {
+ if (!a.face)return a;
+ this.fonts = this.fonts || {};
+ var b = {w: a.w, face: {}, glyphs: {}}, c = a.face["font-family"];
+ for (var d in a.face)a.face[g](d) && (b.face[d] = a.face[d]);
+ this.fonts[c] ? this.fonts[c].push(b) : this.fonts[c] = [b];
+ if (!a.svg) {
+ b.face["units-per-em"] = R(a.face["units-per-em"], 10);
+ for (var e in a.glyphs)if (a.glyphs[g](e)) {
+ var f = a.glyphs[e];
+ b.glyphs[e] = {w: f.w, k: {}, d: f.d && "M" + f.d.replace(/[mlcxtrv]/g, function (a) {return{l: "L", c: "C", x: "z", t: "m", r: "l", v: "c"}[a] || "M"}) + "z"};
+ if (f.k)for (var h in f.k)f[g](h) && (b.glyphs[e].k[h] = f.k[h])
+ }
+ }
+ return a
+ }, k.getFont = function (b, c, d, e) {
+ e = e || "normal", d = d || "normal", c = +c || {normal: 400, bold: 700, lighter: 300, bolder: 800}[c] || 400;
+ if (!!a.fonts) {
+ var f = a.fonts[b];
+ if (!f) {
+ var h = new RegExp("(^|\\s)" + b.replace(/[^\w\d\s+!~.:_-]/g, p) + "(\\s|$)", "i");
+ for (var i in a.fonts)if (a.fonts[g](i) && h.test(i)) {
+ f = a.fonts[i];
+ break
+ }
+ }
+ var j;
+ if (f)for (var k = 0, l = f.length; k < l; k++) {
+ j = f[k];
+ if (j.face["font-weight"] == c && (j.face["font-style"] == d || !j.face["font-style"]) && j.face["font-stretch"] == e)break
+ }
+ return j
+ }
+ }, k.print = function (b, d, e, f, g, h, i) {
+ h = h || "middle", i = x(y(i || 0, 1), -1);
+ var j = r(e)[s](p), k = 0, l = 0, m = p, n;
+ a.is(f, e) && (f = this.getFont(f));
+ if (f) {
+ n = (g || 16) / f.face["units-per-em"];
+ var o = f.face.bbox[s](c), q = +o[0], t = o[3] - o[1], u = 0, v = +o[1] + (h == "baseline" ? t + +f.face.descent : t / 2);
+ for (var w = 0, z = j.length; w < z; w++) {
+ if (j[w] == "\n")k = 0, B = 0, l = 0, u += t; else {
+ var A = l && f.glyphs[j[w - 1]] || {}, B = f.glyphs[j[w]];
+ k += l ? (A.w || f.w) + (A.k && A.k[j[w]] || 0) + f.w * i : 0, l = 1
+ }
+ B && B.d && (m += a.transformPath(B.d, ["t", k * n, u * n, "s", n, n, q, v, "t", (b - q) / n, (d - v) / n]))
+ }
+ }
+ return this.path(m).attr({fill: "#000", stroke: "none"})
+ }, k.add = function (b) {
+ if (a.is(b, "array")) {
+ var c = this.set(), e = 0, f = b.length, h;
+ for (; e < f; e++)h = b[e] || {}, d[g](h.type) && c.push(this[h.type]().attr(h))
+ }
+ return c
+ }, a.format = function (b, c) {
+ var d = a.is(c, E) ? [0][n](c) : arguments;
+ b && a.is(b, D) && d.length - 1 && (b = b.replace(e, function (a, b) {return d[++b] == null ? p : d[b]}));
+ return b || p
+ }, a.fullfill = function () {
+ var a = /\{([^\}]+)\}/g, b = /(?:(?:^|\.)(.+?)(?=\[|\.|$|\()|\[('|")(.+?)\2\])(\(\))?/g, c = function (a, c, d) {
+ var e = d;
+ c.replace(b, function (a, b, c, d, f) {b = b || d, e && (b in e && (e = e[b]), typeof e == "function" && f && (e = e()))}), e = (e == null || e == d ? a : e) + "";
+ return e
+ };
+ return function (b, d) {return String(b).replace(a, function (a, b) {return c(a, b, d)})}
+ }(), a.ninja = function () {
+ i.was ? h.win.Raphael = i.is : delete Raphael;
+ return a
+ }, a.st = cH, function (b, c, d) {
+ function e() {/in/.test(b.readyState) ? setTimeout(e, 9) : a.eve("raphael.DOMload")}
+
+ b.readyState == null && b.addEventListener && (b.addEventListener(c, d = function () {b.removeEventListener(c, d, !1), b.readyState = "complete"}, !1), b.readyState = "loading"), e()
+ }(document, "DOMContentLoaded"), i.was ? h.win.Raphael = a : Raphael = a, eve.on("raphael.DOMload", function () {b = !0})
+}(), window.Raphael.svg && function (a) {
+ var b = "hasOwnProperty", c = String, d = parseFloat, e = parseInt, f = Math, g = f.max, h = f.abs, i = f.pow, j = /[, ]+/, k = a.eve, l = "", m = " ", n = "http://www.w3.org/1999/xlink", o = {block: "M5,0 0,2.5 5,5z", classic: "M5,0 0,2.5 5,5 3.5,3 3.5,2z", diamond: "M2.5,0 5,2.5 2.5,5 0,2.5z", open: "M6,1 1,3.5 6,6", oval: "M2.5,0A2.5,2.5,0,0,1,2.5,5 2.5,2.5,0,0,1,2.5,0z"}, p = {};
+ a.toString = function () {return"Your browser supports SVG.\nYou are running Raphaël " + this.version};
+ var q = function (d, e) {
+ if (e) {
+ typeof d == "string" && (d = q(d));
+ for (var f in e)e[b](f) && (f.substring(0, 6) == "xlink:" ? d.setAttributeNS(n, f.substring(6), c(e[f])) : d.setAttribute(f, c(e[f])))
+ } else d = a._g.doc.createElementNS("http://www.w3.org/2000/svg", d), d.style && (d.style.webkitTapHighlightColor = "rgba(0,0,0,0)");
+ return d
+ }, r = function (b, e) {
+ var j = "linear", k = b.id + e, m = .5, n = .5, o = b.node, p = b.paper, r = o.style, s = a._g.doc.getElementById(k);
+ if (!s) {
+ e = c(e).replace(a._radial_gradient, function (a, b, c) {
+ j = "radial";
+ if (b && c) {
+ m = d(b), n = d(c);
+ var e = (n > .5) * 2 - 1;
+ i(m - .5, 2) + i(n - .5, 2) > .25 && (n = f.sqrt(.25 - i(m - .5, 2)) * e + .5) && n != .5 && (n = n.toFixed(5) - 1e-5 * e)
+ }
+ return l
+ }), e = e.split(/\s*\-\s*/);
+ if (j == "linear") {
+ var t = e.shift();
+ t = -d(t);
+ if (isNaN(t))return null;
+ var u = [0, 0, f.cos(a.rad(t)), f.sin(a.rad(t))], v = 1 / (g(h(u[2]), h(u[3])) || 1);
+ u[2] *= v, u[3] *= v, u[2] < 0 && (u[0] = -u[2], u[2] = 0), u[3] < 0 && (u[1] = -u[3], u[3] = 0)
+ }
+ var w = a._parseDots(e);
+ if (!w)return null;
+ k = k.replace(/[\(\)\s,\xb0#]/g, "_"), b.gradient && k != b.gradient.id && (p.defs.removeChild(b.gradient), delete b.gradient);
+ if (!b.gradient) {
+ s = q(j + "Gradient", {id: k}), b.gradient = s, q(s, j == "radial" ? {fx: m, fy: n} : {x1: u[0], y1: u[1], x2: u[2], y2: u[3], gradientTransform: b.matrix.invert()}), p.defs.appendChild(s);
+ for (var x = 0, y = w.length; x < y; x++)s.appendChild(q("stop", {offset: w[x].offset ? w[x].offset : x ? "100%" : "0%", "stop-color": w[x].color || "#fff"}))
+ }
+ }
+ q(o, {fill: "url(#" + k + ")", opacity: 1, "fill-opacity": 1}), r.fill = l, r.opacity = 1, r.fillOpacity = 1;
+ return 1
+ }, s = function (a) {
+ var b = a.getBBox(1);
+ q(a.pattern, {patternTransform: a.matrix.invert() + " translate(" + b.x + "," + b.y + ")"})
+ }, t = function (d, e, f) {
+ if (d.type == "path") {
+ var g = c(e).toLowerCase().split("-"), h = d.paper, i = f ? "end" : "start", j = d.node, k = d.attrs, m = k["stroke-width"], n = g.length, r = "classic", s, t, u, v, w, x = 3, y = 3, z = 5;
+ while (n--)switch (g[n]) {
+ case"block":
+ case"classic":
+ case"oval":
+ case"diamond":
+ case"open":
+ case"none":
+ r = g[n];
+ break;
+ case"wide":
+ y = 5;
+ break;
+ case"narrow":
+ y = 2;
+ break;
+ case"long":
+ x = 5;
+ break;
+ case"short":
+ x = 2
+ }
+ r == "open" ? (x += 2, y += 2, z += 2, u = 1, v = f ? 4 : 1, w = {fill: "none", stroke: k.stroke}) : (v = u = x / 2, w = {fill: k.stroke, stroke: "none"}), d._.arrows ? f ? (d._.arrows.endPath && p[d._.arrows.endPath]--, d._.arrows.endMarker && p[d._.arrows.endMarker]--) : (d._.arrows.startPath && p[d._.arrows.startPath]--, d._.arrows.startMarker && p[d._.arrows.startMarker]--) : d._.arrows = {};
+ if (r != "none") {
+ var A = "raphael-marker-" + r, B = "raphael-marker-" + i + r + x + y;
+ a._g.doc.getElementById(A) ? p[A]++ : (h.defs.appendChild(q(q("path"), {"stroke-linecap": "round", d: o[r], id: A})), p[A] = 1);
+ var C = a._g.doc.getElementById(B), D;
+ C ? (p[B]++, D = C.getElementsByTagName("use")[0]) : (C = q(q("marker"), {id: B, markerHeight: y, markerWidth: x, orient: "auto", refX: v, refY: y / 2}), D = q(q("use"), {"xlink:href": "#" + A, transform: (f ? "rotate(180 " + x / 2 + " " + y / 2 + ") " : l) + "scale(" + x / z + "," + y / z + ")", "stroke-width": (1 / ((x / z + y / z) / 2)).toFixed(4)}), C.appendChild(D), h.defs.appendChild(C), p[B] = 1), q(D, w);
+ var F = u * (r != "diamond" && r != "oval");
+ f ? (s = d._.arrows.startdx * m || 0, t = a.getTotalLength(k.path) - F * m) : (s = F * m, t = a.getTotalLength(k.path) - (d._.arrows.enddx * m || 0)), w = {}, w["marker-" + i] = "url(#" + B + ")";
+ if (t || s)w.d = Raphael.getSubpath(k.path, s, t);
+ q(j, w), d._.arrows[i + "Path"] = A, d._.arrows[i + "Marker"] = B, d._.arrows[i + "dx"] = F, d._.arrows[i + "Type"] = r, d._.arrows[i + "String"] = e
+ } else f ? (s = d._.arrows.startdx * m || 0, t = a.getTotalLength(k.path) - s) : (s = 0, t = a.getTotalLength(k.path) - (d._.arrows.enddx * m || 0)), d._.arrows[i + "Path"] && q(j, {d: Raphael.getSubpath(k.path, s, t)}), delete d._.arrows[i + "Path"], delete d._.arrows[i + "Marker"], delete d._.arrows[i + "dx"], delete d._.arrows[i + "Type"], delete d._.arrows[i + "String"];
+ for (w in p)if (p[b](w) && !p[w]) {
+ var G = a._g.doc.getElementById(w);
+ G && G.parentNode.removeChild(G)
+ }
+ }
+ }, u = {"": [0], none: [0], "-": [3, 1], ".": [1, 1], "-.": [3, 1, 1, 1], "-..": [3, 1, 1, 1, 1, 1], ". ": [1, 3], "- ": [4, 3], "--": [8, 3], "- .": [4, 3, 1, 3], "--.": [8, 3, 1, 3], "--..": [8, 3, 1, 3, 1, 3]}, v = function (a, b, d) {
+ b = u[c(b).toLowerCase()];
+ if (b) {
+ var e = a.attrs["stroke-width"] || "1", f = {round: e, square: e, butt: 0}[a.attrs["stroke-linecap"] || d["stroke-linecap"]] || 0, g = [], h = b.length;
+ while (h--)g[h] = b[h] * e + (h % 2 ? 1 : -1) * f;
+ q(a.node, {"stroke-dasharray": g.join(",")})
+ }
+ }, w = function (d, f) {
+ var i = d.node, k = d.attrs, m = i.style.visibility;
+ i.style.visibility = "hidden";
+ for (var o in f)if (f[b](o)) {
+ if (!a._availableAttrs[b](o))continue;
+ var p = f[o];
+ k[o] = p;
+ switch (o) {
+ case"blur":
+ d.blur(p);
+ break;
+ case"href":
+ case"title":
+ case"target":
+ var u = i.parentNode;
+ if (u.tagName.toLowerCase() != "a") {
+ var w = q("a");
+ u.insertBefore(w, i), w.appendChild(i), u = w
+ }
+ o == "target" ? u.setAttributeNS(n, "show", p == "blank" ? "new" : p) : u.setAttributeNS(n, o, p);
+ break;
+ case"cursor":
+ i.style.cursor = p;
+ break;
+ case"transform":
+ d.transform(p);
+ break;
+ case"arrow-start":
+ t(d, p);
+ break;
+ case"arrow-end":
+ t(d, p, 1);
+ break;
+ case"clip-rect":
+ var x = c(p).split(j);
+ if (x.length == 4) {
+ d.clip && d.clip.parentNode.parentNode.removeChild(d.clip.parentNode);
+ var z = q("clipPath"), A = q("rect");
+ z.id = a.createUUID(), q(A, {x: x[0], y: x[1], width: x[2], height: x[3]}), z.appendChild(A), d.paper.defs.appendChild(z), q(i, {"clip-path": "url(#" + z.id + ")"}), d.clip = A
+ }
+ if (!p) {
+ var B = i.getAttribute("clip-path");
+ if (B) {
+ var C = a._g.doc.getElementById(B.replace(/(^url\(#|\)$)/g, l));
+ C && C.parentNode.removeChild(C), q(i, {"clip-path": l}), delete d.clip
+ }
+ }
+ break;
+ case"path":
+ d.type == "path" && (q(i, {d: p ? k.path = a._pathToAbsolute(p) : "M0,0"}), d._.dirty = 1, d._.arrows && ("startString"in d._.arrows && t(d, d._.arrows.startString), "endString"in d._.arrows && t(d, d._.arrows.endString, 1)));
+ break;
+ case"width":
+ i.setAttribute(o, p), d._.dirty = 1;
+ if (k.fx)o = "x", p = k.x; else break;
+ case"x":
+ k.fx && (p = -k.x - (k.width || 0));
+ case"rx":
+ if (o == "rx" && d.type == "rect")break;
+ case"cx":
+ i.setAttribute(o, p), d.pattern && s(d), d._.dirty = 1;
+ break;
+ case"height":
+ i.setAttribute(o, p), d._.dirty = 1;
+ if (k.fy)o = "y", p = k.y; else break;
+ case"y":
+ k.fy && (p = -k.y - (k.height || 0));
+ case"ry":
+ if (o == "ry" && d.type == "rect")break;
+ case"cy":
+ i.setAttribute(o, p), d.pattern && s(d), d._.dirty = 1;
+ break;
+ case"r":
+ d.type == "rect" ? q(i, {rx: p, ry: p}) : i.setAttribute(o, p), d._.dirty = 1;
+ break;
+ case"src":
+ d.type == "image" && i.setAttributeNS(n, "href", p);
+ break;
+ case"stroke-width":
+ if (d._.sx != 1 || d._.sy != 1)p /= g(h(d._.sx), h(d._.sy)) || 1;
+ d.paper._vbSize && (p *= d.paper._vbSize), i.setAttribute(o, p), k["stroke-dasharray"] && v(d, k["stroke-dasharray"], f), d._.arrows && ("startString"in d._.arrows && t(d, d._.arrows.startString), "endString"in d._.arrows && t(d, d._.arrows.endString, 1));
+ break;
+ case"stroke-dasharray":
+ v(d, p, f);
+ break;
+ case"fill":
+ var D = c(p).match(a._ISURL);
+ if (D) {
+ z = q("pattern");
+ var F = q("image");
+ z.id = a.createUUID(), q(z, {x: 0, y: 0, patternUnits: "userSpaceOnUse", height: 1, width: 1}), q(F, {x: 0, y: 0, "xlink:href": D[1]}), z.appendChild(F), function (b) {
+ a._preload(D[1], function () {
+ var a = this.offsetWidth, c = this.offsetHeight;
+ q(b, {width: a, height: c}), q(F, {width: a, height: c}), d.paper.safari()
+ })
+ }(z), d.paper.defs.appendChild(z), q(i, {fill: "url(#" + z.id + ")"}), d.pattern = z, d.pattern && s(d);
+ break
+ }
+ var G = a.getRGB(p);
+ if (!G.error)delete f.gradient, delete k.gradient, !a.is(k.opacity, "undefined") && a.is(f.opacity, "undefined") && q(i, {opacity: k.opacity}), !a.is(k["fill-opacity"], "undefined") && a.is(f["fill-opacity"], "undefined") && q(i, {"fill-opacity": k["fill-opacity"]}); else if ((d.type == "circle" || d.type == "ellipse" || c(p).charAt() != "r") && r(d, p)) {
+ if ("opacity"in k || "fill-opacity"in k) {
+ var H = a._g.doc.getElementById(i.getAttribute("fill").replace(/^url\(#|\)$/g, l));
+ if (H) {
+ var I = H.getElementsByTagName("stop");
+ q(I[I.length - 1], {"stop-opacity": ("opacity"in k ? k.opacity : 1) * ("fill-opacity"in k ? k["fill-opacity"] : 1)})
+ }
+ }
+ k.gradient = p, k.fill = "none";
+ break
+ }
+ G[b]("opacity") && q(i, {"fill-opacity": G.opacity > 1 ? G.opacity / 100 : G.opacity});
+ case"stroke":
+ G = a.getRGB(p), i.setAttribute(o, G.hex), o == "stroke" && G[b]("opacity") && q(i, {"stroke-opacity": G.opacity > 1 ? G.opacity / 100 : G.opacity}), o == "stroke" && d._.arrows && ("startString"in d._.arrows && t(d, d._.arrows.startString), "endString"in d._.arrows && t(d, d._.arrows.endString, 1));
+ break;
+ case"gradient":
+ (d.type == "circle" || d.type == "ellipse" || c(p).charAt() != "r") && r(d, p);
+ break;
+ case"opacity":
+ k.gradient && !k[b]("stroke-opacity") && q(i, {"stroke-opacity": p > 1 ? p / 100 : p});
+ case"fill-opacity":
+ if (k.gradient) {
+ H = a._g.doc.getElementById(i.getAttribute("fill").replace(/^url\(#|\)$/g, l)), H && (I = H.getElementsByTagName("stop"), q(I[I.length - 1], {"stop-opacity": p}));
+ break
+ }
+ ;
+ default:
+ o == "font-size" && (p = e(p, 10) + "px");
+ var J = o.replace(/(\-.)/g, function (a) {return a.substring(1).toUpperCase()});
+ i.style[J] = p, d._.dirty = 1, i.setAttribute(o, p)
+ }
+ }
+ y(d, f), i.style.visibility = m
+ }, x = 1.2, y = function (d, f) {
+ if (d.type == "text" && !!(f[b]("text") || f[b]("font") || f[b]("font-size") || f[b]("x") || f[b]("y"))) {
+ var g = d.attrs, h = d.node, i = h.firstChild ? e(a._g.doc.defaultView.getComputedStyle(h.firstChild, l).getPropertyValue("font-size"), 10) : 10;
+ if (f[b]("text")) {
+ g.text = f.text;
+ while (h.firstChild)h.removeChild(h.firstChild);
+ var j = c(f.text).split("\n"), k = [], m;
+ for (var n = 0, o = j.length; n < o; n++)m = q("tspan"), n && q(m, {dy: i * x, x: g.x}), m.appendChild(a._g.doc.createTextNode(j[n])), h.appendChild(m), k[n] = m
+ } else {
+ k = h.getElementsByTagName("tspan");
+ for (n = 0, o = k.length; n < o; n++)n ? q(k[n], {dy: i * x, x: g.x}) : q(k[0], {dy: 0})
+ }
+ q(h, {x: g.x, y: g.y}), d._.dirty = 1;
+ var p = d._getBBox(), r = g.y - (p.y + p.height / 2);
+ r && a.is(r, "finite") && q(k[0], {dy: r})
+ }
+ }, z = function (b, c) {
+ var d = 0, e = 0;
+ this[0] = this.node = b, b.raphael = !0, this.id = a._oid++, b.raphaelid = this.id, this.matrix = a.matrix(), this.realPath = null, this.paper = c, this.attrs = this.attrs || {}, this._ = {transform: [], sx: 1, sy: 1, deg: 0, dx: 0, dy: 0, dirty: 1}, !c.bottom && (c.bottom = this), this.prev = c.top, c.top && (c.top.next = this), c.top = this, this.next = null
+ }, A = a.el;
+ z.prototype = A, A.constructor = z, a._engine.path = function (a, b) {
+ var c = q("path");
+ b.canvas && b.canvas.appendChild(c);
+ var d = new z(c, b);
+ d.type = "path", w(d, {fill: "none", stroke: "#000", path: a});
+ return d
+ }, A.rotate = function (a, b, e) {
+ if (this.removed)return this;
+ a = c(a).split(j), a.length - 1 && (b = d(a[1]), e = d(a[2])), a = d(a[0]), e == null && (b = e);
+ if (b == null || e == null) {
+ var f = this.getBBox(1);
+ b = f.x + f.width / 2, e = f.y + f.height / 2
+ }
+ this.transform(this._.transform.concat([
+ ["r", a, b, e]
+ ]));
+ return this
+ }, A.scale = function (a, b, e, f) {
+ if (this.removed)return this;
+ a = c(a).split(j), a.length - 1 && (b = d(a[1]), e = d(a[2]), f = d(a[3])), a = d(a[0]), b == null && (b = a), f == null && (e = f);
+ if (e == null || f == null)var g = this.getBBox(1);
+ e = e == null ? g.x + g.width / 2 : e, f = f == null ? g.y + g.height / 2 : f, this.transform(this._.transform.concat([
+ ["s", a, b, e, f]
+ ]));
+ return this
+ }, A.translate = function (a, b) {
+ if (this.removed)return this;
+ a = c(a).split(j), a.length - 1 && (b = d(a[1])), a = d(a[0]) || 0, b = +b || 0, this.transform(this._.transform.concat([
+ ["t", a, b]
+ ]));
+ return this
+ }, A.transform = function (c) {
+ var d = this._;
+ if (c == null)return d.transform;
+ a._extractTransform(this, c), this.clip && q(this.clip, {transform: this.matrix.invert()}), this.pattern && s(this), this.node && q(this.node, {transform: this.matrix});
+ if (d.sx != 1 || d.sy != 1) {
+ var e = this.attrs[b]("stroke-width") ? this.attrs["stroke-width"] : 1;
+ this.attr({"stroke-width": e})
+ }
+ return this
+ }, A.hide = function () {
+ !this.removed && this.paper.safari(this.node.style.display = "none");
+ return this
+ }, A.show = function () {
+ !this.removed && this.paper.safari(this.node.style.display = "");
+ return this
+ }, A.remove = function () {
+ if (!this.removed && !!this.node.parentNode) {
+ var b = this.paper;
+ b.__set__ && b.__set__.exclude(this), k.unbind("raphael.*.*." + this.id), this.gradient && b.defs.removeChild(this.gradient), a._tear(this, b), this.node.parentNode.tagName.toLowerCase() == "a" ? this.node.parentNode.parentNode.removeChild(this.node.parentNode) : this.node.parentNode.removeChild(this.node);
+ for (var c in this)this[c] = typeof this[c] == "function" ? a._removedFactory(c) : null;
+ this.removed = !0
+ }
+ }, A._getBBox = function () {
+ if (this.node.style.display == "none") {
+ this.show();
+ var a = !0
+ }
+ var b = {};
+ try {b = this.node.getBBox()} catch (c) {} finally {b = b || {}}
+ a && this.hide();
+ return b
+ }, A.attr = function (c, d) {
+ if (this.removed)return this;
+ if (c == null) {
+ var e = {};
+ for (var f in this.attrs)this.attrs[b](f) && (e[f] = this.attrs[f]);
+ e.gradient && e.fill == "none" && (e.fill = e.gradient) && delete e.gradient, e.transform = this._.transform;
+ return e
+ }
+ if (d == null && a.is(c, "string")) {
+ if (c == "fill" && this.attrs.fill == "none" && this.attrs.gradient)return this.attrs.gradient;
+ if (c == "transform")return this._.transform;
+ var g = c.split(j), h = {};
+ for (var i = 0, l = g.length; i < l; i++)c = g[i], c in this.attrs ? h[c] = this.attrs[c] : a.is(this.paper.customAttributes[c], "function") ? h[c] = this.paper.customAttributes[c].def : h[c] = a._availableAttrs[c];
+ return l - 1 ? h : h[g[0]]
+ }
+ if (d == null && a.is(c, "array")) {
+ h = {};
+ for (i = 0, l = c.length; i < l; i++)h[c[i]] = this.attr(c[i]);
+ return h
+ }
+ if (d != null) {
+ var m = {};
+ m[c] = d
+ } else c != null && a.is(c, "object") && (m = c);
+ for (var n in m)k("raphael.attr." + n + "." + this.id, this, m[n]);
+ for (n in this.paper.customAttributes)if (this.paper.customAttributes[b](n) && m[b](n) && a.is(this.paper.customAttributes[n], "function")) {
+ var o = this.paper.customAttributes[n].apply(this, [].concat(m[n]));
+ this.attrs[n] = m[n];
+ for (var p in o)o[b](p) && (m[p] = o[p])
+ }
+ w(this, m);
+ return this
+ }, A.toFront = function () {
+ if (this.removed)return this;
+ this.node.parentNode.tagName.toLowerCase() == "a" ? this.node.parentNode.parentNode.appendChild(this.node.parentNode) : this.node.parentNode.appendChild(this.node);
+ var b = this.paper;
+ b.top != this && a._tofront(this, b);
+ return this
+ }, A.toBack = function () {
+ if (this.removed)return this;
+ var b = this.node.parentNode;
+ b.tagName.toLowerCase() == "a" ? b.parentNode.insertBefore(this.node.parentNode, this.node.parentNode.parentNode.firstChild) : b.firstChild != this.node && b.insertBefore(this.node, this.node.parentNode.firstChild), a._toback(this, this.paper);
+ var c = this.paper;
+ return this
+ }, A.insertAfter = function (b) {
+ if (this.removed)return this;
+ var c = b.node || b[b.length - 1].node;
+ c.nextSibling ? c.parentNode.insertBefore(this.node, c.nextSibling) : c.parentNode.appendChild(this.node), a._insertafter(this, b, this.paper);
+ return this
+ }, A.insertBefore = function (b) {
+ if (this.removed)return this;
+ var c = b.node || b[0].node;
+ c.parentNode.insertBefore(this.node, c), a._insertbefore(this, b, this.paper);
+ return this
+ }, A.blur = function (b) {
+ var c = this;
+ if (+b !== 0) {
+ var d = q("filter"), e = q("feGaussianBlur");
+ c.attrs.blur = b, d.id = a.createUUID(), q(e, {stdDeviation: +b || 1.5}), d.appendChild(e), c.paper.defs.appendChild(d), c._blur = d, q(c.node, {filter: "url(#" + d.id + ")"})
+ } else c._blur && (c._blur.parentNode.removeChild(c._blur), delete c._blur, delete c.attrs.blur), c.node.removeAttribute("filter")
+ }, a._engine.circle = function (a, b, c, d) {
+ var e = q("circle");
+ a.canvas && a.canvas.appendChild(e);
+ var f = new z(e, a);
+ f.attrs = {cx: b, cy: c, r: d, fill: "none", stroke: "#000"}, f.type = "circle", q(e, f.attrs);
+ return f
+ }, a._engine.rect = function (a, b, c, d, e, f) {
+ var g = q("rect");
+ a.canvas && a.canvas.appendChild(g);
+ var h = new z(g, a);
+ h.attrs = {x: b, y: c, width: d, height: e, r: f || 0, rx: f || 0, ry: f || 0, fill: "none", stroke: "#000"}, h.type = "rect", q(g, h.attrs);
+ return h
+ }, a._engine.ellipse = function (a, b, c, d, e) {
+ var f = q("ellipse");
+ a.canvas && a.canvas.appendChild(f);
+ var g = new z(f, a);
+ g.attrs = {cx: b, cy: c, rx: d, ry: e, fill: "none", stroke: "#000"}, g.type = "ellipse", q(f, g.attrs);
+ return g
+ }, a._engine.image = function (a, b, c, d, e, f) {
+ var g = q("image");
+ q(g, {x: c, y: d, width: e, height: f, preserveAspectRatio: "none"}), g.setAttributeNS(n, "href", b), a.canvas && a.canvas.appendChild(g);
+ var h = new z(g, a);
+ h.attrs = {x: c, y: d, width: e, height: f, src: b}, h.type = "image";
+ return h
+ }, a._engine.text = function (b, c, d, e) {
+ var f = q("text");
+ b.canvas && b.canvas.appendChild(f);
+ var g = new z(f, b);
+ g.attrs = {x: c, y: d, "text-anchor": "middle", text: e, font: a._availableAttrs.font, stroke: "none", fill: "#000"}, g.type = "text", w(g, g.attrs);
+ return g
+ }, a._engine.setSize = function (a, b) {
+ this.width = a || this.width, this.height = b || this.height, this.canvas.setAttribute("width", this.width), this.canvas.setAttribute("height", this.height), this._viewBox && this.setViewBox.apply(this, this._viewBox);
+ return this
+ }, a._engine.create = function () {
+ var b = a._getContainer.apply(0, arguments), c = b && b.container, d = b.x, e = b.y, f = b.width, g = b.height;
+ if (!c)throw new Error("SVG container not found.");
+ var h = q("svg"), i = "overflow:hidden;", j;
+ d = d || 0, e = e || 0, f = f || 512, g = g || 342, q(h, {height: g, version: 1.1, width: f, xmlns: "http://www.w3.org/2000/svg"}), c == 1 ? (h.style.cssText = i + "position:absolute;left:" + d + "px;top:" + e + "px", a._g.doc.body.appendChild(h), j = 1) : (h.style.cssText = i + "position:relative", c.firstChild ? c.insertBefore(h, c.firstChild) : c.appendChild(h)), c = new a._Paper, c.width = f, c.height = g, c.canvas = h, c.clear(), c._left = c._top = 0, j && (c.renderfix = function () {}), c.renderfix();
+ return c
+ }, a._engine.setViewBox = function (a, b, c, d, e) {
+ k("raphael.setViewBox", this, this._viewBox, [a, b, c, d, e]);
+ var f = g(c / this.width, d / this.height), h = this.top, i = e ? "meet" : "xMinYMin", j, l;
+ a == null ? (this._vbSize && (f = 1), delete this._vbSize, j = "0 0 " + this.width + m + this.height) : (this._vbSize = f, j = a + m + b + m + c + m + d), q(this.canvas, {viewBox: j, preserveAspectRatio: i});
+ while (f && h)l = "stroke-width"in h.attrs ? h.attrs["stroke-width"] : 1, h.attr({"stroke-width": l}), h._.dirty = 1, h._.dirtyT = 1, h = h.prev;
+ this._viewBox = [a, b, c, d, !!e];
+ return this
+ }, a.prototype.renderfix = function () {
+ var a = this.canvas, b = a.style, c;
+ try {c = a.getScreenCTM() || a.createSVGMatrix()} catch (d) {c = a.createSVGMatrix()}
+ var e = -c.e % 1, f = -c.f % 1;
+ if (e || f)e && (this._left = (this._left + e) % 1, b.left = this._left + "px"), f && (this._top = (this._top + f) % 1, b.top = this._top + "px")
+ }, a.prototype.clear = function () {
+ a.eve("raphael.clear", this);
+ var b = this.canvas;
+ while (b.firstChild)b.removeChild(b.firstChild);
+ this.bottom = this.top = null, (this.desc = q("desc")).appendChild(a._g.doc.createTextNode("Created with Raphaël " + a.version)), b.appendChild(this.desc), b.appendChild(this.defs = q("defs"))
+ }, a.prototype.remove = function () {
+ k("raphael.remove", this), this.canvas.parentNode && this.canvas.parentNode.removeChild(this.canvas);
+ for (var b in this)this[b] = typeof this[b] == "function" ? a._removedFactory(b) : null
+ };
+ var B = a.st;
+ for (var C in A)A[b](C) && !B[b](C) && (B[C] = function (a) {
+ return function () {
+ var b = arguments;
+ return this.forEach(function (c) {c[a].apply(c, b)})
+ }
+ }(C))
+}(window.Raphael), window.Raphael.vml && function (a) {
+ var b = "hasOwnProperty", c = String, d = parseFloat, e = Math, f = e.round, g = e.max, h = e.min, i = e.abs, j = "fill", k = /[, ]+/, l = a.eve, m = " progid:DXImageTransform.Microsoft", n = " ", o = "", p = {M: "m", L: "l", C: "c", Z: "x", m: "t", l: "r", c: "v", z: "x"}, q = /([clmz]),?([^clmz]*)/gi, r = / progid:\S+Blur\([^\)]+\)/g, s = /-?[^,\s-]+/g, t = "position:absolute;left:0;top:0;width:1px;height:1px", u = 21600, v = {path: 1, rect: 1, image: 1}, w = {circle: 1, ellipse: 1}, x = function (b) {
+ var d = /[ahqstv]/ig, e = a._pathToAbsolute;
+ c(b).match(d) && (e = a._path2curve), d = /[clmz]/g;
+ if (e == a._pathToAbsolute && !c(b).match(d)) {
+ var g = c(b).replace(q, function (a, b, c) {
+ var d = [], e = b.toLowerCase() == "m", g = p[b];
+ c.replace(s, function (a) {e && d.length == 2 && (g += d + p[b == "m" ? "l" : "L"], d = []), d.push(f(a * u))});
+ return g + d
+ });
+ return g
+ }
+ var h = e(b), i, j;
+ g = [];
+ for (var k = 0, l = h.length; k < l; k++) {
+ i = h[k], j = h[k][0].toLowerCase(), j == "z" && (j = "x");
+ for (var m = 1, r = i.length; m < r; m++)j += f(i[m] * u) + (m != r - 1 ? "," : o);
+ g.push(j)
+ }
+ return g.join(n)
+ }, y = function (b, c, d) {
+ var e = a.matrix();
+ e.rotate(-b, .5, .5);
+ return{dx: e.x(c, d), dy: e.y(c, d)}
+ }, z = function (a, b, c, d, e, f) {
+ var g = a._, h = a.matrix, k = g.fillpos, l = a.node, m = l.style, o = 1, p = "", q, r = u / b, s = u / c;
+ m.visibility = "hidden";
+ if (!!b && !!c) {
+ l.coordsize = i(r) + n + i(s), m.rotation = f * (b * c < 0 ? -1 : 1);
+ if (f) {
+ var t = y(f, d, e);
+ d = t.dx, e = t.dy
+ }
+ b < 0 && (p += "x"), c < 0 && (p += " y") && (o = -1), m.flip = p, l.coordorigin = d * -r + n + e * -s;
+ if (k || g.fillsize) {
+ var v = l.getElementsByTagName(j);
+ v = v && v[0], l.removeChild(v), k && (t = y(f, h.x(k[0], k[1]), h.y(k[0], k[1])), v.position = t.dx * o + n + t.dy * o), g.fillsize && (v.size = g.fillsize[0] * i(b) + n + g.fillsize[1] * i(c)), l.appendChild(v)
+ }
+ m.visibility = "visible"
+ }
+ };
+ a.toString = function () {return"Your browser doesn’t support SVG. Falling down to VML.\nYou are running Raphaël " + this.version};
+ var A = function (a, b, d) {
+ var e = c(b).toLowerCase().split("-"), f = d ? "end" : "start", g = e.length, h = "classic", i = "medium", j = "medium";
+ while (g--)switch (e[g]) {
+ case"block":
+ case"classic":
+ case"oval":
+ case"diamond":
+ case"open":
+ case"none":
+ h = e[g];
+ break;
+ case"wide":
+ case"narrow":
+ j = e[g];
+ break;
+ case"long":
+ case"short":
+ i = e[g]
+ }
+ var k = a.node.getElementsByTagName("stroke")[0];
+ k[f + "arrow"] = h, k[f + "arrowlength"] = i, k[f + "arrowwidth"] = j
+ }, B = function (e, i) {
+ e.attrs = e.attrs || {};
+ var l = e.node, m = e.attrs, p = l.style, q, r = v[e.type] && (i.x != m.x || i.y != m.y || i.width != m.width || i.height != m.height || i.cx != m.cx || i.cy != m.cy || i.rx != m.rx || i.ry != m.ry || i.r != m.r), s = w[e.type] && (m.cx != i.cx || m.cy != i.cy || m.r != i.r || m.rx != i.rx || m.ry != i.ry), t = e;
+ for (var y in i)i[b](y) && (m[y] = i[y]);
+ r && (m.path = a._getPath[e.type](e), e._.dirty = 1), i.href && (l.href = i.href), i.title && (l.title = i.title), i.target && (l.target = i.target), i.cursor && (p.cursor = i.cursor), "blur"in i && e.blur(i.blur);
+ if (i.path && e.type == "path" || r)l.path = x(~c(m.path).toLowerCase().indexOf("r") ? a._pathToAbsolute(m.path) : m.path), e.type == "image" && (e._.fillpos = [m.x, m.y], e._.fillsize = [m.width, m.height], z(e, 1, 1, 0, 0, 0));
+ "transform"in i && e.transform(i.transform);
+ if (s) {
+ var B = +m.cx, D = +m.cy, E = +m.rx || +m.r || 0, G = +m.ry || +m.r || 0;
+ l.path = a.format("ar{0},{1},{2},{3},{4},{1},{4},{1}x", f((B - E) * u), f((D - G) * u), f((B + E) * u), f((D + G) * u), f(B * u))
+ }
+ if ("clip-rect"in i) {
+ var H = c(i["clip-rect"]).split(k);
+ if (H.length == 4) {
+ H[2] = +H[2] + +H[0], H[3] = +H[3] + +H[1];
+ var I = l.clipRect || a._g.doc.createElement("div"), J = I.style;
+ J.clip = a.format("rect({1}px {2}px {3}px {0}px)", H), l.clipRect || (J.position = "absolute", J.top = 0, J.left = 0, J.width = e.paper.width + "px", J.height = e.paper.height + "px", l.parentNode.insertBefore(I, l), I.appendChild(l), l.clipRect = I)
+ }
+ i["clip-rect"] || l.clipRect && (l.clipRect.style.clip = "auto")
+ }
+ if (e.textpath) {
+ var K = e.textpath.style;
+ i.font && (K.font = i.font), i["font-family"] && (K.fontFamily = '"' + i["font-family"].split(",")[0].replace(/^['"]+|['"]+$/g, o) + '"'), i["font-size"] && (K.fontSize = i["font-size"]), i["font-weight"] && (K.fontWeight = i["font-weight"]), i["font-style"] && (K.fontStyle = i["font-style"])
+ }
+ "arrow-start"in i && A(t, i["arrow-start"]), "arrow-end"in i && A(t, i["arrow-end"], 1);
+ if (i.opacity != null || i["stroke-width"] != null || i.fill != null || i.src != null || i.stroke != null || i["stroke-width"] != null || i["stroke-opacity"] != null || i["fill-opacity"] != null || i["stroke-dasharray"] != null || i["stroke-miterlimit"] != null || i["stroke-linejoin"] != null || i["stroke-linecap"] != null) {
+ var L = l.getElementsByTagName(j), M = !1;
+ L = L && L[0], !L && (M = L = F(j)), e.type == "image" && i.src && (L.src = i.src), i.fill && (L.on = !0);
+ if (L.on == null || i.fill == "none" || i.fill === null)L.on = !1;
+ if (L.on && i.fill) {
+ var N = c(i.fill).match(a._ISURL);
+ if (N) {
+ L.parentNode == l && l.removeChild(L), L.rotate = !0, L.src = N[1], L.type = "tile";
+ var O = e.getBBox(1);
+ L.position = O.x + n + O.y, e._.fillpos = [O.x, O.y], a._preload(N[1], function () {e._.fillsize = [this.offsetWidth, this.offsetHeight]})
+ } else L.color = a.getRGB(i.fill).hex, L.src = o, L.type = "solid", a.getRGB(i.fill).error && (t.type in{circle: 1, ellipse: 1} || c(i.fill).charAt() != "r") && C(t, i.fill, L) && (m.fill = "none", m.gradient = i.fill, L.rotate = !1)
+ }
+ if ("fill-opacity"in i || "opacity"in i) {
+ var P = ((+m["fill-opacity"] + 1 || 2) - 1) * ((+m.opacity + 1 || 2) - 1) * ((+a.getRGB(i.fill).o + 1 || 2) - 1);
+ P = h(g(P, 0), 1), L.opacity = P, L.src && (L.color = "none")
+ }
+ l.appendChild(L);
+ var Q = l.getElementsByTagName("stroke") && l.getElementsByTagName("stroke")[0], T = !1;
+ !Q && (T = Q = F("stroke"));
+ if (i.stroke && i.stroke != "none" || i["stroke-width"] || i["stroke-opacity"] != null || i["stroke-dasharray"] || i["stroke-miterlimit"] || i["stroke-linejoin"] || i["stroke-linecap"])Q.on = !0;
+ (i.stroke == "none" || i.stroke === null || Q.on == null || i.stroke == 0 || i["stroke-width"] == 0) && (Q.on = !1);
+ var U = a.getRGB(i.stroke);
+ Q.on && i.stroke && (Q.color = U.hex), P = ((+m["stroke-opacity"] + 1 || 2) - 1) * ((+m.opacity + 1 || 2) - 1) * ((+U.o + 1 || 2) - 1);
+ var V = (d(i["stroke-width"]) || 1) * .75;
+ P = h(g(P, 0), 1), i["stroke-width"] == null && (V = m["stroke-width"]), i["stroke-width"] && (Q.weight = V), V && V < 1 && (P *= V) && (Q.weight = 1), Q.opacity = P, i["stroke-linejoin"] && (Q.joinstyle = i["stroke-linejoin"] || "miter"), Q.miterlimit = i["stroke-miterlimit"] || 8, i["stroke-linecap"] && (Q.endcap = i["stroke-linecap"] == "butt" ? "flat" : i["stroke-linecap"] == "square" ? "square" : "round");
+ if (i["stroke-dasharray"]) {
+ var W = {"-": "shortdash", ".": "shortdot", "-.": "shortdashdot", "-..": "shortdashdotdot", ". ": "dot", "- ": "dash", "--": "longdash", "- .": "dashdot", "--.": "longdashdot", "--..": "longdashdotdot"};
+ Q.dashstyle = W[b](i["stroke-dasharray"]) ? W[i["stroke-dasharray"]] : o
+ }
+ T && l.appendChild(Q)
+ }
+ if (t.type == "text") {
+ t.paper.canvas.style.display = o;
+ var X = t.paper.span, Y = 100, Z = m.font && m.font.match(/\d+(?:\.\d*)?(?=px)/);
+ p = X.style, m.font && (p.font = m.font), m["font-family"] && (p.fontFamily = m["font-family"]), m["font-weight"] && (p.fontWeight = m["font-weight"]), m["font-style"] && (p.fontStyle = m["font-style"]), Z = d(m["font-size"] || Z && Z[0]) || 10, p.fontSize = Z * Y + "px", t.textpath.string && (X.innerHTML = c(t.textpath.string).replace(/</g, "&#60;").replace(/&/g, "&#38;").replace(/\n/g, "<br>"));
+ var $ = X.getBoundingClientRect();
+ t.W = m.w = ($.right - $.left) / Y, t.H = m.h = ($.bottom - $.top) / Y, t.X = m.x, t.Y = m.y + t.H / 2, ("x"in i || "y"in i) && (t.path.v = a.format("m{0},{1}l{2},{1}", f(m.x * u), f(m.y * u), f(m.x * u) + 1));
+ var _ = ["x", "y", "text", "font", "font-family", "font-weight", "font-style", "font-size"];
+ for (var ba = 0, bb = _.length; ba < bb; ba++)if (_[ba]in i) {
+ t._.dirty = 1;
+ break
+ }
+ switch (m["text-anchor"]) {
+ case"start":
+ t.textpath.style["v-text-align"] = "left", t.bbx = t.W / 2;
+ break;
+ case"end":
+ t.textpath.style["v-text-align"] = "right", t.bbx = -t.W / 2;
+ break;
+ default:
+ t.textpath.style["v-text-align"] = "center", t.bbx = 0
+ }
+ t.textpath.style["v-text-kern"] = !0
+ }
+ }, C = function (b, f, g) {
+ b.attrs = b.attrs || {};
+ var h = b.attrs, i = Math.pow, j, k, l = "linear", m = ".5 .5";
+ b.attrs.gradient = f, f = c(f).replace(a._radial_gradient, function (a, b, c) {
+ l = "radial", b && c && (b = d(b), c = d(c), i(b - .5, 2) + i(c - .5, 2) > .25 && (c = e.sqrt(.25 - i(b - .5, 2)) * ((c > .5) * 2 - 1) + .5), m = b + n + c);
+ return o
+ }), f = f.split(/\s*\-\s*/);
+ if (l == "linear") {
+ var p = f.shift();
+ p = -d(p);
+ if (isNaN(p))return null
+ }
+ var q = a._parseDots(f);
+ if (!q)return null;
+ b = b.shape || b.node;
+ if (q.length) {
+ b.removeChild(g), g.on = !0, g.method = "none", g.color = q[0].color, g.color2 = q[q.length - 1].color;
+ var r = [];
+ for (var s = 0, t = q.length; s < t; s++)q[s].offset && r.push(q[s].offset + n + q[s].color);
+ g.colors = r.length ? r.join() : "0% " + g.color, l == "radial" ? (g.type = "gradientTitle", g.focus = "100%", g.focussize = "0 0", g.focusposition = m, g.angle = 0) : (g.type = "gradient", g.angle = (270 - p) % 360), b.appendChild(g)
+ }
+ return 1
+ }, D = function (b, c) {this[0] = this.node = b, b.raphael = !0, this.id = a._oid++, b.raphaelid = this.id, this.X = 0, this.Y = 0, this.attrs = {}, this.paper = c, this.matrix = a.matrix(), this._ = {transform: [], sx: 1, sy: 1, dx: 0, dy: 0, deg: 0, dirty: 1, dirtyT: 1}, !c.bottom && (c.bottom = this), this.prev = c.top, c.top && (c.top.next = this), c.top = this, this.next = null}, E = a.el;
+ D.prototype = E, E.constructor = D, E.transform = function (b) {
+ if (b == null)return this._.transform;
+ var d = this.paper._viewBoxShift, e = d ? "s" + [d.scale, d.scale] + "-1-1t" + [d.dx, d.dy] : o, f;
+ d && (f = b = c(b).replace(/\.{3}|\u2026/g, this._.transform || o)), a._extractTransform(this, e + b);
+ var g = this.matrix.clone(), h = this.skew, i = this.node, j, k = ~c(this.attrs.fill).indexOf("-"), l = !c(this.attrs.fill).indexOf("url(");
+ g.translate(-0.5, -0.5);
+ if (l || k || this.type == "image") {
+ h.matrix = "1 0 0 1", h.offset = "0 0", j = g.split();
+ if (k && j.noRotation || !j.isSimple) {
+ i.style.filter = g.toFilter();
+ var m = this.getBBox(), p = this.getBBox(1), q = m.x - p.x, r = m.y - p.y;
+ i.coordorigin = q * -u + n + r * -u, z(this, 1, 1, q, r, 0)
+ } else i.style.filter = o, z(this, j.scalex, j.scaley, j.dx, j.dy, j.rotate)
+ } else i.style.filter = o, h.matrix = c(g), h.offset = g.offset();
+ f && (this._.transform = f);
+ return this
+ }, E.rotate = function (a, b, e) {
+ if (this.removed)return this;
+ if (a != null) {
+ a = c(a).split(k), a.length - 1 && (b = d(a[1]), e = d(a[2])), a = d(a[0]), e == null && (b = e);
+ if (b == null || e == null) {
+ var f = this.getBBox(1);
+ b = f.x + f.width / 2, e = f.y + f.height / 2
+ }
+ this._.dirtyT = 1, this.transform(this._.transform.concat([
+ ["r", a, b, e]
+ ]));
+ return this
+ }
+ }, E.translate = function (a, b) {
+ if (this.removed)return this;
+ a = c(a).split(k), a.length - 1 && (b = d(a[1])), a = d(a[0]) || 0, b = +b || 0, this._.bbox && (this._.bbox.x += a, this._.bbox.y += b), this.transform(this._.transform.concat([
+ ["t", a, b]
+ ]));
+ return this
+ }, E.scale = function (a, b, e, f) {
+ if (this.removed)return this;
+ a = c(a).split(k), a.length - 1 && (b = d(a[1]), e = d(a[2]), f = d(a[3]), isNaN(e) && (e = null), isNaN(f) && (f = null)), a = d(a[0]), b == null && (b = a), f == null && (e = f);
+ if (e == null || f == null)var g = this.getBBox(1);
+ e = e == null ? g.x + g.width / 2 : e, f = f == null ? g.y + g.height / 2 : f, this.transform(this._.transform.concat([
+ ["s", a, b, e, f]
+ ])), this._.dirtyT = 1;
+ return this
+ }, E.hide = function () {
+ !this.removed && (this.node.style.display = "none");
+ return this
+ }, E.show = function () {
+ !this.removed && (this.node.style.display = o);
+ return this
+ }, E._getBBox = function () {
+ if (this.removed)return{};
+ return{x: this.X + (this.bbx || 0) - this.W / 2, y: this.Y - this.H, width: this.W, height: this.H}
+ }, E.remove = function () {
+ if (!this.removed && !!this.node.parentNode) {
+ this.paper.__set__ && this.paper.__set__.exclude(this), a.eve.unbind("raphael.*.*." + this.id), a._tear(this, this.paper), this.node.parentNode.removeChild(this.node), this.shape && this.shape.parentNode.removeChild(this.shape);
+ for (var b in this)this[b] = typeof this[b] == "function" ? a._removedFactory(b) : null;
+ this.removed = !0
+ }
+ }, E.attr = function (c, d) {
+ if (this.removed)return this;
+ if (c == null) {
+ var e = {};
+ for (var f in this.attrs)this.attrs[b](f) && (e[f] = this.attrs[f]);
+ e.gradient && e.fill == "none" && (e.fill = e.gradient) && delete e.gradient, e.transform = this._.transform;
+ return e
+ }
+ if (d == null && a.is(c, "string")) {
+ if (c == j && this.attrs.fill == "none" && this.attrs.gradient)return this.attrs.gradient;
+ var g = c.split(k), h = {};
+ for (var i = 0, m = g.length; i < m; i++)c = g[i], c in this.attrs ? h[c] = this.attrs[c] : a.is(this.paper.customAttributes[c], "function") ? h[c] = this.paper.customAttributes[c].def : h[c] = a._availableAttrs[c];
+ return m - 1 ? h : h[g[0]]
+ }
+ if (this.attrs && d == null && a.is(c, "array")) {
+ h = {};
+ for (i = 0, m = c.length; i < m; i++)h[c[i]] = this.attr(c[i]);
+ return h
+ }
+ var n;
+ d != null && (n = {}, n[c] = d), d == null && a.is(c, "object") && (n = c);
+ for (var o in n)l("raphael.attr." + o + "." + this.id, this, n[o]);
+ if (n) {
+ for (o in this.paper.customAttributes)if (this.paper.customAttributes[b](o) && n[b](o) && a.is(this.paper.customAttributes[o], "function")) {
+ var p = this.paper.customAttributes[o].apply(this, [].concat(n[o]));
+ this.attrs[o] = n[o];
+ for (var q in p)p[b](q) && (n[q] = p[q])
+ }
+ n.text && this.type == "text" && (this.textpath.string = n.text), B(this, n)
+ }
+ return this
+ }, E.toFront = function () {
+ !this.removed && this.node.parentNode.appendChild(this.node), this.paper && this.paper.top != this && a._tofront(this, this.paper);
+ return this
+ }, E.toBack = function () {
+ if (this.removed)return this;
+ this.node.parentNode.firstChild != this.node && (this.node.parentNode.insertBefore(this.node, this.node.parentNode.firstChild), a._toback(this, this.paper));
+ return this
+ }, E.insertAfter = function (b) {
+ if (this.removed)return this;
+ b.constructor == a.st.constructor && (b = b[b.length - 1]), b.node.nextSibling ? b.node.parentNode.insertBefore(this.node, b.node.nextSibling) : b.node.parentNode.appendChild(this.node), a._insertafter(this, b, this.paper);
+ return this
+ }, E.insertBefore = function (b) {
+ if (this.removed)return this;
+ b.constructor == a.st.constructor && (b = b[0]), b.node.parentNode.insertBefore(this.node, b.node), a._insertbefore(this, b, this.paper);
+ return this
+ }, E.blur = function (b) {
+ var c = this.node.runtimeStyle, d = c.filter;
+ d = d.replace(r, o), +b !== 0 ? (this.attrs.blur = b, c.filter = d + n + m + ".Blur(pixelradius=" + (+b || 1.5) + ")", c.margin = a.format("-{0}px 0 0 -{0}px", f(+b || 1.5))) : (c.filter = d, c.margin = 0, delete this.attrs.blur)
+ }, a._engine.path = function (a, b) {
+ var c = F("shape");
+ c.style.cssText = t, c.coordsize = u + n + u, c.coordorigin = b.coordorigin;
+ var d = new D(c, b), e = {fill: "none", stroke: "#000"};
+ a && (e.path = a), d.type = "path", d.path = [], d.Path = o, B(d, e), b.canvas.appendChild(c);
+ var f = F("skew");
+ f.on = !0, c.appendChild(f), d.skew = f, d.transform(o);
+ return d
+ }, a._engine.rect = function (b, c, d, e, f, g) {
+ var h = a._rectPath(c, d, e, f, g), i = b.path(h), j = i.attrs;
+ i.X = j.x = c, i.Y = j.y = d, i.W = j.width = e, i.H = j.height = f, j.r = g, j.path = h, i.type = "rect";
+ return i
+ }, a._engine.ellipse = function (a, b, c, d, e) {
+ var f = a.path(), g = f.attrs;
+ f.X = b - d, f.Y = c - e, f.W = d * 2, f.H = e * 2, f.type = "ellipse", B(f, {cx: b, cy: c, rx: d, ry: e});
+ return f
+ }, a._engine.circle = function (a, b, c, d) {
+ var e = a.path(), f = e.attrs;
+ e.X = b - d, e.Y = c - d, e.W = e.H = d * 2, e.type = "circle", B(e, {cx: b, cy: c, r: d});
+ return e
+ }, a._engine.image = function (b, c, d, e, f, g) {
+ var h = a._rectPath(d, e, f, g), i = b.path(h).attr({stroke: "none"}), k = i.attrs, l = i.node, m = l.getElementsByTagName(j)[0];
+ k.src = c, i.X = k.x = d, i.Y = k.y = e, i.W = k.width = f, i.H = k.height = g, k.path = h, i.type = "image", m.parentNode == l && l.removeChild(m), m.rotate = !0, m.src = c, m.type = "tile", i._.fillpos = [d, e], i._.fillsize = [f, g], l.appendChild(m), z(i, 1, 1, 0, 0, 0);
+ return i
+ }, a._engine.text = function (b, d, e, g) {
+ var h = F("shape"), i = F("path"), j = F("textpath");
+ d = d || 0, e = e || 0, g = g || "", i.v = a.format("m{0},{1}l{2},{1}", f(d * u), f(e * u), f(d * u) + 1), i.textpathok = !0, j.string = c(g), j.on = !0, h.style.cssText = t, h.coordsize = u + n + u, h.coordorigin = "0 0";
+ var k = new D(h, b), l = {fill: "#000", stroke: "none", font: a._availableAttrs.font, text: g};
+ k.shape = h, k.path = i, k.textpath = j, k.type = "text", k.attrs.text = c(g), k.attrs.x = d, k.attrs.y = e, k.attrs.w = 1, k.attrs.h = 1, B(k, l), h.appendChild(j), h.appendChild(i), b.canvas.appendChild(h);
+ var m = F("skew");
+ m.on = !0, h.appendChild(m), k.skew = m, k.transform(o);
+ return k
+ }, a._engine.setSize = function (b, c) {
+ var d = this.canvas.style;
+ this.width = b, this.height = c, b == +b && (b += "px"), c == +c && (c += "px"), d.width = b, d.height = c, d.clip = "rect(0 " + b + " " + c + " 0)", this._viewBox && a._engine.setViewBox.apply(this, this._viewBox);
+ return this
+ }, a._engine.setViewBox = function (b, c, d, e, f) {
+ a.eve("raphael.setViewBox", this, this._viewBox, [b, c, d, e, f]);
+ var h = this.width, i = this.height, j = 1 / g(d / h, e / i), k, l;
+ f && (k = i / e, l = h / d, d * k < h && (b -= (h - d * k) / 2 / k), e * l < i && (c -= (i - e * l) / 2 / l)), this._viewBox = [b, c, d, e, !!f], this._viewBoxShift = {dx: -b, dy: -c, scale: j}, this.forEach(function (a) {a.transform("...")});
+ return this
+ };
+ var F;
+ a._engine.initWin = function (a) {
+ var b = a.document;
+ b.createStyleSheet().addRule(".rvml", "behavior:url(#default#VML)");
+ try {!b.namespaces.rvml && b.namespaces.add("rvml", "urn:schemas-microsoft-com:vml"), F = function (a) {return b.createElement("<rvml:" + a + ' class="rvml">')}} catch (c) {F = function (a) {return b.createElement("<" + a + ' xmlns="urn:schemas-microsoft.com:vml" class="rvml">')}}
+ }, a._engine.initWin(a._g.win), a._engine.create = function () {
+ var b = a._getContainer.apply(0, arguments), c = b.container, d = b.height, e, f = b.width, g = b.x, h = b.y;
+ if (!c)throw new Error("VML container not found.");
+ var i = new a._Paper, j = i.canvas = a._g.doc.createElement("div"), k = j.style;
+ g = g || 0, h = h || 0, f = f || 512, d = d || 342, i.width = f, i.height = d, f == +f && (f += "px"), d == +d && (d += "px"), i.coordsize = u * 1e3 + n + u * 1e3, i.coordorigin = "0 0", i.span = a._g.doc.createElement("span"), i.span.style.cssText = "position:absolute;left:-9999em;top:-9999em;padding:0;margin:0;line-height:1;", j.appendChild(i.span), k.cssText = a.format("top:0;left:0;width:{0};height:{1};display:inline-block;position:relative;clip:rect(0 {0} {1} 0);overflow:hidden", f, d), c == 1 ? (a._g.doc.body.appendChild(j), k.left = g + "px", k.top = h + "px", k.position = "absolute") : c.firstChild ? c.insertBefore(j, c.firstChild) : c.appendChild(j), i.renderfix = function () {};
+ return i
+ }, a.prototype.clear = function () {a.eve("raphael.clear", this), this.canvas.innerHTML = o, this.span = a._g.doc.createElement("span"), this.span.style.cssText = "position:absolute;left:-9999em;top:-9999em;padding:0;margin:0;line-height:1;display:inline;", this.canvas.appendChild(this.span), this.bottom = this.top = null}, a.prototype.remove = function () {
+ a.eve("raphael.remove", this), this.canvas.parentNode.removeChild(this.canvas);
+ for (var b in this)this[b] = typeof this[b] == "function" ? a._removedFactory(b) : null;
+ return!0
+ };
+ var G = a.st;
+ for (var H in E)E[b](H) && !G[b](H) && (G[H] = function (a) {
+ return function () {
+ var b = arguments;
+ return this.forEach(function (c) {c[a].apply(c, b)})
+ }
+ }(H))
+}(window.Raphael); \ No newline at end of file
diff --git a/plugins/UserCountryMap/js/visitor-map.js b/plugins/UserCountryMap/js/visitor-map.js
index 1a69b5caad..190da458e3 100644
--- a/plugins/UserCountryMap/js/visitor-map.js
+++ b/plugins/UserCountryMap/js/visitor-map.js
@@ -8,14 +8,14 @@
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
-(function() {
+(function () {
// create a global namespace for UserCountryMap plugin
// this is used both by visitor map and realtime map
window.UserCountryMap = window.UserCountryMap || {};
// the main class for this widget, provides the interface for the template
- var VisitorMap = window.UserCountryMap.VisitorMap = function(config, theWidget) {
+ var VisitorMap = window.UserCountryMap.VisitorMap = function (config, theWidget) {
this.config = config;
this.theWidget = theWidget || false;
this.run();
@@ -26,7 +26,7 @@
/*
* initializes the map after widget creation
*/
- run: function() {
+ run: function () {
var self = this,
config = self.config;
@@ -76,7 +76,7 @@
function ajax(params, dataType) {
dataType = dataType || 'json';
params = $.extend({}, params);
- var token_auth = ''+params.token_auth;
+ var token_auth = '' + params.token_auth;
delete params['token_auth'];
return $.ajax({
url: 'index.php?' + $.param(params),
@@ -87,22 +87,22 @@
}
function minmax(values) {
- values = values.sort(function(a,b) { return Number(a) - Number(b); });
+ values = values.sort(function (a, b) { return Number(a) - Number(b); });
return {
min: values[0],
- max: values[values.length-1],
- median: values[Math.floor(values.length*0.5)],
- p33: values[Math.floor(values.length*0.33)],
- p66: values[Math.floor(values.length*0.66)],
- p90: values[Math.floor(values.length*0.9)]
+ max: values[values.length - 1],
+ median: values[Math.floor(values.length * 0.5)],
+ p33: values[Math.floor(values.length * 0.33)],
+ p66: values[Math.floor(values.length * 0.66)],
+ p90: values[Math.floor(values.length * 0.9)]
};
}
function formatNumber(v) {
v = Number(v);
- return v > 1000000 ? (v/1000000).toFixed(1) + 'm' :
- v > 1000 ? (v/1000).toFixed(1) + 'k' :
- v;
+ return v > 1000000 ? (v / 1000000).toFixed(1) + 'm' :
+ v > 1000 ? (v / 1000).toFixed(1) + 'k' :
+ v;
}
//
@@ -113,8 +113,8 @@
//
function formatValueForTooltips(data, metric, id) {
- var val = data[metric] % 1 === 0 || Number(data[metric]) != data[metric] ? data[metric] : data[metric].toFixed(1),
- v = _[metric].replace('%s', '<b>'+val+'</b>');
+ var val = data[metric] % 1 === 0 || Number(data[metric]) != data[metric] ? data[metric] : data[metric].toFixed(1),
+ v = _[metric].replace('%s', '<b>' + val + '</b>');
if (val == 1 && metric == 'nb_visits') v = _.one_visit;
@@ -126,17 +126,17 @@
else if (id == 'world') total = _worldTotal;
else {
total = 0;
- $.each(UserCountryMap.countriesByIso, function(iso, country) {
+ $.each(UserCountryMap.countriesByIso, function (iso, country) {
if (UserCountryMap.ISO3toCONT[iso] == id) {
total += country[metric];
}
});
}
if (total) {
- v += ' ('+formatPercentage(data[metric] / total)+')';
+ v += ' (' + formatPercentage(data[metric] / total) + ')';
}
} else if (metric == 'avg_time_on_site') {
- v += '<br/> (over '+data.nb_visits+' visits)';
+ v += '<br/> (over ' + data.nb_visits + ' visits)';
}
return v;
}
@@ -150,7 +150,7 @@
metric = $$('.userCountryMapSelectMetrics').val(),
v = formatNumber(Math.round(val)) + (metric == 'avg_time_on_site' ? first ? ' sec' : 's' : '');
d.css({ width: 17, height: 17, float: 'left', background: colscale(val) });
- l.css({ 'margin-left':20, 'line-height': '20px', 'text-align': 'right' }).html(v);
+ l.css({ 'margin-left': 20, 'line-height': '20px', 'text-align': 'right' }).html(v);
r.css({ clear: 'both', height: 19 });
r.append(d).append(l);
$('.UserCountryMap-legend .content').append(r);
@@ -158,7 +158,7 @@
var stats, values = [], id = self.lastSelected, c;
- $.each(rows, function(i, r) {
+ $.each(rows, function (i, r) {
if (!$.isFunction(filter) || filter(r)) {
var v = quantify(r, metric);
if (!isNaN(v)) values.push(v);
@@ -168,7 +168,7 @@
stats = minmax(values);
if (stats.min == stats.max) {
- colscale = function() { return chroma.hex('#CDDAEF'); };
+ colscale = function () { return chroma.hex('#CDDAEF'); };
if (choropleth) {
$('.UserCountryMap-legend .content').html('').show();
addLegendItem(stats.min, true);
@@ -184,7 +184,7 @@
if (metric == 'avg_time_on_site' || metric == 'nb_actions_per_visit' || metric == 'bounce_rate') {
if (id.length == 3) {
c = (stats.p90 - stats.min) / (stats.max - stats.min);
- colscale = chroma.scale(['#385993', '#385993','#E87500', '#E87500'], [0, c, c+0.001, 1])
+ colscale = chroma.scale(['#385993', '#385993', '#E87500', '#E87500'], [0, c, c + 0.001, 1])
.domain(chroma.limits(rows, 'c', 5, 'curMetric', filter))
.mode('hsl');
}
@@ -194,7 +194,7 @@
if (choropleth) {
$('.UserCountryMap-legend .content').html('').show();
var itemExists = {};
- $.each(chroma.limits(values, 'k', 3), function(i, v) {
+ $.each(chroma.limits(values, 'k', 3), function (i, v) {
if (itemExists[v]) return;
addLegendItem(v, i === 0);
itemExists[v] = true;
@@ -210,7 +210,7 @@
function formatPercentage(val) {
if (val < 0.001) return '< 0.1%';
- return Math.round(1000 * val)/10 + '%';
+ return Math.round(1000 * val) / 10 + '%';
}
/*
@@ -231,7 +231,7 @@
function initUserInterface() {
// react to changes of country select
- $$('.userCountryMapSelectCountry').off('change').change(function() {
+ $$('.userCountryMapSelectCountry').off('change').change(function () {
updateState($$('.userCountryMapSelectCountry').val());
});
@@ -252,13 +252,13 @@
$(window).off('resize').resize(onResizeLazy);
// enable metric changes
- $$('.userCountryMapSelectMetrics').off('change').change(function() {
+ $$('.userCountryMapSelectMetrics').off('change').change(function () {
updateState(self.lastSelected);
});
// handle city button
- (function(btn) {
- btn.off('click').click(function() {
+ (function (btn) {
+ btn.off('click').click(function () {
if (self.lastSelected.length == 3) {
if (self.mode != "city") {
self.mode = "city";
@@ -269,8 +269,8 @@
})($$('.UserCountryMap-btn-city'));
// handle region button
- (function(btn) {
- btn.off('click').click(function() {
+ (function (btn) {
+ btn.off('click').click(function () {
if (self.mode != "region") {
$$('.UserCountryMap-view-mode-buttons a').removeClass('activeIcon');
self.mode = "region";
@@ -286,11 +286,11 @@
$$('.UserCountryMap_map').append(bl);
var infobtn = $('.UserCountryMap-info-btn');
- infobtn.off('mouseenter').on('mouseenter', function(e) {
+ infobtn.off('mouseenter').on('mouseenter',function (e) {
$(infobtn.data('tooltip-target')).show();
- }).off('mouseleave').on('mouseleave', function(e) {
- $(infobtn.data('tooltip-target')).hide();
- });
+ }).off('mouseleave').on('mouseleave', function (e) {
+ $(infobtn.data('tooltip-target')).hide();
+ });
$('.UserCountryMap-tooltip').hide();
}
@@ -358,7 +358,7 @@
if (id.length == 3) {
if (UserCountryMap.countriesByIso[id]) { // we have visits in this country
flag.css({
- 'background-image': 'url('+UserCountryMap.countriesByIso[id].flag+')',
+ 'background-image': 'url(' + UserCountryMap.countriesByIso[id].flag + ')',
'background-repeat': 'no-repeat',
'background-position': '5px 5px'
});
@@ -382,16 +382,16 @@
var mapTitle = id.length == 3 ?
UserCountryMap.countriesByIso[id].name :
- $$('.userCountryMapSelectCountry option[value='+id+']').html(),
+ $$('.userCountryMapSelectCountry option[value=' + id + ']').html(),
totalVisits = 0;
// update map title
$('.map-title').html(mapTitle);
- $$('.widgetUserCountryMapvisitorMap .widgetName .map-title').html(' – '+mapTitle);
+ $$('.widgetUserCountryMapvisitorMap .widgetName .map-title').html(' – ' + mapTitle);
// update total visits for that region
if (id.length == 3) {
totalVisits = UserCountryMap.countriesByIso[id]['nb_visits'];
} else if (id.length == 2) {
- $.each(UserCountryMap.countriesByIso, function(iso, country) {
+ $.each(UserCountryMap.countriesByIso, function (iso, country) {
if (UserCountryMap.ISO3toCONT[iso] == id) {
totalVisits += country['nb_visits'];
}
@@ -404,8 +404,8 @@
$('.map-stats').html(formatValueForTooltips(UserCountryMap.countriesByIso[id], metric, 'world'));
} else {
$('.map-stats').html(
- _.nb_visits.replace('%s', '<b>'+formatNumber(totalVisits) + '</b>') +(id != 'world' ? ' ('+
- formatPercentage(totalVisits / worldTotalVisits)+')' : '')
+ _.nb_visits.replace('%s', '<b>' + formatNumber(totalVisits) + '</b>') + (id != 'world' ? ' (' +
+ formatPercentage(totalVisits / worldTotalVisits) + ')' : '')
);
}
}
@@ -422,7 +422,7 @@
// Create a chroma ColorScale for the selected metric that regards only the
// countries that are visible in the map.
- colscale = getColorScale(UserCountryMap.countryData, metric, function(r) {
+ colscale = getColorScale(UserCountryMap.countryData, metric, function (r) {
if (target.length == 2) {
return UserCountryMap.ISO3toCONT[r.iso] == target;
} else {
@@ -441,23 +441,23 @@
// Apply the color scale to the map.
map.getLayer('countries')
- .style('fill', countryFill)
- .on('mouseenter', function(d, path, evt) {
+ .style('fill', countryFill)
+ .on('mouseenter', function (d, path, evt) {
if (evt.shiftKey) { // highlight on mouseover with shift pressed
path.attr('fill', '#f4f45b');
}
})
- .on('mouseleave', function(d, path, evt) {
+ .on('mouseleave', function (d, path, evt) {
if ($.inArray(UserCountryMap.countriesByIso[d.iso].name, _rowEvolution.labels) == -1) {
path.attr('fill', countryFill(d)); // reset color
}
});
// Update the map tooltips.
- map.getLayer('countries').tooltips(function(data) {
+ map.getLayer('countries').tooltips(function (data) {
var metric = $$('.userCountryMapSelectMetrics').val(),
country = UserCountryMap.countriesByIso[data.iso];
- return '<h3>'+country.name + '</h3>'+
+ return '<h3>' + country.name + '</h3>' +
formatValueForTooltips(country, metric, target);
});
}
@@ -470,32 +470,32 @@
}
// otherwise we need to load another map svg
- _updateMap(target + '.svg', function() {
+ _updateMap(target + '.svg', function () {
// add a layer for non-selectable countries = for which no data is
// defined in the current report
map.addLayer('countries', {
name: 'context',
- filter: function(pd) {
+ filter: function (pd) {
return UserCountryMap.countriesByIso[pd.iso] === undefined;
},
- tooltips: function(pd) {
- return '<h3>'+pd.name+'</h3>' + _.no_visit;
+ tooltips: function (pd) {
+ return '<h3>' + pd.name + '</h3>' + _.no_visit;
}
});
// add a layer for selectable countries = for which we have data
// available in the current report
- map.addLayer('countries', { name: 'countryBG', filter: function(pd) {
+ map.addLayer('countries', { name: 'countryBG', filter: function (pd) {
return UserCountryMap.countriesByIso[pd.iso] !== undefined;
}});
map.addLayer('countries', {
key: 'iso',
- filter: function(pd) {
+ filter: function (pd) {
return UserCountryMap.countriesByIso[pd.iso] !== undefined;
},
- click: function(data, path, evt) {
+ click: function (data, path, evt) {
evt.stopPropagation();
if (evt.shiftKey || _rowEvolution.labels.length) {
if (evt.altKey) {
@@ -526,7 +526,7 @@
* updateMap is called by renderCountryMap() and renderWorldMap()
*/
function _updateMap(svgUrl, callback) {
- map.loadMap(config.svgBasePath + svgUrl, function() {
+ map.loadMap(config.svgBasePath + svgUrl, function () {
map.clear();
self.resize();
@@ -576,7 +576,7 @@
function aggregate(rows, groupBy) {
var groups = {};
- $.each(rows, function(i, row) {
+ $.each(rows, function (i, row) {
var g_id = groupBy ? groupBy(row) : 'X';
g_id = g_id === true ? $.isNumeric(i) && i === Number(i) ? false : i : g_id;
if (g_id) {
@@ -588,19 +588,19 @@
bounce_count: 0
};
}
- $.each(groups[g_id], function(metric) {
+ $.each(groups[g_id], function (metric) {
groups[g_id][metric] += row[metric];
});
}
});
- $.each(groups, function(g_id, group) {
+ $.each(groups, function (g_id, group) {
var apv = group.nb_actions / group.nb_visits,
ats = group.sum_visit_length / group.nb_visits,
br = (group.bounce_count * 100 / group.bounce_count);
group['nb_actions_per_visit'] = apv;
- group['avg_time_on_site'] = new Date(0,0,0,ats / 3600, ats % 3600 / 60, ats % 60).toLocaleTimeString();
- group['bounce_rate'] = (br % 1 !== 0 ? br.toFixed(1) : br)+"%";
+ group['avg_time_on_site'] = new Date(0, 0, 0, ats / 3600, ats % 3600 / 60, ats % 60).toLocaleTimeString();
+ group['bounce_rate'] = (br % 1 !== 0 ? br.toFixed(1) : br) + "%";
});
return groupBy ? groups : groups.X;
@@ -610,7 +610,7 @@
$('.unlocated-stats').html(
$('.unlocated-stats').data('tpl')
.replace('%s', unlocated)
- .replace('%p', '('+formatPercentage(unlocated/total)+')')
+ .replace('%p', '(' + formatPercentage(unlocated / total) + ')')
.replace('%c', UserCountryMap.countriesByIso[self.lastSelected].name)
);
$('.UserCountryMap-info-btn').show();
@@ -636,120 +636,120 @@
indicateLoading();
// load data from Piwik API
ajax(_reportParams('UserCountry', 'getRegion', UserCountryMap.countriesByIso[iso].iso2))
- .done(function(data) {
+ .done(function (data) {
- loadingComplete();
+ loadingComplete();
- var regionDict = {},
- totalCountryVisits = UserCountryMap.countriesByIso[iso].nb_visits,
- unlocated = totalCountryVisits;
- // self.lastReportMetricStats = {};
+ var regionDict = {},
+ totalCountryVisits = UserCountryMap.countriesByIso[iso].nb_visits,
+ unlocated = totalCountryVisits;
+ // self.lastReportMetricStats = {};
- function regionCode(region) {
- var key = UserCountryMap.keys[iso] || 'fips';
- return key.substr(0,4) == "fips" ? region[key].substr(2) : region[key]; // cut first two letters from fips code (=country code)
- }
+ function regionCode(region) {
+ var key = UserCountryMap.keys[iso] || 'fips';
+ return key.substr(0, 4) == "fips" ? region[key].substr(2) : region[key]; // cut first two letters from fips code (=country code)
+ }
- function regionExistsInMap(code) {
- var key = UserCountryMap.keys[iso] || 'fips', q = {};
- q[key] = key.substr(0,4) == 'fips' ? UserCountryMap.countriesByIso[iso].fips + code : code;
- if (map.getLayer('regions').getPaths(q).length === 0) {
- return false;
+ function regionExistsInMap(code) {
+ var key = UserCountryMap.keys[iso] || 'fips', q = {};
+ q[key] = key.substr(0, 4) == 'fips' ? UserCountryMap.countriesByIso[iso].fips + code : code;
+ if (map.getLayer('regions').getPaths(q).length === 0) {
+ return false;
+ }
+ return true;
}
- return true;
- }
- $.each(data.reportData, function(i, row) {
- regionDict[data.reportMetadata[i].region] = $.extend(row, data.reportMetadata[i], {
- curMetric: quantify(row, metric)
+ $.each(data.reportData, function (i, row) {
+ regionDict[data.reportMetadata[i].region] = $.extend(row, data.reportMetadata[i], {
+ curMetric: quantify(row, metric)
+ });
});
- });
- var metric = $$('.userCountryMapSelectMetrics').val();
+ var metric = $$('.userCountryMapSelectMetrics').val();
- if (UserCountryMap.aggregate[iso]) {
- var aggregated = aggregate(regionDict, function(row) {
- var id = row.region, res = false;
- $.each(UserCountryMap.aggregate[iso].groups, function(group, codes) {
- if ($.inArray(id, codes) > -1) {
- res = group;
- }
+ if (UserCountryMap.aggregate[iso]) {
+ var aggregated = aggregate(regionDict, function (row) {
+ var id = row.region, res = false;
+ $.each(UserCountryMap.aggregate[iso].groups, function (group, codes) {
+ if ($.inArray(id, codes) > -1) {
+ res = group;
+ }
+ });
+ return res;
});
- return res;
- });
- //if (!UserCountryMap.aggregate.partial) regionDict = {};
- $.each(aggregated, function(id, group) {
- group.curMetric = quantify(group, metric);
- regionDict[id] = group;
+ //if (!UserCountryMap.aggregate.partial) regionDict = {};
+ $.each(aggregated, function (id, group) {
+ group.curMetric = quantify(group, metric);
+ regionDict[id] = group;
+ });
+ }
+
+ $.each(regionDict, function (key, region) {
+ if (regionExistsInMap(key)) unlocated -= region.nb_visits;
});
- }
+ displayUnlocatableCount(unlocated, totalCountryVisits);
- $.each(regionDict, function(key, region) {
- if (regionExistsInMap(key)) unlocated -= region.nb_visits;
- });
- displayUnlocatableCount(unlocated, totalCountryVisits);
+ // create color scale
+ colscale = getColorScale(regionDict, 'curMetric', null, true);
- // create color scale
- colscale = getColorScale(regionDict, 'curMetric', null, true);
+ function regionFill(data) {
+ var code = regionCode(data);
+ return regionDict[code] === undefined ? '#fff' : colscale(regionDict[code].curMetric);
+ }
- function regionFill(data) {
- var code = regionCode(data);
- return regionDict[code] === undefined ? '#fff' : colscale(regionDict[code].curMetric);
- }
+ // apply colors to map
+ map.getLayer('regions')
+ .style('fill', regionFill)
+ .style('stroke',function (data) {
+ return regionDict[regionCode(data)] === undefined ? '#bbb' : '#3C6FB6';
+ }).sort(function (data) {
+ var code = regionCode(data);
+ return regionDict[code] === undefined ? -1 : regionDict[code].curMetric;
+ }).tooltips(function (data) {
+ var metric = $$('.userCountryMapSelectMetrics').val(),
+ region = regionDict[regionCode(data)];
+ if (region === undefined) {
+ return '<h3>' + data.name + '</h3><p>' + _.nb_visits.replace('%s', '<b>0</b>') + '</p>';
+ }
+ return '<h3>' + data.name + '</h3>' +
+ formatValueForTooltips(region, metric, iso);
+ }).on('click',function (d, path, evt) {
+ var region = regionDict[regionCode(d)];
+ if (region && region.label) {
+ if (evt.shiftKey) {
+ path.attr('fill', '#f4f45b');
+ addMultipleRowEvolution('getRegion', region.label);
+ } else {
+ map.getLayer('regions').style('fill', regionFill);
+ showRowEvolution('getRegion', region.label);
+ }
+ }
+ }).on('mouseenter',function (d, path, evt) {
+ var region = regionDict[regionCode(d)];
+ if (region && region.label) {
+ if (evt.shiftKey) {
+ path.attr('fill', '#f4f45b');
+ }
+ }
+ }).on('mouseleave',function (d, path, evt) {
+ var region = regionDict[regionCode(d)];
+ if (region && region.label) {
+ if ($.inArray(region.label, _rowEvolution.labels) == -1) {
+ // reset color
+ path.attr('fill', regionFill(d));
+ }
+ }
+ }).style('cursor', function (d) {
+ return regionDict[regionCode(d)] && regionDict[regionCode(d)].label ? 'pointer' : 'default';
+ });
- // apply colors to map
- map.getLayer('regions')
- .style('fill', regionFill)
- .style('stroke', function(data) {
- return regionDict[regionCode(data)] === undefined ? '#bbb' : '#3C6FB6';
- }).sort(function(data) {
- var code = regionCode(data);
- return regionDict[code] === undefined ? -1 : regionDict[code].curMetric;
- }).tooltips(function(data) {
- var metric = $$('.userCountryMapSelectMetrics').val(),
- region = regionDict[regionCode(data)];
- if (region === undefined) {
- return '<h3>'+data.name+'</h3><p>'+_.nb_visits.replace('%s', '<b>0</b>')+'</p>';
- }
- return '<h3>'+data.name+'</h3>'+
- formatValueForTooltips(region, metric, iso);
- }).on('click', function(d, path, evt) {
- var region = regionDict[regionCode(d)];
- if (region && region.label) {
- if (evt.shiftKey) {
- path.attr('fill', '#f4f45b');
- addMultipleRowEvolution('getRegion', region.label);
- } else {
- map.getLayer('regions').style('fill', regionFill);
- showRowEvolution('getRegion', region.label);
- }
- }
- }).on('mouseenter', function(d, path, evt) {
- var region = regionDict[regionCode(d)];
- if (region && region.label) {
- if (evt.shiftKey) {
- path.attr('fill', '#f4f45b');
+ // check for regions missing in the map
+ $.each(regionDict, function (code, region) {
+ if (!regionExistsInMap(code)) {
+ console.warn('possible region mismatch!', code, region.nb_visits);
}
- }
- }).on('mouseleave', function(d, path, evt) {
- var region = regionDict[regionCode(d)];
- if (region && region.label) {
- if ($.inArray(region.label, _rowEvolution.labels) == -1) {
- // reset color
- path.attr('fill', regionFill(d));
- }
- }
- }).style('cursor', function(d) {
- return regionDict[regionCode(d)] && regionDict[regionCode(d)].label ? 'pointer' : 'default';
- });
-
- // check for regions missing in the map
- $.each(regionDict, function(code, region) {
- if (!regionExistsInMap(code)) {
- console.warn('possible region mismatch!', code, region.nb_visits);
- }
+ });
});
- });
}
/*
@@ -765,174 +765,175 @@
// get visits per city from API
ajax(_reportParams('UserCountry', 'getCity', UserCountryMap.countriesByIso[iso].iso2))
- .done(function(data) {
+ .done(function (data) {
- loadingComplete();
+ loadingComplete();
- var metric = $$('.userCountryMapSelectMetrics').val(),
- colscale,
- totalCountryVisits = UserCountryMap.countriesByIso[iso].nb_visits,
- unlocated = totalCountryVisits,
- cities = [];
-
- // merge reportData and reportMetadata to cities array
- $.each(data.reportData, function(i, row) {
- unlocated -= row.nb_visits;
- cities.push($.extend(row, data.reportMetadata[i], {
- curMetric: quantify(row, metric)
- }));
- });
+ var metric = $$('.userCountryMapSelectMetrics').val(),
+ colscale,
+ totalCountryVisits = UserCountryMap.countriesByIso[iso].nb_visits,
+ unlocated = totalCountryVisits,
+ cities = [];
+
+ // merge reportData and reportMetadata to cities array
+ $.each(data.reportData, function (i, row) {
+ unlocated -= row.nb_visits;
+ cities.push($.extend(row, data.reportMetadata[i], {
+ curMetric: quantify(row, metric)
+ }));
+ });
- displayUnlocatableCount(unlocated, totalCountryVisits);
+ displayUnlocatableCount(unlocated, totalCountryVisits);
- // sort by current metric
- cities.sort(function(a, b) { return b.curMetric - a.curMetric; });
+ // sort by current metric
+ cities.sort(function (a, b) { return b.curMetric - a.curMetric; });
- colscale = getColorScale(cities, metric);
+ colscale = getColorScale(cities, metric);
- // construct scale
- var radscale = $K.scale.linear(cities.concat({ curMetric: 0 }), 'curMetric');
+ // construct scale
+ var radscale = $K.scale.linear(cities.concat({ curMetric: 0 }), 'curMetric');
- var area = map.container.width() * map.container.height(),
- sumArea = 0,
- f = {
- nb_visits: 0.002,
- nb_actions: 0.002,
- avg_time_on_site: 0.02,
- nb_actions_per_visit: 0.02,
- bounce_rate: 0.02
- },
- maxRad;
+ var area = map.container.width() * map.container.height(),
+ sumArea = 0,
+ f = {
+ nb_visits: 0.002,
+ nb_actions: 0.002,
+ avg_time_on_site: 0.02,
+ nb_actions_per_visit: 0.02,
+ bounce_rate: 0.02
+ },
+ maxRad;
- $.each(cities, function(i, city) {
- sumArea += isNaN(city.curMetric) ? 0 : Math.pow(radscale(city.curMetric), 2);
- });
- maxRad = Math.sqrt(area * f[metric] / sumArea);
-
- radscale = $K.scale.sqrt(cities.concat({ curMetric: 0 }), 'curMetric').range([2, maxRad+2]);
-
- var is_rate = metric.substr(0,3) != 'nb_' || metric == 'nb_actions_per_visit';
-
- var citySymbols = map.addSymbols({
- type: Kartograph.LabeledBubble,
- data: cities,
- clustering: 'noverlap',
- clusteringOpts: {
- size: 128,
- tolerance: 0
- },
- title: function(d) {
- return radscale(d.curMetric) > 10 ? formatNumber(d.curMetric) : '';
- },
- labelattrs: {
- fill: '#fff',
- 'font-size': 11,
- stroke: false,
- cursor: 'pointer'
- },
- filter: function(d) {
- if (isNaN(d.lat) || isNaN(d.long)) return false;
- return is_rate ? d.nb_visits > 5 && d.curMetric : d.curMetric;
- },
- aggregate: function(rows) {
- var row = aggregate(rows);
- row.city_names = [];
- row.label = rows[0].label; // keep label of biggest city for row evolution
- $.each(rows, function(i, r) {
- row.city_names = row.city_names.concat(r.city_names ? r.city_names : [r.city_name]);
- });
- row.city_name = row.city_names[0] + (row.city_names.length > 1 ? ' '+_.and_n_others.replace('%s', (row.city_names.length-1)) : '');
- row.curMetric = quantify(row, metric);
- return row;
- },
- sortBy: 'radius desc',
- location: function(city) { return [city.long, city.lat]; },
- radius: function(city) { return radscale(city.curMetric); },
- tooltip: function(city) {
- return '<h3>'+city.city_name+'</h3>'+
- formatValueForTooltips(city, metric, iso);
- },
- attrs: function(city) {
- return {
- fill: colscale(city.curMetric).hex(),
- 'fill-opacity': 0.7,
- stroke: '#fff',
+ $.each(cities, function (i, city) {
+ sumArea += isNaN(city.curMetric) ? 0 : Math.pow(radscale(city.curMetric), 2);
+ });
+ maxRad = Math.sqrt(area * f[metric] / sumArea);
+
+ radscale = $K.scale.sqrt(cities.concat({ curMetric: 0 }), 'curMetric').range([2, maxRad + 2]);
+
+ var is_rate = metric.substr(0, 3) != 'nb_' || metric == 'nb_actions_per_visit';
+
+ var citySymbols = map.addSymbols({
+ type: Kartograph.LabeledBubble,
+ data: cities,
+ clustering: 'noverlap',
+ clusteringOpts: {
+ size: 128,
+ tolerance: 0
+ },
+ title: function (d) {
+ return radscale(d.curMetric) > 10 ? formatNumber(d.curMetric) : '';
+ },
+ labelattrs: {
+ fill: '#fff',
+ 'font-size': 11,
+ stroke: false,
cursor: 'pointer'
- };
- },
- mouseenter: function(city, symbol, evt) {
- symbol.path.attr({
- 'fill-opacity': 1,
- 'stroke': '#000000',
- 'stroke-opacity': 1,
- 'stroke-width': 2
- });
- if (evt.shiftKey) {
- symbol.path.attr({ fill: '#f4f45b' });
- if (symbol.label) symbol.label.attr({ fill: '#000' });
- }
- },
- mouseleave: function(city, symbol) {
- symbol.path.attr({
- 'fill-opacity': 0.7,
- 'stroke-opacity': 1,
- 'stroke-width': 1,
- 'stroke': '#ffffff'
- });
- if ($.inArray(city.label, _rowEvolution.labels) == -1) {
- symbol.path.attr({ fill: colscale(city.curMetric) });
- if (symbol.label) symbol.label.attr({ fill: '#fff' });
- }
- },
- click: function(city, symbol, evt) {
- if (evt.shiftKey) {
- addMultipleRowEvolution('getCity', city.label);
- symbol.path.attr('fill', '#f4f45b');
- if (symbol.label) symbol.label.attr('fill', '#000');
- } else {
- showRowEvolution('getCity', city.label);
- citySymbols.update({
- attrs: function(city) {
- return { fill: colscale(city.curMetric) };
- }
+ },
+ filter: function (d) {
+ if (isNaN(d.lat) || isNaN(d.long)) return false;
+ return is_rate ? d.nb_visits > 5 && d.curMetric : d.curMetric;
+ },
+ aggregate: function (rows) {
+ var row = aggregate(rows);
+ row.city_names = [];
+ row.label = rows[0].label; // keep label of biggest city for row evolution
+ $.each(rows, function (i, r) {
+ row.city_names = row.city_names.concat(r.city_names ? r.city_names : [r.city_name]);
+ });
+ row.city_name = row.city_names[0] + (row.city_names.length > 1 ? ' ' + _.and_n_others.replace('%s', (row.city_names.length - 1)) : '');
+ row.curMetric = quantify(row, metric);
+ return row;
+ },
+ sortBy: 'radius desc',
+ location: function (city) { return [city.long, city.lat]; },
+ radius: function (city) { return radscale(city.curMetric); },
+ tooltip: function (city) {
+ return '<h3>' + city.city_name + '</h3>' +
+ formatValueForTooltips(city, metric, iso);
+ },
+ attrs: function (city) {
+ return {
+ fill: colscale(city.curMetric).hex(),
+ 'fill-opacity': 0.7,
+ stroke: '#fff',
+ cursor: 'pointer'
+ };
+ },
+ mouseenter: function (city, symbol, evt) {
+ symbol.path.attr({
+ 'fill-opacity': 1,
+ 'stroke': '#000000',
+ 'stroke-opacity': 1,
+ 'stroke-width': 2
});
+ if (evt.shiftKey) {
+ symbol.path.attr({ fill: '#f4f45b' });
+ if (symbol.label) symbol.label.attr({ fill: '#000' });
+ }
+ },
+ mouseleave: function (city, symbol) {
+ symbol.path.attr({
+ 'fill-opacity': 0.7,
+ 'stroke-opacity': 1,
+ 'stroke-width': 1,
+ 'stroke': '#ffffff'
+ });
+ if ($.inArray(city.label, _rowEvolution.labels) == -1) {
+ symbol.path.attr({ fill: colscale(city.curMetric) });
+ if (symbol.label) symbol.label.attr({ fill: '#fff' });
+ }
+ },
+ click: function (city, symbol, evt) {
+ if (evt.shiftKey) {
+ addMultipleRowEvolution('getCity', city.label);
+ symbol.path.attr('fill', '#f4f45b');
+ if (symbol.label) symbol.label.attr('fill', '#000');
+ } else {
+ showRowEvolution('getCity', city.label);
+ citySymbols.update({
+ attrs: function (city) {
+ return { fill: colscale(city.curMetric) };
+ }
+ });
+ }
}
- }
+ });
});
- });
}
- _updateMap(iso + '.svg', function() {
+ _updateMap(iso + '.svg', function () {
// add background
map.addLayer('context', {
key: 'iso',
- filter: function(pd) {
+ filter: function (pd) {
return UserCountryMap.countriesByIso[pd.iso] === undefined;
}
});
map.addLayer('context', {
key: 'iso',
name: 'context-clickable',
- filter: function(pd) {
+ filter: function (pd) {
return UserCountryMap.countriesByIso[pd.iso] !== undefined;
},
- click: function(path, p, evt) { // add click events for surrounding countries
+ click: function (path, p, evt) { // add click events for surrounding countries
evt.stopPropagation();
updateState(path.iso);
},
- tooltips: function(data) {
+ tooltips: function (data) {
if (UserCountryMap.countriesByIso[data.iso] === undefined) {
return 'no data';
}
var metric = $$('.userCountryMapSelectMetrics').val(),
country = UserCountryMap.countriesByIso[data.iso];
- return '<h3>'+country.name+'</h3>'+
+ return '<h3>' + country.name + '</h3>' +
formatValueForTooltips(country, metric, 'world');
}
});
function isThisCountry(d) { return d.iso == iso;}
+
map.addLayer("context", {
name: "regionBG",
filter: isThisCountry
@@ -947,7 +948,7 @@
styles: {
stroke: '#aaa'
},
- click: function(d, p, evt) {
+ click: function (d, p, evt) {
evt.stopPropagation();
}
});
@@ -957,19 +958,21 @@
map.getLayer('context-clickable').getPath(data.iso) &&
Math.abs(map.getLayer('context-clickable').getPath(data.iso).path.area()) > 700;
}
+
// returns either the reference to the country polygon or a custom label
// position if defined in UserCountryMap.customLabelPositions
function countryLabelPos(data) {
var CLP = UserCountryMap.customLabelPositions;
if (CLP[iso] && CLP[iso][data.iso]) return CLP[iso][data.iso];
- return 'context-clickable.'+data.iso;
+ return 'context-clickable.' + data.iso;
}
+
map.addSymbols({
data: map.getLayer('context-clickable').getPathsData(),
type: $K.Label,
filter: filtCountryLabels,
location: countryLabelPos,
- text: function(data) { return UserCountryMap.countriesByIso[data.iso].iso2; },
+ text: function (data) { return UserCountryMap.countriesByIso[data.iso].iso2; },
'class': 'countryLabelBg'
});
map.addSymbols({
@@ -977,7 +980,7 @@
type: $K.Label,
filter: filtCountryLabels,
location: countryLabelPos,
- text: function(data) { return UserCountryMap.countriesByIso[data.iso].iso2; },
+ text: function (data) { return UserCountryMap.countriesByIso[data.iso].iso2; },
'class': 'countryLabel'
});
@@ -1012,7 +1015,7 @@
if (multiple) {
_rowEvolution.labels.push(label);
- $.each(_rowEvolution.labels, function(i,l) {
+ $.each(_rowEvolution.labels, function (i, l) {
_rowEvolution.labels[i] = l.replace(/, /g, '%2C%20');
});
}
@@ -1032,86 +1035,86 @@
if (column) { requestParams.column = column; }
ajax(requestParams, 'html')
- .done(function(html) {
- Piwik_Popover.setContent(html);
-
- // use the popover title returned from the server
- var title = box.find('div.popover-title');
- if (title.size() > 0) {
- Piwik_Popover.setTitle(title.html());
- title.remove();
- }
+ .done(function (html) {
+ Piwik_Popover.setContent(html);
+
+ // use the popover title returned from the server
+ var title = box.find('div.popover-title');
+ if (title.size() > 0) {
+ Piwik_Popover.setTitle(title.html());
+ title.remove();
+ }
- box.find('.compare-container').hide();
- box.find('.rowevolution-startmulti').hide();
- box.find('.multirowevoltion-metric').off('change').change(function(e) {
- _rowEvolution.labels = oldLabels;
- showRowEvolution(method, label, box.find('.multirowevoltion-metric').val());
+ box.find('.compare-container').hide();
+ box.find('.rowevolution-startmulti').hide();
+ box.find('.multirowevoltion-metric').off('change').change(function (e) {
+ _rowEvolution.labels = oldLabels;
+ showRowEvolution(method, label, box.find('.multirowevoltion-metric').val());
+ });
});
- });
_rowEvolution.labels = [];
}
// now load the metrics for all countries
ajax(_reportParams('UserCountry', 'getCountry'))
- .done(function(report) {
- var metrics = $$('.userCountryMapSelectMetrics option');
- var countryData = [], countrySelect = $$('.userCountryMapSelectCountry'),
- countriesByIso = {};
- UserCountryMap.lastReportMetricStats = {};
- // read api result to countryData and countriesByIso
- $.each(report.reportData, function(i, data) {
- var meta = report.reportMetadata[i],
- country = {
- name: data.label,
- iso2: meta.code.toUpperCase(),
- fips: meta.code.toUpperCase(),
- iso: UserCountryMap.ISO2toISO3[meta.code.toUpperCase()],
- flag: meta.logo
- };
- if (UserCountryMap.differentFIPS[country.iso2]) {
- country.fips = UserCountryMap.differentFIPS[country.iso2];
- }
- $.each(metrics, function(i, metric) {
- metric = $(metric).attr('value');
- country[metric] = data[metric];
- });
- countryData.push(country);
- countriesByIso[country.iso] = country;
- worldTotalVisits += country['nb_visits'];
- });
- _worldTotal = worldTotalVisits;
- // sort countries by name
- countryData.sort(function(a,b) { return a.name > b.name ? 1 : -1; });
-
- // store country data globally
- UserCountryMap.countryData = countryData;
- UserCountryMap.countriesByIso = countriesByIso;
-
- map.loadCSS(config.mapCssPath, function() {
- // map stylesheets are loaded
-
- // hide loading indicator
- $$('.UserCountryMap .loadingPiwik').hide();
- $('.mapWidgetStatus').height(0);
-
- // start with default view (or saved state??)
- var params = self.widget.dashboardWidget('getWidgetObject').parameters;
- self.mode = params && params.viewMode ? params.viewMode : 'region';
- if (params && params.lastMetric) $$('.userCountryMapSelectMetrics').val(params.lastMetric);
- //alert('updateState: '+params && params.lastMap ? params.lastMap : 'world');
- updateState(params && params.lastMap ? params.lastMap : 'world');
-
- // populate country select
- $.each(countryData, function(i, country) {
- countrySelect.append('<option value="'+country.iso+'">'+country.name+'</option>');
+ .done(function (report) {
+ var metrics = $$('.userCountryMapSelectMetrics option');
+ var countryData = [], countrySelect = $$('.userCountryMapSelectCountry'),
+ countriesByIso = {};
+ UserCountryMap.lastReportMetricStats = {};
+ // read api result to countryData and countriesByIso
+ $.each(report.reportData, function (i, data) {
+ var meta = report.reportMetadata[i],
+ country = {
+ name: data.label,
+ iso2: meta.code.toUpperCase(),
+ fips: meta.code.toUpperCase(),
+ iso: UserCountryMap.ISO2toISO3[meta.code.toUpperCase()],
+ flag: meta.logo
+ };
+ if (UserCountryMap.differentFIPS[country.iso2]) {
+ country.fips = UserCountryMap.differentFIPS[country.iso2];
+ }
+ $.each(metrics, function (i, metric) {
+ metric = $(metric).attr('value');
+ country[metric] = data[metric];
+ });
+ countryData.push(country);
+ countriesByIso[country.iso] = country;
+ worldTotalVisits += country['nb_visits'];
});
+ _worldTotal = worldTotalVisits;
+ // sort countries by name
+ countryData.sort(function (a, b) { return a.name > b.name ? 1 : -1; });
+
+ // store country data globally
+ UserCountryMap.countryData = countryData;
+ UserCountryMap.countriesByIso = countriesByIso;
+
+ map.loadCSS(config.mapCssPath, function () {
+ // map stylesheets are loaded
+
+ // hide loading indicator
+ $$('.UserCountryMap .loadingPiwik').hide();
+ $('.mapWidgetStatus').height(0);
+
+ // start with default view (or saved state??)
+ var params = self.widget.dashboardWidget('getWidgetObject').parameters;
+ self.mode = params && params.viewMode ? params.viewMode : 'region';
+ if (params && params.lastMetric) $$('.userCountryMapSelectMetrics').val(params.lastMetric);
+ //alert('updateState: '+params && params.lastMap ? params.lastMap : 'world');
+ updateState(params && params.lastMap ? params.lastMap : 'world');
+
+ // populate country select
+ $.each(countryData, function (i, country) {
+ countrySelect.append('<option value="' + country.iso + '">' + country.name + '</option>');
+ });
- initUserInterface();
+ initUserInterface();
+ });
});
- });
function hideOverlay(e) {
var overlay = $('.content', $(e.target).parents('.UserCountryMap-overlay'));
@@ -1119,36 +1122,36 @@
overlay.data('locked', true);
overlay.fadeOut(200);
- $$('.UserCountryMap').mouseleave(function() {
+ $$('.UserCountryMap').mouseleave(function () {
overlay.fadeIn(200);
$$('.UserCountryMap').parent().off('mouseleave');
- setTimeout(function() {
+ setTimeout(function () {
overlay.data('locked', false);
}, 1000);
});
var offset = $$('.UserCountryMap').offset(),
dim = {
- x: overlay.offset().left - offset.left,
- y: overlay.offset().top - offset.top,
- w: overlay.width(),
- h: overlay.height()
- };
- $$('.UserCountryMap').mousemove(function(e) {
+ x: overlay.offset().left - offset.left,
+ y: overlay.offset().top - offset.top,
+ w: overlay.width(),
+ h: overlay.height()
+ };
+ $$('.UserCountryMap').mousemove(function (e) {
var mx = e.pageX - offset.left, my = e.pageY - offset.top, pad = 20,
- outside = mx < dim.x - pad || mx > dim.x + dim.w + pad || my < dim.y - pad || my > dim.y + dim.h + pad;
+ outside = mx < dim.x - pad || mx > dim.x + dim.w + pad || my < dim.y - pad || my > dim.y + dim.h + pad;
if (outside) {
$$('.UserCountryMap').parent().off('mouseleave');
- setTimeout(function() {
+ setTimeout(function () {
overlay.fadeIn(200);
- setTimeout(function() {
+ setTimeout(function () {
overlay.data('locked', false);
}, 1000);
}, 100);
}
});
/*setTimeout(function() {
- overlay.fadeIn(1000);
- }, 3000);*/
+ overlay.fadeIn(1000);
+ }, 3000);*/
}
$('.UserCountryMap-overlay').off('mouseenter').on('mouseenter', hideOverlay);
@@ -1161,7 +1164,7 @@
/*
* resizes the map
*/
- resize: function() {
+ resize: function () {
var ratio, w, h,
map = this.map,
maxHeight = $(window).height() - (this.theWidget && this.theWidget.isMaximised ? 150 : 55);
@@ -1179,7 +1182,7 @@
/*
* removes the map
*/
- destroy: function() {
+ destroy: function () {
this.map.clear();
$(this.map.container).html('');
}
@@ -1250,8 +1253,8 @@ $.extend(UserCountryMap, {
CZE: {
partial: true,
groups: {
- "82": ["82","70","23","20"],
- "88": ["88","41"]
+ "82": ["82", "70", "23", "20"],
+ "88": ["88", "41"]
}
},
BEL: {
@@ -1266,7 +1269,7 @@ $.extend(UserCountryMap, {
"19": ["19", "07"],
"18": ["18", "15"],
"20": ["20", "12"],
- "21": ["21", "11", "04"]
+ "21": ["21", "11", "04"]
}
}
},
@@ -1284,7 +1287,7 @@ $.extend(UserCountryMap, {
CZE: { DEU: [12.3, 49] },
DEU: { AUT: [13.9, 48.1] },
ESP: { PRT: [-8.5, 39.6] },
- NLD: { BEL: [4.6, 51,1], DEU: [6.9, 51.5] },
+ NLD: { BEL: [4.6, 51, 1], DEU: [6.9, 51.5] },
CHE: { FRA: [6.2, 47.2], AUT: [9.95, 47.2], ITA: [9.7, 46.0], DEU: [8.14, 47.83] },
USA: { MEX: [-102, 24], CAN: [-97, 52] },
BIH: { HRV: [15.3, 45] }
diff --git a/plugins/UserCountryMap/templates/realtime-map.tpl b/plugins/UserCountryMap/templates/realtime-map.tpl
index 231ecd5ddb..583eed74fa 100644
--- a/plugins/UserCountryMap/templates/realtime-map.tpl
+++ b/plugins/UserCountryMap/templates/realtime-map.tpl
@@ -3,7 +3,8 @@
<div id="RealTimeMap_container">
<div id="RealTimeMap_map" style="overflow:hidden"></div>
<div class="realTimeMap_overlay">
- <span class="showing_visits_of" style="display:none">{'UserCountryMap_ShowingVisits'|translate} <span class="realTimeMap_timeSpan" style="font-weight:bold"></span></span>
+ <span class="showing_visits_of" style="display:none">{'UserCountryMap_ShowingVisits'|translate} <span class="realTimeMap_timeSpan"
+ style="font-weight:bold"></span></span>
<span class="no_data" style="display:none">{'CoreHome_ThereIsNoDataForThisReport'|translate}</span>
<span class="loading_data">{'General_LoadingData'|translate}...</span>
<img src="{$piwikUrl}plugins/UserCountryMap/img/realtimemap-loading.gif" style="vertical-align:baseline;position:relative;left:-2px;">
@@ -21,26 +22,29 @@
<!-- configure some piwik vars -->
<script type="text/javascript">
-{* If the map is loaded from the menu, do a few tweaks to clean up the display *}
-{if $mapIsStandaloneNotWidget}
- function initStandaloneMap() {ldelim}
- $('.top_controls').hide();
- $('ul.nav').on('piwikSwitchPage', function(event, item) {ldelim}
- var clickedMenuIsNotMap = ($(item).text() != "{'UserCountryMap_RealTimeMap'|translate|escape:'js'}");
- if(clickedMenuIsNotMap) {ldelim}
- $('.top_controls').show();
- {rdelim}
- {rdelim});
- $('.realTimeMap_overlay').css('top', '0px');
- $('.realTimeMap_datetime').css('top', '20px');
- {rdelim}
+ {* If the map is loaded from the menu, do a few tweaks to clean up the display *}
+ {if $mapIsStandaloneNotWidget}
+ function initStandaloneMap() {ldelim}
+ $('.top_controls').hide();
+ $('ul.nav').on('piwikSwitchPage', function (event, item) {ldelim}
+ var clickedMenuIsNotMap = ($(item).text() != "{'UserCountryMap_RealTimeMap'|translate|escape:'js'}");
+ if (clickedMenuIsNotMap) {ldelim}
+ $('.top_controls').show();
+ {rdelim
+ }
+ {rdelim
+ });
+ $('.realTimeMap_overlay').css('top', '0px');
+ $('.realTimeMap_datetime').css('top', '20px');
+ {rdelim
+ }
- initStandaloneMap();
-{/if}
+ initStandaloneMap();
+ {/if}
- {literal}
+ {literal}
var config = { metrics: {} };
-{/literal}
+ {/literal}
config.svgBasePath = "{$piwikUrl}plugins/UserCountryMap/svg/";
config.liveRefreshAfterMs = {$liveRefreshAfterMs};
@@ -52,24 +56,24 @@
var realtimeMap;
-{literal}
+ {literal}
if ($('#dashboardWidgetsArea').length) {
// dashboard mode
var $widgetContent = $('#RealTimeMap').parents('.widgetContent');
- $widgetContent.on('widget:create', function(evt, widget) {
+ $widgetContent.on('widget:create',function (evt, widget) {
realtimeMap = new UserCountryMap.RealtimeMap(config, widget);
- }).on('widget:maximise', function(evt) {
- realtimeMap.resize();
- }).on('widget:minimise', function(evt) {
- realtimeMap.resize();
- }).on('widget:destroy', function(evt) {
- realtimeMap.destroy();
- });
+ }).on('widget:maximise',function (evt) {
+ realtimeMap.resize();
+ }).on('widget:minimise',function (evt) {
+ realtimeMap.resize();
+ }).on('widget:destroy', function (evt) {
+ realtimeMap.destroy();
+ });
} else {
// stand-alone mode
realtimeMap = new UserCountryMap.RealtimeMap(config);
}
-{/literal}
+ {/literal}
</script>
diff --git a/plugins/UserCountryMap/templates/visitor-map.tpl b/plugins/UserCountryMap/templates/visitor-map.tpl
index a3b4c37cf8..8e967f8213 100644
--- a/plugins/UserCountryMap/templates/visitor-map.tpl
+++ b/plugins/UserCountryMap/templates/visitor-map.tpl
@@ -4,7 +4,7 @@
<div class="UserCountryMap-overlay UserCountryMap-title">
<div class="content">
<!--<div class="map-title" style="font-weight:bold; color:#9A9386;"></div>-->
- <div class="map-stats" style="color:#565656;"><b></b> </div>
+ <div class="map-stats" style="color:#565656;"><b></b></div>
</div>
</div>
<div class="UserCountryMap-overlay UserCountryMap-legend">
@@ -12,16 +12,16 @@
</div>
</div>
<div class="UserCountryMap-tooltip UserCountryMap-info">
- <div foo="bar" class="content unlocated-stats" data-tpl="{'UserCountryMap_Unlocated'|translate}" >
+ <div foo="bar" class="content unlocated-stats" data-tpl="{'UserCountryMap_Unlocated'|translate}">
</div>
</div>
<div class="UserCountryMap-info-btn" data-tooltip-target=".UserCountryMap-tooltip"></div>
</div>
<div class="mapWidgetStatus">
{if $noData }
- <div class="pk-emptyDataTable">{'CoreHome_ThereIsNoDataForThisReport'|translate}</div>
+ <div class="pk-emptyDataTable">{'CoreHome_ThereIsNoDataForThisReport'|translate}</div>
{else}
- <span class="loadingPiwik">
+ <span class="loadingPiwik">
<img src="{$piwikUrl}themes/default/images/loading-blue.gif"> {'General_LoadingData'|translate}...
</span>
{/if}
@@ -29,23 +29,30 @@
<div class="dataTableFeatures" style="padding-top:0px;">
<div class="dataTableFooterIcons">
<div class="dataTableFooterWrap" var="graphVerticalBar">
- <img class="UserCountryMap-activeItem dataTableFooterActiveItem" src="{$piwikUrl}themes/default/images/data_table_footer_active_item.png" style="left: 25px;">
+ <img class="UserCountryMap-activeItem dataTableFooterActiveItem" src="{$piwikUrl}themes/default/images/data_table_footer_active_item.png"
+ style="left: 25px;">
<div class="tableIconsGroup">
<span class="tableAllColumnsSwitch">
- <a class="UserCountryMap-btn-zoom tableIcon" format="table"><img src="{$piwikUrl}plugins/UserCountryMap/img/zoom-out.png" title="Zoom to world"></a>
+ <a class="UserCountryMap-btn-zoom tableIcon" format="table"><img src="{$piwikUrl}plugins/UserCountryMap/img/zoom-out.png"
+ title="Zoom to world"></a>
</span>
</div>
<div class="tableIconsGroup UserCountryMap-view-mode-buttons">
<span class="tableAllColumnsSwitch">
- <a var="tableAllColumns" class="UserCountryMap-btn-region tableIcon activeIcon" format="tableAllColumns" data-region="{'UserCountryMap_Regions'|translate}" data-country="{'UserCountryMap_Countries'|translate}"><img src="{$piwikUrl}plugins/UserCountryMap/img/regions.png" title="Show vistors per region/country"> <span style="margin:0">{'UserCountryMap_Countries'|translate}</span>&nbsp;</a>
- <a var="tableGoals" class="UserCountryMap-btn-city tableIcon inactiveIco" format="tableGoals"><img src="{$piwikUrl}plugins/UserCountryMap/img/cities.png" title="Show visitors per city"> <span style="margin:0">{'UserCountryMap_Cities'|translate}</span>&nbsp;</a>
+ <a var="tableAllColumns" class="UserCountryMap-btn-region tableIcon activeIcon" format="tableAllColumns"
+ data-region="{'UserCountryMap_Regions'|translate}" data-country="{'UserCountryMap_Countries'|translate}"><img
+ src="{$piwikUrl}plugins/UserCountryMap/img/regions.png" title="Show vistors per region/country"> <span
+ style="margin:0">{'UserCountryMap_Countries'|translate}</span>&nbsp;</a>
+ <a var="tableGoals" class="UserCountryMap-btn-city tableIcon inactiveIco" format="tableGoals"><img
+ src="{$piwikUrl}plugins/UserCountryMap/img/cities.png" title="Show visitors per city"> <span
+ style="margin:0">{'UserCountryMap_Cities'|translate}</span>&nbsp;</a>
</span>
</div>
</div>
- <select class="userCountryMapSelectMetrics" style="float:right;margin-right:0;margin-bottom:5px;max-width: 9em;font-size:10px">
+ <select class="userCountryMapSelectMetrics" style="float:right;margin-right:0;margin-bottom:5px;max-width: 9em;font-size:10px">
{foreach from=$metrics item=metric}
<option value="{$metric[0]}" {if $metric[0] == $defaultMetric}selected="selected"{/if}>{$metric[1]}</option>
{/foreach}
@@ -67,36 +74,36 @@
{if !$noData }
-<!-- configure some piwik vars -->
-<script type="text/javascript">
+ <!-- configure some piwik vars -->
+ <script type="text/javascript">
- var visitorMap,
- config = JSON.parse('{$config|escape:'javascript'}');
- config._ = JSON.parse('{$localeJSON|escape:'javascript'}');
- config.reqParams = JSON.parse('{$reqParamsJSON|escape:'javascript'}');
+ var visitorMap,
+ config = JSON.parse('{$config|escape:'javascript'}');
+ config._ = JSON.parse('{$localeJSON|escape:'javascript'}');
+ config.reqParams = JSON.parse('{$reqParamsJSON|escape:'javascript'}');
- $('.UserCountryMap').addClass('dataTable');
+ $('.UserCountryMap').addClass('dataTable');
-{literal}
- if ($('#dashboardWidgetsArea').length) {
- // dashboard mode
- var $widgetContent = $('.UserCountryMap').parents('.widgetContent');
+ {literal}
+ if ($('#dashboardWidgetsArea').length) {
+ // dashboard mode
+ var $widgetContent = $('.UserCountryMap').parents('.widgetContent');
- $widgetContent.on('widget:create', function(evt, widget) {
- visitorMap = new UserCountryMap.VisitorMap(config, widget);
- }).on('widget:maximise', function(evt) {
- visitorMap.resize();
- }).on('widget:minimise', function(evt) {
- visitorMap.resize();
- }).on('widget:destroy', function(evt) {
- visitorMap.destroy();
- });
- } else {
- // stand-alone mode
- visitorMap = new UserCountryMap.VisitorMap(config);
- }
-{/literal}
+ $widgetContent.on('widget:create',function (evt, widget) {
+ visitorMap = new UserCountryMap.VisitorMap(config, widget);
+ }).on('widget:maximise',function (evt) {
+ visitorMap.resize();
+ }).on('widget:minimise',function (evt) {
+ visitorMap.resize();
+ }).on('widget:destroy', function (evt) {
+ visitorMap.destroy();
+ });
+ } else {
+ // stand-alone mode
+ visitorMap = new UserCountryMap.VisitorMap(config);
+ }
+ {/literal}
-</script>
+ </script>
{/if}
diff --git a/plugins/UserSettings/API.php b/plugins/UserSettings/API.php
index f118dcff56..49a6c4d883 100644
--- a/plugins/UserSettings/API.php
+++ b/plugins/UserSettings/API.php
@@ -22,75 +22,74 @@ require_once PIWIK_INCLUDE_PATH . '/plugins/UserSettings/functions.php';
*/
class Piwik_UserSettings_API
{
- static private $instance = null;
- static public function getInstance()
- {
- if (self::$instance == null)
- {
- self::$instance = new self;
- }
- return self::$instance;
- }
-
- protected function getDataTable($name, $idSite, $period, $date, $segment)
- {
- Piwik::checkUserHasViewAccess( $idSite );
- $archive = Piwik_Archive::build($idSite, $period, $date, $segment );
- $dataTable = $archive->getDataTable($name);
- $dataTable->filter('Sort', array(Piwik_Archive::INDEX_NB_VISITS));
- $dataTable->queueFilter('ReplaceColumnNames');
- $dataTable->queueFilter('ReplaceSummaryRowLabel');
- return $dataTable;
- }
-
- public function getResolution( $idSite, $period, $date, $segment = false )
- {
- $dataTable = $this->getDataTable('UserSettings_resolution', $idSite, $period, $date, $segment);
- return $dataTable;
- }
-
- public function getConfiguration( $idSite, $period, $date, $segment = false )
- {
- $dataTable = $this->getDataTable('UserSettings_configuration', $idSite, $period, $date, $segment);
- $dataTable->queueFilter('ColumnCallbackReplace', array('label', 'Piwik_getConfigurationLabel'));
- return $dataTable;
- }
-
- public function getOS( $idSite, $period, $date, $segment = false, $addShortLabel = true )
- {
- $dataTable = $this->getDataTable('UserSettings_os', $idSite, $period, $date, $segment);
- // these filters are applied directly so other API methods can use GroupBy on the result of this method
- $dataTable->filter('ColumnCallbackAddMetadata', array('label', 'logo', 'Piwik_getOSLogo'));
- if ($addShortLabel)
- {
- $dataTable->filter(
- 'ColumnCallbackAddMetadata', array( 'label', 'shortLabel', 'Piwik_getOSShortLabel') );
- }
- $dataTable->filter('ColumnCallbackReplace', array( 'label', 'Piwik_getOSLabel') );
- return $dataTable;
- }
-
- /**
- * Gets a DataTable displaying number of visits by operating system family. The operating
- * system families are listed in /libs/UserAgentParser/UserAgentParser.php.
- */
- public function getOSFamily( $idSite, $period, $date, $segment = false )
- {
- $dataTable = $this->getOS($idSite, $period, $date, $segment, $addShortLabel = false);
- $dataTable->filter('GroupBy', array('label', 'Piwik_UserSettings_getOSFamily'));
- $dataTable->queueFilter('ColumnCallbackReplace', array('label', 'Piwik_Translate'));
- return $dataTable;
- }
-
- /**
- * Gets a DataTable displaying number of visits by device type (mobile vs. desktop).
- */
- public function getMobileVsDesktop( $idSite, $period, $date, $segment = false )
- {
- $dataTable = $this->getOS($idSite, $period, $date, $segment, $addShortLabel = false);
- $dataTable->filter('GroupBy', array('label', 'Piwik_UserSettings_getDeviceTypeFromOS'));
-
- // make sure the datatable has a row for mobile & desktop (if it has rows)
+ static private $instance = null;
+
+ static public function getInstance()
+ {
+ if (self::$instance == null) {
+ self::$instance = new self;
+ }
+ return self::$instance;
+ }
+
+ protected function getDataTable($name, $idSite, $period, $date, $segment)
+ {
+ Piwik::checkUserHasViewAccess($idSite);
+ $archive = Piwik_Archive::build($idSite, $period, $date, $segment);
+ $dataTable = $archive->getDataTable($name);
+ $dataTable->filter('Sort', array(Piwik_Archive::INDEX_NB_VISITS));
+ $dataTable->queueFilter('ReplaceColumnNames');
+ $dataTable->queueFilter('ReplaceSummaryRowLabel');
+ return $dataTable;
+ }
+
+ public function getResolution($idSite, $period, $date, $segment = false)
+ {
+ $dataTable = $this->getDataTable('UserSettings_resolution', $idSite, $period, $date, $segment);
+ return $dataTable;
+ }
+
+ public function getConfiguration($idSite, $period, $date, $segment = false)
+ {
+ $dataTable = $this->getDataTable('UserSettings_configuration', $idSite, $period, $date, $segment);
+ $dataTable->queueFilter('ColumnCallbackReplace', array('label', 'Piwik_getConfigurationLabel'));
+ return $dataTable;
+ }
+
+ public function getOS($idSite, $period, $date, $segment = false, $addShortLabel = true)
+ {
+ $dataTable = $this->getDataTable('UserSettings_os', $idSite, $period, $date, $segment);
+ // these filters are applied directly so other API methods can use GroupBy on the result of this method
+ $dataTable->filter('ColumnCallbackAddMetadata', array('label', 'logo', 'Piwik_getOSLogo'));
+ if ($addShortLabel) {
+ $dataTable->filter(
+ 'ColumnCallbackAddMetadata', array('label', 'shortLabel', 'Piwik_getOSShortLabel'));
+ }
+ $dataTable->filter('ColumnCallbackReplace', array('label', 'Piwik_getOSLabel'));
+ return $dataTable;
+ }
+
+ /**
+ * Gets a DataTable displaying number of visits by operating system family. The operating
+ * system families are listed in /libs/UserAgentParser/UserAgentParser.php.
+ */
+ public function getOSFamily($idSite, $period, $date, $segment = false)
+ {
+ $dataTable = $this->getOS($idSite, $period, $date, $segment, $addShortLabel = false);
+ $dataTable->filter('GroupBy', array('label', 'Piwik_UserSettings_getOSFamily'));
+ $dataTable->queueFilter('ColumnCallbackReplace', array('label', 'Piwik_Translate'));
+ return $dataTable;
+ }
+
+ /**
+ * Gets a DataTable displaying number of visits by device type (mobile vs. desktop).
+ */
+ public function getMobileVsDesktop($idSite, $period, $date, $segment = false)
+ {
+ $dataTable = $this->getOS($idSite, $period, $date, $segment, $addShortLabel = false);
+ $dataTable->filter('GroupBy', array('label', 'Piwik_UserSettings_getDeviceTypeFromOS'));
+
+ // make sure the datatable has a row for mobile & desktop (if it has rows)
$dataTables = array($dataTable);
if ($dataTable instanceof Piwik_DataTable_Array) {
$dataTables = $dataTable->getArray();
@@ -98,7 +97,7 @@ class Piwik_UserSettings_API
$requiredRows = array(
'General_Desktop' => Piwik_Archive::INDEX_NB_VISITS,
- 'General_Mobile' => Piwik_Archive::INDEX_NB_VISITS
+ 'General_Mobile' => Piwik_Archive::INDEX_NB_VISITS
);
foreach ($dataTables AS $table) {
@@ -109,127 +108,126 @@ class Piwik_UserSettings_API
$row = $table->getRowFromLabel($requiredRow);
if (empty($row)) {
$table->addRowsFromSimpleArray(array(
- array('label' => $requiredRow, $key => 0)
- ));
+ array('label' => $requiredRow, $key => 0)
+ ));
}
}
}
- // set the logo metadata
- $dataTable->queueFilter('MetadataCallbackReplace',
- array('logo', 'Piwik_UserSettings_getDeviceTypeImg', null, array('label')));
-
- // translate the labels
- $dataTable->queueFilter('ColumnCallbackReplace', array('label', 'Piwik_Translate'));
-
- return $dataTable;
- }
-
- public function getBrowserVersion( $idSite, $period, $date, $segment = false )
- {
- $dataTable = $this->getDataTable('UserSettings_browser', $idSite, $period, $date, $segment);
- $dataTable->filter('ColumnCallbackAddMetadata', array('label', 'logo', 'Piwik_getBrowsersLogo'));
- $dataTable->filter('ColumnCallbackAddMetadata', array('label', 'shortLabel', 'Piwik_getBrowserShortLabel'));
- $dataTable->filter('ColumnCallbackReplace', array('label', 'Piwik_getBrowserLabel'));
- return $dataTable;
- }
-
- /**
- * Gets a DataTable displaying number of visits by browser (ie, Firefox, Chrome, etc.).
- * The browser version is not included in this report.
- */
- public function getBrowser( $idSite, $period, $date, $segment = false )
- {
- $dataTable = $this->getDataTable('UserSettings_browser', $idSite, $period, $date, $segment);
- $dataTable->filter('ColumnCallbackAddMetadata', array('label', 'logo', 'Piwik_getBrowsersLogo'));
- $dataTable->filter('ColumnCallbackReplace', array('label', 'Piwik_getBrowserLabel'));
-
- $getBrowserFromBrowserVersion = 'Piwik_UserSettings_getBrowserFromBrowserVersion';
- $dataTable->filter('GroupBy', array('label', $getBrowserFromBrowserVersion));
-
- return $dataTable;
- }
-
- public function getBrowserType( $idSite, $period, $date, $segment = false )
- {
- $dataTable = $this->getDataTable('UserSettings_browserType', $idSite, $period, $date, $segment);
- $dataTable->queueFilter('ColumnCallbackAddMetadata', array('label', 'shortLabel', 'ucfirst'));
- $dataTable->queueFilter('ColumnCallbackReplace', array('label', 'Piwik_getBrowserTypeLabel'));
- return $dataTable;
- }
-
- public function getWideScreen( $idSite, $period, $date, $segment = false )
- {
- $dataTable = $this->getDataTable('UserSettings_wideScreen', $idSite, $period, $date, $segment);
- $dataTable->queueFilter('ColumnCallbackAddMetadata', array('label', 'logo', 'Piwik_getScreensLogo'));
- $dataTable->queueFilter('ColumnCallbackReplace', array('label', 'ucfirst'));
- return $dataTable;
- }
-
- public function getPlugin( $idSite, $period, $date, $segment = false )
- {
- // fetch all archive data required
- $dataTable = $this->getDataTable('UserSettings_plugin', $idSite, $period, $date, $segment);
- $browserTypes = $this->getDataTable('UserSettings_browserType', $idSite, $period, $date, $segment);
- $archive = Piwik_Archive::build($idSite, $period, $date, $segment);
- $visitsSums = $archive->getNumeric('nb_visits');
-
- // check whether given tables are arrays
- if($dataTable instanceof Piwik_DataTable_Array) {
- $tableArray = $dataTable->getArray();
- $browserTypesArray = $browserTypes->getArray();
- $visitSumsArray = $visitsSums->getArray();
- } else {
- $tableArray = Array($dataTable);
- $browserTypesArray = Array($browserTypes);
- $visitSumsArray = Array($visitsSums);
- }
-
- // walk through the results and calculate the percentage
- foreach($tableArray as $key => $table) {
-
- // get according browserType table
- foreach($browserTypesArray AS $k => $browsers) {
- if($k == $key) {
- $browserType = $browsers;
- }
- }
-
- // get according visitsSum
- foreach($visitSumsArray AS $k => $visits) {
- if($k == $key) {
- if(is_object($visits)) {
- $visitsSumTotal = (float)$visits->getFirstRow()->getColumn(0);
- } else {
- $visitsSumTotal = (float)$visits;
- }
- }
- }
-
- // Calculate percentage, but ignore IE users because plugin detection doesn't work on IE
- $ieVisits = 0;
-
- $ieStats = $browserType->getRowFromLabel('ie');
- if($ieStats !== false)
- {
- $ieVisits = $ieStats->getColumn(Piwik_Archive::INDEX_NB_VISITS);
- }
-
- $visitsSum = $visitsSumTotal - $ieVisits;
-
- // The filter must be applied now so that the new column can
- // be sorted by the generic filters (applied right after this loop exits)
- $table->filter('ColumnCallbackAddColumnPercentage', array('nb_visits_percentage', Piwik_Archive::INDEX_NB_VISITS, $visitsSum, 1));
- $table->filter('RangeCheck', array('nb_visits_percentage'));
- }
-
- $dataTable->queueFilter('ColumnCallbackAddMetadata', array('label', 'logo', 'Piwik_getPluginsLogo'));
- $dataTable->queueFilter('ColumnCallbackReplace', array('label', 'ucfirst'));
-
- return $dataTable;
- }
-
- public function getLanguage( $idSite, $period, $date, $segment = false )
+ // set the logo metadata
+ $dataTable->queueFilter('MetadataCallbackReplace',
+ array('logo', 'Piwik_UserSettings_getDeviceTypeImg', null, array('label')));
+
+ // translate the labels
+ $dataTable->queueFilter('ColumnCallbackReplace', array('label', 'Piwik_Translate'));
+
+ return $dataTable;
+ }
+
+ public function getBrowserVersion($idSite, $period, $date, $segment = false)
+ {
+ $dataTable = $this->getDataTable('UserSettings_browser', $idSite, $period, $date, $segment);
+ $dataTable->filter('ColumnCallbackAddMetadata', array('label', 'logo', 'Piwik_getBrowsersLogo'));
+ $dataTable->filter('ColumnCallbackAddMetadata', array('label', 'shortLabel', 'Piwik_getBrowserShortLabel'));
+ $dataTable->filter('ColumnCallbackReplace', array('label', 'Piwik_getBrowserLabel'));
+ return $dataTable;
+ }
+
+ /**
+ * Gets a DataTable displaying number of visits by browser (ie, Firefox, Chrome, etc.).
+ * The browser version is not included in this report.
+ */
+ public function getBrowser($idSite, $period, $date, $segment = false)
+ {
+ $dataTable = $this->getDataTable('UserSettings_browser', $idSite, $period, $date, $segment);
+ $dataTable->filter('ColumnCallbackAddMetadata', array('label', 'logo', 'Piwik_getBrowsersLogo'));
+ $dataTable->filter('ColumnCallbackReplace', array('label', 'Piwik_getBrowserLabel'));
+
+ $getBrowserFromBrowserVersion = 'Piwik_UserSettings_getBrowserFromBrowserVersion';
+ $dataTable->filter('GroupBy', array('label', $getBrowserFromBrowserVersion));
+
+ return $dataTable;
+ }
+
+ public function getBrowserType($idSite, $period, $date, $segment = false)
+ {
+ $dataTable = $this->getDataTable('UserSettings_browserType', $idSite, $period, $date, $segment);
+ $dataTable->queueFilter('ColumnCallbackAddMetadata', array('label', 'shortLabel', 'ucfirst'));
+ $dataTable->queueFilter('ColumnCallbackReplace', array('label', 'Piwik_getBrowserTypeLabel'));
+ return $dataTable;
+ }
+
+ public function getWideScreen($idSite, $period, $date, $segment = false)
+ {
+ $dataTable = $this->getDataTable('UserSettings_wideScreen', $idSite, $period, $date, $segment);
+ $dataTable->queueFilter('ColumnCallbackAddMetadata', array('label', 'logo', 'Piwik_getScreensLogo'));
+ $dataTable->queueFilter('ColumnCallbackReplace', array('label', 'ucfirst'));
+ return $dataTable;
+ }
+
+ public function getPlugin($idSite, $period, $date, $segment = false)
+ {
+ // fetch all archive data required
+ $dataTable = $this->getDataTable('UserSettings_plugin', $idSite, $period, $date, $segment);
+ $browserTypes = $this->getDataTable('UserSettings_browserType', $idSite, $period, $date, $segment);
+ $archive = Piwik_Archive::build($idSite, $period, $date, $segment);
+ $visitsSums = $archive->getNumeric('nb_visits');
+
+ // check whether given tables are arrays
+ if ($dataTable instanceof Piwik_DataTable_Array) {
+ $tableArray = $dataTable->getArray();
+ $browserTypesArray = $browserTypes->getArray();
+ $visitSumsArray = $visitsSums->getArray();
+ } else {
+ $tableArray = Array($dataTable);
+ $browserTypesArray = Array($browserTypes);
+ $visitSumsArray = Array($visitsSums);
+ }
+
+ // walk through the results and calculate the percentage
+ foreach ($tableArray as $key => $table) {
+
+ // get according browserType table
+ foreach ($browserTypesArray AS $k => $browsers) {
+ if ($k == $key) {
+ $browserType = $browsers;
+ }
+ }
+
+ // get according visitsSum
+ foreach ($visitSumsArray AS $k => $visits) {
+ if ($k == $key) {
+ if (is_object($visits)) {
+ $visitsSumTotal = (float)$visits->getFirstRow()->getColumn(0);
+ } else {
+ $visitsSumTotal = (float)$visits;
+ }
+ }
+ }
+
+ // Calculate percentage, but ignore IE users because plugin detection doesn't work on IE
+ $ieVisits = 0;
+
+ $ieStats = $browserType->getRowFromLabel('ie');
+ if ($ieStats !== false) {
+ $ieVisits = $ieStats->getColumn(Piwik_Archive::INDEX_NB_VISITS);
+ }
+
+ $visitsSum = $visitsSumTotal - $ieVisits;
+
+ // The filter must be applied now so that the new column can
+ // be sorted by the generic filters (applied right after this loop exits)
+ $table->filter('ColumnCallbackAddColumnPercentage', array('nb_visits_percentage', Piwik_Archive::INDEX_NB_VISITS, $visitsSum, 1));
+ $table->filter('RangeCheck', array('nb_visits_percentage'));
+ }
+
+ $dataTable->queueFilter('ColumnCallbackAddMetadata', array('label', 'logo', 'Piwik_getPluginsLogo'));
+ $dataTable->queueFilter('ColumnCallbackReplace', array('label', 'ucfirst'));
+
+ return $dataTable;
+ }
+
+ public function getLanguage($idSite, $period, $date, $segment = false)
{
$dataTable = $this->getDataTable('UserSettings_language', $idSite, $period, $date, $segment);
$dataTable->filter('ColumnCallbackReplace', array('label', 'Piwik_LanguageTranslate'));
diff --git a/plugins/UserSettings/Controller.php b/plugins/UserSettings/Controller.php
index 82c23fe870..d4e39fcc3b 100644
--- a/plugins/UserSettings/Controller.php
+++ b/plugins/UserSettings/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_UserSettings
*/
@@ -13,181 +13,181 @@
*
* @package Piwik_UserSettings
*/
-class Piwik_UserSettings_Controller extends Piwik_Controller
+class Piwik_UserSettings_Controller extends Piwik_Controller
{
- /** The set of related reports displayed under the 'Operating Systems' header. */
- private $osRelatedReports = null;
-
- public function __construct()
- {
- parent::__construct();
- $this->osRelatedReports = array(
- 'UserSettings.getOSFamily' => Piwik_Translate('UserSettings_OperatingSystemFamily'),
- 'UserSettings.getOS' => Piwik_Translate('UserSettings_OperatingSystems')
- );
- }
-
- function index()
- {
- $view = Piwik_View::factory('index');
-
- $view->dataTablePlugin = $this->getPlugin( true );
- $view->dataTableResolution = $this->getResolution( true );
- $view->dataTableConfiguration = $this->getConfiguration( true );
- $view->dataTableOS = $this->getOS( true );
- $view->dataTableBrowser = $this->getBrowser( true );
- $view->dataTableBrowserType = $this->getBrowserType ( true );
- $view->dataTableMobileVsDesktop = $this->getMobileVsDesktop( true );
- $view->dataTableBrowserLanguage = $this->getLanguage( true );
-
- echo $view->render();
- }
-
- function getResolution( $fetch = false)
- {
- $view = $this->getStandardDataTableUserSettings(
- __FUNCTION__,
- 'UserSettings.getResolution'
- );
- $view->setColumnTranslation('label', Piwik_Translate('UserSettings_ColumnResolution'));
- return $this->renderView($view, $fetch);
- }
-
- function getConfiguration( $fetch = false)
- {
- $view = $this->getStandardDataTableUserSettings(
- __FUNCTION__,
- 'UserSettings.getConfiguration'
- );
- $view->setColumnTranslation('label', Piwik_Translate('UserSettings_ColumnConfiguration'));
- $view->setLimit( 3 );
- return $this->renderView($view, $fetch);
- }
-
- function getOS( $fetch = false)
- {
- $view = $this->getStandardDataTableUserSettings(
- __FUNCTION__,
- 'UserSettings.getOS'
- );
- $view->setColumnTranslation('label', Piwik_Translate('UserSettings_ColumnOperatingSystem'));
- $view->addRelatedReports(Piwik_Translate('UserSettings_OperatingSystems'), $this->osRelatedReports);
- return $this->renderView($view, $fetch);
- }
-
- /**
- * Returns or echos a report displaying the number of visits by operating system family.
- */
- public function getOSFamily( $fetch = false )
- {
- $view = $this->getStandardDataTableUserSettings(__FUNCTION__, 'UserSettings.getOSFamily');
- $view->setColumnTranslation('label', Piwik_Translate('UserSettings_OperatingSystemFamily'));
- $view->addRelatedReports(Piwik_Translate('UserSettings_OperatingSystemFamily'), $this->osRelatedReports);
- return $this->renderView($view, $fetch);
- }
-
- function getBrowserVersion( $fetch = false)
- {
- $view = $this->getStandardDataTableUserSettings(
- __FUNCTION__,
- 'UserSettings.getBrowserVersion'
- );
- $view->setColumnTranslation('label', Piwik_Translate('UserSettings_ColumnBrowserVersion'));
- $view->setGraphLimit(7);
- $view->addRelatedReports(Piwik_Translate('UserSettings_ColumnBrowserVersion'), array(
- 'UserSettings.getBrowser' => Piwik_Translate('UserSettings_Browsers')
- ));
- return $this->renderView($view, $fetch);
- }
-
- /**
- * Returns or echos a report displaying the number of visits by browser type. The browser
- * version is not included in this report.
- */
- public function getBrowser( $fetch = false )
- {
- $view = $this->getStandardDataTableUserSettings(__FUNCTION__, 'UserSettings.getBrowser');
- $view->setColumnTranslation('label', Piwik_Translate('UserSettings_ColumnBrowser'));
- $view->setGraphLimit(7);
- $view->addRelatedReports(Piwik_Translate('UserSettings_Browsers'), array(
- 'UserSettings.getBrowserVersion' => Piwik_Translate('UserSettings_ColumnBrowserVersion')
- ));
- return $this->renderView($view, $fetch);
- }
-
- function getBrowserType ( $fetch = false)
- {
- $view = $this->getStandardDataTableUserSettings(
- __FUNCTION__,
- 'UserSettings.getBrowserType',
- 'graphPie'
- );
- $view->setColumnTranslation('label', Piwik_Translate('UserSettings_ColumnBrowserFamily'));
- $view->disableOffsetInformationAndPaginationControls();
- return $this->renderView($view, $fetch);
- }
-
- function getWideScreen( $fetch = false)
- {
- $view = $this->getStandardDataTableUserSettings(
- __FUNCTION__,
- 'UserSettings.getWideScreen'
- );
- $view->setColumnTranslation('label', Piwik_Translate('UserSettings_ColumnTypeOfScreen'));
- $view->disableOffsetInformationAndPaginationControls();
- $view->addRelatedReports(Piwik_Translate('UserSettings_ColumnTypeOfScreen'), array(
- 'UserSettings.getMobileVsDesktop' => Piwik_Translate('UserSettings_MobileVsDesktop')
- ));
- return $this->renderView($view, $fetch);
- }
-
- /**
- * Returns or echos a report displaying the number of visits by device type (Mobile or Desktop).
- */
- public function getMobileVsDesktop( $fetch = false )
- {
- $view = $this->getStandardDataTableUserSettings(__FUNCTION__, 'UserSettings.getMobileVsDesktop');
- $view->setColumnTranslation('label', Piwik_Translate('UserSettings_MobileVsDesktop'));
- $view->addRelatedReports(Piwik_Translate('UserSettings_MobileVsDesktop'), array(
- 'UserSettings.getWideScreen' => Piwik_Translate('UserSettings_ColumnTypeOfScreen')
- ));
- return $this->renderView($view, $fetch);
- }
-
- function getPlugin( $fetch = false)
- {
- $view = $this->getStandardDataTableUserSettings(
- __FUNCTION__,
- 'UserSettings.getPlugin'
- );
- $view->disableShowAllViewsIcons();
- $view->disableShowAllColumns();
- $view->disableOffsetInformationAndPaginationControls();
- $view->setColumnsToDisplay( array('label','nb_visits_percentage','nb_visits') );
- $view->setColumnTranslation('label', Piwik_Translate('UserSettings_ColumnPlugin'));
- $view->setColumnTranslation('nb_visits_percentage', str_replace(' ', '&nbsp;', Piwik_Translate('General_ColumnPercentageVisits')));
- $view->setSortedColumn('nb_visits_percentage');
- $view->setLimit( 10 );
- $view->setFooterMessage( Piwik_Translate('UserSettings_PluginDetectionDoesNotWorkInIE'));
- return $this->renderView($view, $fetch);
- }
-
- protected function getStandardDataTableUserSettings( $currentControllerAction,
- $APItoCall,
- $defaultDatatableType = null )
- {
- $view = Piwik_ViewDataTable::factory( $defaultDatatableType);
- $view->init( $this->pluginName, $currentControllerAction, $APItoCall );
- $view->disableSearchBox();
- $view->disableExcludeLowPopulation();
- $view->setLimit( 5 );
- $view->setGraphLimit(5);
-
- $this->setPeriodVariablesView($view);
- $this->setMetricsVariablesView($view);
-
- return $view;
- }
+ /** The set of related reports displayed under the 'Operating Systems' header. */
+ private $osRelatedReports = null;
+
+ public function __construct()
+ {
+ parent::__construct();
+ $this->osRelatedReports = array(
+ 'UserSettings.getOSFamily' => Piwik_Translate('UserSettings_OperatingSystemFamily'),
+ 'UserSettings.getOS' => Piwik_Translate('UserSettings_OperatingSystems')
+ );
+ }
+
+ function index()
+ {
+ $view = Piwik_View::factory('index');
+
+ $view->dataTablePlugin = $this->getPlugin(true);
+ $view->dataTableResolution = $this->getResolution(true);
+ $view->dataTableConfiguration = $this->getConfiguration(true);
+ $view->dataTableOS = $this->getOS(true);
+ $view->dataTableBrowser = $this->getBrowser(true);
+ $view->dataTableBrowserType = $this->getBrowserType(true);
+ $view->dataTableMobileVsDesktop = $this->getMobileVsDesktop(true);
+ $view->dataTableBrowserLanguage = $this->getLanguage(true);
+
+ echo $view->render();
+ }
+
+ function getResolution($fetch = false)
+ {
+ $view = $this->getStandardDataTableUserSettings(
+ __FUNCTION__,
+ 'UserSettings.getResolution'
+ );
+ $view->setColumnTranslation('label', Piwik_Translate('UserSettings_ColumnResolution'));
+ return $this->renderView($view, $fetch);
+ }
+
+ function getConfiguration($fetch = false)
+ {
+ $view = $this->getStandardDataTableUserSettings(
+ __FUNCTION__,
+ 'UserSettings.getConfiguration'
+ );
+ $view->setColumnTranslation('label', Piwik_Translate('UserSettings_ColumnConfiguration'));
+ $view->setLimit(3);
+ return $this->renderView($view, $fetch);
+ }
+
+ function getOS($fetch = false)
+ {
+ $view = $this->getStandardDataTableUserSettings(
+ __FUNCTION__,
+ 'UserSettings.getOS'
+ );
+ $view->setColumnTranslation('label', Piwik_Translate('UserSettings_ColumnOperatingSystem'));
+ $view->addRelatedReports(Piwik_Translate('UserSettings_OperatingSystems'), $this->osRelatedReports);
+ return $this->renderView($view, $fetch);
+ }
+
+ /**
+ * Returns or echos a report displaying the number of visits by operating system family.
+ */
+ public function getOSFamily($fetch = false)
+ {
+ $view = $this->getStandardDataTableUserSettings(__FUNCTION__, 'UserSettings.getOSFamily');
+ $view->setColumnTranslation('label', Piwik_Translate('UserSettings_OperatingSystemFamily'));
+ $view->addRelatedReports(Piwik_Translate('UserSettings_OperatingSystemFamily'), $this->osRelatedReports);
+ return $this->renderView($view, $fetch);
+ }
+
+ function getBrowserVersion($fetch = false)
+ {
+ $view = $this->getStandardDataTableUserSettings(
+ __FUNCTION__,
+ 'UserSettings.getBrowserVersion'
+ );
+ $view->setColumnTranslation('label', Piwik_Translate('UserSettings_ColumnBrowserVersion'));
+ $view->setGraphLimit(7);
+ $view->addRelatedReports(Piwik_Translate('UserSettings_ColumnBrowserVersion'), array(
+ 'UserSettings.getBrowser' => Piwik_Translate('UserSettings_Browsers')
+ ));
+ return $this->renderView($view, $fetch);
+ }
+
+ /**
+ * Returns or echos a report displaying the number of visits by browser type. The browser
+ * version is not included in this report.
+ */
+ public function getBrowser($fetch = false)
+ {
+ $view = $this->getStandardDataTableUserSettings(__FUNCTION__, 'UserSettings.getBrowser');
+ $view->setColumnTranslation('label', Piwik_Translate('UserSettings_ColumnBrowser'));
+ $view->setGraphLimit(7);
+ $view->addRelatedReports(Piwik_Translate('UserSettings_Browsers'), array(
+ 'UserSettings.getBrowserVersion' => Piwik_Translate('UserSettings_ColumnBrowserVersion')
+ ));
+ return $this->renderView($view, $fetch);
+ }
+
+ function getBrowserType($fetch = false)
+ {
+ $view = $this->getStandardDataTableUserSettings(
+ __FUNCTION__,
+ 'UserSettings.getBrowserType',
+ 'graphPie'
+ );
+ $view->setColumnTranslation('label', Piwik_Translate('UserSettings_ColumnBrowserFamily'));
+ $view->disableOffsetInformationAndPaginationControls();
+ return $this->renderView($view, $fetch);
+ }
+
+ function getWideScreen($fetch = false)
+ {
+ $view = $this->getStandardDataTableUserSettings(
+ __FUNCTION__,
+ 'UserSettings.getWideScreen'
+ );
+ $view->setColumnTranslation('label', Piwik_Translate('UserSettings_ColumnTypeOfScreen'));
+ $view->disableOffsetInformationAndPaginationControls();
+ $view->addRelatedReports(Piwik_Translate('UserSettings_ColumnTypeOfScreen'), array(
+ 'UserSettings.getMobileVsDesktop' => Piwik_Translate('UserSettings_MobileVsDesktop')
+ ));
+ return $this->renderView($view, $fetch);
+ }
+
+ /**
+ * Returns or echos a report displaying the number of visits by device type (Mobile or Desktop).
+ */
+ public function getMobileVsDesktop($fetch = false)
+ {
+ $view = $this->getStandardDataTableUserSettings(__FUNCTION__, 'UserSettings.getMobileVsDesktop');
+ $view->setColumnTranslation('label', Piwik_Translate('UserSettings_MobileVsDesktop'));
+ $view->addRelatedReports(Piwik_Translate('UserSettings_MobileVsDesktop'), array(
+ 'UserSettings.getWideScreen' => Piwik_Translate('UserSettings_ColumnTypeOfScreen')
+ ));
+ return $this->renderView($view, $fetch);
+ }
+
+ function getPlugin($fetch = false)
+ {
+ $view = $this->getStandardDataTableUserSettings(
+ __FUNCTION__,
+ 'UserSettings.getPlugin'
+ );
+ $view->disableShowAllViewsIcons();
+ $view->disableShowAllColumns();
+ $view->disableOffsetInformationAndPaginationControls();
+ $view->setColumnsToDisplay(array('label', 'nb_visits_percentage', 'nb_visits'));
+ $view->setColumnTranslation('label', Piwik_Translate('UserSettings_ColumnPlugin'));
+ $view->setColumnTranslation('nb_visits_percentage', str_replace(' ', '&nbsp;', Piwik_Translate('General_ColumnPercentageVisits')));
+ $view->setSortedColumn('nb_visits_percentage');
+ $view->setLimit(10);
+ $view->setFooterMessage(Piwik_Translate('UserSettings_PluginDetectionDoesNotWorkInIE'));
+ return $this->renderView($view, $fetch);
+ }
+
+ protected function getStandardDataTableUserSettings($currentControllerAction,
+ $APItoCall,
+ $defaultDatatableType = null)
+ {
+ $view = Piwik_ViewDataTable::factory($defaultDatatableType);
+ $view->init($this->pluginName, $currentControllerAction, $APItoCall);
+ $view->disableSearchBox();
+ $view->disableExcludeLowPopulation();
+ $view->setLimit(5);
+ $view->setGraphLimit(5);
+
+ $this->setPeriodVariablesView($view);
+ $this->setMetricsVariablesView($view);
+
+ return $view;
+ }
/**
* Renders datatable for browser language
@@ -196,17 +196,17 @@ class Piwik_UserSettings_Controller extends Piwik_Controller
*
* @return string|void
*/
- public function getLanguage( $fetch = false)
+ public function getLanguage($fetch = false)
{
$view = Piwik_ViewDataTable::factory();
- $view->init( $this->pluginName, __FUNCTION__, "UserSettings.getLanguage" );
+ $view->init($this->pluginName, __FUNCTION__, "UserSettings.getLanguage");
$view->disableExcludeLowPopulation();
- $view->setColumnsToDisplay( array('label','nb_visits') );
+ $view->setColumnsToDisplay(array('label', 'nb_visits'));
$view->setColumnTranslation('label', Piwik_Translate('General_Language'));
$view->setSortedColumn('nb_visits');
$view->disableSearchBox();
- $view->setLimit( 5 );
+ $view->setLimit(5);
return $this->renderView($view, $fetch);
}
diff --git a/plugins/UserSettings/UserSettings.php b/plugins/UserSettings/UserSettings.php
index b9a2ec5b20..d46b952c56 100644
--- a/plugins/UserSettings/UserSettings.php
+++ b/plugins/UserSettings/UserSettings.php
@@ -15,417 +15,409 @@
*/
class Piwik_UserSettings extends Piwik_Plugin
{
- public function getInformation()
- {
- return array(
- 'description' => Piwik_Translate('UserSettings_PluginDescription'),
- 'author' => 'Piwik',
- 'author_homepage' => 'http://piwik.org/',
- 'version' => Piwik_Version::VERSION,
- );
- }
-
- /*
- * Mapping between the browser family shortcode and the displayed name
- */
- static public $browserType_display = array(
- 'ie' => 'Trident (IE)',
- 'gecko' => 'Gecko (Firefox)',
- 'khtml' => 'KHTML (Konqueror)',
- 'webkit' => 'WebKit (Safari, Chrome)',
- 'opera' => 'Presto (Opera)',
- );
-
- /*
- * Defines API reports.
- * Also used to define Widgets.
- *
- * @array Category, Report Name, API Module, API action, Translated column name,
- * $segment, $sqlSegment, $acceptedValues, $sqlFilter
- */
- protected $reportMetadata = array(
- array( 'UserSettings_VisitorSettings',
- 'UserSettings_WidgetResolutions',
- 'UserSettings',
- 'getResolution',
- 'UserSettings_ColumnResolution',
- 'resolution',
- 'log_visit.config_resolution',
- '1280x1024, 800x600, etc.',
- null,),
-
- array( 'UserSettings_VisitorSettings',
- 'UserSettings_WidgetBrowsers',
- 'UserSettings',
- 'getBrowser',
- 'UserSettings_ColumnBrowser',
- 'browserName',
- 'log_visit.config_browser_name',
- 'FF, IE, CH, SF, OP, etc.',
- null,),
-
- // browser version
- array( 'UserSettings_VisitorSettings',
- 'UserSettings_WidgetBrowserVersion',
- 'UserSettings',
- 'getBrowserVersion',
- 'UserSettings_ColumnBrowserVersion',
- 'browserVersion',
- 'log_visit.config_browser_version',
- '1.0, 8.0, etc.',
- null,),
-
- array( 'UserSettings_VisitorSettings',
- 'UserSettings_WidgetBrowserFamilies',
- 'UserSettings',
- 'getBrowserType',
- 'UserSettings_ColumnBrowserFamily',
- null,
- null,
- null,
- null,),
-
- array( 'UserSettings_VisitorSettings',
- 'UserSettings_WidgetPlugins',
- 'UserSettings',
- 'getPlugin',
- 'UserSettings_ColumnPlugin',
- null,
- null,
- null,
- null,),
-
- array( 'UserSettings_VisitorSettings',
- 'UserSettings_WidgetWidescreen',
- 'UserSettings',
- 'getWideScreen',
- 'UserSettings_ColumnTypeOfScreen',
- null,
- null,
- null,
- null,),
-
- array( 'UserSettings_VisitorSettings',
- 'UserSettings_WidgetOperatingSystems',
- 'UserSettings',
- 'getOS',
- 'UserSettings_ColumnOperatingSystem',
- 'operatingSystem',
- 'log_visit.config_os',
- 'WXP, WI7, MAC, LIN, AND, IPD, etc.',
- null,),
-
- array( 'UserSettings_VisitorSettings',
- 'UserSettings_WidgetGlobalVisitors',
- 'UserSettings',
- 'getConfiguration',
- 'UserSettings_ColumnConfiguration',
- null,
- null,
- null,
- null),
-
- // operating system family
- array( 'UserSettings_VisitorSettings',
- 'UserSettings_WidgetOperatingSystemFamily',
- 'UserSettings',
- 'getOSFamily',
- 'UserSettings_OperatingSystemFamily',
- null,
- null,
- null,
- null),
-
- // device type
- array( 'UserSettings_VisitorSettings',
- 'UserSettings_MobileVsDesktop',
- 'UserSettings',
- 'getMobileVsDesktop',
- 'UserSettings_MobileVsDesktop',
- null,
- null,
- null,
- null),
-
- // Browser language
- array( 'UserSettings_VisitorSettings',
- 'UserSettings_BrowserLanguage',
- 'UserSettings',
- 'getLanguage',
- 'General_Language',
- null,
- null,
- null,
- null),
- );
-
- /*
- * List of hooks
- */
- function getListHooksRegistered()
- {
- $hooks = array(
- 'ArchiveProcessing_Day.compute' => 'archiveDay',
- 'ArchiveProcessing_Period.compute' => 'archivePeriod',
- 'WidgetsList.add' => 'addWidgets',
- 'Menu.add' => 'addMenu',
- 'API.getReportMetadata' => 'getReportMetadata',
- 'API.getSegmentsMetadata' => 'getSegmentsMetadata',
- );
- return $hooks;
- }
-
- /*
- * Registers reports metadata
- *
- * @param Piwik_Event_Notification $notification notification object
- */
- public function getReportMetadata($notification)
- {
- $reports = &$notification->getNotificationObject();
-
- $i = 0;
- foreach($this->reportMetadata as $report)
- {
- list( $category, $name, $apiModule, $apiAction, $columnName ) = $report;
- if($category == false) continue;
-
- $report = array(
- 'category' => Piwik_Translate($category),
- 'name' => Piwik_Translate($name),
- 'module' => $apiModule,
- 'action' => $apiAction,
- 'dimension' => Piwik_Translate($columnName),
- 'order' => $i++
- );
-
- $translation = $name.'Documentation';
- $translated = Piwik_Translate($translation, '<br />');
- if ($translated != $translation) {
- $report['documentation'] = $translated;
- }
-
- // getPlugin returns only a subset of metrics
- if($apiAction == 'getPlugin')
- {
- $report['metrics'] = array(
- 'nb_visits',
- 'nb_visits_percentage' => Piwik_Translate('General_ColumnPercentageVisits')
- );
- // There is no processedMetrics for this report
- $report['processedMetrics'] = array();
- // Always has same number of rows, 1 per plugin
- $report['constantRowsCount'] = true;
- }
- $reports[] = $report;
- }
- }
-
- /**
- * Get segments meta data
- *
- * @param Piwik_Event_Notification $notification notification object
- */
- public function getSegmentsMetadata($notification)
- {
- $segments =& $notification->getNotificationObject();
- foreach($this->reportMetadata as $report)
- {
- @list( $category, $name, $apiModule, $apiAction, $columnName, $segment, $sqlSegment, $acceptedValues, $sqlFilter ) = $report;
- if(empty($segment)) continue;
- $segments[] = array(
- 'type' => 'dimension',
- 'category' => 'Visit',
- 'name' => $columnName,
- 'segment' => $segment,
- 'acceptedValues' => $acceptedValues,
- 'sqlSegment' => $sqlSegment,
- 'sqlFilter' => isset($sqlFilter) ? $sqlFilter : false,
- );
- }
- }
-
- /**
- * Adds the various User Settings widgets
- */
- function addWidgets()
- {
- // in this case, Widgets have same names as API reports
- foreach($this->reportMetadata as $report)
- {
- list( $category, $name, $controllerName, $controllerAction ) = $report;
- if($category == false) continue;
- Piwik_AddWidget( $category, $name, $controllerName, $controllerAction );
- }
- }
-
- /**
- * Adds the User Settings menu
- */
- function addMenu()
- {
- Piwik_AddMenu('General_Visitors', 'UserSettings_SubmenuSettings', array('module' => 'UserSettings', 'action' => 'index'));
- }
-
- /**
- * Daily archive of User Settings report. Processes reports for Visits by Resolution,
- * by Browser, Browser family, etc. Some reports are built from the logs, some reports
- * are superset of an existing report (eg. Browser family is built from the Browser report)
- *
- * @param Piwik_Event_Notification $notification notification object
- * @return void
- */
- function archiveDay( $notification )
- {
- require_once PIWIK_INCLUDE_PATH . '/plugins/UserSettings/functions.php';
- $maximumRowsInDataTable = Piwik_Config::getInstance()->General['datatable_archiving_maximum_rows_standard'];
- $columnToSortByBeforeTruncation = Piwik_Archive::INDEX_NB_VISITS;
-
- $archiveProcessing = $notification->getNotificationObject();
-
- if(!$archiveProcessing->shouldProcessReportsForPlugin($this->getPluginName())) return;
-
- $this->archiveProcessing = $archiveProcessing;
-
- $recordName = 'UserSettings_configuration';
- $labelSQL = "CONCAT(log_visit.config_os, ';', log_visit.config_browser_name, ';', log_visit.config_resolution)";
- $interestByConfiguration = $archiveProcessing->getArrayInterestForLabel($labelSQL);
-
- $tableConfiguration = $archiveProcessing->getDataTableFromArray($interestByConfiguration);
- $archiveProcessing->insertBlobRecord($recordName, $tableConfiguration->getSerialized($maximumRowsInDataTable, null, $columnToSortByBeforeTruncation));
- destroy($tableConfiguration);
-
- $recordName = 'UserSettings_os';
- $labelSQL = "log_visit.config_os";
- $interestByOs = $archiveProcessing->getArrayInterestForLabel($labelSQL);
- $tableOs = $archiveProcessing->getDataTableFromArray($interestByOs);
- $archiveProcessing->insertBlobRecord($recordName, $tableOs->getSerialized($maximumRowsInDataTable, null, $columnToSortByBeforeTruncation));
- destroy($tableOs);
-
- $recordName = 'UserSettings_browser';
- $labelSQL = "CONCAT(log_visit.config_browser_name, ';', log_visit.config_browser_version)";
- $interestByBrowser = $archiveProcessing->getArrayInterestForLabel($labelSQL);
- $tableBrowser = $archiveProcessing->getDataTableFromArray($interestByBrowser);
- $archiveProcessing->insertBlobRecord($recordName, $tableBrowser->getSerialized($maximumRowsInDataTable, null, $columnToSortByBeforeTruncation));
-
- $recordName = 'UserSettings_browserType';
- $tableBrowserType = $this->getTableBrowserByType($tableBrowser);
- $archiveProcessing->insertBlobRecord($recordName, $tableBrowserType->getSerialized());
- destroy($tableBrowser);
- destroy($tableBrowserType);
-
- $recordName = 'UserSettings_resolution';
- $labelSQL = "log_visit.config_resolution";
- $interestByResolution = $archiveProcessing->getArrayInterestForLabel($labelSQL);
- $tableResolution = $archiveProcessing->getDataTableFromArray($interestByResolution);
- $tableResolution->filter('ColumnCallbackDeleteRow', array('label', 'Piwik_UserSettings_keepStrlenGreater'));
- $archiveProcessing->insertBlobRecord($recordName, $tableResolution->getSerialized($maximumRowsInDataTable, null, $columnToSortByBeforeTruncation));
-
- $recordName = 'UserSettings_wideScreen';
- $tableWideScreen = $this->getTableWideScreen($tableResolution);
- $archiveProcessing->insertBlobRecord($recordName, $tableWideScreen->getSerialized());
- destroy($tableResolution);
- destroy($tableWideScreen);
-
- $recordName = 'UserSettings_plugin';
- $tablePlugin = $this->getDataTablePlugin();
- $archiveProcessing->insertBlobRecord($recordName, $tablePlugin->getSerialized());
- destroy($tablePlugin);
+ public function getInformation()
+ {
+ return array(
+ 'description' => Piwik_Translate('UserSettings_PluginDescription'),
+ 'author' => 'Piwik',
+ 'author_homepage' => 'http://piwik.org/',
+ 'version' => Piwik_Version::VERSION,
+ );
+ }
+
+ /*
+ * Mapping between the browser family shortcode and the displayed name
+ */
+ static public $browserType_display = array(
+ 'ie' => 'Trident (IE)',
+ 'gecko' => 'Gecko (Firefox)',
+ 'khtml' => 'KHTML (Konqueror)',
+ 'webkit' => 'WebKit (Safari, Chrome)',
+ 'opera' => 'Presto (Opera)',
+ );
+
+ /*
+ * Defines API reports.
+ * Also used to define Widgets.
+ *
+ * @array Category, Report Name, API Module, API action, Translated column name,
+ * $segment, $sqlSegment, $acceptedValues, $sqlFilter
+ */
+ protected $reportMetadata = array(
+ array('UserSettings_VisitorSettings',
+ 'UserSettings_WidgetResolutions',
+ 'UserSettings',
+ 'getResolution',
+ 'UserSettings_ColumnResolution',
+ 'resolution',
+ 'log_visit.config_resolution',
+ '1280x1024, 800x600, etc.',
+ null,),
+
+ array('UserSettings_VisitorSettings',
+ 'UserSettings_WidgetBrowsers',
+ 'UserSettings',
+ 'getBrowser',
+ 'UserSettings_ColumnBrowser',
+ 'browserName',
+ 'log_visit.config_browser_name',
+ 'FF, IE, CH, SF, OP, etc.',
+ null,),
+
+ // browser version
+ array('UserSettings_VisitorSettings',
+ 'UserSettings_WidgetBrowserVersion',
+ 'UserSettings',
+ 'getBrowserVersion',
+ 'UserSettings_ColumnBrowserVersion',
+ 'browserVersion',
+ 'log_visit.config_browser_version',
+ '1.0, 8.0, etc.',
+ null,),
+
+ array('UserSettings_VisitorSettings',
+ 'UserSettings_WidgetBrowserFamilies',
+ 'UserSettings',
+ 'getBrowserType',
+ 'UserSettings_ColumnBrowserFamily',
+ null,
+ null,
+ null,
+ null,),
+
+ array('UserSettings_VisitorSettings',
+ 'UserSettings_WidgetPlugins',
+ 'UserSettings',
+ 'getPlugin',
+ 'UserSettings_ColumnPlugin',
+ null,
+ null,
+ null,
+ null,),
+
+ array('UserSettings_VisitorSettings',
+ 'UserSettings_WidgetWidescreen',
+ 'UserSettings',
+ 'getWideScreen',
+ 'UserSettings_ColumnTypeOfScreen',
+ null,
+ null,
+ null,
+ null,),
+
+ array('UserSettings_VisitorSettings',
+ 'UserSettings_WidgetOperatingSystems',
+ 'UserSettings',
+ 'getOS',
+ 'UserSettings_ColumnOperatingSystem',
+ 'operatingSystem',
+ 'log_visit.config_os',
+ 'WXP, WI7, MAC, LIN, AND, IPD, etc.',
+ null,),
+
+ array('UserSettings_VisitorSettings',
+ 'UserSettings_WidgetGlobalVisitors',
+ 'UserSettings',
+ 'getConfiguration',
+ 'UserSettings_ColumnConfiguration',
+ null,
+ null,
+ null,
+ null),
+
+ // operating system family
+ array('UserSettings_VisitorSettings',
+ 'UserSettings_WidgetOperatingSystemFamily',
+ 'UserSettings',
+ 'getOSFamily',
+ 'UserSettings_OperatingSystemFamily',
+ null,
+ null,
+ null,
+ null),
+
+ // device type
+ array('UserSettings_VisitorSettings',
+ 'UserSettings_MobileVsDesktop',
+ 'UserSettings',
+ 'getMobileVsDesktop',
+ 'UserSettings_MobileVsDesktop',
+ null,
+ null,
+ null,
+ null),
+
+ // Browser language
+ array('UserSettings_VisitorSettings',
+ 'UserSettings_BrowserLanguage',
+ 'UserSettings',
+ 'getLanguage',
+ 'General_Language',
+ null,
+ null,
+ null,
+ null),
+ );
+
+ /*
+ * List of hooks
+ */
+ function getListHooksRegistered()
+ {
+ $hooks = array(
+ 'ArchiveProcessing_Day.compute' => 'archiveDay',
+ 'ArchiveProcessing_Period.compute' => 'archivePeriod',
+ 'WidgetsList.add' => 'addWidgets',
+ 'Menu.add' => 'addMenu',
+ 'API.getReportMetadata' => 'getReportMetadata',
+ 'API.getSegmentsMetadata' => 'getSegmentsMetadata',
+ );
+ return $hooks;
+ }
+
+ /*
+ * Registers reports metadata
+ *
+ * @param Piwik_Event_Notification $notification notification object
+ */
+ public function getReportMetadata($notification)
+ {
+ $reports = & $notification->getNotificationObject();
+
+ $i = 0;
+ foreach ($this->reportMetadata as $report) {
+ list($category, $name, $apiModule, $apiAction, $columnName) = $report;
+ if ($category == false) continue;
+
+ $report = array(
+ 'category' => Piwik_Translate($category),
+ 'name' => Piwik_Translate($name),
+ 'module' => $apiModule,
+ 'action' => $apiAction,
+ 'dimension' => Piwik_Translate($columnName),
+ 'order' => $i++
+ );
+
+ $translation = $name . 'Documentation';
+ $translated = Piwik_Translate($translation, '<br />');
+ if ($translated != $translation) {
+ $report['documentation'] = $translated;
+ }
+
+ // getPlugin returns only a subset of metrics
+ if ($apiAction == 'getPlugin') {
+ $report['metrics'] = array(
+ 'nb_visits',
+ 'nb_visits_percentage' => Piwik_Translate('General_ColumnPercentageVisits')
+ );
+ // There is no processedMetrics for this report
+ $report['processedMetrics'] = array();
+ // Always has same number of rows, 1 per plugin
+ $report['constantRowsCount'] = true;
+ }
+ $reports[] = $report;
+ }
+ }
+
+ /**
+ * Get segments meta data
+ *
+ * @param Piwik_Event_Notification $notification notification object
+ */
+ public function getSegmentsMetadata($notification)
+ {
+ $segments =& $notification->getNotificationObject();
+ foreach ($this->reportMetadata as $report) {
+ @list($category, $name, $apiModule, $apiAction, $columnName, $segment, $sqlSegment, $acceptedValues, $sqlFilter) = $report;
+ if (empty($segment)) continue;
+ $segments[] = array(
+ 'type' => 'dimension',
+ 'category' => 'Visit',
+ 'name' => $columnName,
+ 'segment' => $segment,
+ 'acceptedValues' => $acceptedValues,
+ 'sqlSegment' => $sqlSegment,
+ 'sqlFilter' => isset($sqlFilter) ? $sqlFilter : false,
+ );
+ }
+ }
+
+ /**
+ * Adds the various User Settings widgets
+ */
+ function addWidgets()
+ {
+ // in this case, Widgets have same names as API reports
+ foreach ($this->reportMetadata as $report) {
+ list($category, $name, $controllerName, $controllerAction) = $report;
+ if ($category == false) continue;
+ Piwik_AddWidget($category, $name, $controllerName, $controllerAction);
+ }
+ }
+
+ /**
+ * Adds the User Settings menu
+ */
+ function addMenu()
+ {
+ Piwik_AddMenu('General_Visitors', 'UserSettings_SubmenuSettings', array('module' => 'UserSettings', 'action' => 'index'));
+ }
+
+ /**
+ * Daily archive of User Settings report. Processes reports for Visits by Resolution,
+ * by Browser, Browser family, etc. Some reports are built from the logs, some reports
+ * are superset of an existing report (eg. Browser family is built from the Browser report)
+ *
+ * @param Piwik_Event_Notification $notification notification object
+ * @return void
+ */
+ function archiveDay($notification)
+ {
+ require_once PIWIK_INCLUDE_PATH . '/plugins/UserSettings/functions.php';
+ $maximumRowsInDataTable = Piwik_Config::getInstance()->General['datatable_archiving_maximum_rows_standard'];
+ $columnToSortByBeforeTruncation = Piwik_Archive::INDEX_NB_VISITS;
+
+ $archiveProcessing = $notification->getNotificationObject();
+
+ if (!$archiveProcessing->shouldProcessReportsForPlugin($this->getPluginName())) return;
+
+ $this->archiveProcessing = $archiveProcessing;
+
+ $recordName = 'UserSettings_configuration';
+ $labelSQL = "CONCAT(log_visit.config_os, ';', log_visit.config_browser_name, ';', log_visit.config_resolution)";
+ $interestByConfiguration = $archiveProcessing->getArrayInterestForLabel($labelSQL);
+
+ $tableConfiguration = $archiveProcessing->getDataTableFromArray($interestByConfiguration);
+ $archiveProcessing->insertBlobRecord($recordName, $tableConfiguration->getSerialized($maximumRowsInDataTable, null, $columnToSortByBeforeTruncation));
+ destroy($tableConfiguration);
+
+ $recordName = 'UserSettings_os';
+ $labelSQL = "log_visit.config_os";
+ $interestByOs = $archiveProcessing->getArrayInterestForLabel($labelSQL);
+ $tableOs = $archiveProcessing->getDataTableFromArray($interestByOs);
+ $archiveProcessing->insertBlobRecord($recordName, $tableOs->getSerialized($maximumRowsInDataTable, null, $columnToSortByBeforeTruncation));
+ destroy($tableOs);
+
+ $recordName = 'UserSettings_browser';
+ $labelSQL = "CONCAT(log_visit.config_browser_name, ';', log_visit.config_browser_version)";
+ $interestByBrowser = $archiveProcessing->getArrayInterestForLabel($labelSQL);
+ $tableBrowser = $archiveProcessing->getDataTableFromArray($interestByBrowser);
+ $archiveProcessing->insertBlobRecord($recordName, $tableBrowser->getSerialized($maximumRowsInDataTable, null, $columnToSortByBeforeTruncation));
+
+ $recordName = 'UserSettings_browserType';
+ $tableBrowserType = $this->getTableBrowserByType($tableBrowser);
+ $archiveProcessing->insertBlobRecord($recordName, $tableBrowserType->getSerialized());
+ destroy($tableBrowser);
+ destroy($tableBrowserType);
+
+ $recordName = 'UserSettings_resolution';
+ $labelSQL = "log_visit.config_resolution";
+ $interestByResolution = $archiveProcessing->getArrayInterestForLabel($labelSQL);
+ $tableResolution = $archiveProcessing->getDataTableFromArray($interestByResolution);
+ $tableResolution->filter('ColumnCallbackDeleteRow', array('label', 'Piwik_UserSettings_keepStrlenGreater'));
+ $archiveProcessing->insertBlobRecord($recordName, $tableResolution->getSerialized($maximumRowsInDataTable, null, $columnToSortByBeforeTruncation));
+
+ $recordName = 'UserSettings_wideScreen';
+ $tableWideScreen = $this->getTableWideScreen($tableResolution);
+ $archiveProcessing->insertBlobRecord($recordName, $tableWideScreen->getSerialized());
+ destroy($tableResolution);
+ destroy($tableWideScreen);
+
+ $recordName = 'UserSettings_plugin';
+ $tablePlugin = $this->getDataTablePlugin();
+ $archiveProcessing->insertBlobRecord($recordName, $tablePlugin->getSerialized());
+ destroy($tablePlugin);
$recordName = 'UserSettings_language';
- $tableLanguage = $this->getDataTableLanguages();
+ $tableLanguage = $this->getDataTableLanguages();
$archiveProcessing->insertBlobRecord($recordName, $tableLanguage->getSerialized($maximumRowsInDataTable, null, $columnToSortByBeforeTruncation));
}
- /**
- * Period archiving: simply sums up daily archives
- *
- * @param Piwik_Event_Notification $notification notification object
- * @return void
- */
- function archivePeriod( $notification )
- {
- $archiveProcessing = $notification->getNotificationObject();
-
- if(!$archiveProcessing->shouldProcessReportsForPlugin($this->getPluginName())) return;
-
- $maximumRowsInDataTable = Piwik_Config::getInstance()->General['datatable_archiving_maximum_rows_standard'];
-
- $dataTableToSum = array(
- 'UserSettings_configuration',
- 'UserSettings_os',
- 'UserSettings_browser',
- 'UserSettings_browserType',
- 'UserSettings_resolution',
- 'UserSettings_wideScreen',
- 'UserSettings_plugin',
- 'UserSettings_language',
- );
-
- $archiveProcessing->archiveDataTable($dataTableToSum, null, $maximumRowsInDataTable);
- }
-
- /**
- * Returns the report Visits by Screen type given the Resolution table
- *
- * @param Piwik_DataTable $tableResolution
- * @return Piwik_DataTable
- */
- protected function getTableWideScreen(Piwik_DataTable $tableResolution)
- {
- $nameToRow = array();
- foreach($tableResolution->getRows() as $row)
- {
- $resolution = $row->getColumn('label');
- $name = Piwik_getScreenTypeFromResolution($resolution);
- if(!isset($nameToRow[$name]))
- {
- $nameToRow[$name] = new Piwik_DataTable_Row();
- $nameToRow[$name]->addColumn('label', $name);
- }
-
- $nameToRow[$name]->sumRow( $row );
- }
- $tableWideScreen = new Piwik_DataTable();
- $tableWideScreen->addRowsFromArray($nameToRow);
-
- return $tableWideScreen;
- }
-
- /**
- * Returns the report Visits by Browser family given the Browser report
- *
- * @param Piwik_DataTable $tableBrowser
- * @return Piwik_DataTable
- */
- protected function getTableBrowserByType(Piwik_DataTable $tableBrowser)
- {
- $nameToRow = array();
- foreach($tableBrowser->getRows() as $row)
- {
- $browserLabel = $row->getColumn('label');
- $familyNameToUse = Piwik_getBrowserFamily($browserLabel);
- if(!isset($nameToRow[$familyNameToUse]))
- {
- $nameToRow[$familyNameToUse] = new Piwik_DataTable_Row();
- $nameToRow[$familyNameToUse]->addColumn('label',$familyNameToUse);
- }
- $nameToRow[$familyNameToUse]->sumRow( $row );
- }
-
- $tableBrowserType = new Piwik_DataTable();
- $tableBrowserType->addRowsFromArray($nameToRow);
- return $tableBrowserType;
- }
-
- /**
- * Returns SQL that processes stats for Plugins
- *
- * @return Piwik_DataTable_Simple
- */
- protected function getDataTablePlugin()
- {
- $toSelect = "sum(case log_visit.config_pdf when 1 then 1 else 0 end) as pdf,
+ /**
+ * Period archiving: simply sums up daily archives
+ *
+ * @param Piwik_Event_Notification $notification notification object
+ * @return void
+ */
+ function archivePeriod($notification)
+ {
+ $archiveProcessing = $notification->getNotificationObject();
+
+ if (!$archiveProcessing->shouldProcessReportsForPlugin($this->getPluginName())) return;
+
+ $maximumRowsInDataTable = Piwik_Config::getInstance()->General['datatable_archiving_maximum_rows_standard'];
+
+ $dataTableToSum = array(
+ 'UserSettings_configuration',
+ 'UserSettings_os',
+ 'UserSettings_browser',
+ 'UserSettings_browserType',
+ 'UserSettings_resolution',
+ 'UserSettings_wideScreen',
+ 'UserSettings_plugin',
+ 'UserSettings_language',
+ );
+
+ $archiveProcessing->archiveDataTable($dataTableToSum, null, $maximumRowsInDataTable);
+ }
+
+ /**
+ * Returns the report Visits by Screen type given the Resolution table
+ *
+ * @param Piwik_DataTable $tableResolution
+ * @return Piwik_DataTable
+ */
+ protected function getTableWideScreen(Piwik_DataTable $tableResolution)
+ {
+ $nameToRow = array();
+ foreach ($tableResolution->getRows() as $row) {
+ $resolution = $row->getColumn('label');
+ $name = Piwik_getScreenTypeFromResolution($resolution);
+ if (!isset($nameToRow[$name])) {
+ $nameToRow[$name] = new Piwik_DataTable_Row();
+ $nameToRow[$name]->addColumn('label', $name);
+ }
+
+ $nameToRow[$name]->sumRow($row);
+ }
+ $tableWideScreen = new Piwik_DataTable();
+ $tableWideScreen->addRowsFromArray($nameToRow);
+
+ return $tableWideScreen;
+ }
+
+ /**
+ * Returns the report Visits by Browser family given the Browser report
+ *
+ * @param Piwik_DataTable $tableBrowser
+ * @return Piwik_DataTable
+ */
+ protected function getTableBrowserByType(Piwik_DataTable $tableBrowser)
+ {
+ $nameToRow = array();
+ foreach ($tableBrowser->getRows() as $row) {
+ $browserLabel = $row->getColumn('label');
+ $familyNameToUse = Piwik_getBrowserFamily($browserLabel);
+ if (!isset($nameToRow[$familyNameToUse])) {
+ $nameToRow[$familyNameToUse] = new Piwik_DataTable_Row();
+ $nameToRow[$familyNameToUse]->addColumn('label', $familyNameToUse);
+ }
+ $nameToRow[$familyNameToUse]->sumRow($row);
+ }
+
+ $tableBrowserType = new Piwik_DataTable();
+ $tableBrowserType->addRowsFromArray($nameToRow);
+ return $tableBrowserType;
+ }
+
+ /**
+ * Returns SQL that processes stats for Plugins
+ *
+ * @return Piwik_DataTable_Simple
+ */
+ protected function getDataTablePlugin()
+ {
+ $toSelect = "sum(case log_visit.config_pdf when 1 then 1 else 0 end) as pdf,
sum(case log_visit.config_flash when 1 then 1 else 0 end) as flash,
sum(case log_visit.config_java when 1 then 1 else 0 end) as java,
sum(case log_visit.config_director when 1 then 1 else 0 end) as director,
@@ -435,38 +427,35 @@ class Piwik_UserSettings extends Piwik_Plugin
sum(case log_visit.config_gears when 1 then 1 else 0 end) as gears,
sum(case log_visit.config_silverlight when 1 then 1 else 0 end) as silverlight,
sum(case log_visit.config_cookie when 1 then 1 else 0 end) as cookie ";
- return $this->archiveProcessing->getSimpleDataTableFromSelect($toSelect, Piwik_Archive::INDEX_NB_VISITS);
- }
+ return $this->archiveProcessing->getSimpleDataTableFromSelect($toSelect, Piwik_Archive::INDEX_NB_VISITS);
+ }
- protected function getDataTableLanguages()
- {
- $labelSQL = "log_visit.location_browser_lang";
- $interestByLanguage = $this->archiveProcessing->getArrayInterestForLabel($labelSQL);
+ protected function getDataTableLanguages()
+ {
+ $labelSQL = "log_visit.location_browser_lang";
+ $interestByLanguage = $this->archiveProcessing->getArrayInterestForLabel($labelSQL);
$languageCodes = array_keys(Piwik_Common::getLanguagesList());
- foreach ($interestByLanguage as $lang => $count)
- {
- // get clean language code
+ foreach ($interestByLanguage as $lang => $count) {
+ // get clean language code
$code = Piwik_Common::extractLanguageCodeFromBrowserLanguage($lang, $languageCodes);
- if ($code != $lang)
- {
- if (!array_key_exists($code, $interestByLanguage)) {
- $interestByLanguage[$code] = array();
- }
- // Add the values to the primary language
- foreach ($count as $key => $value)
- {
- if (array_key_exists($key, $interestByLanguage[$code])) {
- $interestByLanguage[$code][$key] += $value;
- } else {
- $interestByLanguage[$code][$key] = $value;
- }
- }
- unset($interestByLanguage[$lang]);
- }
- }
- $tableLanguage = $this->archiveProcessing->getDataTableFromArray($interestByLanguage);
- return $tableLanguage;
- }
+ if ($code != $lang) {
+ if (!array_key_exists($code, $interestByLanguage)) {
+ $interestByLanguage[$code] = array();
+ }
+ // Add the values to the primary language
+ foreach ($count as $key => $value) {
+ if (array_key_exists($key, $interestByLanguage[$code])) {
+ $interestByLanguage[$code][$key] += $value;
+ } else {
+ $interestByLanguage[$code][$key] = $value;
+ }
+ }
+ unset($interestByLanguage[$lang]);
+ }
+ }
+ $tableLanguage = $this->archiveProcessing->getDataTableFromArray($interestByLanguage);
+ return $tableLanguage;
+ }
}
diff --git a/plugins/UserSettings/functions.php b/plugins/UserSettings/functions.php
index 790a61f939..6e5db05428 100644
--- a/plugins/UserSettings/functions.php
+++ b/plugins/UserSettings/functions.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_UserSettings
*/
@@ -13,250 +13,224 @@
* @see libs/UserAgentParser/UserAgentParser.php
*/
require_once PIWIK_INCLUDE_PATH . '/libs/UserAgentParser/UserAgentParser.php';
-
-function Piwik_getPluginsLogo( $oldLabel )
+
+function Piwik_getPluginsLogo($oldLabel)
{
- return 'plugins/UserSettings/images/plugins/'. $oldLabel . '.gif';
+ return 'plugins/UserSettings/images/plugins/' . $oldLabel . '.gif';
}
function Piwik_getOSLabel($osId)
{
- $osName = UserAgentParser::getOperatingSystemNameFromId($osId);
- if($osName !== false)
- {
- return $osName;
- }
- if( $osId == 'UNK')
- {
- return Piwik_Translate('General_Unknown');
- }
- return $osId;
+ $osName = UserAgentParser::getOperatingSystemNameFromId($osId);
+ if ($osName !== false) {
+ return $osName;
+ }
+ if ($osId == 'UNK') {
+ return Piwik_Translate('General_Unknown');
+ }
+ return $osId;
}
function Piwik_getOSShortLabel($osId)
{
- $osShortName = UserAgentParser::getOperatingSystemShortNameFromId($osId);
- if($osShortName !== false)
- {
- return $osShortName;
- }
- if( $osId == 'UNK')
- {
- return Piwik_Translate('General_Unknown');
- }
- return $osId;
+ $osShortName = UserAgentParser::getOperatingSystemShortNameFromId($osId);
+ if ($osShortName !== false) {
+ return $osShortName;
+ }
+ if ($osId == 'UNK') {
+ return Piwik_Translate('General_Unknown');
+ }
+ return $osId;
}
function Piwik_UserSettings_getOSFamily($osLabel)
{
- $osId = UserAgentParser::getOperatingSystemIdFromName($osLabel);
- $osFamily = UserAgentParser::getOperatingSystemFamilyFromId($osId);
-
- if ($osFamily == 'unknown')
- {
- $osFamily = Piwik_Translate('General_Unknown');
- }
- else if ($osFamily == 'Gaming Console')
- {
- $osFamily = Piwik_Translate('UserSettings_GamingConsole');
- }
-
- return $osFamily;
+ $osId = UserAgentParser::getOperatingSystemIdFromName($osLabel);
+ $osFamily = UserAgentParser::getOperatingSystemFamilyFromId($osId);
+
+ if ($osFamily == 'unknown') {
+ $osFamily = Piwik_Translate('General_Unknown');
+ } else if ($osFamily == 'Gaming Console') {
+ $osFamily = Piwik_Translate('UserSettings_GamingConsole');
+ }
+
+ return $osFamily;
}
function Piwik_UserSettings_getDeviceTypeFromOS($osLabel)
{
- $osId = UserAgentParser::getOperatingSystemIdFromName($osLabel);
- $osFamily = UserAgentParser::getOperatingSystemFamilyFromId($osId);
-
- // NOTE: translations done in another filter
- switch ($osFamily)
- {
- case 'Windows':
- case 'Linux':
- case 'Mac':
- case 'Unix':
- case 'Other':
- case 'Gaming Console':
- return 'General_Desktop';
- case 'iOS':
- case 'Android':
- case 'Windows Mobile':
- case 'Other Mobile':
- case 'Mobile Gaming Console':
- return 'General_Mobile';
- default:
- return 'General_Unknown';
- }
+ $osId = UserAgentParser::getOperatingSystemIdFromName($osLabel);
+ $osFamily = UserAgentParser::getOperatingSystemFamilyFromId($osId);
+
+ // NOTE: translations done in another filter
+ switch ($osFamily) {
+ case 'Windows':
+ case 'Linux':
+ case 'Mac':
+ case 'Unix':
+ case 'Other':
+ case 'Gaming Console':
+ return 'General_Desktop';
+ case 'iOS':
+ case 'Android':
+ case 'Windows Mobile':
+ case 'Other Mobile':
+ case 'Mobile Gaming Console':
+ return 'General_Mobile';
+ default:
+ return 'General_Unknown';
+ }
}
function Piwik_getBrowserTypeLabel($oldLabel)
{
- if(isset(Piwik_UserSettings::$browserType_display[$oldLabel]))
- {
- return Piwik_UserSettings::$browserType_display[$oldLabel];
- }
- if($oldLabel == 'unknown')
- {
- return Piwik_Translate('General_Unknown');
- }
- return $oldLabel;
+ if (isset(Piwik_UserSettings::$browserType_display[$oldLabel])) {
+ return Piwik_UserSettings::$browserType_display[$oldLabel];
+ }
+ if ($oldLabel == 'unknown') {
+ return Piwik_Translate('General_Unknown');
+ }
+ return $oldLabel;
}
function Piwik_getConfigurationLabel($str)
{
- if(strpos($str, ';') === false)
- {
- return $str;
- }
- $values = explode(";", $str);
-
- $os = Piwik_getOSLabel($values[0]);
- $name = $values[1];
- $browser = UserAgentParser::getBrowserNameFromId($name);
- if($browser === false)
- {
- $browser = Piwik_Translate('General_Unknown');
- }
- $resolution = $values[2];
- return $os . " / " . $browser . " / " . $resolution;
+ if (strpos($str, ';') === false) {
+ return $str;
+ }
+ $values = explode(";", $str);
+
+ $os = Piwik_getOSLabel($values[0]);
+ $name = $values[1];
+ $browser = UserAgentParser::getBrowserNameFromId($name);
+ if ($browser === false) {
+ $browser = Piwik_Translate('General_Unknown');
+ }
+ $resolution = $values[2];
+ return $os . " / " . $browser . " / " . $resolution;
}
function Piwik_getBrowserLabel($oldLabel)
{
- $browserId = Piwik_getBrowserId($oldLabel);
- $version = Piwik_getBrowserVersion($oldLabel);
- $browserName = UserAgentParser::getBrowserNameFromId($browserId);
- if( $browserName !== false)
- {
- return $browserName . " ". $version;
- }
- if( $browserId == 'UNK')
- {
- return Piwik_Translate('General_Unknown');
- }
- return $oldLabel;
+ $browserId = Piwik_getBrowserId($oldLabel);
+ $version = Piwik_getBrowserVersion($oldLabel);
+ $browserName = UserAgentParser::getBrowserNameFromId($browserId);
+ if ($browserName !== false) {
+ return $browserName . " " . $version;
+ }
+ if ($browserId == 'UNK') {
+ return Piwik_Translate('General_Unknown');
+ }
+ return $oldLabel;
}
function Piwik_getBrowserShortLabel($oldLabel)
{
- $browserId = Piwik_getBrowserId($oldLabel);
- $version = Piwik_getBrowserVersion($oldLabel);
- $browserName = UserAgentParser::getBrowserShortNameFromId($browserId);
- if( $browserName !== false)
- {
- return $browserName . " ". $version;
- }
- if( $browserId == 'UNK')
- {
- return Piwik_Translate('General_Unknown');
- }
- return $oldLabel;
+ $browserId = Piwik_getBrowserId($oldLabel);
+ $version = Piwik_getBrowserVersion($oldLabel);
+ $browserName = UserAgentParser::getBrowserShortNameFromId($browserId);
+ if ($browserName !== false) {
+ return $browserName . " " . $version;
+ }
+ if ($browserId == 'UNK') {
+ return Piwik_Translate('General_Unknown');
+ }
+ return $oldLabel;
}
function Piwik_getBrowserId($str)
{
- return substr($str, 0, strpos($str, ';'));
+ return substr($str, 0, strpos($str, ';'));
}
function Piwik_getBrowserVersion($str)
{
- return substr($str, strpos($str, ';') + 1);
+ return substr($str, strpos($str, ';') + 1);
}
function Piwik_getBrowsersLogo($label)
{
- $id = Piwik_getBrowserId($label);
- // For aggregated row 'Others'
- if(empty($id)) {
- $id = 'UNK';
- }
- return 'plugins/UserSettings/images/browsers/'. $id . '.gif';
+ $id = Piwik_getBrowserId($label);
+ // For aggregated row 'Others'
+ if (empty($id)) {
+ $id = 'UNK';
+ }
+ return 'plugins/UserSettings/images/browsers/' . $id . '.gif';
}
function Piwik_getOSLogo($label)
{
- // For aggregated row 'Others'
- if(empty($label)) {
- $label = 'UNK';
- }
- $path = 'plugins/UserSettings/images/os/'. $label . '.gif';
- return $path;
+ // For aggregated row 'Others'
+ if (empty($label)) {
+ $label = 'UNK';
+ }
+ $path = 'plugins/UserSettings/images/os/' . $label . '.gif';
+ return $path;
}
function Piwik_getScreensLogo($label)
{
- return 'plugins/UserSettings/images/screens/' . $label . '.gif';
+ return 'plugins/UserSettings/images/screens/' . $label . '.gif';
}
-function Piwik_UserSettings_getDeviceTypeImg( $oldOSImage, $osFamilyLabel )
+function Piwik_UserSettings_getDeviceTypeImg($oldOSImage, $osFamilyLabel)
{
- switch ($osFamilyLabel)
- {
- case 'General_Desktop':
- return 'plugins/UserSettings/images/screens/normal.gif';
- case 'General_Mobile':
- return 'plugins/UserSettings/images/screens/mobile.gif';
- case 'General_Unknown':
- default:
- return 'plugins/UserSettings/images/os/UNK.gif';
- }
+ switch ($osFamilyLabel) {
+ case 'General_Desktop':
+ return 'plugins/UserSettings/images/screens/normal.gif';
+ case 'General_Mobile':
+ return 'plugins/UserSettings/images/screens/mobile.gif';
+ case 'General_Unknown':
+ default:
+ return 'plugins/UserSettings/images/os/UNK.gif';
+ }
}
function Piwik_UserSettings_keepStrlenGreater($value)
{
- return strlen($value) > 5;
+ return strlen($value) > 5;
}
function Piwik_getScreenTypeFromResolution($resolution)
{
- if($resolution === 'unknown')
- {
- return $resolution;
- }
-
- $width = intval(substr($resolution, 0, strpos($resolution, 'x')));
- $height= intval(substr($resolution, strpos($resolution, 'x') + 1));
- $ratio = Piwik::secureDiv($width, $height);
-
- if($width < 640)
- {
- $name = 'mobile';
- }
- elseif($ratio < 1.4)
- {
- $name = 'normal';
- }
- else if($ratio < 2)
- {
- $name = 'wide';
- }
- else
- {
- $name = 'dual';
- }
- return $name;
+ if ($resolution === 'unknown') {
+ return $resolution;
+ }
+
+ $width = intval(substr($resolution, 0, strpos($resolution, 'x')));
+ $height = intval(substr($resolution, strpos($resolution, 'x') + 1));
+ $ratio = Piwik::secureDiv($width, $height);
+
+ if ($width < 640) {
+ $name = 'mobile';
+ } elseif ($ratio < 1.4) {
+ $name = 'normal';
+ } else if ($ratio < 2) {
+ $name = 'wide';
+ } else {
+ $name = 'dual';
+ }
+ return $name;
}
function Piwik_getBrowserFamily($browserLabel)
{
- $familyNameToUse = UserAgentParser::getBrowserFamilyFromId(substr($browserLabel, 0, 2));
- return $familyNameToUse;
+ $familyNameToUse = UserAgentParser::getBrowserFamilyFromId(substr($browserLabel, 0, 2));
+ return $familyNameToUse;
}
/**
* Extracts the browser name from a string with the browser name and version.
*/
-function Piwik_UserSettings_getBrowserFromBrowserVersion( $browserWithVersion )
+function Piwik_UserSettings_getBrowserFromBrowserVersion($browserWithVersion)
{
- if (preg_match("/(.+) [0-9]+(?:\.[0-9]+)?$/", $browserWithVersion, $matches) === 0)
- {
- return $browserWithVersion;
- }
-
- return $matches[1];
+ if (preg_match("/(.+) [0-9]+(?:\.[0-9]+)?$/", $browserWithVersion, $matches) === 0) {
+ return $browserWithVersion;
+ }
+
+ return $matches[1];
}
/**
diff --git a/plugins/UserSettings/templates/index.tpl b/plugins/UserSettings/templates/index.tpl
index 0532de2f72..830e77e0ff 100644
--- a/plugins/UserSettings/templates/index.tpl
+++ b/plugins/UserSettings/templates/index.tpl
@@ -1,27 +1,27 @@
<div id='leftcolumn'>
- <h2>{'UserSettings_BrowserFamilies'|translate}</h2>
- {$dataTableBrowserType}
-
- <h2>{'UserSettings_Browsers'|translate}</h2>
- {$dataTableBrowser}
-
- <h2>{'UserSettings_Plugins'|translate}</h2>
- {$dataTablePlugin}
+ <h2>{'UserSettings_BrowserFamilies'|translate}</h2>
+ {$dataTableBrowserType}
+
+ <h2>{'UserSettings_Browsers'|translate}</h2>
+ {$dataTableBrowser}
+
+ <h2>{'UserSettings_Plugins'|translate}</h2>
+ {$dataTablePlugin}
</div>
<div id='rightcolumn'>
- <h2>{'UserSettings_Configurations'|translate}</h2>
- {$dataTableConfiguration}
-
- <h2>{'UserSettings_OperatingSystems'|translate}</h2>
- {$dataTableOS}
-
- <h2>{'UserSettings_Resolutions'|translate}</h2>
- {$dataTableResolution}
-
- <h2>{'UserSettings_MobileVsDesktop'|translate}</h2>
- {$dataTableMobileVsDesktop}
+ <h2>{'UserSettings_Configurations'|translate}</h2>
+ {$dataTableConfiguration}
+
+ <h2>{'UserSettings_OperatingSystems'|translate}</h2>
+ {$dataTableOS}
+
+ <h2>{'UserSettings_Resolutions'|translate}</h2>
+ {$dataTableResolution}
+
+ <h2>{'UserSettings_MobileVsDesktop'|translate}</h2>
+ {$dataTableMobileVsDesktop}
- <h2>{'UserSettings_BrowserLanguage'|translate}</h2>
- {$dataTableBrowserLanguage}
+ <h2>{'UserSettings_BrowserLanguage'|translate}</h2>
+ {$dataTableBrowserLanguage}
</div>
diff --git a/plugins/UsersManager/API.php b/plugins/UsersManager/API.php
index a5e58a703e..340fb97961 100644
--- a/plugins/UsersManager/API.php
+++ b/plugins/UsersManager/API.php
@@ -1,698 +1,661 @@
<?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_UsersManager
*/
/**
* The UsersManager API lets you Manage Users and their permissions to access specific websites.
- *
+ *
* You can create users via "addUser", update existing users via "updateUser" and delete users via "deleteUser".
- * There are many ways to list users based on their login "getUser" and "getUsers", their email "getUserByEmail",
+ * There are many ways to list users based on their login "getUser" and "getUsers", their email "getUserByEmail",
* or which users have permission (view or admin) to access the specified websites "getUsersWithSiteAccess".
- *
- * Existing Permissions are listed given a login via "getSitesAccessFromUser", or a website ID via "getUsersAccessFromSite",
+ *
+ * Existing Permissions are listed given a login via "getSitesAccessFromUser", or a website ID via "getUsersAccessFromSite",
* or you can list all users and websites for a given permission via "getUsersSitesFromAccess". Permissions are set and updated
* via the method "setUserAccess".
* See also the documentation about <a href='http://piwik.org/docs/manage-users/' target='_blank'>Managing Users</a> in Piwik.
* @package Piwik_UsersManager
*/
-class Piwik_UsersManager_API
+class Piwik_UsersManager_API
{
- static private $instance = null;
-
- /**
- * You can create your own Users Plugin to override this class.
- * Example of how you would overwrite the UsersManager_API with your own class:
- * Call the following in your plugin __construct() for example:
- *
- * Zend_Registry::set('UsersManager_API',Piwik_MyCustomUsersManager_API::getInstance());
- *
- * @throws Exception
- * @return Piwik_UsersManager_API
- */
- static public function getInstance()
- {
- try {
- $instance = Zend_Registry::get('UsersManager_API');
- if( !($instance instanceof Piwik_UsersManager_API) ) {
- // Exception is caught below and corrected
- throw new Exception('UsersManager_API must inherit Piwik_UsersManager_API');
- }
- self::$instance = $instance;
- }
- catch (Exception $e) {
- self::$instance = new self;
- Zend_Registry::set('UsersManager_API', self::$instance);
- }
- return self::$instance;
- }
- const PREFERENCE_DEFAULT_REPORT = 'defaultReport';
- const PREFERENCE_DEFAULT_REPORT_DATE = 'defaultReportDate';
-
- /**
- * Sets a user preference
- * @param string $userLogin
- * @param string $preferenceName
- * @param string $preferenceValue
- * @return void
- */
- public function setUserPreference($userLogin, $preferenceName, $preferenceValue)
- {
- Piwik::checkUserIsSuperUserOrTheUser($userLogin);
- Piwik_SetOption($this->getPreferenceId($userLogin, $preferenceName), $preferenceValue);
- }
-
- /**
- * Gets a user preference
- * @param string $userLogin
- * @param string $preferenceName
- * @return bool|string
- */
- public function getUserPreference($userLogin, $preferenceName)
- {
- Piwik::checkUserIsSuperUserOrTheUser($userLogin);
- return Piwik_GetOption($this->getPreferenceId($userLogin, $preferenceName));
- }
-
- private function getPreferenceId($login, $preference)
- {
- return $login . '_' . $preference;
- }
-
- /**
- * Returns the list of all the users
- *
- * @param string $userLogins Comma separated list of users to select. If not specified, will return all users
- * @return array the list of all the users
- */
- public function getUsers( $userLogins = '' )
- {
- Piwik::checkUserHasSomeAdminAccess();
-
- $where = '';
- $bind = array();
- if(!empty($userLogins))
- {
- $userLogins = explode(',', $userLogins);
- $where = 'WHERE login IN ('. Piwik_Common::getSqlStringFieldsArray($userLogins).')';
- $bind = $userLogins;
- }
- $db = Zend_Registry::get('db');
- $users = $db->fetchAll("SELECT *
- FROM ".Piwik_Common::prefixTable("user")."
+ static private $instance = null;
+
+ /**
+ * You can create your own Users Plugin to override this class.
+ * Example of how you would overwrite the UsersManager_API with your own class:
+ * Call the following in your plugin __construct() for example:
+ *
+ * Zend_Registry::set('UsersManager_API',Piwik_MyCustomUsersManager_API::getInstance());
+ *
+ * @throws Exception
+ * @return Piwik_UsersManager_API
+ */
+ static public function getInstance()
+ {
+ try {
+ $instance = Zend_Registry::get('UsersManager_API');
+ if (!($instance instanceof Piwik_UsersManager_API)) {
+ // Exception is caught below and corrected
+ throw new Exception('UsersManager_API must inherit Piwik_UsersManager_API');
+ }
+ self::$instance = $instance;
+ } catch (Exception $e) {
+ self::$instance = new self;
+ Zend_Registry::set('UsersManager_API', self::$instance);
+ }
+ return self::$instance;
+ }
+
+ const PREFERENCE_DEFAULT_REPORT = 'defaultReport';
+ const PREFERENCE_DEFAULT_REPORT_DATE = 'defaultReportDate';
+
+ /**
+ * Sets a user preference
+ * @param string $userLogin
+ * @param string $preferenceName
+ * @param string $preferenceValue
+ * @return void
+ */
+ public function setUserPreference($userLogin, $preferenceName, $preferenceValue)
+ {
+ Piwik::checkUserIsSuperUserOrTheUser($userLogin);
+ Piwik_SetOption($this->getPreferenceId($userLogin, $preferenceName), $preferenceValue);
+ }
+
+ /**
+ * Gets a user preference
+ * @param string $userLogin
+ * @param string $preferenceName
+ * @return bool|string
+ */
+ public function getUserPreference($userLogin, $preferenceName)
+ {
+ Piwik::checkUserIsSuperUserOrTheUser($userLogin);
+ return Piwik_GetOption($this->getPreferenceId($userLogin, $preferenceName));
+ }
+
+ private function getPreferenceId($login, $preference)
+ {
+ return $login . '_' . $preference;
+ }
+
+ /**
+ * Returns the list of all the users
+ *
+ * @param string $userLogins Comma separated list of users to select. If not specified, will return all users
+ * @return array the list of all the users
+ */
+ public function getUsers($userLogins = '')
+ {
+ Piwik::checkUserHasSomeAdminAccess();
+
+ $where = '';
+ $bind = array();
+ if (!empty($userLogins)) {
+ $userLogins = explode(',', $userLogins);
+ $where = 'WHERE login IN (' . Piwik_Common::getSqlStringFieldsArray($userLogins) . ')';
+ $bind = $userLogins;
+ }
+ $db = Zend_Registry::get('db');
+ $users = $db->fetchAll("SELECT *
+ FROM " . Piwik_Common::prefixTable("user") . "
$where
ORDER BY login ASC", $bind);
- // Non Super user can only access login & alias
- if(!Piwik::isUserIsSuperUser())
- {
- foreach($users as &$user)
- {
- $user = array('login' => $user['login'], 'alias' => $user['alias'] );
- }
- }
- return $users;
- }
-
- /**
- * Returns the list of all the users login
- *
- * @return array the list of all the users login
- */
- public function getUsersLogin()
- {
- Piwik::checkUserHasSomeAdminAccess();
-
- $db = Zend_Registry::get('db');
- $users = $db->fetchAll("SELECT login
- FROM ".Piwik_Common::prefixTable("user")."
+ // Non Super user can only access login & alias
+ if (!Piwik::isUserIsSuperUser()) {
+ foreach ($users as &$user) {
+ $user = array('login' => $user['login'], 'alias' => $user['alias']);
+ }
+ }
+ return $users;
+ }
+
+ /**
+ * Returns the list of all the users login
+ *
+ * @return array the list of all the users login
+ */
+ public function getUsersLogin()
+ {
+ Piwik::checkUserHasSomeAdminAccess();
+
+ $db = Zend_Registry::get('db');
+ $users = $db->fetchAll("SELECT login
+ FROM " . Piwik_Common::prefixTable("user") . "
ORDER BY login ASC");
- $return = array();
- foreach($users as $login)
- {
- $return[] = $login['login'];
- }
- return $return;
- }
-
- /**
- * For each user, returns the list of website IDs where the user has the supplied $access level.
- * If a user doesn't have the given $access to any website IDs,
- * the user will not be in the returned array.
- *
- * @param string Access can have the following values : 'view' or 'admin'
- *
- * @return array The returned array has the format
- * array(
- * login1 => array ( idsite1,idsite2),
- * login2 => array(idsite2),
- * ...
- * )
- *
- */
- public function getUsersSitesFromAccess( $access )
- {
- Piwik::checkUserIsSuperUser();
-
- $this->checkAccessType($access);
-
- $db = Zend_Registry::get('db');
- $users = $db->fetchAll("SELECT login,idsite
- FROM ".Piwik_Common::prefixTable("access")
- ." WHERE access = ?
+ $return = array();
+ foreach ($users as $login) {
+ $return[] = $login['login'];
+ }
+ return $return;
+ }
+
+ /**
+ * For each user, returns the list of website IDs where the user has the supplied $access level.
+ * If a user doesn't have the given $access to any website IDs,
+ * the user will not be in the returned array.
+ *
+ * @param string Access can have the following values : 'view' or 'admin'
+ *
+ * @return array The returned array has the format
+ * array(
+ * login1 => array ( idsite1,idsite2),
+ * login2 => array(idsite2),
+ * ...
+ * )
+ *
+ */
+ public function getUsersSitesFromAccess($access)
+ {
+ Piwik::checkUserIsSuperUser();
+
+ $this->checkAccessType($access);
+
+ $db = Zend_Registry::get('db');
+ $users = $db->fetchAll("SELECT login,idsite
+ FROM " . Piwik_Common::prefixTable("access")
+ . " WHERE access = ?
ORDER BY login, idsite", $access);
- $return = array();
- foreach($users as $user)
- {
- $return[$user['login']][] = $user['idsite'];
- }
- return $return;
-
- }
-
- /**
- * For each user, returns his access level for the given $idSite.
- * If a user doesn't have any access to the $idSite ('noaccess'),
- * the user will not be in the returned array.
- *
- * @param string website ID
- *
- * @return array The returned array has the format
- * array(
- * login1 => 'view',
- * login2 => 'admin',
- * login3 => 'view',
- * ...
- * )
- */
- public function getUsersAccessFromSite( $idSite )
- {
- Piwik::checkUserHasAdminAccess( $idSite );
-
- $db = Zend_Registry::get('db');
- $users = $db->fetchAll("SELECT login,access
- FROM ".Piwik_Common::prefixTable("access")
- ." WHERE idsite = ?", $idSite);
- $return = array();
- foreach($users as $user)
- {
- $return[$user['login']] = $user['access'];
- }
- return $return;
- }
-
- public function getUsersWithSiteAccess( $idSite, $access )
- {
- Piwik::checkUserHasAdminAccess( $idSite );
- $this->checkAccessType( $access );
-
- $db = Zend_Registry::get('db');
- $users = $db->fetchAll("SELECT login
- FROM ".Piwik_Common::prefixTable("access")
- ." WHERE idsite = ? AND access = ?", array($idSite, $access));
- $logins = array();
- foreach($users as $user)
- {
- $logins[] = $user['login'];
- }
- if(empty($logins))
- {
- return array();
- }
- $logins = implode(',', $logins);
- return $this->getUsers($logins);
- }
-
- /**
- * For each website ID, returns the access level of the given $userLogin.
- * If the user doesn't have any access to a website ('noaccess'),
- * this website will not be in the returned array.
- * If the user doesn't have any access, the returned array will be an empty array.
- *
- * @param string User that has to be valid
- *
- * @return array The returned array has the format
- * array(
- * idsite1 => 'view',
- * idsite2 => 'admin',
- * idsite3 => 'view',
- * ...
- * )
- */
- public function getSitesAccessFromUser( $userLogin )
- {
- Piwik::checkUserIsSuperUser();
- $this->checkUserExists($userLogin);
- $this->checkUserIsNotSuperUser($userLogin);
-
- $db = Zend_Registry::get('db');
- $users = $db->fetchAll("SELECT idsite,access
- FROM ".Piwik_Common::prefixTable("access")
- ." WHERE login = ?", $userLogin);
- $return = array();
- foreach($users as $user)
- {
- $return[] = array(
- 'site' => $user['idsite'],
- 'access' => $user['access'],
- );
- }
- return $return;
- }
-
- /**
- * Returns the user information (login, password md5, alias, email, date_registered, etc.)
- *
- * @param string the user login
- *
- * @return array the user information
- */
- public function getUser( $userLogin )
- {
- Piwik::checkUserIsSuperUserOrTheUser($userLogin);
- $this->checkUserExists($userLogin);
- $this->checkUserIsNotSuperUser($userLogin);
-
- $db = Zend_Registry::get('db');
- $user = $db->fetchRow("SELECT *
- FROM ".Piwik_Common::prefixTable("user")
- ." WHERE login = ?", $userLogin);
- return $user;
- }
-
- /**
- * Returns the user information (login, password md5, alias, email, date_registered, etc.)
- *
- * @param string the user email
- *
- * @return array the user information
- */
- public function getUserByEmail( $userEmail )
- {
- Piwik::checkUserIsSuperUser();
- $this->checkUserEmailExists($userEmail);
-
- $db = Zend_Registry::get('db');
- $user = $db->fetchRow("SELECT *
- FROM ".Piwik_Common::prefixTable("user")
- ." WHERE email = ?", $userEmail);
- return $user;
- }
-
- private function checkLogin($userLogin)
- {
- if($this->userExists($userLogin))
- {
- throw new Exception(Piwik_TranslateException('UsersManager_ExceptionLoginExists', $userLogin));
- }
-
- Piwik::checkValidLoginString($userLogin);
- }
-
- private function checkEmail($email)
- {
- if($this->userEmailExists($email))
- {
- throw new Exception(Piwik_TranslateException('UsersManager_ExceptionEmailExists', $email));
- }
-
- if(!Piwik::isValidEmailString($email))
- {
- throw new Exception(Piwik_TranslateException('UsersManager_ExceptionInvalidEmail'));
- }
- }
-
- private function getCleanAlias($alias,$userLogin)
- {
- if(empty($alias))
- {
- $alias = $userLogin;
- }
- return $alias;
- }
-
- /**
- * Add a user in the database.
- * A user is defined by
- * - a login that has to be unique and valid
- * - a password that has to be valid
- * - an alias
- * - an email that has to be in a correct format
- *
- * @see userExists()
- * @see isValidLoginString()
- * @see isValidPasswordString()
- * @see isValidEmailString()
- *
- * @exception in case of an invalid parameter
- */
- public function addUser( $userLogin, $password, $email, $alias = false )
- {
- Piwik::checkUserIsSuperUser();
-
- $this->checkLogin($userLogin);
- $this->checkUserIsNotSuperUser($userLogin);
- $this->checkEmail($email);
-
- $password = Piwik_Common::unsanitizeInputValue($password);
- Piwik_UsersManager::checkPassword($password);
-
- $alias = $this->getCleanAlias($alias,$userLogin);
- $passwordTransformed = Piwik_UsersManager::getPasswordHash($password);
-
- $token_auth = $this->getTokenAuth($userLogin, $passwordTransformed);
-
- $db = Zend_Registry::get('db');
-
- $db->insert( Piwik_Common::prefixTable("user"), array(
- 'login' => $userLogin,
- 'password' => $passwordTransformed,
- 'alias' => $alias,
- 'email' => $email,
- 'token_auth' => $token_auth,
- 'date_registered' => Piwik_Date::now()->getDatetime()
- )
- );
-
- // we reload the access list which doesn't yet take in consideration this new user
- Zend_Registry::get('access')->reloadAccess();
- Piwik_Tracker_Cache::deleteTrackerCache();
-
- Piwik_PostEvent('UsersManager.addUser', $userLogin);
- }
-
- /**
- * Updates a user in the database.
- * Only login and password are required (case when we update the password).
- * When the password changes, the key token for this user will change, which could break
- * its API calls.
- *
- * @see addUser() for all the parameters
- */
- public function updateUser( $userLogin, $password = false, $email = false, $alias = false,
- $_isPasswordHashed = false )
- {
- Piwik::checkUserIsSuperUserOrTheUser($userLogin);
- $this->checkUserIsNotAnonymous( $userLogin );
- $this->checkUserIsNotSuperUser($userLogin);
- $userInfo = $this->getUser($userLogin);
-
- if(empty($password))
- {
- $password = $userInfo['password'];
- }
- else
- {
- $password = Piwik_Common::unsanitizeInputValue($password);
- if (!$_isPasswordHashed)
- {
- Piwik_UsersManager::checkPassword($password);
- $password = Piwik_UsersManager::getPasswordHash($password);
- }
- }
-
- if(empty($alias))
- {
- $alias = $userInfo['alias'];
- }
-
- if(empty($email))
- {
- $email = $userInfo['email'];
- }
-
- if($email != $userInfo['email'])
- {
- $this->checkEmail($email);
- }
-
- $alias = $this->getCleanAlias($alias,$userLogin);
- $token_auth = $this->getTokenAuth($userLogin,$password);
-
- $db = Zend_Registry::get('db');
-
- $db->update( Piwik_Common::prefixTable("user"),
- array(
- 'password' => $password,
- 'alias' => $alias,
- 'email' => $email,
- 'token_auth' => $token_auth,
- ),
- "login = '$userLogin'"
- );
- Piwik_Tracker_Cache::deleteTrackerCache();
-
- Piwik_PostEvent('UsersManager.updateUser', $userLogin);
- }
-
- /**
- * Delete a user and all its access, given its login.
- *
- * @param string $userLogin the user login.
- *
- * @throws Exception if the user doesn't exist
- *
- * @return bool true on success
- */
- public function deleteUser( $userLogin )
- {
- Piwik::checkUserIsSuperUser();
- $this->checkUserIsNotAnonymous( $userLogin );
- $this->checkUserIsNotSuperUser($userLogin);
- if(!$this->userExists($userLogin))
- {
- throw new Exception(Piwik_TranslateException("UsersManager_ExceptionDeleteDoesNotExist", $userLogin));
- }
-
- $this->deleteUserOnly( $userLogin );
- $this->deleteUserAccess( $userLogin );
- Piwik_Tracker_Cache::deleteTrackerCache();
- }
-
- /**
- * Returns true if the given userLogin is known in the database
- *
- * @return bool true if the user is known
- */
- public function userExists( $userLogin )
- {
- $count = Piwik_FetchOne("SELECT count(*)
- FROM ".Piwik_Common::prefixTable("user"). "
+ $return = array();
+ foreach ($users as $user) {
+ $return[$user['login']][] = $user['idsite'];
+ }
+ return $return;
+
+ }
+
+ /**
+ * For each user, returns his access level for the given $idSite.
+ * If a user doesn't have any access to the $idSite ('noaccess'),
+ * the user will not be in the returned array.
+ *
+ * @param string website ID
+ *
+ * @return array The returned array has the format
+ * array(
+ * login1 => 'view',
+ * login2 => 'admin',
+ * login3 => 'view',
+ * ...
+ * )
+ */
+ public function getUsersAccessFromSite($idSite)
+ {
+ Piwik::checkUserHasAdminAccess($idSite);
+
+ $db = Zend_Registry::get('db');
+ $users = $db->fetchAll("SELECT login,access
+ FROM " . Piwik_Common::prefixTable("access")
+ . " WHERE idsite = ?", $idSite);
+ $return = array();
+ foreach ($users as $user) {
+ $return[$user['login']] = $user['access'];
+ }
+ return $return;
+ }
+
+ public function getUsersWithSiteAccess($idSite, $access)
+ {
+ Piwik::checkUserHasAdminAccess($idSite);
+ $this->checkAccessType($access);
+
+ $db = Zend_Registry::get('db');
+ $users = $db->fetchAll("SELECT login
+ FROM " . Piwik_Common::prefixTable("access")
+ . " WHERE idsite = ? AND access = ?", array($idSite, $access));
+ $logins = array();
+ foreach ($users as $user) {
+ $logins[] = $user['login'];
+ }
+ if (empty($logins)) {
+ return array();
+ }
+ $logins = implode(',', $logins);
+ return $this->getUsers($logins);
+ }
+
+ /**
+ * For each website ID, returns the access level of the given $userLogin.
+ * If the user doesn't have any access to a website ('noaccess'),
+ * this website will not be in the returned array.
+ * If the user doesn't have any access, the returned array will be an empty array.
+ *
+ * @param string User that has to be valid
+ *
+ * @return array The returned array has the format
+ * array(
+ * idsite1 => 'view',
+ * idsite2 => 'admin',
+ * idsite3 => 'view',
+ * ...
+ * )
+ */
+ public function getSitesAccessFromUser($userLogin)
+ {
+ Piwik::checkUserIsSuperUser();
+ $this->checkUserExists($userLogin);
+ $this->checkUserIsNotSuperUser($userLogin);
+
+ $db = Zend_Registry::get('db');
+ $users = $db->fetchAll("SELECT idsite,access
+ FROM " . Piwik_Common::prefixTable("access")
+ . " WHERE login = ?", $userLogin);
+ $return = array();
+ foreach ($users as $user) {
+ $return[] = array(
+ 'site' => $user['idsite'],
+ 'access' => $user['access'],
+ );
+ }
+ return $return;
+ }
+
+ /**
+ * Returns the user information (login, password md5, alias, email, date_registered, etc.)
+ *
+ * @param string the user login
+ *
+ * @return array the user information
+ */
+ public function getUser($userLogin)
+ {
+ Piwik::checkUserIsSuperUserOrTheUser($userLogin);
+ $this->checkUserExists($userLogin);
+ $this->checkUserIsNotSuperUser($userLogin);
+
+ $db = Zend_Registry::get('db');
+ $user = $db->fetchRow("SELECT *
+ FROM " . Piwik_Common::prefixTable("user")
+ . " WHERE login = ?", $userLogin);
+ return $user;
+ }
+
+ /**
+ * Returns the user information (login, password md5, alias, email, date_registered, etc.)
+ *
+ * @param string the user email
+ *
+ * @return array the user information
+ */
+ public function getUserByEmail($userEmail)
+ {
+ Piwik::checkUserIsSuperUser();
+ $this->checkUserEmailExists($userEmail);
+
+ $db = Zend_Registry::get('db');
+ $user = $db->fetchRow("SELECT *
+ FROM " . Piwik_Common::prefixTable("user")
+ . " WHERE email = ?", $userEmail);
+ return $user;
+ }
+
+ private function checkLogin($userLogin)
+ {
+ if ($this->userExists($userLogin)) {
+ throw new Exception(Piwik_TranslateException('UsersManager_ExceptionLoginExists', $userLogin));
+ }
+
+ Piwik::checkValidLoginString($userLogin);
+ }
+
+ private function checkEmail($email)
+ {
+ if ($this->userEmailExists($email)) {
+ throw new Exception(Piwik_TranslateException('UsersManager_ExceptionEmailExists', $email));
+ }
+
+ if (!Piwik::isValidEmailString($email)) {
+ throw new Exception(Piwik_TranslateException('UsersManager_ExceptionInvalidEmail'));
+ }
+ }
+
+ private function getCleanAlias($alias, $userLogin)
+ {
+ if (empty($alias)) {
+ $alias = $userLogin;
+ }
+ return $alias;
+ }
+
+ /**
+ * Add a user in the database.
+ * A user is defined by
+ * - a login that has to be unique and valid
+ * - a password that has to be valid
+ * - an alias
+ * - an email that has to be in a correct format
+ *
+ * @see userExists()
+ * @see isValidLoginString()
+ * @see isValidPasswordString()
+ * @see isValidEmailString()
+ *
+ * @exception in case of an invalid parameter
+ */
+ public function addUser($userLogin, $password, $email, $alias = false)
+ {
+ Piwik::checkUserIsSuperUser();
+
+ $this->checkLogin($userLogin);
+ $this->checkUserIsNotSuperUser($userLogin);
+ $this->checkEmail($email);
+
+ $password = Piwik_Common::unsanitizeInputValue($password);
+ Piwik_UsersManager::checkPassword($password);
+
+ $alias = $this->getCleanAlias($alias, $userLogin);
+ $passwordTransformed = Piwik_UsersManager::getPasswordHash($password);
+
+ $token_auth = $this->getTokenAuth($userLogin, $passwordTransformed);
+
+ $db = Zend_Registry::get('db');
+
+ $db->insert(Piwik_Common::prefixTable("user"), array(
+ 'login' => $userLogin,
+ 'password' => $passwordTransformed,
+ 'alias' => $alias,
+ 'email' => $email,
+ 'token_auth' => $token_auth,
+ 'date_registered' => Piwik_Date::now()->getDatetime()
+ )
+ );
+
+ // we reload the access list which doesn't yet take in consideration this new user
+ Zend_Registry::get('access')->reloadAccess();
+ Piwik_Tracker_Cache::deleteTrackerCache();
+
+ Piwik_PostEvent('UsersManager.addUser', $userLogin);
+ }
+
+ /**
+ * Updates a user in the database.
+ * Only login and password are required (case when we update the password).
+ * When the password changes, the key token for this user will change, which could break
+ * its API calls.
+ *
+ * @see addUser() for all the parameters
+ */
+ public function updateUser($userLogin, $password = false, $email = false, $alias = false,
+ $_isPasswordHashed = false)
+ {
+ Piwik::checkUserIsSuperUserOrTheUser($userLogin);
+ $this->checkUserIsNotAnonymous($userLogin);
+ $this->checkUserIsNotSuperUser($userLogin);
+ $userInfo = $this->getUser($userLogin);
+
+ if (empty($password)) {
+ $password = $userInfo['password'];
+ } else {
+ $password = Piwik_Common::unsanitizeInputValue($password);
+ if (!$_isPasswordHashed) {
+ Piwik_UsersManager::checkPassword($password);
+ $password = Piwik_UsersManager::getPasswordHash($password);
+ }
+ }
+
+ if (empty($alias)) {
+ $alias = $userInfo['alias'];
+ }
+
+ if (empty($email)) {
+ $email = $userInfo['email'];
+ }
+
+ if ($email != $userInfo['email']) {
+ $this->checkEmail($email);
+ }
+
+ $alias = $this->getCleanAlias($alias, $userLogin);
+ $token_auth = $this->getTokenAuth($userLogin, $password);
+
+ $db = Zend_Registry::get('db');
+
+ $db->update(Piwik_Common::prefixTable("user"),
+ array(
+ 'password' => $password,
+ 'alias' => $alias,
+ 'email' => $email,
+ 'token_auth' => $token_auth,
+ ),
+ "login = '$userLogin'"
+ );
+ Piwik_Tracker_Cache::deleteTrackerCache();
+
+ Piwik_PostEvent('UsersManager.updateUser', $userLogin);
+ }
+
+ /**
+ * Delete a user and all its access, given its login.
+ *
+ * @param string $userLogin the user login.
+ *
+ * @throws Exception if the user doesn't exist
+ *
+ * @return bool true on success
+ */
+ public function deleteUser($userLogin)
+ {
+ Piwik::checkUserIsSuperUser();
+ $this->checkUserIsNotAnonymous($userLogin);
+ $this->checkUserIsNotSuperUser($userLogin);
+ if (!$this->userExists($userLogin)) {
+ throw new Exception(Piwik_TranslateException("UsersManager_ExceptionDeleteDoesNotExist", $userLogin));
+ }
+
+ $this->deleteUserOnly($userLogin);
+ $this->deleteUserAccess($userLogin);
+ Piwik_Tracker_Cache::deleteTrackerCache();
+ }
+
+ /**
+ * Returns true if the given userLogin is known in the database
+ *
+ * @return bool true if the user is known
+ */
+ public function userExists($userLogin)
+ {
+ $count = Piwik_FetchOne("SELECT count(*)
+ FROM " . Piwik_Common::prefixTable("user") . "
WHERE login = ?", $userLogin);
- return $count != 0;
- }
-
- /**
- * Returns true if user with given email (userEmail) is known in the database, or the super user
- *
- * @return bool true if the user is known
- */
- public function userEmailExists( $userEmail )
- {
- Piwik::checkUserIsNotAnonymous();
- $count = Piwik_FetchOne("SELECT count(*)
- FROM ".Piwik_Common::prefixTable("user"). "
+ return $count != 0;
+ }
+
+ /**
+ * Returns true if user with given email (userEmail) is known in the database, or the super user
+ *
+ * @return bool true if the user is known
+ */
+ public function userEmailExists($userEmail)
+ {
+ Piwik::checkUserIsNotAnonymous();
+ $count = Piwik_FetchOne("SELECT count(*)
+ FROM " . Piwik_Common::prefixTable("user") . "
WHERE email = ?", $userEmail);
- return $count != 0
- || Piwik_Config::getInstance()->superuser['email'] == $userEmail;
- }
-
- /**
- * Set an access level to a given user for a list of websites ID.
- *
- * If access = 'noaccess' the current access (if any) will be deleted.
- * If access = 'view' or 'admin' the current access level is deleted and updated with the new value.
- *
- * @param string $userLogin The user login
- * @param string $access Access to grant. Must have one of the following value : noaccess, view, admin
- * @param int|array $idSites The array of idSites on which to apply the access level for the user.
- * If the value is "all" then we apply the access level to all the websites ID for which the current authentificated user has an 'admin' access.
- *
- * @throws Exception if the user doesn't exist
- * @throws Exception if the access parameter doesn't have a correct value
- * @throws Exception if any of the given website ID doesn't exist
- *
- * @return bool true on success
- */
- public function setUserAccess( $userLogin, $access, $idSites)
- {
- $this->checkAccessType( $access );
- $this->checkUserExists( $userLogin);
- $this->checkUserIsNotSuperUser($userLogin);
-
- if($userLogin == 'anonymous'
- && $access == 'admin')
- {
- throw new Exception(Piwik_TranslateException("UsersManager_ExceptionAdminAnonymous"));
- }
-
- // in case idSites is null we grant access to all the websites on which the current connected user
- // has an 'admin' access
- if($idSites === 'all')
- {
- $idSites = Piwik_SitesManager_API::getInstance()->getSitesIdWithAdminAccess();
- }
- // in case the idSites is an integer we build an array
- else
- {
- $idSites = Piwik_Site::getIdSitesFromIdSitesString($idSites);
- }
-
- if(empty($idSites))
- {
- throw new Exception('Specify at least one website ID in &idSites=');
- }
- // it is possible to set user access on websites only for the websites admin
- // basically an admin can give the view or the admin access to any user for the websites he manages
- Piwik::checkUserHasAdminAccess( $idSites );
-
- $this->deleteUserAccess( $userLogin, $idSites);
-
- // delete UserAccess
- $db = Zend_Registry::get('db');
-
- // if the access is noaccess then we don't save it as this is the default value
- // when no access are specified
- if($access != 'noaccess')
- {
- foreach($idSites as $idsite)
- {
- $db->insert( Piwik_Common::prefixTable("access"),
- array( "idsite" => $idsite,
- "login" => $userLogin,
- "access" => $access)
- );
- }
- }
-
- // we reload the access list which doesn't yet take in consideration this new user access
- Zend_Registry::get('access')->reloadAccess();
- Piwik_Tracker_Cache::deleteTrackerCache();
- }
-
- /**
- * Throws an exception is the user login doesn't exist
- *
- * @param string $userLogin user login
- * @throws Exception if the user doesn't exist
- */
- private function checkUserExists( $userLogin )
- {
- if(!$this->userExists($userLogin))
- {
- throw new Exception(Piwik_TranslateException("UsersManager_ExceptionUserDoesNotExist", $userLogin));
- }
- }
-
- /**
- * Throws an exception is the user email cannot be found
- *
- * @param string $userEmail user email
- * @throws Exception if the user doesn't exist
- */
- private function checkUserEmailExists( $userEmail )
- {
- if(!$this->userEmailExists($userEmail))
- {
- throw new Exception(Piwik_TranslateException("UsersManager_ExceptionUserDoesNotExist", $userEmail));
- }
- }
-
- private function checkUserIsNotAnonymous( $userLogin )
- {
- if($userLogin == 'anonymous')
- {
- throw new Exception(Piwik_TranslateException("UsersManager_ExceptionEditAnonymous"));
- }
- }
-
- private function checkUserIsNotSuperUser( $userLogin )
- {
- if($userLogin == Piwik_Config::getInstance()->superuser['login'])
- {
- throw new Exception(Piwik_TranslateException("UsersManager_ExceptionSuperUser"));
- }
- }
-
- private function checkAccessType($access)
- {
- $accessList = Piwik_Access::getListAccess();
-
- // do not allow to set the superUser access
- unset($accessList[array_search("superuser", $accessList)]);
-
- if(!in_array($access,$accessList))
- {
- throw new Exception(Piwik_TranslateException("UsersManager_ExceptionAccessValues", implode(", ", $accessList)));
- }
- }
-
- /**
- * Delete a user given its login.
- * The user's access are not deleted.
- *
- * @param string the user login.
- *
- */
- private function deleteUserOnly( $userLogin )
- {
- $db = Zend_Registry::get('db');
- $db->query("DELETE FROM ".Piwik_Common::prefixTable("user")." WHERE login = ?", $userLogin);
-
- Piwik_PostEvent('UsersManager.deleteUser', $userLogin);
- }
-
-
- /**
- * Delete the user access for the given websites.
- * The array of idsite must be either null OR the values must have been checked before for their validity!
- *
- * @param string the user login
- * @param array array of idsites on which to delete the access. If null then delete all the access for this user.
- *
- * @return bool true on success
- */
- private function deleteUserAccess( $userLogin, $idSites = null )
- {
- $db = Zend_Registry::get('db');
-
- if(is_null($idSites))
- {
- $db->query( "DELETE FROM ".Piwik_Common::prefixTable("access").
- " WHERE login = ?",
- array( $userLogin) );
- }
- else
- {
- foreach($idSites as $idsite)
- {
- $db->query( "DELETE FROM ".Piwik_Common::prefixTable("access").
- " WHERE idsite = ? AND login = ?",
- array($idsite, $userLogin)
- );
- }
- }
- }
-
- /**
- * Generates a unique MD5 for the given login & password
- *
- * @param string $userLogin Login
- * @param string $md5Password MD5ied string of the password
- * @throws Exception
- * @return string
- */
- public function getTokenAuth($userLogin, $md5Password)
- {
- if(strlen($md5Password) != 32)
- {
- throw new Exception(Piwik_TranslateException('UsersManager_ExceptionPasswordMD5HashExpected'));
- }
- return md5($userLogin . $md5Password );
- }
+ return $count != 0
+ || Piwik_Config::getInstance()->superuser['email'] == $userEmail;
+ }
+
+ /**
+ * Set an access level to a given user for a list of websites ID.
+ *
+ * If access = 'noaccess' the current access (if any) will be deleted.
+ * If access = 'view' or 'admin' the current access level is deleted and updated with the new value.
+ *
+ * @param string $userLogin The user login
+ * @param string $access Access to grant. Must have one of the following value : noaccess, view, admin
+ * @param int|array $idSites The array of idSites on which to apply the access level for the user.
+ * If the value is "all" then we apply the access level to all the websites ID for which the current authentificated user has an 'admin' access.
+ *
+ * @throws Exception if the user doesn't exist
+ * @throws Exception if the access parameter doesn't have a correct value
+ * @throws Exception if any of the given website ID doesn't exist
+ *
+ * @return bool true on success
+ */
+ public function setUserAccess($userLogin, $access, $idSites)
+ {
+ $this->checkAccessType($access);
+ $this->checkUserExists($userLogin);
+ $this->checkUserIsNotSuperUser($userLogin);
+
+ if ($userLogin == 'anonymous'
+ && $access == 'admin'
+ ) {
+ throw new Exception(Piwik_TranslateException("UsersManager_ExceptionAdminAnonymous"));
+ }
+
+ // in case idSites is null we grant access to all the websites on which the current connected user
+ // has an 'admin' access
+ if ($idSites === 'all') {
+ $idSites = Piwik_SitesManager_API::getInstance()->getSitesIdWithAdminAccess();
+ } // in case the idSites is an integer we build an array
+ else {
+ $idSites = Piwik_Site::getIdSitesFromIdSitesString($idSites);
+ }
+
+ if (empty($idSites)) {
+ throw new Exception('Specify at least one website ID in &idSites=');
+ }
+ // it is possible to set user access on websites only for the websites admin
+ // basically an admin can give the view or the admin access to any user for the websites he manages
+ Piwik::checkUserHasAdminAccess($idSites);
+
+ $this->deleteUserAccess($userLogin, $idSites);
+
+ // delete UserAccess
+ $db = Zend_Registry::get('db');
+
+ // if the access is noaccess then we don't save it as this is the default value
+ // when no access are specified
+ if ($access != 'noaccess') {
+ foreach ($idSites as $idsite) {
+ $db->insert(Piwik_Common::prefixTable("access"),
+ array("idsite" => $idsite,
+ "login" => $userLogin,
+ "access" => $access)
+ );
+ }
+ }
+
+ // we reload the access list which doesn't yet take in consideration this new user access
+ Zend_Registry::get('access')->reloadAccess();
+ Piwik_Tracker_Cache::deleteTrackerCache();
+ }
+
+ /**
+ * Throws an exception is the user login doesn't exist
+ *
+ * @param string $userLogin user login
+ * @throws Exception if the user doesn't exist
+ */
+ private function checkUserExists($userLogin)
+ {
+ if (!$this->userExists($userLogin)) {
+ throw new Exception(Piwik_TranslateException("UsersManager_ExceptionUserDoesNotExist", $userLogin));
+ }
+ }
+
+ /**
+ * Throws an exception is the user email cannot be found
+ *
+ * @param string $userEmail user email
+ * @throws Exception if the user doesn't exist
+ */
+ private function checkUserEmailExists($userEmail)
+ {
+ if (!$this->userEmailExists($userEmail)) {
+ throw new Exception(Piwik_TranslateException("UsersManager_ExceptionUserDoesNotExist", $userEmail));
+ }
+ }
+
+ private function checkUserIsNotAnonymous($userLogin)
+ {
+ if ($userLogin == 'anonymous') {
+ throw new Exception(Piwik_TranslateException("UsersManager_ExceptionEditAnonymous"));
+ }
+ }
+
+ private function checkUserIsNotSuperUser($userLogin)
+ {
+ if ($userLogin == Piwik_Config::getInstance()->superuser['login']) {
+ throw new Exception(Piwik_TranslateException("UsersManager_ExceptionSuperUser"));
+ }
+ }
+
+ private function checkAccessType($access)
+ {
+ $accessList = Piwik_Access::getListAccess();
+
+ // do not allow to set the superUser access
+ unset($accessList[array_search("superuser", $accessList)]);
+
+ if (!in_array($access, $accessList)) {
+ throw new Exception(Piwik_TranslateException("UsersManager_ExceptionAccessValues", implode(", ", $accessList)));
+ }
+ }
+
+ /**
+ * Delete a user given its login.
+ * The user's access are not deleted.
+ *
+ * @param string the user login.
+ *
+ */
+ private function deleteUserOnly($userLogin)
+ {
+ $db = Zend_Registry::get('db');
+ $db->query("DELETE FROM " . Piwik_Common::prefixTable("user") . " WHERE login = ?", $userLogin);
+
+ Piwik_PostEvent('UsersManager.deleteUser', $userLogin);
+ }
+
+
+ /**
+ * Delete the user access for the given websites.
+ * The array of idsite must be either null OR the values must have been checked before for their validity!
+ *
+ * @param string the user login
+ * @param array array of idsites on which to delete the access. If null then delete all the access for this user.
+ *
+ * @return bool true on success
+ */
+ private function deleteUserAccess($userLogin, $idSites = null)
+ {
+ $db = Zend_Registry::get('db');
+
+ if (is_null($idSites)) {
+ $db->query("DELETE FROM " . Piwik_Common::prefixTable("access") .
+ " WHERE login = ?",
+ array($userLogin));
+ } else {
+ foreach ($idSites as $idsite) {
+ $db->query("DELETE FROM " . Piwik_Common::prefixTable("access") .
+ " WHERE idsite = ? AND login = ?",
+ array($idsite, $userLogin)
+ );
+ }
+ }
+ }
+
+ /**
+ * Generates a unique MD5 for the given login & password
+ *
+ * @param string $userLogin Login
+ * @param string $md5Password MD5ied string of the password
+ * @throws Exception
+ * @return string
+ */
+ public function getTokenAuth($userLogin, $md5Password)
+ {
+ if (strlen($md5Password) != 32) {
+ throw new Exception(Piwik_TranslateException('UsersManager_ExceptionPasswordMD5HashExpected'));
+ }
+ return md5($userLogin . $md5Password);
+ }
}
diff --git a/plugins/UsersManager/Controller.php b/plugins/UsersManager/Controller.php
index c44a5df6e8..77d7f5f8ce 100644
--- a/plugins/UsersManager/Controller.php
+++ b/plugins/UsersManager/Controller.php
@@ -1,370 +1,335 @@
<?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_UsersManager
*/
/**
- *
+ *
* @package Piwik_UsersManager
*/
class Piwik_UsersManager_Controller extends Piwik_Controller_Admin
{
- static function orderByName($a, $b)
- {
- return strcmp($a['name'], $b['name']);
- }
-
- /**
- * The "Manage Users and Permissions" Admin UI screen
- */
- function index()
- {
- Piwik::checkUserIsNotAnonymous();
-
- $view = Piwik_View::factory('UsersManager');
-
- $IdSitesAdmin = Piwik_SitesManager_API::getInstance()->getSitesIdWithAdminAccess();
- $idSiteSelected = 1;
-
- if(count($IdSitesAdmin) > 0)
- {
- $defaultWebsiteId = $IdSitesAdmin[0];
- $idSiteSelected = Piwik_Common::getRequestVar('idSite', $defaultWebsiteId);
- }
-
- if($idSiteSelected==='all')
- {
- $usersAccessByWebsite = array();
- $defaultReportSiteName = Piwik_Translate('UsersManager_ApplyToAllWebsites');
- }
- else
- {
- $usersAccessByWebsite = Piwik_UsersManager_API::getInstance()->getUsersAccessFromSite( $idSiteSelected );
- $defaultReportSiteName = Piwik_Site::getNameFor($idSiteSelected);
- }
-
- // we dont want to display the user currently logged so that the user can't change his settings from admin to view...
- $currentlyLogged = Piwik::getCurrentUserLogin();
- $usersLogin = Piwik_UsersManager_API::getInstance()->getUsersLogin();
- foreach($usersLogin as $login)
- {
- if(!isset($usersAccessByWebsite[$login]))
- {
- $usersAccessByWebsite[$login] = 'noaccess';
- }
- }
- unset($usersAccessByWebsite[$currentlyLogged]);
-
-
- // $usersAccessByWebsite is not supposed to contain unexistant logins, but it does when upgrading from some old Piwik version
- foreach($usersAccessByWebsite as $login => $access)
- {
- if(!in_array($login, $usersLogin))
- {
- unset($usersAccessByWebsite[$login]);
- continue;
- }
- }
-
- ksort($usersAccessByWebsite);
-
- $users = array();
- $usersAliasByLogin = array();
- if(Piwik::isUserHasSomeAdminAccess())
- {
- $users = Piwik_UsersManager_API::getInstance()->getUsers();
- foreach($users as $user)
- {
- $usersAliasByLogin[$user['login']] = $user['alias'];
- }
- }
- $view->anonymousHasViewAccess = $this->hasAnonymousUserViewAccess($usersAccessByWebsite);
- $view->idSiteSelected = $idSiteSelected;
- $view->defaultReportSiteName = $defaultReportSiteName;
- $view->users = $users;
- $view->usersAliasByLogin = $usersAliasByLogin;
- $view->usersCount = count($users) - 1;
- $view->usersAccessByWebsite = $usersAccessByWebsite;
- $websites = Piwik_SitesManager_API::getInstance()->getSitesWithAdminAccess();
- uasort($websites, array('Piwik_UsersManager_Controller', 'orderByName'));
- $view->websites = $websites;
- $this->setBasicVariablesView($view);
- $view->menu = Piwik_GetAdminMenu();
- echo $view->render();
- }
-
- private function hasAnonymousUserViewAccess($usersAccessByWebsite)
- {
- $anonymousHasViewAccess = false;
- foreach ($usersAccessByWebsite as $login => $access) {
- if ($login == 'anonymous'
- && $access != 'noaccess'
- ) {
- $anonymousHasViewAccess = true;
- }
- }
- return $anonymousHasViewAccess;
- }
-
- /**
- * Returns default date for Piwik reports
- *
- * @param string $user
- * @return string today, yesterday, week, month, year
- */
- protected function getDefaultDateForUser($user)
- {
- $userSettingsDate = Piwik_UsersManager_API::getInstance()->getUserPreference($user, Piwik_UsersManager_API::PREFERENCE_DEFAULT_REPORT_DATE);
- if($userSettingsDate === false)
- {
- return Piwik_Config::getInstance()->General['default_day'];
- }
- return $userSettingsDate;
- }
-
- /**
- * The "User Settings" admin UI screen view
- */
- public function userSettings()
- {
- Piwik::checkUserIsNotAnonymous();
-
- $view = Piwik_View::factory('userSettings');
-
- $userLogin = Piwik::getCurrentUserLogin();
- if(Piwik::isUserIsSuperUser())
- {
- $view->userAlias = $userLogin;
- $view->userEmail = Piwik::getSuperUserEmail();
- if(!Piwik_Config::getInstance()->isFileWritable())
- {
- $view->configFileNotWritable = true;
- }
- }
- else
- {
- $user = Piwik_UsersManager_API::getInstance()->getUser($userLogin);
- $view->userAlias = $user['alias'];
- $view->userEmail = $user['email'];
- }
-
- $defaultReport = Piwik_UsersManager_API::getInstance()->getUserPreference($userLogin, Piwik_UsersManager_API::PREFERENCE_DEFAULT_REPORT);
- if($defaultReport === false)
- {
- $defaultReport = $this->getDefaultWebsiteId();
- }
- $view->defaultReport = $defaultReport;
-
- if ($defaultReport == 'MultiSites')
- {
- $view->defaultReportSiteName = Piwik_Site::getNameFor($this->getDefaultWebsiteId());
- }
- else
- {
- $view->defaultReportSiteName = Piwik_Site::getNameFor($defaultReport);
- }
-
- $view->defaultDate = $this->getDefaultDateForUser($userLogin);
- $view->availableDefaultDates = array(
- 'today' => Piwik_Translate('General_Today'),
- 'yesterday' => Piwik_Translate('General_Yesterday'),
- 'previous7' => Piwik_Translate('General_PreviousDays', 7),
- 'previous30' => Piwik_Translate('General_PreviousDays', 30),
- 'last7' => Piwik_Translate('General_LastDays', 7),
- 'last30' => Piwik_Translate('General_LastDays', 30),
- 'week' => Piwik_Translate('General_CurrentWeek'),
- 'month' => Piwik_Translate('General_CurrentMonth'),
- 'year' => Piwik_Translate('General_CurrentYear'),
- );
-
- $view->ignoreCookieSet = Piwik_Tracker_IgnoreCookie::isIgnoreCookieFound();
- $this->initViewAnonymousUserSettings($view);
- $view->piwikHost = Piwik_Url::getCurrentHost();
- $this->setBasicVariablesView($view);
- $view->menu = Piwik_GetAdminMenu();
- echo $view->render();
- }
-
- public function setIgnoreCookie()
- {
- Piwik::checkUserHasSomeViewAccess();
- Piwik::checkUserIsNotAnonymous();
- $this->checkTokenInUrl();
-
- Piwik_Tracker_IgnoreCookie::setIgnoreCookie();
- Piwik::redirectToModule('UsersManager', 'userSettings', array('token_auth'=> false));
- }
-
- /**
- * The Super User can modify Anonymous user settings
- * @param Piwik_View $view
- */
- protected function initViewAnonymousUserSettings($view)
- {
- if(!Piwik::isUserIsSuperUser())
- {
- return;
- }
- $userLogin = 'anonymous';
-
- // Which websites are available to the anonymous users?
- $anonymousSitesAccess = Piwik_UsersManager_API::getInstance()->getSitesAccessFromUser($userLogin);
- $anonymousSites = array();
- foreach($anonymousSitesAccess as $info)
- {
- $idSite = $info['site'];
- $site = Piwik_SitesManager_API::getInstance()->getSiteFromId($idSite);
- // Work around manual website deletion
- if(!empty($site))
- {
- $anonymousSites[$idSite] = $site;
- }
- }
- $view->anonymousSites = $anonymousSites;
-
- // Which report is displayed by default to the anonymous user?
- $anonymousDefaultReport = Piwik_UsersManager_API::getInstance()->getUserPreference($userLogin, Piwik_UsersManager_API::PREFERENCE_DEFAULT_REPORT);
- if($anonymousDefaultReport === false)
- {
- if(empty($anonymousSites))
- {
- $anonymousDefaultReport = Piwik::getLoginPluginName();
- }
- else
- {
- // we manually imitate what would happen, in case the anonymous user logs in
- // and is redirected to the first website available to him in the list
- // @see getDefaultWebsiteId()
- reset($anonymousSites);
- $anonymousDefaultReport = key($anonymousSites);
- }
- }
- $view->anonymousDefaultReport = $anonymousDefaultReport;
-
- $view->anonymousDefaultDate = $this->getDefaultDateForUser($userLogin);
- }
-
- /**
- * Records settings for the anonymous users (default report, default date)
- */
- public function recordAnonymousUserSettings()
- {
- $response = new Piwik_API_ResponseBuilder(Piwik_Common::getRequestVar('format'));
- try {
- Piwik::checkUserIsSuperUser();
- $this->checkTokenInUrl();
-
- $anonymousDefaultReport = Piwik_Common::getRequestVar('anonymousDefaultReport');
- $anonymousDefaultDate = Piwik_Common::getRequestVar('anonymousDefaultDate');
- $userLogin = 'anonymous';
- Piwik_UsersManager_API::getInstance()->setUserPreference($userLogin,
- Piwik_UsersManager_API::PREFERENCE_DEFAULT_REPORT,
- $anonymousDefaultReport);
- Piwik_UsersManager_API::getInstance()->setUserPreference($userLogin,
- Piwik_UsersManager_API::PREFERENCE_DEFAULT_REPORT_DATE,
- $anonymousDefaultDate);
- $toReturn = $response->getResponse();
- } catch(Exception $e ) {
- $toReturn = $response->getResponseException( $e );
- }
- echo $toReturn;
- }
-
- /**
- * Records settings from the "User Settings" page
- * @throws Exception
- */
- public function recordUserSettings()
- {
- $response = new Piwik_API_ResponseBuilder(Piwik_Common::getRequestVar('format'));
- try {
- $this->checkTokenInUrl();
-
- $alias = Piwik_Common::getRequestVar('alias');
- $email = Piwik_Common::getRequestVar('email');
- $defaultReport = Piwik_Common::getRequestVar('defaultReport');
- $defaultDate = Piwik_Common::getRequestVar('defaultDate');
-
- $newPassword = false;
- $password = Piwik_Common::getRequestvar('password', false);
- $passwordBis = Piwik_Common::getRequestvar('passwordBis', false);
- if(!empty($password)
- || !empty($passwordBis))
- {
- if($password != $passwordBis)
- {
- throw new Exception(Piwik_Translate('Login_PasswordsDoNotMatch'));
- }
- $newPassword = $password;
- }
-
- // UI disables password change on invalid host, but check here anyway
- if (!Piwik_Url::isValidHost()
- && $newPassword !== false)
- {
- throw new Exception("Cannot change password with untrusted hostname!");
- }
-
- $userLogin = Piwik::getCurrentUserLogin();
- if(Piwik::isUserIsSuperUser())
- {
- $superUser = Piwik_Config::getInstance()->superuser;
- $updatedSuperUser = false;
-
- if($newPassword !== false)
- {
- $newPassword = Piwik_Common::unsanitizeInputValue($newPassword);
- $md5PasswordSuperUser = md5($newPassword);
- $superUser['password'] = $md5PasswordSuperUser;
- $updatedSuperUser = true;
- }
- if($superUser['email'] != $email)
- {
- $superUser['email'] = $email;
- $updatedSuperUser = true;
- }
- if($updatedSuperUser)
- {
- Piwik_Config::getInstance()->superuser = $superUser;
- Piwik_Config::getInstance()->forceSave();
- }
- }
- else
- {
- Piwik_UsersManager_API::getInstance()->updateUser($userLogin, $newPassword, $email, $alias);
- if($newPassword !== false)
- {
- $newPassword = Piwik_Common::unsanitizeInputValue($newPassword);
- }
- }
-
- // logs the user in with the new password
- if($newPassword !== false)
- {
- $info = array(
- 'login' => $userLogin,
- 'md5Password' => md5($newPassword),
- 'rememberMe' => false,
- );
- Piwik_PostEvent('Login.initSession', $info);
- }
-
- Piwik_UsersManager_API::getInstance()->setUserPreference($userLogin,
- Piwik_UsersManager_API::PREFERENCE_DEFAULT_REPORT,
- $defaultReport);
- Piwik_UsersManager_API::getInstance()->setUserPreference($userLogin,
- Piwik_UsersManager_API::PREFERENCE_DEFAULT_REPORT_DATE,
- $defaultDate);
- $toReturn = $response->getResponse();
- } catch(Exception $e ) {
- $toReturn = $response->getResponseException( $e );
- }
- echo $toReturn;
- }
+ static function orderByName($a, $b)
+ {
+ return strcmp($a['name'], $b['name']);
+ }
+
+ /**
+ * The "Manage Users and Permissions" Admin UI screen
+ */
+ function index()
+ {
+ Piwik::checkUserIsNotAnonymous();
+
+ $view = Piwik_View::factory('UsersManager');
+
+ $IdSitesAdmin = Piwik_SitesManager_API::getInstance()->getSitesIdWithAdminAccess();
+ $idSiteSelected = 1;
+
+ if (count($IdSitesAdmin) > 0) {
+ $defaultWebsiteId = $IdSitesAdmin[0];
+ $idSiteSelected = Piwik_Common::getRequestVar('idSite', $defaultWebsiteId);
+ }
+
+ if ($idSiteSelected === 'all') {
+ $usersAccessByWebsite = array();
+ $defaultReportSiteName = Piwik_Translate('UsersManager_ApplyToAllWebsites');
+ } else {
+ $usersAccessByWebsite = Piwik_UsersManager_API::getInstance()->getUsersAccessFromSite($idSiteSelected);
+ $defaultReportSiteName = Piwik_Site::getNameFor($idSiteSelected);
+ }
+
+ // we dont want to display the user currently logged so that the user can't change his settings from admin to view...
+ $currentlyLogged = Piwik::getCurrentUserLogin();
+ $usersLogin = Piwik_UsersManager_API::getInstance()->getUsersLogin();
+ foreach ($usersLogin as $login) {
+ if (!isset($usersAccessByWebsite[$login])) {
+ $usersAccessByWebsite[$login] = 'noaccess';
+ }
+ }
+ unset($usersAccessByWebsite[$currentlyLogged]);
+
+
+ // $usersAccessByWebsite is not supposed to contain unexistant logins, but it does when upgrading from some old Piwik version
+ foreach ($usersAccessByWebsite as $login => $access) {
+ if (!in_array($login, $usersLogin)) {
+ unset($usersAccessByWebsite[$login]);
+ continue;
+ }
+ }
+
+ ksort($usersAccessByWebsite);
+
+ $users = array();
+ $usersAliasByLogin = array();
+ if (Piwik::isUserHasSomeAdminAccess()) {
+ $users = Piwik_UsersManager_API::getInstance()->getUsers();
+ foreach ($users as $user) {
+ $usersAliasByLogin[$user['login']] = $user['alias'];
+ }
+ }
+ $view->anonymousHasViewAccess = $this->hasAnonymousUserViewAccess($usersAccessByWebsite);
+ $view->idSiteSelected = $idSiteSelected;
+ $view->defaultReportSiteName = $defaultReportSiteName;
+ $view->users = $users;
+ $view->usersAliasByLogin = $usersAliasByLogin;
+ $view->usersCount = count($users) - 1;
+ $view->usersAccessByWebsite = $usersAccessByWebsite;
+ $websites = Piwik_SitesManager_API::getInstance()->getSitesWithAdminAccess();
+ uasort($websites, array('Piwik_UsersManager_Controller', 'orderByName'));
+ $view->websites = $websites;
+ $this->setBasicVariablesView($view);
+ $view->menu = Piwik_GetAdminMenu();
+ echo $view->render();
+ }
+
+ private function hasAnonymousUserViewAccess($usersAccessByWebsite)
+ {
+ $anonymousHasViewAccess = false;
+ foreach ($usersAccessByWebsite as $login => $access) {
+ if ($login == 'anonymous'
+ && $access != 'noaccess'
+ ) {
+ $anonymousHasViewAccess = true;
+ }
+ }
+ return $anonymousHasViewAccess;
+ }
+
+ /**
+ * Returns default date for Piwik reports
+ *
+ * @param string $user
+ * @return string today, yesterday, week, month, year
+ */
+ protected function getDefaultDateForUser($user)
+ {
+ $userSettingsDate = Piwik_UsersManager_API::getInstance()->getUserPreference($user, Piwik_UsersManager_API::PREFERENCE_DEFAULT_REPORT_DATE);
+ if ($userSettingsDate === false) {
+ return Piwik_Config::getInstance()->General['default_day'];
+ }
+ return $userSettingsDate;
+ }
+
+ /**
+ * The "User Settings" admin UI screen view
+ */
+ public function userSettings()
+ {
+ Piwik::checkUserIsNotAnonymous();
+
+ $view = Piwik_View::factory('userSettings');
+
+ $userLogin = Piwik::getCurrentUserLogin();
+ if (Piwik::isUserIsSuperUser()) {
+ $view->userAlias = $userLogin;
+ $view->userEmail = Piwik::getSuperUserEmail();
+ if (!Piwik_Config::getInstance()->isFileWritable()) {
+ $view->configFileNotWritable = true;
+ }
+ } else {
+ $user = Piwik_UsersManager_API::getInstance()->getUser($userLogin);
+ $view->userAlias = $user['alias'];
+ $view->userEmail = $user['email'];
+ }
+
+ $defaultReport = Piwik_UsersManager_API::getInstance()->getUserPreference($userLogin, Piwik_UsersManager_API::PREFERENCE_DEFAULT_REPORT);
+ if ($defaultReport === false) {
+ $defaultReport = $this->getDefaultWebsiteId();
+ }
+ $view->defaultReport = $defaultReport;
+
+ if ($defaultReport == 'MultiSites') {
+ $view->defaultReportSiteName = Piwik_Site::getNameFor($this->getDefaultWebsiteId());
+ } else {
+ $view->defaultReportSiteName = Piwik_Site::getNameFor($defaultReport);
+ }
+
+ $view->defaultDate = $this->getDefaultDateForUser($userLogin);
+ $view->availableDefaultDates = array(
+ 'today' => Piwik_Translate('General_Today'),
+ 'yesterday' => Piwik_Translate('General_Yesterday'),
+ 'previous7' => Piwik_Translate('General_PreviousDays', 7),
+ 'previous30' => Piwik_Translate('General_PreviousDays', 30),
+ 'last7' => Piwik_Translate('General_LastDays', 7),
+ 'last30' => Piwik_Translate('General_LastDays', 30),
+ 'week' => Piwik_Translate('General_CurrentWeek'),
+ 'month' => Piwik_Translate('General_CurrentMonth'),
+ 'year' => Piwik_Translate('General_CurrentYear'),
+ );
+
+ $view->ignoreCookieSet = Piwik_Tracker_IgnoreCookie::isIgnoreCookieFound();
+ $this->initViewAnonymousUserSettings($view);
+ $view->piwikHost = Piwik_Url::getCurrentHost();
+ $this->setBasicVariablesView($view);
+ $view->menu = Piwik_GetAdminMenu();
+ echo $view->render();
+ }
+
+ public function setIgnoreCookie()
+ {
+ Piwik::checkUserHasSomeViewAccess();
+ Piwik::checkUserIsNotAnonymous();
+ $this->checkTokenInUrl();
+
+ Piwik_Tracker_IgnoreCookie::setIgnoreCookie();
+ Piwik::redirectToModule('UsersManager', 'userSettings', array('token_auth' => false));
+ }
+
+ /**
+ * The Super User can modify Anonymous user settings
+ * @param Piwik_View $view
+ */
+ protected function initViewAnonymousUserSettings($view)
+ {
+ if (!Piwik::isUserIsSuperUser()) {
+ return;
+ }
+ $userLogin = 'anonymous';
+
+ // Which websites are available to the anonymous users?
+ $anonymousSitesAccess = Piwik_UsersManager_API::getInstance()->getSitesAccessFromUser($userLogin);
+ $anonymousSites = array();
+ foreach ($anonymousSitesAccess as $info) {
+ $idSite = $info['site'];
+ $site = Piwik_SitesManager_API::getInstance()->getSiteFromId($idSite);
+ // Work around manual website deletion
+ if (!empty($site)) {
+ $anonymousSites[$idSite] = $site;
+ }
+ }
+ $view->anonymousSites = $anonymousSites;
+
+ // Which report is displayed by default to the anonymous user?
+ $anonymousDefaultReport = Piwik_UsersManager_API::getInstance()->getUserPreference($userLogin, Piwik_UsersManager_API::PREFERENCE_DEFAULT_REPORT);
+ if ($anonymousDefaultReport === false) {
+ if (empty($anonymousSites)) {
+ $anonymousDefaultReport = Piwik::getLoginPluginName();
+ } else {
+ // we manually imitate what would happen, in case the anonymous user logs in
+ // and is redirected to the first website available to him in the list
+ // @see getDefaultWebsiteId()
+ reset($anonymousSites);
+ $anonymousDefaultReport = key($anonymousSites);
+ }
+ }
+ $view->anonymousDefaultReport = $anonymousDefaultReport;
+
+ $view->anonymousDefaultDate = $this->getDefaultDateForUser($userLogin);
+ }
+
+ /**
+ * Records settings for the anonymous users (default report, default date)
+ */
+ public function recordAnonymousUserSettings()
+ {
+ $response = new Piwik_API_ResponseBuilder(Piwik_Common::getRequestVar('format'));
+ try {
+ Piwik::checkUserIsSuperUser();
+ $this->checkTokenInUrl();
+
+ $anonymousDefaultReport = Piwik_Common::getRequestVar('anonymousDefaultReport');
+ $anonymousDefaultDate = Piwik_Common::getRequestVar('anonymousDefaultDate');
+ $userLogin = 'anonymous';
+ Piwik_UsersManager_API::getInstance()->setUserPreference($userLogin,
+ Piwik_UsersManager_API::PREFERENCE_DEFAULT_REPORT,
+ $anonymousDefaultReport);
+ Piwik_UsersManager_API::getInstance()->setUserPreference($userLogin,
+ Piwik_UsersManager_API::PREFERENCE_DEFAULT_REPORT_DATE,
+ $anonymousDefaultDate);
+ $toReturn = $response->getResponse();
+ } catch (Exception $e) {
+ $toReturn = $response->getResponseException($e);
+ }
+ echo $toReturn;
+ }
+
+ /**
+ * Records settings from the "User Settings" page
+ * @throws Exception
+ */
+ public function recordUserSettings()
+ {
+ $response = new Piwik_API_ResponseBuilder(Piwik_Common::getRequestVar('format'));
+ try {
+ $this->checkTokenInUrl();
+
+ $alias = Piwik_Common::getRequestVar('alias');
+ $email = Piwik_Common::getRequestVar('email');
+ $defaultReport = Piwik_Common::getRequestVar('defaultReport');
+ $defaultDate = Piwik_Common::getRequestVar('defaultDate');
+
+ $newPassword = false;
+ $password = Piwik_Common::getRequestvar('password', false);
+ $passwordBis = Piwik_Common::getRequestvar('passwordBis', false);
+ if (!empty($password)
+ || !empty($passwordBis)
+ ) {
+ if ($password != $passwordBis) {
+ throw new Exception(Piwik_Translate('Login_PasswordsDoNotMatch'));
+ }
+ $newPassword = $password;
+ }
+
+ // UI disables password change on invalid host, but check here anyway
+ if (!Piwik_Url::isValidHost()
+ && $newPassword !== false
+ ) {
+ throw new Exception("Cannot change password with untrusted hostname!");
+ }
+
+ $userLogin = Piwik::getCurrentUserLogin();
+ if (Piwik::isUserIsSuperUser()) {
+ $superUser = Piwik_Config::getInstance()->superuser;
+ $updatedSuperUser = false;
+
+ if ($newPassword !== false) {
+ $newPassword = Piwik_Common::unsanitizeInputValue($newPassword);
+ $md5PasswordSuperUser = md5($newPassword);
+ $superUser['password'] = $md5PasswordSuperUser;
+ $updatedSuperUser = true;
+ }
+ if ($superUser['email'] != $email) {
+ $superUser['email'] = $email;
+ $updatedSuperUser = true;
+ }
+ if ($updatedSuperUser) {
+ Piwik_Config::getInstance()->superuser = $superUser;
+ Piwik_Config::getInstance()->forceSave();
+ }
+ } else {
+ Piwik_UsersManager_API::getInstance()->updateUser($userLogin, $newPassword, $email, $alias);
+ if ($newPassword !== false) {
+ $newPassword = Piwik_Common::unsanitizeInputValue($newPassword);
+ }
+ }
+
+ // logs the user in with the new password
+ if ($newPassword !== false) {
+ $info = array(
+ 'login' => $userLogin,
+ 'md5Password' => md5($newPassword),
+ 'rememberMe' => false,
+ );
+ Piwik_PostEvent('Login.initSession', $info);
+ }
+
+ Piwik_UsersManager_API::getInstance()->setUserPreference($userLogin,
+ Piwik_UsersManager_API::PREFERENCE_DEFAULT_REPORT,
+ $defaultReport);
+ Piwik_UsersManager_API::getInstance()->setUserPreference($userLogin,
+ Piwik_UsersManager_API::PREFERENCE_DEFAULT_REPORT_DATE,
+ $defaultDate);
+ $toReturn = $response->getResponse();
+ } catch (Exception $e) {
+ $toReturn = $response->getResponseException($e);
+ }
+ echo $toReturn;
+ }
}
diff --git a/plugins/UsersManager/UsersManager.php b/plugins/UsersManager/UsersManager.php
index 57e4d42a7e..41334931c2 100644
--- a/plugins/UsersManager/UsersManager.php
+++ b/plugins/UsersManager/UsersManager.php
@@ -16,142 +16,139 @@
*/
class Piwik_UsersManager extends Piwik_Plugin
{
- const PASSWORD_MIN_LENGTH = 6;
- const PASSWORD_MAX_LENGTH = 26;
+ const PASSWORD_MIN_LENGTH = 6;
+ const PASSWORD_MAX_LENGTH = 26;
- /**
- * Plugin information
- *
- * @see Piwik_Plugin
- *
- * @return array
- */
- public function getInformation()
- {
- $info = array(
- 'description' => Piwik_Translate('UsersManager_PluginDescription'),
- 'author' => 'Piwik',
- 'author_homepage' => 'http://piwik.org/',
- 'version' => Piwik_Version::VERSION,
- );
+ /**
+ * Plugin information
+ *
+ * @see Piwik_Plugin
+ *
+ * @return array
+ */
+ public function getInformation()
+ {
+ $info = array(
+ 'description' => Piwik_Translate('UsersManager_PluginDescription'),
+ 'author' => 'Piwik',
+ 'author_homepage' => 'http://piwik.org/',
+ 'version' => Piwik_Version::VERSION,
+ );
- return $info;
- }
+ return $info;
+ }
- /**
- * Get list of hooks to register.
- *
- * @see Piwik_PluginsManager.loadPlugin()
- *
- * @return array
- */
- function getListHooksRegistered()
- {
- return array(
- 'AdminMenu.add' => 'addMenu',
- 'AssetManager.getJsFiles' => 'getJsFiles',
- 'SitesManager.deleteSite' => 'deleteSite',
- 'Common.fetchWebsiteAttributes' => 'recordAdminUsersInCache',
- );
- }
+ /**
+ * Get list of hooks to register.
+ *
+ * @see Piwik_PluginsManager.loadPlugin()
+ *
+ * @return array
+ */
+ function getListHooksRegistered()
+ {
+ return array(
+ 'AdminMenu.add' => 'addMenu',
+ 'AssetManager.getJsFiles' => 'getJsFiles',
+ 'SitesManager.deleteSite' => 'deleteSite',
+ 'Common.fetchWebsiteAttributes' => 'recordAdminUsersInCache',
+ );
+ }
- /**
- * Hooks when a website tracker cache is flushed (website/user updated, cache deleted, or empty cache)
- * Will record in the tracker config file the list of Admin token_auth for this website. This
- * will be used when the Tracking API is used with setIp(), setForceDateTime(), setVisitorId(), etc.
- *
- * @param Piwik_Event_Notification $notification notification object
- * @return void
- */
- function recordAdminUsersInCache($notification)
- {
- $idSite = $notification->getNotificationInfo();
- // add the 'hosts' entry in the website array
- $users = Piwik_UsersManager_API::getInstance()->getUsersWithSiteAccess($idSite, 'admin');
+ /**
+ * Hooks when a website tracker cache is flushed (website/user updated, cache deleted, or empty cache)
+ * Will record in the tracker config file the list of Admin token_auth for this website. This
+ * will be used when the Tracking API is used with setIp(), setForceDateTime(), setVisitorId(), etc.
+ *
+ * @param Piwik_Event_Notification $notification notification object
+ * @return void
+ */
+ function recordAdminUsersInCache($notification)
+ {
+ $idSite = $notification->getNotificationInfo();
+ // add the 'hosts' entry in the website array
+ $users = Piwik_UsersManager_API::getInstance()->getUsersWithSiteAccess($idSite, 'admin');
- $tokens = array();
- foreach($users as $user)
- {
- $tokens[] = $user['token_auth'];
- }
- $array =& $notification->getNotificationObject();
- $array['admin_token_auth'] = $tokens;
- }
+ $tokens = array();
+ foreach ($users as $user) {
+ $tokens[] = $user['token_auth'];
+ }
+ $array =& $notification->getNotificationObject();
+ $array['admin_token_auth'] = $tokens;
+ }
- /**
- * Delete user preferences associated with a particular site
- *
- * @param Piwik_Event_Notification $notification notification object
- */
- function deleteSite($notification)
- {
- $idSite = &$notification->getNotificationObject();
+ /**
+ * Delete user preferences associated with a particular site
+ *
+ * @param Piwik_Event_Notification $notification notification object
+ */
+ function deleteSite($notification)
+ {
+ $idSite = & $notification->getNotificationObject();
- Piwik_Option::getInstance()->deleteLike('%\_' . Piwik_UsersManager_API::PREFERENCE_DEFAULT_REPORT, $idSite);
- }
+ Piwik_Option::getInstance()->deleteLike('%\_' . Piwik_UsersManager_API::PREFERENCE_DEFAULT_REPORT, $idSite);
+ }
- /**
- * Return list of plug-in specific JavaScript files to be imported by the asset manager
- *
- * @see Piwik_AssetManager
- *
- * @param Piwik_Event_Notification $notification notification object
- */
- function getJsFiles($notification)
- {
- $jsFiles = &$notification->getNotificationObject();
+ /**
+ * Return list of plug-in specific JavaScript files to be imported by the asset manager
+ *
+ * @see Piwik_AssetManager
+ *
+ * @param Piwik_Event_Notification $notification notification object
+ */
+ function getJsFiles($notification)
+ {
+ $jsFiles = & $notification->getNotificationObject();
- $jsFiles[] = "plugins/UsersManager/templates/UsersManager.js";
- $jsFiles[] = "plugins/UsersManager/templates/userSettings.js";
- }
+ $jsFiles[] = "plugins/UsersManager/templates/UsersManager.js";
+ $jsFiles[] = "plugins/UsersManager/templates/userSettings.js";
+ }
- /**
- * Add admin menu items
- */
- function addMenu()
- {
- Piwik_AddAdminSubMenu('CoreAdminHome_MenuManage', 'UsersManager_MenuUsers',
- array('module' => 'UsersManager', 'action' => 'index'),
- Piwik::isUserHasSomeAdminAccess(),
- $order = 2);
- Piwik_AddAdminSubMenu('CoreAdminHome_MenuManage', 'UsersManager_MenuUserSettings',
- array('module' => 'UsersManager', 'action' => 'userSettings'),
- Piwik::isUserHasSomeViewAccess(),
- $order = 3);
- }
+ /**
+ * Add admin menu items
+ */
+ function addMenu()
+ {
+ Piwik_AddAdminSubMenu('CoreAdminHome_MenuManage', 'UsersManager_MenuUsers',
+ array('module' => 'UsersManager', 'action' => 'index'),
+ Piwik::isUserHasSomeAdminAccess(),
+ $order = 2);
+ Piwik_AddAdminSubMenu('CoreAdminHome_MenuManage', 'UsersManager_MenuUserSettings',
+ array('module' => 'UsersManager', 'action' => 'userSettings'),
+ Piwik::isUserHasSomeViewAccess(),
+ $order = 3);
+ }
- /**
- * Returns true if the password is complex enough (at least 6 characters and max 26 characters)
- *
- * @param string email
- * @return bool
- */
- public static function isValidPasswordString($input)
- {
- if(!Piwik::isChecksEnabled()
- && !empty($input)
- )
- {
- return true;
- }
- $l = strlen($input);
- return $l >= self::PASSWORD_MIN_LENGTH && $l <= self::PASSWORD_MAX_LENGTH;
- }
+ /**
+ * Returns true if the password is complex enough (at least 6 characters and max 26 characters)
+ *
+ * @param string email
+ * @return bool
+ */
+ public static function isValidPasswordString($input)
+ {
+ if (!Piwik::isChecksEnabled()
+ && !empty($input)
+ ) {
+ return true;
+ }
+ $l = strlen($input);
+ return $l >= self::PASSWORD_MIN_LENGTH && $l <= self::PASSWORD_MAX_LENGTH;
+ }
- public static function checkPassword($password)
- {
- if(!self::isValidPasswordString($password))
- {
- throw new Exception(Piwik_TranslateException('UsersManager_ExceptionInvalidPassword', array(self::PASSWORD_MIN_LENGTH,
- self::PASSWORD_MAX_LENGTH)));
- }
- }
+ public static function checkPassword($password)
+ {
+ if (!self::isValidPasswordString($password)) {
+ throw new Exception(Piwik_TranslateException('UsersManager_ExceptionInvalidPassword', array(self::PASSWORD_MIN_LENGTH,
+ self::PASSWORD_MAX_LENGTH)));
+ }
+ }
- public static function getPasswordHash($password)
- {
- // if change here, should also edit the installation process
- // to change how the root pwd is saved in the config file
- return md5($password);
- }
+ public static function getPasswordHash($password)
+ {
+ // if change here, should also edit the installation process
+ // to change how the root pwd is saved in the config file
+ return md5($password);
+ }
}
diff --git a/plugins/UsersManager/templates/UsersManager.js b/plugins/UsersManager/templates/UsersManager.js
index 505e400f44..1da9a43943 100644
--- a/plugins/UsersManager/templates/UsersManager.js
+++ b/plugins/UsersManager/templates/UsersManager.js
@@ -5,14 +5,13 @@
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
-function sendUpdateUserAJAX( row )
-{
- var parameters = {};
- parameters.userLogin = $(row).children('#userLogin').html();
- var password = $(row).find('input#password').val();
- if(password != '-') parameters.password = password;
- parameters.email = $(row).find('input#email').val();
- parameters.alias = $(row).find('input#alias').val();
+function sendUpdateUserAJAX(row) {
+ var parameters = {};
+ parameters.userLogin = $(row).children('#userLogin').html();
+ var password = $(row).find('input#password').val();
+ if (password != '-') parameters.password = password;
+ parameters.email = $(row).find('input#email').val();
+ parameters.alias = $(row).find('input#alias').val();
var ajaxHandler = new ajaxHelper();
ajaxHandler.addParams({
@@ -26,8 +25,7 @@ function sendUpdateUserAJAX( row )
ajaxHandler.send(true);
}
-function sendDeleteUserAJAX( login )
-{
+function sendDeleteUserAJAX(login) {
var ajaxHandler = new ajaxHelper();
ajaxHandler.addParams({
module: 'API',
@@ -41,13 +39,12 @@ function sendDeleteUserAJAX( login )
ajaxHandler.send(true);
}
-function sendAddUserAJAX( row )
-{
- var parameters = {};
- parameters.userLogin = $(row).find('input#useradd_login').val();
- parameters.password = $(row).find('input#useradd_password').val();
- parameters.email = $(row).find('input#useradd_email').val();
- parameters.alias = $(row).find('input#useradd_alias').val();
+function sendAddUserAJAX(row) {
+ var parameters = {};
+ parameters.userLogin = $(row).find('input#useradd_login').val();
+ parameters.password = $(row).find('input#useradd_password').val();
+ parameters.email = $(row).find('input#useradd_email').val();
+ parameters.alias = $(row).find('input#useradd_alias').val();
var ajaxHandler = new ajaxHelper();
ajaxHandler.addParams({
@@ -62,17 +59,15 @@ function sendAddUserAJAX( row )
ajaxHandler.send(true);
}
-function getIdSites()
-{
- return $('.custom_select_main_link').attr('siteid');
+function getIdSites() {
+ return $('.custom_select_main_link').attr('siteid');
}
-function sendUpdateUserAccess(login, access, successCallback)
-{
- var parameters = {};
- parameters.userLogin = login;
- parameters.access = access;
- parameters.idSites = getIdSites();
+function sendUpdateUserAccess(login, access, successCallback) {
+ var parameters = {};
+ parameters.userLogin = login;
+ parameters.access = access;
+ parameters.idSites = getIdSites();
var ajaxHandler = new ajaxHelper();
ajaxHandler.addParams({
@@ -87,171 +82,165 @@ function sendUpdateUserAccess(login, access, successCallback)
ajaxHandler.send(true);
}
-function submitOnEnter(e)
-{
- var key=e.keyCode || e.which;
- if (key==13)
- {
- $(this).find('.adduser').click();
- $(this).find('.updateuser').click();
- }
+function submitOnEnter(e) {
+ var key = e.keyCode || e.which;
+ if (key == 13) {
+ $(this).find('.adduser').click();
+ $(this).find('.updateuser').click();
+ }
}
-function launchAjaxRequest(self, successCallback)
-{
+function launchAjaxRequest(self, successCallback) {
sendUpdateUserAccess(
$(self).parent().parent().find('#login').html(), //if changed change also the modal
$(self).parent().attr('id'),
successCallback
);
}
-function hideAccessUpdated()
-{
- setTimeout(function(){
- $('#accessUpdated').fadeOut(500);
- }, 2000);
+function hideAccessUpdated() {
+ setTimeout(function () {
+ $('#accessUpdated').fadeOut(500);
+ }, 2000);
}
-function bindUpdateAccess()
-{
- var self = this;
- hideAccessUpdated(1);
- // callback called when the ajax request Update the user permissions is successful
- function successCallback (response)
- {
+function bindUpdateAccess() {
+ var self = this;
+ hideAccessUpdated(1);
+ // callback called when the ajax request Update the user permissions is successful
+ function successCallback(response) {
var mainDiv = $(self).parent().parent();
var login = $('#login', mainDiv).text();
mainDiv.find('.accessGranted')
- .attr("src","plugins/UsersManager/images/no-access.png" )
- .attr("class","updateAccess" )
+ .attr("src", "plugins/UsersManager/images/no-access.png")
+ .attr("class", "updateAccess")
.click(bindUpdateAccess)
- ;
+ ;
$(self)
- .attr('src',"plugins/UsersManager/images/ok.png" )
- .attr('class',"accessGranted" )
- ;
+ .attr('src', "plugins/UsersManager/images/ok.png")
+ .attr('class', "accessGranted")
+ ;
$('#accessUpdated').css('display', 'inline-block');
hideAccessUpdated();
// reload if user anonymous was updated, since we display a Notice message when anon has view access
- if(login == 'anonymous') {
+ if (login == 'anonymous') {
window.location.reload();
}
- }
-
- var idSite = getIdSites();
- if(idSite == 'all')
- {
- var target = this;
-
- //ask confirmation
- var userLogin = $(this).parent().parent().find('#login').text();
- $('#confirm').find('#login').text( userLogin ); // if changed here change also the launchAjaxRequest
+ }
- function onValidate()
- {
- launchAjaxRequest(target, successCallback);
- }
- piwikHelper.modalConfirm( '#confirm', {yes: onValidate})
- }
- else
- {
- launchAjaxRequest(this, successCallback);
- }
+ var idSite = getIdSites();
+ if (idSite == 'all') {
+ var target = this;
+
+ //ask confirmation
+ var userLogin = $(this).parent().parent().find('#login').text();
+ $('#confirm').find('#login').text(userLogin); // if changed here change also the launchAjaxRequest
+
+ function onValidate() {
+ launchAjaxRequest(target, successCallback);
+ }
+
+ piwikHelper.modalConfirm('#confirm', {yes: onValidate})
+ }
+ else {
+ launchAjaxRequest(this, successCallback);
+ }
}
-$(document).ready( function() {
- var alreadyEdited = new Array;
- // when click on edituser, the cells become editable
- $('.edituser')
- .click( function() {
- piwikHelper.hideAjaxError();
- var idRow = $(this).attr('id');
- if(alreadyEdited[idRow]==1) return;
- alreadyEdited[idRow] = 1;
- $('tr#'+idRow+' .editable').each(
- // make the fields editable
- // change the EDIT button to VALID button
- function (i,n) {
- var contentBefore = $(n).text();
- var idName = $(n).attr('id');
- if(idName != 'userLogin')
- {
- var contentAfter = '<input id="'+idName+'" value="'+piwikHelper.htmlEntities(contentBefore)+'" size="25" />';
- $(n).html(contentAfter);
- }
- }
- );
-
- $(this)
- .toggle()
- .parent()
- .prepend( $('<input type="submit" class="submit updateuser" value="'+_pk_translate('General_Save_js')+'" />')
- .click( function(){
- var onValidate = function() {
- sendUpdateUserAJAX($('tr#'+idRow));
- };
- if($('tr#'+idRow).find('input#password').val() != '-') {
- piwikHelper.modalConfirm( '#confirmPasswordChange', {yes: onValidate});
- } else {
- onValidate();
- }
- } )
- );
- });
-
- $('.editable').keypress( submitOnEnter );
-
- $('td.editable')
- .click( function(){ $(this).parent().find('.edituser').click(); } );
-
- // when click on deleteuser, the we ask for confirmation and then delete the user
- $('.deleteuser')
- .click( function() {
- piwikHelper.hideAjaxError();
- var idRow = $(this).attr('id');
- var loginToDelete = $(this).parent().parent().find('#userLogin').html();
- $('#confirmUserRemove h2').text(sprintf(_pk_translate('UsersManager_DeleteConfirm_js'),'"'+loginToDelete+'"'));
- piwikHelper.modalConfirm( '#confirmUserRemove', {yes: function(){ sendDeleteUserAJAX( loginToDelete ); }});
- }
- );
-
- $('.addrow').click( function() {
- piwikHelper.hideAjaxError();
- $(this).toggle();
-
- var numberOfRows = $('table#users')[0].rows.length;
- var newRowId = numberOfRows + 1;
- newRowId = 'row' + newRowId;
-
- $(' <tr id="'+newRowId+'">\
+$(document).ready(function () {
+ var alreadyEdited = new Array;
+ // when click on edituser, the cells become editable
+ $('.edituser')
+ .click(function () {
+ piwikHelper.hideAjaxError();
+ var idRow = $(this).attr('id');
+ if (alreadyEdited[idRow] == 1) return;
+ alreadyEdited[idRow] = 1;
+ $('tr#' + idRow + ' .editable').each(
+ // make the fields editable
+ // change the EDIT button to VALID button
+ function (i, n) {
+ var contentBefore = $(n).text();
+ var idName = $(n).attr('id');
+ if (idName != 'userLogin') {
+ var contentAfter = '<input id="' + idName + '" value="' + piwikHelper.htmlEntities(contentBefore) + '" size="25" />';
+ $(n).html(contentAfter);
+ }
+ }
+ );
+
+ $(this)
+ .toggle()
+ .parent()
+ .prepend($('<input type="submit" class="submit updateuser" value="' + _pk_translate('General_Save_js') + '" />')
+ .click(function () {
+ var onValidate = function () {
+ sendUpdateUserAJAX($('tr#' + idRow));
+ };
+ if ($('tr#' + idRow).find('input#password').val() != '-') {
+ piwikHelper.modalConfirm('#confirmPasswordChange', {yes: onValidate});
+ } else {
+ onValidate();
+ }
+ })
+ );
+ });
+
+ $('.editable').keypress(submitOnEnter);
+
+ $('td.editable')
+ .click(function () { $(this).parent().find('.edituser').click(); });
+
+ // when click on deleteuser, the we ask for confirmation and then delete the user
+ $('.deleteuser')
+ .click(function () {
+ piwikHelper.hideAjaxError();
+ var idRow = $(this).attr('id');
+ var loginToDelete = $(this).parent().parent().find('#userLogin').html();
+ $('#confirmUserRemove h2').text(sprintf(_pk_translate('UsersManager_DeleteConfirm_js'), '"' + loginToDelete + '"'));
+ piwikHelper.modalConfirm('#confirmUserRemove', {yes: function () { sendDeleteUserAJAX(loginToDelete); }});
+ }
+ );
+
+ $('.addrow').click(function () {
+ piwikHelper.hideAjaxError();
+ $(this).toggle();
+
+ var numberOfRows = $('table#users')[0].rows.length;
+ var newRowId = numberOfRows + 1;
+ newRowId = 'row' + newRowId;
+
+ $(' <tr id="' + newRowId + '">\
<td><input id="useradd_login" value="login?" size="10" /></td>\
<td><input id="useradd_password" value="password" size="10" /></td>\
<td><input id="useradd_email" value="email@domain.com" size="15" /></td>\
<td><input id="useradd_alias" value="alias" size="15" /></td>\
<td>-</td>\
- <td><input type="submit" class="submit adduser" value="'+_pk_translate('General_Save_js')+'" /></td>\
- <td><span class="cancel">'+sprintf(_pk_translate('General_OrCancel_js'),"","")+'</span></td>\
+ <td><input type="submit" class="submit adduser" value="' + _pk_translate('General_Save_js') + '" /></td>\
+ <td><span class="cancel">' + sprintf(_pk_translate('General_OrCancel_js'), "", "") + '</span></td>\
</tr>')
- .appendTo('#users')
- ;
- $('#'+newRowId).keypress( submitOnEnter );
- $('.adduser').click( function(){ sendAddUserAJAX($('tr#'+newRowId)); } );
- $('.cancel').click(function() { piwikHelper.hideAjaxError(); $(this).parents('tr').remove(); $('.addrow').toggle(); });
- });
+ .appendTo('#users')
+ ;
+ $('#' + newRowId).keypress(submitOnEnter);
+ $('.adduser').click(function () { sendAddUserAJAX($('tr#' + newRowId)); });
+ $('.cancel').click(function () {
+ piwikHelper.hideAjaxError();
+ $(this).parents('tr').remove();
+ $('.addrow').toggle();
+ });
+ });
+
+ $('.updateAccess')
+ .click(bindUpdateAccess);
- $('.updateAccess')
- .click( bindUpdateAccess );
-
- // when a site is selected, reload the page w/o showing the ajax loading element
- $('#usersManagerSiteSelect').bind('piwik:siteSelected', function(e, site) {
- if (site.id != piwik.idSite)
- {
- switchSite(
- site.id,
- site.name,
- false /* do not show main ajax loading animation */,
- true /* do not go to all websites dash */
- );
- }
- });
+ // when a site is selected, reload the page w/o showing the ajax loading element
+ $('#usersManagerSiteSelect').bind('piwik:siteSelected', function (e, site) {
+ if (site.id != piwik.idSite) {
+ switchSite(
+ site.id,
+ site.name,
+ false /* do not show main ajax loading animation */,
+ true /* do not go to all websites dash */
+ );
+ }
+ });
});
diff --git a/plugins/UsersManager/templates/UsersManager.tpl b/plugins/UsersManager/templates/UsersManager.tpl
index ae2c7bd814..c73e3bc182 100644
--- a/plugins/UsersManager/templates/UsersManager.tpl
+++ b/plugins/UsersManager/templates/UsersManager.tpl
@@ -2,149 +2,154 @@
{loadJavascriptTranslations plugins='UsersManager'}
{literal}
-<style type="text/css">
-.dialog {
- display: none;
- padding:20px 10px;
- color:#7A0101;
- cursor:wait;
- font-size:1.2em;
- font-weight:bold;
- text-align:center;
-}
-.editable:hover, .addrow:hover, .updateAccess:hover, .accessGranted:hover, .adduser:hover, .edituser:hover, .deleteuser:hover, .updateuser:hover, .cancel:hover{
- cursor: pointer;
-}
-.addrow {
- padding:1em;
- font-weight:bold;
-}
-.addrow a {
- text-decoration: none;
-}
-.addrow img {
- vertical-align: middle;
-}
-</style>
+ <style type="text/css">
+ .dialog {
+ display: none;
+ padding: 20px 10px;
+ color: #7A0101;
+ cursor: wait;
+ font-size: 1.2em;
+ font-weight: bold;
+ text-align: center;
+ }
+
+ .editable:hover, .addrow:hover, .updateAccess:hover, .accessGranted:hover, .adduser:hover, .edituser:hover, .deleteuser:hover, .updateuser:hover, .cancel:hover {
+ cursor: pointer;
+ }
+
+ .addrow {
+ padding: 1em;
+ font-weight: bold;
+ }
+
+ .addrow a {
+ text-decoration: none;
+ }
+
+ .addrow img {
+ vertical-align: middle;
+ }
+ </style>
{/literal}
<h2>{'UsersManager_ManageAccess'|translate}</h2>
<div id="sites">
- <section class="sites_selector_container">
- <p>{'UsersManager_MainDescription'|translate}</p>
- <div style="display:inline-block;margin-top:5px;">{'UsersManager_Sites'|translate}: </div>
-
- {capture name=applyAllSitesText assign=applyAllSitesText}
- <strong>{'UsersManager_ApplyToAllWebsites'|translate}</strong>
- {/capture}
- {include file="CoreHome/templates/sites_selection.tpl"
- siteName=$defaultReportSiteName idSite=$idSiteSelected allSitesItemText=$applyAllSitesText
- allWebsitesLinkLocation=top siteSelectorId="usersManagerSiteSelect" switchSiteOnSelect=false}
- </section>
+ <section class="sites_selector_container">
+ <p>{'UsersManager_MainDescription'|translate}</p>
+
+ <div style="display:inline-block;margin-top:5px;">{'UsersManager_Sites'|translate}:</div>
+
+ {capture name=applyAllSitesText assign=applyAllSitesText}
+ <strong>{'UsersManager_ApplyToAllWebsites'|translate}</strong>
+ {/capture}
+ {include file="CoreHome/templates/sites_selection.tpl"
+ siteName=$defaultReportSiteName idSite=$idSiteSelected allSitesItemText=$applyAllSitesText
+ allWebsitesLinkLocation=top siteSelectorId="usersManagerSiteSelect" switchSiteOnSelect=false}
+ </section>
</div>
{ajaxErrorDiv}
{ajaxLoadingDiv}
<div class="entityContainer" style='width:600px'>
- {if $anonymousHasViewAccess}
- <div class="ajaxSuccess" style="display:inline-block">
- {'UsersManager_AnonymousUserHasViewAccess'|translate:"'anonymous'":"'view'"}<br/>
- {'UsersManager_AnonymousUserHasViewAccess2'|translate}
- </div>
- {/if}
- <table class="entityTable dataTable" id="access" style="display:inline-table;width:500px;">
- <thead>
- <tr>
- <th class='first'>{'UsersManager_User'|translate}</th>
- <th>{'UsersManager_Alias'|translate}</th>
- <th>{'UsersManager_PrivNone'|translate}</th>
- <th>{'UsersManager_PrivView'|translate}</th>
- <th>{'UsersManager_PrivAdmin'|translate}</th>
- </tr>
- </thead>
-
- <tbody>
- {assign var=accesValid value="<img src='plugins/UsersManager/images/ok.png' class='accessGranted' />"}
- {assign var=accesInvalid value="<img src='plugins/UsersManager/images/no-access.png' class='updateAccess' />"}
- {foreach from=$usersAccessByWebsite key=login item=access}
- <tr>
- <td id='login'>{$login}</td>
- <td>{$usersAliasByLogin[$login]}</td>
- <td id='noaccess'>{if $access=='noaccess' and $idSiteSelected!='all'}{$accesValid}{else}{$accesInvalid}{/if}&nbsp;</td>
- <td id='view'>{if $access=='view' and $idSiteSelected!='all'}{$accesValid}{else}{$accesInvalid}{/if}&nbsp;</td>
- <td id='admin'>
- {if $login=='anonymous'}
- N/A
- {else}
- {if $access=='admin' and $idSiteSelected!='all'}{$accesValid}{else}{$accesInvalid}{/if}&nbsp;
- {/if}
- </td>
- </tr>
- {/foreach}
- </tbody>
- </table>
- <div id="accessUpdated" class="ajaxSuccess" style="display:none;vertical-align:top;">{'General_Done'|translate}!</div>
+ {if $anonymousHasViewAccess}
+ <div class="ajaxSuccess" style="display:inline-block">
+ {'UsersManager_AnonymousUserHasViewAccess'|translate:"'anonymous'":"'view'"}<br/>
+ {'UsersManager_AnonymousUserHasViewAccess2'|translate}
+ </div>
+ {/if}
+ <table class="entityTable dataTable" id="access" style="display:inline-table;width:500px;">
+ <thead>
+ <tr>
+ <th class='first'>{'UsersManager_User'|translate}</th>
+ <th>{'UsersManager_Alias'|translate}</th>
+ <th>{'UsersManager_PrivNone'|translate}</th>
+ <th>{'UsersManager_PrivView'|translate}</th>
+ <th>{'UsersManager_PrivAdmin'|translate}</th>
+ </tr>
+ </thead>
+
+ <tbody>
+ {assign var=accesValid value="<img src='plugins/UsersManager/images/ok.png' class='accessGranted' />"}
+ {assign var=accesInvalid value="<img src='plugins/UsersManager/images/no-access.png' class='updateAccess' />"}
+ {foreach from=$usersAccessByWebsite key=login item=access}
+ <tr>
+ <td id='login'>{$login}</td>
+ <td>{$usersAliasByLogin[$login]}</td>
+ <td id='noaccess'>{if $access=='noaccess' and $idSiteSelected!='all'}{$accesValid}{else}{$accesInvalid}{/if}&nbsp;</td>
+ <td id='view'>{if $access=='view' and $idSiteSelected!='all'}{$accesValid}{else}{$accesInvalid}{/if}&nbsp;</td>
+ <td id='admin'>
+ {if $login=='anonymous'}
+ N/A
+ {else}
+ {if $access=='admin' and $idSiteSelected!='all'}{$accesValid}{else}{$accesInvalid}{/if}&nbsp;
+ {/if}
+ </td>
+ </tr>
+ {/foreach}
+ </tbody>
+ </table>
+ <div id="accessUpdated" class="ajaxSuccess" style="display:none;vertical-align:top;">{'General_Done'|translate}!</div>
</div>
<div class="ui-confirm" id="confirm">
- <h2>{'UsersManager_ChangeAllConfirm'|translate:"<span id='login'></span>"}</h2>
- <input role="yes" type="button" value="{'General_Yes'|translate}" />
- <input role="no" type="button" value="{'General_No'|translate}" />
-</div>
+ <h2>{'UsersManager_ChangeAllConfirm'|translate:"<span id='login'></span>"}</h2>
+ <input role="yes" type="button" value="{'General_Yes'|translate}"/>
+ <input role="no" type="button" value="{'General_No'|translate}"/>
+</div>
{if $userIsSuperUser}
<div class="ui-confirm" id="confirmUserRemove">
<h2></h2>
- <input role="yes" type="button" value="{'General_Yes'|translate}" />
- <input role="no" type="button" value="{'General_No'|translate}" />
- </div>
+ <input role="yes" type="button" value="{'General_Yes'|translate}"/>
+ <input role="no" type="button" value="{'General_No'|translate}"/>
+ </div>
<div class="ui-confirm" id="confirmPasswordChange">
<h2>{'UsersManager_ChangePasswordConfirm'|translate}</h2>
- <input role="yes" type="button" value="{'General_Yes'|translate}" />
- <input role="no" type="button" value="{'General_No'|translate}" />
- </div>
-
- <br />
- <h2>{'UsersManager_UsersManagement'|translate}</h2>
- <p>{'UsersManager_UsersManagementMainDescription'|translate}
- {'UsersManager_ThereAreCurrentlyNRegisteredUsers'|translate:"<b>$usersCount</b>"}</p>
-
- {ajaxErrorDiv id=ajaxErrorUsersManagement}
- {ajaxLoadingDiv id=ajaxLoadingUsersManagement}
+ <input role="yes" type="button" value="{'General_Yes'|translate}"/>
+ <input role="no" type="button" value="{'General_No'|translate}"/>
+ </div>
+ <br/>
+ <h2>{'UsersManager_UsersManagement'|translate}</h2>
+ <p>{'UsersManager_UsersManagementMainDescription'|translate}
+ {'UsersManager_ThereAreCurrentlyNRegisteredUsers'|translate:"<b>$usersCount</b>"}</p>
+ {ajaxErrorDiv id=ajaxErrorUsersManagement}
+ {ajaxLoadingDiv id=ajaxLoadingUsersManagement}
+ <div class="entityContainer" style='margin-bottom:50px'>
+ <table class="entityTable dataTable" id="users">
+ <thead>
+ <tr>
+ <th>{'General_Username'|translate}</th>
+ <th>{'UsersManager_Password'|translate}</th>
+ <th>{'UsersManager_Email'|translate}</th>
+ <th>{'UsersManager_Alias'|translate}</th>
+ <th>token_auth</th>
+ <th>{'General_Edit'|translate}</th>
+ <th>{'General_Delete'|translate}</th>
+ </tr>
+ </thead>
- <div class="entityContainer" style='margin-bottom:50px'>
- <table class="entityTable dataTable" id="users">
- <thead>
- <tr>
- <th>{'General_Username'|translate}</th>
- <th>{'UsersManager_Password'|translate}</th>
- <th>{'UsersManager_Email'|translate}</th>
- <th>{'UsersManager_Alias'|translate}</th>
- <th>token_auth</th>
- <th>{'General_Edit'|translate}</th>
- <th>{'General_Delete'|translate}</th>
- </tr>
- </thead>
-
- <tbody>
- {foreach from=$users item=user key=i}
- {if $user.login != 'anonymous'}
- <tr class="editable" id="row{$i}">
- <td id="userLogin" class="editable">{$user.login}</td>
- <td id="password" class="editable">-</td>
- <td id="email" class="editable">{$user.email}</td>
- <td id="alias" class="editable">{$user.alias}</td>
- <td id="token_auth">{$user.token_auth}</td>
- <td><span class="edituser link_but" id="row{$i}"><img title="{'General_Edit'|translate}" src='themes/default/images/ico_edit.png' /> {'General_Edit'|translate} </span></td>
- <td><span class="deleteuser link_but" id="row{$i}"><img title="{'General_Delete'|translate}" src='themes/default/images/ico_delete.png' /> {'General_Delete'|translate} </span></td>
- </tr>
- {/if}
- {/foreach}
- </tbody>
- </table>
- <div class="addrow"><img src='plugins/UsersManager/images/add.png' /> {'UsersManager_AddUser'|translate}</div>
- </div>
+ <tbody>
+ {foreach from=$users item=user key=i}
+ {if $user.login != 'anonymous'}
+ <tr class="editable" id="row{$i}">
+ <td id="userLogin" class="editable">{$user.login}</td>
+ <td id="password" class="editable">-</td>
+ <td id="email" class="editable">{$user.email}</td>
+ <td id="alias" class="editable">{$user.alias}</td>
+ <td id="token_auth">{$user.token_auth}</td>
+ <td><span class="edituser link_but" id="row{$i}"><img title="{'General_Edit'|translate}"
+ src='themes/default/images/ico_edit.png'/> {'General_Edit'|translate} </span></td>
+ <td><span class="deleteuser link_but" id="row{$i}"><img title="{'General_Delete'|translate}"
+ src='themes/default/images/ico_delete.png'/> {'General_Delete'|translate} </span>
+ </td>
+ </tr>
+ {/if}
+ {/foreach}
+ </tbody>
+ </table>
+ <div class="addrow"><img src='plugins/UsersManager/images/add.png'/> {'UsersManager_AddUser'|translate}</div>
+ </div>
{/if}
{include file="CoreAdminHome/templates/footer.tpl"}
diff --git a/plugins/UsersManager/templates/userSettings.js b/plugins/UsersManager/templates/userSettings.js
index abb00cc68b..0bfdf3d8bb 100644
--- a/plugins/UsersManager/templates/userSettings.js
+++ b/plugins/UsersManager/templates/userSettings.js
@@ -5,38 +5,35 @@
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
-function sendUserSettingsAJAX()
-{
- var params;
- var defaultDate = $('input[name=defaultDate]:checked').val();
- if (defaultDate == 'today' || defaultDate == 'yesterday') {
- params = 'period=day&date='+defaultDate;
- } else if(defaultDate.indexOf('last') >= 0
- || defaultDate.indexOf('previous') >= 0) {
- params = 'period=range&date='+defaultDate;
- } else {
- params = 'date=today&period='+defaultDate;
- }
+function sendUserSettingsAJAX() {
+ var params;
+ var defaultDate = $('input[name=defaultDate]:checked').val();
+ if (defaultDate == 'today' || defaultDate == 'yesterday') {
+ params = 'period=day&date=' + defaultDate;
+ } else if (defaultDate.indexOf('last') >= 0
+ || defaultDate.indexOf('previous') >= 0) {
+ params = 'period=range&date=' + defaultDate;
+ } else {
+ params = 'date=today&period=' + defaultDate;
+ }
- var alias = $('#alias').val();
- var email = $('#email').val();
- var password = $('#password').val();
- var passwordBis = $('#passwordBis').val();
- var defaultReport = $('input[name=defaultReport]:checked').val();
- if (defaultReport == 1) {
- defaultReport = $('#defaultReportSiteSelector .custom_select_main_link').attr('siteid');
- }
- var postParams = {};
+ var alias = $('#alias').val();
+ var email = $('#email').val();
+ var password = $('#password').val();
+ var passwordBis = $('#passwordBis').val();
+ var defaultReport = $('input[name=defaultReport]:checked').val();
+ if (defaultReport == 1) {
+ defaultReport = $('#defaultReportSiteSelector .custom_select_main_link').attr('siteid');
+ }
+ var postParams = {};
postParams.alias = alias;
postParams.email = email;
- if (password)
- {
+ if (password) {
postParams.password = password;
- }
- if (passwordBis)
- {
+ }
+ if (passwordBis) {
postParams.passwordBis = passwordBis;
- }
+ }
postParams.defaultReport = defaultReport;
postParams.defaultDate = defaultDate;
@@ -52,13 +49,12 @@ function sendUserSettingsAJAX()
ajaxHandler.setErrorElement('#ajaxErrorUserSettings');
ajaxHandler.send(true);
}
-function sendAnonymousUserSettingsAJAX()
-{
- var anonymousDefaultReport = $('input[name=anonymousDefaultReport]:checked').val();
- if (anonymousDefaultReport == 1) {
- anonymousDefaultReport = $('#anonymousDefaultReportWebsite option:selected').val();
- }
- var anonymousDefaultDate = $('input[name=anonymousDefaultDate]:checked').val();
+function sendAnonymousUserSettingsAJAX() {
+ var anonymousDefaultReport = $('input[name=anonymousDefaultReport]:checked').val();
+ if (anonymousDefaultReport == 1) {
+ anonymousDefaultReport = $('#anonymousDefaultReportWebsite option:selected').val();
+ }
+ var anonymousDefaultDate = $('input[name=anonymousDefaultDate]:checked').val();
var ajaxHandler = new ajaxHelper();
ajaxHandler.addParams({
@@ -76,22 +72,23 @@ function sendAnonymousUserSettingsAJAX()
ajaxHandler.send(true);
}
-$(document).ready( function() {
- $('#userSettingsSubmit').click( function() {
- if($('#password').length > 0 && $('#password').val() != '') {
- piwikHelper.modalConfirm( '#confirmPasswordChange', {yes: sendUserSettingsAJAX});
- } else {
+$(document).ready(function () {
+ $('#userSettingsSubmit').click(function () {
+ if ($('#password').length > 0 && $('#password').val() != '') {
+ piwikHelper.modalConfirm('#confirmPasswordChange', {yes: sendUserSettingsAJAX});
+ } else {
sendUserSettingsAJAX();
- }
-
- });
- $('#userSettingsTable input').keypress( function(e) {
- var key=e.keyCode || e.which;
- if (key==13) {
- $('#userSettingsSubmit').click();
- }});
-
- $('#anonymousUserSettingsSubmit').click( function() {
- sendAnonymousUserSettingsAJAX();
- });
+ }
+
+ });
+ $('#userSettingsTable input').keypress(function (e) {
+ var key = e.keyCode || e.which;
+ if (key == 13) {
+ $('#userSettingsSubmit').click();
+ }
+ });
+
+ $('#anonymousUserSettingsSubmit').click(function () {
+ sendAnonymousUserSettingsAJAX();
+ });
});
diff --git a/plugins/UsersManager/templates/userSettings.tpl b/plugins/UsersManager/templates/userSettings.tpl
index 72449625dd..3219d1f561 100644
--- a/plugins/UsersManager/templates/userSettings.tpl
+++ b/plugins/UsersManager/templates/userSettings.tpl
@@ -2,138 +2,149 @@
{loadJavascriptTranslations plugins='UsersManager'}
<h2>{'UsersManager_MenuUserSettings'|translate}</h2>
-<br />
+<br/>
<div class="ui-confirm" id="confirmPasswordChange">
<h2>{'UsersManager_ChangePasswordConfirm'|translate}</h2>
- <input role="yes" type="button" value="{'General_Yes'|translate}" />
- <input role="no" type="button" value="{'General_No'|translate}" />
-</div>
+ <input role="yes" type="button" value="{'General_Yes'|translate}"/>
+ <input role="no" type="button" value="{'General_No'|translate}"/>
+</div>
<table id='userSettingsTable' class="adminTable" style='width:1000px'>
-<tr>
- <td><label for="username">{'General_Username'|translate} </label></td>
- <td>
- <input size="25" value="{$userLogin}" id="username" disabled="disabled" />
- <span class='form-description'>{'UsersManager_YourUsernameCannotBeChanged'|translate}</span>
- </td>
-</tr>
+ <tr>
+ <td><label for="username">{'General_Username'|translate} </label></td>
+ <td>
+ <input size="25" value="{$userLogin}" id="username" disabled="disabled"/>
+ <span class='form-description'>{'UsersManager_YourUsernameCannotBeChanged'|translate}</span>
+ </td>
+ </tr>
-<tr>
- <td><label for="alias">{'UsersManager_Alias'|translate} </label></td>
- <td><input size="25" value="{$userAlias}" id="alias"{if $isSuperUser} disabled="disabled"{/if} />
- {if $isSuperUser}
- <span class='form-description'>
+ <tr>
+ <td><label for="alias">{'UsersManager_Alias'|translate} </label></td>
+ <td><input size="25" value="{$userAlias}" id="alias"{if $isSuperUser} disabled="disabled"{/if} />
+ {if $isSuperUser}
+ <span class='form-description'>
{'UsersManager_TheSuperUserAliasCannotBeChanged'|translate}
</span>
- {/if}
- </td>
-</tr>
-<tr>
- <td><label for="email">{'UsersManager_Email'|translate} </label></td>
- <td><input size="25" value="{$userEmail}" id="email" /></td>
-</tr>
-<tr>
- <td>{'UsersManager_ReportToLoadByDefault'|translate}</td>
- <td>
- <fieldset>
- <label><input type="radio" value="MultiSites" name="defaultReport"{if $defaultReport=='MultiSites'} checked="checked"{/if} /> {'General_AllWebsitesDashboard'|translate}</label><br />
- <label style="padding-right:12px;"><input type="radio" value="1" name="defaultReport"{if $defaultReport!='MultiSites'} checked="checked"{/if} /> {'General_DashboardForASpecificWebsite'|translate}</label>
- {if $defaultReport=='MultiSites'}{assign var=defaultReportIdSite value=1}{else}{assign var=defaultReportIdSite value=$defaultReport}{/if}
- {include file="CoreHome/templates/sites_selection.tpl"
- siteName=$defaultReportSiteName idSite=$defaultReportIdSite switchSiteOnSelect=false showAllSitesItem=false
- showSelectedSite=false siteSelectorId='defaultReportSiteSelector'}
- </fieldset>
- </td>
-</tr>
-<tr>
- <td>{'UsersManager_ReportDateToLoadByDefault'|translate}</td>
- <td>
- <fieldset>
- {foreach from=$availableDefaultDates key=value item=description}
- <label><input type="radio"{if $defaultDate==$value} checked="checked"{/if} value="{$value}" name="defaultDate" /> {$description}</label><br />
- {/foreach}
- </fieldset>
- </td>
-</tr>
+ {/if}
+ </td>
+ </tr>
+ <tr>
+ <td><label for="email">{'UsersManager_Email'|translate} </label></td>
+ <td><input size="25" value="{$userEmail}" id="email"/></td>
+ </tr>
+ <tr>
+ <td>{'UsersManager_ReportToLoadByDefault'|translate}</td>
+ <td>
+ <fieldset>
+ <label><input type="radio" value="MultiSites"
+ name="defaultReport"{if $defaultReport=='MultiSites'} checked="checked"{/if} /> {'General_AllWebsitesDashboard'|translate}</label><br/>
+ <label style="padding-right:12px;"><input type="radio" value="1"
+ name="defaultReport"{if $defaultReport!='MultiSites'} checked="checked"{/if} /> {'General_DashboardForASpecificWebsite'|translate}
+ </label>
+ {if $defaultReport=='MultiSites'}{assign var=defaultReportIdSite value=1}{else}{assign var=defaultReportIdSite value=$defaultReport}{/if}
+ {include file="CoreHome/templates/sites_selection.tpl"
+ siteName=$defaultReportSiteName idSite=$defaultReportIdSite switchSiteOnSelect=false showAllSitesItem=false
+ showSelectedSite=false siteSelectorId='defaultReportSiteSelector'}
+ </fieldset>
+ </td>
+ </tr>
+ <tr>
+ <td>{'UsersManager_ReportDateToLoadByDefault'|translate}</td>
+ <td>
+ <fieldset>
+ {foreach from=$availableDefaultDates key=value item=description}
+ <label><input type="radio"{if $defaultDate==$value} checked="checked"{/if} value="{$value}" name="defaultDate"/> {$description}</label>
+ <br/>
+ {/foreach}
+ </fieldset>
+ </td>
+ </tr>
-{if isset($isValidHost) && $isValidHost}
-<tr>
- <td><label for="email">{'UsersManager_ChangePassword'|translate} </label></td>
- <td><input size="25" value="" autocomplete="off" id="password" type="password" />
- <span class='form-description'>{'UsersManager_IfYouWouldLikeToChangeThePasswordTypeANewOne'|translate}</span>
- <br /><br /><input size="25" value="" autocomplete="off" id="passwordBis" type="password" />
- <span class='form-description'> {'UsersManager_TypeYourPasswordAgain'|translate}</span>
- </td>
-</tr>
-{/if}
+ {if isset($isValidHost) && $isValidHost}
+ <tr>
+ <td><label for="email">{'UsersManager_ChangePassword'|translate} </label></td>
+ <td><input size="25" value="" autocomplete="off" id="password" type="password"/>
+ <span class='form-description'>{'UsersManager_IfYouWouldLikeToChangeThePasswordTypeANewOne'|translate}</span>
+ <br/><br/><input size="25" value="" autocomplete="off" id="passwordBis" type="password"/>
+ <span class='form-description'> {'UsersManager_TypeYourPasswordAgain'|translate}</span>
+ </td>
+ </tr>
+ {/if}
</table>
{if !isset($isValidHost) || !$isValidHost}
-<div class="ajaxSuccess">
- {'UsersManager_InjectedHostCannotChangePwd'|translate:$invalidHost}&nbsp;{if !$isSuperUser}{'UsersManager_EmailYourAdministrator'|translate:$invalidHostMailLinkStart:'</a>'}{/if}
-</div>
-<br/>
+ <div class="ajaxSuccess">
+ {'UsersManager_InjectedHostCannotChangePwd'|translate:$invalidHost}
+ &nbsp;{if !$isSuperUser}{'UsersManager_EmailYourAdministrator'|translate:$invalidHostMailLinkStart:'</a>'}{/if}
+ </div>
+ <br/>
{/if}
{ajaxErrorDiv id=ajaxErrorUserSettings}
{ajaxLoadingDiv id=ajaxLoadingUserSettings}
-<input type="submit" value="{'General_Save'|translate}" id="userSettingsSubmit" class="submit" />
+<input type="submit" value="{'General_Save'|translate}" id="userSettingsSubmit" class="submit"/>
<br/><br/>
<a name='excludeCookie'></a><h2>{'UsersManager_ExcludeVisitsViaCookie'|translate}</h2>
<p>{if $ignoreCookieSet}{'UsersManager_YourVisitsAreIgnoredOnDomain'|translate:"<strong>":$piwikHost:"</strong>"}
-{else}{'UsersManager_YourVisitsAreNotIgnored'|translate:"<strong>":"</strong>"}{/if}</p>
+ {else}{'UsersManager_YourVisitsAreNotIgnored'|translate:"<strong>":"</strong>"}{/if}</p>
<span style='margin-left:20px'>
<a href='{url token_auth=$token_auth action=setIgnoreCookie}#excludeCookie'>&rsaquo; {if $ignoreCookieSet}{'UsersManager_ClickHereToDeleteTheCookie'|translate}
-{else}{'UsersManager_ClickHereToSetTheCookieOnDomain'|translate:$piwikHost}{/if}
-<br />
+ {else}{'UsersManager_ClickHereToSetTheCookieOnDomain'|translate:$piwikHost}{/if}
+ <br/>
</a></span>
<br/><br/>
{if $isSuperUser}
- <h2>{'UsersManager_MenuAnonymousUserSettings'|translate}</h2>
- {if count($anonymousSites) == 0}
- <h3 class='form-description'><b>{'UsersManager_NoteNoAnonymousUserAccessSettingsWontBeUsed2'|translate}</b></h3><br />
- {else}
- <br />
-
- {ajaxErrorDiv id=ajaxErrorAnonymousUserSettings}
- {ajaxLoadingDiv id=ajaxLoadingAnonymousUserSettings}
-
- <table id='anonymousUserSettingsTable' class="adminTable" style='width:850px;'>
- <tr>
- <td style='width:400px'>{'UsersManager_WhenUsersAreNotLoggedInAndVisitPiwikTheyShouldAccess'|translate}</td>
- <td>
- <fieldset>
- <label><input type="radio" value="Login" name="anonymousDefaultReport"{if $anonymousDefaultReport==$loginModule} checked="checked"{/if} /> {'UsersManager_TheLoginScreen'|translate}</label><br />
- <label><input {if empty($anonymousSites)}disabled="disabled" {/if}type="radio" value="MultiSites" name="anonymousDefaultReport"{if $anonymousDefaultReport=='MultiSites'} checked="checked"{/if} /> {'General_AllWebsitesDashboard'|translate}</label><br />
-
- <label><input {if empty($anonymousSites)}disabled="disabled" {/if}type="radio" value="1" name="anonymousDefaultReport"{if $anonymousDefaultReport>0} checked="checked"{/if} /> {'General_DashboardForASpecificWebsite'|translate}</label>
- {if !empty($anonymousSites)}
- <select id="anonymousDefaultReportWebsite">
- {foreach from=$anonymousSites item=info}
- <option value="{$info.idsite}" {if $anonymousDefaultReport==$info.idsite} selected="selected"{/if}>{$info.name}</option>
- {/foreach}
- </select>
- {/if}
- </fieldset>
- </td>
- </tr>
- <tr>
- <td>{'UsersManager_ForAnonymousUsersReportDateToLoadByDefault'|translate}</td>
- <td>
- <fieldset>
- {foreach from=$availableDefaultDates key=value item=description}
- <label><input type="radio" {if $anonymousDefaultDate==$value}checked="checked" {/if}value="{$value}" name="anonymousDefaultDate" /> {$description}</label><br />
- {/foreach}
- </fieldset>
- </td>
- </tr>
-
- </table>
-
- <input type="submit" value="{'General_Save'|translate}" id="anonymousUserSettingsSubmit" class="submit"/>
- {/if}
+ <h2>{'UsersManager_MenuAnonymousUserSettings'|translate}</h2>
+ {if count($anonymousSites) == 0}
+ <h3 class='form-description'><b>{'UsersManager_NoteNoAnonymousUserAccessSettingsWontBeUsed2'|translate}</b></h3>
+ <br/>
+ {else}
+ <br/>
+ {ajaxErrorDiv id=ajaxErrorAnonymousUserSettings}
+ {ajaxLoadingDiv id=ajaxLoadingAnonymousUserSettings}
+ <table id='anonymousUserSettingsTable' class="adminTable" style='width:850px;'>
+ <tr>
+ <td style='width:400px'>{'UsersManager_WhenUsersAreNotLoggedInAndVisitPiwikTheyShouldAccess'|translate}</td>
+ <td>
+ <fieldset>
+ <label><input type="radio" value="Login"
+ name="anonymousDefaultReport"{if $anonymousDefaultReport==$loginModule} checked="checked"{/if} /> {'UsersManager_TheLoginScreen'|translate}
+ </label><br/>
+ <label><input {if empty($anonymousSites)}disabled="disabled" {/if}type="radio" value="MultiSites"
+ name="anonymousDefaultReport"{if $anonymousDefaultReport=='MultiSites'} checked="checked"{/if} /> {'General_AllWebsitesDashboard'|translate}
+ </label><br/>
+
+ <label><input {if empty($anonymousSites)}disabled="disabled" {/if}type="radio" value="1"
+ name="anonymousDefaultReport"{if $anonymousDefaultReport>0} checked="checked"{/if} /> {'General_DashboardForASpecificWebsite'|translate}
+ </label>
+ {if !empty($anonymousSites)}
+ <select id="anonymousDefaultReportWebsite">
+ {foreach from=$anonymousSites item=info}
+ <option value="{$info.idsite}" {if $anonymousDefaultReport==$info.idsite} selected="selected"{/if}>{$info.name}</option>
+ {/foreach}
+ </select>
+ {/if}
+ </fieldset>
+ </td>
+ </tr>
+ <tr>
+ <td>{'UsersManager_ForAnonymousUsersReportDateToLoadByDefault'|translate}</td>
+ <td>
+ <fieldset>
+ {foreach from=$availableDefaultDates key=value item=description}
+ <label><input type="radio" {if $anonymousDefaultDate==$value}checked="checked" {/if}value="{$value}"
+ name="anonymousDefaultDate"/> {$description}</label>
+ <br/>
+ {/foreach}
+ </fieldset>
+ </td>
+ </tr>
+
+ </table>
+ <input type="submit" value="{'General_Save'|translate}" id="anonymousUserSettingsSubmit" class="submit"/>
+ {/if}
{/if}
diff --git a/plugins/VisitFrequency/API.php b/plugins/VisitFrequency/API.php
index fbefbc505e..d712b67831 100644
--- a/plugins/VisitFrequency/API.php
+++ b/plugins/VisitFrequency/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_VisitFrequency
*/
@@ -13,137 +13,127 @@
* VisitFrequency API lets you access a list of metrics related to Returning Visitors.
* @package Piwik_VisitFrequency
*/
-class Piwik_VisitFrequency_API
+class Piwik_VisitFrequency_API
{
- static private $instance = null;
- static public function getInstance()
- {
- if (self::$instance == null)
- {
- self::$instance = new self;
- }
- return self::$instance;
- }
-
- public function get( $idSite, $period, $date, $segment = false, $columns = false )
- {
- Piwik::checkUserHasViewAccess( $idSite );
- $archive = Piwik_Archive::build($idSite, $period, $date, $segment );
-
- // array values are comma separated
- $columns = Piwik::getArrayFromApiParameter($columns);
- $tempColumns = array();
-
- $bounceRateReturningRequested = $averageVisitDurationReturningRequested = $actionsPerVisitReturningRequested = false;
- if(!empty($columns))
- {
- // make sure base metrics are there for processed metrics
- if(false !== ($bounceRateReturningRequested = array_search('bounce_rate_returning', $columns)))
- {
- if (!in_array('nb_visits_returning', $columns)) $tempColumns[] = 'nb_visits_returning';
- if (!in_array('bounce_count_returning', $columns)) $tempColumns[] = 'bounce_count_returning';
- unset($columns[$bounceRateReturningRequested]);
- }
- if(false !== ($actionsPerVisitReturningRequested = array_search('nb_actions_per_visit_returning', $columns)))
- {
- if (!in_array('nb_actions_returning', $columns)) $tempColumns[] = 'nb_actions_returning';
- if (!in_array('nb_visits_returning', $columns)) $tempColumns[] = 'nb_visits_returning';
- unset($columns[$actionsPerVisitReturningRequested]);
- }
- if(false !== ($averageVisitDurationReturningRequested = array_search('avg_time_on_site_returning', $columns)))
- {
- if (!in_array('sum_visit_length_returning', $columns)) $tempColumns[] = 'sum_visit_length_returning';
- if (!in_array('nb_visits_returning', $columns)) $tempColumns[] = 'nb_visits_returning';
- unset($columns[$averageVisitDurationReturningRequested]);
- }
-
- $tempColumns = array_unique($tempColumns);
- $columns = array_merge($columns, $tempColumns);
- }
- else
- {
- $bounceRateReturningRequested = $averageVisitDurationReturningRequested = $actionsPerVisitReturningRequested = true;
- $columns = array(
- 'nb_visits_returning',
- 'nb_actions_returning',
- 'max_actions_returning',
- 'sum_visit_length_returning',
- 'bounce_count_returning',
- 'nb_visits_converted_returning',
- );
-
- if($period=='day')
- {
- $columns = array_merge(array('nb_uniq_visitors_returning'), $columns);
- }
- }
- $dataTable = $archive->getDataTableFromNumeric($columns);
-
- // Process ratio metrics
- if($bounceRateReturningRequested !== false)
- {
- $dataTable->filter('ColumnCallbackAddColumnPercentage', array('bounce_rate_returning', 'bounce_count_returning', 'nb_visits_returning', 0));
- }
- if($actionsPerVisitReturningRequested !== false)
- {
- $dataTable->filter('ColumnCallbackAddColumnQuotient', array('nb_actions_per_visit_returning', 'nb_actions_returning', 'nb_visits_returning', 1));
- }
- if($averageVisitDurationReturningRequested !== false)
- {
- $dataTable->filter('ColumnCallbackAddColumnQuotient', array('avg_time_on_site_returning', 'sum_visit_length_returning', 'nb_visits_returning', 0));
- }
-
- // remove temporary metrics that were used to compute processed metrics
- $dataTable->deleteColumns($tempColumns);
-
- return $dataTable;
- }
-
- protected function getNumeric( $idSite, $period, $date, $toFetch )
- {
- Piwik::checkUserHasViewAccess( $idSite );
- $archive = Piwik_Archive::build($idSite, $period, $date );
- $dataTable = $archive->getNumeric($toFetch);
- return $dataTable;
- }
-
- /**
- * @ignore
- */
- public function getVisitsReturning( $idSite, $period, $date )
- {
- return $this->getNumeric( $idSite, $period, $date, 'nb_visits_returning');
- }
-
- /**
- * @ignore
- */
- public function getActionsReturning( $idSite, $period, $date )
- {
- return $this->getNumeric( $idSite, $period, $date, 'nb_actions_returning');
- }
-
- /**
- * @ignore
- */
- public function getSumVisitsLengthReturning( $idSite, $period, $date )
- {
- return $this->getNumeric( $idSite, $period, $date, 'sum_visit_length_returning');
- }
-
- /**
- * @ignore
- */
- public function getBounceCountReturning( $idSite, $period, $date )
- {
- return $this->getNumeric( $idSite, $period, $date, 'bounce_count_returning');
- }
-
- /**
- * @ignore
- */
- public function getConvertedVisitsReturning( $idSite, $period, $date )
- {
- return $this->getNumeric( $idSite, $period, $date, 'nb_visits_converted_returning');
- }
+ static private $instance = null;
+
+ static public function getInstance()
+ {
+ if (self::$instance == null) {
+ self::$instance = new self;
+ }
+ return self::$instance;
+ }
+
+ public function get($idSite, $period, $date, $segment = false, $columns = false)
+ {
+ Piwik::checkUserHasViewAccess($idSite);
+ $archive = Piwik_Archive::build($idSite, $period, $date, $segment);
+
+ // array values are comma separated
+ $columns = Piwik::getArrayFromApiParameter($columns);
+ $tempColumns = array();
+
+ $bounceRateReturningRequested = $averageVisitDurationReturningRequested = $actionsPerVisitReturningRequested = false;
+ if (!empty($columns)) {
+ // make sure base metrics are there for processed metrics
+ if (false !== ($bounceRateReturningRequested = array_search('bounce_rate_returning', $columns))) {
+ if (!in_array('nb_visits_returning', $columns)) $tempColumns[] = 'nb_visits_returning';
+ if (!in_array('bounce_count_returning', $columns)) $tempColumns[] = 'bounce_count_returning';
+ unset($columns[$bounceRateReturningRequested]);
+ }
+ if (false !== ($actionsPerVisitReturningRequested = array_search('nb_actions_per_visit_returning', $columns))) {
+ if (!in_array('nb_actions_returning', $columns)) $tempColumns[] = 'nb_actions_returning';
+ if (!in_array('nb_visits_returning', $columns)) $tempColumns[] = 'nb_visits_returning';
+ unset($columns[$actionsPerVisitReturningRequested]);
+ }
+ if (false !== ($averageVisitDurationReturningRequested = array_search('avg_time_on_site_returning', $columns))) {
+ if (!in_array('sum_visit_length_returning', $columns)) $tempColumns[] = 'sum_visit_length_returning';
+ if (!in_array('nb_visits_returning', $columns)) $tempColumns[] = 'nb_visits_returning';
+ unset($columns[$averageVisitDurationReturningRequested]);
+ }
+
+ $tempColumns = array_unique($tempColumns);
+ $columns = array_merge($columns, $tempColumns);
+ } else {
+ $bounceRateReturningRequested = $averageVisitDurationReturningRequested = $actionsPerVisitReturningRequested = true;
+ $columns = array(
+ 'nb_visits_returning',
+ 'nb_actions_returning',
+ 'max_actions_returning',
+ 'sum_visit_length_returning',
+ 'bounce_count_returning',
+ 'nb_visits_converted_returning',
+ );
+
+ if ($period == 'day') {
+ $columns = array_merge(array('nb_uniq_visitors_returning'), $columns);
+ }
+ }
+ $dataTable = $archive->getDataTableFromNumeric($columns);
+
+ // Process ratio metrics
+ if ($bounceRateReturningRequested !== false) {
+ $dataTable->filter('ColumnCallbackAddColumnPercentage', array('bounce_rate_returning', 'bounce_count_returning', 'nb_visits_returning', 0));
+ }
+ if ($actionsPerVisitReturningRequested !== false) {
+ $dataTable->filter('ColumnCallbackAddColumnQuotient', array('nb_actions_per_visit_returning', 'nb_actions_returning', 'nb_visits_returning', 1));
+ }
+ if ($averageVisitDurationReturningRequested !== false) {
+ $dataTable->filter('ColumnCallbackAddColumnQuotient', array('avg_time_on_site_returning', 'sum_visit_length_returning', 'nb_visits_returning', 0));
+ }
+
+ // remove temporary metrics that were used to compute processed metrics
+ $dataTable->deleteColumns($tempColumns);
+
+ return $dataTable;
+ }
+
+ protected function getNumeric($idSite, $period, $date, $toFetch)
+ {
+ Piwik::checkUserHasViewAccess($idSite);
+ $archive = Piwik_Archive::build($idSite, $period, $date);
+ $dataTable = $archive->getNumeric($toFetch);
+ return $dataTable;
+ }
+
+ /**
+ * @ignore
+ */
+ public function getVisitsReturning($idSite, $period, $date)
+ {
+ return $this->getNumeric($idSite, $period, $date, 'nb_visits_returning');
+ }
+
+ /**
+ * @ignore
+ */
+ public function getActionsReturning($idSite, $period, $date)
+ {
+ return $this->getNumeric($idSite, $period, $date, 'nb_actions_returning');
+ }
+
+ /**
+ * @ignore
+ */
+ public function getSumVisitsLengthReturning($idSite, $period, $date)
+ {
+ return $this->getNumeric($idSite, $period, $date, 'sum_visit_length_returning');
+ }
+
+ /**
+ * @ignore
+ */
+ public function getBounceCountReturning($idSite, $period, $date)
+ {
+ return $this->getNumeric($idSite, $period, $date, 'bounce_count_returning');
+ }
+
+ /**
+ * @ignore
+ */
+ public function getConvertedVisitsReturning($idSite, $period, $date)
+ {
+ return $this->getNumeric($idSite, $period, $date, 'nb_visits_converted_returning');
+ }
}
diff --git a/plugins/VisitFrequency/Controller.php b/plugins/VisitFrequency/Controller.php
index ba7cf432d1..9709bc9774 100644
--- a/plugins/VisitFrequency/Controller.php
+++ b/plugins/VisitFrequency/Controller.php
@@ -15,91 +15,89 @@
*/
class Piwik_VisitFrequency_Controller extends Piwik_Controller
{
- function index()
- {
- $view = Piwik_View::factory('index');
- $view->graphEvolutionVisitFrequency = $this->getEvolutionGraph(true, array('nb_visits_returning') );
- $this->setSparklinesAndNumbers($view);
- echo $view->render();
- }
-
- public function getSparklines()
- {
- $view = Piwik_View::factory('sparklines');
- $this->setSparklinesAndNumbers($view);
- echo $view->render();
- }
-
- public function getEvolutionGraph( $fetch = false, array $columns = array())
- {
- if(empty($columns))
- {
- $columns = Piwik_Common::getRequestVar('columns');
- $columns = Piwik::getArrayFromApiParameter($columns);
- }
-
- $documentation = Piwik_Translate('VisitFrequency_ReturningVisitsDocumentation').'<br />'
- . Piwik_Translate('General_BrokenDownReportDocumentation').'<br />'
- . Piwik_Translate('VisitFrequency_ReturningVisitDocumentation');
-
- // Note: if you edit this array, maybe edit the code below as well
- $selectableColumns = array(
- // columns from VisitFrequency.get
- 'nb_visits_returning',
- 'nb_actions_returning',
- 'nb_actions_per_visit_returning',
- 'bounce_rate_returning',
- 'avg_time_on_site_returning',
- // columns from VisitsSummary.get
- 'nb_visits',
- 'nb_actions',
- 'nb_actions_per_visit',
- 'bounce_rate',
- 'avg_time_on_site'
- );
-
- $period = Piwik_Common::getRequestVar('period', false);
- if ($period == 'day')
- {
- // add number of unique (returning) visitors for period=day
- $selectableColumns = array_merge(
- array($selectableColumns[0]),
- array('nb_uniq_visitors_returning'),
- array_slice($selectableColumns, 1, -4),
- array('nb_uniq_visitors'),
- array_slice($selectableColumns, -4));
- }
-
- $view = $this->getLastUnitGraphAcrossPlugins($this->pluginName, __FUNCTION__, $columns,
- $selectableColumns, $documentation);
-
- return $this->renderView($view, $fetch);
- }
-
- protected function setSparklinesAndNumbers($view)
- {
- $view->urlSparklineNbVisitsReturning = $this->getUrlSparkline( 'getEvolutionGraph', array('columns' => array('nb_visits_returning')));
- $view->urlSparklineNbActionsReturning = $this->getUrlSparkline( 'getEvolutionGraph', array('columns' => array('nb_actions_returning')));
- $view->urlSparklineActionsPerVisitReturning = $this->getUrlSparkline( 'getEvolutionGraph', array('columns' => array('nb_actions_per_visit_returning')));
- $view->urlSparklineAvgVisitDurationReturning = $this->getUrlSparkline( 'getEvolutionGraph', array('columns' => array('avg_time_on_site_returning')));
- $view->urlSparklineBounceRateReturning = $this->getUrlSparkline( 'getEvolutionGraph', array('columns' => array('bounce_rate_returning')));
-
- $dataTableFrequency = $this->getSummary();
- $dataRow = $dataTableFrequency->getFirstRow();
- $nbVisitsReturning = $dataRow->getColumn('nb_visits_returning');
- $view->nbVisitsReturning = $nbVisitsReturning;
- $view->nbActionsReturning = $dataRow->getColumn('nb_actions_returning');
- $view->nbActionsPerVisitReturning = $dataRow->getColumn('nb_actions_per_visit_returning');
- $view->avgVisitDurationReturning = $dataRow->getColumn('avg_time_on_site_returning');
- $nbBouncedReturningVisits = $dataRow->getColumn('bounce_count_returning');
- $view->bounceRateReturning = Piwik::getPercentageSafe($nbBouncedReturningVisits, $nbVisitsReturning);
-
- }
+ function index()
+ {
+ $view = Piwik_View::factory('index');
+ $view->graphEvolutionVisitFrequency = $this->getEvolutionGraph(true, array('nb_visits_returning'));
+ $this->setSparklinesAndNumbers($view);
+ echo $view->render();
+ }
- protected function getSummary()
- {
- $requestString = "method=VisitFrequency.get&format=original";
- $request = new Piwik_API_Request($requestString);
- return $request->process();
- }
+ public function getSparklines()
+ {
+ $view = Piwik_View::factory('sparklines');
+ $this->setSparklinesAndNumbers($view);
+ echo $view->render();
+ }
+
+ public function getEvolutionGraph($fetch = false, array $columns = array())
+ {
+ if (empty($columns)) {
+ $columns = Piwik_Common::getRequestVar('columns');
+ $columns = Piwik::getArrayFromApiParameter($columns);
+ }
+
+ $documentation = Piwik_Translate('VisitFrequency_ReturningVisitsDocumentation') . '<br />'
+ . Piwik_Translate('General_BrokenDownReportDocumentation') . '<br />'
+ . Piwik_Translate('VisitFrequency_ReturningVisitDocumentation');
+
+ // Note: if you edit this array, maybe edit the code below as well
+ $selectableColumns = array(
+ // columns from VisitFrequency.get
+ 'nb_visits_returning',
+ 'nb_actions_returning',
+ 'nb_actions_per_visit_returning',
+ 'bounce_rate_returning',
+ 'avg_time_on_site_returning',
+ // columns from VisitsSummary.get
+ 'nb_visits',
+ 'nb_actions',
+ 'nb_actions_per_visit',
+ 'bounce_rate',
+ 'avg_time_on_site'
+ );
+
+ $period = Piwik_Common::getRequestVar('period', false);
+ if ($period == 'day') {
+ // add number of unique (returning) visitors for period=day
+ $selectableColumns = array_merge(
+ array($selectableColumns[0]),
+ array('nb_uniq_visitors_returning'),
+ array_slice($selectableColumns, 1, -4),
+ array('nb_uniq_visitors'),
+ array_slice($selectableColumns, -4));
+ }
+
+ $view = $this->getLastUnitGraphAcrossPlugins($this->pluginName, __FUNCTION__, $columns,
+ $selectableColumns, $documentation);
+
+ return $this->renderView($view, $fetch);
+ }
+
+ protected function setSparklinesAndNumbers($view)
+ {
+ $view->urlSparklineNbVisitsReturning = $this->getUrlSparkline('getEvolutionGraph', array('columns' => array('nb_visits_returning')));
+ $view->urlSparklineNbActionsReturning = $this->getUrlSparkline('getEvolutionGraph', array('columns' => array('nb_actions_returning')));
+ $view->urlSparklineActionsPerVisitReturning = $this->getUrlSparkline('getEvolutionGraph', array('columns' => array('nb_actions_per_visit_returning')));
+ $view->urlSparklineAvgVisitDurationReturning = $this->getUrlSparkline('getEvolutionGraph', array('columns' => array('avg_time_on_site_returning')));
+ $view->urlSparklineBounceRateReturning = $this->getUrlSparkline('getEvolutionGraph', array('columns' => array('bounce_rate_returning')));
+
+ $dataTableFrequency = $this->getSummary();
+ $dataRow = $dataTableFrequency->getFirstRow();
+ $nbVisitsReturning = $dataRow->getColumn('nb_visits_returning');
+ $view->nbVisitsReturning = $nbVisitsReturning;
+ $view->nbActionsReturning = $dataRow->getColumn('nb_actions_returning');
+ $view->nbActionsPerVisitReturning = $dataRow->getColumn('nb_actions_per_visit_returning');
+ $view->avgVisitDurationReturning = $dataRow->getColumn('avg_time_on_site_returning');
+ $nbBouncedReturningVisits = $dataRow->getColumn('bounce_count_returning');
+ $view->bounceRateReturning = Piwik::getPercentageSafe($nbBouncedReturningVisits, $nbVisitsReturning);
+
+ }
+
+ protected function getSummary()
+ {
+ $requestString = "method=VisitFrequency.get&format=original";
+ $request = new Piwik_API_Request($requestString);
+ return $request->process();
+ }
}
diff --git a/plugins/VisitFrequency/VisitFrequency.php b/plugins/VisitFrequency/VisitFrequency.php
index adfcaf03c9..4659feee91 100644
--- a/plugins/VisitFrequency/VisitFrequency.php
+++ b/plugins/VisitFrequency/VisitFrequency.php
@@ -15,137 +15,135 @@
*/
class Piwik_VisitFrequency extends Piwik_Plugin
{
- public function getInformation()
- {
- $info = array(
- 'description' => Piwik_Translate('VisitFrequency_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' => 'addMenu',
- 'API.getReportMetadata' => 'getReportMetadata',
- );
- 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('VisitFrequency_ColumnReturningVisits'),
- 'module' => 'VisitFrequency',
- 'action' => 'get',
- 'metrics' => array(
- 'nb_visits_returning' => Piwik_Translate('VisitFrequency_ColumnReturningVisits'),
- 'nb_actions_returning' => Piwik_Translate('VisitFrequency_ColumnActionsByReturningVisits'),
- 'avg_time_on_site_returning' => Piwik_Translate('VisitFrequency_ColumnAverageVisitDurationForReturningVisitors'),
- 'bounce_rate_returning' => Piwik_Translate('VisitFrequency_ColumnBounceRateForReturningVisits'),
- 'nb_actions_per_visit_returning' => Piwik_Translate('VisitFrequency_ColumnAvgActionsPerReturningVisit'),
- 'nb_uniq_visitors_returning' => Piwik_Translate('VisitFrequency_ColumnUniqueReturningVisitors'),
+ public function getInformation()
+ {
+ $info = array(
+ 'description' => Piwik_Translate('VisitFrequency_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' => 'addMenu',
+ 'API.getReportMetadata' => 'getReportMetadata',
+ );
+ 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('VisitFrequency_ColumnReturningVisits'),
+ 'module' => 'VisitFrequency',
+ 'action' => 'get',
+ 'metrics' => array(
+ 'nb_visits_returning' => Piwik_Translate('VisitFrequency_ColumnReturningVisits'),
+ 'nb_actions_returning' => Piwik_Translate('VisitFrequency_ColumnActionsByReturningVisits'),
+ 'avg_time_on_site_returning' => Piwik_Translate('VisitFrequency_ColumnAverageVisitDurationForReturningVisitors'),
+ 'bounce_rate_returning' => Piwik_Translate('VisitFrequency_ColumnBounceRateForReturningVisits'),
+ 'nb_actions_per_visit_returning' => Piwik_Translate('VisitFrequency_ColumnAvgActionsPerReturningVisit'),
+ 'nb_uniq_visitors_returning' => Piwik_Translate('VisitFrequency_ColumnUniqueReturningVisitors'),
// Not displayed
// 'nb_visits_converted_returning',
// 'sum_visit_length_returning',
// 'max_actions_returning',
// 'bounce_count_returning',
- ),
- 'processedMetrics' => false,
- 'order' => 40
- );
- }
-
- function addWidgets()
- {
- Piwik_AddWidget( 'General_Visitors', 'VisitFrequency_WidgetOverview', 'VisitFrequency', 'getSparklines');
- Piwik_AddWidget( 'General_Visitors', 'VisitFrequency_WidgetGraphReturning', 'VisitFrequency', 'getEvolutionGraph', array('columns' => array('nb_visits_returning')));
- }
-
- function addMenu()
- {
- Piwik_AddMenu('General_Visitors', 'VisitFrequency_SubmenuFrequency', array('module' => 'VisitFrequency', 'action' => 'index'));
- }
-
- /**
- * @param Piwik_Event_Notification $notification notification object
- * @return mixed
- */
- function archivePeriod( $notification )
- {
- $archiveProcessing = $notification->getNotificationObject();
-
- if(!$archiveProcessing->shouldProcessReportsForPlugin($this->getPluginName())) return;
-
- $numericToSum = array(
- 'nb_visits_returning',
- 'nb_actions_returning',
- 'sum_visit_length_returning',
- 'bounce_count_returning',
- 'nb_visits_converted_returning',
- );
- $archiveProcessing->archiveNumericValuesSum($numericToSum);
- $archiveProcessing->archiveNumericValuesMax('max_actions_returning');
- }
-
- /**
- * @param Piwik_Event_Notification $notification notification object
- * @return mixed
- */
- function archiveDay($notification)
- {
- /* @var $archiveProcessing Piwik_ArchiveProcessing */
- $archiveProcessing = $notification->getNotificationObject();
-
- if(!$archiveProcessing->shouldProcessReportsForPlugin($this->getPluginName())) return;
-
- $select = "count(distinct log_visit.idvisitor) as nb_uniq_visitors_returning,
+ ),
+ 'processedMetrics' => false,
+ 'order' => 40
+ );
+ }
+
+ function addWidgets()
+ {
+ Piwik_AddWidget('General_Visitors', 'VisitFrequency_WidgetOverview', 'VisitFrequency', 'getSparklines');
+ Piwik_AddWidget('General_Visitors', 'VisitFrequency_WidgetGraphReturning', 'VisitFrequency', 'getEvolutionGraph', array('columns' => array('nb_visits_returning')));
+ }
+
+ function addMenu()
+ {
+ Piwik_AddMenu('General_Visitors', 'VisitFrequency_SubmenuFrequency', array('module' => 'VisitFrequency', 'action' => 'index'));
+ }
+
+ /**
+ * @param Piwik_Event_Notification $notification notification object
+ * @return mixed
+ */
+ function archivePeriod($notification)
+ {
+ $archiveProcessing = $notification->getNotificationObject();
+
+ if (!$archiveProcessing->shouldProcessReportsForPlugin($this->getPluginName())) return;
+
+ $numericToSum = array(
+ 'nb_visits_returning',
+ 'nb_actions_returning',
+ 'sum_visit_length_returning',
+ 'bounce_count_returning',
+ 'nb_visits_converted_returning',
+ );
+ $archiveProcessing->archiveNumericValuesSum($numericToSum);
+ $archiveProcessing->archiveNumericValuesMax('max_actions_returning');
+ }
+
+ /**
+ * @param Piwik_Event_Notification $notification notification object
+ * @return mixed
+ */
+ function archiveDay($notification)
+ {
+ /* @var $archiveProcessing Piwik_ArchiveProcessing */
+ $archiveProcessing = $notification->getNotificationObject();
+
+ if (!$archiveProcessing->shouldProcessReportsForPlugin($this->getPluginName())) return;
+
+ $select = "count(distinct log_visit.idvisitor) as nb_uniq_visitors_returning,
count(*) as nb_visits_returning,
sum(log_visit.visit_total_actions) as nb_actions_returning,
max(log_visit.visit_total_actions) as max_actions_returning,
sum(log_visit.visit_total_time) as sum_visit_length_returning,
sum(case log_visit.visit_total_actions when 1 then 1 when 0 then 1 else 0 end) as bounce_count_returning,
sum(case log_visit.visit_goal_converted when 1 then 1 else 0 end) as nb_visits_converted_returning";
-
- $from = "log_visit";
-
- $where = "log_visit.visit_last_action_time >= ?
+
+ $from = "log_visit";
+
+ $where = "log_visit.visit_last_action_time >= ?
AND log_visit.visit_last_action_time <= ?
AND log_visit.idsite = ?
AND log_visit.visitor_returning >= 1";
-
- $bind = array($archiveProcessing->getStartDatetimeUTC(),
- $archiveProcessing->getEndDatetimeUTC(), $archiveProcessing->idsite);
-
- $query = $archiveProcessing->getSegment()->getSelectQuery($select, $from, $where, $bind);
-
- $row = $archiveProcessing->db->fetchRow($query['sql'], $query['bind']);
-
- if($row === false || $row === null)
- {
- $row['nb_visits_returning'] = 0;
- $row['nb_actions_returning'] = 0;
- $row['max_actions_returning'] = 0;
- $row['sum_visit_length_returning'] = 0;
- $row['bounce_count_returning'] = 0;
- $row['nb_visits_converted_returning'] = 0;
- }
-
- foreach($row as $name => $value)
- {
- $archiveProcessing->insertNumericRecord($name, $value);
- }
- }
+
+ $bind = array($archiveProcessing->getStartDatetimeUTC(),
+ $archiveProcessing->getEndDatetimeUTC(), $archiveProcessing->idsite);
+
+ $query = $archiveProcessing->getSegment()->getSelectQuery($select, $from, $where, $bind);
+
+ $row = $archiveProcessing->db->fetchRow($query['sql'], $query['bind']);
+
+ if ($row === false || $row === null) {
+ $row['nb_visits_returning'] = 0;
+ $row['nb_actions_returning'] = 0;
+ $row['max_actions_returning'] = 0;
+ $row['sum_visit_length_returning'] = 0;
+ $row['bounce_count_returning'] = 0;
+ $row['nb_visits_converted_returning'] = 0;
+ }
+
+ foreach ($row as $name => $value) {
+ $archiveProcessing->insertNumericRecord($name, $value);
+ }
+ }
}
diff --git a/plugins/VisitFrequency/templates/index.tpl b/plugins/VisitFrequency/templates/index.tpl
index 94bbbfbcc9..d44c578c09 100644
--- a/plugins/VisitFrequency/templates/index.tpl
+++ b/plugins/VisitFrequency/templates/index.tpl
@@ -3,8 +3,8 @@
<a name="evolutionGraph" graphId="VisitFrequencygetEvolutionGraph"></a>
<h2>{'VisitFrequency_ColumnReturningVisits'|translate}</h2>
{$graphEvolutionVisitFrequency}
-<br />
+<br/>
{include file="VisitFrequency/templates/sparklines.tpl"}
-
+
{postEvent name="template_footerVisitsFrequency"}
diff --git a/plugins/VisitFrequency/templates/sparklines.tpl b/plugins/VisitFrequency/templates/sparklines.tpl
index 250d924e56..e3a3ba9dc4 100644
--- a/plugins/VisitFrequency/templates/sparklines.tpl
+++ b/plugins/VisitFrequency/templates/sparklines.tpl
@@ -1,13 +1,12 @@
-
<div class="sparkline">{sparkline src=$urlSparklineNbVisitsReturning}
-{'VisitFrequency_ReturnVisits'|translate:"<strong>$nbVisitsReturning</strong>"}</div>
+ {'VisitFrequency_ReturnVisits'|translate:"<strong>$nbVisitsReturning</strong>"}</div>
<div class="sparkline">{sparkline src=$urlSparklineNbActionsReturning}
-{'VisitFrequency_ReturnActions'|translate:"<strong>$nbActionsReturning</strong>"}</div>
+ {'VisitFrequency_ReturnActions'|translate:"<strong>$nbActionsReturning</strong>"}</div>
<div class="sparkline">{sparkline src=$urlSparklineActionsPerVisitReturning}
- {'VisitFrequency_ReturnAvgActions'|translate:"<strong>$nbActionsPerVisitReturning</strong>"}</div>
+ {'VisitFrequency_ReturnAvgActions'|translate:"<strong>$nbActionsPerVisitReturning</strong>"}</div>
<div class="sparkline">{sparkline src=$urlSparklineAvgVisitDurationReturning}
- {assign var=avgVisitDurationReturning value=$avgVisitDurationReturning|sumtime}
- {'VisitFrequency_ReturnAverageVisitDuration'|translate:"<strong>$avgVisitDurationReturning</strong>"}</div>
+ {assign var=avgVisitDurationReturning value=$avgVisitDurationReturning|sumtime}
+ {'VisitFrequency_ReturnAverageVisitDuration'|translate:"<strong>$avgVisitDurationReturning</strong>"}</div>
<div class="sparkline">{sparkline src=$urlSparklineBounceRateReturning}
- {'VisitFrequency_ReturnBounceRate'|translate:"<strong>$bounceRateReturning%</strong>"} </div>
+ {'VisitFrequency_ReturnBounceRate'|translate:"<strong>$bounceRateReturning%</strong>"} </div>
{include file="CoreHome/templates/sparkline_footer.tpl"}
diff --git a/plugins/VisitTime/API.php b/plugins/VisitTime/API.php
index 04e38a3768..3da0646fb7 100644
--- a/plugins/VisitTime/API.php
+++ b/plugins/VisitTime/API.php
@@ -1,184 +1,175 @@
<?php
/**
* Piwik - Open source web analytics
- *
+ *
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
- *
+ *
* @category Piwik_Plugins
* @package Piwik_VisitTime
*/
/**
* VisitTime API lets you access reports by Hour (Server time), and by Hour Local Time of your visitors.
- *
+ *
* @package Piwik_VisitTime
*/
class Piwik_VisitTime_API
{
- static private $instance = null;
- static public function getInstance()
- {
- if (self::$instance == null)
- {
- self::$instance = new self;
- }
- return self::$instance;
- }
-
- protected function getDataTable($name, $idSite, $period, $date, $segment )
- {
- Piwik::checkUserHasViewAccess( $idSite );
- $archive = Piwik_Archive::build($idSite, $period, $date, $segment );
- $dataTable = $archive->getDataTable($name);
- $dataTable->filter('Sort', array('label', 'asc', true));
- $dataTable->queueFilter('ColumnCallbackReplace', array('label', 'Piwik_getTimeLabel'));
- $dataTable->queueFilter('ReplaceColumnNames');
- return $dataTable;
- }
-
- public function getVisitInformationPerLocalTime( $idSite, $period, $date, $segment = false )
- {
- return $this->getDataTable('VisitTime_localTime', $idSite, $period, $date, $segment );
- }
-
- public function getVisitInformationPerServerTime( $idSite, $period, $date, $segment = false, $hideFutureHoursWhenToday = false )
- {
- $table = $this->getDataTable('VisitTime_serverTime', $idSite, $period, $date, $segment );
- if($hideFutureHoursWhenToday)
- {
- $table = $this->removeHoursInFuture($table, $idSite, $period, $date);
- }
- return $table;
- }
-
- /**
- * Returns datatable describing the number of visits for each day of the week.
- *
- * @param string $idSite The site ID. Cannot refer to multiple sites.
- * @param string $period The period type: day, week, year, range...
- * @param string $date The start date of the period. Cannot refer to multiple dates.
- * @param string $segment The segment.
- * @return Piwik_DataTable
- */
- public function getByDayOfWeek( $idSite, $period, $date, $segment = false )
- {
- Piwik::checkUserHasViewAccess( $idSite );
-
- // disabled for multiple sites/dates
- if (Piwik_Archive::isMultipleSites($idSite))
- {
- throw new Exception("VisitTime.getByDayOfWeek does not support multiple sites.");
- }
-
- if (Piwik_Archive::isMultiplePeriod($date, $period))
- {
- throw new Exception("VisitTime.getByDayOfWeek does not support multiple dates.");
- }
-
- // metrics to query
- $metrics = Piwik_ArchiveProcessing::getCoreMetrics();
-
- // get metric data for every day within the supplied period
- $oSite = new Piwik_Site($idSite);
- $oPeriod = Piwik_Archive::makePeriodFromQueryParams($oSite, $period, $date);
- $dateRange = $oPeriod->getDateStart()->toString().','.$oPeriod->getDateEnd()->toString();
-
- $archive = Piwik_Archive::build($idSite, 'day', $dateRange, $segment);
- $dataTable = $archive->getDataTableFromNumeric($metrics)->mergeChildren();
-
- // if there's no data for this report, don't bother w/ anything else
- if ($dataTable->getRowsCount() == 0)
- {
- return $dataTable;
- }
-
- // group by the day of the week (see below for dayOfWeekFromDate function)
- $dataTable->filter('GroupBy', array('label', 'Piwik_VisitTime_dayOfWeekFromDate'));
-
- // create new datatable w/ empty rows, then add calculated datatable
- $rows = array();
- foreach (array(1,2,3,4,5,6,7) as $day)
- {
- $rows[] = array('label' => $day, 'nb_visits' => 0);
- }
-
- $result = new Piwik_DataTable();
- $result->addRowsFromSimpleArray($rows);
- $result->addDataTable($dataTable);
-
- // set day of week integer as metadata
- $result->filter('ColumnCallbackAddMetadata', array('label', 'day_of_week'));
-
- // translate labels
- $result->filter('ColumnCallbackReplace', array('label', 'Piwik_VisitTime_translateDayOfWeek'));
-
- // set datatable metadata for period start & finish
- $result->setMetadata('date_start', $oPeriod->getDateStart());
- $result->setMetadata('date_end', $oPeriod->getDateEnd());
-
- return $result;
- }
-
- protected function removeHoursInFuture($table, $idSite, $period, $date)
- {
- $site = new Piwik_Site($idSite);
-
- if( $period == 'day'
- && ($date == 'today'
- || $date == Piwik_Date::factory('now', $site->getTimezone())->toString()))
- {
- $currentHour = Piwik_Date::factory('now', $site->getTimezone())->toString('G');
- // If no data for today, this is an exception to the API output rule, as we normally return nothing:
- // we shall return all hours of the day, with nb_visits = 0
- if($table->getRowsCount() == 0)
- {
- for($hour = 0; $hour <= $currentHour; $hour++)
- {
- $table->addRowFromSimpleArray( array('label' => $hour, 'nb_visits' => 0));
- }
- return $table;
- }
-
- $idsToDelete = array();
- foreach($table->getRows() as $id => $row)
- {
- $hour = $row->getColumn('label');
- if($hour > $currentHour)
- {
- $idsToDelete[] = $id;
- }
- }
- $table->deleteRows($idsToDelete);
- }
- return $table;
- }
+ static private $instance = null;
+
+ static public function getInstance()
+ {
+ if (self::$instance == null) {
+ self::$instance = new self;
+ }
+ return self::$instance;
+ }
+
+ protected function getDataTable($name, $idSite, $period, $date, $segment)
+ {
+ Piwik::checkUserHasViewAccess($idSite);
+ $archive = Piwik_Archive::build($idSite, $period, $date, $segment);
+ $dataTable = $archive->getDataTable($name);
+ $dataTable->filter('Sort', array('label', 'asc', true));
+ $dataTable->queueFilter('ColumnCallbackReplace', array('label', 'Piwik_getTimeLabel'));
+ $dataTable->queueFilter('ReplaceColumnNames');
+ return $dataTable;
+ }
+
+ public function getVisitInformationPerLocalTime($idSite, $period, $date, $segment = false)
+ {
+ return $this->getDataTable('VisitTime_localTime', $idSite, $period, $date, $segment);
+ }
+
+ public function getVisitInformationPerServerTime($idSite, $period, $date, $segment = false, $hideFutureHoursWhenToday = false)
+ {
+ $table = $this->getDataTable('VisitTime_serverTime', $idSite, $period, $date, $segment);
+ if ($hideFutureHoursWhenToday) {
+ $table = $this->removeHoursInFuture($table, $idSite, $period, $date);
+ }
+ return $table;
+ }
+
+ /**
+ * Returns datatable describing the number of visits for each day of the week.
+ *
+ * @param string $idSite The site ID. Cannot refer to multiple sites.
+ * @param string $period The period type: day, week, year, range...
+ * @param string $date The start date of the period. Cannot refer to multiple dates.
+ * @param string $segment The segment.
+ * @return Piwik_DataTable
+ */
+ public function getByDayOfWeek($idSite, $period, $date, $segment = false)
+ {
+ Piwik::checkUserHasViewAccess($idSite);
+
+ // disabled for multiple sites/dates
+ if (Piwik_Archive::isMultipleSites($idSite)) {
+ throw new Exception("VisitTime.getByDayOfWeek does not support multiple sites.");
+ }
+
+ if (Piwik_Archive::isMultiplePeriod($date, $period)) {
+ throw new Exception("VisitTime.getByDayOfWeek does not support multiple dates.");
+ }
+
+ // metrics to query
+ $metrics = Piwik_ArchiveProcessing::getCoreMetrics();
+
+ // get metric data for every day within the supplied period
+ $oSite = new Piwik_Site($idSite);
+ $oPeriod = Piwik_Archive::makePeriodFromQueryParams($oSite, $period, $date);
+ $dateRange = $oPeriod->getDateStart()->toString() . ',' . $oPeriod->getDateEnd()->toString();
+
+ $archive = Piwik_Archive::build($idSite, 'day', $dateRange, $segment);
+ $dataTable = $archive->getDataTableFromNumeric($metrics)->mergeChildren();
+
+ // if there's no data for this report, don't bother w/ anything else
+ if ($dataTable->getRowsCount() == 0) {
+ return $dataTable;
+ }
+
+ // group by the day of the week (see below for dayOfWeekFromDate function)
+ $dataTable->filter('GroupBy', array('label', 'Piwik_VisitTime_dayOfWeekFromDate'));
+
+ // create new datatable w/ empty rows, then add calculated datatable
+ $rows = array();
+ foreach (array(1, 2, 3, 4, 5, 6, 7) as $day) {
+ $rows[] = array('label' => $day, 'nb_visits' => 0);
+ }
+
+ $result = new Piwik_DataTable();
+ $result->addRowsFromSimpleArray($rows);
+ $result->addDataTable($dataTable);
+
+ // set day of week integer as metadata
+ $result->filter('ColumnCallbackAddMetadata', array('label', 'day_of_week'));
+
+ // translate labels
+ $result->filter('ColumnCallbackReplace', array('label', 'Piwik_VisitTime_translateDayOfWeek'));
+
+ // set datatable metadata for period start & finish
+ $result->setMetadata('date_start', $oPeriod->getDateStart());
+ $result->setMetadata('date_end', $oPeriod->getDateEnd());
+
+ return $result;
+ }
+
+ protected function removeHoursInFuture($table, $idSite, $period, $date)
+ {
+ $site = new Piwik_Site($idSite);
+
+ if ($period == 'day'
+ && ($date == 'today'
+ || $date == Piwik_Date::factory('now', $site->getTimezone())->toString())
+ ) {
+ $currentHour = Piwik_Date::factory('now', $site->getTimezone())->toString('G');
+ // If no data for today, this is an exception to the API output rule, as we normally return nothing:
+ // we shall return all hours of the day, with nb_visits = 0
+ if ($table->getRowsCount() == 0) {
+ for ($hour = 0; $hour <= $currentHour; $hour++) {
+ $table->addRowFromSimpleArray(array('label' => $hour, 'nb_visits' => 0));
+ }
+ return $table;
+ }
+
+ $idsToDelete = array();
+ foreach ($table->getRows() as $id => $row) {
+ $hour = $row->getColumn('label');
+ if ($hour > $currentHour) {
+ $idsToDelete[] = $id;
+ }
+ }
+ $table->deleteRows($idsToDelete);
+ }
+ return $table;
+ }
}
function Piwik_getTimeLabel($label)
{
- return sprintf(Piwik_Translate('VisitTime_NHour'), $label);
+ return sprintf(Piwik_Translate('VisitTime_NHour'), $label);
}
/**
* Returns the day of the week for a date string, without creating a new
* Piwik_Date instance.
- *
+ *
* @param string $dateStr
* @return int The day of the week (1-7)
*/
-function Piwik_VisitTime_dayOfWeekFromDate( $dateStr )
+function Piwik_VisitTime_dayOfWeekFromDate($dateStr)
{
- return date('N', strtotime($dateStr));
+ return date('N', strtotime($dateStr));
}
/**
* Returns translated long name of a day of the week.
- *
+ *
* @param int $dayOfWeek 1-7, for Sunday-Saturday
* @return string
*/
-function Piwik_VisitTime_translateDayOfWeek( $dayOfWeek )
+function Piwik_VisitTime_translateDayOfWeek($dayOfWeek)
{
- return Piwik_Translate('General_LongDay_'.$dayOfWeek);
+ return Piwik_Translate('General_LongDay_' . $dayOfWeek);
}
diff --git a/plugins/VisitTime/Controller.php b/plugins/VisitTime/Controller.php
index d56cf58f2b..95e5d97f4d 100644
--- a/plugins/VisitTime/Controller.php
+++ b/plugins/VisitTime/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_VisitTime
*/
@@ -13,96 +13,91 @@
*
* @package Piwik_VisitTime
*/
-class Piwik_VisitTime_Controller extends Piwik_Controller
+class Piwik_VisitTime_Controller extends Piwik_Controller
{
- public function index()
- {
- $view = Piwik_View::factory('index');
- $view->dataTableVisitInformationPerLocalTime = $this->getVisitInformationPerLocalTime(true);
- $view->dataTableVisitInformationPerServerTime = $this->getVisitInformationPerServerTime(true);
- echo $view->render();
- }
-
- public function getVisitInformationPerServerTime( $fetch = false)
- {
- $view = $this->getGraph(__FUNCTION__, 'VisitTime.getVisitInformationPerServerTime',
- 'VisitTime_ColumnServerTime');
-
- $view->setCustomParameter('hideFutureHoursWhenToday', 1);
- $view->enableShowGoals();
-
- return $this->renderView($view, $fetch);
- }
-
- public function getVisitInformationPerLocalTime( $fetch = false)
- {
- $view = $this->getGraph(__FUNCTION__, 'VisitTime.getVisitInformationPerLocalTime',
- 'VisitTime_ColumnLocalTime');
-
- // add the visits by day of week as a related report, if the current period is not 'day'
- if (Piwik_Common::getRequestVar('period', 'day') != 'day')
- {
- $view->addRelatedReports(Piwik_Translate('VisitTime_LocalTime'), array(
- 'VisitTime.getByDayOfWeek' => Piwik_Translate('VisitTime_VisitsByDayOfWeek')
- ));
- }
-
- return $this->renderView($view, $fetch);
- }
-
- public function getByDayOfWeek( $fetch = false )
- {
- $view = $this->getGraph(
- __FUNCTION__, 'VisitTime.getByDayOfWeek', 'VisitTime_DayOfWeek', $limit = 7, $sort = false);
- $view->disableSort();
-
- if ($view instanceof Piwik_ViewDataTable_GenerateGraphHTML)
- {
- $view->showAllTicks();
- }
-
- // get query params
- $idsite = Piwik_Common::getRequestVar('idSite');
- $date = Piwik_Common::getRequestVar('date');
- $period = Piwik_Common::getRequestVar('period');
-
- // create a period instance
- $oSite = new Piwik_Site($idsite);
- $oPeriod = Piwik_Archive::makePeriodFromQueryParams($oSite, $period, $date);
-
- // set the footer message using the period start & end date
- $start = $oPeriod->getDateStart()->toString();
- $end = $oPeriod->getDateEnd()->toString();
- if ($start == $end)
- {
- $dateRange = $start;
- }
- else
- {
- $dateRange = $start." &ndash; ".$end;
- }
-
- $view->setFooterMessage(Piwik_Translate('General_ReportGeneratedFrom', $dateRange));
-
- return $this->renderView($view, $fetch);
- }
-
- private function getGraph( $controllerMethod, $apiMethod, $labelTranslation, $limit = 24 )
- {
- $view = Piwik_ViewDataTable::factory('graphVerticalBar');
- $view->init($this->pluginName, $controllerMethod, $apiMethod);
-
-
- $view->setColumnTranslation('label', Piwik_Translate($labelTranslation));
- $view->setSortedColumn( 'label', 'asc' );
-
- $view->setLimit($limit);
- $view->setGraphLimit($limit);
- $view->disableSearchBox();
- $view->disableExcludeLowPopulation();
- $view->disableOffsetInformationAndPaginationControls();
- $this->setMetricsVariablesView($view);
-
- return $view;
- }
+ public function index()
+ {
+ $view = Piwik_View::factory('index');
+ $view->dataTableVisitInformationPerLocalTime = $this->getVisitInformationPerLocalTime(true);
+ $view->dataTableVisitInformationPerServerTime = $this->getVisitInformationPerServerTime(true);
+ echo $view->render();
+ }
+
+ public function getVisitInformationPerServerTime($fetch = false)
+ {
+ $view = $this->getGraph(__FUNCTION__, 'VisitTime.getVisitInformationPerServerTime',
+ 'VisitTime_ColumnServerTime');
+
+ $view->setCustomParameter('hideFutureHoursWhenToday', 1);
+ $view->enableShowGoals();
+
+ return $this->renderView($view, $fetch);
+ }
+
+ public function getVisitInformationPerLocalTime($fetch = false)
+ {
+ $view = $this->getGraph(__FUNCTION__, 'VisitTime.getVisitInformationPerLocalTime',
+ 'VisitTime_ColumnLocalTime');
+
+ // add the visits by day of week as a related report, if the current period is not 'day'
+ if (Piwik_Common::getRequestVar('period', 'day') != 'day') {
+ $view->addRelatedReports(Piwik_Translate('VisitTime_LocalTime'), array(
+ 'VisitTime.getByDayOfWeek' => Piwik_Translate('VisitTime_VisitsByDayOfWeek')
+ ));
+ }
+
+ return $this->renderView($view, $fetch);
+ }
+
+ public function getByDayOfWeek($fetch = false)
+ {
+ $view = $this->getGraph(
+ __FUNCTION__, 'VisitTime.getByDayOfWeek', 'VisitTime_DayOfWeek', $limit = 7, $sort = false);
+ $view->disableSort();
+
+ if ($view instanceof Piwik_ViewDataTable_GenerateGraphHTML) {
+ $view->showAllTicks();
+ }
+
+ // get query params
+ $idsite = Piwik_Common::getRequestVar('idSite');
+ $date = Piwik_Common::getRequestVar('date');
+ $period = Piwik_Common::getRequestVar('period');
+
+ // create a period instance
+ $oSite = new Piwik_Site($idsite);
+ $oPeriod = Piwik_Archive::makePeriodFromQueryParams($oSite, $period, $date);
+
+ // set the footer message using the period start & end date
+ $start = $oPeriod->getDateStart()->toString();
+ $end = $oPeriod->getDateEnd()->toString();
+ if ($start == $end) {
+ $dateRange = $start;
+ } else {
+ $dateRange = $start . " &ndash; " . $end;
+ }
+
+ $view->setFooterMessage(Piwik_Translate('General_ReportGeneratedFrom', $dateRange));
+
+ return $this->renderView($view, $fetch);
+ }
+
+ private function getGraph($controllerMethod, $apiMethod, $labelTranslation, $limit = 24)
+ {
+ $view = Piwik_ViewDataTable::factory('graphVerticalBar');
+ $view->init($this->pluginName, $controllerMethod, $apiMethod);
+
+
+ $view->setColumnTranslation('label', Piwik_Translate($labelTranslation));
+ $view->setSortedColumn('label', 'asc');
+
+ $view->setLimit($limit);
+ $view->setGraphLimit($limit);
+ $view->disableSearchBox();
+ $view->disableExcludeLowPopulation();
+ $view->disableOffsetInformationAndPaginationControls();
+ $this->setMetricsVariablesView($view);
+
+ return $view;
+ }
}
diff --git a/plugins/VisitTime/VisitTime.php b/plugins/VisitTime/VisitTime.php
index bbf26457ef..5b05342bed 100644
--- a/plugins/VisitTime/VisitTime.php
+++ b/plugins/VisitTime/VisitTime.php
@@ -15,215 +15,211 @@
*/
class Piwik_VisitTime extends Piwik_Plugin
{
- public function getInformation()
- {
- $info = array(
- 'description' => Piwik_Translate('VisitTime_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' => 'addMenu',
- '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(
- 'category' => Piwik_Translate('VisitsSummary_VisitsSummary'),
- 'name' => Piwik_Translate('VisitTime_WidgetLocalTime'),
- 'module' => 'VisitTime',
- 'action' => 'getVisitInformationPerLocalTime',
- 'dimension' => Piwik_Translate('VisitTime_ColumnLocalTime'),
- 'documentation' => Piwik_Translate('VisitTime_WidgetLocalTimeDocumentation', array('<b>', '</b>')),
- 'constantRowsCount' => true,
- 'order' => 20
- );
-
- $reports[] = array(
- 'category' => Piwik_Translate('VisitsSummary_VisitsSummary'),
- 'name' => Piwik_Translate('VisitTime_WidgetServerTime'),
- 'module' => 'VisitTime',
- 'action' => 'getVisitInformationPerServerTime',
- 'dimension' => Piwik_Translate('VisitTime_ColumnServerTime'),
- 'documentation' => Piwik_Translate('VisitTime_WidgetServerTimeDocumentation', array('<b>', '</b>')),
- 'constantRowsCount' => true,
- 'order' => 15,
- );
-
- $reports[] = array(
- 'category' => Piwik_Translate('VisitsSummary_VisitsSummary'),
- 'name' => Piwik_Translate('VisitTime_VisitsByDayOfWeek'),
- 'module' => 'VisitTime',
- 'action' => 'getByDayOfWeek',
- 'dimension' => Piwik_Translate('VisitTime_DayOfWeek'),
- 'documentation' => Piwik_Translate('VisitTime_WidgetByDayOfWeekDocumentation'),
- 'constantRowsCount' => true,
- 'order' => 25,
- );
- }
-
- function addWidgets()
- {
- Piwik_AddWidget( 'VisitsSummary_VisitsSummary', 'VisitTime_WidgetLocalTime', 'VisitTime', 'getVisitInformationPerLocalTime');
- Piwik_AddWidget( 'VisitsSummary_VisitsSummary', 'VisitTime_WidgetServerTime', 'VisitTime', 'getVisitInformationPerServerTime');
- Piwik_AddWidget( 'VisitsSummary_VisitsSummary', 'VisitTime_VisitsByDayOfWeek', 'VisitTime', 'getByDayOfWeek');
- }
-
- function addMenu()
- {
- Piwik_AddMenu('General_Visitors', 'VisitTime_SubmenuTimes', array('module' => 'VisitTime', 'action' => 'index'));
- }
-
- /**
- * @param Piwik_Event_Notification $notification notification object
- */
- function getReportsWithGoalMetrics( $notification )
- {
- $dimensions =& $notification->getNotificationObject();
- $dimensions[] = array('category' => Piwik_Translate('VisitTime_ColumnServerTime'),
- 'name' => Piwik_Translate('VisitTime_ColumnServerTime'),
- 'module' => 'VisitTime',
- 'action' => 'getVisitInformationPerServerTime',
- );
- }
-
- /**
- * @param Piwik_Event_Notification $notification notification object
- */
- public function getSegmentsMetadata($notification)
- {
- $segments =& $notification->getNotificationObject();
- $acceptedValues = "0, 1, 2, 3, ..., 20, 21, 22, 23";
- $segments[] = array(
- 'type' => 'dimension',
- 'category' => 'Visit',
- 'name' => Piwik_Translate('VisitTime_ColumnServerTime'),
- 'segment' => 'visitServerHour',
- 'sqlSegment' => 'HOUR(log_visit.visit_last_action_time)',
- 'acceptedValues' => $acceptedValues
- );
- $segments[] = array(
- 'type' => 'dimension',
- 'category' => 'Visit',
- 'name' => Piwik_Translate('VisitTime_ColumnLocalTime'),
- 'segment' => 'visitLocalHour',
- 'sqlSegment' => 'HOUR(log_visit.visitor_localtime)',
- 'acceptedValues' => $acceptedValues
- );
- }
-
- /**
- * @param Piwik_Event_Notification $notification notification object
- * @return mixed
- */
- function archivePeriod( $notification )
- {
- $archiveProcessing = $notification->getNotificationObject();
-
- if(!$archiveProcessing->shouldProcessReportsForPlugin($this->getPluginName())) return;
-
- $dataTableToSum = array(
- 'VisitTime_localTime',
- 'VisitTime_serverTime',
- );
- $archiveProcessing->archiveDataTable($dataTableToSum);
- }
-
- /**
- * @param Piwik_Event_Notification $notification notification object
- * @return mixed
- */
- public function archiveDay( $notification )
- {
- $archiveProcessing = $notification->getNotificationObject();
-
- if(!$archiveProcessing->shouldProcessReportsForPlugin($this->getPluginName())) return;
-
- $this->archiveDayAggregateVisits($archiveProcessing);
- $this->archiveDayAggregateGoals($archiveProcessing);
- $this->archiveDayRecordInDatabase($archiveProcessing);
- }
-
- protected function archiveDayAggregateVisits($archiveProcessing)
- {
- $labelSQL = "HOUR(log_visit.visitor_localtime)";
- $this->interestByLocalTime = $archiveProcessing->getArrayInterestForLabel($labelSQL);
-
- $labelSQL = "HOUR(log_visit.visit_last_action_time)";
- $this->interestByServerTime = $archiveProcessing->getArrayInterestForLabel($labelSQL);
- }
-
- protected function convertServerTimeToLocalTimezone($interestByServerTime, $archiveProcessing)
- {
- $date = Piwik_Date::factory($archiveProcessing->getStartDatetimeUTC())->toString();
- $timezone = $archiveProcessing->site->getTimezone();
- $visitsByHourTz = array();
- foreach($interestByServerTime as $hour => $stats)
- {
- $datetime = $date . ' '.$hour.':00:00';
- $hourInTz = (int)Piwik_Date::factory($datetime, $timezone)->toString('H');
- $visitsByHourTz[$hourInTz] = $stats;
- }
- return $visitsByHourTz;
- }
-
- protected function archiveDayAggregateGoals($archiveProcessing)
- {
- $query = $archiveProcessing->queryConversionsByDimension("HOUR(log_conversion.server_time)");
-
- if($query === false) return;
-
- while($row = $query->fetch())
- {
- if(!isset($this->interestByServerTime[$row['label']][Piwik_Archive::INDEX_GOALS][$row['idgoal']])) $this->interestByServerTime[$row['label']][Piwik_Archive::INDEX_GOALS][$row['idgoal']] = $archiveProcessing->getNewGoalRow($row['idgoal']);
- $archiveProcessing->updateGoalStats($row, $this->interestByServerTime[$row['label']][Piwik_Archive::INDEX_GOALS][$row['idgoal']]);
- }
- $goalByServerTime = $this->convertServerTimeToLocalTimezone($this->interestByServerTime, $archiveProcessing);
- $archiveProcessing->enrichConversionsByLabelArray($this->interestByServerTime);
- }
-
- protected function archiveDayRecordInDatabase($archiveProcessing)
- {
- $tableLocalTime = $archiveProcessing->getDataTableFromArray($this->interestByLocalTime);
- $this->makeSureAllHoursAreSet($tableLocalTime, $archiveProcessing);
- $archiveProcessing->insertBlobRecord('VisitTime_localTime', $tableLocalTime->getSerialized());
- destroy($tableLocalTime);
-
- $this->interestByServerTime = $this->convertServerTimeToLocalTimezone($this->interestByServerTime, $archiveProcessing);
- $tableServerTime = $archiveProcessing->getDataTableFromArray($this->interestByServerTime);
- $this->makeSureAllHoursAreSet($tableServerTime, $archiveProcessing);
- $archiveProcessing->insertBlobRecord('VisitTime_serverTime', $tableServerTime->getSerialized());
- destroy($tableServerTime);
- }
-
- private function makeSureAllHoursAreSet($table, $archiveProcessing)
- {
- for($i=0; $i<=23; $i++)
- {
- if($table->getRowFromLabel($i) === false)
- {
- $row = $archiveProcessing->getNewInterestRowLabeled($i);
- $table->addRow( $row );
- }
- }
- }
+ public function getInformation()
+ {
+ $info = array(
+ 'description' => Piwik_Translate('VisitTime_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' => 'addMenu',
+ '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(
+ 'category' => Piwik_Translate('VisitsSummary_VisitsSummary'),
+ 'name' => Piwik_Translate('VisitTime_WidgetLocalTime'),
+ 'module' => 'VisitTime',
+ 'action' => 'getVisitInformationPerLocalTime',
+ 'dimension' => Piwik_Translate('VisitTime_ColumnLocalTime'),
+ 'documentation' => Piwik_Translate('VisitTime_WidgetLocalTimeDocumentation', array('<b>', '</b>')),
+ 'constantRowsCount' => true,
+ 'order' => 20
+ );
+
+ $reports[] = array(
+ 'category' => Piwik_Translate('VisitsSummary_VisitsSummary'),
+ 'name' => Piwik_Translate('VisitTime_WidgetServerTime'),
+ 'module' => 'VisitTime',
+ 'action' => 'getVisitInformationPerServerTime',
+ 'dimension' => Piwik_Translate('VisitTime_ColumnServerTime'),
+ 'documentation' => Piwik_Translate('VisitTime_WidgetServerTimeDocumentation', array('<b>', '</b>')),
+ 'constantRowsCount' => true,
+ 'order' => 15,
+ );
+
+ $reports[] = array(
+ 'category' => Piwik_Translate('VisitsSummary_VisitsSummary'),
+ 'name' => Piwik_Translate('VisitTime_VisitsByDayOfWeek'),
+ 'module' => 'VisitTime',
+ 'action' => 'getByDayOfWeek',
+ 'dimension' => Piwik_Translate('VisitTime_DayOfWeek'),
+ 'documentation' => Piwik_Translate('VisitTime_WidgetByDayOfWeekDocumentation'),
+ 'constantRowsCount' => true,
+ 'order' => 25,
+ );
+ }
+
+ function addWidgets()
+ {
+ Piwik_AddWidget('VisitsSummary_VisitsSummary', 'VisitTime_WidgetLocalTime', 'VisitTime', 'getVisitInformationPerLocalTime');
+ Piwik_AddWidget('VisitsSummary_VisitsSummary', 'VisitTime_WidgetServerTime', 'VisitTime', 'getVisitInformationPerServerTime');
+ Piwik_AddWidget('VisitsSummary_VisitsSummary', 'VisitTime_VisitsByDayOfWeek', 'VisitTime', 'getByDayOfWeek');
+ }
+
+ function addMenu()
+ {
+ Piwik_AddMenu('General_Visitors', 'VisitTime_SubmenuTimes', array('module' => 'VisitTime', 'action' => 'index'));
+ }
+
+ /**
+ * @param Piwik_Event_Notification $notification notification object
+ */
+ function getReportsWithGoalMetrics($notification)
+ {
+ $dimensions =& $notification->getNotificationObject();
+ $dimensions[] = array('category' => Piwik_Translate('VisitTime_ColumnServerTime'),
+ 'name' => Piwik_Translate('VisitTime_ColumnServerTime'),
+ 'module' => 'VisitTime',
+ 'action' => 'getVisitInformationPerServerTime',
+ );
+ }
+
+ /**
+ * @param Piwik_Event_Notification $notification notification object
+ */
+ public function getSegmentsMetadata($notification)
+ {
+ $segments =& $notification->getNotificationObject();
+ $acceptedValues = "0, 1, 2, 3, ..., 20, 21, 22, 23";
+ $segments[] = array(
+ 'type' => 'dimension',
+ 'category' => 'Visit',
+ 'name' => Piwik_Translate('VisitTime_ColumnServerTime'),
+ 'segment' => 'visitServerHour',
+ 'sqlSegment' => 'HOUR(log_visit.visit_last_action_time)',
+ 'acceptedValues' => $acceptedValues
+ );
+ $segments[] = array(
+ 'type' => 'dimension',
+ 'category' => 'Visit',
+ 'name' => Piwik_Translate('VisitTime_ColumnLocalTime'),
+ 'segment' => 'visitLocalHour',
+ 'sqlSegment' => 'HOUR(log_visit.visitor_localtime)',
+ 'acceptedValues' => $acceptedValues
+ );
+ }
+
+ /**
+ * @param Piwik_Event_Notification $notification notification object
+ * @return mixed
+ */
+ function archivePeriod($notification)
+ {
+ $archiveProcessing = $notification->getNotificationObject();
+
+ if (!$archiveProcessing->shouldProcessReportsForPlugin($this->getPluginName())) return;
+
+ $dataTableToSum = array(
+ 'VisitTime_localTime',
+ 'VisitTime_serverTime',
+ );
+ $archiveProcessing->archiveDataTable($dataTableToSum);
+ }
+
+ /**
+ * @param Piwik_Event_Notification $notification notification object
+ * @return mixed
+ */
+ public function archiveDay($notification)
+ {
+ $archiveProcessing = $notification->getNotificationObject();
+
+ if (!$archiveProcessing->shouldProcessReportsForPlugin($this->getPluginName())) return;
+
+ $this->archiveDayAggregateVisits($archiveProcessing);
+ $this->archiveDayAggregateGoals($archiveProcessing);
+ $this->archiveDayRecordInDatabase($archiveProcessing);
+ }
+
+ protected function archiveDayAggregateVisits($archiveProcessing)
+ {
+ $labelSQL = "HOUR(log_visit.visitor_localtime)";
+ $this->interestByLocalTime = $archiveProcessing->getArrayInterestForLabel($labelSQL);
+
+ $labelSQL = "HOUR(log_visit.visit_last_action_time)";
+ $this->interestByServerTime = $archiveProcessing->getArrayInterestForLabel($labelSQL);
+ }
+
+ protected function convertServerTimeToLocalTimezone($interestByServerTime, $archiveProcessing)
+ {
+ $date = Piwik_Date::factory($archiveProcessing->getStartDatetimeUTC())->toString();
+ $timezone = $archiveProcessing->site->getTimezone();
+ $visitsByHourTz = array();
+ foreach ($interestByServerTime as $hour => $stats) {
+ $datetime = $date . ' ' . $hour . ':00:00';
+ $hourInTz = (int)Piwik_Date::factory($datetime, $timezone)->toString('H');
+ $visitsByHourTz[$hourInTz] = $stats;
+ }
+ return $visitsByHourTz;
+ }
+
+ protected function archiveDayAggregateGoals($archiveProcessing)
+ {
+ $query = $archiveProcessing->queryConversionsByDimension("HOUR(log_conversion.server_time)");
+
+ if ($query === false) return;
+
+ while ($row = $query->fetch()) {
+ if (!isset($this->interestByServerTime[$row['label']][Piwik_Archive::INDEX_GOALS][$row['idgoal']])) $this->interestByServerTime[$row['label']][Piwik_Archive::INDEX_GOALS][$row['idgoal']] = $archiveProcessing->getNewGoalRow($row['idgoal']);
+ $archiveProcessing->updateGoalStats($row, $this->interestByServerTime[$row['label']][Piwik_Archive::INDEX_GOALS][$row['idgoal']]);
+ }
+ $goalByServerTime = $this->convertServerTimeToLocalTimezone($this->interestByServerTime, $archiveProcessing);
+ $archiveProcessing->enrichConversionsByLabelArray($this->interestByServerTime);
+ }
+
+ protected function archiveDayRecordInDatabase($archiveProcessing)
+ {
+ $tableLocalTime = $archiveProcessing->getDataTableFromArray($this->interestByLocalTime);
+ $this->makeSureAllHoursAreSet($tableLocalTime, $archiveProcessing);
+ $archiveProcessing->insertBlobRecord('VisitTime_localTime', $tableLocalTime->getSerialized());
+ destroy($tableLocalTime);
+
+ $this->interestByServerTime = $this->convertServerTimeToLocalTimezone($this->interestByServerTime, $archiveProcessing);
+ $tableServerTime = $archiveProcessing->getDataTableFromArray($this->interestByServerTime);
+ $this->makeSureAllHoursAreSet($tableServerTime, $archiveProcessing);
+ $archiveProcessing->insertBlobRecord('VisitTime_serverTime', $tableServerTime->getSerialized());
+ destroy($tableServerTime);
+ }
+
+ private function makeSureAllHoursAreSet($table, $archiveProcessing)
+ {
+ for ($i = 0; $i <= 23; $i++) {
+ if ($table->getRowFromLabel($i) === false) {
+ $row = $archiveProcessing->getNewInterestRowLabeled($i);
+ $table->addRow($row);
+ }
+ }
+ }
}
diff --git a/plugins/VisitTime/templates/index.tpl b/plugins/VisitTime/templates/index.tpl
index 82af946c7a..3d742d6293 100644
--- a/plugins/VisitTime/templates/index.tpl
+++ b/plugins/VisitTime/templates/index.tpl
@@ -1,9 +1,9 @@
<div id='leftcolumn'>
-<h2>{'VisitTime_LocalTime'|translate}</h2>
-{$dataTableVisitInformationPerLocalTime}
+ <h2>{'VisitTime_LocalTime'|translate}</h2>
+ {$dataTableVisitInformationPerLocalTime}
</div>
<div id='rightcolumn'>
-<h2>{'VisitTime_ServerTime'|translate}</h2>
-{$dataTableVisitInformationPerServerTime}
+ <h2>{'VisitTime_ServerTime'|translate}</h2>
+ {$dataTableVisitInformationPerServerTime}
</div>
diff --git a/plugins/VisitorGenerator/Controller.php b/plugins/VisitorGenerator/Controller.php
index ec75c39ace..54ebc3aea0 100644
--- a/plugins/VisitorGenerator/Controller.php
+++ b/plugins/VisitorGenerator/Controller.php
@@ -15,144 +15,142 @@
*/
class Piwik_VisitorGenerator_Controller extends Piwik_Controller_Admin
{
- public function index()
- {
- Piwik::checkUserIsSuperUser();
-
- $sitesList = Piwik_SitesManager_API::getInstance()->getSitesWithAdminAccess();
-
- $view = Piwik_View::factory('index');
- $this->setBasicVariablesView($view);
- $view->assign('sitesList', $sitesList);
- $view->nonce = Piwik_Nonce::getNonce('Piwik_VisitorGenerator.generate');
- $view->countActionsPerRun = count($this->getAccessLog());
- $view->accessLogPath = $this->getAccessLogPath();
- $view->menu = Piwik_GetAdminMenu();
- echo $view->render();
- }
- private function getAccessLogPath()
- {
- return PIWIK_INCLUDE_PATH . "/plugins/VisitorGenerator/data/access.log";
- }
- private function getAccessLog()
- {
- $log = file($this->getAccessLogPath());
- return $log;
- }
-
- public function generate()
- {
- Piwik::checkUserIsSuperUser();
- $nonce = Piwik_Common::getRequestVar('form_nonce', '', 'string', $_POST);
- if(Piwik_Common::getRequestVar('choice', 'no') != 'yes' ||
- !Piwik_Nonce::verifyNonce('Piwik_VisitorGenerator.generate', $nonce))
- {
- Piwik::redirectToModule('VisitorGenerator', 'index');
- }
- Piwik_Nonce::discardNonce('Piwik_VisitorGenerator.generate');
-
- $daysToCompute = Piwik_Common::getRequestVar('daysToCompute', 1, 'int');
-
- // get idSite from POST with fallback to GET
- $idSite = Piwik_Common::getRequestVar('idSite', false, 'int', $_GET);
- $idSite = Piwik_Common::getRequestVar('idSite', $idSite, 'int', $_POST);
-
- Piwik::setMaxExecutionTime(0);
-
- $timer = new Piwik_Timer;
- $time = time() - ($daysToCompute-1)*86400;
-
- $nbActionsTotal = 0;
- $dates = array();
- while($time <= time())
- {
- $nbActionsTotalThisDay = $this->generateVisits($time, $idSite);
- $dates[] = date("Y-m-d", $time);
- $time += 86400;
- $nbActionsTotal += $nbActionsTotalThisDay;
- }
-
- $api = Piwik_CoreAdminHome_API::getInstance();
- $api->invalidateArchivedReports($idSite, implode($dates, ","));
-
- $browserArchiving = Piwik_ArchiveProcessing::isBrowserTriggerArchivingEnabled();
-
- // Init view
- $view = Piwik_View::factory('generate');
- $this->setBasicVariablesView($view);
- $view->menu = Piwik_GetAdminMenu();
- $view->assign('browserArchivingEnabled', $browserArchiving);
- $view->assign('timer', $timer);
- $view->assign('days', $daysToCompute);
- $view->assign('nbActionsTotal', $nbActionsTotal);
- $view->assign('nbRequestsPerSec', round($nbActionsTotal / $timer->getTime(),0));
- $view->assign('siteName', Piwik_Site::getNameFor($idSite));
- echo $view->render();
- }
-
- private function generateVisits($time = false, $idSite = 1)
- {
- $logs = $this->getAccessLog();
- if(empty($time)) $time = time();
- $date = date("Y-m-d", $time);
-
- $acceptLanguages = array(
- "el,fi;q=0.5",
- "de-de,de;q=0.8,en-us",
- "pl,en-us;q=0.7,en;q=",
- "zh-cn",
- "fr-ca",
- "en-us",
- "en-gb",
- "fr-be",
- "fr,de-ch;q=0.5",
- "fr",
- "fr-ch",
- "fr",
- );
- $prefix = Piwik_Url::getCurrentUrlWithoutFileName() . "piwik.php";
- $count = 0;
- foreach($logs as $log)
- {
- if (!preg_match('/^(\S+) \S+ \S+ \[(.*?)\] "GET (\S+.*?)" \d+ \d+ "(.*?)" "(.*?)"/', $log, $m)) {
- continue;
- }
- $ip = $m[1];
- $time = $m[2];
- $url = $m[3];
- $referrer = $m[4];
- $ua = $m[5];
-
- $start = strpos($url, 'piwik.php?') + strlen('piwik.php?');
- $url = substr($url, $start, strrpos($url, " ")-$start);
- $datetime = $date . " " . Piwik_Date::factory($time)->toString("H:i:s");
- $ip = strlen($ip) < 10 ? "13.5.111.3" : $ip;
-
- // Force date/ip & authenticate
- $url .= "&cdt=" . urlencode($datetime);
- if(strpos($url, 'cip') === false)
- {
- $url .= "&cip=" . $ip;
- }
- $url .= "&token_auth=" . Piwik::getCurrentUserTokenAuth();
- $url = $prefix . "?" . $url;
-
- // Make order IDs unique per day
- $url = str_replace("ec_id=", "ec_id=$date-", $url);
-
- // Disable provider plugin
- $url .= "&dp=1";
-
- // Replace idsite
- $url = preg_replace("/idsite=[0-9]+/", "idsite=$idSite", $url);
-
- $acceptLanguage = $acceptLanguages[$count % count($acceptLanguages)];
-
- if($output = Piwik_Http::sendHttpRequest($url, $timeout = 5, $ua, $path = null, $follow = 0, $acceptLanguage))
- {
- $count++;
- }
- }
- return $count;
- }
+ public function index()
+ {
+ Piwik::checkUserIsSuperUser();
+
+ $sitesList = Piwik_SitesManager_API::getInstance()->getSitesWithAdminAccess();
+
+ $view = Piwik_View::factory('index');
+ $this->setBasicVariablesView($view);
+ $view->assign('sitesList', $sitesList);
+ $view->nonce = Piwik_Nonce::getNonce('Piwik_VisitorGenerator.generate');
+ $view->countActionsPerRun = count($this->getAccessLog());
+ $view->accessLogPath = $this->getAccessLogPath();
+ $view->menu = Piwik_GetAdminMenu();
+ echo $view->render();
+ }
+
+ private function getAccessLogPath()
+ {
+ return PIWIK_INCLUDE_PATH . "/plugins/VisitorGenerator/data/access.log";
+ }
+
+ private function getAccessLog()
+ {
+ $log = file($this->getAccessLogPath());
+ return $log;
+ }
+
+ public function generate()
+ {
+ Piwik::checkUserIsSuperUser();
+ $nonce = Piwik_Common::getRequestVar('form_nonce', '', 'string', $_POST);
+ if (Piwik_Common::getRequestVar('choice', 'no') != 'yes' ||
+ !Piwik_Nonce::verifyNonce('Piwik_VisitorGenerator.generate', $nonce)
+ ) {
+ Piwik::redirectToModule('VisitorGenerator', 'index');
+ }
+ Piwik_Nonce::discardNonce('Piwik_VisitorGenerator.generate');
+
+ $daysToCompute = Piwik_Common::getRequestVar('daysToCompute', 1, 'int');
+
+ // get idSite from POST with fallback to GET
+ $idSite = Piwik_Common::getRequestVar('idSite', false, 'int', $_GET);
+ $idSite = Piwik_Common::getRequestVar('idSite', $idSite, 'int', $_POST);
+
+ Piwik::setMaxExecutionTime(0);
+
+ $timer = new Piwik_Timer;
+ $time = time() - ($daysToCompute - 1) * 86400;
+
+ $nbActionsTotal = 0;
+ $dates = array();
+ while ($time <= time()) {
+ $nbActionsTotalThisDay = $this->generateVisits($time, $idSite);
+ $dates[] = date("Y-m-d", $time);
+ $time += 86400;
+ $nbActionsTotal += $nbActionsTotalThisDay;
+ }
+
+ $api = Piwik_CoreAdminHome_API::getInstance();
+ $api->invalidateArchivedReports($idSite, implode($dates, ","));
+
+ $browserArchiving = Piwik_ArchiveProcessing::isBrowserTriggerArchivingEnabled();
+
+ // Init view
+ $view = Piwik_View::factory('generate');
+ $this->setBasicVariablesView($view);
+ $view->menu = Piwik_GetAdminMenu();
+ $view->assign('browserArchivingEnabled', $browserArchiving);
+ $view->assign('timer', $timer);
+ $view->assign('days', $daysToCompute);
+ $view->assign('nbActionsTotal', $nbActionsTotal);
+ $view->assign('nbRequestsPerSec', round($nbActionsTotal / $timer->getTime(), 0));
+ $view->assign('siteName', Piwik_Site::getNameFor($idSite));
+ echo $view->render();
+ }
+
+ private function generateVisits($time = false, $idSite = 1)
+ {
+ $logs = $this->getAccessLog();
+ if (empty($time)) $time = time();
+ $date = date("Y-m-d", $time);
+
+ $acceptLanguages = array(
+ "el,fi;q=0.5",
+ "de-de,de;q=0.8,en-us",
+ "pl,en-us;q=0.7,en;q=",
+ "zh-cn",
+ "fr-ca",
+ "en-us",
+ "en-gb",
+ "fr-be",
+ "fr,de-ch;q=0.5",
+ "fr",
+ "fr-ch",
+ "fr",
+ );
+ $prefix = Piwik_Url::getCurrentUrlWithoutFileName() . "piwik.php";
+ $count = 0;
+ foreach ($logs as $log) {
+ if (!preg_match('/^(\S+) \S+ \S+ \[(.*?)\] "GET (\S+.*?)" \d+ \d+ "(.*?)" "(.*?)"/', $log, $m)) {
+ continue;
+ }
+ $ip = $m[1];
+ $time = $m[2];
+ $url = $m[3];
+ $referrer = $m[4];
+ $ua = $m[5];
+
+ $start = strpos($url, 'piwik.php?') + strlen('piwik.php?');
+ $url = substr($url, $start, strrpos($url, " ") - $start);
+ $datetime = $date . " " . Piwik_Date::factory($time)->toString("H:i:s");
+ $ip = strlen($ip) < 10 ? "13.5.111.3" : $ip;
+
+ // Force date/ip & authenticate
+ $url .= "&cdt=" . urlencode($datetime);
+ if (strpos($url, 'cip') === false) {
+ $url .= "&cip=" . $ip;
+ }
+ $url .= "&token_auth=" . Piwik::getCurrentUserTokenAuth();
+ $url = $prefix . "?" . $url;
+
+ // Make order IDs unique per day
+ $url = str_replace("ec_id=", "ec_id=$date-", $url);
+
+ // Disable provider plugin
+ $url .= "&dp=1";
+
+ // Replace idsite
+ $url = preg_replace("/idsite=[0-9]+/", "idsite=$idSite", $url);
+
+ $acceptLanguage = $acceptLanguages[$count % count($acceptLanguages)];
+
+ if ($output = Piwik_Http::sendHttpRequest($url, $timeout = 5, $ua, $path = null, $follow = 0, $acceptLanguage)) {
+ $count++;
+ }
+ }
+ return $count;
+ }
}
diff --git a/plugins/VisitorGenerator/VisitorGenerator.php b/plugins/VisitorGenerator/VisitorGenerator.php
index 6249ab4df5..ebeb94e976 100644
--- a/plugins/VisitorGenerator/VisitorGenerator.php
+++ b/plugins/VisitorGenerator/VisitorGenerator.php
@@ -10,32 +10,36 @@
*/
/**
- *
+ *
* @package Piwik_VisitorGenerator
*/
-class Piwik_VisitorGenerator extends Piwik_Plugin {
+class Piwik_VisitorGenerator extends Piwik_Plugin
+{
- public function getInformation() {
- return array(
- 'description' => Piwik_Translate('VisitorGenerator_PluginDescription'),
- 'author' => 'Piwik',
- 'author_homepage' => 'http://piwik.org/',
- 'version' => Piwik_Version::VERSION,
- );
- }
+ public function getInformation()
+ {
+ return array(
+ 'description' => Piwik_Translate('VisitorGenerator_PluginDescription'),
+ 'author' => 'Piwik',
+ 'author_homepage' => 'http://piwik.org/',
+ 'version' => Piwik_Version::VERSION,
+ );
+ }
- public function getListHooksRegistered() {
- return array(
- 'AdminMenu.add' => 'addMenu',
- );
- }
+ public function getListHooksRegistered()
+ {
+ return array(
+ 'AdminMenu.add' => 'addMenu',
+ );
+ }
- public function addMenu() {
- Piwik_AddAdminSubMenu(
- 'CoreAdminHome_MenuDiagnostic', 'VisitorGenerator_VisitorGenerator',
- array('module' => 'VisitorGenerator', 'action' => 'index'),
- Piwik::isUserIsSuperUser(),
- $order = 20
- );
- }
+ public function addMenu()
+ {
+ Piwik_AddAdminSubMenu(
+ 'CoreAdminHome_MenuDiagnostic', 'VisitorGenerator_VisitorGenerator',
+ array('module' => 'VisitorGenerator', 'action' => 'index'),
+ Piwik::isUserIsSuperUser(),
+ $order = 20
+ );
+ }
}
diff --git a/plugins/VisitorGenerator/templates/generate.tpl b/plugins/VisitorGenerator/templates/generate.tpl
index 12d13fdac3..370d40b794 100644
--- a/plugins/VisitorGenerator/templates/generate.tpl
+++ b/plugins/VisitorGenerator/templates/generate.tpl
@@ -2,16 +2,17 @@
<h2>{'VisitorGenerator_VisitorGenerator'|translate}</h2>
-Generated visits for {$siteName} and for {'General_LastDays'|translate:$days}.<br />
-Generated {'General_NbActions'|translate}: {$nbActionsTotal}<br />
-{'VisitorGenerator_NbRequestsPerSec'|translate}: {$nbRequestsPerSec}<br />
+Generated visits for {$siteName} and for {'General_LastDays'|translate:$days}.<br/>
+Generated {'General_NbActions'|translate}: {$nbActionsTotal}<br/>
+{'VisitorGenerator_NbRequestsPerSec'|translate}: {$nbRequestsPerSec}<br/>
{$timer}</p>
<p><strong>
-{if $browserArchivingEnabled}
-The reports will be reprocessed the next time you visit the Piwik reports, it might take a few minutes.
-{else}
-Please re-run the archive.php Piwik script in the crontab to refresh the reports. <a href="http://piwik.org/docs/setup-auto-archiving/">See "How to Set up Auto-Archiving of Your Reports"</a>
-{/if}
-</strong>
+ {if $browserArchivingEnabled}
+ The reports will be reprocessed the next time you visit the Piwik reports, it might take a few minutes.
+ {else}
+ Please re-run the archive.php Piwik script in the crontab to refresh the reports.
+ <a href="http://piwik.org/docs/setup-auto-archiving/">See "How to Set up Auto-Archiving of Your Reports"</a>
+ {/if}
+ </strong>
</p>
{include file="CoreAdminHome/templates/footer.tpl"}
diff --git a/plugins/VisitorGenerator/templates/index.tpl b/plugins/VisitorGenerator/templates/index.tpl
index 944b82dee2..f3477285ac 100644
--- a/plugins/VisitorGenerator/templates/index.tpl
+++ b/plugins/VisitorGenerator/templates/index.tpl
@@ -2,36 +2,38 @@
<h2>{'VisitorGenerator_VisitorGenerator'|translate}</h2>
<p>{'VisitorGenerator_PluginDescription'|translate}</p>
-<p>You can overwrite the log file that is used to generate fake visits (change {$accessLogPath}). This is a log file of requests to "piwik.php" in the format "Apache combined log".</p>
+<p>You can overwrite the log file that is used to generate fake visits (change {$accessLogPath}). This is a log file of requests to "piwik.php" in the format
+ "Apache combined log".</p>
<form method="POST" action="{url module=VisitorGenerator action=generate}">
-<table class="adminTable" style="width: 600px;">
-<tr>
- <td><label for="idSite">{'General_ChooseWebsite'|translate}</label></td>
- <td>
- {include file="CoreHome/templates/sites_selection.tpl"
- showAllSitesItem=false showSelectedSite=true switchSiteOnSelect=false inputName=idSite}
- </td>
-</tr>
-<tr>
- <td><label for="daysToCompute">{'VisitorGenerator_DaysToCompute'|translate}</label></td>
- <td><input type="text" value="1" name="daysToCompute" /></td>
-</tr>
-</table>
-{'VisitorGenerator_Warning'|translate}<br />
-{'VisitorGenerator_NotReversible'|translate:'<b>':'</b>'}<br /><br />
-<p><strong>This will generate approximately {$countActionsPerRun} fake actions on this site for each day</strong>.<br/>
-</p>
-{'VisitorGenerator_AreYouSure'|translate}<br /><br/>
-<input type="checkbox" name="choice" id="choice" value="yes" /> <label for="choice">{'VisitorGenerator_ChoiceYes'|translate}</label>
-<br />
-<input type="hidden" value="{$token_auth}" name="token_auth" />
-<input type="hidden" value="{$nonce}" name="form_nonce" />
-<br/>
+ <table class="adminTable" style="width: 600px;">
+ <tr>
+ <td><label for="idSite">{'General_ChooseWebsite'|translate}</label></td>
+ <td>
+ {include file="CoreHome/templates/sites_selection.tpl"
+ showAllSitesItem=false showSelectedSite=true switchSiteOnSelect=false inputName=idSite}
+ </td>
+ </tr>
+ <tr>
+ <td><label for="daysToCompute">{'VisitorGenerator_DaysToCompute'|translate}</label></td>
+ <td><input type="text" value="1" name="daysToCompute"/></td>
+ </tr>
+ </table>
+ {'VisitorGenerator_Warning'|translate}<br/>
+ {'VisitorGenerator_NotReversible'|translate:'<b>':'</b>'}<br/><br/>
- NOTE: It might take a few minutes to generate visits and actions, please be patient...<br/>
- There is also a faster tool that will import large test data in Piwik, see the <a href='https://github.com/piwik/piwik/tree/master/tests#testing-data'>README</a>.</p>
- <br/>
-<input type="submit" value="{'VisitorGenerator_Submit'|translate}" name="submit" class="submit" />
+ <p><strong>This will generate approximately {$countActionsPerRun} fake actions on this site for each day</strong>.<br/>
+ </p>
+ {'VisitorGenerator_AreYouSure'|translate}<br/><br/>
+ <input type="checkbox" name="choice" id="choice" value="yes"/> <label for="choice">{'VisitorGenerator_ChoiceYes'|translate}</label>
+ <br/>
+ <input type="hidden" value="{$token_auth}" name="token_auth"/>
+ <input type="hidden" value="{$nonce}" name="form_nonce"/>
+ <br/>
+
+ NOTE: It might take a few minutes to generate visits and actions, please be patient...<br/>
+ There is also a faster tool that will import large test data in Piwik, see the <a href='https://github.com/piwik/piwik/tree/master/tests#testing-data'>README</a>.</p>
+ <br/>
+ <input type="submit" value="{'VisitorGenerator_Submit'|translate}" name="submit" class="submit"/>
</form>
{include file="CoreAdminHome/templates/footer.tpl"}
diff --git a/plugins/VisitorInterest/API.php b/plugins/VisitorInterest/API.php
index 72b43207f5..7adfd7ca4e 100644
--- a/plugins/VisitorInterest/API.php
+++ b/plugins/VisitorInterest/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_VisitorInterest
*/
@@ -12,118 +12,114 @@
/**
* VisitorInterest API lets you access two Visitor Engagement reports: number of visits per number of pages,
* and number of visits per visit duration.
- *
+ *
* @package Piwik_VisitorInterest
*/
-class Piwik_VisitorInterest_API
+class Piwik_VisitorInterest_API
{
- static private $instance = null;
- static public function getInstance()
- {
- if (self::$instance == null)
- {
- self::$instance = new self;
- }
- return self::$instance;
- }
+ static private $instance = null;
- protected function getDataTable($name, $idSite, $period, $date, $segment, $column = Piwik_Archive::INDEX_NB_VISITS)
- {
- Piwik::checkUserHasViewAccess( $idSite );
- $archive = Piwik_Archive::build($idSite, $period, $date, $segment );
- $dataTable = $archive->getDataTable($name);
- $dataTable->queueFilter('ReplaceColumnNames');
- return $dataTable;
- }
-
- public function getNumberOfVisitsPerVisitDuration( $idSite, $period, $date, $segment = false )
- {
- $dataTable = $this->getDataTable('VisitorInterest_timeGap', $idSite, $period, $date, $segment);
- $dataTable->queueFilter('Sort', array('label', 'asc', true));
- $dataTable->queueFilter('BeautifyTimeRangeLabels', array(
- Piwik_Translate('VisitorInterest_BetweenXYSeconds'),
- Piwik_Translate('VisitorInterest_OneMinute'),
- Piwik_Translate('VisitorInterest_PlusXMin')));
- return $dataTable;
- }
+ static public function getInstance()
+ {
+ if (self::$instance == null) {
+ self::$instance = new self;
+ }
+ return self::$instance;
+ }
- public function getNumberOfVisitsPerPage( $idSite, $period, $date, $segment = false )
- {
- $dataTable = $this->getDataTable('VisitorInterest_pageGap', $idSite, $period, $date, $segment);
- $dataTable->queueFilter('Sort', array('label', 'asc', true));
- $dataTable->queueFilter('BeautifyRangeLabels', array(
- Piwik_Translate('VisitorInterest_OnePage'),
- Piwik_Translate('VisitorInterest_NPages')));
- return $dataTable;
- }
-
- /**
- * Returns a DataTable that associates counts of days (N) with the count of visits that
- * occurred within N days of the last visit.
- *
- * @param int $idSite The site to select data from.
- * @param string $period The period type.
- * @param string $date The date type.
- * @param string|bool $segment The segment.
- * @return Piwik_DataTable the archived report data.
- */
- public function getNumberOfVisitsByDaysSinceLast( $idSite, $period, $date, $segment = false )
- {
- $recordName = 'VisitorInterest_daysSinceLastVisit';
- $dataTable = $this->getDataTable(
- $recordName, $idSite, $period, $date, $segment, Piwik_Archive::INDEX_NB_VISITS);
+ protected function getDataTable($name, $idSite, $period, $date, $segment, $column = Piwik_Archive::INDEX_NB_VISITS)
+ {
+ Piwik::checkUserHasViewAccess($idSite);
+ $archive = Piwik_Archive::build($idSite, $period, $date, $segment);
+ $dataTable = $archive->getDataTable($name);
+ $dataTable->queueFilter('ReplaceColumnNames');
+ return $dataTable;
+ }
- $dataTable->queueFilter('BeautifyRangeLabels', array(
- Piwik_Translate('General_OneDay'), Piwik_Translate('General_NDays')));
-
- return $dataTable;
- }
-
- /**
- * Returns a DataTable that associates ranges of visit numbers with the count of visits
- * whose visit number falls within those ranges.
- *
- * @param int $idSite The site to select data from.
- * @param string $period The period type.
- * @param string $date The date type.
- * @param string|bool $segment The segment.
- * @return Piwik_DataTable the archived report data.
- */
- public function getNumberOfVisitsByVisitCount( $idSite, $period, $date, $segment = false )
- {
- $dataTable = $this->getDataTable(
- 'VisitorInterest_visitsByVisitCount', $idSite, $period, $date, $segment, Piwik_Archive::INDEX_NB_VISITS);
+ public function getNumberOfVisitsPerVisitDuration($idSite, $period, $date, $segment = false)
+ {
+ $dataTable = $this->getDataTable('VisitorInterest_timeGap', $idSite, $period, $date, $segment);
+ $dataTable->queueFilter('Sort', array('label', 'asc', true));
+ $dataTable->queueFilter('BeautifyTimeRangeLabels', array(
+ Piwik_Translate('VisitorInterest_BetweenXYSeconds'),
+ Piwik_Translate('VisitorInterest_OneMinute'),
+ Piwik_Translate('VisitorInterest_PlusXMin')));
+ return $dataTable;
+ }
- $dataTable->queueFilter('BeautifyRangeLabels', array(
- Piwik_Translate('General_OneVisit'), Piwik_Translate('General_NVisits')));
+ public function getNumberOfVisitsPerPage($idSite, $period, $date, $segment = false)
+ {
+ $dataTable = $this->getDataTable('VisitorInterest_pageGap', $idSite, $period, $date, $segment);
+ $dataTable->queueFilter('Sort', array('label', 'asc', true));
+ $dataTable->queueFilter('BeautifyRangeLabels', array(
+ Piwik_Translate('VisitorInterest_OnePage'),
+ Piwik_Translate('VisitorInterest_NPages')));
+ return $dataTable;
+ }
- // add visit percent column
- self::addVisitsPercentColumn($dataTable);
+ /**
+ * Returns a DataTable that associates counts of days (N) with the count of visits that
+ * occurred within N days of the last visit.
+ *
+ * @param int $idSite The site to select data from.
+ * @param string $period The period type.
+ * @param string $date The date type.
+ * @param string|bool $segment The segment.
+ * @return Piwik_DataTable the archived report data.
+ */
+ public function getNumberOfVisitsByDaysSinceLast($idSite, $period, $date, $segment = false)
+ {
+ $recordName = 'VisitorInterest_daysSinceLastVisit';
+ $dataTable = $this->getDataTable(
+ $recordName, $idSite, $period, $date, $segment, Piwik_Archive::INDEX_NB_VISITS);
- return $dataTable;
- }
+ $dataTable->queueFilter('BeautifyRangeLabels', array(
+ Piwik_Translate('General_OneDay'), Piwik_Translate('General_NDays')));
- /**
- * Utility function that adds a visit percent column to a data table,
- * regardless of whether the data table is an data table array or just
- * a data table.
- *
- * @param Piwik_DataTable $dataTable The data table to modify.
- */
- private static function addVisitsPercentColumn( $dataTable )
- {
- if ($dataTable instanceof Piwik_DataTable_Array)
- {
- foreach($dataTable->getArray() as $table)
- {
- self::addVisitsPercentColumn($table);
- }
- }
- else
- {
- $totalVisits = array_sum($dataTable->getColumn(Piwik_Archive::INDEX_NB_VISITS));
- $dataTable->queueFilter('ColumnCallbackAddColumnPercentage', array(
- 'nb_visits_percentage', 'nb_visits', $totalVisits));
- }
- }
+ return $dataTable;
+ }
+
+ /**
+ * Returns a DataTable that associates ranges of visit numbers with the count of visits
+ * whose visit number falls within those ranges.
+ *
+ * @param int $idSite The site to select data from.
+ * @param string $period The period type.
+ * @param string $date The date type.
+ * @param string|bool $segment The segment.
+ * @return Piwik_DataTable the archived report data.
+ */
+ public function getNumberOfVisitsByVisitCount($idSite, $period, $date, $segment = false)
+ {
+ $dataTable = $this->getDataTable(
+ 'VisitorInterest_visitsByVisitCount', $idSite, $period, $date, $segment, Piwik_Archive::INDEX_NB_VISITS);
+
+ $dataTable->queueFilter('BeautifyRangeLabels', array(
+ Piwik_Translate('General_OneVisit'), Piwik_Translate('General_NVisits')));
+
+ // add visit percent column
+ self::addVisitsPercentColumn($dataTable);
+
+ return $dataTable;
+ }
+
+ /**
+ * Utility function that adds a visit percent column to a data table,
+ * regardless of whether the data table is an data table array or just
+ * a data table.
+ *
+ * @param Piwik_DataTable $dataTable The data table to modify.
+ */
+ private static function addVisitsPercentColumn($dataTable)
+ {
+ if ($dataTable instanceof Piwik_DataTable_Array) {
+ foreach ($dataTable->getArray() as $table) {
+ self::addVisitsPercentColumn($table);
+ }
+ } else {
+ $totalVisits = array_sum($dataTable->getColumn(Piwik_Archive::INDEX_NB_VISITS));
+ $dataTable->queueFilter('ColumnCallbackAddColumnPercentage', array(
+ 'nb_visits_percentage', 'nb_visits', $totalVisits));
+ }
+ }
}
diff --git a/plugins/VisitorInterest/Controller.php b/plugins/VisitorInterest/Controller.php
index 6acef00f43..ef50ad5cac 100644
--- a/plugins/VisitorInterest/Controller.php
+++ b/plugins/VisitorInterest/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_VisitorInterest
*/
@@ -12,101 +12,101 @@
/**
* @package Piwik_VisitorInterest
*/
-class Piwik_VisitorInterest_Controller extends Piwik_Controller
+class Piwik_VisitorInterest_Controller extends Piwik_Controller
{
- function index()
- {
- $view = Piwik_View::factory('index');
- $view->dataTableNumberOfVisitsPerVisitDuration = $this->getNumberOfVisitsPerVisitDuration(true);
- $view->dataTableNumberOfVisitsPerPage = $this->getNumberOfVisitsPerPage(true);
- $view->dataTableNumberOfVisitsByVisitNum = $this->getNumberOfVisitsByVisitCount(true);
- $view->dataTableNumberOfVisitsByDaysSinceLast = $this->getNumberOfVisitsByDaysSinceLast(true);
- echo $view->render();
- }
-
- function getNumberOfVisitsPerVisitDuration( $fetch = false)
- {
- $view = Piwik_ViewDataTable::factory( 'cloud' );
- $view->init( $this->pluginName, __FUNCTION__, "VisitorInterest.getNumberOfVisitsPerVisitDuration" );
-
- $view->setColumnsToDisplay( array('label','nb_visits') );
- $view->setSortedColumn( 'label', 'asc' );
- $view->setColumnTranslation('label', Piwik_Translate('VisitorInterest_ColumnVisitDuration'));
- $view->setGraphLimit(10);
- $view->disableSort();
- $view->disableExcludeLowPopulation();
- $view->disableOffsetInformationAndPaginationControls();
- $view->disableSearchBox();
- $view->disableShowAllColumns();
-
- return $this->renderView($view, $fetch);
- }
-
- function getNumberOfVisitsPerPage( $fetch = false)
- {
- $view = Piwik_ViewDataTable::factory( 'cloud' );
- $view->init( $this->pluginName, __FUNCTION__, "VisitorInterest.getNumberOfVisitsPerPage" );
- $view->setColumnsToDisplay( array('label','nb_visits') );
- $view->setSortedColumn( 'label', 'asc' );
- $view->setColumnTranslation('label', Piwik_Translate('VisitorInterest_ColumnPagesPerVisit'));
- $view->setGraphLimit(10);
- $view->disableExcludeLowPopulation();
- $view->disableOffsetInformationAndPaginationControls();
- $view->disableSearchBox();
- $view->disableSort();
- $view->disableShowAllColumns();
-
- return $this->renderView($view, $fetch);
- }
-
- /**
- * Returns a report that lists the count of visits for different ranges of
- * a visitor's visit number.
- *
- * @param bool $fetch Whether to return the rendered view as a string or echo it.
- * @return string The rendered report or nothing if $fetch is set to false.
- */
- public function getNumberOfVisitsByVisitCount( $fetch = false )
- {
- $view = Piwik_ViewDataTable::factory( );
- $view->init( $this->pluginName, __FUNCTION__, "VisitorInterest.getNumberOfVisitsByVisitCount" );
- $view->setColumnsToDisplay( array('label', 'nb_visits', 'nb_visits_percentage') );
- $view->setSortedColumn('label', 'asc');
- $view->setColumnTranslation('label', Piwik_Translate('VisitorInterest_VisitNum'));
- $view->setColumnTranslation('nb_visits_percentage', str_replace(' ', '&nbsp;', Piwik_Translate('General_ColumnPercentageVisits')));
- $view->disableExcludeLowPopulation();
- $view->disableOffsetInformationAndPaginationControls();
- $view->disableShowAllViewsIcons();
- $view->setLimit(15);
- $view->disableSearchBox();
- $view->disableSort();
- $view->disableShowAllColumns();
-
- return $this->renderView($view, $fetch);
- }
-
- /**
- * Returns a rendered report that lists the count of visits for different ranges
- * of days since a visitor's last visit.
- *
- * @param bool $fetch Whether to return the rendered view as a string or echo it.
- * @return string The rendered report or nothing if $fetch is set to false.
- */
- public function getNumberOfVisitsByDaysSinceLast( $fetch = false )
- {
- $view = Piwik_ViewDataTable::factory( );
- $view->init( $this->pluginName, __FUNCTION__, 'VisitorInterest.getNumberOfVisitsByDaysSinceLast' );
- $view->setColumnsToDisplay( array('label', 'nb_visits') );
- $view->setSortedColumn('label', 'asc');
- $view->setColumnTranslation('label', Piwik_Translate('General_DaysSinceLastVisit'));
- $view->disableExcludeLowPopulation();
- $view->disableOffsetInformationAndPaginationControls();
- $view->disableShowAllViewsIcons();
- $view->setLimit(15);
- $view->disableSearchBox();
- $view->disableSort();
- $view->disableShowAllColumns();
-
- return $this->renderView($view, $fetch);
- }
+ function index()
+ {
+ $view = Piwik_View::factory('index');
+ $view->dataTableNumberOfVisitsPerVisitDuration = $this->getNumberOfVisitsPerVisitDuration(true);
+ $view->dataTableNumberOfVisitsPerPage = $this->getNumberOfVisitsPerPage(true);
+ $view->dataTableNumberOfVisitsByVisitNum = $this->getNumberOfVisitsByVisitCount(true);
+ $view->dataTableNumberOfVisitsByDaysSinceLast = $this->getNumberOfVisitsByDaysSinceLast(true);
+ echo $view->render();
+ }
+
+ function getNumberOfVisitsPerVisitDuration($fetch = false)
+ {
+ $view = Piwik_ViewDataTable::factory('cloud');
+ $view->init($this->pluginName, __FUNCTION__, "VisitorInterest.getNumberOfVisitsPerVisitDuration");
+
+ $view->setColumnsToDisplay(array('label', 'nb_visits'));
+ $view->setSortedColumn('label', 'asc');
+ $view->setColumnTranslation('label', Piwik_Translate('VisitorInterest_ColumnVisitDuration'));
+ $view->setGraphLimit(10);
+ $view->disableSort();
+ $view->disableExcludeLowPopulation();
+ $view->disableOffsetInformationAndPaginationControls();
+ $view->disableSearchBox();
+ $view->disableShowAllColumns();
+
+ return $this->renderView($view, $fetch);
+ }
+
+ function getNumberOfVisitsPerPage($fetch = false)
+ {
+ $view = Piwik_ViewDataTable::factory('cloud');
+ $view->init($this->pluginName, __FUNCTION__, "VisitorInterest.getNumberOfVisitsPerPage");
+ $view->setColumnsToDisplay(array('label', 'nb_visits'));
+ $view->setSortedColumn('label', 'asc');
+ $view->setColumnTranslation('label', Piwik_Translate('VisitorInterest_ColumnPagesPerVisit'));
+ $view->setGraphLimit(10);
+ $view->disableExcludeLowPopulation();
+ $view->disableOffsetInformationAndPaginationControls();
+ $view->disableSearchBox();
+ $view->disableSort();
+ $view->disableShowAllColumns();
+
+ return $this->renderView($view, $fetch);
+ }
+
+ /**
+ * Returns a report that lists the count of visits for different ranges of
+ * a visitor's visit number.
+ *
+ * @param bool $fetch Whether to return the rendered view as a string or echo it.
+ * @return string The rendered report or nothing if $fetch is set to false.
+ */
+ public function getNumberOfVisitsByVisitCount($fetch = false)
+ {
+ $view = Piwik_ViewDataTable::factory();
+ $view->init($this->pluginName, __FUNCTION__, "VisitorInterest.getNumberOfVisitsByVisitCount");
+ $view->setColumnsToDisplay(array('label', 'nb_visits', 'nb_visits_percentage'));
+ $view->setSortedColumn('label', 'asc');
+ $view->setColumnTranslation('label', Piwik_Translate('VisitorInterest_VisitNum'));
+ $view->setColumnTranslation('nb_visits_percentage', str_replace(' ', '&nbsp;', Piwik_Translate('General_ColumnPercentageVisits')));
+ $view->disableExcludeLowPopulation();
+ $view->disableOffsetInformationAndPaginationControls();
+ $view->disableShowAllViewsIcons();
+ $view->setLimit(15);
+ $view->disableSearchBox();
+ $view->disableSort();
+ $view->disableShowAllColumns();
+
+ return $this->renderView($view, $fetch);
+ }
+
+ /**
+ * Returns a rendered report that lists the count of visits for different ranges
+ * of days since a visitor's last visit.
+ *
+ * @param bool $fetch Whether to return the rendered view as a string or echo it.
+ * @return string The rendered report or nothing if $fetch is set to false.
+ */
+ public function getNumberOfVisitsByDaysSinceLast($fetch = false)
+ {
+ $view = Piwik_ViewDataTable::factory();
+ $view->init($this->pluginName, __FUNCTION__, 'VisitorInterest.getNumberOfVisitsByDaysSinceLast');
+ $view->setColumnsToDisplay(array('label', 'nb_visits'));
+ $view->setSortedColumn('label', 'asc');
+ $view->setColumnTranslation('label', Piwik_Translate('General_DaysSinceLastVisit'));
+ $view->disableExcludeLowPopulation();
+ $view->disableOffsetInformationAndPaginationControls();
+ $view->disableShowAllViewsIcons();
+ $view->setLimit(15);
+ $view->disableSearchBox();
+ $view->disableSort();
+ $view->disableShowAllColumns();
+
+ return $this->renderView($view, $fetch);
+ }
}
diff --git a/plugins/VisitorInterest/VisitorInterest.php b/plugins/VisitorInterest/VisitorInterest.php
index d4895502ce..00689e5c2c 100644
--- a/plugins/VisitorInterest/VisitorInterest.php
+++ b/plugins/VisitorInterest/VisitorInterest.php
@@ -15,326 +15,321 @@
*/
class Piwik_VisitorInterest extends Piwik_Plugin
{
- public function getInformation()
- {
- $info = array(
- 'description' => Piwik_Translate('VisitorInterest_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' => 'addMenu',
- 'API.getReportMetadata' => 'getReportMetadata',
- );
- 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('VisitorInterest_WidgetLengths'),
- 'module' => 'VisitorInterest',
- 'action' => 'getNumberOfVisitsPerVisitDuration',
- 'dimension' => Piwik_Translate('VisitorInterest_ColumnVisitDuration'),
- 'metrics' => array( 'nb_visits' ),
- 'processedMetrics' => false,
- 'constantRowsCount' => true,
- 'documentation' => Piwik_Translate('VisitorInterest_WidgetLengthsDocumentation')
- .'<br />'.Piwik_Translate('General_ChangeTagCloudView'),
- 'order' => 15
- );
-
- $reports[] = array(
- 'category' => Piwik_Translate('General_Visitors'),
- 'name' => Piwik_Translate('VisitorInterest_WidgetPages'),
- 'module' => 'VisitorInterest',
- 'action' => 'getNumberOfVisitsPerPage',
- 'dimension' => Piwik_Translate('VisitorInterest_ColumnPagesPerVisit'),
- 'metrics' => array( 'nb_visits' ),
- 'processedMetrics' => false,
- 'constantRowsCount' => true,
- 'documentation' => Piwik_Translate('VisitorInterest_WidgetPagesDocumentation')
- .'<br />'.Piwik_Translate('General_ChangeTagCloudView'),
- 'order' => 20
- );
-
- $reports[] = array(
- 'category' => Piwik_Translate('General_Visitors'),
- 'name' => Piwik_Translate('VisitorInterest_visitsByVisitCount'),
- 'module' => 'VisitorInterest',
- 'action' => 'getNumberOfVisitsByVisitCount',
- 'dimension' => Piwik_Translate('VisitorInterest_visitsByVisitCount'),
- 'metrics' => array(
- 'nb_visits',
- 'nb_visits_percentage' => Piwik_Translate('General_ColumnPercentageVisits')
- ),
- 'processedMetrics' => false,
- 'constantRowsCount' => true,
- 'documentation' => Piwik_Translate('VisitorInterest_WidgetVisitsByNumDocumentation')
- .'<br />'.Piwik_Translate('General_ChangeTagCloudView'),
- 'order' => 25
- );
-
- $reports[] = array(
- 'category' => Piwik_Translate('General_Visitors'),
- 'name' => Piwik_Translate('VisitorInterest_VisitsByDaysSinceLast'),
- 'module' => 'VisitorInterest',
- 'action' => 'getNumberOfVisitsByDaysSinceLast',
- 'dimension' => Piwik_Translate('VisitorInterest_VisitsByDaysSinceLast'),
- 'metrics' => array( 'nb_visits' ),
- 'processedMetrics' => false,
- 'constantRowsCount' => true,
- 'documentation' => Piwik_Translate('VisitorInterest_WidgetVisitsByDaysSinceLastDocumentation'),
- 'order' => 30
- );
- }
-
- function addWidgets()
- {
- Piwik_AddWidget( 'General_Visitors', 'VisitorInterest_WidgetLengths', 'VisitorInterest', 'getNumberOfVisitsPerVisitDuration');
- Piwik_AddWidget( 'General_Visitors', 'VisitorInterest_WidgetPages', 'VisitorInterest', 'getNumberOfVisitsPerPage');
- Piwik_AddWidget( 'General_Visitors', 'VisitorInterest_visitsByVisitCount', 'VisitorInterest', 'getNumberOfVisitsByVisitCount');
- Piwik_AddWidget( 'General_Visitors', 'VisitorInterest_WidgetVisitsByDaysSinceLast', 'VisitorInterest', 'getNumberOfVisitsByDaysSinceLast');
- }
-
- function addMenu()
- {
- Piwik_RenameMenuEntry('General_Visitors', 'VisitFrequency_SubmenuFrequency',
- 'General_Visitors', 'VisitorInterest_Engagement' );
- }
-
- function postLoad()
- {
- Piwik_AddAction('template_headerVisitsFrequency', array('Piwik_VisitorInterest','headerVisitsFrequency'));
- Piwik_AddAction('template_footerVisitsFrequency', array('Piwik_VisitorInterest','footerVisitsFrequency'));
- }
-
- // third element is unit (s for seconds, default is munutes)
- protected static $timeGap = array(
- array(0, 10, 's'),
- array(11, 30, 's'),
- array(31, 60, 's'),
- array(1, 2),
- array(2, 4),
- array(4, 7),
- array(7, 10),
- array(10, 15),
- array(15, 30),
- array(30)
- );
-
- protected static $pageGap = array(
- array(1, 1),
- array(2, 2),
- array(3, 3),
- array(4, 4),
- array(5, 5),
- array(6, 7),
- array(8, 10),
- array(11, 14),
- array(15, 20),
- array(20)
- );
-
- /**
- * The set of ranges used when calculating the 'visitors who visited at least N times' report.
- */
- protected static $visitNumberGap = array(
- array(1, 1),
- array(2, 2),
- array(3, 3),
- array(4, 4),
- array(5, 5),
- array(6, 6),
- array(7, 7),
- array(8, 8),
- array(9, 14),
- array(15, 25),
- array(26, 50),
- array(51, 100),
- array(101, 200),
- array(200)
- );
-
- /**
- * The set of ranges used when calculating the 'days since last visit' report.
- */
- protected static $daysSinceLastVisitGap = array(
- array(0, 0),
- array(1, 1),
- array(2, 2),
- array(3, 3),
- array(4, 4),
- array(5, 5),
- array(6, 6),
- array(7, 7),
- array(8, 14),
- array(15, 30),
- array(31, 60),
- array(61, 120),
- array(121, 364),
- array(364)
- );
-
- /**
- * @param Piwik_Event_Notification $notification notification object
- * @return mixed
- */
- function archivePeriod( $notification )
- {
- $archiveProcessing = $notification->getNotificationObject();
-
- if(!$archiveProcessing->shouldProcessReportsForPlugin($this->getPluginName())) return;
-
- $dataTableToSum = array(
- 'VisitorInterest_timeGap',
- 'VisitorInterest_pageGap',
- 'VisitorInterest_visitsByVisitCount',
- 'VisitorInterest_daysSinceLastVisit'
- );
- $archiveProcessing->archiveDataTable($dataTableToSum);
- }
-
- /**
- * @param Piwik_Event_Notification $notification notification object
- * @return mixed
- */
- public function archiveDay( $notification )
- {
- $this->archiveProcessing = $notification->getNotificationObject();
-
- if(!$this->archiveProcessing->shouldProcessReportsForPlugin($this->getPluginName())) return;
-
- // these prefixes are prepended to the 'SELECT as' parts of each SELECT expression. detecting
- // these prefixes allows us to get all the data in one query.
- $timeGapPrefix = 'tg';
- $pageGapPrefix = 'pg';
- $visitsByVisitNumPrefix = 'vbvn';
- $daysSinceLastVisitPrefix = 'dslv';
-
- // extra condition for the SQL SELECT that makes sure only returning visits are counted
- // when creating the 'days since last visit' report. the SELECT expression below it
- // is used to count all new visits.
- $daysSinceLastExtraCondition = 'and log_visit.visitor_returning = 1';
- $selectAs = $daysSinceLastVisitPrefix.'General_NewVisits';
- $newVisitCountSelect = "sum(case when log_visit.visitor_returning = 0 then 1 else 0 end) as `$selectAs`";
-
- // create the select expressions to use
- $timeGapSelects = Piwik_ArchiveProcessing_Day::buildReduceByRangeSelect(
- 'visit_total_time', self::getSecondsGap(), 'log_visit', $timeGapPrefix);
- $pageGapSelects = Piwik_ArchiveProcessing_Day::buildReduceByRangeSelect(
- 'visit_total_actions', self::$pageGap, 'log_visit', $pageGapPrefix);
- $visitsByVisitNumSelects = Piwik_ArchiveProcessing_Day::buildReduceByRangeSelect(
- 'visitor_count_visits', self::$visitNumberGap, 'log_visit', $visitsByVisitNumPrefix);
-
- $daysSinceLastVisitSelects = Piwik_ArchiveProcessing_Day::buildReduceByRangeSelect(
- 'visitor_days_since_last', self::$daysSinceLastVisitGap, 'log_visit', $daysSinceLastVisitPrefix,
- $daysSinceLastExtraCondition);
- array_unshift($daysSinceLastVisitSelects, $newVisitCountSelect);
-
- $selects = array_merge(
- $timeGapSelects, $pageGapSelects, $visitsByVisitNumSelects, $daysSinceLastVisitSelects);
-
- // select data for every report
- $row = $this->archiveProcessing->queryVisitsSimple(implode(',', $selects));
-
- // archive visits by total time report
- $recordName = 'VisitorInterest_timeGap';
- $this->archiveRangeStats($recordName, $row, Piwik_Archive::INDEX_NB_VISITS, $timeGapPrefix);
-
- // archive visits by total actions report
- $recordName = 'VisitorInterest_pageGap';
- $this->archiveRangeStats($recordName, $row, Piwik_Archive::INDEX_NB_VISITS, $pageGapPrefix);
-
- // archive visits by visit number report
- $recordName = 'VisitorInterest_visitsByVisitCount';
- $this->archiveRangeStats($recordName, $row, Piwik_Archive::INDEX_NB_VISITS, $visitsByVisitNumPrefix);
-
- // archive days since last visit report
- $recordName = 'VisitorInterest_daysSinceLastVisit';
- $this->archiveRangeStats($recordName, $row, Piwik_Archive::INDEX_NB_VISITS, $daysSinceLastVisitPrefix);
- }
-
- /**
- * Transforms and returns the set of ranges used to calculate the 'visits by total time'
- * report from ranges in minutes to equivalent ranges in seconds.
- */
- protected static function getSecondsGap()
- {
- $secondsGap = array();
- foreach(self::$timeGap as $gap)
- {
- if (count($gap) == 3 && $gap[2] == 's') // if the units are already in seconds, just assign them
- {
- $secondsGap[] = array($gap[0], $gap[1]);
- }
- else if (count($gap) == 2)
- {
- $secondsGap[] = array($gap[0] * 60, $gap[1] * 60);
- }
- else
- {
- $secondsGap[] = array($gap[0] * 60);
- }
- }
- return $secondsGap;
- }
-
- /**
- * Creates and archives a DataTable from some (or all) elements of a supplied database
- * row.
- *
- * @param string $recordName The record name to use when inserting the new archive.
- * @param array $row The database row to use.
- * @param string $selectAsPrefix The string to look for as the prefix of SELECT as
- * expressions. Elements in $row that have a SELECT as
- * with this string as a prefix are used in creating
- * the DataTable.'
- */
- protected function archiveRangeStats($recordName, $row, $index, $selectAsPrefix)
- {
- // create the DataTable from parts of the result row
- $dataTable = $this->archiveProcessing->getSimpleDataTableFromRow($row, $index, $selectAsPrefix);
-
- // insert the data table as a blob archive
- $this->archiveProcessing->insertBlobRecord($recordName, $dataTable->getSerialized());
- destroy($dataTable);
- }
-
- /**
- * @param Piwik_Event_Notification $notification notification object
- */
- static public function headerVisitsFrequency($notification)
- {
- $out =& $notification->getNotificationObject();
- $out = '<div id="leftcolumn">';
- }
-
- /**
- * @param Piwik_Event_Notification $notification notification object
- */
- static public function footerVisitsFrequency($notification)
- {
- $out =& $notification->getNotificationObject();
- $out = '</div>
+ public function getInformation()
+ {
+ $info = array(
+ 'description' => Piwik_Translate('VisitorInterest_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' => 'addMenu',
+ 'API.getReportMetadata' => 'getReportMetadata',
+ );
+ 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('VisitorInterest_WidgetLengths'),
+ 'module' => 'VisitorInterest',
+ 'action' => 'getNumberOfVisitsPerVisitDuration',
+ 'dimension' => Piwik_Translate('VisitorInterest_ColumnVisitDuration'),
+ 'metrics' => array('nb_visits'),
+ 'processedMetrics' => false,
+ 'constantRowsCount' => true,
+ 'documentation' => Piwik_Translate('VisitorInterest_WidgetLengthsDocumentation')
+ . '<br />' . Piwik_Translate('General_ChangeTagCloudView'),
+ 'order' => 15
+ );
+
+ $reports[] = array(
+ 'category' => Piwik_Translate('General_Visitors'),
+ 'name' => Piwik_Translate('VisitorInterest_WidgetPages'),
+ 'module' => 'VisitorInterest',
+ 'action' => 'getNumberOfVisitsPerPage',
+ 'dimension' => Piwik_Translate('VisitorInterest_ColumnPagesPerVisit'),
+ 'metrics' => array('nb_visits'),
+ 'processedMetrics' => false,
+ 'constantRowsCount' => true,
+ 'documentation' => Piwik_Translate('VisitorInterest_WidgetPagesDocumentation')
+ . '<br />' . Piwik_Translate('General_ChangeTagCloudView'),
+ 'order' => 20
+ );
+
+ $reports[] = array(
+ 'category' => Piwik_Translate('General_Visitors'),
+ 'name' => Piwik_Translate('VisitorInterest_visitsByVisitCount'),
+ 'module' => 'VisitorInterest',
+ 'action' => 'getNumberOfVisitsByVisitCount',
+ 'dimension' => Piwik_Translate('VisitorInterest_visitsByVisitCount'),
+ 'metrics' => array(
+ 'nb_visits',
+ 'nb_visits_percentage' => Piwik_Translate('General_ColumnPercentageVisits')
+ ),
+ 'processedMetrics' => false,
+ 'constantRowsCount' => true,
+ 'documentation' => Piwik_Translate('VisitorInterest_WidgetVisitsByNumDocumentation')
+ . '<br />' . Piwik_Translate('General_ChangeTagCloudView'),
+ 'order' => 25
+ );
+
+ $reports[] = array(
+ 'category' => Piwik_Translate('General_Visitors'),
+ 'name' => Piwik_Translate('VisitorInterest_VisitsByDaysSinceLast'),
+ 'module' => 'VisitorInterest',
+ 'action' => 'getNumberOfVisitsByDaysSinceLast',
+ 'dimension' => Piwik_Translate('VisitorInterest_VisitsByDaysSinceLast'),
+ 'metrics' => array('nb_visits'),
+ 'processedMetrics' => false,
+ 'constantRowsCount' => true,
+ 'documentation' => Piwik_Translate('VisitorInterest_WidgetVisitsByDaysSinceLastDocumentation'),
+ 'order' => 30
+ );
+ }
+
+ function addWidgets()
+ {
+ Piwik_AddWidget('General_Visitors', 'VisitorInterest_WidgetLengths', 'VisitorInterest', 'getNumberOfVisitsPerVisitDuration');
+ Piwik_AddWidget('General_Visitors', 'VisitorInterest_WidgetPages', 'VisitorInterest', 'getNumberOfVisitsPerPage');
+ Piwik_AddWidget('General_Visitors', 'VisitorInterest_visitsByVisitCount', 'VisitorInterest', 'getNumberOfVisitsByVisitCount');
+ Piwik_AddWidget('General_Visitors', 'VisitorInterest_WidgetVisitsByDaysSinceLast', 'VisitorInterest', 'getNumberOfVisitsByDaysSinceLast');
+ }
+
+ function addMenu()
+ {
+ Piwik_RenameMenuEntry('General_Visitors', 'VisitFrequency_SubmenuFrequency',
+ 'General_Visitors', 'VisitorInterest_Engagement');
+ }
+
+ function postLoad()
+ {
+ Piwik_AddAction('template_headerVisitsFrequency', array('Piwik_VisitorInterest', 'headerVisitsFrequency'));
+ Piwik_AddAction('template_footerVisitsFrequency', array('Piwik_VisitorInterest', 'footerVisitsFrequency'));
+ }
+
+ // third element is unit (s for seconds, default is munutes)
+ protected static $timeGap = array(
+ array(0, 10, 's'),
+ array(11, 30, 's'),
+ array(31, 60, 's'),
+ array(1, 2),
+ array(2, 4),
+ array(4, 7),
+ array(7, 10),
+ array(10, 15),
+ array(15, 30),
+ array(30)
+ );
+
+ protected static $pageGap = array(
+ array(1, 1),
+ array(2, 2),
+ array(3, 3),
+ array(4, 4),
+ array(5, 5),
+ array(6, 7),
+ array(8, 10),
+ array(11, 14),
+ array(15, 20),
+ array(20)
+ );
+
+ /**
+ * The set of ranges used when calculating the 'visitors who visited at least N times' report.
+ */
+ protected static $visitNumberGap = array(
+ array(1, 1),
+ array(2, 2),
+ array(3, 3),
+ array(4, 4),
+ array(5, 5),
+ array(6, 6),
+ array(7, 7),
+ array(8, 8),
+ array(9, 14),
+ array(15, 25),
+ array(26, 50),
+ array(51, 100),
+ array(101, 200),
+ array(200)
+ );
+
+ /**
+ * The set of ranges used when calculating the 'days since last visit' report.
+ */
+ protected static $daysSinceLastVisitGap = array(
+ array(0, 0),
+ array(1, 1),
+ array(2, 2),
+ array(3, 3),
+ array(4, 4),
+ array(5, 5),
+ array(6, 6),
+ array(7, 7),
+ array(8, 14),
+ array(15, 30),
+ array(31, 60),
+ array(61, 120),
+ array(121, 364),
+ array(364)
+ );
+
+ /**
+ * @param Piwik_Event_Notification $notification notification object
+ * @return mixed
+ */
+ function archivePeriod($notification)
+ {
+ $archiveProcessing = $notification->getNotificationObject();
+
+ if (!$archiveProcessing->shouldProcessReportsForPlugin($this->getPluginName())) return;
+
+ $dataTableToSum = array(
+ 'VisitorInterest_timeGap',
+ 'VisitorInterest_pageGap',
+ 'VisitorInterest_visitsByVisitCount',
+ 'VisitorInterest_daysSinceLastVisit'
+ );
+ $archiveProcessing->archiveDataTable($dataTableToSum);
+ }
+
+ /**
+ * @param Piwik_Event_Notification $notification notification object
+ * @return mixed
+ */
+ public function archiveDay($notification)
+ {
+ $this->archiveProcessing = $notification->getNotificationObject();
+
+ if (!$this->archiveProcessing->shouldProcessReportsForPlugin($this->getPluginName())) return;
+
+ // these prefixes are prepended to the 'SELECT as' parts of each SELECT expression. detecting
+ // these prefixes allows us to get all the data in one query.
+ $timeGapPrefix = 'tg';
+ $pageGapPrefix = 'pg';
+ $visitsByVisitNumPrefix = 'vbvn';
+ $daysSinceLastVisitPrefix = 'dslv';
+
+ // extra condition for the SQL SELECT that makes sure only returning visits are counted
+ // when creating the 'days since last visit' report. the SELECT expression below it
+ // is used to count all new visits.
+ $daysSinceLastExtraCondition = 'and log_visit.visitor_returning = 1';
+ $selectAs = $daysSinceLastVisitPrefix . 'General_NewVisits';
+ $newVisitCountSelect = "sum(case when log_visit.visitor_returning = 0 then 1 else 0 end) as `$selectAs`";
+
+ // create the select expressions to use
+ $timeGapSelects = Piwik_ArchiveProcessing_Day::buildReduceByRangeSelect(
+ 'visit_total_time', self::getSecondsGap(), 'log_visit', $timeGapPrefix);
+ $pageGapSelects = Piwik_ArchiveProcessing_Day::buildReduceByRangeSelect(
+ 'visit_total_actions', self::$pageGap, 'log_visit', $pageGapPrefix);
+ $visitsByVisitNumSelects = Piwik_ArchiveProcessing_Day::buildReduceByRangeSelect(
+ 'visitor_count_visits', self::$visitNumberGap, 'log_visit', $visitsByVisitNumPrefix);
+
+ $daysSinceLastVisitSelects = Piwik_ArchiveProcessing_Day::buildReduceByRangeSelect(
+ 'visitor_days_since_last', self::$daysSinceLastVisitGap, 'log_visit', $daysSinceLastVisitPrefix,
+ $daysSinceLastExtraCondition);
+ array_unshift($daysSinceLastVisitSelects, $newVisitCountSelect);
+
+ $selects = array_merge(
+ $timeGapSelects, $pageGapSelects, $visitsByVisitNumSelects, $daysSinceLastVisitSelects);
+
+ // select data for every report
+ $row = $this->archiveProcessing->queryVisitsSimple(implode(',', $selects));
+
+ // archive visits by total time report
+ $recordName = 'VisitorInterest_timeGap';
+ $this->archiveRangeStats($recordName, $row, Piwik_Archive::INDEX_NB_VISITS, $timeGapPrefix);
+
+ // archive visits by total actions report
+ $recordName = 'VisitorInterest_pageGap';
+ $this->archiveRangeStats($recordName, $row, Piwik_Archive::INDEX_NB_VISITS, $pageGapPrefix);
+
+ // archive visits by visit number report
+ $recordName = 'VisitorInterest_visitsByVisitCount';
+ $this->archiveRangeStats($recordName, $row, Piwik_Archive::INDEX_NB_VISITS, $visitsByVisitNumPrefix);
+
+ // archive days since last visit report
+ $recordName = 'VisitorInterest_daysSinceLastVisit';
+ $this->archiveRangeStats($recordName, $row, Piwik_Archive::INDEX_NB_VISITS, $daysSinceLastVisitPrefix);
+ }
+
+ /**
+ * Transforms and returns the set of ranges used to calculate the 'visits by total time'
+ * report from ranges in minutes to equivalent ranges in seconds.
+ */
+ protected static function getSecondsGap()
+ {
+ $secondsGap = array();
+ foreach (self::$timeGap as $gap) {
+ if (count($gap) == 3 && $gap[2] == 's') // if the units are already in seconds, just assign them
+ {
+ $secondsGap[] = array($gap[0], $gap[1]);
+ } else if (count($gap) == 2) {
+ $secondsGap[] = array($gap[0] * 60, $gap[1] * 60);
+ } else {
+ $secondsGap[] = array($gap[0] * 60);
+ }
+ }
+ return $secondsGap;
+ }
+
+ /**
+ * Creates and archives a DataTable from some (or all) elements of a supplied database
+ * row.
+ *
+ * @param string $recordName The record name to use when inserting the new archive.
+ * @param array $row The database row to use.
+ * @param string $selectAsPrefix The string to look for as the prefix of SELECT as
+ * expressions. Elements in $row that have a SELECT as
+ * with this string as a prefix are used in creating
+ * the DataTable.'
+ */
+ protected function archiveRangeStats($recordName, $row, $index, $selectAsPrefix)
+ {
+ // create the DataTable from parts of the result row
+ $dataTable = $this->archiveProcessing->getSimpleDataTableFromRow($row, $index, $selectAsPrefix);
+
+ // insert the data table as a blob archive
+ $this->archiveProcessing->insertBlobRecord($recordName, $dataTable->getSerialized());
+ destroy($dataTable);
+ }
+
+ /**
+ * @param Piwik_Event_Notification $notification notification object
+ */
+ static public function headerVisitsFrequency($notification)
+ {
+ $out =& $notification->getNotificationObject();
+ $out = '<div id="leftcolumn">';
+ }
+
+ /**
+ * @param Piwik_Event_Notification $notification notification object
+ */
+ static public function footerVisitsFrequency($notification)
+ {
+ $out =& $notification->getNotificationObject();
+ $out = '</div>
<div id="rightcolumn">
';
- $out .= Piwik_FrontController::getInstance()->fetchDispatch('VisitorInterest','index');
- $out .= '</div>';
- }
+ $out .= Piwik_FrontController::getInstance()->fetchDispatch('VisitorInterest', 'index');
+ $out .= '</div>';
+ }
}
diff --git a/plugins/VisitorInterest/templates/index.tpl b/plugins/VisitorInterest/templates/index.tpl
index 22e3fa2421..6ba4cc6c5a 100644
--- a/plugins/VisitorInterest/templates/index.tpl
+++ b/plugins/VisitorInterest/templates/index.tpl
@@ -1,4 +1,3 @@
-
<h2>{'VisitorInterest_VisitsPerDuration'|translate}</h2>
{$dataTableNumberOfVisitsPerVisitDuration}
diff --git a/plugins/VisitsSummary/API.php b/plugins/VisitsSummary/API.php
index 9294e03b74..0864c517b5 100644
--- a/plugins/VisitsSummary/API.php
+++ b/plugins/VisitsSummary/API.php
@@ -17,146 +17,136 @@
*/
class Piwik_VisitsSummary_API
{
- static private $instance = null;
- /**
- * @return Piwik_VisitsSummary_API
- */
- static public function getInstance()
- {
- if (self::$instance == null)
- {
- self::$instance = new self;
- }
- return self::$instance;
- }
-
- public function get( $idSite, $period, $date, $segment = false, $columns = false)
- {
- Piwik::checkUserHasViewAccess( $idSite );
- $archive = Piwik_Archive::build($idSite, $period, $date, $segment );
-
- // array values are comma separated
- $columns = Piwik::getArrayFromApiParameter($columns);
- $tempColumns = array();
-
- $bounceRateRequested = $actionsPerVisitRequested = $averageVisitDurationRequested = false;
- if(!empty($columns))
- {
- // make sure base metrics are there for processed metrics
- if(false !== ($bounceRateRequested = array_search('bounce_rate', $columns)))
- {
- if (!in_array('nb_visits', $columns)) $tempColumns[] = 'nb_visits';
- if (!in_array('bounce_count', $columns)) $tempColumns[] = 'bounce_count';
- unset($columns[$bounceRateRequested]);
- }
- if(false !== ($actionsPerVisitRequested = array_search('nb_actions_per_visit', $columns)))
- {
- if (!in_array('nb_visits', $columns)) $tempColumns[] = 'nb_visits';
- if (!in_array('nb_actions', $columns)) $tempColumns[] = 'nb_actions';
- unset($columns[$actionsPerVisitRequested]);
- }
- if(false !== ($averageVisitDurationRequested = array_search('avg_time_on_site', $columns)))
- {
- if (!in_array('nb_visits', $columns)) $tempColumns[] = 'nb_visits';
- if (!in_array('sum_visit_length', $columns)) $tempColumns[] = 'sum_visit_length';
- unset($columns[$averageVisitDurationRequested]);
- }
- $tempColumns = array_unique($tempColumns);
- rsort($tempColumns);
- $columns = array_merge($columns, $tempColumns);
- }
- else
- {
- $bounceRateRequested = $actionsPerVisitRequested = $averageVisitDurationRequested = true;
- $columns = array(
- 'nb_visits',
- 'nb_actions',
- 'nb_visits_converted',
- 'bounce_count',
- 'sum_visit_length',
- 'max_actions'
- );
- if(Piwik::isUniqueVisitorsEnabled($period))
- {
- $columns = array_merge(array('nb_uniq_visitors'), $columns);
- }
- // Force reindex from 0 to N otherwise the SQL bind will fail
- $columns = array_values($columns);
- }
-
- $dataTable = $archive->getDataTableFromNumeric($columns);
-
- // Process ratio metrics from base metrics, when requested
- if($bounceRateRequested !== false)
- {
- $dataTable->filter('ColumnCallbackAddColumnPercentage', array('bounce_rate', 'bounce_count', 'nb_visits', 0));
- }
- if($actionsPerVisitRequested !== false)
- {
- $dataTable->filter('ColumnCallbackAddColumnQuotient', array('nb_actions_per_visit', 'nb_actions', 'nb_visits', 1));
- }
- if($averageVisitDurationRequested !== false)
- {
- $dataTable->filter('ColumnCallbackAddColumnQuotient', array('avg_time_on_site', 'sum_visit_length', 'nb_visits', 0));
- }
-
- // remove temp metrics that were used to compute processed metrics
- $dataTable->deleteColumns($tempColumns);
-
- return $dataTable;
- }
-
- protected function getNumeric( $idSite, $period, $date, $segment, $toFetch )
- {
- Piwik::checkUserHasViewAccess( $idSite );
- $archive = Piwik_Archive::build($idSite, $period, $date, $segment );
- $dataTable = $archive->getNumeric($toFetch);
- return $dataTable;
- }
-
- public function getVisits( $idSite, $period, $date, $segment = false )
- {
- return $this->getNumeric( $idSite, $period, $date, $segment, 'nb_visits');
- }
-
- public function getUniqueVisitors( $idSite, $period, $date, $segment = false )
- {
- return $this->getNumeric( $idSite, $period, $date, $segment, 'nb_uniq_visitors');
- }
-
- public function getActions( $idSite, $period, $date, $segment = false )
- {
- return $this->getNumeric( $idSite, $period, $date, $segment, 'nb_actions');
- }
-
- public function getMaxActions( $idSite, $period, $date, $segment = false )
- {
- return $this->getNumeric( $idSite, $period, $date, $segment, 'max_actions');
- }
-
- public function getBounceCount( $idSite, $period, $date, $segment = false )
- {
- return $this->getNumeric( $idSite, $period, $date, $segment, 'bounce_count');
- }
-
- public function getVisitsConverted( $idSite, $period, $date, $segment = false )
- {
- return $this->getNumeric( $idSite, $period, $date, $segment, 'nb_visits_converted');
- }
-
- public function getSumVisitsLength( $idSite, $period, $date, $segment = false )
- {
- return $this->getNumeric( $idSite, $period, $date, $segment, 'sum_visit_length');
- }
-
- public function getSumVisitsLengthPretty( $idSite, $period, $date, $segment = false )
- {
- $table = $this->getSumVisitsLength( $idSite, $period, $date, $segment );
- if($table instanceof Piwik_DataTable_Array) {
- $table->filter('ColumnCallbackReplace', array(0, array('Piwik', 'getPrettyTimeFromSeconds')));
- } else {
- $table = Piwik::getPrettyTimeFromSeconds($table);
- }
- return $table;
- }
+ static private $instance = null;
+
+ /**
+ * @return Piwik_VisitsSummary_API
+ */
+ static public function getInstance()
+ {
+ if (self::$instance == null) {
+ self::$instance = new self;
+ }
+ return self::$instance;
+ }
+
+ public function get($idSite, $period, $date, $segment = false, $columns = false)
+ {
+ Piwik::checkUserHasViewAccess($idSite);
+ $archive = Piwik_Archive::build($idSite, $period, $date, $segment);
+
+ // array values are comma separated
+ $columns = Piwik::getArrayFromApiParameter($columns);
+ $tempColumns = array();
+
+ $bounceRateRequested = $actionsPerVisitRequested = $averageVisitDurationRequested = false;
+ if (!empty($columns)) {
+ // make sure base metrics are there for processed metrics
+ if (false !== ($bounceRateRequested = array_search('bounce_rate', $columns))) {
+ if (!in_array('nb_visits', $columns)) $tempColumns[] = 'nb_visits';
+ if (!in_array('bounce_count', $columns)) $tempColumns[] = 'bounce_count';
+ unset($columns[$bounceRateRequested]);
+ }
+ if (false !== ($actionsPerVisitRequested = array_search('nb_actions_per_visit', $columns))) {
+ if (!in_array('nb_visits', $columns)) $tempColumns[] = 'nb_visits';
+ if (!in_array('nb_actions', $columns)) $tempColumns[] = 'nb_actions';
+ unset($columns[$actionsPerVisitRequested]);
+ }
+ if (false !== ($averageVisitDurationRequested = array_search('avg_time_on_site', $columns))) {
+ if (!in_array('nb_visits', $columns)) $tempColumns[] = 'nb_visits';
+ if (!in_array('sum_visit_length', $columns)) $tempColumns[] = 'sum_visit_length';
+ unset($columns[$averageVisitDurationRequested]);
+ }
+ $tempColumns = array_unique($tempColumns);
+ rsort($tempColumns);
+ $columns = array_merge($columns, $tempColumns);
+ } else {
+ $bounceRateRequested = $actionsPerVisitRequested = $averageVisitDurationRequested = true;
+ $columns = array(
+ 'nb_visits',
+ 'nb_actions',
+ 'nb_visits_converted',
+ 'bounce_count',
+ 'sum_visit_length',
+ 'max_actions'
+ );
+ if (Piwik::isUniqueVisitorsEnabled($period)) {
+ $columns = array_merge(array('nb_uniq_visitors'), $columns);
+ }
+ // Force reindex from 0 to N otherwise the SQL bind will fail
+ $columns = array_values($columns);
+ }
+
+ $dataTable = $archive->getDataTableFromNumeric($columns);
+
+ // Process ratio metrics from base metrics, when requested
+ if ($bounceRateRequested !== false) {
+ $dataTable->filter('ColumnCallbackAddColumnPercentage', array('bounce_rate', 'bounce_count', 'nb_visits', 0));
+ }
+ if ($actionsPerVisitRequested !== false) {
+ $dataTable->filter('ColumnCallbackAddColumnQuotient', array('nb_actions_per_visit', 'nb_actions', 'nb_visits', 1));
+ }
+ if ($averageVisitDurationRequested !== false) {
+ $dataTable->filter('ColumnCallbackAddColumnQuotient', array('avg_time_on_site', 'sum_visit_length', 'nb_visits', 0));
+ }
+
+ // remove temp metrics that were used to compute processed metrics
+ $dataTable->deleteColumns($tempColumns);
+
+ return $dataTable;
+ }
+
+ protected function getNumeric($idSite, $period, $date, $segment, $toFetch)
+ {
+ Piwik::checkUserHasViewAccess($idSite);
+ $archive = Piwik_Archive::build($idSite, $period, $date, $segment);
+ $dataTable = $archive->getNumeric($toFetch);
+ return $dataTable;
+ }
+
+ public function getVisits($idSite, $period, $date, $segment = false)
+ {
+ return $this->getNumeric($idSite, $period, $date, $segment, 'nb_visits');
+ }
+
+ public function getUniqueVisitors($idSite, $period, $date, $segment = false)
+ {
+ return $this->getNumeric($idSite, $period, $date, $segment, 'nb_uniq_visitors');
+ }
+
+ public function getActions($idSite, $period, $date, $segment = false)
+ {
+ return $this->getNumeric($idSite, $period, $date, $segment, 'nb_actions');
+ }
+
+ public function getMaxActions($idSite, $period, $date, $segment = false)
+ {
+ return $this->getNumeric($idSite, $period, $date, $segment, 'max_actions');
+ }
+
+ public function getBounceCount($idSite, $period, $date, $segment = false)
+ {
+ return $this->getNumeric($idSite, $period, $date, $segment, 'bounce_count');
+ }
+
+ public function getVisitsConverted($idSite, $period, $date, $segment = false)
+ {
+ return $this->getNumeric($idSite, $period, $date, $segment, 'nb_visits_converted');
+ }
+
+ public function getSumVisitsLength($idSite, $period, $date, $segment = false)
+ {
+ return $this->getNumeric($idSite, $period, $date, $segment, 'sum_visit_length');
+ }
+
+ public function getSumVisitsLengthPretty($idSite, $period, $date, $segment = false)
+ {
+ $table = $this->getSumVisitsLength($idSite, $period, $date, $segment);
+ if ($table instanceof Piwik_DataTable_Array) {
+ $table->filter('ColumnCallbackReplace', array(0, array('Piwik', 'getPrettyTimeFromSeconds')));
+ } else {
+ $table = Piwik::getPrettyTimeFromSeconds($table);
+ }
+ return $table;
+ }
}
diff --git a/plugins/VisitsSummary/Controller.php b/plugins/VisitsSummary/Controller.php
index dec6a70957..86ebf7b5ac 100644
--- a/plugins/VisitsSummary/Controller.php
+++ b/plugins/VisitsSummary/Controller.php
@@ -15,156 +15,153 @@
*/
class Piwik_VisitsSummary_Controller extends Piwik_Controller
{
- public function index()
- {
- $view = Piwik_View::factory('index');
- $this->setPeriodVariablesView($view);
- $view->graphEvolutionVisitsSummary = $this->getEvolutionGraph( true, array('nb_visits') );
- $this->setSparklinesAndNumbers($view);
- echo $view->render();
- }
-
- public function getSparklines()
- {
- $view = Piwik_View::factory('sparklines');
- $this->setPeriodVariablesView($view);
- $this->setSparklinesAndNumbers($view);
- echo $view->render();
- }
-
- public function getEvolutionGraph( $fetch = false, array $columns = array())
- {
- if(empty($columns))
- {
- $columns = Piwik_Common::getRequestVar('columns');
- $columns = Piwik::getArrayFromApiParameter($columns);
- }
-
- $documentation = Piwik_Translate('VisitsSummary_VisitsSummaryDocumentation').'<br />'
- . Piwik_Translate('General_BrokenDownReportDocumentation').'<br /><br />'
-
- . '<b>'.Piwik_Translate('General_ColumnNbVisits').':</b> '
- . Piwik_Translate('General_ColumnNbVisitsDocumentation').'<br />'
-
- . '<b>'.Piwik_Translate('General_ColumnNbUniqVisitors').':</b> '
- . Piwik_Translate('General_ColumnNbUniqVisitorsDocumentation').'<br />'
-
- . '<b>'.Piwik_Translate('General_ColumnNbActions').':</b> '
- . Piwik_Translate('General_ColumnNbActionsDocumentation').'<br />'
-
- . '<b>'.Piwik_Translate('General_ColumnActionsPerVisit').':</b> '
- . Piwik_Translate('General_ColumnActionsPerVisitDocumentation');
-
- $selectableColumns = array(
- // columns from VisitsSummary.get
- 'nb_visits',
- 'nb_uniq_visitors',
- 'avg_time_on_site',
- 'bounce_rate',
- 'nb_actions_per_visit',
- 'max_actions',
- 'nb_visits_converted',
- // columns from Actions.get
- 'nb_pageviews',
- 'nb_uniq_pageviews',
- 'nb_downloads',
- 'nb_uniq_downloads',
- 'nb_outlinks',
- 'nb_uniq_outlinks'
- );
-
- $idSite = Piwik_Common::getRequestVar('idSite');
- $displaySiteSearch = Piwik_Site::isSiteSearchEnabledFor($idSite);
-
- if($displaySiteSearch) {
- $selectableColumns[] = 'nb_searches';
- $selectableColumns[] = 'nb_keywords';
- }
- $view = $this->getLastUnitGraphAcrossPlugins($this->pluginName, __FUNCTION__, $columns,
- $selectableColumns, $documentation);
-
- return $this->renderView($view, $fetch);
- }
-
- static public function getVisitsSummary()
- {
- $requestString = "method=VisitsSummary.get".
- "&format=original".
- // we disable filters for example "search for pattern", in the case this method is called
- // by a method that already calls the API with some generic filters applied
- "&disable_generic_filters=1";
- $request = new Piwik_API_Request($requestString);
- $result = $request->process();
- return empty($result) ? new Piwik_DataTable() : $result;
- }
-
- static public function getVisits()
- {
- $requestString = "method=VisitsSummary.getVisits".
- "&format=original".
- "&disable_generic_filters=1";
- $request = new Piwik_API_Request($requestString);
- return $request->process();
- }
-
- protected function setSparklinesAndNumbers($view)
- {
- $view->urlSparklineNbVisits = $this->getUrlSparkline( 'getEvolutionGraph', array('columns' => $view->displayUniqueVisitors ? array('nb_visits', 'nb_uniq_visitors') : array('nb_visits')));
- $view->urlSparklineNbPageviews = $this->getUrlSparkline( 'getEvolutionGraph', array('columns' => array('nb_pageviews', 'nb_uniq_pageviews')));
- $view->urlSparklineNbDownloads = $this->getUrlSparkline( 'getEvolutionGraph', array('columns' => array('nb_downloads', 'nb_uniq_downloads')));
- $view->urlSparklineNbOutlinks = $this->getUrlSparkline( 'getEvolutionGraph', array('columns' => array('nb_outlinks', 'nb_uniq_outlinks')));
- $view->urlSparklineAvgVisitDuration = $this->getUrlSparkline( 'getEvolutionGraph', array('columns' => array('avg_time_on_site')));
- $view->urlSparklineMaxActions = $this->getUrlSparkline( 'getEvolutionGraph', array('columns' => array('max_actions')));
- $view->urlSparklineActionsPerVisit = $this->getUrlSparkline( 'getEvolutionGraph', array('columns' => array('nb_actions_per_visit')));
- $view->urlSparklineBounceRate = $this->getUrlSparkline( 'getEvolutionGraph', array('columns' => array('bounce_rate')));
-
- $idSite = Piwik_Common::getRequestVar('idSite');
- $displaySiteSearch = Piwik_Site::isSiteSearchEnabledFor($idSite);
- if($displaySiteSearch)
- {
- $view->urlSparklineNbSearches = $this->getUrlSparkline( 'getEvolutionGraph', array('columns' => array('nb_searches', 'nb_keywords')));
- }
- $view->displaySiteSearch = $displaySiteSearch;
-
- $dataTableVisit = self::getVisitsSummary();
- $dataRow = $dataTableVisit->getRowsCount() == 0 ? new Piwik_DataTable_Row() : $dataTableVisit->getFirstRow();
-
- $dataTableActions = Piwik_Actions_API::getInstance()->get($idSite, Piwik_Common::getRequestVar('period'), Piwik_Common::getRequestVar('date'), Piwik_Common::getRequestVar('segment',false));
- $dataActionsRow =
- $dataTableActions->getRowsCount() == 0 ? new Piwik_DataTable_Row() : $dataTableActions->getFirstRow();
-
- $view->nbUniqVisitors = (int)$dataRow->getColumn('nb_uniq_visitors');
- $nbVisits = (int)$dataRow->getColumn('nb_visits');
- $view->nbVisits = $nbVisits;
- $view->nbPageviews = (int)$dataActionsRow->getColumn('nb_pageviews');
- $view->nbUniquePageviews = (int)$dataActionsRow->getColumn('nb_uniq_pageviews');
- $view->nbDownloads = (int)$dataActionsRow->getColumn('nb_downloads');
- $view->nbUniqueDownloads = (int)$dataActionsRow->getColumn('nb_uniq_downloads');
- $view->nbOutlinks = (int)$dataActionsRow->getColumn('nb_outlinks');
- $view->nbUniqueOutlinks = (int)$dataActionsRow->getColumn('nb_uniq_outlinks');
- $view->averageVisitDuration = $dataRow->getColumn('avg_time_on_site');
- $nbBouncedVisits = $dataRow->getColumn('bounce_count');
- $view->bounceRate = Piwik::getPercentageSafe($nbBouncedVisits, $nbVisits);
- $view->maxActions = (int)$dataRow->getColumn('max_actions');
- $view->nbActionsPerVisit = $dataRow->getColumn('nb_actions_per_visit');
-
- if($displaySiteSearch)
- {
- $view->nbSearches = (int)$dataActionsRow->getColumn('nb_searches');
- $view->nbKeywords = (int)$dataActionsRow->getColumn('nb_keywords');
- }
-
- // backward compatibility:
- // show actions if the finer metrics are not archived
- $view->showOnlyActions = false;
- if ( $dataActionsRow->getColumn('nb_pageviews')
- + $dataActionsRow->getColumn('nb_downloads')
- + $dataActionsRow->getColumn('nb_outlinks') == 0
- && $dataRow->getColumn('nb_actions') > 0)
- {
- $view->showOnlyActions = true;
- $view->nbActions = $dataRow->getColumn('nb_actions');
- $view->urlSparklineNbActions = $this->getUrlSparkline( 'getEvolutionGraph', array('columns' => array('nb_actions')));
- }
- }
+ public function index()
+ {
+ $view = Piwik_View::factory('index');
+ $this->setPeriodVariablesView($view);
+ $view->graphEvolutionVisitsSummary = $this->getEvolutionGraph(true, array('nb_visits'));
+ $this->setSparklinesAndNumbers($view);
+ echo $view->render();
+ }
+
+ public function getSparklines()
+ {
+ $view = Piwik_View::factory('sparklines');
+ $this->setPeriodVariablesView($view);
+ $this->setSparklinesAndNumbers($view);
+ echo $view->render();
+ }
+
+ public function getEvolutionGraph($fetch = false, array $columns = array())
+ {
+ if (empty($columns)) {
+ $columns = Piwik_Common::getRequestVar('columns');
+ $columns = Piwik::getArrayFromApiParameter($columns);
+ }
+
+ $documentation = Piwik_Translate('VisitsSummary_VisitsSummaryDocumentation') . '<br />'
+ . Piwik_Translate('General_BrokenDownReportDocumentation') . '<br /><br />'
+
+ . '<b>' . Piwik_Translate('General_ColumnNbVisits') . ':</b> '
+ . Piwik_Translate('General_ColumnNbVisitsDocumentation') . '<br />'
+
+ . '<b>' . Piwik_Translate('General_ColumnNbUniqVisitors') . ':</b> '
+ . Piwik_Translate('General_ColumnNbUniqVisitorsDocumentation') . '<br />'
+
+ . '<b>' . Piwik_Translate('General_ColumnNbActions') . ':</b> '
+ . Piwik_Translate('General_ColumnNbActionsDocumentation') . '<br />'
+
+ . '<b>' . Piwik_Translate('General_ColumnActionsPerVisit') . ':</b> '
+ . Piwik_Translate('General_ColumnActionsPerVisitDocumentation');
+
+ $selectableColumns = array(
+ // columns from VisitsSummary.get
+ 'nb_visits',
+ 'nb_uniq_visitors',
+ 'avg_time_on_site',
+ 'bounce_rate',
+ 'nb_actions_per_visit',
+ 'max_actions',
+ 'nb_visits_converted',
+ // columns from Actions.get
+ 'nb_pageviews',
+ 'nb_uniq_pageviews',
+ 'nb_downloads',
+ 'nb_uniq_downloads',
+ 'nb_outlinks',
+ 'nb_uniq_outlinks'
+ );
+
+ $idSite = Piwik_Common::getRequestVar('idSite');
+ $displaySiteSearch = Piwik_Site::isSiteSearchEnabledFor($idSite);
+
+ if ($displaySiteSearch) {
+ $selectableColumns[] = 'nb_searches';
+ $selectableColumns[] = 'nb_keywords';
+ }
+ $view = $this->getLastUnitGraphAcrossPlugins($this->pluginName, __FUNCTION__, $columns,
+ $selectableColumns, $documentation);
+
+ return $this->renderView($view, $fetch);
+ }
+
+ static public function getVisitsSummary()
+ {
+ $requestString = "method=VisitsSummary.get" .
+ "&format=original" .
+ // we disable filters for example "search for pattern", in the case this method is called
+ // by a method that already calls the API with some generic filters applied
+ "&disable_generic_filters=1";
+ $request = new Piwik_API_Request($requestString);
+ $result = $request->process();
+ return empty($result) ? new Piwik_DataTable() : $result;
+ }
+
+ static public function getVisits()
+ {
+ $requestString = "method=VisitsSummary.getVisits" .
+ "&format=original" .
+ "&disable_generic_filters=1";
+ $request = new Piwik_API_Request($requestString);
+ return $request->process();
+ }
+
+ protected function setSparklinesAndNumbers($view)
+ {
+ $view->urlSparklineNbVisits = $this->getUrlSparkline('getEvolutionGraph', array('columns' => $view->displayUniqueVisitors ? array('nb_visits', 'nb_uniq_visitors') : array('nb_visits')));
+ $view->urlSparklineNbPageviews = $this->getUrlSparkline('getEvolutionGraph', array('columns' => array('nb_pageviews', 'nb_uniq_pageviews')));
+ $view->urlSparklineNbDownloads = $this->getUrlSparkline('getEvolutionGraph', array('columns' => array('nb_downloads', 'nb_uniq_downloads')));
+ $view->urlSparklineNbOutlinks = $this->getUrlSparkline('getEvolutionGraph', array('columns' => array('nb_outlinks', 'nb_uniq_outlinks')));
+ $view->urlSparklineAvgVisitDuration = $this->getUrlSparkline('getEvolutionGraph', array('columns' => array('avg_time_on_site')));
+ $view->urlSparklineMaxActions = $this->getUrlSparkline('getEvolutionGraph', array('columns' => array('max_actions')));
+ $view->urlSparklineActionsPerVisit = $this->getUrlSparkline('getEvolutionGraph', array('columns' => array('nb_actions_per_visit')));
+ $view->urlSparklineBounceRate = $this->getUrlSparkline('getEvolutionGraph', array('columns' => array('bounce_rate')));
+
+ $idSite = Piwik_Common::getRequestVar('idSite');
+ $displaySiteSearch = Piwik_Site::isSiteSearchEnabledFor($idSite);
+ if ($displaySiteSearch) {
+ $view->urlSparklineNbSearches = $this->getUrlSparkline('getEvolutionGraph', array('columns' => array('nb_searches', 'nb_keywords')));
+ }
+ $view->displaySiteSearch = $displaySiteSearch;
+
+ $dataTableVisit = self::getVisitsSummary();
+ $dataRow = $dataTableVisit->getRowsCount() == 0 ? new Piwik_DataTable_Row() : $dataTableVisit->getFirstRow();
+
+ $dataTableActions = Piwik_Actions_API::getInstance()->get($idSite, Piwik_Common::getRequestVar('period'), Piwik_Common::getRequestVar('date'), Piwik_Common::getRequestVar('segment', false));
+ $dataActionsRow =
+ $dataTableActions->getRowsCount() == 0 ? new Piwik_DataTable_Row() : $dataTableActions->getFirstRow();
+
+ $view->nbUniqVisitors = (int)$dataRow->getColumn('nb_uniq_visitors');
+ $nbVisits = (int)$dataRow->getColumn('nb_visits');
+ $view->nbVisits = $nbVisits;
+ $view->nbPageviews = (int)$dataActionsRow->getColumn('nb_pageviews');
+ $view->nbUniquePageviews = (int)$dataActionsRow->getColumn('nb_uniq_pageviews');
+ $view->nbDownloads = (int)$dataActionsRow->getColumn('nb_downloads');
+ $view->nbUniqueDownloads = (int)$dataActionsRow->getColumn('nb_uniq_downloads');
+ $view->nbOutlinks = (int)$dataActionsRow->getColumn('nb_outlinks');
+ $view->nbUniqueOutlinks = (int)$dataActionsRow->getColumn('nb_uniq_outlinks');
+ $view->averageVisitDuration = $dataRow->getColumn('avg_time_on_site');
+ $nbBouncedVisits = $dataRow->getColumn('bounce_count');
+ $view->bounceRate = Piwik::getPercentageSafe($nbBouncedVisits, $nbVisits);
+ $view->maxActions = (int)$dataRow->getColumn('max_actions');
+ $view->nbActionsPerVisit = $dataRow->getColumn('nb_actions_per_visit');
+
+ if ($displaySiteSearch) {
+ $view->nbSearches = (int)$dataActionsRow->getColumn('nb_searches');
+ $view->nbKeywords = (int)$dataActionsRow->getColumn('nb_keywords');
+ }
+
+ // backward compatibility:
+ // show actions if the finer metrics are not archived
+ $view->showOnlyActions = false;
+ if ($dataActionsRow->getColumn('nb_pageviews')
+ + $dataActionsRow->getColumn('nb_downloads')
+ + $dataActionsRow->getColumn('nb_outlinks') == 0
+ && $dataRow->getColumn('nb_actions') > 0
+ ) {
+ $view->showOnlyActions = true;
+ $view->nbActions = $dataRow->getColumn('nb_actions');
+ $view->urlSparklineNbActions = $this->getUrlSparkline('getEvolutionGraph', array('columns' => array('nb_actions')));
+ }
+ }
}
diff --git a/plugins/VisitsSummary/VisitsSummary.php b/plugins/VisitsSummary/VisitsSummary.php
index 816472455b..87b320c947 100644
--- a/plugins/VisitsSummary/VisitsSummary.php
+++ b/plugins/VisitsSummary/VisitsSummary.php
@@ -19,66 +19,66 @@
*/
class Piwik_VisitsSummary extends Piwik_Plugin
{
- public function getInformation()
- {
- $info = array(
- 'description' => Piwik_Translate('VisitsSummary_PluginDescription'),
- 'author' => 'Piwik',
- 'author_homepage' => 'http://piwik.org/',
- 'version' => Piwik_Version::VERSION,
- );
- return $info;
- }
-
- function getListHooksRegistered()
- {
- return array(
- 'API.getReportMetadata' => 'getReportMetadata',
- 'WidgetsList.add' => 'addWidgets',
- 'Menu.add' => 'addMenu',
- );
- }
+ public function getInformation()
+ {
+ $info = array(
+ 'description' => Piwik_Translate('VisitsSummary_PluginDescription'),
+ 'author' => 'Piwik',
+ 'author_homepage' => 'http://piwik.org/',
+ 'version' => Piwik_Version::VERSION,
+ );
+ return $info;
+ }
- /**
- * @param Piwik_Event_Notification $notification notification object
- */
- public function getReportMetadata($notification)
- {
- $reports = &$notification->getNotificationObject();
- $reports[] = array(
- 'category' => Piwik_Translate('VisitsSummary_VisitsSummary'),
- 'name' => Piwik_Translate('VisitsSummary_VisitsSummary'),
- 'module' => 'VisitsSummary',
- 'action' => 'get',
- 'metrics' => array(
- 'nb_uniq_visitors',
- 'nb_visits',
- 'nb_actions',
- 'nb_actions_per_visit',
- 'bounce_rate',
- 'avg_time_on_site' => Piwik_Translate('General_VisitDuration'),
- 'max_actions' => Piwik_Translate('General_ColumnMaxActions'),
+ function getListHooksRegistered()
+ {
+ return array(
+ 'API.getReportMetadata' => 'getReportMetadata',
+ 'WidgetsList.add' => 'addWidgets',
+ 'Menu.add' => 'addMenu',
+ );
+ }
+
+ /**
+ * @param Piwik_Event_Notification $notification notification object
+ */
+ public function getReportMetadata($notification)
+ {
+ $reports = & $notification->getNotificationObject();
+ $reports[] = array(
+ 'category' => Piwik_Translate('VisitsSummary_VisitsSummary'),
+ 'name' => Piwik_Translate('VisitsSummary_VisitsSummary'),
+ 'module' => 'VisitsSummary',
+ 'action' => 'get',
+ 'metrics' => array(
+ 'nb_uniq_visitors',
+ 'nb_visits',
+ 'nb_actions',
+ 'nb_actions_per_visit',
+ 'bounce_rate',
+ 'avg_time_on_site' => Piwik_Translate('General_VisitDuration'),
+ 'max_actions' => Piwik_Translate('General_ColumnMaxActions'),
// Used to process metrics, not displayed/used directly
// 'sum_visit_length',
// 'nb_visits_converted',
- ),
- 'processedMetrics' => false,
- 'order' => 1
- );
- }
-
- function addWidgets()
- {
- Piwik_AddWidget( 'VisitsSummary_VisitsSummary', 'VisitsSummary_WidgetLastVisits', 'VisitsSummary', 'getEvolutionGraph', array('columns' => array('nb_visits')));
- Piwik_AddWidget( 'VisitsSummary_VisitsSummary', 'VisitsSummary_WidgetVisits', 'VisitsSummary', 'getSparklines');
- Piwik_AddWidget( 'VisitsSummary_VisitsSummary', 'VisitsSummary_WidgetOverviewGraph', 'VisitsSummary', 'index');
- }
-
- function addMenu()
- {
- Piwik_AddMenu('General_Visitors', '', array('module' => 'VisitsSummary', 'action' => 'index'), true, 10);
- Piwik_AddMenu('General_Visitors', 'VisitsSummary_SubmenuOverview', array('module' => 'VisitsSummary', 'action' => 'index'), true, 1);
- }
+ ),
+ 'processedMetrics' => false,
+ 'order' => 1
+ );
+ }
+
+ function addWidgets()
+ {
+ Piwik_AddWidget('VisitsSummary_VisitsSummary', 'VisitsSummary_WidgetLastVisits', 'VisitsSummary', 'getEvolutionGraph', array('columns' => array('nb_visits')));
+ Piwik_AddWidget('VisitsSummary_VisitsSummary', 'VisitsSummary_WidgetVisits', 'VisitsSummary', 'getSparklines');
+ Piwik_AddWidget('VisitsSummary_VisitsSummary', 'VisitsSummary_WidgetOverviewGraph', 'VisitsSummary', 'index');
+ }
+
+ function addMenu()
+ {
+ Piwik_AddMenu('General_Visitors', '', array('module' => 'VisitsSummary', 'action' => 'index'), true, 10);
+ Piwik_AddMenu('General_Visitors', 'VisitsSummary_SubmenuOverview', array('module' => 'VisitsSummary', 'action' => 'index'), true, 1);
+ }
}
diff --git a/plugins/VisitsSummary/templates/sparklines.tpl b/plugins/VisitsSummary/templates/sparklines.tpl
index 907e58e918..a0302f407a 100644
--- a/plugins/VisitsSummary/templates/sparklines.tpl
+++ b/plugins/VisitsSummary/templates/sparklines.tpl
@@ -1,58 +1,58 @@
<div id='leftcolumn'>
- <div class="sparkline">
- {sparkline src=$urlSparklineNbVisits}
- {'VisitsSummary_NbVisits'|translate:"<strong>$nbVisits</strong>"}{if $displayUniqueVisitors},
- {'VisitsSummary_NbUniqueVisitors'|translate:"<strong>$nbUniqVisitors</strong>"}{/if}
- </div>
- <div class="sparkline">
- {sparkline src=$urlSparklineAvgVisitDuration}
- {assign var=averageVisitDuration value=$averageVisitDuration|sumtime}
- {'VisitsSummary_AverageVisitDuration'|translate:"<strong>$averageVisitDuration</strong>"}
- </div>
- <div class="sparkline">
- {sparkline src=$urlSparklineBounceRate}
- {'VisitsSummary_NbVisitsBounced'|translate:"<strong>$bounceRate%</strong>"}
- </div>
- <div class="sparkline">
- {sparkline src=$urlSparklineActionsPerVisit}
- {'VisitsSummary_NbActionsPerVisit'|translate:"<strong>$nbActionsPerVisit</strong>"}
- </div>
+ <div class="sparkline">
+ {sparkline src=$urlSparklineNbVisits}
+ {'VisitsSummary_NbVisits'|translate:"<strong>$nbVisits</strong>"}{if $displayUniqueVisitors},
+ {'VisitsSummary_NbUniqueVisitors'|translate:"<strong>$nbUniqVisitors</strong>"}{/if}
+ </div>
+ <div class="sparkline">
+ {sparkline src=$urlSparklineAvgVisitDuration}
+ {assign var=averageVisitDuration value=$averageVisitDuration|sumtime}
+ {'VisitsSummary_AverageVisitDuration'|translate:"<strong>$averageVisitDuration</strong>"}
+ </div>
+ <div class="sparkline">
+ {sparkline src=$urlSparklineBounceRate}
+ {'VisitsSummary_NbVisitsBounced'|translate:"<strong>$bounceRate%</strong>"}
+ </div>
+ <div class="sparkline">
+ {sparkline src=$urlSparklineActionsPerVisit}
+ {'VisitsSummary_NbActionsPerVisit'|translate:"<strong>$nbActionsPerVisit</strong>"}
+ </div>
</div>
<div id='rightcolumn'>
- {if $showOnlyActions}
- <div class="sparkline">
- {sparkline src=$urlSparklineNbActions}
- {'VisitsSummary_NbActionsDescription'|translate:"<strong>$nbActions</strong>"}
- </div>
- {else}
- <div class="sparkline">
- {sparkline src=$urlSparklineNbPageviews}
- {'VisitsSummary_NbPageviewsDescription'|translate:"<strong>$nbPageviews</strong>"|trim},
- {'VisitsSummary_NbUniquePageviewsDescription'|translate:"<strong>$nbUniquePageviews</strong>"}
- </div>
- {if $displaySiteSearch}
- <div class="sparkline">
- {sparkline src=$urlSparklineNbSearches}
- {'VisitsSummary_NbSearchesDescription'|translate:"<strong>$nbSearches</strong>"|trim},
- {'VisitsSummary_NbKeywordsDescription'|translate:"<strong>$nbKeywords</strong>"}
- </div>
- {/if}
- <div class="sparkline">
- {sparkline src=$urlSparklineNbDownloads}
- {'VisitsSummary_NbDownloadsDescription'|translate:"<strong>$nbDownloads</strong>"|trim},
- {'VisitsSummary_NbUniqueDownloadsDescription'|translate:"<strong>$nbUniqueDownloads</strong>"}
- </div>
- <div class="sparkline">
- {sparkline src=$urlSparklineNbOutlinks}
- {'VisitsSummary_NbOutlinksDescription'|translate:"<strong>$nbOutlinks</strong>"|trim},
- {'VisitsSummary_NbUniqueOutlinksDescription'|translate:"<strong>$nbUniqueOutlinks</strong>"}
- </div>
- {/if}
- <div class="sparkline">
- {sparkline src=$urlSparklineMaxActions}
- {'VisitsSummary_MaxNbActions'|translate:"<strong>$maxActions</strong>"}
- </div>
+ {if $showOnlyActions}
+ <div class="sparkline">
+ {sparkline src=$urlSparklineNbActions}
+ {'VisitsSummary_NbActionsDescription'|translate:"<strong>$nbActions</strong>"}
+ </div>
+ {else}
+ <div class="sparkline">
+ {sparkline src=$urlSparklineNbPageviews}
+ {'VisitsSummary_NbPageviewsDescription'|translate:"<strong>$nbPageviews</strong>"|trim},
+ {'VisitsSummary_NbUniquePageviewsDescription'|translate:"<strong>$nbUniquePageviews</strong>"}
+ </div>
+ {if $displaySiteSearch}
+ <div class="sparkline">
+ {sparkline src=$urlSparklineNbSearches}
+ {'VisitsSummary_NbSearchesDescription'|translate:"<strong>$nbSearches</strong>"|trim},
+ {'VisitsSummary_NbKeywordsDescription'|translate:"<strong>$nbKeywords</strong>"}
+ </div>
+ {/if}
+ <div class="sparkline">
+ {sparkline src=$urlSparklineNbDownloads}
+ {'VisitsSummary_NbDownloadsDescription'|translate:"<strong>$nbDownloads</strong>"|trim},
+ {'VisitsSummary_NbUniqueDownloadsDescription'|translate:"<strong>$nbUniqueDownloads</strong>"}
+ </div>
+ <div class="sparkline">
+ {sparkline src=$urlSparklineNbOutlinks}
+ {'VisitsSummary_NbOutlinksDescription'|translate:"<strong>$nbOutlinks</strong>"|trim},
+ {'VisitsSummary_NbUniqueOutlinksDescription'|translate:"<strong>$nbUniqueOutlinks</strong>"}
+ </div>
+ {/if}
+ <div class="sparkline">
+ {sparkline src=$urlSparklineMaxActions}
+ {'VisitsSummary_MaxNbActions'|translate:"<strong>$maxActions</strong>"}
+ </div>
</div>
<div style="clear:both;"></div>
diff --git a/plugins/Widgetize/Controller.php b/plugins/Widgetize/Controller.php
index cf084197b1..6441a84b48 100644
--- a/plugins/Widgetize/Controller.php
+++ b/plugins/Widgetize/Controller.php
@@ -1,77 +1,77 @@
<?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_Widgetize
*/
/**
- *
+ *
* @package Piwik_Widgetize
*/
class Piwik_Widgetize_Controller extends Piwik_Controller
{
- function index()
- {
- $view = Piwik_View::factory('index');
- $view->availableWidgets = Piwik_Common::json_encode(Piwik_GetWidgetsList());
- $this->setGeneralVariablesView($view);
- echo $view->render();
- }
+ function index()
+ {
+ $view = Piwik_View::factory('index');
+ $view->availableWidgets = Piwik_Common::json_encode(Piwik_GetWidgetsList());
+ $this->setGeneralVariablesView($view);
+ echo $view->render();
+ }
- function testJsInclude1()
- {
- $view = Piwik_View::factory('test_jsinclude');
- $view->url1 = '?module=Widgetize&action=js&moduleToWidgetize=UserSettings&actionToWidgetize=getBrowser&idSite=1&period=day&date=yesterday';
- $view->url2 = '?module=Widgetize&action=js&moduleToWidgetize=API&actionToWidgetize=index&method=ExamplePlugin.getGoldenRatio&format=original';
- echo $view->render();
- }
-
- function testJsInclude2()
- {
- $view = Piwik_View::factory('test_jsinclude2');
- $view->url1 = '?module=Widgetize&action=js&moduleToWidgetize=UserSettings&actionToWidgetize=getBrowser&idSite=1&period=day&date=yesterday';
- $view->url2 = '?module=Widgetize&action=js&moduleToWidgetize=UserCountry&actionToWidgetize=getCountry&idSite=1&period=day&date=yesterday&viewDataTable=cloud&show_footer=0';
- $view->url3 = '?module=Widgetize&action=js&moduleToWidgetize=Referers&actionToWidgetize=getKeywords&idSite=1&period=day&date=yesterday&viewDataTable=table&show_footer=0';
- echo $view->render();
- }
-
- /**
- * Disabled for now, not obvious that this is useful (iframe sounds like a better solution)
- */
- private function js()
- {
- Piwik_API_Request::reloadAuthUsingTokenAuth();
- $controllerName = Piwik_Common::getRequestVar('moduleToWidgetize');
- $actionName = Piwik_Common::getRequestVar('actionToWidgetize');
- $parameters = array ( $fetch = true );
- $content = Piwik_FrontController::getInstance()->fetchDispatch( $controllerName, $actionName, $parameters);
- $view = Piwik_View::factory('js');
- $content = str_replace(array("\t","\n","\r\n","\r"), "", $content);
- $view->content = $content;
- echo $view->render();
- }
+ function testJsInclude1()
+ {
+ $view = Piwik_View::factory('test_jsinclude');
+ $view->url1 = '?module=Widgetize&action=js&moduleToWidgetize=UserSettings&actionToWidgetize=getBrowser&idSite=1&period=day&date=yesterday';
+ $view->url2 = '?module=Widgetize&action=js&moduleToWidgetize=API&actionToWidgetize=index&method=ExamplePlugin.getGoldenRatio&format=original';
+ echo $view->render();
+ }
- function iframe()
- {
- Piwik_API_Request::reloadAuthUsingTokenAuth();
- $this->init();
- $controllerName = Piwik_Common::getRequestVar('moduleToWidgetize');
- $actionName = Piwik_Common::getRequestVar('actionToWidgetize');
- $parameters = array ( $fetch = true );
- $outputDataTable = Piwik_FrontController::getInstance()->fetchDispatch( $controllerName, $actionName, $parameters);
- if($controllerName == 'Dashboard' && $actionName == 'index') {
- $view = Piwik_View::factory('empty');
- } else {
- $view = Piwik_View::factory('iframe');
- }
- $this->setGeneralVariablesView($view);
- $view->setXFrameOptions('allow');
- $view->content = $outputDataTable;
- echo $view->render();
- }
+ function testJsInclude2()
+ {
+ $view = Piwik_View::factory('test_jsinclude2');
+ $view->url1 = '?module=Widgetize&action=js&moduleToWidgetize=UserSettings&actionToWidgetize=getBrowser&idSite=1&period=day&date=yesterday';
+ $view->url2 = '?module=Widgetize&action=js&moduleToWidgetize=UserCountry&actionToWidgetize=getCountry&idSite=1&period=day&date=yesterday&viewDataTable=cloud&show_footer=0';
+ $view->url3 = '?module=Widgetize&action=js&moduleToWidgetize=Referers&actionToWidgetize=getKeywords&idSite=1&period=day&date=yesterday&viewDataTable=table&show_footer=0';
+ echo $view->render();
+ }
+
+ /**
+ * Disabled for now, not obvious that this is useful (iframe sounds like a better solution)
+ */
+ private function js()
+ {
+ Piwik_API_Request::reloadAuthUsingTokenAuth();
+ $controllerName = Piwik_Common::getRequestVar('moduleToWidgetize');
+ $actionName = Piwik_Common::getRequestVar('actionToWidgetize');
+ $parameters = array($fetch = true);
+ $content = Piwik_FrontController::getInstance()->fetchDispatch($controllerName, $actionName, $parameters);
+ $view = Piwik_View::factory('js');
+ $content = str_replace(array("\t", "\n", "\r\n", "\r"), "", $content);
+ $view->content = $content;
+ echo $view->render();
+ }
+
+ function iframe()
+ {
+ Piwik_API_Request::reloadAuthUsingTokenAuth();
+ $this->init();
+ $controllerName = Piwik_Common::getRequestVar('moduleToWidgetize');
+ $actionName = Piwik_Common::getRequestVar('actionToWidgetize');
+ $parameters = array($fetch = true);
+ $outputDataTable = Piwik_FrontController::getInstance()->fetchDispatch($controllerName, $actionName, $parameters);
+ if ($controllerName == 'Dashboard' && $actionName == 'index') {
+ $view = Piwik_View::factory('empty');
+ } else {
+ $view = Piwik_View::factory('iframe');
+ }
+ $this->setGeneralVariablesView($view);
+ $view->setXFrameOptions('allow');
+ $view->content = $outputDataTable;
+ echo $view->render();
+ }
}
diff --git a/plugins/Widgetize/Widgetize.php b/plugins/Widgetize/Widgetize.php
index 556e46c396..0201cfd9ef 100644
--- a/plugins/Widgetize/Widgetize.php
+++ b/plugins/Widgetize/Widgetize.php
@@ -2,74 +2,74 @@
/**
* 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_Widgetize
*/
/**
- *
+ *
* @package Piwik_Widgetize
*/
-class Piwik_Widgetize extends Piwik_Plugin
+class Piwik_Widgetize extends Piwik_Plugin
{
- public function getInformation()
- {
- return array(
- 'description' => Piwik_Translate('Widgetize_PluginDescription'),
- 'author' => 'Piwik',
- 'author_homepage' => 'http://piwik.org/',
- 'version' => Piwik_Version::VERSION,
- );
- }
-
- public function getListHooksRegistered()
- {
- return array(
- 'AssetManager.getJsFiles' => 'getJsFiles',
- 'AssetManager.getCssFiles' => 'getCssFiles',
- 'TopMenu.add' => 'addTopMenu',
- );
- }
-
- public function addTopMenu()
- {
- $tooltip = Piwik_Translate('Widgetize_TopLinkTooltip');
- $urlParams = array('module' => 'Widgetize', 'action' => 'index');
-
- Piwik_AddTopMenu('General_Widgets', $urlParams, true, 5, $isHTML = false, $tooltip);
- }
+ public function getInformation()
+ {
+ return array(
+ 'description' => Piwik_Translate('Widgetize_PluginDescription'),
+ 'author' => 'Piwik',
+ 'author_homepage' => 'http://piwik.org/',
+ 'version' => Piwik_Version::VERSION,
+ );
+ }
- /**
- * @param Piwik_Event_Notification $notification notification object
- */
- function getJsFiles($notification)
- {
- $jsFiles = &$notification->getNotificationObject();
+ public function getListHooksRegistered()
+ {
+ return array(
+ 'AssetManager.getJsFiles' => 'getJsFiles',
+ 'AssetManager.getCssFiles' => 'getCssFiles',
+ 'TopMenu.add' => 'addTopMenu',
+ );
+ }
- $jsFiles[] = "libs/jquery/jquery.tooltip.js";
- $jsFiles[] = "libs/jquery/jquery.truncate.js";
- $jsFiles[] = "libs/jquery/jquery.scrollTo.js";
- $jsFiles[] = "themes/default/common.js";
- $jsFiles[] = "plugins/CoreHome/templates/datatable.js";
- $jsFiles[] = "plugins/Dashboard/templates/widgetMenu.js";
- $jsFiles[] = "plugins/Widgetize/templates/widgetize.js";
- }
+ public function addTopMenu()
+ {
+ $tooltip = Piwik_Translate('Widgetize_TopLinkTooltip');
+ $urlParams = array('module' => 'Widgetize', 'action' => 'index');
- /**
- * @param Piwik_Event_Notification $notification notification object
- */
- function getCssFiles($notification)
- {
- $cssFiles = &$notification->getNotificationObject();
-
- $cssFiles[] = "plugins/CoreHome/templates/styles.css";
- $cssFiles[] = "plugins/CoreHome/templates/datatable.css";
- $cssFiles[] = "plugins/CoreHome/templates/cloud.css";
- $cssFiles[] = "plugins/Dashboard/templates/dashboard.css";
- }
+ Piwik_AddTopMenu('General_Widgets', $urlParams, true, 5, $isHTML = false, $tooltip);
+ }
+
+ /**
+ * @param Piwik_Event_Notification $notification notification object
+ */
+ function getJsFiles($notification)
+ {
+ $jsFiles = & $notification->getNotificationObject();
+
+ $jsFiles[] = "libs/jquery/jquery.tooltip.js";
+ $jsFiles[] = "libs/jquery/jquery.truncate.js";
+ $jsFiles[] = "libs/jquery/jquery.scrollTo.js";
+ $jsFiles[] = "themes/default/common.js";
+ $jsFiles[] = "plugins/CoreHome/templates/datatable.js";
+ $jsFiles[] = "plugins/Dashboard/templates/widgetMenu.js";
+ $jsFiles[] = "plugins/Widgetize/templates/widgetize.js";
+ }
+
+ /**
+ * @param Piwik_Event_Notification $notification notification object
+ */
+ function getCssFiles($notification)
+ {
+ $cssFiles = & $notification->getNotificationObject();
+
+ $cssFiles[] = "plugins/CoreHome/templates/styles.css";
+ $cssFiles[] = "plugins/CoreHome/templates/datatable.css";
+ $cssFiles[] = "plugins/CoreHome/templates/cloud.css";
+ $cssFiles[] = "plugins/Dashboard/templates/dashboard.css";
+ }
}
diff --git a/plugins/Widgetize/templates/iframe.tpl b/plugins/Widgetize/templates/iframe.tpl
index 9ca106f5ef..eb8287098a 100644
--- a/plugins/Widgetize/templates/iframe.tpl
+++ b/plugins/Widgetize/templates/iframe.tpl
@@ -1,22 +1,22 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
- "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
-<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
-{loadJavascriptTranslations plugins='CoreHome'}
-{include file="CoreHome/templates/js_global_variables.tpl"}
-<!--[if lt IE 9]>
-<script language="javascript" type="text/javascript" src="libs/jqplot/excanvas.min.js"></script>
-<![endif]-->
-{include file="CoreHome/templates/js_css_includes.tpl"}
-<!--[if IE]>
-<link rel="stylesheet" type="text/css" href="themes/default/ieonly.css" />
-<![endif]-->
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
+ {loadJavascriptTranslations plugins='CoreHome'}
+ {include file="CoreHome/templates/js_global_variables.tpl"}
+ <!--[if lt IE 9]>
+ <script language="javascript" type="text/javascript" src="libs/jqplot/excanvas.min.js"></script>
+ <![endif]-->
+ {include file="CoreHome/templates/js_css_includes.tpl"}
+ <!--[if IE]>
+ <link rel="stylesheet" type="text/css" href="themes/default/ieonly.css"/>
+ <![endif]-->
</head>
<body>
<div class="widget">
-{$content}
+ {$content}
</div>
</body>
diff --git a/plugins/Widgetize/templates/index.tpl b/plugins/Widgetize/templates/index.tpl
index 854e4e92da..d29684958f 100644
--- a/plugins/Widgetize/templates/index.tpl
+++ b/plugins/Widgetize/templates/index.tpl
@@ -6,71 +6,74 @@
{literal}
-<style type="text/css">
-.widgetize{
- width:100%;
- padding:15px 15px 0 15px;
- font-size:13px;
-}
-.widgetize p{
- padding: 0 0 20px 0;
-}
-.menu {
- display: inline;
-}
-.widgetize .formEmbedCode{
- font-size: 11px;
- text-decoration: none;
- background-color: #FBFDFF;
- border: 1px solid #ECECEC;
- width:220px;
-}
-
-#periodString {
- margin-left:15px;
-}
-
-.widgetize label {
- color:#666666;
- line-height:18px;
- margin-right:5px;
- font-weight:bold;
- padding-bottom:100px;
-}
-
-#embedThisWidgetIframe,
-#embedThisWidgetFlash,
-#embedThisWidgetEverywhere {
- margin-top:5px;
-}
-
-.menuSelected{
- font-weight:bold;
-}
-</style>
+ <style type="text/css">
+ .widgetize {
+ width: 100%;
+ padding: 15px 15px 0 15px;
+ font-size: 13px;
+ }
+
+ .widgetize p {
+ padding: 0 0 20px 0;
+ }
+
+ .menu {
+ display: inline;
+ }
+
+ .widgetize .formEmbedCode {
+ font-size: 11px;
+ text-decoration: none;
+ background-color: #FBFDFF;
+ border: 1px solid #ECECEC;
+ width: 220px;
+ }
+
+ #periodString {
+ margin-left: 15px;
+ }
+
+ .widgetize label {
+ color: #666666;
+ line-height: 18px;
+ margin-right: 5px;
+ font-weight: bold;
+ padding-bottom: 100px;
+ }
+
+ #embedThisWidgetIframe,
+ #embedThisWidgetFlash,
+ #embedThisWidgetEverywhere {
+ margin-top: 5px;
+ }
+
+ .menuSelected {
+ font-weight: bold;
+ }
+ </style>
{/literal}
<script type="text/javascript">
-{literal}
-$(document).ready( function() {
- var widgetized = new widgetize();
- var urlPath = document.location.protocol + '//' + document.location.hostname + (document.location.port == '' ? '' : (':' + document.location.port)) + document.location.pathname ;
- var dashboardUrl = urlPath + '?module=Widgetize&action=iframe&moduleToWidgetize=Dashboard&actionToWidgetize=index&idSite='+piwik.idSite+'&period=week&date=yesterday';
- $('#exportFullDashboard').html(
- widgetized.getInputFormWithHtml( 'dashboardEmbed', '<iframe src="'+ dashboardUrl +'" frameborder="0" marginheight="0" marginwidth="0" width="100%" height="100%"></iframe>')
- );
- $('#linkDashboardUrl').attr('href',dashboardUrl);
-
- var allWebsitesDashboardUrl = urlPath + '?module=Widgetize&action=iframe&moduleToWidgetize=MultiSites&actionToWidgetize=standalone&idSite='+piwik.idSite+'&period=week&date=yesterday';
- $('#exportAllWebsitesDashboard').html(
- widgetized.getInputFormWithHtml( 'allWebsitesDashboardEmbed', '<iframe src="'+ allWebsitesDashboardUrl +'" frameborder="0" marginheight="0" marginwidth="0" width="100%" height="100%"></iframe>')
- );
- $('#linkAllWebsitesDashboardUrl').attr('href',allWebsitesDashboardUrl);
- $('#widgetPreview').widgetPreview({
- onPreviewLoaded: widgetized.callbackAddExportButtonsUnderWidget
+ {literal}
+ $(document).ready(function () {
+ var widgetized = new widgetize();
+ var urlPath = document.location.protocol + '//' + document.location.hostname + (document.location.port == '' ? '' : (':' + document.location.port)) + document.location.pathname;
+ var dashboardUrl = urlPath + '?module=Widgetize&action=iframe&moduleToWidgetize=Dashboard&actionToWidgetize=index&idSite=' + piwik.idSite + '&period=week&date=yesterday';
+ $('#exportFullDashboard').html(
+ widgetized.getInputFormWithHtml('dashboardEmbed', '<iframe src="' + dashboardUrl + '" frameborder="0" marginheight="0" marginwidth="0" width="100%" height="100%"></iframe>')
+ );
+ $('#linkDashboardUrl').attr('href', dashboardUrl);
+
+ var allWebsitesDashboardUrl = urlPath + '?module=Widgetize&action=iframe&moduleToWidgetize=MultiSites&actionToWidgetize=standalone&idSite=' + piwik.idSite + '&period=week&date=yesterday';
+ $('#exportAllWebsitesDashboard').html(
+ widgetized.getInputFormWithHtml('allWebsitesDashboardEmbed', '<iframe src="' + allWebsitesDashboardUrl + '" frameborder="0" marginheight="0" marginwidth="0" width="100%" height="100%"></iframe>')
+ );
+ $('#linkAllWebsitesDashboardUrl').attr('href', allWebsitesDashboardUrl);
+ $('#widgetPreview').widgetPreview({
+ onPreviewLoaded: widgetized.callbackAddExportButtonsUnderWidget
+ });
});
-});
-{/literal}
+ {/literal}
</script>
<div class="top_controls_inner">
@@ -78,24 +81,34 @@ $(document).ready( function() {
</div>
<div class="widgetize">
- <p>With Piwik, you can export your Web Analytics reports on your blog, website, or intranet dashboard... in one click.
- <p><b>&rsaquo; Widget authentication:</b> If you want your widgets to be viewable by everybody, you first have to set the 'view' permissions
- to the anonymous user in the <a href='index.php?module=UsersManager' target='_blank'>Users Management section</a>.
- <br />Alternatively, if you are publishing widgets on a password protected or private page,
- you don't necessarily have to allow 'anonymous' to view your reports. In this case, you can add the secret token_auth parameter (found in the <a href='{url module=API action=listAllAPI}' target='_blank'>API page</a>) in the widget URL.
- </p>
- <p><b>&rsaquo; Widgetize the full dashboard:</b> You can also display the full Piwik dashboard in your application or website in an IFRAME (<a href='' target='_blank' id='linkDashboardUrl'>see example</a>).
- The date parameter can be set to a specific calendar date, "today", or "yesterday". The period parameter can be set to "day", "week", "month", or "year".
- The language parameter can be set to the language code of a translation, such as language=fr.
- For example, for idSite=1 and date=yesterday, you can write: <span id='exportFullDashboard'></span>
- </p>
- <p><b>&rsaquo; Widgetize the all websites dashboard in an IFRAME</b> (<a href='' target='_blank' id='linkAllWebsitesDashboardUrl'>see example</a>) <span id='exportAllWebsitesDashboard'></span>
- </p>
- <p> <b>&rsaquo; Select a report, and copy paste in your page the embed code below the widget:</b>
+ <p>With Piwik, you can export your Web Analytics reports on your blog, website, or intranet dashboard... in one click.
+
+ <p><b>&rsaquo; Widget authentication:</b> If you want your widgets to be viewable by everybody, you first have to set the 'view' permissions
+ to the anonymous user in the <a href='index.php?module=UsersManager' target='_blank'>Users Management section</a>.
+ <br/>Alternatively, if you are publishing widgets on a password protected or private page,
+ you don't necessarily have to allow 'anonymous' to view your reports. In this case, you can add the secret token_auth parameter (found in the <a
+ href='{url module=API action=listAllAPI}' target='_blank'>API page</a>) in the widget URL.
+ </p>
+
+ <p><b>&rsaquo; Widgetize the full dashboard:</b> You can also display the full Piwik dashboard in your application or website in an IFRAME (<a href=''
+ target='_blank'
+ id='linkDashboardUrl'>see
+ example</a>).
+ The date parameter can be set to a specific calendar date, "today", or "yesterday". The period parameter can be set to "day", "week", "month", or
+ "year".
+ The language parameter can be set to the language code of a translation, such as language=fr.
+ For example, for idSite=1 and date=yesterday, you can write: <span id='exportFullDashboard'></span>
+ </p>
+
+ <p><b>&rsaquo; Widgetize the all websites dashboard in an IFRAME</b> (<a href='' target='_blank' id='linkAllWebsitesDashboardUrl'>see example</a>) <span
+ id='exportAllWebsitesDashboard'></span>
+ </p>
+
+ <p><b>&rsaquo; Select a report, and copy paste in your page the embed code below the widget:</b>
<div id="widgetPreview"></div>
- <div id='iframeDivToExport' style='display:none;'></div>
+ <div id='iframeDivToExport' style='display:none;'></div>
</div>
diff --git a/plugins/Widgetize/templates/js.tpl b/plugins/Widgetize/templates/js.tpl
index 8c81a44bf1..8976875413 100644
--- a/plugins/Widgetize/templates/js.tpl
+++ b/plugins/Widgetize/templates/js.tpl
@@ -1,18 +1,30 @@
{loadJavascriptTranslations disableOutputScriptTag=1 plugins='CoreHome'}
-document.write('<link rel="stylesheet" type="text/css" href="{$piwikUrl}themes/default/common.css" />');
-document.write('<link rel="stylesheet" type="text/css" href="{$piwikUrl}plugins/CoreHome/templates/styles.css" />');
-document.write('<link rel="stylesheet" type="text/css" href="{$piwikUrl}plugins/CoreHome/templates/datatable.css" />');
-document.write('<link rel="stylesheet" type="text/css" href="{$piwikUrl}plugins/CoreHome/templates/cloud.css" />');
+document.write('
+<link rel="stylesheet" type="text/css" href="{$piwikUrl}themes/default/common.css"/>');
+document.write('
+<link rel="stylesheet" type="text/css" href="{$piwikUrl}plugins/CoreHome/templates/styles.css"/>');
+document.write('
+<link rel="stylesheet" type="text/css" href="{$piwikUrl}plugins/CoreHome/templates/datatable.css"/>');
+document.write('
+<link rel="stylesheet" type="text/css" href="{$piwikUrl}plugins/CoreHome/templates/cloud.css"/>');
-document.write('<scr'+'ipt type="text/javascript" src="{$piwikUrl}libs/jquery/jquery.js"></scr'+'ipt>');
-document.write('<scr'+'ipt type="text/javascript" src="{$piwikUrl}libs/jquery/jquery-ui.js"></scr'+'ipt>');
-document.write('<scr'+'ipt type="text/javascript" src="{$piwikUrl}libs/jquery/jquery.tooltip.js"></scr'+'ipt>');
-document.write('<scr'+'ipt type="text/javascript" src="{$piwikUrl}libs/jquery/jquery.truncate.js"></scr'+'ipt>');
-document.write('<scr'+'ipt type="text/javascript" src="{$piwikUrl}libs/javascript/sprintf.js"></scr'+'ipt>');
-document.write('<scr'+'ipt type="text/javascript" src="{$piwikUrl}themes/default/common.js"></scr'+'ipt>');
-document.write('<scr'+'ipt type="text/javascript" src="{$piwikUrl}plugins/CoreHome/templates/datatable.js"></scr'+'ipt>');
+document.write('
+<scr'+'ipt type="text/javascript" src="{$piwikUrl}libs/jquery/jquery.js"></scr'+'ipt>');
+document.write('
+<scr'+'ipt type="text/javascript" src="{$piwikUrl}libs/jquery/jquery-ui.js"></scr'+'ipt>');
+document.write('
+<scr'+'ipt type="text/javascript" src="{$piwikUrl}libs/jquery/jquery.tooltip.js"></scr'+'ipt>');
+document.write('
+<scr'+'ipt type="text/javascript" src="{$piwikUrl}libs/jquery/jquery.truncate.js"></scr'+'ipt>');
+document.write('
+<scr'+'ipt type="text/javascript" src="{$piwikUrl}libs/javascript/sprintf.js"></scr'+'ipt>');
+document.write('
+<scr'+'ipt type="text/javascript" src="{$piwikUrl}themes/default/common.js"></scr'+'ipt>');
+document.write('
+<scr'+'ipt type="text/javascript" src="{$piwikUrl}plugins/CoreHome/templates/datatable.js"></scr'+'ipt>');
var content = '{$content|escape:'javascript'}';
-document.write('<scr'+'ipt type="text/javascript">document.write(content)</scr'+'ipt>');
+document.write('
+<scr'+'ipt type="text/javascript">document.write(content)</scr'+'ipt>');
diff --git a/plugins/Widgetize/templates/test_jsinclude.tpl b/plugins/Widgetize/templates/test_jsinclude.tpl
index b2416acee3..10800a14c2 100644
--- a/plugins/Widgetize/templates/test_jsinclude.tpl
+++ b/plugins/Widgetize/templates/test_jsinclude.tpl
@@ -6,12 +6,15 @@
<h2>Test tag cloud in a JS include</h2>
<div style="width:500px">
-<script type="text/javascript" src="{$url1}"></script>
-<noscript>Powered by <a href="http://piwik.org">Piwik</a></div></noscript>
+ <script type="text/javascript" src="{$url1}"></script>
+ <noscript>Powered by <a href="http://piwik.org">Piwik</a>
+</div>
+</noscript>
</div>
<p>This text is after the JS INCLUDE</p>
<h2>Test calling the API in Javascript</h2>
+
<p>using a javascript include. This is a dirty way (much better to ajax call the API!) but an interesting show case.</P>
<script type="text/javascript" src="{$url2}"></script>
<noscript>Powered by <a href="http://piwik.org">Piwik</a></div></noscript>
diff --git a/plugins/Widgetize/templates/widgetize.js b/plugins/Widgetize/templates/widgetize.js
index ec8595eabf..e54210642f 100644
--- a/plugins/Widgetize/templates/widgetize.js
+++ b/plugins/Widgetize/templates/widgetize.js
@@ -5,84 +5,78 @@
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
-function widgetize()
-{
- var self = this;
-
- this.getInputFormWithHtml = function(inputId, htmlEmbed)
- {
- return '<input class="formEmbedCode" id="'+inputId+'" value="'+ htmlEmbed.replace(/"/g, '&quot;') +'" onclick="javascript:document.getElementById(\''+inputId+'\').focus();document.getElementById(\''+inputId+'\').select();" readonly="true" type="text" />';
- }
-
- this.getEmbedUrl = function( parameters, exportFormat )
- {
- copyParameters = {};
- for(var variableName in parameters) {
- copyParameters[variableName] = parameters[variableName];
- }
- copyParameters['moduleToWidgetize'] = parameters['module'];
- copyParameters['actionToWidgetize'] = parameters['action'];
- delete copyParameters['action'];
- delete copyParameters['module'];
- var sourceUrl;
- sourceUrl = document.location.protocol + '//' + document.location.hostname + (document.location.port == '' ? '' : (':' + document.location.port)) + document.location.pathname + '?';
- sourceUrl += "module=Widgetize" +
- "&action="+exportFormat+
- "&"+piwikHelper.getQueryStringFromParameters(copyParameters)+
- "&idSite="+piwik.idSite+
- "&period="+piwik.period+
- "&date="+broadcast.getValueFromUrl('date')+
- "&disableLink=1&widget=1";
- return sourceUrl;
- }
-
- this.htmlentities = function(s)
- {
- return s.replace( /[<>&]/g, function(m) { return "&" + m.charCodeAt(0) + ";"; });
- }
-
- this.callbackAddExportButtonsUnderWidget = function ( widgetUniqueId,
- loadedWidgetElement)
- {
- widget = widgetsHelper.getWidgetObjectFromUniqueId(widgetUniqueId);
- widgetName = widget["name"];
- widgetParameters = widget['parameters'];
-
- var exportButtonsElement = $('<span id="exportButtons">');
+function widgetize() {
+ var self = this;
- var urlIframe = self.getEmbedUrl(widgetParameters, "iframe");
- // We first build the HTML code that will load the widget in an IFRAME
- var widgetIframeHtml = '<div id="widgetIframe">'+
- '<iframe width="100%" height="350" src="'+
- urlIframe +
- '" scrolling="no" frameborder="0" marginheight="0" marginwidth="0">'+
- '</iframe>'+
- '</div>';
+ this.getInputFormWithHtml = function (inputId, htmlEmbed) {
+ return '<input class="formEmbedCode" id="' + inputId + '" value="' + htmlEmbed.replace(/"/g, '&quot;') + '" onclick="javascript:document.getElementById(\'' + inputId + '\').focus();document.getElementById(\'' + inputId + '\').select();" readonly="true" type="text" />';
+ }
- // Add the input field containing the widget in an Iframe
- $(exportButtonsElement).append(
- '<div id="embedThisWidgetIframe">'+
- '<label for="embedThisWidgetIframeInput">&rsaquo; Embed Iframe</label>'+
- '<span id="embedThisWidgetIframeInput">'+
- self.getInputFormWithHtml('iframeEmbed', widgetIframeHtml)+
- '</span>'+
- '</div>' +
- '<div> <label for="embedThisWidgetDirectLink">&rsaquo; Direct Link</label>'+
- '<span id="embedThisWidgetDirectLink"> '+self.getInputFormWithHtml('directLinkEmbed', urlIframe)+' - <a href="'+urlIframe+'" target="_blank">'+_pk_translate('General_OpenInNewWindow_js')+'</a></span>'
- +'</div>'
- );
-
- // We then replace the div iframeDivToExport with the actual Iframe html
- $('#iframeDivToExport')
- .html(widgetIframeHtml);
+ this.getEmbedUrl = function (parameters, exportFormat) {
+ copyParameters = {};
+ for (var variableName in parameters) {
+ copyParameters[variableName] = parameters[variableName];
+ }
+ copyParameters['moduleToWidgetize'] = parameters['module'];
+ copyParameters['actionToWidgetize'] = parameters['action'];
+ delete copyParameters['action'];
+ delete copyParameters['module'];
+ var sourceUrl;
+ sourceUrl = document.location.protocol + '//' + document.location.hostname + (document.location.port == '' ? '' : (':' + document.location.port)) + document.location.pathname + '?';
+ sourceUrl += "module=Widgetize" +
+ "&action=" + exportFormat +
+ "&" + piwikHelper.getQueryStringFromParameters(copyParameters) +
+ "&idSite=" + piwik.idSite +
+ "&period=" + piwik.period +
+ "&date=" + broadcast.getValueFromUrl('date') +
+ "&disableLink=1&widget=1";
+ return sourceUrl;
+ }
- // Finally we append the content to the parent widget DIV
- $(loadedWidgetElement)
- .parent()
- .append(exportButtonsElement);
-
- // JS is buggy at least on IE
- //var widgetJS = '<script type="text/javascript" src="'+ getEmbedUrl(pluginId, actionId, "js") +'"></scr'+'ipt>';
- //divEmbedThisWidget.append('<br />Embed JS: '+ getInputFormWithHtml('javascriptEmbed', widgetJS));
- }
+ this.htmlentities = function (s) {
+ return s.replace(/[<>&]/g, function (m) { return "&" + m.charCodeAt(0) + ";"; });
+ }
+
+ this.callbackAddExportButtonsUnderWidget = function (widgetUniqueId, loadedWidgetElement) {
+ widget = widgetsHelper.getWidgetObjectFromUniqueId(widgetUniqueId);
+ widgetName = widget["name"];
+ widgetParameters = widget['parameters'];
+
+ var exportButtonsElement = $('<span id="exportButtons">');
+
+ var urlIframe = self.getEmbedUrl(widgetParameters, "iframe");
+ // We first build the HTML code that will load the widget in an IFRAME
+ var widgetIframeHtml = '<div id="widgetIframe">' +
+ '<iframe width="100%" height="350" src="' +
+ urlIframe +
+ '" scrolling="no" frameborder="0" marginheight="0" marginwidth="0">' +
+ '</iframe>' +
+ '</div>';
+
+ // Add the input field containing the widget in an Iframe
+ $(exportButtonsElement).append(
+ '<div id="embedThisWidgetIframe">' +
+ '<label for="embedThisWidgetIframeInput">&rsaquo; Embed Iframe</label>' +
+ '<span id="embedThisWidgetIframeInput">' +
+ self.getInputFormWithHtml('iframeEmbed', widgetIframeHtml) +
+ '</span>' +
+ '</div>' +
+ '<div> <label for="embedThisWidgetDirectLink">&rsaquo; Direct Link</label>' +
+ '<span id="embedThisWidgetDirectLink"> ' + self.getInputFormWithHtml('directLinkEmbed', urlIframe) + ' - <a href="' + urlIframe + '" target="_blank">' + _pk_translate('General_OpenInNewWindow_js') + '</a></span>'
+ + '</div>'
+ );
+
+ // We then replace the div iframeDivToExport with the actual Iframe html
+ $('#iframeDivToExport')
+ .html(widgetIframeHtml);
+
+ // Finally we append the content to the parent widget DIV
+ $(loadedWidgetElement)
+ .parent()
+ .append(exportButtonsElement);
+
+ // JS is buggy at least on IE
+ //var widgetJS = '<script type="text/javascript" src="'+ getEmbedUrl(pluginId, actionId, "js") +'"></scr'+'ipt>';
+ //divEmbedThisWidget.append('<br />Embed JS: '+ getInputFormWithHtml('javascriptEmbed', widgetJS));
+ }
}