summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Bugzilla/CGI.pm8
-rw-r--r--extensions/GoogleAnalytics/Config.pm16
-rw-r--r--extensions/GoogleAnalytics/Extension.pm23
-rw-r--r--extensions/GoogleAnalytics/lib/Config.pm41
-rw-r--r--extensions/GoogleAnalytics/template/en/default/admin/params/googleanalytics.html.tmpl20
-rw-r--r--extensions/GoogleAnalytics/template/en/default/hook/global/header-additional_header.html.tmpl24
-rw-r--r--extensions/GoogleAnalytics/template/en/default/hook/global/header-start.html.tmpl14
-rw-r--r--extensions/GoogleAnalytics/web/js/analytics.js21
-rw-r--r--extensions/GoogleAnalytics/web/js/dnt-helper.js54
-rwxr-xr-xindex.cgi2
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;
+};
diff --git a/index.cgi b/index.cgi
index d73ccd5d9..63865f908 100755
--- a/index.cgi
+++ b/index.cgi
@@ -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(