diff options
-rw-r--r-- | Bugzilla/CGI.pm | 8 | ||||
-rw-r--r-- | extensions/GoogleAnalytics/Config.pm | 16 | ||||
-rw-r--r-- | extensions/GoogleAnalytics/Extension.pm | 23 | ||||
-rw-r--r-- | extensions/GoogleAnalytics/lib/Config.pm | 41 | ||||
-rw-r--r-- | extensions/GoogleAnalytics/template/en/default/admin/params/googleanalytics.html.tmpl | 20 | ||||
-rw-r--r-- | extensions/GoogleAnalytics/template/en/default/hook/global/header-additional_header.html.tmpl | 24 | ||||
-rw-r--r-- | extensions/GoogleAnalytics/template/en/default/hook/global/header-start.html.tmpl | 14 | ||||
-rw-r--r-- | extensions/GoogleAnalytics/web/js/analytics.js | 21 | ||||
-rw-r--r-- | extensions/GoogleAnalytics/web/js/dnt-helper.js | 54 | ||||
-rwxr-xr-x | index.cgi | 2 |
10 files changed, 218 insertions, 5 deletions
diff --git a/Bugzilla/CGI.pm b/Bugzilla/CGI.pm index 248ee12cb..ea8d96da8 100644 --- a/Bugzilla/CGI.pm +++ b/Bugzilla/CGI.pm @@ -34,9 +34,9 @@ BEGIN { sub DEFAULT_CSP { my %policy = ( default_src => [ 'self' ], - script_src => [ 'self', 'unsafe-inline', 'unsafe-eval' ], + script_src => [ 'self', 'unsafe-inline', 'unsafe-eval', 'https://www.google-analytics.com' ], child_src => [ 'self', ], - img_src => [ 'self', 'https://secure.gravatar.com' ], + img_src => [ 'self', 'https://secure.gravatar.com', 'https://www.google-analytics.com' ], style_src => [ 'self', 'unsafe-inline' ], object_src => [ 'none' ], form_action => [ @@ -61,9 +61,9 @@ sub DEFAULT_CSP { sub SHOW_BUG_MODAL_CSP { my ($bug_id) = @_; my %policy = ( - script_src => ['self', 'nonce', 'unsafe-inline', 'unsafe-eval' ], + script_src => ['self', 'nonce', 'unsafe-inline', 'unsafe-eval', 'https://www.google-analytics.com' ], object_src => [correct_urlbase() . "extensions/BugModal/web/ZeroClipboard/ZeroClipboard.swf"], - img_src => [ 'self', 'https://secure.gravatar.com' ], + img_src => [ 'self', 'https://secure.gravatar.com', 'https://www.google-analytics.com' ], connect_src => [ 'self', # This is from extensions/OrangeFactor/web/js/orange_factor.js diff --git a/extensions/GoogleAnalytics/Config.pm b/extensions/GoogleAnalytics/Config.pm new file mode 100644 index 000000000..f4699db37 --- /dev/null +++ b/extensions/GoogleAnalytics/Config.pm @@ -0,0 +1,16 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This Source Code Form is "Incompatible With Secondary Licenses", as +# defined by the Mozilla Public License, v. 2.0. + +package Bugzilla::Extension::GoogleAnalytics; + +use 5.10.1; +use strict; +use warnings; + +use constant NAME => 'GoogleAnalytics'; + +__PACKAGE__->NAME; diff --git a/extensions/GoogleAnalytics/Extension.pm b/extensions/GoogleAnalytics/Extension.pm new file mode 100644 index 000000000..e9b144da4 --- /dev/null +++ b/extensions/GoogleAnalytics/Extension.pm @@ -0,0 +1,23 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This Source Code Form is "Incompatible With Secondary Licenses", as +# defined by the Mozilla Public License, v. 2.0. + +package Bugzilla::Extension::GoogleAnalytics; + +use 5.10.1; +use strict; +use warnings; +use parent qw(Bugzilla::Extension); + +our $VERSION = '0.1'; + +sub config_add_panels { + my ($self, $args) = @_; + my $modules = $args->{panel_modules}; + $modules->{GoogleAnalytics} = "Bugzilla::Extension::GoogleAnalytics::Config"; +} + +__PACKAGE__->NAME; diff --git a/extensions/GoogleAnalytics/lib/Config.pm b/extensions/GoogleAnalytics/lib/Config.pm new file mode 100644 index 000000000..f9e003ce0 --- /dev/null +++ b/extensions/GoogleAnalytics/lib/Config.pm @@ -0,0 +1,41 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This Source Code Form is "Incompatible With Secondary Licenses", as +# defined by the Mozilla Public License, v. 2.0. + +package Bugzilla::Extension::GoogleAnalytics::Config; + +use 5.10.1; +use strict; +use warnings; + +use Bugzilla::Config::Common; + +sub get_param_list { + my ($class) = @_; + + my @params = ( + { + name => 'google_analytics_tracking_id', + type => 't', + default => '', + checker => sub { + my ($tracking_id) = (@_); + + return 'must be like UA-XXXXXX-X' unless $tracking_id =~ m{^(UA-[[:xdigit:]]+-[[:xdigit:]]+)?$}; + return ''; + } + }, + { + name => 'google_analytics_debug', + type => 'b', + default => 0 + }, + ); + + return @params; +} + +1; diff --git a/extensions/GoogleAnalytics/template/en/default/admin/params/googleanalytics.html.tmpl b/extensions/GoogleAnalytics/template/en/default/admin/params/googleanalytics.html.tmpl new file mode 100644 index 000000000..3fdce57e6 --- /dev/null +++ b/extensions/GoogleAnalytics/template/en/default/admin/params/googleanalytics.html.tmpl @@ -0,0 +1,20 @@ +[%# This Source Code Form is subject to the terms of the Mozilla Public + # License, v. 2.0. If a copy of the MPL was not distributed with this + # file, You can obtain one at http://mozilla.org/MPL/2.0/. + # + # This Source Code Form is "Incompatible With Secondary Licenses", as + # defined by the Mozilla Public License, v. 2.0. + #%] + +[% + title = "Google Analytics" + desc = "Configure Google Analytics" +%] + +[% + param_descs = { + google_analytics_tracking_id => "Google Analytics Tracking ID", + google_analytics_debug => "If this option is set, the debug version of the analytics.js " _ + "library will be used.", + } +%] diff --git a/extensions/GoogleAnalytics/template/en/default/hook/global/header-additional_header.html.tmpl b/extensions/GoogleAnalytics/template/en/default/hook/global/header-additional_header.html.tmpl new file mode 100644 index 000000000..fc0ebc480 --- /dev/null +++ b/extensions/GoogleAnalytics/template/en/default/hook/global/header-additional_header.html.tmpl @@ -0,0 +1,24 @@ +[%# This Source Code Form is subject to the terms of the Mozilla Public + # License, v. 2.0. If a copy of the MPL was not distributed with this + # file, You can obtain one at http://mozilla.org/MPL/2.0/. + # + # This Source Code Form is "Incompatible With Secondary Licenses", as + # defined by the Mozilla Public License, v. 2.0. + #%] + +[%# Disable tracking of security group members as well as private bugs #%] +[% RETURN IF !Bugzilla.params.google_analytics_tracking_id || + user.in_group(Param('insidergroup')) || (bug.defined && bug.groups_in.size) %] + +<meta name="google-analytics" content="[% Bugzilla.params.google_analytics_tracking_id FILTER html %]" data-location=" + [%~ urlbase FILTER html %][% template.name.replace('\.html\.tmpl$', '') FILTER html ~%] + " data-title=" + [%~ IF template.name == 'pages/user_activity.html.tmpl' ~%] + User Activity Report + [%~ ELSIF template.name == 'pages/user_profile.html.tmpl' ~%] + User Profile + [%~ ELSE ~%] + [% title FILTER html | collapse %] + [%~ END ~%]"> +<script async src="https://www.google-analytics.com/analytics + [%~ "_debug" IF Bugzilla.params.google_analytics_debug ~%].js"></script> diff --git a/extensions/GoogleAnalytics/template/en/default/hook/global/header-start.html.tmpl b/extensions/GoogleAnalytics/template/en/default/hook/global/header-start.html.tmpl new file mode 100644 index 000000000..27a8587ef --- /dev/null +++ b/extensions/GoogleAnalytics/template/en/default/hook/global/header-start.html.tmpl @@ -0,0 +1,14 @@ +[%# This Source Code Form is subject to the terms of the Mozilla Public + # License, v. 2.0. If a copy of the MPL was not distributed with this + # file, You can obtain one at http://mozilla.org/MPL/2.0/. + # + # This Source Code Form is "Incompatible With Secondary Licenses", as + # defined by the Mozilla Public License, v. 2.0. + #%] + +[% IF !javascript_urls %] + [% javascript_urls = [] %] +[% END %] + +[% javascript_urls.push('extensions/GoogleAnalytics/web/js/analytics.js') %] +[% javascript_urls.push('extensions/GoogleAnalytics/web/js/dnt-helper.js') %] diff --git a/extensions/GoogleAnalytics/web/js/analytics.js b/extensions/GoogleAnalytics/web/js/analytics.js new file mode 100644 index 000000000..25f7d7527 --- /dev/null +++ b/extensions/GoogleAnalytics/web/js/analytics.js @@ -0,0 +1,21 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This Source Code Form is "Incompatible With Secondary Licenses", as + * defined by the Mozilla Public License, v. 2.0. */ + +$(function() { + var meta = $('meta[name="google-analytics"]'); + + if (typeof Mozilla.dntEnabled === 'function' && !Mozilla.dntEnabled() && meta.length) { + // Activate Google Analytics + window.ga=window.ga||function(){(ga.q=ga.q||[]).push(arguments)};ga.l=+new Date; + ga('create', meta.attr('content'), 'auto'); + ga('set', 'anonymizeIp', true); + ga('set', 'location', meta.data('location')); + ga('set', 'title', meta.data('title')); + // Track page view + ga('send', 'pageview'); + } +}); diff --git a/extensions/GoogleAnalytics/web/js/dnt-helper.js b/extensions/GoogleAnalytics/web/js/dnt-helper.js new file mode 100644 index 000000000..828fdc7ea --- /dev/null +++ b/extensions/GoogleAnalytics/web/js/dnt-helper.js @@ -0,0 +1,54 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// create namespace +if (typeof Mozilla === 'undefined') { + var Mozilla = {}; +} + +/** + * Returns true or false based on whether doNotTrack is enabled. It also takes into account the + * anomalies, such as !bugzilla 887703, which effect versions of Fx 31 and lower. It also handles + * IE versions on Windows 7, 8 and 8.1, where the DNT implementation does not honor the spec. + * @see https://bugzilla.mozilla.org/show_bug.cgi?id=1217896 for more details + * @params {string} [dnt] - An optional mock doNotTrack string to ease unit testing. + * @params {string} [ua] - An optional mock userAgent string to ease unit testing. + * @returns {boolean} true if enabled else false + */ +Mozilla.dntEnabled = function(dnt, ua) { + 'use strict'; + + // for old version of IE we need to use the msDoNotTrack property of navigator + // on newer versions, and newer platforms, this is doNotTrack but, on the window object + // Safari also exposes the property on the window object. + var dntStatus = dnt || navigator.doNotTrack || window.doNotTrack || navigator.msDoNotTrack; + var userAgent = ua || navigator.userAgent; + + // List of Windows versions known to not implement DNT according to the standard. + var anomalousWinVersions = ['Windows NT 6.1', 'Windows NT 6.2', 'Windows NT 6.3']; + + var fxMatch = userAgent.match(/Firefox\/(\d+)/); + var ieRegEx = /MSIE|Trident/i; + var isIE = ieRegEx.test(userAgent); + // Matches from Windows up to the first occurance of ; un-greedily + // http://www.regexr.com/3c2el + var platform = userAgent.match(/Windows.+?(?=;)/g); + + // With old versions of IE, DNT did not exist so we simply return false; + if (isIE && typeof Array.prototype.indexOf !== 'function') { + return false; + } else if (fxMatch && parseInt(fxMatch[1], 10) < 32) { + // Can't say for sure if it is 1 or 0, due to Fx bug 887703 + dntStatus = 'Unspecified'; + } else if (isIE && platform && anomalousWinVersions.indexOf(platform.toString()) !== -1) { + // default is on, which does not honor the specification + dntStatus = 'Unspecified'; + } else { + // sets dntStatus to Disabled or Enabled based on the value returned by the browser. + // If dntStatus is undefined, it will be set to Unspecified + dntStatus = { '0': 'Disabled', '1': 'Enabled' }[dntStatus] || 'Unspecified'; + } + + return dntStatus === 'Enabled' ? true : false; +}; @@ -64,7 +64,7 @@ if ($can_cache && $if_none_match && any { $_ eq $weak_etag } split(/,\s*/, $if_n } else { my $template = Bugzilla->template; - $cgi->content_security_policy(script_src => ['self']); + $cgi->content_security_policy(script_src => ['self', 'https://www.google-analytics.com']); # Return the appropriate HTTP response headers. print $cgi->header( |