summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDylan William Hardison <dylan@hardison.net>2018-08-04 18:24:15 +0200
committerDylan William Hardison <dylan@hardison.net>2018-08-04 18:24:15 +0200
commitf44392e8cdbea85ac308b2472f813ee605ebae4b (patch)
tree6e7adaf99a0e5a43eb1bf5a0d673d86b60f34f99
parent5be3a7fd0061aa0bc3059e09079741873b9b833f (diff)
parent4528b21bc922f8b1e0ba8581d230a492aa43c9cf (diff)
downloadbugzilla-f44392e8cdbea85ac308b2472f813ee605ebae4b.tar.gz
bugzilla-f44392e8cdbea85ac308b2472f813ee605ebae4b.tar.xz
Merge branch 'mojo-poc'
-rw-r--r--.circleci/checksetup_answers.legacy.txt1
-rw-r--r--.circleci/checksetup_answers.txt1
-rw-r--r--.circleci/config.yml237
-rw-r--r--.perlcriticrc2
-rw-r--r--Bugzilla.pm1
-rw-r--r--Bugzilla/CGI.pm15
-rw-r--r--Bugzilla/Config/Common.pm14
-rw-r--r--Bugzilla/Config/General.pm11
-rw-r--r--Bugzilla/Constants.pm6
-rw-r--r--Bugzilla/DB.pm8
-rw-r--r--Bugzilla/DB/Mysql.pm116
-rw-r--r--Bugzilla/DB/Schema/Mysql.pm27
-rw-r--r--Bugzilla/DaemonControl.pm6
-rw-r--r--Bugzilla/Install/DB.pm12
-rw-r--r--Bugzilla/Memcached.pm2
-rw-r--r--Bugzilla/PatchReader/Raw.pm1
-rw-r--r--Bugzilla/Quantum.pm191
-rw-r--r--Bugzilla/Quantum/CGI.pm97
-rw-r--r--Bugzilla/Quantum/Plugin/BasicAuth.pm40
-rw-r--r--Bugzilla/Quantum/Plugin/BlockIP.pm24
-rw-r--r--Bugzilla/Quantum/Plugin/Glue.pm41
-rw-r--r--Bugzilla/Quantum/SES.pm89
-rw-r--r--Bugzilla/Quantum/Static.pm4
-rw-r--r--Bugzilla/Quantum/Stdout.pm41
-rw-r--r--Bugzilla/Template.pm12
-rw-r--r--Bugzilla/User.pm39
-rw-r--r--Bugzilla/Util.pm4
-rw-r--r--Bugzilla/WebService/Server/REST/Resources/BugUserLastVisit.pm9
-rw-r--r--Bugzilla/WebService/Server/REST/Resources/User.pm5
-rw-r--r--Bugzilla/WebService/User.pm66
-rwxr-xr-xMakefile.PL4
-rwxr-xr-xbuglist.cgi42
-rw-r--r--docs/en/rst/administering/parameters.rst6
-rw-r--r--docs/en/rst/api/core/v1/bug-user-last-visit.rst2
-rw-r--r--docs/en/rst/style.rst5
-rwxr-xr-xeditparams.cgi3
-rw-r--r--extensions/BMO/Extension.pm92
-rw-r--r--extensions/BMO/template/en/default/bug/create/create-swag.html.tmpl159
-rw-r--r--extensions/BMO/template/en/default/hook/global/header-external-links.html.tmpl2
-rw-r--r--extensions/BMO/template/en/default/hook/reports/components-start.html.tmpl10
-rw-r--r--extensions/BMO/template/en/default/pages/group_members.html.tmpl2
-rw-r--r--extensions/BMO/template/en/default/reports/components.html.tmpl99
-rw-r--r--extensions/BMO/web/js/edituser_menu.js2
-rw-r--r--extensions/BugModal/lib/MonkeyPatches.pm25
-rw-r--r--extensions/BugModal/template/en/default/bug_modal/activity_stream.html.tmpl20
-rw-r--r--extensions/BugModal/template/en/default/bug_modal/edit.html.tmpl94
-rw-r--r--extensions/BugModal/template/en/default/bug_modal/header.html.tmpl4
-rw-r--r--extensions/BugModal/template/en/default/bug_modal/user.html.tmpl6
-rw-r--r--extensions/BugModal/web/bug_modal.css52
-rw-r--r--extensions/BugModal/web/bug_modal.js80
-rw-r--r--extensions/BugmailFilter/template/en/default/account/prefs/bugmail_filter.html.tmpl2
-rw-r--r--extensions/ComponentWatching/Extension.pm33
-rw-r--r--extensions/ComponentWatching/lib/WebService.pm113
-rw-r--r--extensions/ComponentWatching/template/en/default/hook/reports/components-component_footer.html.tmpl10
-rw-r--r--extensions/ComponentWatching/template/en/default/hook/reports/components-product_header.html.tmpl10
-rw-r--r--extensions/ComponentWatching/template/en/default/hook/reports/components-start.html.tmpl12
-rw-r--r--extensions/ComponentWatching/web/js/overlay.js218
-rw-r--r--extensions/GoogleAnalytics/web/js/analytics.js1
-rw-r--r--extensions/Gravatar/template/en/default/hook/bug/comments-user-image.html.tmpl6
-rw-r--r--extensions/PhabBugz/lib/Feed.pm36
-rw-r--r--extensions/PhabBugz/lib/Project.pm8
-rw-r--r--extensions/PhabBugz/lib/Revision.pm2
-rw-r--r--extensions/PhabBugz/lib/User.pm16
-rw-r--r--extensions/PhabBugz/lib/Util.pm3
-rw-r--r--extensions/PhabBugz/lib/WebService.pm71
-rw-r--r--extensions/Review/Extension.pm49
-rw-r--r--extensions/UserProfile/template/en/default/hook/account/prefs/account-start.html.tmpl2
-rwxr-xr-xheartbeat.cgi9
-rw-r--r--js/dropdown.js69
-rw-r--r--js/field.js2
-rw-r--r--js/global.js41
-rw-r--r--qa/t/lib/QA/Util.pm28
-rw-r--r--qa/t/test_bug_edit.t117
-rw-r--r--qa/t/test_shutdown.t72
-rwxr-xr-xscripts/entrypoint.pl13
-rwxr-xr-xscripts/generate_conduit_data.pl35
-rwxr-xr-xscripts/remove-non-public-data.pl2
-rw-r--r--skins/standard/describecomponents.css97
-rw-r--r--skins/standard/global.css465
-rw-r--r--skins/standard/reports.css97
-rw-r--r--t/phabbugz.t243
-rw-r--r--template/en/default/admin/params/editparams.html.tmpl1
-rw-r--r--template/en/default/admin/params/general.html.tmpl5
-rw-r--r--template/en/default/global/field-descs.none.tmpl10
-rw-r--r--template/en/default/global/header.html.tmpl10
-rw-r--r--template/en/default/global/messages.html.tmpl64
-rw-r--r--template/en/default/reports/components.html.tmpl111
-rw-r--r--template/en/default/reports/menu.html.tmpl5
-rw-r--r--template/en/default/setup/strings.txt.pl7
-rwxr-xr-xvagrant_support/hypnotoad1
-rw-r--r--vagrant_support/my.cnf13
-rw-r--r--vagrant_support/playbook.yml3
92 files changed, 2280 insertions, 1663 deletions
diff --git a/.circleci/checksetup_answers.legacy.txt b/.circleci/checksetup_answers.legacy.txt
index 759ee081a..2a0486836 100644
--- a/.circleci/checksetup_answers.legacy.txt
+++ b/.circleci/checksetup_answers.legacy.txt
@@ -8,3 +8,4 @@ $answer{'create_htaccess'} = '';
$answer{'cvsbin'} = '/usr/bin/cvs';
$answer{'diffpath'} = '/usr/bin';
$answer{'interdiffbin'} = '/usr/bin/interdiff';
+$answer{'utf8'} = 'utf8mb4';
diff --git a/.circleci/checksetup_answers.txt b/.circleci/checksetup_answers.txt
index d75fcc5dc..272c436c0 100644
--- a/.circleci/checksetup_answers.txt
+++ b/.circleci/checksetup_answers.txt
@@ -12,3 +12,4 @@ $answer{'interdiffbin'} = '/usr/bin/interdiff';
$answer{'urlbase'} = 'http://bmo.test/';
$answer{'mail_delivery_method'} = 'Test';
$answer{'auth_delegation'} = 1;
+$answer{'utf8'} = 'utf8mb4';
diff --git a/.circleci/config.yml b/.circleci/config.yml
deleted file mode 100644
index 5b18fac1a..000000000
--- a/.circleci/config.yml
+++ /dev/null
@@ -1,237 +0,0 @@
-# References:
-# 1. https://circleci.com/blog/how-to-build-a-docker-image-on-circleci-2-0/
-# 2. https://circleci.com/docs/2.0/building-docker-images/
-#
-
-version: 2
-
-defaults:
- bmo_slim_image: &bmo_slim_image
- image: bugzilla/harmony-slim:20180318.1
- user: app
-
- mysql_image: &mysql_image
- image: mozillabteam/bmo-mysql:5.6
-
- store_log: &store_log
- store_artifacts:
- path: /app/bugzilla.log
- destination: bugzilla.log
-
- main_filters: &main_filters
- branches:
- ignore:
- - /^(?:release|test)-20\d\d\d\d\d\d\.\d+/
- - /\//
- - production
-
- bmo_env: &bmo_env
- PORT: 8000
- LOGGING_PORT: 5880
- LOCALCONFIG_ENV: 1
- LOG4PERL_CONFIG_FILE: log4perl-test.conf
- BMO_db_user: bugs
- BMO_db_host: 127.0.0.1
- BMO_db_pass: bugs
- BMO_db_name: bugs
- BMO_memcached_servers: localhost:11211
- BMO_memcached_namespace: "bugzilla:"
- BMO_urlbase: AUTOMATIC
- HTTPD_StartServers: 1
- HTTPD_MinSpareServers: 1
- HTTPD_MaxSpareServers: 1
- HTTPD_ServerLimit: 1
- HTTPD_MaxClients: 1
- HTTPD_MaxRequestsPerChild: 4000
-
- mysql_env: &mysql_env
- MYSQL_DATABASE: bugs
- MYSQL_USER: bugs
- MYSQL_PASSWORD: bugs
- MYSQL_ALLOW_EMPTY_PASSWORD: 1
-
- docker_oldtests: &docker_oldtests
- - <<: *bmo_slim_image
- environment:
- <<: *bmo_env
- BZ_QA_CONF_FILE: /app/.circleci/selenium_test.conf
- BZ_QA_ANSWERS_FILE: /app/.circleci/checksetup_answers.legacy.txt
- BZ_QA_LEGACY_MODE: 1
- - <<: *mysql_image
- environment: *mysql_env
- - image: selenium/standalone-firefox:2.53.1
- - image: memcached:latest
-
- default_qa_setup: &default_qa_setup
- run:
- name: default qa setup
- command: |
- mv /opt/bmo/local /app/local
- perl -MSys::Hostname -i -pE 's/bmo.test/hostname() . ":$ENV{PORT}"/ges' $BZ_QA_CONF_FILE
- /app/scripts/entrypoint.pl checksetup_gen_files --default-localconfig
- mkdir artifacts
-
-jobs:
- build:
- working_directory: /app
- docker:
- - image: docker:17.06.1-ce
- steps:
- - setup_remote_docker
- - run:
- name: install git and ssh
- command: apk update && apk add git openssh-client
- - checkout
- - run: |
- docker build \
- --build-arg CI="$CI" \
- --build-arg CIRCLE_SHA1="$CIRCLE_SHA1" \
- --build-arg CIRCLE_BUILD_URL="$CIRCLE_BUILD_URL" \
- -t bmo .
- - attach_workspace:
- at: /app/build_info
- - run: "docker run --name bmo --entrypoint true bmo"
- - run: "docker cp bmo:/app/version.json build_info/version.json"
- - store_artifacts:
- path: /app/build_info
- - *store_log
- - deploy:
- command: |
- exit 0
-
- test_sanity:
- parallelism: 1
- working_directory: /app
- docker:
- - <<: *bmo_slim_image
- environment: *bmo_env
- steps:
- - checkout
- - run: |
- mv /opt/bmo/local /app/local
- mkdir artifacts
- - run: |
-<<<<<<< HEAD
-=======
- [[ -f build_info/only_version_changed.txt ]] && exit 0
- perl -I/app -I/app/local/lib/perl5 -c -E 'use Bugzilla; BEGIN { Bugzilla->extensions }'
- - run: |
- [[ -f build_info/only_version_changed.txt ]] && exit 0
->>>>>>> bmo/master
- perl Makefile.PL
- - run:
- name: run sanity tests
- command: |
- /app/scripts/entrypoint.pl prove -qf $(circleci tests glob 't/*.t' | circleci tests split) | tee artifacts/$CIRCLE_JOB.txt
- - store_artifacts:
- path: /app/artifacts
- - *store_log
-
- test_webservices:
- parallelism: 1
- working_directory: /app
- docker: *docker_oldtests
- steps:
- - checkout
- - *default_qa_setup
- - run: |
- /app/scripts/entrypoint.pl load_test_data
- - run: |
- /app/scripts/entrypoint.pl test_webservices | tee artifacts/$CIRCLE_JOB.txt
- - store_artifacts:
- path: /app/artifacts
- - *store_log
-
- test_selenium:
- parallelism: 1
- working_directory: /app
- docker: *docker_oldtests
- steps:
- - checkout
- - *default_qa_setup
- - run: |
- /app/scripts/entrypoint.pl load_test_data --legacy
- - run: |
- /app/scripts/entrypoint.pl test_selenium | tee artifacts/$CIRCLE_JOB.txt
- - store_artifacts:
- path: /app/artifacts
- - *store_log
-
- migrate_4.2_mysql:
- parallelism: 1
- working_directory: /app
- docker:
- - <<: *bmo_slim_image
- environment:
- <<: *bmo_env
- BMO_urlbase: http://localhost/
- DB_DUMP_URL: "https://github.com/bugzilla/harmony-test-fixtures/blob/master/db-dumps/bugzilla-4.2_mysql.sql.gz?raw=true"
- DB_DUMP_FILE: "bugzilla-4.2_mysql.sql"
- - <<: *mysql_image
- environment: *mysql_env
- steps:
- - checkout
- - run: |
- mv /opt/bmo/local /app/local
- /app/scripts/entrypoint.pl wait_for_db
- curl -L "$DB_DUMP_URL" > "$DB_DUMP_FILE"
- zcat "$DB_DUMP_FILE" | mysql -u$BMO_db_user -p$BMO_db_pass -h $BMO_db_host $BMO_db_name
- perl checksetup.pl
-
-
- test_bmo:
- parallelism: 1
- working_directory: /app
- docker:
- - <<: *bmo_slim_image
- environment:
- <<: *bmo_env
- BZ_QA_ANSWERS_FILE: /app/.circleci/checksetup_answers.txt
- TWD_HOST: localhost
- TWD_PORT: 4444
- TWD_BROWSER: firefox
- - <<: *mysql_image
- environment: *mysql_env
- - image: memcached:latest
- - image: selenium/standalone-firefox:2.53.1
- steps:
- - checkout
- - run: |
- mv /opt/bmo/local /app/local
- /app/scripts/entrypoint.pl checksetup_gen_files
- /app/scripts/entrypoint.pl load_test_data
- mkdir artifacts
- - run: |
- /app/scripts/entrypoint.pl test_bmo -q -f t/bmo/*.t
- - *store_log
-
-workflows:
- version: 2
- main:
- jobs:
- - build:
- filters: *main_filters
- requires:
- - test_sanity
- - test_bmo
- - test_webservices
- - test_selenium
- - test_sanity:
- filters: *main_filters
- - migrate_4.2_mysql:
- filters: *main_filters
- - test_bmo:
- filters: *main_filters
- - test_webservices:
- filters: *main_filters
- - test_selenium:
- filters: *main_filters
- requires:
- - test_sanity
- - migrate_4.2_mysql
- - build:
- filters: *main_filters
- requires:
- - test_sanity
- - test_bmo
- - test_webservices
diff --git a/.perlcriticrc b/.perlcriticrc
index 0b8e4c862..d5fc03fa0 100644
--- a/.perlcriticrc
+++ b/.perlcriticrc
@@ -5,6 +5,8 @@ severity = 1
#perltidyrc = .perltidyrc
#severity = 2
+[-CodeLayout::ProhibitParensWithBuiltins]
+
[InputOutput::RequireCheckedSyscalls]
severity = 2
functions = :builtins
diff --git a/Bugzilla.pm b/Bugzilla.pm
index f6c9abad2..a6f4e2b4d 100644
--- a/Bugzilla.pm
+++ b/Bugzilla.pm
@@ -830,6 +830,7 @@ sub markdown_parser {
# Per-process cleanup. Note that this is a plain subroutine, not a method,
# so we don't have $class available.
+*cleanup = \&_cleanup;
sub _cleanup {
return if $^C;
diff --git a/Bugzilla/CGI.pm b/Bugzilla/CGI.pm
index e50b394fb..9ac01c71e 100644
--- a/Bugzilla/CGI.pm
+++ b/Bugzilla/CGI.pm
@@ -39,11 +39,13 @@ sub DEFAULT_CSP {
script_src => [ 'self', 'nonce', 'unsafe-inline', 'https://www.google-analytics.com' ],
frame_src => [ 'none', ],
worker_src => [ 'none', ],
- img_src => [ 'self', 'https://secure.gravatar.com', 'https://www.google-analytics.com' ],
+ img_src => [ 'self', 'https://secure.gravatar.com' ],
style_src => [ 'self', 'unsafe-inline' ],
object_src => [ 'none' ],
connect_src => [
'self',
+ # This is for extensions/GoogleAnalytics using beacon or XHR
+ 'https://www.google-analytics.com',
# This is from extensions/OrangeFactor/web/js/orange_factor.js
'https://treeherder.mozilla.org/api/failurecount/',
],
@@ -70,9 +72,11 @@ sub SHOW_BUG_MODAL_CSP {
my ($bug_id) = @_;
my %policy = (
script_src => ['self', 'nonce', 'unsafe-inline', 'unsafe-eval', 'https://www.google-analytics.com' ],
- img_src => [ 'self', 'https://secure.gravatar.com', 'https://www.google-analytics.com' ],
+ img_src => [ 'self', 'https://secure.gravatar.com' ],
connect_src => [
'self',
+ # This is for extensions/GoogleAnalytics using beacon or XHR
+ 'https://www.google-analytics.com',
# This is from extensions/OrangeFactor/web/js/orange_factor.js
'https://treeherder.mozilla.org/api/failurecount/',
],
@@ -137,6 +141,7 @@ sub new {
# apache collapses // to / in $ENV{PATH_INFO} but not in $self->path_info.
# url() requires the full path in ENV in order to generate the correct url.
$ENV{PATH_INFO} = $path;
+ DEBUG("redirecting because we see PATH_INFO and don't like it");
print $self->redirect($self->url(-path => 0, -query => 1));
exit;
}
@@ -147,6 +152,7 @@ sub new {
# Redirect to urlbase if we are not viewing an attachment.
if ($self->url_is_attachment_base and $script ne 'attachment.cgi') {
+ DEBUG("Redirecting to urlbase because the url is in the attachment base and not attachment.cgi");
$self->redirect_to_urlbase();
}
@@ -611,7 +617,7 @@ sub header {
return '';
}
else {
- return $headers;
+ LOGDIE("Bugzilla::CGI->header() should only be called from inside Bugzilla::Quantum::CGI!");
}
}
@@ -729,6 +735,7 @@ sub redirect {
return $self->SUPER::redirect(@_);
}
+use Bugzilla::Logging;
# This helps implement Bugzilla::Search::Recent, and also shortens search
# URLs that get POSTed to buglist.cgi.
sub redirect_search_url {
@@ -777,6 +784,7 @@ sub redirect_search_url {
# are only redirected if they're under the CGI_URI_LIMIT though.
my $self_url = $self->self_url();
if ($self->request_method() ne 'POST' or length($self_url) < CGI_URI_LIMIT) {
+ DEBUG("Redirecting search url");
print $self->redirect(-url => $self_url);
exit;
}
@@ -798,6 +806,7 @@ sub redirect_to_https {
# XML-RPC clients (SOAP::Lite at least) require a 301 to redirect properly
# and do not work with 302. Our redirect really is permanent anyhow, so
# it doesn't hurt to make it a 301.
+ DEBUG("Redirecting to https");
print $self->redirect(-location => $url, -status => 301);
exit;
}
diff --git a/Bugzilla/Config/Common.pm b/Bugzilla/Config/Common.pm
index fabf7c880..24b636099 100644
--- a/Bugzilla/Config/Common.pm
+++ b/Bugzilla/Config/Common.pm
@@ -83,14 +83,16 @@ sub check_email {
sub check_utf8 {
- my $utf8 = shift;
+ my ($utf8, $entry) = @_;
- # You cannot turn off the UTF-8 parameter if you've already converted
- # your tables to utf-8.
- my $dbh = Bugzilla->dbh;
- if ( $dbh->isa('Bugzilla::DB::Mysql') && $dbh->bz_db_is_utf8 && !$utf8 ) {
- return "You cannot disable UTF-8 support, because your MySQL database" . " is encoded in UTF-8";
+ # You cannot turn off the UTF-8 parameter.
+ if ( !$utf8 ) {
+ return "You cannot disable UTF-8 support.";
}
+ elsif ($entry eq 'utf8mb4' && $utf8 ne 'utf8mb4') {
+ return "You cannot disable UTF8-MB4 support.";
+ }
+
return "";
}
diff --git a/Bugzilla/Config/General.pm b/Bugzilla/Config/General.pm
index 7e1c812c1..fa7cf2d08 100644
--- a/Bugzilla/Config/General.pm
+++ b/Bugzilla/Config/General.pm
@@ -41,18 +41,13 @@ use constant get_param_list => (
{
name => 'utf8',
- type => 'b',
- default => '0',
+ type => 's',
+ choices => [ '1', 'utf8', 'utf8mb4' ],
+ default => 'utf8',
checker => \&check_utf8
},
{
- name => 'shutdownhtml',
- type => 'l',
- default => ''
- },
-
- {
name => 'announcehtml',
type => 'l',
default => ''
diff --git a/Bugzilla/Constants.pm b/Bugzilla/Constants.pm
index d71ec25ee..34e4a4cfe 100644
--- a/Bugzilla/Constants.pm
+++ b/Bugzilla/Constants.pm
@@ -647,11 +647,7 @@ sub _bz_locations {
# directory under both mod_cgi and mod_perl. We call dirname twice
# to get the name of the directory above the "Bugzilla/" directory.
#
- # Calling dirname twice like that won't work on VMS or AmigaOS
- # but I doubt anybody runs Bugzilla on those.
- #
- # On mod_cgi this will be a relative path. On mod_perl it will be an
- # absolute path.
+ # Always use an absolute path, based on the location of this file.
my $libpath = realpath(dirname(dirname(__FILE__)));
# We have to detaint $libpath, but we can't use Bugzilla::Util here.
$libpath =~ /(.*)/;
diff --git a/Bugzilla/DB.pm b/Bugzilla/DB.pm
index ec0f058b9..f07bb7183 100644
--- a/Bugzilla/DB.pm
+++ b/Bugzilla/DB.pm
@@ -348,6 +348,14 @@ sub import {
$Exporter::ExportLevel-- if $is_exporter;
}
+sub sql_prefix_match {
+ my ($self, $column, $str) = @_;
+ my $must_escape = $str =~ s/([_%!])/!$1/g;
+ my $escape = $must_escape ? q/ESCAPE '!'/ : '';
+ my $quoted_str = $self->quote("$str%");
+ return "$column LIKE $quoted_str $escape";
+}
+
sub sql_istrcmp {
my ($self, $left, $right, $op) = @_;
$op ||= "=";
diff --git a/Bugzilla/DB/Mysql.pm b/Bugzilla/DB/Mysql.pm
index d0b9724eb..4dd2620d3 100644
--- a/Bugzilla/DB/Mysql.pm
+++ b/Bugzilla/DB/Mysql.pm
@@ -32,8 +32,9 @@ use Bugzilla::Util;
use Bugzilla::Error;
use Bugzilla::DB::Schema::Mysql;
-use List::Util qw(max);
+use List::Util qw(max any);
use Text::ParseWords;
+use Carp;
# This is how many comments of MAX_COMMENT_LENGTH we expect on a single bug.
# In reality, you could have a LOT more comments than this, because
@@ -52,9 +53,7 @@ sub BUILDARGS {
$dsn .= ";port=$port" if $port;
$dsn .= ";mysql_socket=$sock" if $sock;
- my %attrs = (
- mysql_enable_utf8 => Bugzilla->params->{'utf8'},
- );
+ my %attrs = ( mysql_enable_utf8 => 1 );
return { dsn => $dsn, user => $user, pass => $pass, attrs => \%attrs };
}
@@ -64,7 +63,9 @@ sub on_dbi_connected {
# This makes sure that if the tables are encoded as UTF-8, we
# return their data correctly.
- $dbh->do("SET NAMES utf8") if Bugzilla->params->{'utf8'};
+ my $charset = $class->utf8_charset;
+ my $collate = $class->utf8_collate;
+ $dbh->do("SET NAMES $charset COLLATE $collate");
# Bug 321645 - disable MySQL strict mode, if set
my ($var, $sql_mode) = $dbh->selectrow_array(
@@ -310,6 +311,26 @@ sub bz_setup_database {
die install_string('mysql_innodb_disabled');
}
+ if ($self->utf8_charset eq 'utf8mb4') {
+ my %global = map { @$_ } @{ $self->selectall_arrayref(q(SHOW GLOBAL VARIABLES LIKE 'innodb_%')) };
+ my $utf8mb4_supported
+ = $global{innodb_file_format} eq 'Barracuda'
+ && $global{innodb_file_per_table} eq 'ON'
+ && $global{innodb_large_prefix} eq 'ON';
+
+ die install_string('mysql_innodb_settings') unless $utf8mb4_supported;
+
+ my $tables = $self->selectall_arrayref('SHOW TABLE STATUS');
+ foreach my $table (@$tables) {
+ my ($table, undef, undef, $row_format) = @$table;
+ my $new_row_format = $self->default_row_format($table);
+ next if $new_row_format =~ /compact/i;
+ if (lc($new_row_format) ne lc($row_format)) {
+ print install_string('mysql_row_format_conversion', { table => $table, format => $new_row_format }), "\n";
+ $self->do(sprintf 'ALTER TABLE %s ROW_FORMAT=%s', $table, $new_row_format);
+ }
+ }
+ }
my ($sd_index_deleted, $longdescs_index_deleted);
my @tables = $self->bz_table_list_real();
@@ -345,9 +366,6 @@ sub bz_setup_database {
'SELECT TABLE_NAME FROM information_schema.TABLES
WHERE TABLE_SCHEMA = ? AND ENGINE = ?',
undef, $db_name, 'MyISAM');
- foreach my $should_be_myisam (Bugzilla::DB::Schema::Mysql::MYISAM_TABLES) {
- @$myisam_tables = grep { $_ ne $should_be_myisam } @$myisam_tables;
- }
if (scalar @$myisam_tables) {
print "Bugzilla now uses the InnoDB storage engine in MySQL for",
@@ -520,9 +538,7 @@ sub bz_setup_database {
# This kind of situation happens when people create the database
# themselves, and if we don't do this they will get the big
# scary WARNING statement about conversion to UTF8.
- if ( !$self->bz_db_is_utf8 && !@tables
- && (Bugzilla->params->{'utf8'} || !scalar keys %{Bugzilla->params}) )
- {
+ unless ( $self->bz_db_is_utf8 ) {
$self->_alter_db_charset_to_utf8();
}
@@ -559,11 +575,13 @@ sub bz_setup_database {
# the table charsets.
#
# TABLE_COLLATION IS NOT NULL prevents us from trying to convert views.
+ my $charset = $self->utf8_charset;
+ my $collate = $self->utf8_collate;
my $non_utf8_tables = $self->selectrow_array(
"SELECT 1 FROM information_schema.TABLES
WHERE TABLE_SCHEMA = ? AND TABLE_COLLATION IS NOT NULL
- AND TABLE_COLLATION NOT LIKE 'utf8%'
- LIMIT 1", undef, $db_name);
+ AND TABLE_COLLATION != ?
+ LIMIT 1", undef, $db_name, $collate);
if (Bugzilla->params->{'utf8'} && $non_utf8_tables) {
print "\n", install_string('mysql_utf8_conversion');
@@ -580,8 +598,7 @@ sub bz_setup_database {
}
}
- print "Converting table storage format to UTF-8. This may take a",
- " while.\n";
+ print "Converting table storage format to $charset (collate $collate). This may take a while.\n";
foreach my $table ($self->bz_table_list_real) {
my $info_sth = $self->prepare("SHOW FULL COLUMNS FROM $table");
$info_sth->execute();
@@ -594,11 +611,11 @@ sub bz_setup_database {
# If this particular column isn't stored in utf-8
if ($column->{Collation}
&& $column->{Collation} ne 'NULL'
- && $column->{Collation} !~ /utf8/)
+ && $column->{Collation} ne $collate)
{
my $name = $column->{Field};
- print "$table.$name needs to be converted to UTF-8...\n";
+ print "$table.$name needs to be converted to $charset (collate $collate)...\n";
# These will be automatically re-created at the end
# of checksetup.
@@ -618,7 +635,7 @@ sub bz_setup_database {
my ($binary, $utf8) = ($sql_def, $sql_def);
my $type = $self->_bz_schema->convert_type($col_info->{TYPE});
$binary =~ s/(\Q$type\E)/$1 CHARACTER SET binary/;
- $utf8 =~ s/(\Q$type\E)/$1 CHARACTER SET utf8/;
+ $utf8 =~ s/(\Q$type\E)/$1 CHARACTER SET $charset COLLATE $collate/;
push(@binary_sql, "MODIFY COLUMN $name $binary");
push(@utf8_sql, "MODIFY COLUMN $name $utf8");
}
@@ -639,7 +656,7 @@ sub bz_setup_database {
print "Converting the $table table to UTF-8...\n";
my $bin = "ALTER TABLE $table " . join(', ', @binary_sql);
my $utf = "ALTER TABLE $table " . join(', ', @utf8_sql,
- 'DEFAULT CHARACTER SET utf8');
+ "DEFAULT CHARACTER SET $charset COLLATE $collate");
$self->do($bin);
$self->do($utf);
@@ -649,7 +666,7 @@ sub bz_setup_database {
}
}
else {
- $self->do("ALTER TABLE $table DEFAULT CHARACTER SET utf8");
+ $self->do("ALTER TABLE $table DEFAULT CHARACTER SET $charset COLLATE $collate");
}
} # foreach my $table (@tables)
@@ -660,7 +677,7 @@ sub bz_setup_database {
# a mysqldump.) So we have this change outside of the above block,
# so that it just happens silently if no actual *table* conversion
# needs to happen.
- if (Bugzilla->params->{'utf8'} && !$self->bz_db_is_utf8) {
+ unless ($self->bz_db_is_utf8) {
$self->_alter_db_charset_to_utf8();
}
@@ -739,18 +756,69 @@ sub _fix_defaults {
}
}
+sub utf8_charset {
+ return 'utf8' unless Bugzilla->params->{'utf8'};
+ return Bugzilla->params->{'utf8'} eq 'utf8mb4' ? 'utf8mb4' : 'utf8';
+}
+
+sub utf8_collate {
+ my $charset = utf8_charset();
+ if ($charset eq 'utf8') {
+ return 'utf8_general_ci';
+ }
+ elsif ($charset eq 'utf8mb4') {
+ return 'utf8mb4_unicode_520_ci';
+ }
+ else {
+ croak "invalid charset: $charset";
+ }
+}
+
+sub default_row_format {
+ my ($class, $table) = @_;
+ my $charset = utf8_charset();
+ if ($charset eq 'utf8') {
+ return 'Compact';
+ }
+ elsif ($charset eq 'utf8mb4') {
+ my @no_compress = qw(
+ bug_user_last_visit
+ cc
+ email_rates
+ logincookies
+ token_data
+ tokens
+ ts_error
+ ts_exitstatus
+ ts_funcmap
+ ts_job
+ ts_note
+ user_request_log
+ votes
+ );
+ return 'Dynamic' if any { $table eq $_ } @no_compress;
+ return 'Compressed';
+ }
+ else {
+ croak "invalid charset: $charset";
+ }
+}
+
sub _alter_db_charset_to_utf8 {
my $self = shift;
my $db_name = Bugzilla->localconfig->{db_name};
- $self->do("ALTER DATABASE $db_name CHARACTER SET utf8");
+ my $charset = $self->utf8_charset;
+ my $collate = $self->utf8_collate;
+ $self->do("ALTER DATABASE $db_name CHARACTER SET $charset COLLATE $collate");
}
sub bz_db_is_utf8 {
my $self = shift;
- my $db_collation = $self->selectrow_arrayref(
+ my $db_charset = $self->selectrow_arrayref(
"SHOW VARIABLES LIKE 'character_set_database'");
# First column holds the variable name, second column holds the value.
- return $db_collation->[1] =~ /utf8/ ? 1 : 0;
+ my $charset = $self->utf8_charset;
+ return $db_charset->[1] eq $charset ? 1 : 0;
}
diff --git a/Bugzilla/DB/Schema/Mysql.pm b/Bugzilla/DB/Schema/Mysql.pm
index 5893c6a80..79814140a 100644
--- a/Bugzilla/DB/Schema/Mysql.pm
+++ b/Bugzilla/DB/Schema/Mysql.pm
@@ -76,8 +76,6 @@ use constant REVERSE_MAPPING => {
# as in their db-specific version, so no reverse mapping is needed.
};
-use constant MYISAM_TABLES => qw();
-
#------------------------------------------------------------------------------
sub _initialize {
@@ -120,16 +118,18 @@ sub _initialize {
} #eosub--_initialize
#------------------------------------------------------------------------------
sub _get_create_table_ddl {
- # Extend superclass method to specify the MYISAM storage engine.
# Returns a "create table" SQL statement.
-
my($self, $table) = @_;
-
- my $charset = "CHARACTER SET utf8";
- my $type = grep($_ eq $table, MYISAM_TABLES) ? 'MYISAM' : 'InnoDB';
- return($self->SUPER::_get_create_table_ddl($table)
- . " ENGINE = $type $charset");
-
+ my $charset = Bugzilla::DB::Mysql->utf8_charset;
+ my $collate = Bugzilla::DB::Mysql->utf8_collate;
+ my $row_format = Bugzilla::DB::Mysql->default_row_format($table);
+ my @parts = (
+ $self->SUPER::_get_create_table_ddl($table),
+ 'ENGINE = InnoDB',
+ "CHARACTER SET $charset COLLATE $collate",
+ "ROW_FORMAT=$row_format",
+ );
+ return join(' ', @parts);
} #eosub--_get_create_table_ddl
#------------------------------------------------------------------------------
sub _get_create_index_ddl {
@@ -153,10 +153,9 @@ sub get_create_database_sql {
my ($self, $name) = @_;
# We only create as utf8 if we have no params (meaning we're doing
# a new installation) or if the utf8 param is on.
- my $create_utf8 = Bugzilla->params->{'utf8'}
- || !defined Bugzilla->params->{'utf8'};
- my $charset = $create_utf8 ? "CHARACTER SET utf8" : '';
- return ("CREATE DATABASE $name $charset");
+ my $charset = Bugzilla::DB::Mysql->utf8_charset;
+ my $collate = Bugzilla::DB::Mysql->utf8_collate;
+ return ("CREATE DATABASE $name CHARACTER SET $charset COLLATE $collate");
}
# MySQL has a simpler ALTER TABLE syntax than ANSI.
diff --git a/Bugzilla/DaemonControl.pm b/Bugzilla/DaemonControl.pm
index d0d6af8f7..5cb32973f 100644
--- a/Bugzilla/DaemonControl.pm
+++ b/Bugzilla/DaemonControl.pm
@@ -41,14 +41,14 @@ our %EXPORT_TAGS = (
utils => [qw(catch_signal on_exception on_finish)],
);
-my $BUGZILLA_DIR = realpath(bz_locations->{cgi_path});
+my $BUGZILLA_DIR = bz_locations->{cgi_path};
my $JOBQUEUE_BIN = catfile( $BUGZILLA_DIR, 'jobqueue.pl' );
my $CEREAL_BIN = catfile( $BUGZILLA_DIR, 'scripts', 'cereal.pl' );
my $BUGZILLA_BIN = catfile( $BUGZILLA_DIR, 'bugzilla.pl' );
my $HYPNOTOAD_BIN = catfile( $BUGZILLA_DIR, 'local', 'bin', 'hypnotoad' );
my @PERL5LIB = ( $BUGZILLA_DIR, catdir($BUGZILLA_DIR, 'lib'), catdir($BUGZILLA_DIR, 'local', 'lib', 'perl5') );
-my %HTTP_COMMAND = (
+my %HTTP_BACKENDS = (
hypnotoad => [ $HYPNOTOAD_BIN, $BUGZILLA_BIN, '-f' ],
simple => [ $BUGZILLA_BIN, 'daemon' ],
);
@@ -108,7 +108,7 @@ sub run_httpd {
$ENV{BUGZILLA_HTTPD_ARGS} = encode_json(\@args);
$ENV{PERL5LIB} = join(':', @PERL5LIB);
my $backend = $ENV{HTTP_BACKEND} // 'hypnotoad';
- my $command = $HTTP_COMMAND{ $backend };
+ my $command = $HTTP_BACKENDS{ $backend };
exec @$command
or die "failed to exec $command->[0] $!";
},
diff --git a/Bugzilla/Install/DB.pm b/Bugzilla/Install/DB.pm
index 86edf8a30..8b3d4b8cc 100644
--- a/Bugzilla/Install/DB.pm
+++ b/Bugzilla/Install/DB.pm
@@ -773,6 +773,7 @@ sub update_table_definitions {
$dbh->bz_add_index('profiles', 'profiles_realname_ft_idx',
{TYPE => 'FULLTEXT', FIELDS => ['realname']});
+ _migrate_nicknames();
################################################################
# New --TABLE-- changes should go *** A B O V E *** this point #
@@ -3920,6 +3921,17 @@ sub _migrate_group_owners {
$dbh->do('UPDATE groups SET owner_user_id = ?', undef, $nobody->id);
}
+sub _migrate_nicknames {
+ my $dbh = Bugzilla->dbh;
+ my $sth = $dbh->prepare('SELECT userid FROM profiles WHERE realname LIKE "%:%" AND is_enabled = 1 AND NOT nickname');
+ $sth->execute();
+ while (my ($user_id) = $sth->fetchrow_array) {
+ my $user = Bugzilla::User->new($user_id);
+ $user->set_name($user->name);
+ $user->update();
+ }
+}
+
sub _migrate_preference_categories {
my $dbh = Bugzilla->dbh;
return if $dbh->bz_column_info('setting', 'category');
diff --git a/Bugzilla/Memcached.pm b/Bugzilla/Memcached.pm
index 40755aa29..6bbef080a 100644
--- a/Bugzilla/Memcached.pm
+++ b/Bugzilla/Memcached.pm
@@ -25,6 +25,8 @@ use Sys::Syslog qw(:DEFAULT);
use constant MAX_KEY_LENGTH => 250;
use constant RATE_LIMIT_PREFIX => "rate:";
+*new = \&_new;
+
sub _new {
my $invocant = shift;
my $class = ref($invocant) || $invocant;
diff --git a/Bugzilla/PatchReader/Raw.pm b/Bugzilla/PatchReader/Raw.pm
index 0a8387a15..bb5a6cefd 100644
--- a/Bugzilla/PatchReader/Raw.pm
+++ b/Bugzilla/PatchReader/Raw.pm
@@ -16,6 +16,7 @@ package Bugzilla::PatchReader::Raw;
use 5.10.1;
use strict;
use warnings;
+no warnings 'utf8';
use Bugzilla::PatchReader::Base;
diff --git a/Bugzilla/Quantum.pm b/Bugzilla/Quantum.pm
index 2519e23ad..135ff94a9 100644
--- a/Bugzilla/Quantum.pm
+++ b/Bugzilla/Quantum.pm
@@ -8,21 +8,21 @@
package Bugzilla::Quantum;
use Mojo::Base 'Mojolicious';
-use CGI::Compile; # Needed for its exit() overload
-use Bugzilla::Logging;
-use Bugzilla::Quantum::Template;
-use Bugzilla::Quantum::CGI;
-use Bugzilla::Quantum::Static;
+# Needed for its exit() overload, must happen early in execution.
+use CGI::Compile;
-use Bugzilla ();
-use Bugzilla::Constants qw(bz_locations);
+use Bugzilla ();
use Bugzilla::BugMail ();
-use Bugzilla::CGI ();
-use Bugzilla::Extension ();
+use Bugzilla::CGI ();
+use Bugzilla::Constants qw(bz_locations);
+use Bugzilla::Extension ();
use Bugzilla::Install::Requirements ();
+use Bugzilla::Logging;
+use Bugzilla::Quantum::CGI;
+use Bugzilla::Quantum::SES;
+use Bugzilla::Quantum::Static;
use Bugzilla::Util ();
use Cwd qw(realpath);
-
use MojoX::Log::Log4perl::Tiny;
has 'static' => sub { Bugzilla::Quantum::Static->new };
@@ -30,9 +30,11 @@ has 'static' => sub { Bugzilla::Quantum::Static->new };
sub startup {
my ($self) = @_;
+ DEBUG('Starting up');
$self->plugin('Bugzilla::Quantum::Plugin::Glue');
$self->plugin('Bugzilla::Quantum::Plugin::Hostage');
$self->plugin('Bugzilla::Quantum::Plugin::BlockIP');
+ $self->plugin('Bugzilla::Quantum::Plugin::BasicAuth');
if ( $self->mode ne 'development' ) {
$self->hook(
@@ -45,14 +47,15 @@ sub startup {
my $r = $self->routes;
Bugzilla::Quantum::CGI->load_all($r);
- Bugzilla::Quantum::CGI->load_one('bzapi_cgi', 'extensions/BzAPI/bin/rest.cgi');
+ Bugzilla::Quantum::CGI->load_one( 'bzapi_cgi', 'extensions/BzAPI/bin/rest.cgi' );
$r->any('/')->to('CGI#index_cgi');
$r->any('/rest')->to('CGI#rest_cgi');
- $r->any('/rest.cgi/*PATH_INFP')->to('CGI#rest_cgi' => { PATH_INFO => '' });
- $r->any('/rest/*PATH_INFO')->to( 'CGI#rest_cgi' => { PATH_INFO => '' });
+ $r->any('/rest.cgi/*PATH_INFO')->to( 'CGI#rest_cgi' => { PATH_INFO => '' } );
+ $r->any('/rest/*PATH_INFO')->to( 'CGI#rest_cgi' => { PATH_INFO => '' } );
$r->any('/bug/:id')->to('CGI#show_bug_cgi');
$r->any('/extensions/BzAPI/bin/rest.cgi/*PATH_INFO')->to('CGI#bzapi_cgi');
+
$r->get(
'/__lbheartbeat__' => sub {
my $c = shift;
@@ -60,140 +63,42 @@ sub startup {
},
);
- $r->get('/__heartbeat__')->to( 'CGI#heartbeat_cgi');
- $r->get('/robots.txt')->to( 'CGI#robots_cgi' );
-
- $r->any('/review')->to( 'CGI#page_cgi' => {'id' => 'splinter.html'});
- $r->any('/user_profile')->to( 'CGI#page_cgi' => {'id' => 'user_profile.html'});
- $r->any('/userprofile')->to( 'CGI#page_cgi' => {'id' => 'user_profile.html'});
- $r->any('/request_defer')->to( 'CGI#page_cgi' => {'id' => 'request_defer.html'});
- $r->any('/login')->to( 'CGI#index_cgi' => { 'GoAheadAndLogIn' => '1' });
-
- $r->any('/:new_bug' => [new_bug => qr{new[-_]bug}])->to( 'CGI#new_bug_cgi');
- $r->any('/:REWRITE_itrequest' => [REWRITE_itrequest => qr{form[\.:]itrequest}])->to(
- 'CGI#enter_bug_cgi' => { 'product' => 'Infrastructure & Operations', 'format' => 'itrequest' }
- );
- $r->any('/:REWRITE_mozlist' => [REWRITE_mozlist => qr{form[\.:]mozlist}])->to(
- 'CGI#enter_bug_cgi' => {'product' => 'mozilla.org', 'format' => 'mozlist'}
- );
- $r->any('/:REWRITE_poweredby' => [REWRITE_poweredby => qr{form[\.:]poweredby}])->to(
- 'CGI#enter_bug_cgi' => {'product' => 'mozilla.org', 'format' => 'poweredby'}
- );
- $r->any('/:REWRITE_presentation' => [REWRITE_presentation => qr{form[\.:]presentation}])->to(
- 'cgi#enter_bug_cgi' => {'product' => 'mozilla.org', 'format' => 'presentation'}
- );
- $r->any('/:REWRITE_trademark' => [REWRITE_trademark => qr{form[\.:]trademark}])->to(
- 'cgi#enter_bug_cgi' => {'product' => 'mozilla.org', 'format' => 'trademark'}
- );
- $r->any('/:REWRITE_recoverykey' => [REWRITE_recoverykey => qr{form[\.:]recoverykey}])->to(
- 'cgi#enter_bug_cgi' => {'product' => 'mozilla.org', 'format' => 'recoverykey'}
- );
- $r->any('/:REWRITE_legal' => [REWRITE_legal => qr{form[\.:]legal}])->to(
- 'CGI#enter_bug_cgi' => { 'product' => 'Legal', 'format' => 'legal' },
- );
- $r->any('/:REWRITE_recruiting' => [REWRITE_recruiting => qr{form[\.:]recruiting}])->to(
- 'CGI#enter_bug_cgi' => {'product' => 'Recruiting', 'format' => 'recruiting'}
- );
- $r->any('/:REWRITE_intern' => [REWRITE_intern => qr{form[\.:]intern}])->to(
- 'CGI#enter_bug_cgi' => {'product' => 'Recruiting', 'format' => 'intern'}
- );
- $r->any('/:REWRITE_mozpr' => [REWRITE_mozpr => qr{form[\.:]mozpr}])->to(
- 'CGI#enter_bug_cgi' => {'product' => 'Mozilla PR', 'format' => 'mozpr' },
- );
- $r->any('/:REWRITE_reps_mentorship' => [REWRITE_reps_mentorship => qr{form[\.:]reps[\.:]mentorship}])->to(
- 'CGI#enter_bug_cgi' => {'product' => 'Mozilla Reps','format' => 'mozreps' },
- );
- $r->any('/:REWRITE_reps_budget' => [REWRITE_reps_budget => qr{form[\.:]reps[\.:]budget}])->to(
- 'CGI#enter_bug_cgi' => {'product' => 'Mozilla Reps','format' => 'remo-budget'}
- );
- $r->any('/:REWRITE_reps_swag' => [REWRITE_reps_swag => qr{form[\.:]reps[\.:]swag}])->to(
- 'CGI#enter_bug_cgi' => {'product' => 'Mozilla Reps','format' => 'remo-swag'}
- );
- $r->any('/:REWRITE_reps_it' => [REWRITE_reps_it => qr{form[\.:]reps[\.:]it}])->to(
- 'CGI#enter_bug_cgi' => {'product' => 'Mozilla Reps','format' => 'remo-it'}
- );
- $r->any('/:REWRITE_reps_payment' => [REWRITE_reps_payment => qr{form[\.:]reps[\.:]payment}])->to(
- 'CGI#page_cgi' => {'id' => 'remo-form-payment.html'}
- );
- $r->any('/:REWRITE_csa_discourse' => [REWRITE_csa_discourse => qr{form[\.:]csa[\.:]discourse}])->to(
- 'CGI#enter_bug_cgi' => {'product' => 'Infrastructure & Operations', 'format' => 'csa-discourse'}
- );
- $r->any('/:REWRITE_employee_incident' => [REWRITE_employee_incident => qr{form[\.:]employee[\.\-:]incident}])->to(
- 'CGI#enter_bug_cgi' => {'product' => 'mozilla.org', 'format' => 'employee-incident'}
- );
- $r->any('/:REWRITE_brownbag' => [REWRITE_brownbag => qr{form[\.:]brownbag}])->to(
- 'CGI#https_air_mozilla_org_requests' => {}
- );
- $r->any('/:REWRITE_finance' => [REWRITE_finance => qr{form[\.:]finance}])->to(
- 'CGI#enter_bug_cgi' => {'product' => 'Finance','format' => 'finance'}
- );
- $r->any('/:REWRITE_moz_project_review' => [REWRITE_moz_project_review => qr{form[\.:]moz[\.\-:]project[\.\-:]review}])->to(
- 'CGI#enter_bug_cgi' => {'product' => 'mozilla.org','format' => 'moz-project-review'}
- );
- $r->any('/:REWRITE_docs' => [REWRITE_docs => qr{form[\.:]docs?}])->to(
- 'CGI#enter_bug_cgi' => {'product' => 'Developer Documentation','format' => 'doc'}
- );
- $r->any('/:REWRITE_mdn' => [REWRITE_mdn => qr{form[\.:]mdn?}])->to(
- 'CGI#enter_bug_cgi' => {'format' => 'mdn','product' => 'developer.mozilla.org'}
- );
- $r->any('/:REWRITE_swag_gear' => [REWRITE_swag_gear => qr{form[\.:](swag|gear)}])->to(
- 'CGI#enter_bug_cgi' => {'format' => 'swag','product' => 'Marketing'}
- );
- $r->any('/:REWRITE_costume' => [REWRITE_costume => qr{form[\.:]costume}])->to(
- 'CGI#enter_bug_cgi' => {'product' => 'Marketing','format' => 'costume'}
- );
- $r->any('/:REWRITE_ipp' => [REWRITE_ipp => qr{form[\.:]ipp}])->to(
- 'CGI#enter_bug_cgi' => {'product' => 'Internet Public Policy','format' => 'ipp'}
- );
- $r->any('/:REWRITE_creative' => [REWRITE_creative => qr{form[\.:]creative}])->to(
- 'CGI#enter_bug_cgi' => {'format' => 'creative','product' => 'Marketing'}
- );
- $r->any('/:REWRITE_user_engagement' => [REWRITE_user_engagement => qr{form[\.:]user[\.\-:]engagement}])->to(
- 'CGI#enter_bug_cgi' => {'format' => 'user-engagement','product' => 'Marketing'}
- );
- $r->any('/:REWRITE_dev_engagement_event' => [REWRITE_dev_engagement_event => qr{form[\.:]dev[\.\-:]engagement[\.\-\:]event}])->to(
- 'CGI#enter_bug_cgi' => {'product' => 'Developer Engagement','format' => 'dev-engagement-event'}
- );
- $r->any('/:REWRITE_mobile_compat' => [REWRITE_mobile_compat => qr{form[\.:]mobile[\.\-:]compat}])->to(
- 'CGI#enter_bug_cgi' => {'product' => 'Tech Evangelism','format' => 'mobile-compat'}
- );
- $r->any('/:REWRITE_web_bounty' => [REWRITE_web_bounty => qr{form[\.:]web[\.:]bounty}])->to(
- 'CGI#enter_bug_cgi' => {'format' => 'web-bounty','product' => 'mozilla.org'}
- );
- $r->any('/:REWRITE_automative' => [REWRITE_automative => qr{form[\.:]automative}])->to(
- 'CGI#enter_bug_cgi' => {'product' => 'Testing','format' => 'automative'}
- );
- $r->any('/:REWRITE_comm_newsletter' => [REWRITE_comm_newsletter => qr{form[\.:]comm[\.:]newsletter}])->to(
- 'CGI#enter_bug_cgi' => {'format' => 'comm-newsletter','product' => 'Marketing'}
- );
- $r->any('/:REWRITE_screen_share_whitelist' => [REWRITE_screen_share_whitelist => qr{form[\.:]screen[\.:]share[\.:]whitelist}])->to(
- 'CGI#enter_bug_cgi' => {'format' => 'screen-share-whitelist','product' => 'Firefox'}
- );
- $r->any('/:REWRITE_data_compliance' => [REWRITE_data_compliance => qr{form[\.:]data[\.\-:]compliance}])->to(
- 'CGI#enter_bug_cgi' => {'product' => 'Data Compliance','format' => 'data-compliance'}
- );
- $r->any('/:REWRITE_fsa_budget' => [REWRITE_fsa_budget => qr{form[\.:]fsa[\.:]budget}])->to(
- 'CGI#enter_bug_cgi' => {'product' => 'FSA','format' => 'fsa-budget'}
- );
- $r->any('/:REWRITE_triage_request' => [REWRITE_triage_request => qr{form[\.:]triage[\.\-]request}])->to(
- 'CGI#page_cgi' => {'id' => 'triage_request.html'}
- );
- $r->any('/:REWRITE_crm_CRM' => [REWRITE_crm_CRM => qr{form[\.:](crm|CRM)}])->to(
- 'CGI#enter_bug_cgi' => {'format' => 'crm','product' => 'Marketing'}
- );
- $r->any('/:REWRITE_nda' => [REWRITE_nda => qr{form[\.:]nda}])->to(
- 'CGI#enter_bug_cgi' => {'product' => 'Legal','format' => 'nda'}
- );
- $r->any('/:REWRITE_name_clearance' => [REWRITE_name_clearance => qr{form[\.:]name[\.:]clearance}])->to(
- 'CGI#enter_bug_cgi' => {'format' => 'name-clearance','product' => 'Legal'}
+ $r->get(
+ '/__version__' => sub {
+ my $c = shift;
+ $c->reply->file( $c->app->home->child('version.json') );
+ },
);
- $r->any('/:REWRITE_shield_studies' => [REWRITE_shield_studies => qr{form[\.:]shield[\.:]studies}])->to(
- 'CGI#enter_bug_cgi' => {'product' => 'Shield','format' => 'shield-studies'}
+
+ $r->get(
+ '/version.json' => sub {
+ my $c = shift;
+ $c->reply->file( $c->app->home->child('version.json') );
+ },
);
- $r->any('/:REWRITE_client_bounty' => [REWRITE_client_bounty => qr{form[\.:]client[\.:]bounty}])->to(
- 'CGI#enter_bug_cgi' => {'product' => 'Firefox','format' => 'client-bounty'}
+
+ $r->get('/__heartbeat__')->to('CGI#heartbeat_cgi');
+ $r->get('/robots.txt')->to('CGI#robots_cgi');
+
+ $r->any('/review')->to( 'CGI#page_cgi' => { 'id' => 'splinter.html' } );
+ $r->any('/user_profile')->to( 'CGI#page_cgi' => { 'id' => 'user_profile.html' } );
+ $r->any('/userprofile')->to( 'CGI#page_cgi' => { 'id' => 'user_profile.html' } );
+ $r->any('/request_defer')->to( 'CGI#page_cgi' => { 'id' => 'request_defer.html' } );
+ $r->any('/login')->to( 'CGI#index_cgi' => { 'GoAheadAndLogIn' => '1' } );
+
+ $r->any( '/:new_bug' => [ new_bug => qr{new[-_]bug} ] )->to('CGI#new_bug_cgi');
+
+ my $ses_auth = $r->under(
+ '/ses' => sub {
+ my ($c) = @_;
+ my $lc = Bugzilla->localconfig;
+
+ return $c->basic_auth( 'SES', $lc->{ses_username}, $lc->{ses_password} );
+ }
);
+ $ses_auth->any('/index.cgi')->to('SES#main');
+ Bugzilla::Hook::process( 'app_startup', { app => $self } );
}
1;
diff --git a/Bugzilla/Quantum/CGI.pm b/Bugzilla/Quantum/CGI.pm
index 16c733686..0a74f1ee5 100644
--- a/Bugzilla/Quantum/CGI.pm
+++ b/Bugzilla/Quantum/CGI.pm
@@ -9,57 +9,55 @@ package Bugzilla::Quantum::CGI;
use Mojo::Base 'Mojolicious::Controller';
use CGI::Compile;
-use Bugzilla::Constants qw(bz_locations);
-use Bugzilla::Quantum::Stdout;
-use File::Slurper qw(read_text);
-use File::Spec::Functions qw(catfile);
-use Sub::Name;
-use Sub::Quote 2.005000;
use Try::Tiny;
use Taint::Util qw(untaint);
-use Socket qw(AF_INET inet_aton);
use Sys::Hostname;
+use Sub::Quote 2.005000;
+use Sub::Name;
+use Socket qw(AF_INET inet_aton);
+use File::Spec::Functions qw(catfile);
+use File::Slurper qw(read_text);
use English qw(-no_match_vars);
+use Bugzilla::Quantum::Stdout;
+use Bugzilla::Constants qw(bz_locations);
our $C;
my %SEEN;
sub load_all {
- my ($class, $r) = @_;
+ my ( $class, $r ) = @_;
- foreach my $file (glob '*.cgi') {
- my $name = _file_to_method($file);
- $class->load_one($name, $file);
+ foreach my $file ( glob '*.cgi' ) {
+ my $name = _file_to_method($file);
+ $class->load_one( $name, $file );
$r->any("/$file")->to("CGI#$name");
}
}
sub load_one {
- my ($class, $name, $file) = @_;
- my $package = __PACKAGE__ . "::$name",
- my $inner_name = "_$name";
- my $content = read_text( catfile( bz_locations->{cgi_path}, $file ) );
+ my ( $class, $name, $file ) = @_;
+ my $package = __PACKAGE__ . "::$name", my $inner_name = "_$name";
+ my $content = read_text( catfile( bz_locations->{cgi_path}, $file ) );
$content = "package $package; $content";
untaint($content);
my %options = (
- package => $package,
- file => $file,
- line => 1,
+ package => $package,
+ file => $file,
+ line => 1,
no_defer => 1,
);
die "Tried to load $file more than once" if $SEEN{$file}++;
my $inner = quote_sub $inner_name, $content, {}, \%options;
my $wrapper = sub {
my ($c) = @_;
- my $stdin = $c->_STDIN;
- my $stdout = '';
- local $C = $c;
- local %ENV = $c->_ENV($file);
- local *STDIN; ## no critic (local)
+ my $stdin = $c->_STDIN;
+ local $C = $c;
+ local %ENV = $c->_ENV($file);
local $CGI::Compile::USE_REAL_EXIT = 0;
- local $PROGRAM_NAME = $file;
+ local $PROGRAM_NAME = $file;
+ local *STDIN; ## no critic (local)
open STDIN, '<', $stdin->path or die "STDIN @{[$stdin->path]}: $!" if -s $stdin->path;
- tie *STDOUT, 'Bugzilla::Quantum::Stdout', controller => $c; ## no critic (tie)
+ tie *STDOUT, 'Bugzilla::Quantum::Stdout', controller => $c; ## no critic (tie)
try {
Bugzilla->init_page();
$inner->();
@@ -70,66 +68,68 @@ sub load_one {
finally {
untie *STDOUT;
$c->finish;
- Bugzilla->_cleanup; ## no critic (private)
+ Bugzilla->cleanup;
CGI::initialize_globals();
};
};
- no strict 'refs'; ## no critic (strict)
- *{$name} = subname($name, $wrapper);
+ no strict 'refs'; ## no critic (strict)
+ *{$name} = subname( $name, $wrapper );
return 1;
}
+
sub _ENV {
- my ($c, $script_name) = @_;
- my $tx = $c->tx;
- my $req = $tx->req;
- my $headers = $req->headers;
+ my ( $c, $script_name ) = @_;
+ my $tx = $c->tx;
+ my $req = $tx->req;
+ my $headers = $req->headers;
my $content_length = $req->content->is_multipart ? $req->body_size : $headers->content_length;
- my %env_headers = ( HTTP_COOKIE => '', HTTP_REFERER => '' );
+ my %env_headers = ( HTTP_COOKIE => '', HTTP_REFERER => '' );
for my $name ( @{ $headers->names } ) {
my $key = uc "http_$name";
- $key =~ s!\W!_!g;
+ $key =~ s/\W/_/g;
$env_headers{$key} = $headers->header($name);
}
my $remote_user;
- if ( my $userinfo = $c->req->url->to_abs->userinfo ) {
+ if ( my $userinfo = $req->url->to_abs->userinfo ) {
$remote_user = $userinfo =~ /([^:]+)/ ? $1 : '';
}
elsif ( my $authenticate = $headers->authorization ) {
$remote_user = $authenticate =~ /Basic\s+(.*)/ ? b64_decode $1 : '';
$remote_user = $remote_user =~ /([^:]+)/ ? $1 : '';
}
- my $path_info = $c->param('PATH_INFO');
+ my $path_info = $c->stash->{'mojo.captures'}{'PATH_INFO'};
my %captures = %{ $c->stash->{'mojo.captures'} // {} };
- foreach my $key (keys %captures) {
- if ($key eq 'action' || $key eq 'PATH_INFO' || $key =~ /^REWRITE_/) {
+ foreach my $key ( keys %captures ) {
+ if ( $key eq 'controller' || $key eq 'action' || $key eq 'PATH_INFO' || $key =~ /^REWRITE_/ ) {
delete $captures{$key};
}
}
my $cgi_query = Mojo::Parameters->new(%captures);
- $cgi_query->append($req->url->query);
+ $cgi_query->append( $req->url->query );
+ my $prefix = $c->stash->{bmo_prefix} ? '/bmo/' : '/';
return (
%ENV,
CONTENT_LENGTH => $content_length || 0,
CONTENT_TYPE => $headers->content_type || '',
GATEWAY_INTERFACE => 'CGI/1.1',
- HTTPS => $req->is_secure ? 'YES' : 'NO',
+ HTTPS => $req->is_secure ? 'on' : 'off',
%env_headers,
- QUERY_STRING => $cgi_query->to_string,
- PATH_INFO => $path_info ? "/$path_info" : '',
- REMOTE_ADDR => $tx->remote_address,
- REMOTE_HOST => gethostbyaddr( inet_aton( $tx->remote_address || '127.0.0.1' ), AF_INET ) || '',
- REMOTE_PORT => $tx->remote_port,
- REMOTE_USER => $remote_user || '',
+ QUERY_STRING => $cgi_query->to_string,
+ PATH_INFO => $path_info ? "/$path_info" : '',
+ REMOTE_ADDR => $tx->original_remote_address,
+ REMOTE_HOST => $tx->original_remote_address,
+ REMOTE_PORT => $tx->remote_port,
+ REMOTE_USER => $remote_user || '',
REQUEST_METHOD => $req->method,
- SCRIPT_NAME => "/$script_name",
+ SCRIPT_NAME => "$prefix$script_name",
SERVER_NAME => hostname,
SERVER_PORT => $tx->local_port,
- SERVER_PROTOCOL => $req->is_secure ? 'HTTPS' : 'HTTP', # TODO: Version is missing
+ SERVER_PROTOCOL => $req->is_secure ? 'HTTPS' : 'HTTP', # TODO: Version is missing
SERVER_SOFTWARE => __PACKAGE__,
);
}
@@ -157,5 +157,4 @@ sub _file_to_method {
return $name;
}
-
1;
diff --git a/Bugzilla/Quantum/Plugin/BasicAuth.pm b/Bugzilla/Quantum/Plugin/BasicAuth.pm
new file mode 100644
index 000000000..e17273404
--- /dev/null
+++ b/Bugzilla/Quantum/Plugin/BasicAuth.pm
@@ -0,0 +1,40 @@
+# 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::Quantum::Plugin::BasicAuth;
+use 5.10.1;
+use Mojo::Base qw(Mojolicious::Plugin);
+
+use Bugzilla::Logging;
+use Carp;
+
+sub register {
+ my ( $self, $app, $conf ) = @_;
+
+ $app->renderer->add_helper(
+ basic_auth => sub {
+ my ( $c, $realm, $auth_user, $auth_pass ) = @_;
+ my $req = $c->req;
+ my ( $user, $password ) = $req->url->to_abs->userinfo =~ /^([^:]+):(.*)/;
+
+ unless ( $realm && $auth_user && $auth_pass ) {
+ croak 'basic_auth() called with missing parameters.';
+ }
+
+ unless ( $user eq $auth_user && $password eq $auth_pass ) {
+ WARN('username and password do not match');
+ $c->res->headers->www_authenticate("Basic realm=\"$realm\"");
+ $c->res->code(401);
+ $c->rendered;
+ return 0;
+ }
+
+ return 1;
+ }
+ );
+}
+
+1; \ No newline at end of file
diff --git a/Bugzilla/Quantum/Plugin/BlockIP.pm b/Bugzilla/Quantum/Plugin/BlockIP.pm
index fbfffad66..058ecbf64 100644
--- a/Bugzilla/Quantum/Plugin/BlockIP.pm
+++ b/Bugzilla/Quantum/Plugin/BlockIP.pm
@@ -4,38 +4,38 @@ use Mojo::Base 'Mojolicious::Plugin';
use Bugzilla::Memcached;
-use constant BLOCK_TIMEOUT => 60*60;
+use constant BLOCK_TIMEOUT => 60 * 60;
-my $MEMCACHED = Bugzilla::Memcached->_new()->{memcached};
+my $MEMCACHED = Bugzilla::Memcached->new()->{memcached};
sub register {
my ( $self, $app, $conf ) = @_;
- $app->hook(before_routes => \&_before_routes);
- $app->helper(block_ip => \&_block_ip);
- $app->helper(unblock_ip => \&_unblock_ip);
+ $app->hook( before_routes => \&_before_routes );
+ $app->helper( block_ip => \&_block_ip );
+ $app->helper( unblock_ip => \&_unblock_ip );
}
sub _block_ip {
- my ($class, $ip) = @_;
- $MEMCACHED->set("block_ip:$ip" => 1, BLOCK_TIMEOUT) if $MEMCACHED;
+ my ( $class, $ip ) = @_;
+ $MEMCACHED->set( "block_ip:$ip" => 1, BLOCK_TIMEOUT ) if $MEMCACHED;
}
sub _unblock_ip {
- my ($class, $ip) = @_;
+ my ( $class, $ip ) = @_;
$MEMCACHED->delete("block_ip:$ip") if $MEMCACHED;
}
sub _before_routes {
- my ( $c ) = @_;
+ my ($c) = @_;
return if $c->stash->{'mojo.static'};
my $ip = $c->tx->remote_address;
- if ($MEMCACHED && $MEMCACHED->get("block_ip:$ip")) {
+ if ( $MEMCACHED && $MEMCACHED->get("block_ip:$ip") ) {
$c->block_ip($ip);
$c->res->code(429);
- $c->res->message("Too Many Requests");
- $c->res->body("Too Many Requests");
+ $c->res->message('Too Many Requests');
+ $c->res->body('Too Many Requests');
$c->finish;
}
}
diff --git a/Bugzilla/Quantum/Plugin/Glue.pm b/Bugzilla/Quantum/Plugin/Glue.pm
index 54a360003..ea21429bd 100644
--- a/Bugzilla/Quantum/Plugin/Glue.pm
+++ b/Bugzilla/Quantum/Plugin/Glue.pm
@@ -11,7 +11,6 @@ use Mojo::Base 'Mojolicious::Plugin';
use Try::Tiny;
use Bugzilla::Constants;
-use Bugzilla::Quantum::Template;
use Bugzilla::Logging;
use Bugzilla::RNG ();
use JSON::MaybeXS qw(decode_json);
@@ -20,10 +19,10 @@ sub register {
my ( $self, $app, $conf ) = @_;
my %D;
- if ($ENV{BUGZILLA_HTTPD_ARGS}) {
- my $args = decode_json($ENV{BUGZILLA_HTTPD_ARGS});
+ if ( $ENV{BUGZILLA_HTTPD_ARGS} ) {
+ my $args = decode_json( $ENV{BUGZILLA_HTTPD_ARGS} );
foreach my $arg (@$args) {
- if ($arg =~ /^-D(\w+)$/) {
+ if ( $arg =~ /^-D(\w+)$/ ) {
$D{$1} = 1;
}
else {
@@ -35,6 +34,7 @@ sub register {
# hypnotoad is weird and doesn't look for MOJO_LISTEN itself.
$app->config(
hypnotoad => {
+ proxy => 1,
listen => [ $ENV{MOJO_LISTEN} ],
},
);
@@ -49,30 +49,32 @@ sub register {
sub {
Bugzilla::RNG::srand();
srand();
- eval { Bugzilla->dbh->ping };
+ try { Bugzilla->dbh->ping };
}
);
$app->hook(
before_dispatch => sub {
my ($c) = @_;
- if ($D{HTTPD_IN_SUBDIR}) {
+ if ( $D{HTTPD_IN_SUBDIR} ) {
my $path = $c->req->url->path;
- $path =~ s{^/bmo}{}s;
- $c->req->url->path($path);
+ if ( $path =~ s{^/bmo}{}s ) {
+ $c->stash->{bmo_prefix} = 1;
+ $c->req->url->path($path);
+ }
}
- Log::Log4perl::MDC->put(request_id => $c->req->request_id);
+ Log::Log4perl::MDC->put( request_id => $c->req->request_id );
}
);
Bugzilla::Extension->load_all();
- if ($app->mode ne 'development') {
+ if ( $app->mode ne 'development' ) {
Bugzilla->preload_features();
- DEBUG("preloading templates");
+ DEBUG('preloading templates');
Bugzilla->preload_templates();
- DEBUG("done preloading templates");
+ DEBUG('done preloading templates');
}
- $app->secrets([Bugzilla->localconfig->{side_wide_secret}]);
+ $app->secrets( [ Bugzilla->localconfig->{side_wide_secret} ] );
$app->renderer->add_handler(
'bugzilla' => sub {
@@ -90,23 +92,16 @@ sub register {
# The controller
$vars->{c} = $c;
my $name = $options->{template};
- unless ($name =~ /\./) {
+ unless ( $name =~ /\./ ) {
$name = sprintf '%s.%s.tmpl', $options->{template}, $options->{format};
}
my $template = Bugzilla->template;
$template->process( $name, $vars, $output )
- or die $template->error;
+ or die $template->error;
}
);
- $app->log(
- MojoX::Log::Log4perl::Tiny->new(
- logger => Log::Log4perl->get_logger(ref $app)
- )
- );
+ $app->log( MojoX::Log::Log4perl::Tiny->new( logger => Log::Log4perl->get_logger( ref $app ) ) );
}
-
-
-
1;
diff --git a/Bugzilla/Quantum/SES.pm b/Bugzilla/Quantum/SES.pm
index e36956b1d..47c591fb5 100644
--- a/Bugzilla/Quantum/SES.pm
+++ b/Bugzilla/Quantum/SES.pm
@@ -1,4 +1,4 @@
-#!/usr/bin/perl
+package Bugzilla::Quantum::SES;
# 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/.
@@ -7,12 +7,8 @@
# defined by the Mozilla Public License, v. 2.0.
use 5.10.1;
-use strict;
-use warnings;
+use Mojo::Base qw( Mojolicious::Controller );
-use lib qw(.. ../lib ../local/lib/perl5);
-
-use Bugzilla ();
use Bugzilla::Constants qw(ERROR_MODE_DIE);
use Bugzilla::Logging;
use Bugzilla::Mailer qw(MessageToMTA);
@@ -22,51 +18,44 @@ use JSON::MaybeXS qw(decode_json);
use LWP::UserAgent ();
use Try::Tiny qw(catch try);
-Bugzilla->error_mode(ERROR_MODE_DIE);
-try {
- main();
-}
-catch {
- FATAL("Fatal error: $_");
- respond( 500 => 'Internal Server Error' );
-};
-
sub main {
- my $message = decode_json_wrapper( Bugzilla->cgi->param('POSTDATA') ) // return;
- my $message_type = $ENV{HTTP_X_AMZ_SNS_MESSAGE_TYPE} // '(missing)';
+ my ($self) = @_;
+ Bugzilla->error_mode(ERROR_MODE_DIE);
+ my $message = $self->_decode_json_wrapper( $self->req->body ) // return;
+ my $message_type = $self->req->headers->header('X-Amz-SNS-Message-Type') // '(missing)';
if ( $message_type eq 'SubscriptionConfirmation' ) {
- confirm_subscription($message);
+ $self->_confirm_subscription($message);
}
elsif ( $message_type eq 'Notification' ) {
- my $notification = decode_json_wrapper( $message->{Message} ) // return;
+ my $notification = $self->_decode_json_wrapper( $message->{Message} ) // return;
unless (
# https://docs.aws.amazon.com/ses/latest/DeveloperGuide/event-publishing-retrieving-sns-contents.html
- handle_notification( $notification, 'eventType' )
+ $self->_handle_notification( $notification, 'eventType' )
# https://docs.aws.amazon.com/ses/latest/DeveloperGuide/notification-contents.html
- || handle_notification( $notification, 'notificationType' )
+ || $self->_handle_notification( $notification, 'notificationType' )
)
{
WARN('Failed to find notification type');
- respond( 400 => 'Bad Request' );
+ $self->_respond( 400 => 'Bad Request' );
}
}
else {
WARN("Unsupported message-type: $message_type");
- respond( 200 => 'OK' );
+ $self->_respond( 200 => 'OK' );
}
}
-sub confirm_subscription {
- my ($message) = @_;
+sub _confirm_subscription {
+ my ($self, $message) = @_;
my $subscribe_url = $message->{SubscribeURL};
if ( !$subscribe_url ) {
WARN('Bad SubscriptionConfirmation request: missing SubscribeURL');
- respond( 400 => 'Bad Request' );
+ $self->_respond( 400 => 'Bad Request' );
return;
}
@@ -74,15 +63,15 @@ sub confirm_subscription {
my $res = $ua->get( $message->{SubscribeURL} );
if ( !$res->is_success ) {
WARN( 'Bad response from SubscribeURL: ' . $res->status_line );
- respond( 400 => 'Bad Request' );
+ $self->_respond( 400 => 'Bad Request' );
return;
}
- respond( 200 => 'OK' );
+ $self->_respond( 200 => 'OK' );
}
-sub handle_notification {
- my ( $notification, $type_field ) = @_;
+sub _handle_notification {
+ my ( $self, $notification, $type_field ) = @_;
if ( !exists $notification->{$type_field} ) {
return 0;
@@ -90,20 +79,20 @@ sub handle_notification {
my $type = $notification->{$type_field};
if ( $type eq 'Bounce' ) {
- process_bounce($notification);
+ $self->_process_bounce($notification);
}
elsif ( $type eq 'Complaint' ) {
- process_complaint($notification);
+ $self->_process_complaint($notification);
}
else {
WARN("Unsupported notification-type: $type");
- respond( 200 => 'OK' );
+ $self->_respond( 200 => 'OK' );
}
return 1;
}
-sub process_bounce {
- my ($notification) = @_;
+sub _process_bounce {
+ my ($self, $notification) = @_;
# disable each account that is bouncing
foreach my $recipient ( @{ $notification->{bounce}->{bouncedRecipients} } ) {
@@ -140,10 +129,11 @@ sub process_bounce {
}
}
- respond( 200 => 'OK' );
+ $self->_respond( 200 => 'OK' );
}
-sub process_complaint {
+sub _process_complaint {
+ my ($self) = @_;
# email notification to bugzilla admin
my ($notification) = @_;
@@ -170,31 +160,28 @@ sub process_complaint {
MessageToMTA($message);
}
- respond( 200 => 'OK' );
+ $self->_respond( 200 => 'OK' );
}
-sub respond {
- my ( $code, $message ) = @_;
- print Bugzilla->cgi->header( -status => "$code $message" );
-
- # apache will generate non-200 response pages for us
- say html_quote($message) if $code == 200;
+sub _respond {
+ my ( $self, $code, $message ) = @_;
+ $self->render(text => "$message\n", status => $code);
}
-sub decode_json_wrapper {
- my ($json) = @_;
+sub _decode_json_wrapper {
+ my ($self, $json) = @_;
my $result;
if ( !defined $json ) {
- WARN( 'Missing JSON from ' . remote_ip() );
- respond( 400 => 'Bad Request' );
+ WARN( 'Missing JSON from ' . $self->tx->remote_address );
+ $self->_respond( 400 => 'Bad Request' );
return undef;
}
my $ok = try {
$result = decode_json($json);
}
catch {
- WARN( 'Malformed JSON from ' . remote_ip() );
- respond( 400 => 'Bad Request' );
+ WARN( 'Malformed JSON from ' . $self->tx->remote_address );
+ $self->_respond( 400 => 'Bad Request' );
return undef;
};
return $ok ? $result : undef;
@@ -212,3 +199,5 @@ sub ua {
}
return $ua;
}
+
+1; \ No newline at end of file
diff --git a/Bugzilla/Quantum/Static.pm b/Bugzilla/Quantum/Static.pm
index 2bb54990e..d687873ab 100644
--- a/Bugzilla/Quantum/Static.pm
+++ b/Bugzilla/Quantum/Static.pm
@@ -16,9 +16,9 @@ my $LEGACY_RE = qr{
}xs;
sub file {
- my ($self, $rel) = @_;
+ my ( $self, $rel ) = @_;
- if (my ($legacy_rel) = $rel =~ $LEGACY_RE) {
+ if ( my ($legacy_rel) = $rel =~ $LEGACY_RE ) {
local $self->{paths} = [ bz_locations->{cgi_path} ];
return $self->SUPER::file($legacy_rel);
}
diff --git a/Bugzilla/Quantum/Stdout.pm b/Bugzilla/Quantum/Stdout.pm
index ee470a56a..be7b546ea 100644
--- a/Bugzilla/Quantum/Stdout.pm
+++ b/Bugzilla/Quantum/Stdout.pm
@@ -9,34 +9,51 @@ package Bugzilla::Quantum::Stdout;
use 5.10.1;
use Moo;
+use Bugzilla::Logging;
+use Encode;
+
has 'controller' => (
is => 'ro',
required => 1,
);
-sub TIEHANDLE { ## no critic (unpack)
+has '_encoding' => (
+ is => 'rw',
+ default => '',
+);
+
+sub TIEHANDLE { ## no critic (unpack)
my $class = shift;
return $class->new(@_);
}
-sub PRINTF { ## no critic (unpack)
+sub PRINTF { ## no critic (unpack)
my $self = shift;
- $self->PRINT(sprintf @_);
+ $self->PRINT( sprintf @_ );
}
-sub PRINT { ## no critic (unpack)
- my $self = shift;
-
- foreach my $chunk (@_) {
- my $str = "$chunk";
- utf8::encode($str);
- $self->controller->write($str);
+sub PRINT { ## no critic (unpack)
+ my $self = shift;
+ my $c = $self->controller;
+ my $bytes = join '', @_;
+ return unless $bytes;
+ if ( $self->_encoding ) {
+ $bytes = encode( $self->_encoding, $bytes );
}
+ $c->write($bytes.$\);
}
sub BINMODE {
- # no-op
+ my ( $self, $mode ) = @_;
+ if ($mode) {
+ if ( $mode eq ':bytes' or $mode eq ':raw' ) {
+ $self->_encoding('');
+ }
+ elsif ( $mode eq ':utf8' ) {
+ $self->_encoding('utf8');
+ }
+ }
}
-1; \ No newline at end of file
+1;
diff --git a/Bugzilla/Template.pm b/Bugzilla/Template.pm
index d4a5b15dc..cdeb54a50 100644
--- a/Bugzilla/Template.pm
+++ b/Bugzilla/Template.pm
@@ -12,6 +12,7 @@ use 5.10.1;
use strict;
use warnings;
+use Bugzilla::Logging;
use Bugzilla::Template::PreloadProvider;
use Bugzilla::Bug;
use Bugzilla::Constants;
@@ -565,13 +566,8 @@ sub create {
PRE_CHOMP => 1,
TRIM => 1,
- # Bugzilla::Template::Plugin::Hook uses the absolute (in mod_perl)
- # or relative (in mod_cgi) paths of hook files to explicitly compile
- # a specific file. Also, these paths may be absolute at any time
- # if a packager has modified bz_locations() to contain absolute
- # paths.
ABSOLUTE => 1,
- RELATIVE => $ENV{SERVER_SOFTWARE} ? 0 : 1,
+ RELATIVE => 0,
# Only use an on-disk template cache if we're running as the web
# server. This ensures the permissions of the cache remain correct.
@@ -584,7 +580,7 @@ sub create {
# Initialize templates (f.e. by loading plugins like Hook).
PRE_PROCESS => ["global/initialize.none.tmpl"],
- ENCODING => Bugzilla->params->{'utf8'} ? 'UTF-8' : undef,
+ ENCODING => 'UTF-8',
# Functions for processing text within templates in various ways.
# IMPORTANT! When adding a filter here that does not override a
@@ -624,6 +620,7 @@ sub create {
# and newlines/carriage returns escaped for use in JS strings.
js => sub {
my ($var) = @_;
+ no warnings 'utf8';
$var =~ s/([\\\'\"\/])/\\$1/g;
$var =~ s/\n/\\n/g;
$var =~ s/\r/\\r/g;
@@ -639,6 +636,7 @@ sub create {
# for details.
json => sub {
my ($var) = @_;
+ no warnings 'utf8';
$var =~ s/([\\\"\/])/\\$1/g;
$var =~ s/\n/\\n/g;
$var =~ s/\r/\\r/g;
diff --git a/Bugzilla/User.pm b/Bugzilla/User.pm
index dc8f60565..4a58043a0 100644
--- a/Bugzilla/User.pm
+++ b/Bugzilla/User.pm
@@ -80,7 +80,8 @@ sub DB_COLUMNS {
'profiles.password_change_required',
'profiles.password_change_reason',
'profiles.mfa',
- 'profiles.mfa_required_date'
+ 'profiles.mfa_required_date',
+ 'profiles.nickname'
),
}
@@ -94,6 +95,7 @@ use constant VALIDATORS => {
disabledtext => \&_check_disabledtext,
login_name => \&check_login_name_for_creation,
realname => \&_check_realname,
+ nickname => \&_check_realname,
extern_id => \&_check_extern_id,
is_enabled => \&_check_is_enabled,
password_change_required => \&Bugzilla::Object::check_boolean,
@@ -114,6 +116,7 @@ sub UPDATE_COLUMNS {
password_change_reason
mfa
mfa_required_date
+ nickname
);
push(@cols, 'cryptpassword') if exists $self->{cryptpassword};
return @cols;
@@ -478,10 +481,29 @@ sub set_login {
delete $self->{nick};
}
+sub _generate_nickname {
+ my ($name, $login, $id) = @_;
+ my ($nick) = extract_nicks($name);
+ if (!$nick) {
+ $nick = "";
+ }
+ my ($count) = Bugzilla->dbh->selectrow_array('SELECT COUNT(*) FROM profiles WHERE nickname = ? AND userid != ?', undef, $nick, $id);
+ if ($count) {
+ $nick = "";
+ }
+ return $nick;
+}
+
sub set_name {
my ($self, $name) = @_;
$self->set('realname', $name);
delete $self->{identity};
+ $self->set('nickname', _generate_nickname($name, $self->login, $self->id));
+}
+
+sub set_nick {
+ my ($self, $nick) = @_;
+ $self->set('nickname', $nick);
}
sub set_password {
@@ -726,12 +748,8 @@ sub nick {
my $self = shift;
return "" unless $self->id;
-
- if (!defined $self->{nick}) {
- $self->{nick} = (split(/@/, $self->login, 2))[0];
- }
-
- return $self->{nick};
+ return $self->{nickname} if $self->{nickname};
+ return $self->{nick} //= (split(/@/, $self->login, 2))[0];
}
sub queries {
@@ -2514,13 +2532,12 @@ sub get_userlist {
}
sub create {
- my $invocant = shift;
- my $class = ref($invocant) || $invocant;
+ my ($class, $params) = @_;
my $dbh = Bugzilla->dbh;
$dbh->bz_start_transaction();
-
- my $user = $class->SUPER::create(@_);
+ $params->{nickname} = _generate_nickname($params->{realname}, $params->{login_name}, 0);
+ my $user = $class->SUPER::create($params);
# Turn on all email for the new user
require Bugzilla::BugMail;
diff --git a/Bugzilla/Util.pm b/Bugzilla/Util.pm
index a1316c7ef..aa524b263 100644
--- a/Bugzilla/Util.pm
+++ b/Bugzilla/Util.pm
@@ -29,7 +29,7 @@ use base qw(Exporter);
get_text template_var disable_utf8
enable_utf8 detect_encoding email_filter
round extract_nicks);
-
+use Bugzilla::Logging;
use Bugzilla::Constants;
use Bugzilla::RNG qw(irand);
@@ -105,6 +105,7 @@ my %html_quote = (
# Bug 319331: Handle BiDi disruptions.
sub html_quote {
my $var = shift;
+ no warnings 'utf8';
$var =~ s/([&<>"@])/$html_quote{$1}/g;
state $use_utf8 = Bugzilla->params->{'utf8'};
@@ -316,6 +317,7 @@ sub do_ssl_redirect_if_required {
# If we're already running under SSL, never redirect.
return if $ENV{HTTPS} && $ENV{HTTPS} eq 'on';
+ DEBUG("Redirect to HTTPS because \$ENV{HTTPS}=$ENV{HTTPS}");
Bugzilla->cgi->redirect_to_https();
}
diff --git a/Bugzilla/WebService/Server/REST/Resources/BugUserLastVisit.pm b/Bugzilla/WebService/Server/REST/Resources/BugUserLastVisit.pm
index a434d4bef..12290e84e 100644
--- a/Bugzilla/WebService/Server/REST/Resources/BugUserLastVisit.pm
+++ b/Bugzilla/WebService/Server/REST/Resources/BugUserLastVisit.pm
@@ -32,6 +32,15 @@ sub _rest_resources {
},
},
},
+ # no bug-id
+ qr{^/bug_user_last_visit$}, {
+ GET => {
+ method => 'get',
+ },
+ POST => {
+ method => 'update',
+ },
+ },
];
}
diff --git a/Bugzilla/WebService/Server/REST/Resources/User.pm b/Bugzilla/WebService/Server/REST/Resources/User.pm
index eb44e9d2d..6185237fb 100644
--- a/Bugzilla/WebService/Server/REST/Resources/User.pm
+++ b/Bugzilla/WebService/Server/REST/Resources/User.pm
@@ -20,6 +20,11 @@ BEGIN {
sub _rest_resources {
my $rest_resources = [
+ qr{^/user/suggest$}, {
+ GET => {
+ method => 'suggest',
+ },
+ },
qr{^/valid_login$}, {
GET => {
method => 'valid_login'
diff --git a/Bugzilla/WebService/User.pm b/Bugzilla/WebService/User.pm
index 5f9b54787..c569cf9d8 100644
--- a/Bugzilla/WebService/User.pm
+++ b/Bugzilla/WebService/User.pm
@@ -24,6 +24,7 @@ use Bugzilla::WebService::Util qw(filter filter_wants validate
use Bugzilla::Hook;
use List::Util qw(first);
+use Taint::Util qw(untaint);
# Don't need auth to login
use constant LOGIN_EXEMPT => {
@@ -33,6 +34,7 @@ use constant LOGIN_EXEMPT => {
use constant READ_ONLY => qw(
get
+ suggest
);
use constant PUBLIC_METHODS => qw(
@@ -135,6 +137,70 @@ sub create {
return { id => $self->type('int', $user->id) };
}
+sub suggest {
+ my ($self, $params) = @_;
+
+ Bugzilla->switch_to_shadow_db();
+
+ ThrowCodeError('params_required', { function => 'User.suggest', params => ['match'] })
+ unless defined $params->{match};
+
+ ThrowUserError('user_access_by_match_denied')
+ unless Bugzilla->user->id;
+
+ untaint($params->{match});
+ my $s = $params->{match};
+ trim($s);
+ return { users => [] } if length($s) < 3;
+
+ my $dbh = Bugzilla->dbh;
+ my @select = ('userid AS id', 'realname AS real_name', 'login_name AS name');
+ my $order = 'last_seen_date DESC';
+ my $where;
+ state $have_mysql = $dbh->isa('Bugzilla::DB::Mysql');
+
+ if ($s =~ /^[:@](.+)$/s) {
+ $where = $dbh->sql_prefix_match(nickname => $1);
+ }
+ elsif ($s =~ /@/) {
+ $where = $dbh->sql_prefix_match(login_name => $s);
+ }
+ else {
+ if ($have_mysql && ( $s =~ /[[:space:]]/ || $s =~ /[^[:ascii:]]/ ) ) {
+ my $match = sprintf 'MATCH(realname) AGAINST (%s) ', $dbh->quote($s);
+ push @select, "$match AS relevance";
+ $order = 'relevance DESC';
+ $where = $match;
+ }
+ elsif ($have_mysql && $s =~ /^[[:upper:]]/) {
+ my $match = sprintf 'MATCH(realname) AGAINST (%s) ', $dbh->quote($s);
+ $where = join ' OR ',
+ $match,
+ $dbh->sql_prefix_match( nickname => $s ),
+ $dbh->sql_prefix_match( login_name => $s );
+ }
+ else {
+ $where = join ' OR ', $dbh->sql_prefix_match( nickname => $s ), $dbh->sql_prefix_match( login_name => $s );
+ }
+ }
+ $where = "($where) AND is_enabled = 1";
+
+ my $sql = 'SELECT ' . join(', ', @select) . " FROM profiles WHERE $where ORDER BY $order LIMIT 25";
+ my $results = $dbh->selectall_arrayref($sql, { Slice => {} });
+
+ my @users = map {
+ {
+ id => $self->type(int => $_->{id}),
+ real_name => $self->type(string => $_->{real_name}),
+ name => $self->type(email => $_->{name}),
+ }
+ } @$results;
+
+ Bugzilla::Hook::process('webservice_user_suggest',
+ { webservice => $self, params => $params, users => \@users });
+
+ return { users => \@users };
+}
# function to return user information by passing either user ids or
# login names or both together:
diff --git a/Makefile.PL b/Makefile.PL
index 710a84e6b..9452438ee 100755
--- a/Makefile.PL
+++ b/Makefile.PL
@@ -83,6 +83,7 @@ my %requires = (
'URI' => '1.55',
'URI::Escape::XS' => '0.14',
'version' => '0.87',
+ 'EV' => 4.0
);
my %build_requires = ( 'ExtUtils::MakeMaker' => '7.22', );
@@ -94,7 +95,7 @@ my %test_requires = (
'Test::Perl::Critic::Progressive' => 0,
'Perl::Critic::Freenode' => 0,
);
-my %recommends = ( Safe => '2.30' );
+my %recommends = ( Safe => '2.30',);
# Windows requires some additional modules.
if ( $OSNAME eq 'MSWin32' ) {
@@ -438,7 +439,6 @@ sub is_bmo_feature {
^
(?: pg
| oracle
- | mod_perl
| sqlite
| auth_ldap
| auth_radius
diff --git a/buglist.cgi b/buglist.cgi
index 58dedf480..d1cde7096 100755
--- a/buglist.cgi
+++ b/buglist.cgi
@@ -41,6 +41,8 @@ my $vars = {};
my $user = Bugzilla->login();
$cgi->redirect_search_url();
+use Bugzilla::Logging;
+DEBUG("After the redirect.");
my $buffer = $cgi->query_string();
if (length($buffer) == 0) {
@@ -103,27 +105,6 @@ my $agent = ($cgi->http('X-Moz') && $cgi->http('X-Moz') =~ /\bmicrosummary\b/);
my $format = $template->get_format("list/list", scalar $cgi->param('format'),
scalar $cgi->param('ctype'));
-# Use server push to display a "Please wait..." message for the user while
-# executing their query if their browser supports it and they are viewing
-# the bug list as HTML and they have not disabled it by adding &serverpush=0
-# to the URL.
-#
-# Server push is a Netscape 3+ hack incompatible with MSIE, Lynx, and others.
-# Even Communicator 4.51 has bugs with it, especially during page reload.
-# http://www.browsercaps.org used as source of compatible browsers.
-# Safari (WebKit) does not support it, despite a UA that says otherwise (bug 188712)
-# MSIE 5+ supports it on Mac (but not on Windows) (bug 190370)
-#
-my $serverpush =
- $format->{'extension'} eq "html"
- && exists $ENV{'HTTP_USER_AGENT'}
- && $ENV{'HTTP_USER_AGENT'} =~ /Mozilla.[3-9]/
- && (($ENV{'HTTP_USER_AGENT'} !~ /[Cc]ompatible/) || ($ENV{'HTTP_USER_AGENT'} =~ /MSIE 5.*Mac_PowerPC/))
- && $ENV{'HTTP_USER_AGENT'} !~ /(?:WebKit|Trident|KHTML)/
- && !$agent
- && !defined($cgi->param('serverpush'))
- || $cgi->param('serverpush');
-
my $order = $cgi->param('order') || "";
# The params object to use for the actual query itself
@@ -744,18 +725,6 @@ $params->delete('limit') if $vars->{'default_limited'};
# Time to use server push to display an interim message to the user until
# the query completes and we can display the bug list.
-if ($serverpush) {
- print $cgi->multipart_init();
- print $cgi->multipart_start(-type => 'text/html');
-
- # Generate and return the UI (HTML page) from the appropriate template.
- $template->process("list/server-push.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
-
- # Don't do multipart_end() until we're ready to display the replacement
- # page, otherwise any errors that happen before then (like SQL errors)
- # will result in a blank page being shown to the user instead of the error.
-}
# Connect to the shadow database if this installation is using one to improve
# query performance.
@@ -1135,10 +1104,3 @@ $cgi->close_standby_message($contenttype, $disposition, $disp_prefix, $format->{
# Generate and return the UI (HTML page) from the appropriate template.
$template->process($format->{'template'}, $vars)
|| ThrowTemplateError($template->error());
-
-
-################################################################################
-# Script Conclusion
-################################################################################
-
-print $cgi->multipart_final() if $serverpush;
diff --git a/docs/en/rst/administering/parameters.rst b/docs/en/rst/administering/parameters.rst
index 338df3eb3..0492e0070 100644
--- a/docs/en/rst/administering/parameters.rst
+++ b/docs/en/rst/administering/parameters.rst
@@ -28,12 +28,6 @@ utf8
.. note:: If you turn this parameter from :paramval:`off` to :paramval:`on`,
you must re-run :file:`checksetup.pl` immediately afterward.
-shutdownhtml
- If there is any text in this field, this Bugzilla installation will
- be completely disabled and this text will appear instead of all
- Bugzilla pages for all users, including Admins. Used in the event
- of site maintenance or outage situations.
-
announcehtml
Any text in this field will be displayed at the top of every HTML
page in this Bugzilla installation. The text is not wrapped in any
diff --git a/docs/en/rst/api/core/v1/bug-user-last-visit.rst b/docs/en/rst/api/core/v1/bug-user-last-visit.rst
index 2c3782662..f98122097 100644
--- a/docs/en/rst/api/core/v1/bug-user-last-visit.rst
+++ b/docs/en/rst/api/core/v1/bug-user-last-visit.rst
@@ -82,7 +82,7 @@ To return more than one specific bug timestamps:
GET /rest/bug_user_last_visit/123?ids=234&ids=456
-To return just the most recent 20 timestamps:
+To return all the timestamps stored during the retention period:
.. code-block:: text
diff --git a/docs/en/rst/style.rst b/docs/en/rst/style.rst
index aa3957b95..5058a51a3 100644
--- a/docs/en/rst/style.rst
+++ b/docs/en/rst/style.rst
@@ -65,7 +65,7 @@ Other block types:
aware of.
.. todo:: This is some documentation-related task that still needs doing.
-
+
Use both of the above block types sparingly. Consider putting the information
in the main text, omitting it, or (if long) placing it in a subsidiary file.
@@ -103,9 +103,6 @@ Inline Directives
* A command to type in the shell:
:command:`command --arguments`
-* A parameter name:
- :param:`shutdownhtml`
-
* A parameter value:
:paramval:`DB`
diff --git a/editparams.cgi b/editparams.cgi
index ac7976d99..495d53937 100755
--- a/editparams.cgi
+++ b/editparams.cgi
@@ -137,9 +137,6 @@ if ($action eq 'save' && $current_module) {
}
push(@changes, $name);
SetParam($name, $value);
- if (($name eq "shutdownhtml") && ($value ne "")) {
- $vars->{'shutdown_is_active'} = 1;
- }
if ($name eq 'duplicate_or_move_bug_status') {
Bugzilla::Status::add_missing_bug_status_transitions($value);
}
diff --git a/extensions/BMO/Extension.pm b/extensions/BMO/Extension.pm
index d2e62eccd..743d03099 100644
--- a/extensions/BMO/Extension.pm
+++ b/extensions/BMO/Extension.pm
@@ -2742,4 +2742,96 @@ sub enter_bug_entrydefaultvars {
}
}
+sub app_startup {
+ my ($self, $args) = @_;
+ my $app = $args->{app};
+ my $r = $app->routes;
+
+ $r->any( '/:REWRITE_itrequest' => [ REWRITE_itrequest => qr{form[\.:]itrequest} ] )
+ ->to( 'CGI#enter_bug_cgi' => { 'product' => 'Infrastructure & Operations', 'format' => 'itrequest' } );
+ $r->any( '/:REWRITE_mozlist' => [ REWRITE_mozlist => qr{form[\.:]mozlist} ] )
+ ->to( 'CGI#enter_bug_cgi' => { 'product' => 'mozilla.org', 'format' => 'mozlist' } );
+ $r->any( '/:REWRITE_poweredby' => [ REWRITE_poweredby => qr{form[\.:]poweredby} ] )
+ ->to( 'CGI#enter_bug_cgi' => { 'product' => 'mozilla.org', 'format' => 'poweredby' } );
+ $r->any( '/:REWRITE_presentation' => [ REWRITE_presentation => qr{form[\.:]presentation} ] )
+ ->to( 'cgi#enter_bug_cgi' => { 'product' => 'mozilla.org', 'format' => 'presentation' } );
+ $r->any( '/:REWRITE_trademark' => [ REWRITE_trademark => qr{form[\.:]trademark} ] )
+ ->to( 'cgi#enter_bug_cgi' => { 'product' => 'mozilla.org', 'format' => 'trademark' } );
+ $r->any( '/:REWRITE_recoverykey' => [ REWRITE_recoverykey => qr{form[\.:]recoverykey} ] )
+ ->to( 'cgi#enter_bug_cgi' => { 'product' => 'mozilla.org', 'format' => 'recoverykey' } );
+ $r->any( '/:REWRITE_legal' => [ REWRITE_legal => qr{form[\.:]legal} ] )
+ ->to( 'CGI#enter_bug_cgi' => { 'product' => 'Legal', 'format' => 'legal' }, );
+ $r->any( '/:REWRITE_recruiting' => [ REWRITE_recruiting => qr{form[\.:]recruiting} ] )
+ ->to( 'CGI#enter_bug_cgi' => { 'product' => 'Recruiting', 'format' => 'recruiting' } );
+ $r->any( '/:REWRITE_intern' => [ REWRITE_intern => qr{form[\.:]intern} ] )
+ ->to( 'CGI#enter_bug_cgi' => { 'product' => 'Recruiting', 'format' => 'intern' } );
+ $r->any( '/:REWRITE_mozpr' => [ REWRITE_mozpr => qr{form[\.:]mozpr} ] )
+ ->to( 'CGI#enter_bug_cgi' => { 'product' => 'Mozilla PR', 'format' => 'mozpr' }, );
+ $r->any( '/:REWRITE_reps_mentorship' => [ REWRITE_reps_mentorship => qr{form[\.:]reps[\.:]mentorship} ] )
+ ->to( 'CGI#enter_bug_cgi' => { 'product' => 'Mozilla Reps', 'format' => 'mozreps' }, );
+ $r->any( '/:REWRITE_reps_budget' => [ REWRITE_reps_budget => qr{form[\.:]reps[\.:]budget} ] )
+ ->to( 'CGI#enter_bug_cgi' => { 'product' => 'Mozilla Reps', 'format' => 'remo-budget' } );
+ $r->any( '/:REWRITE_reps_swag' => [ REWRITE_reps_swag => qr{form[\.:]reps[\.:]swag} ] )
+ ->to( 'CGI#enter_bug_cgi' => { 'product' => 'Mozilla Reps', 'format' => 'remo-swag' } );
+ $r->any( '/:REWRITE_reps_it' => [ REWRITE_reps_it => qr{form[\.:]reps[\.:]it} ] )
+ ->to( 'CGI#enter_bug_cgi' => { 'product' => 'Mozilla Reps', 'format' => 'remo-it' } );
+ $r->any( '/:REWRITE_reps_payment' => [ REWRITE_reps_payment => qr{form[\.:]reps[\.:]payment} ] )
+ ->to( 'CGI#page_cgi' => { 'id' => 'remo-form-payment.html' } );
+ $r->any( '/:REWRITE_csa_discourse' => [ REWRITE_csa_discourse => qr{form[\.:]csa[\.:]discourse} ] )
+ ->to( 'CGI#enter_bug_cgi' => { 'product' => 'Infrastructure & Operations', 'format' => 'csa-discourse' } );
+ $r->any( '/:REWRITE_employee_incident' => [ REWRITE_employee_incident => qr{form[\.:]employee[\.\-:]incident} ] )
+ ->to( 'CGI#enter_bug_cgi' => { 'product' => 'mozilla.org', 'format' => 'employee-incident' } );
+ $r->any( '/:REWRITE_brownbag' => [ REWRITE_brownbag => qr{form[\.:]brownbag} ] )
+ ->to( 'CGI#https_air_mozilla_org_requests' => {} );
+ $r->any( '/:REWRITE_finance' => [ REWRITE_finance => qr{form[\.:]finance} ] )
+ ->to( 'CGI#enter_bug_cgi' => { 'product' => 'Finance', 'format' => 'finance' } );
+ $r->any(
+ '/:REWRITE_moz_project_review' => [ REWRITE_moz_project_review => qr{form[\.:]moz[\.\-:]project[\.\-:]review} ]
+ )->to( 'CGI#enter_bug_cgi' => { 'product' => 'mozilla.org', 'format' => 'moz-project-review' } );
+ $r->any( '/:REWRITE_docs' => [ REWRITE_docs => qr{form[\.:]docs?} ] )
+ ->to( 'CGI#enter_bug_cgi' => { 'product' => 'Developer Documentation', 'format' => 'doc' } );
+ $r->any( '/:REWRITE_mdn' => [ REWRITE_mdn => qr{form[\.:]mdn?} ] )
+ ->to( 'CGI#enter_bug_cgi' => { 'format' => 'mdn', 'product' => 'developer.mozilla.org' } );
+ $r->any( '/:REWRITE_swag_gear' => [ REWRITE_swag_gear => qr{form[\.:](swag|gear)} ] )
+ ->to( 'CGI#enter_bug_cgi' => { 'format' => 'swag', 'product' => 'Marketing' } );
+ $r->any( '/:REWRITE_costume' => [ REWRITE_costume => qr{form[\.:]costume} ] )
+ ->to( 'CGI#enter_bug_cgi' => { 'product' => 'Marketing', 'format' => 'costume' } );
+ $r->any( '/:REWRITE_ipp' => [ REWRITE_ipp => qr{form[\.:]ipp} ] )
+ ->to( 'CGI#enter_bug_cgi' => { 'product' => 'Internet Public Policy', 'format' => 'ipp' } );
+ $r->any( '/:REWRITE_creative' => [ REWRITE_creative => qr{form[\.:]creative} ] )
+ ->to( 'CGI#enter_bug_cgi' => { 'format' => 'creative', 'product' => 'Marketing' } );
+ $r->any( '/:REWRITE_user_engagement' => [ REWRITE_user_engagement => qr{form[\.:]user[\.\-:]engagement} ] )
+ ->to( 'CGI#enter_bug_cgi' => { 'format' => 'user-engagement', 'product' => 'Marketing' } );
+ $r->any( '/:REWRITE_dev_engagement_event' =>
+ [ REWRITE_dev_engagement_event => qr{form[\.:]dev[\.\-:]engagement[\.\-\:]event} ] )
+ ->to( 'CGI#enter_bug_cgi' => { 'product' => 'Developer Engagement', 'format' => 'dev-engagement-event' } );
+ $r->any( '/:REWRITE_mobile_compat' => [ REWRITE_mobile_compat => qr{form[\.:]mobile[\.\-:]compat} ] )
+ ->to( 'CGI#enter_bug_cgi' => { 'product' => 'Tech Evangelism', 'format' => 'mobile-compat' } );
+ $r->any( '/:REWRITE_web_bounty' => [ REWRITE_web_bounty => qr{form[\.:]web[\.:]bounty} ] )
+ ->to( 'CGI#enter_bug_cgi' => { 'format' => 'web-bounty', 'product' => 'mozilla.org' } );
+ $r->any( '/:REWRITE_automative' => [ REWRITE_automative => qr{form[\.:]automative} ] )
+ ->to( 'CGI#enter_bug_cgi' => { 'product' => 'Testing', 'format' => 'automative' } );
+ $r->any( '/:REWRITE_comm_newsletter' => [ REWRITE_comm_newsletter => qr{form[\.:]comm[\.:]newsletter} ] )
+ ->to( 'CGI#enter_bug_cgi' => { 'format' => 'comm-newsletter', 'product' => 'Marketing' } );
+ $r->any( '/:REWRITE_screen_share_whitelist' =>
+ [ REWRITE_screen_share_whitelist => qr{form[\.:]screen[\.:]share[\.:]whitelist} ] )
+ ->to( 'CGI#enter_bug_cgi' => { 'format' => 'screen-share-whitelist', 'product' => 'Firefox' } );
+ $r->any( '/:REWRITE_data_compliance' => [ REWRITE_data_compliance => qr{form[\.:]data[\.\-:]compliance} ] )
+ ->to( 'CGI#enter_bug_cgi' => { 'product' => 'Data Compliance', 'format' => 'data-compliance' } );
+ $r->any( '/:REWRITE_fsa_budget' => [ REWRITE_fsa_budget => qr{form[\.:]fsa[\.:]budget} ] )
+ ->to( 'CGI#enter_bug_cgi' => { 'product' => 'FSA', 'format' => 'fsa-budget' } );
+ $r->any( '/:REWRITE_triage_request' => [ REWRITE_triage_request => qr{form[\.:]triage[\.\-]request} ] )
+ ->to( 'CGI#page_cgi' => { 'id' => 'triage_request.html' } );
+ $r->any( '/:REWRITE_crm_CRM' => [ REWRITE_crm_CRM => qr{form[\.:](crm|CRM)} ] )
+ ->to( 'CGI#enter_bug_cgi' => { 'format' => 'crm', 'product' => 'Marketing' } );
+ $r->any( '/:REWRITE_nda' => [ REWRITE_nda => qr{form[\.:]nda} ] )
+ ->to( 'CGI#enter_bug_cgi' => { 'product' => 'Legal', 'format' => 'nda' } );
+ $r->any( '/:REWRITE_name_clearance' => [ REWRITE_name_clearance => qr{form[\.:]name[\.:]clearance} ] )
+ ->to( 'CGI#enter_bug_cgi' => { 'format' => 'name-clearance', 'product' => 'Legal' } );
+ $r->any( '/:REWRITE_shield_studies' => [ REWRITE_shield_studies => qr{form[\.:]shield[\.:]studies} ] )
+ ->to( 'CGI#enter_bug_cgi' => { 'product' => 'Shield', 'format' => 'shield-studies' } );
+ $r->any( '/:REWRITE_client_bounty' => [ REWRITE_client_bounty => qr{form[\.:]client[\.:]bounty} ] )
+ ->to( 'CGI#enter_bug_cgi' => { 'product' => 'Firefox', 'format' => 'client-bounty' } );
+}
+
__PACKAGE__->NAME;
diff --git a/extensions/BMO/template/en/default/bug/create/create-swag.html.tmpl b/extensions/BMO/template/en/default/bug/create/create-swag.html.tmpl
index cff5e5796..05ec4e2d9 100644
--- a/extensions/BMO/template/en/default/bug/create/create-swag.html.tmpl
+++ b/extensions/BMO/template/en/default/bug/create/create-swag.html.tmpl
@@ -13,106 +13,106 @@
items = [
{ id => '', name => 'Splendidest Gear' },
{ id => '#185687', name => 'Moleskine Notebook (Firefox)' },
- { id => '#155749', name => 'Rickshaw Messenger Bag ' },
- { id => '#155415S', name => 'Champion Hooded Sweatshirt S' },
- { id => '#155415M', name => 'Champion Hooded Sweatshirt M' },
- { id => '#155415L', name => 'Champion Hooded Sweatshirt L' },
- { id => '#155415X', name => 'Champion Hooded Sweatshirt XL' },
- { id => '#1554152', name => 'Champion Hooded Sweatshirt 2XL' },
- { id => '#157452S', name => 'Very Splendid Package Ladies S' },
- { id => '#157452M', name => 'Very Splendid Package Ladies M' },
- { id => '#157452L', name => 'Very Splendid Package Ladies L' },
- { id => '#157452X', name => 'Very Splendid Package Ladies XL' },
- { id => '#1574522', name => 'Very Splendid Package Ladies 2XL' },
- { id => '#157454S', name => 'Very Splendid Package Men\'s S' },
- { id => '#157454M', name => 'Very Splendid Package Men\'s M' },
- { id => '#157454L', name => 'Very Splendid Package Men\'s L' },
- { id => '#157454X', name => 'Very Splendid Package Men\'s XL' },
- { id => '#1574542', name => 'Very Splendid Package Men\'s 2XL' },
- { id => '#1574543', name => 'Very Splendid Package Men\'s 3XL' },
- { id => '#157451S', name => 'Most Splendid Package, S' },
- { id => '#157451M', name => 'Most Splendid Package, M' },
- { id => '#157451L', name => 'Most Splendid Package, L' },
- { id => '#157451X', name => 'Most Splendid Package, XL' },
- { id => '', name => 'Splendider Gear' },
- { id => '#155751', name => 'Drawstring Tote ' },
- { id => '#155340', name => 'Beanie' },
- { id => '#155339', name => 'Black Cap with Tote ' },
- { id => '#212669', name => 'Stoneware Ceramic Mug' },
- { id => '#190928S', name => 'Firefox Tee w/Woven Tag Navy S' },
- { id => '#190928M', name => 'Firefox Tee w/Woven Tag Navy M' },
- { id => '#190928L', name => 'Firefox Tee w/Woven Tag Navy L' },
- { id => '#190928X', name => 'Firefox Tee w/Woven Tag Navy XL' },
- { id => '#1909282', name => 'Firefox Tee w/Woven Tag Navy 2XL' },
- { id => '#1909283', name => 'Firefox Tee w/Woven Tag Navy 3XL' },
- { id => '#190929S', name => 'Firefox SS Lapis Tee Lapis S' },
- { id => '#190929M', name => 'Firefox SS Lapis Tee Lapis M' },
- { id => '#190929L', name => 'Firefox SS Lapis Tee Lapis L' },
- { id => '#190929X', name => 'Firefox SS Lapis Tee Lapis XL' },
- { id => '#155413S', name => 'Ladies\' T-shirt Navy S' },
- { id => '#155413M', name => 'Ladies\' T-shirt Navy M' },
- { id => '#155413L', name => 'Ladies\' T-shirt Navy L' },
- { id => '#155413X', name => 'Ladies\' T-shirt Navy XL' },
- { id => '#1554132', name => 'Ladies\' T-shirt Navy 2XL' },
+# { id => '#155749', name => 'Rickshaw Messenger Bag ' },
+# { id => '#155415S', name => 'Champion Hooded Sweatshirt S' },
+# { id => '#155415M', name => 'Champion Hooded Sweatshirt M' },
+# { id => '#155415L', name => 'Champion Hooded Sweatshirt L' },
+# { id => '#155415X', name => 'Champion Hooded Sweatshirt XL' },
+# { id => '#1554152', name => 'Champion Hooded Sweatshirt 2XL' },
+# { id => '#157452S', name => 'Very Splendid Package Ladies S' },
+# { id => '#157452M', name => 'Very Splendid Package Ladies M' },
+# { id => '#157452L', name => 'Very Splendid Package Ladies L' },
+# { id => '#157452X', name => 'Very Splendid Package Ladies XL' },
+# { id => '#1574522', name => 'Very Splendid Package Ladies 2XL' },
+# { id => '#157454S', name => 'Very Splendid Package Men\'s S' },
+# { id => '#157454M', name => 'Very Splendid Package Men\'s M' },
+# { id => '#157454L', name => 'Very Splendid Package Men\'s L' },
+# { id => '#157454X', name => 'Very Splendid Package Men\'s XL' },
+# { id => '#1574542', name => 'Very Splendid Package Men\'s 2XL' },
+# { id => '#1574543', name => 'Very Splendid Package Men\'s 3XL' },
+# { id => '#157451S', name => 'Most Splendid Package, S' },
+# { id => '#157451M', name => 'Most Splendid Package, M' },
+# { id => '#157451L', name => 'Most Splendid Package, L' },
+# { id => '#157451X', name => 'Most Splendid Package, XL' },
+# { id => '', name => 'Splendider Gear' },
+# { id => '#155751', name => 'Drawstring Tote ' },
+# { id => '#155340', name => 'Beanie' },
+# { id => '#155339', name => 'Black Cap with Tote ' },
+# { id => '#212669', name => 'Stoneware Ceramic Mug' },
+# { id => '#190928S', name => 'Firefox Tee w/Woven Tag Navy S' },
+# { id => '#190928M', name => 'Firefox Tee w/Woven Tag Navy M' },
+# { id => '#190928L', name => 'Firefox Tee w/Woven Tag Navy L' },
+# { id => '#190928X', name => 'Firefox Tee w/Woven Tag Navy XL' },
+# { id => '#1909282', name => 'Firefox Tee w/Woven Tag Navy 2XL' },
+# { id => '#1909283', name => 'Firefox Tee w/Woven Tag Navy 3XL' },
+# { id => '#190929S', name => 'Firefox SS Lapis Tee Lapis S' },
+# { id => '#190929M', name => 'Firefox SS Lapis Tee Lapis M' },
+# { id => '#190929L', name => 'Firefox SS Lapis Tee Lapis L' },
+# { id => '#190929X', name => 'Firefox SS Lapis Tee Lapis XL' },
+# { id => '#155413S', name => 'Ladies\' T-shirt Navy S' },
+# { id => '#155413M', name => 'Ladies\' T-shirt Navy M' },
+# { id => '#155413L', name => 'Ladies\' T-shirt Navy L' },
+# { id => '#155413X', name => 'Ladies\' T-shirt Navy XL' },
+# { id => '#1554132', name => 'Ladies\' T-shirt Navy 2XL' },
{ id => '', name => 'Splendid Gear' },
- { id => '#192150', name => '1.25" Firefox Button-PKG25 ' },
+# { id => '#192150', name => '1.25" Firefox Button-PKG25 ' },
{ id => '#197156', name => 'Firefox Tattoos- Pkg50' },
- { id => '#197158', name => 'Firefox Sticker' },
- { id => '#197159', name => 'Firefox Laminated Badge' },
- { id => '#155754', name => 'Lanyard with Bulldog Clip (Mozilla)' },
+# { id => '#197158', name => 'Firefox Sticker' },
+# { id => '#197159', name => 'Firefox Laminated Badge' },
+# { id => '#155754', name => 'Lanyard with Bulldog Clip (Mozilla)' },
{ id => '#155756', name => 'Silicone Wristband ' },
- { id => '', name => 'Limited Availability Gear' },
- { id => '#265073', name => 'Mozilla Cap' },
+# { id => '', name => 'Limited Availability Gear' },
+# { id => '#265073', name => 'Mozilla Cap' },
{ id => '#265080', name => 'Fox Plush' },
- { id => '#265072S', name => 'Mozilla Custom Hoodie' },
- { id => '#265072M', name => 'Mozilla Custom Hoodie' },
- { id => '#265072L', name => 'Mozilla Custom Hoodie' },
- { id => '#265072X', name => 'Mozilla Custom Hoodie' },
- { id => '#2650722', name => 'Mozilla Custom Hoodie' },
- { id => '#265074S', name => 'Ladies\' Firefox Logo T-Shirt' },
- { id => '#265074M', name => 'Ladies\' Firefox Logo T-Shirt' },
- { id => '#265074L', name => 'Ladies\' Firefox Logo T-Shirt' },
- { id => '#265074X', name => 'Ladies\' Firefox Logo T-Shirt' },
- { id => '#2650742', name => 'Ladies\' Firefox Logo T-Shirt' },
- { id => '#265075S', name => 'Women\'s Vertical T-Shirt' },
- { id => '#265075M', name => 'Women\'s Vertical T-Shirt' },
- { id => '#265075L', name => 'Women\'s Vertical T-Shirt' },
- { id => '#265075X', name => 'Women\'s Vertical T-Shirt' },
- { id => '#2650752', name => 'Women\'s Vertical T-Shirt' },
- { id => '#265078S', name => 'Mozilla Horizontal T-Shirt' },
- { id => '#265078M', name => 'Mozilla Horizontal T-Shirt' },
- { id => '#265078L', name => 'Mozilla Horizontal T-Shirt' },
- { id => '#265078X', name => 'Mozilla Horizontal T-Shirt' },
- { id => '#2650782', name => 'Mozilla Horizontal T-Shirt' },
+# { id => '#265072S', name => 'Mozilla Custom Hoodie' },
+# { id => '#265072M', name => 'Mozilla Custom Hoodie' },
+# { id => '#265072L', name => 'Mozilla Custom Hoodie' },
+# { id => '#265072X', name => 'Mozilla Custom Hoodie' },
+# { id => '#2650722', name => 'Mozilla Custom Hoodie' },
+# { id => '#265074S', name => 'Ladies\' Firefox Logo T-Shirt' },
+# { id => '#265074M', name => 'Ladies\' Firefox Logo T-Shirt' },
+# { id => '#265074L', name => 'Ladies\' Firefox Logo T-Shirt' },
+# { id => '#265074X', name => 'Ladies\' Firefox Logo T-Shirt' },
+# { id => '#2650742', name => 'Ladies\' Firefox Logo T-Shirt' },
+# { id => '#265075S', name => 'Women\'s Vertical T-Shirt' },
+# { id => '#265075M', name => 'Women\'s Vertical T-Shirt' },
+# { id => '#265075L', name => 'Women\'s Vertical T-Shirt' },
+# { id => '#265075X', name => 'Women\'s Vertical T-Shirt' },
+# { id => '#2650752', name => 'Women\'s Vertical T-Shirt' },
+# { id => '#265078S', name => 'Mozilla Horizontal T-Shirt' },
+# { id => '#265078M', name => 'Mozilla Horizontal T-Shirt' },
+# { id => '#265078L', name => 'Mozilla Horizontal T-Shirt' },
+# { id => '#265078X', name => 'Mozilla Horizontal T-Shirt' },
+# { id => '#2650782', name => 'Mozilla Horizontal T-Shirt' },
];
mozspaces = [
{
name => 'Beijing',
- address1 => 'Mozilla Online Ltd, International Club Office Tower 800A',
- address2 => '21 Jian Guo Men Wai Avenue',
+ address1 => 'Mozilla Online Ltd.',
+ address2 => 'China Resources Building, Suite 1708, 8 Jianguomenbei Avenue',
city => 'Beijing',
- state => 'Chaoyang District',
+ state => 'Dongcheng District',
country => 'China',
- postcode => '100020',
+ postcode => '100005',
},
{
name => 'Berlin',
address1 => 'MZ Denmark ApS - Germany',
- address2 => 'Voltastrasse 5 / Building (Haus) 10 / Stair (Treppe) 6 / 2nd floor.',
+ address2 => 'GSG-Hof Schlesische Straße, Gebäude 3, 4. Obergeschoss, Schlesische Straße 27',
city => 'Berlin',
state => 'Germany',
country => 'Germany',
- postcode => '13355',
+ postcode => '10997',
},
{
name => 'London',
address1 => 'Mozilla London',
- address2 => '101 St. Martin\'s Lane, 3rd Floor',
+ address2 => 'Metal Box Factory, Suite 441, 4th floor, 30 Great Guildford Street',
city => 'London',
state => 'Greater London',
country => 'UK',
- postcode => 'WC2N 4AZ',
+ postcode => 'SE1 0HS',
},
{
name => 'Mountain View',
@@ -160,15 +160,6 @@ mozspaces = [
postcode => '11047',
},
{
- name => 'Tokyo',
- address1 => '7-5-6 Roppongi',
- address2 => '',
- city => 'Minato-ku',
- state => 'Tokyo',
- country => 'Japan',
- postcode => '106-0032',
- },
- {
name => 'Toronto',
address1 => 'Mozilla Canada',
address2 => '366 Adelaide Street W, Suite 500',
diff --git a/extensions/BMO/template/en/default/hook/global/header-external-links.html.tmpl b/extensions/BMO/template/en/default/hook/global/header-external-links.html.tmpl
index 54a2f0e49..f79548e3d 100644
--- a/extensions/BMO/template/en/default/hook/global/header-external-links.html.tmpl
+++ b/extensions/BMO/template/en/default/hook/global/header-external-links.html.tmpl
@@ -15,7 +15,7 @@
<li role="presentation">
<a href="https://www.mozilla.org/" role="menuitem" tabindex="-1">Mozilla Home</a>
</li>
- <li role="separator" class="dropdown-separator"></li>
+ <li role="separator"></li>
<li role="presentation">
<a href="https://www.mozilla.org/privacy/websites/" role="menuitem" tabindex="-1">Privacy</a>
</li>
diff --git a/extensions/BMO/template/en/default/hook/reports/components-start.html.tmpl b/extensions/BMO/template/en/default/hook/reports/components-start.html.tmpl
new file mode 100644
index 000000000..a4234caa2
--- /dev/null
+++ b/extensions/BMO/template/en/default/hook/reports/components-start.html.tmpl
@@ -0,0 +1,10 @@
+[%# 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.
+ #%]
+
+[%# Don't show the default assignees and QA contacts %]
+[% show_default_people = 0 %]
diff --git a/extensions/BMO/template/en/default/pages/group_members.html.tmpl b/extensions/BMO/template/en/default/pages/group_members.html.tmpl
index ec2cb2e46..1c593c07e 100644
--- a/extensions/BMO/template/en/default/pages/group_members.html.tmpl
+++ b/extensions/BMO/template/en/default/pages/group_members.html.tmpl
@@ -82,7 +82,7 @@
<a href="editusers.cgi?action=edit&amp;userid=[% member.id FILTER none %]"
target="_blank">
[% ELSE %]
- <a href="user_profile?login=[% member.login FILTER uri %]"
+ <a href="user_profile?user_id=[% member.id FILTER none %]"
target="_blank">
[% END %]
<span [% 'class="bz_inactive"' UNLESS member.is_enabled %]>
diff --git a/extensions/BMO/template/en/default/reports/components.html.tmpl b/extensions/BMO/template/en/default/reports/components.html.tmpl
deleted file mode 100644
index 3e23d389e..000000000
--- a/extensions/BMO/template/en/default/reports/components.html.tmpl
+++ /dev/null
@@ -1,99 +0,0 @@
-[%# The contents of this file are subject to the Mozilla Public
- # License Version 1.1 (the "License"); you may not use this file
- # except in compliance with the License. You may obtain a copy of
- # the License at http://www.mozilla.org/MPL/
- #
- # Software distributed under the License is distributed on an "AS
- # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
- # implied. See the License for the specific language governing
- # rights and limitations under the License.
- #
- # The Original Code is the Bugzilla Bug Tracking System.
- #
- # The Initial Developer of the Original Code is Netscape Communications
- # Corporation. Portions created by Netscape are
- # Copyright (C) 1998 Netscape Communications Corporation. All
- # Rights Reserved.
- #
- # Contributor(s): Bradley Baetz <bbaetz@student.usyd.edu.au>
- # Max Kanat-Alexander <mkanat@bugzilla.org>
- #%]
-
-[%# INTERFACE:
- # product: object. The product for which we want to display component
- # descriptions.
- # component: string. The name of the component to hilight in the browser
- #%]
-
-[% title = BLOCK %]
- Components for [% product.name FILTER html %]
-[% END %]
-
-[% inline_style = BLOCK %]
-.product_name {
- font-size: 2em;
- font-weight: normal;
-}
-.component_name {
- font-size: 1.5em;
- font-weight: normal;
-}
-.product_desc, .component_desc {
- padding-left: 1em;
- font-size: 1em;
-}
-.component_container {
- padding-left: 1em;
- margin-bottom: 1em;
-}
-.product_container, .instructions {
- margin-bottom: 1em;
-}
-.component_highlight {
- padding: 0 0 0 1em;
-}
-[% END %]
-
-[% PROCESS global/header.html.tmpl
- style_urls = [ "skins/standard/reports.css" ]
- title = title
- style = inline_style
-%]
-
-<h2>[% mark FILTER html %]</h2>
-
-<div class="product_container">
- <span class="product_name">[% product.name FILTER html %]</span>
- <div class="product_desc">
- [% product.description FILTER html_light %]
- </div>
-</div>
-
-<div class="instructions">
- Select a component to see open [% terms.bugs %] in that component:
-</div>
-
-[% FOREACH comp = product.components %]
- [% INCLUDE describe_comp %]
-[% END %]
-
-[% PROCESS global/footer.html.tmpl %]
-
-[%############################################################################%]
-[%# BLOCK for components %]
-[%############################################################################%]
-
-[% BLOCK describe_comp %]
- <div class="component_container [%- IF comp.name == component_mark %] component_hilite[% END %]">
- <div class="component_name">
- <a name="[% comp.name FILTER html %]"
- href="buglist.cgi?product=
- [%- product.name FILTER uri %]&amp;component=
- [%- comp.name FILTER uri %]&amp;resolution=---">
- [% comp.name FILTER html %]</a>
- </div>
- <div class="component_desc">
- [% comp.description FILTER html_light %]
- </div>
- </div>
-[% END %]
diff --git a/extensions/BMO/web/js/edituser_menu.js b/extensions/BMO/web/js/edituser_menu.js
index 7008a2b84..a300f5c24 100644
--- a/extensions/BMO/web/js/edituser_menu.js
+++ b/extensions/BMO/web/js/edituser_menu.js
@@ -10,7 +10,7 @@ function show_usermenu(id, email, show_edit) {
{
name: "Profile",
callback: function () {
- var href = "user_profile?login=" + encodeURIComponent(email);
+ var href = "user_profile?user_id=" + id;
window.open(href, "_blank");
}
},
diff --git a/extensions/BugModal/lib/MonkeyPatches.pm b/extensions/BugModal/lib/MonkeyPatches.pm
index 88fce11af..54bd6e560 100644
--- a/extensions/BugModal/lib/MonkeyPatches.pm
+++ b/extensions/BugModal/lib/MonkeyPatches.pm
@@ -40,31 +40,6 @@ sub active_attachments {
1;
-package Bugzilla::User;
-
-use 5.10.1;
-use strict;
-use warnings;
-
-sub moz_nick {
- my ($self) = @_;
- if (!exists $self->{moz_nick}) {
- if ($self->name =~ /:?:(\S+?)\b/) {
- $self->{moz_nick} = $1;
- }
- elsif ($self->name) {
- $self->{moz_nick} = $self->name;
- }
- else {
- $self->login =~ /^([^\@]+)\@/;
- $self->{moz_nick} = $1;
- }
- }
- return $self->{moz_nick};
-}
-
-1;
-
package Bugzilla::Attachment;
use 5.10.1;
diff --git a/extensions/BugModal/template/en/default/bug_modal/activity_stream.html.tmpl b/extensions/BugModal/template/en/default/bug_modal/activity_stream.html.tmpl
index 51919ab27..36494773b 100644
--- a/extensions/BugModal/template/en/default/bug_modal/activity_stream.html.tmpl
+++ b/extensions/BugModal/template/en/default/bug_modal/activity_stream.html.tmpl
@@ -15,8 +15,8 @@
<div class="dropdown">
<button type="button" id="comment-tags-btn" arai-haspopup="true" aria-label="Tags Menu"
aria-expanded="false" aria-controls="comment-tags-menu" class="dropdown-button minor">Tags &#9662;</button>
- <ul id="comment-tags-menu" role="menu" tabindex="0" class="dropdown-content" style="display:none">
- <li class="dropdown-separator" role="presentation">
+ <ul id="comment-tags-menu" role="menu" tabindex="0" class="dropdown-content left" style="display:none">
+ <li role="presentation">
<a role="menuitem" tabindex="-1" data-comment-tag="">Reset</a>
</li>
</ul>
@@ -24,19 +24,21 @@
<div class="dropdown">
<button type="button" id="view-menu-btn" arai-haspopup="true" aria-label="View Menu"
aria-expanded="false" aria-controls="view-menu" class="dropdown-button minor">View &#9662;</button>
- <ul id="view-menu" role="menu" tabindex="0" class="dropdown-content" style="display:none">
- <li class="dropdown-separator" role="presentation">
+ <ul id="view-menu" role="menu" tabindex="0" class="dropdown-content left" style="display:none">
+ <li role="presentation">
<a id="view-reset" role="menuitem" tabindex="-1">Reset</a>
</li>
+ <li role="separator"></li>
<li role="presentation">
<a id="view-collapse-all" role="menuitem" tabindex="-1">Collapse All</a>
</li>
<li role="presentation">
<a id="view-expand-all" role="menuitem" tabindex="-1">Expand All</a>
</li>
- <li class="dropdown-separator" role="presentation">
+ <li role="presentation">
<a id="view-comments-only" role="menuitem" tabindex="-1">Comments Only</a>
</li>
+ <li role="separator"></li>
<li role="presentation">
<a id="view-toggle-cc" role="menuitem" tabindex="-1">Show CC Changes</a>
</li>
@@ -146,7 +148,7 @@
[% END %]
<button type="button" class="reply-btn minor"
data-reply-id="[% comment.count FILTER none %]"
- data-reply-name="[% comment.author.name || comment.author.moz_nick FILTER html %]"
+ data-reply-name="[% comment.author.name || comment.author.nick FILTER html %]"
>Reply</button>
[% END %]
<button type="button" class="change-spinner minor" id="cs-[% comment.count FILTER none %]">-</button>
@@ -184,9 +186,9 @@
<tr>
<td class="comment-collapse-reason"
[% IF user.setting("ui_use_absolute_time") == "on" %]
- title="[% comment.author.moz_nick FILTER html %] [[% comment.creation_ts FILTER time("%Y-%m-%d %H:%M %Z") FILTER html %]]">
+ title="[% comment.author.nick FILTER html %] [[% comment.creation_ts FILTER time("%Y-%m-%d %H:%M %Z") FILTER html %]]">
[% ELSE %]
- title="[% comment.author.moz_nick FILTER html %] [[% comment.creation_ts FILTER time_duration FILTER html %]]">
+ title="[% comment.author.nick FILTER html %] [[% comment.creation_ts FILTER time_duration FILTER html %]]">
[% END %]
Comment hidden ([% comment.collapsed_reason FILTER html %])
</td>
@@ -383,7 +385,7 @@
value FILTER bug_list_link;
ELSE;
- value FILTER truncate(256, '&hellip;') FILTER html;
+ value FILTER truncate(256, '…') FILTER html;
END;
END;
diff --git a/extensions/BugModal/template/en/default/bug_modal/edit.html.tmpl b/extensions/BugModal/template/en/default/bug_modal/edit.html.tmpl
index 48c2c1803..4e740e35d 100644
--- a/extensions/BugModal/template/en/default/bug_modal/edit.html.tmpl
+++ b/extensions/BugModal/template/en/default/bug_modal/edit.html.tmpl
@@ -320,24 +320,26 @@
<div class="dropdown">
<button type="button" id="action-menu-btn" aria-haspopup="true" aria-label="Actions Menu"
aria-expanded="false" aria-controls="action-menu" class="dropdown-button minor">&#9662;</button>
- <ul class="dropdown-content" id="action-menu" role="menu" style="display:none;">
+ <ul class="dropdown-content left" id="action-menu" role="menu" style="display:none;">
<li role="presentation">
<a id="action-reset" role="menuitem" tabindex="-1">Reset Sections</a>
</li>
<li role="presentation">
<a id="action-expand-all" role="menuitem" tabindex="-1">Expand All Sections</a>
</li>
- <li class="dropdown-separator" role="presentation">
+ <li role="presentation">
<a id="action-collapse-all" role="menuitem" tabindex="-1">Collapse All Sections</a>
</li>
+ <li role="separator"></li>
[% IF user.id %]
<li role="presentation">
<a id="action-add-comment" role="menuitem" tabindex="-1">Add Comment</a>
</li>
[% END %]
- <li class="dropdown-separator" role="presentation">
+ <li role="presentation">
<a id="action-last-comment" role="menuitem" tabindex="-1">Last Comment</a>
</li>
+ <li role="separator"></li>
<li role="presentation">
<a id="action-history" role="menuitem" tabindex="-1">History</a>
</li>
@@ -374,17 +376,29 @@
hide_on_edit = can_edit_product
help = "describecomponents.cgi?product=$filtered_product"
%]
- <span aria-owns="product-name product-latch">
- <span role="button" aria-label="show product information" aria-expanded="false" tabindex="0"
- class="spin-latch" id="product-latch" data-latch="product" data-for="product">&#9656;</span>
- <div title="show product information" tabindex="0" class="spin-toggle"
- id="product-name" data-latch="product" data-for="product">
+ <div class="name-info-outer dropdown">
+ <span id="product-name" class="dropdown-button" tabindex="0" role="button"
+ aria-haspopup="menu" aria-controls="product-info">
[% bug.product FILTER html %]
- </div>
- <div id="product-info" style="display:none">
- [% bug.product_obj.description FILTER html_light %]
- </div>
- </span>
+ <span class="icon" aria-hidden="true">&#x25BE;</span>
+ </span>
+ <aside id="product-info" class="name-info-popup dropdown-content right hover-display" hidden role="menu"
+ aria-label="Product description and actions">
+ <header>
+ <div class="title">[%~ bug.product FILTER html ~%]</div>
+ <div class="description">[% bug.product_obj.description FILTER html_light %]</div>
+ </header>
+ <li role="separator"></li>
+ <div class="actions">
+ <div><a href="buglist.cgi?product=[% bug.product FILTER uri %]&amp;bug_status=__open__"
+ target="_blank" role="menuitem" tabindex="-1">See Other [% terms.Bugs %]</a></div>
+ <div><button disabled type="button" class="minor component-watching" role="menuitem" tabindex="-1"
+ data-product="[% bug.product FILTER html %]"
+ data-label-watch="Watch This Product" data-label-unwatch="Unwatch This Product"
+ data-source="BugModal">Watch This Product</button></div>
+ </div>
+ </aside>
+ </div>
[% END %]
[% WRAPPER bug_modal/field.html.tmpl
field = bug_fields.product
@@ -417,20 +431,30 @@
help = "describecomponents.cgi?product=$filtered_product&component=$filtered_component#$filtered_component"
%]
- <span aria-owns="component-name component-latch">
- <span role="button" aria-label="show component description" aria-expanded="false" tabindex="0"
- class="spin-latch" id="component-latch" data-latch="component" data-for="component">&#9656;</span>
- <div title="show component information" tabindex="0" class="spin-toggle" id="component-name"
- data-latch="#component-latch" data-for="component">
- [% bug.component FILTER html %]
- </div>
- <div id="component-info" style="display:none">
- <div>[% bug.component_obj.description FILTER html_light %]</div>
- <a href="buglist.cgi?component=[% bug.component FILTER uri %]&amp;
- [%~ %]product=[% bug.product FILTER uri %]&amp;
- [%~ %]bug_status=__open__" target="_blank">Other [% terms.Bugs %]</a>
- </div>
- </span>
+ <div class="name-info-outer dropdown">
+ <span id="component-name" class="dropdown-button" tabindex="0" role="button"
+ aria-haspopup="menu" aria-controls="component-info">
+ [% bug.component FILTER html %]
+ <span class="icon" aria-hidden="true">&#x25BE;</span>
+ </span>
+ <aside id="component-info" class="name-info-popup dropdown-content right hover-display" hidden role="menu"
+ aria-label="Component description and actions">
+ <header>
+ <div class="title">[%~ bug.product _ " :: " _ bug.component FILTER html ~%]</div>
+ <div class="description">[% bug.component_obj.description FILTER html_light %]</div>
+ </header>
+ <li role="separator"></li>
+ <div class="actions">
+ <div><a href="buglist.cgi?product=[% bug.product FILTER uri %]&amp;
+ [%~ %]component=[% bug.component FILTER uri %]&amp;bug_status=__open__"
+ target="_blank" role="menuitem" tabindex="-1">See Other [% terms.Bugs %]</a></div>
+ <div><button disabled type="button" class="minor component-watching" role="menuitem" tabindex="-1"
+ data-product="[% bug.product FILTER html %]" data-component="[% bug.component FILTER html %]"
+ data-label-watch="Watch This Component" data-label-unwatch="Unwatch This Component"
+ data-source="BugModal">Watch This Component</button></div>
+ </div>
+ </aside>
+ </div>
[% END %]
[%# importance %]
@@ -565,8 +589,8 @@
[%
sub = [];
- sub.push("Reporter: " _ bug.reporter.moz_nick);
- sub.push(unassigned ? "Unassigned" : "Assigned: " _ bug.assigned_to.moz_nick);
+ sub.push("Reporter: " _ bug.reporter.nick);
+ sub.push(unassigned ? "Unassigned" : "Assigned: " _ bug.assigned_to.nick);
IF bug.mentors.size;
sub.push("Mentored");
END;
@@ -1023,7 +1047,7 @@
[%
sub = [];
IF bug.status_whiteboard != "";
- sub.push("Whiteboard: " _ bug.status_whiteboard.truncate(256, '&hellip;'));
+ sub.push("Whiteboard: " _ bug.status_whiteboard.truncate(256, '…'));
END;
IF bug.cf_crash_signature != "";
sub.push("crash signature");
@@ -1325,7 +1349,7 @@
<div class="dropdown">
<button type="button" id="format-btn" aria-haspopup="true" aria-label="Format [% terms.Bug %] Menu"
aria-expanded="false" aria-controls="format-menu" class="dropdown-button minor">Format [% terms.Bug %] &#9652;</button>
- <ul class="dropdown-content menu-up" id="format-menu" role="menu" style="display:none;">
+ <ul class="dropdown-content left menu-up" id="format-menu" role="menu" style="display:none;">
<li role="presentation">
<a href="show_bug.cgi?format=multiple&amp;id=[% bug.id FILTER uri %]" role="menuitem" tabindex="-1">For Printing</a>
</li>
@@ -1346,7 +1370,7 @@
<div class="dropdown">
<button type="button" id="new-bug-btn" aria-haspopup="true" aria-label="New/Clone [% terms.Bug %] Menu"
aria-expanded="false" aria-controls="new-bug-menu" class="dropdown-button minor">New/Clone [% terms.Bug %] &#9652;</button>
- <ul class="dropdown-content menu-up" id="new-bug-menu" role="menu" style="display:none;">
+ <ul class="dropdown-content left menu-up" id="new-bug-menu" role="menu" style="display:none;">
<li role="presentation">
<a href="enter_bug.cgi" role="menuitem" tabindex="-1" target="_blank">
Create a new [% terms.bug %]</a>
@@ -1355,18 +1379,20 @@
<a href="enter_bug.cgi?product=[% bug.product FILTER uri %]"
role="menuitem" tabindex="-1" target="_blank">&#8230; in this product</a>
</li>
- <li class="dropdown-separator" role="presentation">
+ <li role="presentation">
<a href="enter_bug.cgi?product=[% bug.product FILTER uri %]&amp;component=[% bug.component FILTER uri %]"
role="menuitem" tabindex="-1" target="_blank">&#8230; in this component</a>
</li>
+ <li role="separator"></li>
<li role="presentation">
<a href="enter_bug.cgi?format=__default__&amp;product=[% bug.product FILTER uri %]&amp;blocked=[% bug.id FILTER uri %]"
role="menuitem" tabindex="-1" target="_blank">&#8230; that blocks this [% terms.bug %]</a>
</li>
- <li class="dropdown-separator" role="presentation">
+ <li role="presentation">
<a href="enter_bug.cgi?format=__default__&amp;product=[% bug.product FILTER uri %]&amp;dependson=[% bug.id FILTER uri %]"
role="menuitem" tabindex="-1" target="_blank">&#8230; that depends on this [% terms.bug %]</a>
</li>
+ <li role="separator"></li>
<li role="presentation">
<a href="enter_bug.cgi?format=__default__&amp;product=[% bug.product FILTER uri %]&amp;cloned_bug_id=[% bug.id FILTER uri %]"
role="menuitem" tabindex="-1" target="_blank">&#8230; as a clone of this [% terms.bug %]</a>
diff --git a/extensions/BugModal/template/en/default/bug_modal/header.html.tmpl b/extensions/BugModal/template/en/default/bug_modal/header.html.tmpl
index b9a42caf3..20561c760 100644
--- a/extensions/BugModal/template/en/default/bug_modal/header.html.tmpl
+++ b/extensions/BugModal/template/en/default/bug_modal/header.html.tmpl
@@ -8,6 +8,7 @@
[%
PROCESS global/variables.none.tmpl;
+ USE Bugzilla;
# <title>
IF bugs.defined;
@@ -53,6 +54,7 @@
"extensions/ProdCompSearch/web/js/prod_comp_search.js",
"extensions/BugModal/web/bug_modal.js",
"extensions/BugModal/web/comments.js",
+ "extensions/ComponentWatching/web/js/overlay.js",
"js/bugzilla-readable-status-min.js",
"js/field.js",
"js/comments.js",
@@ -89,6 +91,8 @@
[%# expose useful data to js %]
BUGZILLA.bug_id = [% bug.id FILTER none %];
BUGZILLA.bug_title = '[% unfiltered_title FILTER js %]';
+ BUGZILLA.bug_summary = '[% bug.short_desc FILTER js %]';
+ BUGZILLA.bug_url = '[% Bugzilla.cgi.self_url FILTER js %]';
BUGZILLA.user = {
id: [% user.id FILTER none %],
login: '[% user.login FILTER js %]',
diff --git a/extensions/BugModal/template/en/default/bug_modal/user.html.tmpl b/extensions/BugModal/template/en/default/bug_modal/user.html.tmpl
index 9eda7b936..6a0ce4e24 100644
--- a/extensions/BugModal/template/en/default/bug_modal/user.html.tmpl
+++ b/extensions/BugModal/template/en/default/bug_modal/user.html.tmpl
@@ -30,9 +30,9 @@ END;
[% IF simple %]
[% IF user.id %]
- <span class="fn" title="[% u.identity FILTER html %]">[% u.moz_nick FILTER html %]</span>
+ <span class="fn" title="[% u.identity FILTER html %]">[% u.nick FILTER html %]</span>
[% ELSE %]
- <span class="fn">[% u.moz_nick FILTER html %]</span>
+ <span class="fn">[% u.nick FILTER html %]</span>
[% END %]
[% ELSE %]
@@ -52,7 +52,7 @@ END;
href="user_profile?user_id=[% u.id FILTER none %]"
[% END %]
>
- <span class="[% user.id ? 'fn' : 'fna' %]">[% nick_only ? u.moz_nick : (u.name || u.nick) FILTER html %]</span>
+ <span class="[% user.id ? 'fn' : 'fna' %]">[% nick_only ? u.nick : (u.name || u.nick) FILTER html %]</span>
[%~~%]
</a>
[% END %]
diff --git a/extensions/BugModal/web/bug_modal.css b/extensions/BugModal/web/bug_modal.css
index a8c469ad6..ee50c6b77 100644
--- a/extensions/BugModal/web/bug_modal.css
+++ b/extensions/BugModal/web/bug_modal.css
@@ -44,26 +44,6 @@ button.major {
padding: 4px 12px;
}
-button.minor {
- background-color: #eee;
- background-image: linear-gradient(#fcfcfc, #eee);
- color: #000;
- font-size: inherit;
- font-weight: 500;
- padding: 4px 8px;
- margin-bottom: 1px;
- text-shadow: none;
- -web-kit-box-shadow: 0 1px 0 0 rgba(0,0,0,0.1), inset 0 -1px 0 0 rgba(0,0,0,0.1);
- -moz-box-shadow: 0 1px 0 0 rgba(0,0,0,0.1), inset 0 -1px 0 0 rgba(0,0,0,0.1);
- box-shadow: 0 1px 0 0 rgba(0,0,0,0.1), inset 0 -1px 0 0 rgba(0,0,0,0.1), inset 0 0 1px 0 rgba(0,0,0,0.1);
-}
-
-button.minor:hover {
- -webkit-box-shadow: 0 1px 0 0 rgba(0,0,0,0.2), inset 0 -1px 0 0 rgba(0,0,0,0.3), inset 0 12px 24px 2px #ddd;
- -moz-box-shadow: 0 1px 0 0 rgba(0,0,0,0.2), inset 0 -1px 0 0 rgba(0,0,0,0.3), inset 0 12px 24px 2px #ddd;
- box-shadow: 0 1px 0 0 rgba(0,0,0,0.1), inset 0 -1px 0 0 rgba(0,0,0,0.1), inset 0 12px 24px 2px #ddd;
-}
-
select[multiple], .text_input, .yui-ac-input, input {
font-size: 12px !important;
}
@@ -329,16 +309,6 @@ input[type="number"] {
margin-bottom: 50px;
}
-#product-info, #component-info {
- color: #484;
- white-space: normal;
-}
-
-#product-latch, #component-latch {
- padding-right: 0;
- cursor: pointer;
-}
-
#cc-latch {
color: #999;
}
@@ -968,6 +938,28 @@ div.ui-tooltip {
right: 8px;
}
+/* product/component popup */
+
+.name-info-popup {
+ width: 320px;
+}
+
+.name-info-popup header {
+ margin: 8px 16px;
+}
+
+.name-info-popup header .title {
+ margin: 0 0 4px;
+ font-size: 16px;
+}
+
+.name-info-popup header .description {
+ font-size: 12px;
+ line-height: 150%;
+ white-space: normal;
+ color: #666;
+}
+
/* product search */
#field-product {
diff --git a/extensions/BugModal/web/bug_modal.js b/extensions/BugModal/web/bug_modal.js
index 4a770e66c..a4ae83d72 100644
--- a/extensions/BugModal/web/bug_modal.js
+++ b/extensions/BugModal/web/bug_modal.js
@@ -339,10 +339,6 @@ $(function() {
// copy summary to clipboard
- function clipboardSummary() {
- return 'Bug ' + BUGZILLA.bug_id + ' - ' + $('#field-value-short_desc').text();
- }
-
if ($('#copy-summary').length) {
var hasExecCopy = false;
try {
@@ -352,11 +348,24 @@ $(function() {
}
if (hasExecCopy) {
+ const url = BUGZILLA.bug_url;
+ const text = `Bug ${BUGZILLA.bug_id} - ${BUGZILLA.bug_summary}`;
+ const html = `<a href="${url}">${text}</a>`;
+
+ document.addEventListener('copy', event => {
+ if (event.target.nodeType === 1 && event.target.matches('#clip')) {
+ event.clipboardData.setData('text/uri-list', url);
+ event.clipboardData.setData('text/plain', text);
+ event.clipboardData.setData('text/html', html);
+ event.preventDefault();
+ }
+ });
+
$('#copy-summary')
.click(function() {
// execCommand("copy") only works on selected text
$('#clip-container').show();
- $('#clip').val(clipboardSummary()).select();
+ $('#clip').val(text).select();
$('#floating-message-text')
.text(document.execCommand("copy") ? 'Bug summary copied!' : 'Couldn’t copy bug summary');
$('#floating-message').fadeIn(250).delay(2500).fadeOut();
@@ -377,27 +386,6 @@ $(function() {
lb_show(this);
});
- // when copying the bug id and summary, reformat to remove \n and alias
- $(document).on(
- 'copy', function(event) {
- var selection = document.getSelection().toString().trim();
- var match = selection.match(/^(Bug \d+)\s*\n(.+)$/) ||
- selection.match(/^(Bug \d+)\s+\([^\)]+\)\s*\n(.+)$/);
- if (match) {
- var content = match[1] + ' - ' + match[2].trim();
- if (event.originalEvent.clipboardData) {
- event.originalEvent.clipboardData.setData('text/plain', content);
- }
- else if (window.clipboardData) {
- window.clipboardData.setData('Text', content);
- }
- else {
- return;
- }
- event.preventDefault();
- }
- });
-
// action button actions
// reset
@@ -1446,46 +1434,6 @@ if (history && history.replaceState) {
}
}
-// ajax wrapper, to simplify error handling and auth
-function bugzilla_ajax(request, done_fn, error_fn) {
- $('#xhr-error').hide('');
- $('#xhr-error').html('');
- request.url += (request.url.match('\\?') ? '&' : '?') +
- 'Bugzilla_api_token=' + encodeURIComponent(BUGZILLA.api_token);
- if (request.type != 'GET') {
- request.contentType = 'application/json';
- request.processData = false;
- if (request.data && request.data.constructor === Object) {
- request.data = JSON.stringify(request.data);
- }
- }
- return $.ajax(request)
- .done(function(data) {
- if (data.error) {
- if (!request.hideError) {
- $('#xhr-error').html(data.message);
- $('#xhr-error').show('fast');
- }
- if (error_fn)
- error_fn(data.message);
- }
- else if (done_fn) {
- done_fn(data);
- }
- })
- .fail(function(data) {
- if (data.statusText === 'abort')
- return;
- var message = data.responseJSON ? data.responseJSON.message : 'Unexpected Error'; // all errors are unexpected :)
- if (!request.hideError) {
- $('#xhr-error').html(message);
- $('#xhr-error').show('fast');
- }
- if (error_fn)
- error_fn(message);
- });
-}
-
// lightbox
function lb_show(el) {
diff --git a/extensions/BugmailFilter/template/en/default/account/prefs/bugmail_filter.html.tmpl b/extensions/BugmailFilter/template/en/default/account/prefs/bugmail_filter.html.tmpl
index bb1381c46..ff64f8323 100644
--- a/extensions/BugmailFilter/template/en/default/account/prefs/bugmail_filter.html.tmpl
+++ b/extensions/BugmailFilter/template/en/default/account/prefs/bugmail_filter.html.tmpl
@@ -221,7 +221,7 @@ var cpts = new Array();
[% FOREACH flag = type.flags %]
[% IF flag_count > 10 && loop.count == 10 %]
<span id="show_all">
- &hellip;
+ …
(<a href="#" onclick="showAllFlags(); return false">show all</a>)
</span>
<span id="all_flags" class="bz_default_hidden">
diff --git a/extensions/ComponentWatching/Extension.pm b/extensions/ComponentWatching/Extension.pm
index ed47e64c3..96eb877a6 100644
--- a/extensions/ComponentWatching/Extension.pm
+++ b/extensions/ComponentWatching/Extension.pm
@@ -469,15 +469,18 @@ sub bugmail_relationships {
#
sub _getWatches {
- my ($user) = @_;
+ my ($user, $watch_id) = @_;
my $dbh = Bugzilla->dbh;
+ $watch_id = (defined $watch_id && $watch_id =~ /^(\d+)$/) ? $1 : undef;
+
my $sth = $dbh->prepare("
SELECT id, product_id, component_id, component_prefix
FROM component_watch
- WHERE user_id = ?
- ");
- $sth->execute($user->id);
+ WHERE user_id = ?" . ($watch_id ? " AND id = ?" : "")
+ );
+ $watch_id ? $sth->execute($user->id, $watch_id) : $sth->execute($user->id);
+
my @watches;
while (my ($id, $productId, $componentId, $prefix) = $sth->fetchrow_array) {
my $product = Bugzilla::Product->new({ id => $productId, cache => 1 });
@@ -500,6 +503,10 @@ sub _getWatches {
push @watches, \%watch;
}
+ if ($watch_id) {
+ return $watches[0] || {};
+ }
+
@watches = sort {
$a->{'product_name'} cmp $b->{'product_name'}
|| $a->{'component_name'} cmp $b->{'component_name'}
@@ -565,6 +572,8 @@ sub _addProductWatch {
VALUES (?, ?)
");
$sth->execute($user->id, $product->id);
+
+ return _getWatches($user, $dbh->bz_last_key());
}
sub _addComponentWatch {
@@ -585,6 +594,8 @@ sub _addComponentWatch {
VALUES (?, ?, ?)
");
$sth->execute($user->id, $component->product_id, $component->id);
+
+ return _getWatches($user, $dbh->bz_last_key());
}
sub _addPrefixWatch {
@@ -620,8 +631,9 @@ sub _deleteWatch {
my $dbh = Bugzilla->dbh;
detaint_natural($id) || ThrowCodeError("component_watch_invalid_id");
- $dbh->do("DELETE FROM component_watch WHERE id=? AND user_id=?",
- undef, $id, $user->id);
+
+ return $dbh->do("DELETE FROM component_watch WHERE id=? AND user_id=?",
+ undef, $id, $user->id);
}
sub _addDefaultSettings {
@@ -717,4 +729,13 @@ sub sanitycheck_repair {
}
}
+#
+# webservice
+#
+
+sub webservice {
+ my ($self, $args) = @_;
+ $args->{dispatch}->{ComponentWatching} = "Bugzilla::Extension::ComponentWatching::WebService";
+}
+
__PACKAGE__->NAME;
diff --git a/extensions/ComponentWatching/lib/WebService.pm b/extensions/ComponentWatching/lib/WebService.pm
new file mode 100644
index 000000000..ba4cb0225
--- /dev/null
+++ b/extensions/ComponentWatching/lib/WebService.pm
@@ -0,0 +1,113 @@
+# 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::ComponentWatching::WebService;
+
+use 5.10.1;
+use strict;
+use warnings;
+
+use base qw(Bugzilla::WebService);
+
+use Bugzilla;
+use Bugzilla::Component;
+use Bugzilla::Constants;
+use Bugzilla::Error;
+use Bugzilla::Product;
+use Bugzilla::User;
+
+sub rest_resources {
+ return [
+ qr{^/component-watching$}, {
+ GET => {
+ method => 'list',
+ },
+ POST => {
+ method => 'add',
+ },
+ },
+ qr{^/component-watching/(\d+)$}, {
+ GET => {
+ method => 'get',
+ params => sub {
+ return { id => $_[0] }
+ },
+ },
+ DELETE => {
+ method => 'remove',
+ params => sub {
+ return { id => $_[0] }
+ },
+ },
+ },
+ ];
+}
+
+#
+# API methods based on Bugzilla::Extension::ComponentWatching->user_preferences
+#
+
+sub list {
+ my ($self, $params) = @_;
+ my $user = Bugzilla->login(LOGIN_REQUIRED);
+
+ return Bugzilla::Extension::ComponentWatching::_getWatches($user);
+}
+
+sub add {
+ my ($self, $params) = @_;
+ my $user = Bugzilla->login(LOGIN_REQUIRED);
+ my $result;
+
+ # load product and verify access
+ my $productName = $params->{'product'};
+ my $product = Bugzilla::Product->new({ name => $productName, cache => 1 });
+ unless ($product && $user->can_access_product($product)) {
+ ThrowUserError('product_access_denied', { product => $productName });
+ }
+
+ my $ra_componentNames = $params->{'component'};
+ $ra_componentNames = [$ra_componentNames || ''] unless ref($ra_componentNames);
+
+ if (grep { $_ eq '' } @$ra_componentNames) {
+ # watching a product
+ $result = Bugzilla::Extension::ComponentWatching::_addProductWatch($user, $product);
+
+ } else {
+ # watching specific components
+ foreach my $componentName (@$ra_componentNames) {
+ my $component = Bugzilla::Component->new({
+ name => $componentName, product => $product, cache => 1
+ });
+ unless ($component) {
+ ThrowUserError('product_access_denied', { product => $productName });
+ }
+ $result = Bugzilla::Extension::ComponentWatching::_addComponentWatch($user, $component);
+ }
+ }
+
+ Bugzilla::Extension::ComponentWatching::_addDefaultSettings($user);
+
+ return $result;
+}
+
+sub get {
+ my ($self, $params) = @_;
+ my $user = Bugzilla->login(LOGIN_REQUIRED);
+
+ return Bugzilla::Extension::ComponentWatching::_getWatches($user, $params->{'id'});
+}
+
+sub remove {
+ my ($self, $params) = @_;
+ my $user = Bugzilla->login(LOGIN_REQUIRED);
+ my %result = (status => Bugzilla::Extension::ComponentWatching::_deleteWatch($user, $params->{'id'}));
+
+ return \%result;
+}
+
+1;
diff --git a/extensions/ComponentWatching/template/en/default/hook/reports/components-component_footer.html.tmpl b/extensions/ComponentWatching/template/en/default/hook/reports/components-component_footer.html.tmpl
new file mode 100644
index 000000000..b8921bcf0
--- /dev/null
+++ b/extensions/ComponentWatching/template/en/default/hook/reports/components-component_footer.html.tmpl
@@ -0,0 +1,10 @@
+[%# 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.
+ #%]
+
+<button disabled type="button" class="minor component-watching" data-product="[% product.name FILTER html %]"
+ data-component="[% comp.name FILTER html %]" data-source="Component Description">Watch</button>
diff --git a/extensions/ComponentWatching/template/en/default/hook/reports/components-product_header.html.tmpl b/extensions/ComponentWatching/template/en/default/hook/reports/components-product_header.html.tmpl
new file mode 100644
index 000000000..bc7120b4e
--- /dev/null
+++ b/extensions/ComponentWatching/template/en/default/hook/reports/components-product_header.html.tmpl
@@ -0,0 +1,10 @@
+[%# 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.
+ #%]
+
+<button disabled type="button" class="minor component-watching" data-product="[% product.name FILTER html %]"
+ data-source="Component Description">Watch</button>
diff --git a/extensions/ComponentWatching/template/en/default/hook/reports/components-start.html.tmpl b/extensions/ComponentWatching/template/en/default/hook/reports/components-start.html.tmpl
new file mode 100644
index 000000000..76cf6bc08
--- /dev/null
+++ b/extensions/ComponentWatching/template/en/default/hook/reports/components-start.html.tmpl
@@ -0,0 +1,12 @@
+[%# 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.
+ #%]
+
+[%
+ javascript_urls.push('extensions/ComponentWatching/web/js/overlay.js');
+ generate_api_token = 1;
+%]
diff --git a/extensions/ComponentWatching/web/js/overlay.js b/extensions/ComponentWatching/web/js/overlay.js
new file mode 100644
index 000000000..c0c540257
--- /dev/null
+++ b/extensions/ComponentWatching/web/js/overlay.js
@@ -0,0 +1,218 @@
+/* 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. */
+
+/**
+ * Reference or define the Bugzilla app namespace.
+ * @namespace
+ */
+var Bugzilla = Bugzilla || {};
+
+/**
+ * Implement the one-click Component Watching functionality that can be added to any page.
+ * @abstract
+ */
+Bugzilla.ComponentWatching = class ComponentWatching {
+ /**
+ * Initialize a new ComponentWatching instance. Since constructors can't be async, use a separate function to move on.
+ */
+ constructor() {
+ this.buttons = document.querySelectorAll('button.component-watching');
+
+ this.init();
+ }
+
+ /**
+ * Send a REST API request, and return the results in a Promise.
+ * @param {Object} [request={}] Request data. If omitted, all the current watches will be returned.
+ * @param {String} [path=''] Optional path to be appended to the request URL.
+ * @returns {Promise<Object|String>} Response data or error message.
+ */
+ async fetch(request = {}, path = '') {
+ request.url = `/rest/component-watching${path}`;
+
+ return new Promise((resolve, reject) => bugzilla_ajax(request, data => resolve(data), error => reject(error)));
+ }
+
+ /**
+ * Start watching the current product or component.
+ * @param {String} product Product name.
+ * @param {String} [component=''] Component name. If omitted, all the components in the product will be watched.
+ * @returns {Promise<Object|String>} Response data or error message.
+ */
+ async watch(product, component = '') {
+ return this.fetch({ type: 'POST', data: { product, component } });
+ }
+
+ /**
+ * Stop watching the current product or component.
+ * @param {Number} id ID of the watch to be removed.
+ * @returns {Promise<Object|String>} Response data or error message.
+ */
+ async unwatch(id) {
+ return this.fetch({ type: 'DELETE' }, `/${id}`);
+ }
+
+ /**
+ * Log an event with Google Analytics if possible. For privacy reasons, we don't send any specific product or
+ * component name.
+ * @param {String} source Event source that will be part of the event category.
+ * @param {String} action `watch` or `unwatch`.
+ * @param {String} type `product` or `component`.
+ * @param {Number} code `0` for a successful change, `1` otherwise.
+ * @see https://developers.google.com/analytics/devguides/collection/analyticsjs/events
+ */
+ track_event(source, action, type, code) {
+ if ('ga' in window) {
+ ga('send', 'event', `Component Watching: ${source}`, action, type, code);
+ }
+ }
+
+ /**
+ * Show a short floating message if the button is on BugModal. This code is from bug_modal.js, requiring jQuery.
+ * @param {String} message Message text.
+ */
+ show_message(message) {
+ if (!document.querySelector('#floating-message')) {
+ return;
+ }
+
+ $('#floating-message-text').text(message);
+ $('#floating-message').fadeIn(250).delay(2500).fadeOut();
+ }
+
+ /**
+ * Get all the component watching buttons on the current page.
+ * @param {String} [product] Optional product name.
+ * @param {String} [component] Optional component name.
+ * @returns {HTMLButtonElement[]} List of button elements.
+ */
+ get_buttons(product = undefined, component = undefined) {
+ let buttons = [...this.buttons];
+
+ if (product) {
+ buttons = buttons.filter($button => $button.dataset.product === product);
+ }
+
+ if (component) {
+ buttons = buttons.filter($button => $button.dataset.component === component);
+ }
+
+ return buttons;
+ }
+
+ /**
+ * Update a Watch/Unwatch button for a product or component.
+ * @param {HTMLButtonElement} $button Button element to be updated.
+ * @param {Boolean} disabled Whether the button has to be disabled.
+ * @param {Number} [watchId] Optional watch ID if the product or component is being watched.
+ */
+ update_button($button, disabled, watchId = undefined) {
+ const { product, component } = $button.dataset;
+
+ if (watchId) {
+ $button.dataset.watchId = watchId;
+ $button.textContent = $button.getAttribute('data-label-unwatch') || 'Unwatch';
+ $button.title = component ?
+ `Stop watching the ${component} component` :
+ `Stop watching all components in the ${product} product`;
+ } else {
+ delete $button.dataset.watchId;
+
+ $button.textContent = $button.getAttribute('data-label-watch') || 'Watch';
+ $button.title = component ?
+ `Start watching the ${component} component` :
+ `Start watching all components in the ${product} product`;
+ }
+
+ $button.disabled = disabled;
+ }
+
+ /**
+ * Called whenever a Watch/Unwatch button is clicked. Send a request to update the user's watch list, and update the
+ * relevant buttons on the page.
+ * @param {HTMLButtonElement} $button Clicked button element.
+ */
+ async button_onclick($button) {
+ const { product, component, watchId, source } = $button.dataset;
+ let message = '';
+ let code = 0;
+
+ // Disable the button until the request is complete
+ $button.disabled = true;
+
+ try {
+ if (watchId) {
+ await this.unwatch(watchId);
+
+ if (component) {
+ message = `You are no longer watching the ${component} component`;
+
+ this.get_buttons(product, component).forEach($button => this.update_button($button, false));
+ } else {
+ message = `You are no longer watching all components in the ${product} product`;
+
+ this.get_buttons(product).forEach($button => this.update_button($button, false));
+ }
+ } else {
+ const watch = await this.watch(product, component);
+
+ if (component) {
+ message = `You are now watching the ${component} component`;
+
+ this.get_buttons(product, component).forEach($button => this.update_button($button, false, watch.id));
+ } else {
+ message = `You are now watching all components in the ${product} product`;
+
+ this.get_buttons(product).forEach($button => {
+ if ($button.dataset.component) {
+ this.update_button($button, true);
+ } else {
+ this.update_button($button, false, watch.id);
+ }
+ });
+ }
+ }
+ } catch (ex) {
+ message = 'Your watch list could not be updated. Please try again later.';
+ code = 1;
+ }
+
+ this.show_message(message);
+ this.track_event(source, watchId ? 'unwatch' : 'watch', component ? 'component' : 'product', code);
+ }
+
+ /**
+ * Retrieve the current watch list, and initialize all the buttons.
+ */
+ async init() {
+ try {
+ const all_watches = await this.fetch();
+
+ this.get_buttons().forEach($button => {
+ const { product, component } = $button.dataset;
+ const watches = all_watches.filter(watch => watch.product_name === product);
+ const product_watch = watches.find(watch => !watch.component);
+
+ if (!component) {
+ // This button is for product watching
+ this.update_button($button, false, product_watch ? product_watch.id : undefined);
+ } else if (product_watch) {
+ // Disabled the button because all the components in the product is being watched
+ this.update_button($button, true);
+ } else {
+ const watch = watches.find(watch => watch.component_name === component);
+
+ this.update_button($button, false, watch ? watch.id : undefined);
+ }
+
+ $button.addEventListener('click', () => this.button_onclick($button));
+ });
+ } catch (ex) {}
+ }
+};
+
+window.addEventListener('DOMContentLoaded', () => new Bugzilla.ComponentWatching(), { once: true });
diff --git a/extensions/GoogleAnalytics/web/js/analytics.js b/extensions/GoogleAnalytics/web/js/analytics.js
index 25f7d7527..86f1f2592 100644
--- a/extensions/GoogleAnalytics/web/js/analytics.js
+++ b/extensions/GoogleAnalytics/web/js/analytics.js
@@ -15,6 +15,7 @@ $(function() {
ga('set', 'anonymizeIp', true);
ga('set', 'location', meta.data('location'));
ga('set', 'title', meta.data('title'));
+ ga('set', 'transport', 'beacon');
// Track page view
ga('send', 'pageview');
}
diff --git a/extensions/Gravatar/template/en/default/hook/bug/comments-user-image.html.tmpl b/extensions/Gravatar/template/en/default/hook/bug/comments-user-image.html.tmpl
index 3788a8452..361c02d2b 100644
--- a/extensions/Gravatar/template/en/default/hook/bug/comments-user-image.html.tmpl
+++ b/extensions/Gravatar/template/en/default/hook/bug/comments-user-image.html.tmpl
@@ -8,11 +8,7 @@
[% IF user.settings.show_gravatars.value == 'On' %]
[% IF who.last_activity_ts %]
- [% IF user.id %]
- <a href="user_profile?login=[% who.login FILTER uri %]">
- [% ELSE %]
- <a href="user_profile?user_id=[% who.id FILTER uri %]">
- [% END %]
+ <a href="user_profile?user_id=[% who.id FILTER none %]">
[% END %]
<img alt="User image" align="middle" src="[% who.gravatar FILTER none %]" width="32" height="32" border="0">
[% "</a>" IF who.last_activity_ts %]
diff --git a/extensions/PhabBugz/lib/Feed.pm b/extensions/PhabBugz/lib/Feed.pm
index c46d36c13..7d6b4e0ed 100644
--- a/extensions/PhabBugz/lib/Feed.pm
+++ b/extensions/PhabBugz/lib/Feed.pm
@@ -12,7 +12,7 @@ use 5.10.1;
use IO::Async::Timer::Periodic;
use IO::Async::Loop;
use List::Util qw(first);
-use List::MoreUtils qw(any);
+use List::MoreUtils qw(any uniq);
use Moo;
use Scalar::Util qw(blessed);
use Try::Tiny;
@@ -322,11 +322,27 @@ sub group_query {
# Make sure phab-bot also a member of the new project group so that it can
# make policy changes to the private revisions
- INFO("Setting project members for " . $project->name);
- my $set_members = $self->get_group_members( $group );
- push @$set_members, $phab_user unless grep $_->phid eq $phab_user->phid, @$set_members;
- $project->set_members( $set_members );
- $project->update();
+ INFO( "Checking project members for " . $project->name );
+ my $set_members = $self->get_group_members($group);
+ my @set_member_phids = uniq map { $_->phid } ( @$set_members, $phab_user );
+ my @current_member_phids = uniq map { $_->phid } @{ $project->members };
+ my ( $removed, $added ) = diff_arrays( \@current_member_phids, \@set_member_phids );
+
+ if (@$added) {
+ INFO( 'Adding project members: ' . join( ',', @$added ) );
+ $project->add_member($_) foreach @$added;
+ }
+
+ if (@$removed) {
+ INFO( 'Removing project members: ' . join( ',', @$removed ) );
+ $project->remove_member($_) foreach @$removed;
+ }
+
+ if (@$added || @$removed) {
+ my $result = $project->update();
+ local Bugzilla::Logging->fields->{api_result} = $result;
+ INFO( "Project " . $project->name . " updated" );
+ }
}
}
@@ -424,9 +440,9 @@ sub process_revision_change {
my ($timestamp) = Bugzilla->dbh->selectrow_array("SELECT NOW()");
INFO('Checking for revision attachment');
- my $attachment = create_revision_attachment($bug, $revision, $timestamp, $revision->author->bugzilla_user);
- INFO('Attachment ' . $attachment->id . ' created or already exists.');
-
+ my $rev_attachment = create_revision_attachment($bug, $revision, $timestamp, $revision->author->bugzilla_user);
+ INFO('Attachment ' . $rev_attachment->id . ' created or already exists.');
+
# ATTACHMENT OBSOLETES
# fixup attachments on current bug
@@ -567,7 +583,7 @@ sub process_revision_change {
# Email changes for this revisions bug and also for any other
# bugs that previously had these revision attachments
foreach my $bug_id ($revision->bug_id, keys %other_bugs) {
- Bugzilla::BugMail::Send($bug_id, { changer => Bugzilla->user });
+ Bugzilla::BugMail::Send($bug_id, { changer => $rev_attachment->attacher });
}
Bugzilla->set_user($old_user);
diff --git a/extensions/PhabBugz/lib/Project.pm b/extensions/PhabBugz/lib/Project.pm
index c52e1a661..b93a6eb9e 100644
--- a/extensions/PhabBugz/lib/Project.pm
+++ b/extensions/PhabBugz/lib/Project.pm
@@ -26,7 +26,7 @@ has id => ( is => 'ro', isa => Int );
has phid => ( is => 'ro', isa => Str );
has type => ( is => 'ro', isa => Str );
has name => ( is => 'ro', isa => Str );
-has description => ( is => 'ro', isa => Str );
+has description => ( is => 'ro', isa => Maybe[Str] );
has creation_ts => ( is => 'ro', isa => Str );
has modification_ts => ( is => 'ro', isa => Str );
has view_policy => ( is => 'ro', isa => Str );
@@ -307,7 +307,7 @@ sub set_policy {
############
sub _build_members {
- my ($self) = @_;
+ my ( $self ) = @_;
return [] unless $self->members_raw;
my @phids;
@@ -317,13 +317,11 @@ sub _build_members {
return [] if !@phids;
- my $users = Bugzilla::Extension::PhabBugz::User->match(
+ return Bugzilla::Extension::PhabBugz::User->match(
{
phids => \@phids
}
);
-
- return [ map { $_->bugzilla_user } @$users ];
}
1;
diff --git a/extensions/PhabBugz/lib/Revision.pm b/extensions/PhabBugz/lib/Revision.pm
index 900454220..4e82fa500 100644
--- a/extensions/PhabBugz/lib/Revision.pm
+++ b/extensions/PhabBugz/lib/Revision.pm
@@ -93,6 +93,8 @@ sub new_from_query {
: "";
return $class->new($result);
}
+
+ return undef;
}
sub BUILDARGS {
diff --git a/extensions/PhabBugz/lib/User.pm b/extensions/PhabBugz/lib/User.pm
index 9d4e9eef4..1bf1a842d 100644
--- a/extensions/PhabBugz/lib/User.pm
+++ b/extensions/PhabBugz/lib/User.pm
@@ -131,14 +131,18 @@ sub match {
attachments => { 'external-accounts' => 1 }
};
+ # We can only fetch 100 users at a time so we need to do this in lumps
my $phab_users = [];
- my $result = request( 'user.search', $data );
-
- if ( exists $result->{result}{data} && @{ $result->{result}{data} } ) {
- foreach my $user ( @{ $result->{result}{data} } ) {
- push @$phab_users, $class->new($user);
+ my $result;
+ do {
+ $result = request( 'user.search', $data );
+ if ( exists $result->{result}{data} && @{ $result->{result}{data} } ) {
+ foreach my $user ( @{ $result->{result}{data} } ) {
+ push @$phab_users, $class->new($user);
+ }
}
- }
+ $data->{after} = $result->{cursor}->{after};
+ } while ($result->{cursor}->{after});
return $phab_users;
}
diff --git a/extensions/PhabBugz/lib/Util.pm b/extensions/PhabBugz/lib/Util.pm
index d25f62f68..091475718 100644
--- a/extensions/PhabBugz/lib/Util.pm
+++ b/extensions/PhabBugz/lib/Util.pm
@@ -146,9 +146,10 @@ sub get_attachment_revisions {
my @revisions;
foreach my $revision_id (@revision_ids) {
- push @revisions, Bugzilla::Extension::PhabBugz::Revision->new_from_query({
+ my $revision = Bugzilla::Extension::PhabBugz::Revision->new_from_query({
ids => [ $revision_id ]
});
+ push @revisions, $revision if $revision;
}
return \@revisions;
diff --git a/extensions/PhabBugz/lib/WebService.pm b/extensions/PhabBugz/lib/WebService.pm
index 0239ccf74..fa9306667 100644
--- a/extensions/PhabBugz/lib/WebService.pm
+++ b/extensions/PhabBugz/lib/WebService.pm
@@ -14,6 +14,7 @@ use warnings;
use base qw(Bugzilla::WebService);
use Bugzilla::Constants;
+use Bugzilla::Error;
use Bugzilla::User;
use Bugzilla::Util qw(detaint_natural datetime_from time_ago trick_taint);
use Bugzilla::WebService::Constants;
@@ -29,34 +30,46 @@ use List::MoreUtils qw(any);
use MIME::Base64 qw(decode_base64);
use constant READ_ONLY => qw(
+ check_user_enter_bug_permission
check_user_permission_for_bug
needs_review
);
use constant PUBLIC_METHODS => qw(
+ check_user_enter_bug_permission
check_user_permission_for_bug
needs_review
set_build_target
);
-sub check_user_permission_for_bug {
- my ($self, $params) = @_;
-
- my $user = Bugzilla->login(LOGIN_REQUIRED);
-
+sub _check_phabricator {
# Ensure PhabBugz is on
ThrowUserError('phabricator_not_enabled')
unless Bugzilla->params->{phabricator_enabled};
+}
+
+sub _validate_phab_user {
+ my ($self, $user) = @_;
+
+ $self->_check_phabricator();
# Validate that the requesting user's email matches phab-bot
ThrowUserError('phabricator_unauthorized_user')
unless $user->login eq PHAB_AUTOMATION_USER;
+}
+
+sub check_user_permission_for_bug {
+ my ($self, $params) = @_;
+
+ my $user = Bugzilla->login(LOGIN_REQUIRED);
+
+ $self->_validate_phab_user($user);
# Validate that a bug id and user id are provided
ThrowUserError('phabricator_invalid_request_params')
unless ($params->{bug_id} && $params->{user_id});
- # Validate that the user and bug exist
+ # Validate that the user exists
my $target_user = Bugzilla::User->check({ id => $params->{user_id}, cache => 1 });
# Send back an object which says { "result": 1|0 }
@@ -65,10 +78,32 @@ sub check_user_permission_for_bug {
};
}
+sub check_user_enter_bug_permission {
+ my ($self, $params) = @_;
+
+ my $user = Bugzilla->login(LOGIN_REQUIRED);
+
+ $self->_validate_phab_user($user);
+
+ # Validate that a product name and user id are provided
+ ThrowUserError('phabricator_invalid_request_params')
+ unless ($params->{product} && $params->{user_id});
+
+ # Validate that the user exists
+ my $target_user = Bugzilla::User->check({ id => $params->{user_id}, cache => 1 });
+
+ # Send back an object with the attribute "result" set to 1 if the user
+ # can enter bugs into the given product, or 0 if not.
+ return {
+ result => $target_user->can_enter_product($params->{product}) ? 1 : 0
+ };
+}
+
sub needs_review {
my ($self, $params) = @_;
- ThrowUserError('phabricator_not_enabled')
- unless Bugzilla->params->{phabricator_enabled};
+
+ $self->_check_phabricator();
+
my $user = Bugzilla->login(LOGIN_REQUIRED);
my $dbh = Bugzilla->dbh;
@@ -169,13 +204,7 @@ sub set_build_target {
my $user = Bugzilla->login(LOGIN_REQUIRED);
- # Ensure PhabBugz is on
- ThrowUserError('phabricator_not_enabled')
- unless Bugzilla->params->{phabricator_enabled};
-
- # Validate that the requesting user's email matches phab-bot
- ThrowUserError('phabricator_unauthorized_user')
- unless $user->login eq PHAB_AUTOMATION_USER;
+ $self->_validate_phab_user($user);
my $revision_id = $params->{revision_id};
my $build_target = $params->{build_target};
@@ -204,13 +233,13 @@ sub rest_resources {
POST => {
method => 'set_build_target',
params => sub {
- return {
+ return {
revision_id => $_[0],
build_target => $_[1]
};
}
}
- },
+ },
# Bug permission checks
qr{^/phabbugz/check_bug/(\d+)/(\d+)$}, {
GET => {
@@ -220,6 +249,14 @@ sub rest_resources {
}
}
},
+ qr{^/phabbugz/check_enter_bug/([^/]+)/(\d+)$}, {
+ GET => {
+ method => 'check_user_enter_bug_permission',
+ params => sub {
+ return { product => $_[0], user_id => $_[1] };
+ },
+ },
+ },
# Review requests
qw{^/phabbugz/needs_review$}, {
GET => {
diff --git a/extensions/Review/Extension.pm b/extensions/Review/Extension.pm
index 72f16e3b6..a918a5ca5 100644
--- a/extensions/Review/Extension.pm
+++ b/extensions/Review/Extension.pm
@@ -25,6 +25,8 @@ use Bugzilla::Search;
use Bugzilla::User;
use Bugzilla::User::Setting;
use Bugzilla::Util qw(clean_text datetime_from diff_arrays);
+use Bugzilla::WebService::Util qw(filter_wants);
+use Scalar::Util qw(blessed);
use constant UNAVAILABLE_RE => qr/\b(?:unavailable|pto|away)\b/i;
use constant MENTOR_LIMIT => 10;
@@ -1068,4 +1070,51 @@ sub config_modify_panels {
};
}
+#
+# hooks
+#
+
+sub webservice_user_get {
+ my ($self, $args) = @_;
+ my ($webservice, $params, $users) = @$args{qw(webservice params users)};
+
+ return unless filter_wants($params, 'requests');
+
+ my $ids = [
+ map { blessed($_->{id}) ? $_->{id}->value : $_->{id} }
+ grep { exists $_->{id} }
+ @$users
+ ];
+
+ return unless @$ids;
+
+ my %user_map = map { $_->id => $_ } @{ Bugzilla::User->new_from_list($ids) };
+
+ foreach my $user (@$users) {
+ my $id = blessed($user->{id}) ? $user->{id}->value : $user->{id};
+ my $user_obj = $user_map{$id};
+
+ $user->{requests} = {
+ review => {
+ blocked => $webservice->type('boolean', $user_obj->reviews_blocked),
+ pending => $webservice->type('int', $user_obj->{review_request_count}),
+ },
+ feedback => {
+ # reviews_blocked includes feedback as well
+ blocked => $webservice->type('boolean', $user_obj->reviews_blocked),
+ pending => $webservice->type('int', $user_obj->{feedback_request_count}),
+ },
+ needinfo => {
+ blocked => $webservice->type('boolean', $user_obj->needinfo_blocked),
+ pending => $webservice->type('int', $user_obj->{needinfo_request_count}),
+ },
+ };
+ }
+}
+
+sub webservice_user_suggest {
+ my ($self, $args) = @_;
+ $self->webservice_user_get($args);
+}
+
__PACKAGE__->NAME;
diff --git a/extensions/UserProfile/template/en/default/hook/account/prefs/account-start.html.tmpl b/extensions/UserProfile/template/en/default/hook/account/prefs/account-start.html.tmpl
index f2e3aad01..b3a2fc5ea 100644
--- a/extensions/UserProfile/template/en/default/hook/account/prefs/account-start.html.tmpl
+++ b/extensions/UserProfile/template/en/default/hook/account/prefs/account-start.html.tmpl
@@ -6,6 +6,6 @@
# defined by the Mozilla Public License, v. 2.0.
#%]
-<a href="user_profile?login=[% user.login FILTER uri %]">
+<a href="user_profile?user_id=[% user.id FILTER none %]">
[% terms.Bugzilla %] User Profile
</a><br><hr>
diff --git a/heartbeat.cgi b/heartbeat.cgi
index 773673697..11bb3ac30 100755
--- a/heartbeat.cgi
+++ b/heartbeat.cgi
@@ -29,6 +29,15 @@ my $ok = eval {
die "database not available" unless $database_ok;
die "memcached server(s) not available" unless $memcached_ok;
+ if ($dbh->isa('Bugzilla::DB::Mysql') && Bugzilla->params->{utf8} eq 'utf8mb4') {
+ my $mysql_var = $dbh->selectall_hashref(q{SHOW VARIABLES LIKE 'character_set%'}, 'Variable_name');
+ foreach my $name (qw( character_set_client character_set_connection character_set_database )) {
+ my $value = $mysql_var->{$name}{Value};
+ if ($value ne 'utf8mb4') {
+ die "Expected MySQL variable '$name' to be 'utf8mb4', found '$value'";
+ }
+ }
+ }
1;
};
FATAL("heartbeat error: $@") if !$ok && $@;
diff --git a/js/dropdown.js b/js/dropdown.js
index fd71d0b6e..03345206b 100644
--- a/js/dropdown.js
+++ b/js/dropdown.js
@@ -16,7 +16,7 @@ $(function() {
// clicking dropdown button opens or closes the dropdown content
if (!$(e.target).hasClass('dropdown-button')) {
$('.dropdown-button').each(function() {
- toggleDropDown(e, $(this), $('#' + $(this).attr('aria-controls')), 1);
+ toggleDropDown(e, $(this), $('#' + $(this).attr('aria-controls')), false, true);
});
}
}).keydown(function(e) {
@@ -25,7 +25,7 @@ $(function() {
$('.dropdown-button').each(function() {
var $button = $(this);
if ($button.siblings('.dropdown-content').is(':visible')) {
- toggleDropDown(e, $button, $('#' + $button.attr('aria-controls')), 1);
+ toggleDropDown(e, $button, $('#' + $button.attr('aria-controls')), false, true);
$button.focus();
}
});
@@ -83,7 +83,7 @@ $(function() {
// navigate to an active link or click on it
// note that `trigger('click')` doesn't always work
if (e.keyCode == 13) {
- var $link = $('.dropdown-content:visible a.active');
+ var $link = $('.dropdown-content:visible .active');
if ($link.length) {
if ($link.attr('href')) {
location.href = $link.attr('href');
@@ -105,7 +105,7 @@ $(function() {
var $content = $div.find('.dropdown-content');
$button.click(function(e) {
// Do not handle non-primary click.
- if (e.button != 0) {
+ if (e.button != 0 || $content.hasClass('hover-display')) {
return;
}
toggleDropDown(e, $button, $content);
@@ -115,9 +115,13 @@ $(function() {
// prevent the form being submitted if the search bar is empty
e.preventDefault();
// navigate to an active link if any
- var $link = $content.find('a.active');
+ var $link = $content.find('.active');
if ($link.length) {
- location.href = $link.attr('href');
+ if ($link.attr('href')) {
+ location.href = $link.attr('href');
+ } else {
+ $link.trigger('click');
+ }
}
}
@@ -125,9 +129,49 @@ $(function() {
toggleDropDown(e, $button, $content);
}
});
+
+ if ($content.hasClass('hover-display')) {
+ const $_button = $button.get(0);
+ const $_content = $content.get(0);
+ let timer;
+
+ const button_handler = event => {
+ event.preventDefault();
+ event.stopPropagation();
+ window.clearTimeout(timer);
+
+ if (event.type === 'mouseleave' && $_content.matches('.hovered')) {
+ return;
+ }
+
+ timer = window.setTimeout(() => {
+ toggleDropDown(event, $button, $content, event.type === 'mouseenter', event.type === 'mouseleave');
+ }, 250);
+ };
+
+ const content_handler = event => {
+ event.preventDefault();
+ event.stopPropagation();
+ window.clearTimeout(timer);
+
+ $_content.classList.toggle('hovered', event.type === 'mouseenter');
+
+ if (event.type === 'mouseleave') {
+ timer = window.setTimeout(() => {
+ toggleDropDown(event, $button, $content, false, true);
+ }, 250);
+ }
+ };
+
+ // Use raw `addEventListener` as jQuery actually listens `mouseover` and `mouseout`
+ $_button.addEventListener('mouseenter', event => button_handler(event));
+ $_button.addEventListener('mouseleave', event => button_handler(event));
+ $_content.addEventListener('mouseenter', event => content_handler(event));
+ $_content.addEventListener('mouseleave', event => content_handler(event));
+ }
});
- function toggleDropDown(e, $button, $content, hide_only) {
+ function toggleDropDown(e, $button, $content, show_only, hide_only) {
// hide other expanded dropdown menu if any
var $expanded = $('.dropdown-button[aria-expanded="true"]');
if ($expanded.length && !$expanded.is($button)) {
@@ -148,13 +192,12 @@ $(function() {
$('[aria-controls="' + content_id + '"]').removeAttr('aria-activedescendant');
$content.find('#' + content_id + '-active-item').removeAttr('id');
}
- if ($content.is(':visible')) {
- $content.hide();
- $button.attr('aria-expanded', false);
- }
// if not using Escape or clicking outside the dropdown div, then we are hiding
- else if (!hide_only) {
- $content.show();
+ if ($content.is(':visible') || hide_only) {
+ $content.fadeOut('fast');
+ $button.attr('aria-expanded', false);
+ } else if (!$content.is(':visible') || show_only) {
+ $content.fadeIn('fast');
$button.attr('aria-expanded', true);
}
}
diff --git a/js/field.js b/js/field.js
index a5e204f8d..ddf6b8b1c 100644
--- a/js/field.js
+++ b/js/field.js
@@ -715,7 +715,7 @@ $(function() {
var options_user = {
appendTo: $('#main-inner'),
forceFixPosition: true,
- serviceUrl: 'rest/elastic/suggest_users',
+ serviceUrl: 'rest/user/suggest',
params: {
Bugzilla_api_token: BUGZILLA.api_token,
},
diff --git a/js/global.js b/js/global.js
index d0396d6a8..37567e3de 100644
--- a/js/global.js
+++ b/js/global.js
@@ -155,6 +155,47 @@ function display_value(field, value) {
return value;
}
+// ajax wrapper, to simplify error handling and auth
+// TODO: Rewrite this method using Promise (Bug 1380437)
+function bugzilla_ajax(request, done_fn, error_fn) {
+ $('#xhr-error').hide('');
+ $('#xhr-error').html('');
+ request.url += (request.url.match('\\?') ? '&' : '?') +
+ 'Bugzilla_api_token=' + encodeURIComponent(BUGZILLA.api_token);
+ if (request.type != 'GET') {
+ request.contentType = 'application/json';
+ request.processData = false;
+ if (request.data && request.data.constructor === Object) {
+ request.data = JSON.stringify(request.data);
+ }
+ }
+ return $.ajax(request)
+ .done(function(data) {
+ if (data.error) {
+ if (!request.hideError) {
+ $('#xhr-error').html(data.message);
+ $('#xhr-error').show('fast');
+ }
+ if (error_fn)
+ error_fn(data.message);
+ }
+ else if (done_fn) {
+ done_fn(data);
+ }
+ })
+ .fail(function(data) {
+ if (data.statusText === 'abort')
+ return;
+ var message = data.responseJSON ? data.responseJSON.message : 'Unexpected Error'; // all errors are unexpected :)
+ if (!request.hideError) {
+ $('#xhr-error').html(message);
+ $('#xhr-error').show('fast');
+ }
+ if (error_fn)
+ error_fn(message);
+ });
+}
+
// polyfill .trim
if (!String.prototype.trim) {
(function() {
diff --git a/qa/t/lib/QA/Util.pm b/qa/t/lib/QA/Util.pm
index 5d041d560..bf9151fee 100644
--- a/qa/t/lib/QA/Util.pm
+++ b/qa/t/lib/QA/Util.pm
@@ -18,6 +18,7 @@ use Sys::Hostname qw(hostname);
use Socket qw(inet_ntoa);
use WWW::Selenium::Util qw(server_is_running);
use URI;
+use URI::QueryParam;
# Fixes wide character warnings
BEGIN {
@@ -50,6 +51,7 @@ use base qw(Exporter);
get_selenium
get_rpc_clients
+ check_page_load
WAIT_TIME
CHROME_MODE
@@ -396,6 +398,32 @@ sub set_parameters {
}
}
+my @ANY_KEYS = qw( t token );
+
+sub check_page_load {
+ my ($sel, $wait, $expected) = @_;
+ my $expected_uri = URI->new($expected);
+ $sel->wait_for_page_to_load_ok($wait);
+ my $uri = URI->new($sel->get_location);
+
+ foreach my $u ($expected_uri, $uri) {
+ $u->host('HOSTNAME');
+ foreach my $any_key (@ANY_KEYS) {
+ if ($u->query_param($any_key)) {
+ $u->query_param($any_key => '__ANYTHING__');
+ }
+ }
+ }
+
+ if ($expected_uri->query_param('id')) {
+ if ($expected_uri->query_param('id') eq '__BUG_ID__') {
+ $uri->query_param('id' => '__BUG_ID__');
+ }
+ }
+ my ($pkg, $file, $line) = caller;
+ is($uri, $expected_uri, "checking location on $file line $line");
+}
+
1;
__END__
diff --git a/qa/t/test_bug_edit.t b/qa/t/test_bug_edit.t
index 07a64876b..01037aa1e 100644
--- a/qa/t/test_bug_edit.t
+++ b/qa/t/test_bug_edit.t
@@ -32,10 +32,10 @@ if ($sel->is_text_present("My bugs from QA_Selenium")) {
# Just in case the test failed before completion previously, reset the CANEDIT bit.
go_to_admin($sel);
$sel->click_ok("link=Groups");
-$sel->wait_for_page_to_load_ok(WAIT_TIME);
+check_page_load($sel, WAIT_TIME, q{http://HOSTNAME:8000/bmo/editgroups.cgi});
$sel->title_is("Edit Groups");
$sel->click_ok("link=Master");
-$sel->wait_for_page_to_load_ok(WAIT_TIME);
+check_page_load($sel, WAIT_TIME, q{http://HOSTNAME:8000/bmo/editgroups.cgi?action=changeform&group=26});
$sel->title_is("Change Group: Master");
my $group_url = $sel->get_location();
$group_url =~ /group=(\d+)$/;
@@ -52,7 +52,7 @@ $sel->select_ok("bug_severity", "label=critical");
$sel->type_ok("short_desc", "Test bug editing");
$sel->type_ok("comment", "ploc");
$sel->click_ok("commit");
-$sel->wait_for_page_to_load_ok(WAIT_TIME);
+check_page_load($sel, WAIT_TIME, qq{http://HOSTNAME:8000/bmo/show_bug.cgi?id=__BUG_ID__});
my $bug1_id = $sel->get_value('//input[@name="id" and @type="hidden"]');
$sel->is_text_present_ok('has been added to the database', "Bug $bug1_id created");
@@ -67,28 +67,28 @@ $sel->type_ok("status_whiteboard", "[Selenium was here]");
$sel->type_ok("comment", "new comment from me :)");
$sel->select_ok("bug_status", "label=RESOLVED");
$sel->click_ok("commit");
-$sel->wait_for_page_to_load_ok(WAIT_TIME);
+check_page_load($sel, WAIT_TIME, qq{http://HOSTNAME:8000/bmo/show_bug.cgi?id=$bug1_id});
$sel->is_text_present_ok("Changes submitted for bug $bug1_id");
# Now move the bug into another product, which has a mandatory group.
$sel->click_ok("link=bug $bug1_id");
-$sel->wait_for_page_to_load_ok(WAIT_TIME);
+check_page_load($sel, WAIT_TIME, qq{http://HOSTNAME:8000/bmo/show_bug.cgi?id=$bug1_id});
$sel->title_like(qr/^$bug1_id /);
$sel->select_ok("product", "label=QA-Selenium-TEST");
$sel->type_ok("comment", "moving to QA-Selenium-TEST");
$sel->click_ok("commit");
-$sel->wait_for_page_to_load_ok(WAIT_TIME);
+check_page_load($sel, WAIT_TIME, q{http://HOSTNAME:8000/bmo/process_bug.cgi});
$sel->title_is("Verify New Product Details...");
$sel->select_ok("component", "label=QA-Selenium-TEST");
$sel->is_element_present_ok('//input[@type="checkbox" and @name="groups" and @value="QA-Selenium-TEST"]');
ok(!$sel->is_editable('//input[@type="checkbox" and @name="groups" and @value="QA-Selenium-TEST"]'), "QA-Selenium-TEST group not editable");
$sel->is_checked_ok('//input[@type="checkbox" and @name="groups" and @value="QA-Selenium-TEST"]', "QA-Selenium-TEST group is selected");
$sel->click_ok("change_product");
-$sel->wait_for_page_to_load_ok(WAIT_TIME);
+check_page_load($sel, WAIT_TIME, qq{http://HOSTNAME:8000/bmo/show_bug.cgi?id=$bug1_id});
$sel->is_text_present_ok("Changes submitted for bug $bug1_id");
$sel->click_ok("link=bug $bug1_id");
-$sel->wait_for_page_to_load_ok(WAIT_TIME);
+check_page_load($sel, WAIT_TIME, qq{http://HOSTNAME:8000/bmo/show_bug.cgi?id=$bug1_id});
$sel->title_like(qr/^$bug1_id /);
$sel->select_ok("bug_severity", "label=normal");
$sel->select_ok("priority", "label=High");
@@ -101,14 +101,14 @@ $sel->type_ok("comment", "Unchecking the reporter_accessible checkbox");
$sel->click_ok("reporter_accessible");
$sel->select_ok("bug_status", "label=VERIFIED");
$sel->click_ok("commit");
-$sel->wait_for_page_to_load_ok(WAIT_TIME);
+check_page_load($sel, WAIT_TIME, qq{http://HOSTNAME:8000/bmo/show_bug.cgi?id=$bug1_id});
$sel->is_text_present_ok("Changes submitted for bug $bug1_id");
$sel->click_ok("link=bug $bug1_id");
-$sel->wait_for_page_to_load_ok(WAIT_TIME);
+check_page_load($sel, WAIT_TIME, qq{http://HOSTNAME:8000/bmo/show_bug.cgi?id=$bug1_id});
$sel->title_like(qr/^$bug1_id /);
$sel->type_ok("comment", "I am the reporter, but I can see the bug anyway as I belong to the mandatory group");
$sel->click_ok("commit");
-$sel->wait_for_page_to_load_ok(WAIT_TIME);
+check_page_load($sel, WAIT_TIME, qq{http://HOSTNAME:8000/bmo/show_bug.cgi?id=$bug1_id});
$sel->is_text_present_ok("Changes submitted for bug $bug1_id");
logout($sel);
@@ -125,16 +125,16 @@ $sel->click_ok("bz_assignee_edit_action");
$sel->type_ok("assigned_to", $config->{admin_user_login});
$sel->type_ok("comment", "I have editbugs privs. Taking!");
$sel->click_ok("commit");
-$sel->wait_for_page_to_load_ok(WAIT_TIME);
+check_page_load($sel, WAIT_TIME, qq{http://HOSTNAME:8000/bmo/show_bug.cgi?id=$bug1_id});
$sel->is_text_present_ok("Changes submitted for bug $bug1_id");
$sel->click_ok("link=bug $bug1_id");
-$sel->wait_for_page_to_load_ok(WAIT_TIME);
+check_page_load($sel, WAIT_TIME, qq{http://HOSTNAME:8000/bmo/show_bug.cgi?id=$bug1_id});
$sel->title_like(qr/^$bug1_id /);
$sel->click_ok("cc_edit_area_showhide");
$sel->type_ok("newcc", $config->{unprivileged_user_login});
$sel->click_ok("commit");
-$sel->wait_for_page_to_load_ok(WAIT_TIME);
+check_page_load($sel, WAIT_TIME, qq{http://HOSTNAME:8000/bmo/show_bug.cgi?id=$bug1_id});
$sel->is_text_present_ok("Changes submitted for bug $bug1_id");
logout($sel);
@@ -153,7 +153,7 @@ go_to_bug($sel, $bug1_id);
$sel->click_ok("cclist_accessible");
$sel->type_ok("comment", "I am allowed to turn off cclist_accessible despite not being in the mandatory group");
$sel->click_ok("commit");
-$sel->wait_for_page_to_load_ok(WAIT_TIME);
+check_page_load($sel, WAIT_TIME, qq{http://HOSTNAME:8000/bmo/show_bug.cgi?id=$bug1_id});
$sel->is_text_present_ok("Changes submitted for bug $bug1_id");
logout($sel);
@@ -162,7 +162,7 @@ logout($sel);
log_in($sel, $config, 'unprivileged');
$sel->type_ok("quicksearch_top", $bug1_id);
$sel->submit("header-search");
-$sel->wait_for_page_to_load_ok(WAIT_TIME);
+check_page_load($sel, WAIT_TIME, qq{http://HOSTNAME:8000/bmo/show_bug.cgi?id=$bug1_id});
$sel->title_is("Access Denied");
$sel->is_text_present_ok("You are not authorized to access bug $bug1_id");
logout($sel);
@@ -178,7 +178,7 @@ $sel->click_ok("set_default_assignee");
$sel->uncheck_ok("set_default_assignee");
$sel->type_ok("comment", "-> Moving back to Testproduct.");
$sel->click_ok("commit");
-$sel->wait_for_page_to_load_ok(WAIT_TIME);
+check_page_load($sel, WAIT_TIME, q{http://HOSTNAME:8000/bmo/process_bug.cgi});
$sel->title_is("Verify New Product Details...");
$sel->select_ok("component", "label=TestComponent");
$sel->is_text_present_ok("These groups are not legal for the 'TestProduct' product or you are not allowed to restrict bugs to these groups");
@@ -189,15 +189,15 @@ $sel->is_element_present_ok('//input[@type="checkbox" and @name="groups" and @va
$sel->is_editable_ok('//input[@type="checkbox" and @name="groups" and @value="Master"]', "Master group is editable");
ok(!$sel->is_checked('//input[@type="checkbox" and @name="groups" and @value="Master"]'), "Master group not selected by default");
$sel->click_ok("change_product");
-$sel->wait_for_page_to_load_ok(WAIT_TIME);
+check_page_load($sel, WAIT_TIME, qq{http://HOSTNAME:8000/bmo/show_bug.cgi?id=$bug1_id});
$sel->is_text_present_ok("Changes submitted for bug $bug1_id");
$sel->click_ok("link=bug $bug1_id");
-$sel->wait_for_page_to_load_ok(WAIT_TIME);
+check_page_load($sel, WAIT_TIME, qq{http://HOSTNAME:8000/bmo/show_bug.cgi?id=$bug1_id});
$sel->title_like(qr/^$bug1_id /);
$sel->click_ok("cclist_accessible");
$sel->type_ok("comment", "I am allowed to turn off cclist_accessible despite not being in the mandatory group");
$sel->click_ok("commit");
-$sel->wait_for_page_to_load_ok(WAIT_TIME);
+check_page_load($sel, WAIT_TIME, qq{http://HOSTNAME:8000/bmo/show_bug.cgi?id=$bug1_id});
$sel->is_text_present_ok("Changes submitted for bug $bug1_id");
logout($sel);
@@ -216,7 +216,7 @@ $sel->click_ok("cc_edit_area_showhide");
$sel->add_selection_ok("cc", "label=" . $config->{admin_user_login});
$sel->click_ok("removecc");
$sel->click_ok("commit");
-$sel->wait_for_page_to_load_ok(WAIT_TIME);
+check_page_load($sel, WAIT_TIME, qq{http://HOSTNAME:8000/bmo/show_bug.cgi?id=$bug1_id});
$sel->is_text_present_ok("Changes submitted for bug $bug1_id");
logout($sel);
@@ -225,11 +225,11 @@ logout($sel);
log_in($sel, $config, 'admin');
edit_product($sel, "TestProduct");
$sel->click_ok("link=Edit Group Access Controls:");
-$sel->wait_for_page_to_load_ok(WAIT_TIME);
+check_page_load($sel, WAIT_TIME, q{http://HOSTNAME:8000/bmo/editproducts.cgi?action=editgroupcontrols&product=TestProduct});
$sel->title_is("Edit Group Controls for TestProduct");
$sel->check_ok("canedit_$master_gid");
$sel->click_ok("submit");
-$sel->wait_for_page_to_load_ok(WAIT_TIME);
+check_page_load($sel, WAIT_TIME, q{http://HOSTNAME:8000/bmo/editproducts.cgi});
$sel->title_is("Update group access controls for TestProduct");
# The user is in the master group, so he can comment.
@@ -237,7 +237,7 @@ $sel->title_is("Update group access controls for TestProduct");
go_to_bug($sel, $bug1_id);
$sel->type_ok("comment", "Do nothing except adding a comment...");
$sel->click_ok("commit");
-$sel->wait_for_page_to_load_ok(WAIT_TIME);
+check_page_load($sel, WAIT_TIME, qq{http://HOSTNAME:8000/bmo/show_bug.cgi?id=$bug1_id});
$sel->is_text_present_ok("Changes submitted for bug $bug1_id");
logout($sel);
@@ -247,7 +247,7 @@ log_in($sel, $config, 'QA_Selenium_TEST');
go_to_bug($sel, $bug1_id);
$sel->type_ok("comment", "Just a comment too...");
$sel->click_ok("commit");
-$sel->wait_for_page_to_load_ok(WAIT_TIME);
+check_page_load($sel, WAIT_TIME, q{http://HOSTNAME:8000/bmo/process_bug.cgi});
$sel->title_is("Product Edit Access Denied");
$sel->is_text_present_ok("You are not permitted to edit bugs in product TestProduct.");
logout($sel);
@@ -256,10 +256,12 @@ logout($sel);
log_in($sel, $config, 'admin');
open_advanced_search_page($sel);
+screenshot_page($sel, '/app/artifacts/line259.png');
$sel->remove_all_selections_ok("product");
$sel->add_selection_ok("product", "TestProduct");
$sel->remove_all_selections_ok("bug_status");
$sel->remove_all_selections_ok("resolution");
+screenshot_page($sel, '/app/artifacts/line264.png');
$sel->is_checked_ok("emailassigned_to1");
$sel->select_ok("emailtype1", "label=is");
$sel->type_ok("email1", $config->{admin_user_login});
@@ -268,21 +270,22 @@ $sel->check_ok("emailqa_contact2");
$sel->check_ok("emailcc2");
$sel->select_ok("emailtype2", "label=is");
$sel->type_ok("email2", $config->{QA_Selenium_TEST_user_login});
+screenshot_page($sel, '/app/artifacts/line271.png');
$sel->click_ok("Search");
-$sel->wait_for_page_to_load_ok(WAIT_TIME);
+check_page_load($sel, WAIT_TIME, q{http://HOSTNAME:8000/bmo/buglist.cgi?emailreporter2=1&emailtype2=exact&order=Importance&list_id=15&emailtype1=exact&emailcc2=1&query_format=advanced&emailassigned_to1=1&emailqa_contact2=1&email2=QA-Selenium-TEST%40mozilla.test&email1=admin%40mozilla.test&emailassigned_to2=1&product=TestProduct});
$sel->title_is("Bug List");
-
+screenshot_page($sel, '/app/artifacts/line275.png');
$sel->is_text_present_ok("One bug found.");
$sel->type_ok("save_newqueryname", "My bugs from QA_Selenium");
$sel->click_ok("remember");
-$sel->wait_for_page_to_load_ok(WAIT_TIME);
+check_page_load($sel, WAIT_TIME, q{http://HOSTNAME:8000/bmo/buglist.cgi?newquery=email1%3Dadmin%2540mozilla.test%26email2%3DQA-Selenium-TEST%2540mozilla.test%26emailassigned_to1%3D1%26emailassigned_to2%3D1%26emailcc2%3D1%26emailqa_contact2%3D1%26emailreporter2%3D1%26emailtype1%3Dexact%26emailtype2%3Dexact%26list_id%3D15%26product%3DTestProduct%26query_format%3Dadvanced%26order%3Dpriority%252Cbug_severity&cmdtype=doit&remtype=asnamed&token=1531926552-dc69995d79c786af046436ec6717000b&newqueryname=My%20bugs%20from%20QA_Selenium&list_id=16});
$sel->title_is("Search created");
$sel->is_text_present_ok("OK, you have a new search named My bugs from QA_Selenium.");
$sel->click_ok("link=My bugs from QA_Selenium");
-$sel->wait_for_page_to_load_ok(WAIT_TIME);
+check_page_load($sel, WAIT_TIME, q{http://HOSTNAME:8000/bmo/buglist.cgi?cmdtype=runnamed&namedcmd=My%20bugs%20from%20QA_Selenium&list_id=17});
$sel->title_is("Bug List: My bugs from QA_Selenium");
$sel->click_ok("long_format");
-$sel->wait_for_page_to_load_ok(WAIT_TIME);
+check_page_load($sel, WAIT_TIME, q{http://HOSTNAME:8000/bmo/show_bug.cgi});
$sel->title_is("Full Text Bug Listing");
$sel->is_text_present_ok("Bug $bug1_id");
$sel->is_text_present_ok("Status: CONFIRMED");
@@ -303,25 +306,25 @@ $sel->type_ok("short_desc", "New bug from me");
# We turned on the CANEDIT bit for TestProduct.
$sel->type_ok("comment", "I can enter a new bug, but not edit it, right?");
$sel->click_ok("commit");
-$sel->wait_for_page_to_load_ok(WAIT_TIME);
+check_page_load($sel, WAIT_TIME, qq{http://HOSTNAME:8000/bmo/show_bug.cgi?id=__BUG_ID__});
my $bug2_id = $sel->get_value('//input[@name="id" and @type="hidden"]');
$sel->is_text_present_ok('has been added to the database', "Bug $bug2_id created");
# Clicking the "Back" button and resubmitting the form again should trigger a suspicous action error.
$sel->go_back_ok();
-$sel->wait_for_page_to_load_ok(WAIT_TIME);
+check_page_load($sel, WAIT_TIME, q{http://HOSTNAME:8000/bmo/enter_bug.cgi?product=TestProduct&format=__default__});
$sel->title_is("Enter Bug: TestProduct");
$sel->click_ok("commit");
-$sel->wait_for_page_to_load_ok(WAIT_TIME);
+check_page_load($sel, WAIT_TIME, q{http://HOSTNAME:8000/bmo/post_bug.cgi});
$sel->title_is("Suspicious Action");
$sel->is_text_present_ok("you have no valid token for the create_bug action");
$sel->click_ok('//input[@value="Confirm Changes"]');
-$sel->wait_for_page_to_load_ok(WAIT_TIME);
+check_page_load($sel, WAIT_TIME, q{http://HOSTNAME:8000/bmo/show_bug.cgi?id=15});
$sel->is_text_present_ok('has been added to the database', 'Bug created');
$sel->type_ok("comment", "New comment not allowed");
$sel->click_ok("commit");
-$sel->wait_for_page_to_load_ok(WAIT_TIME);
+check_page_load($sel, WAIT_TIME, q{http://HOSTNAME:8000/bmo/process_bug.cgi});
$sel->title_is("Product Edit Access Denied");
$sel->is_text_present_ok("You are not permitted to edit bugs in product TestProduct.");
logout($sel);
@@ -334,42 +337,46 @@ $sel->click_ok("bz_assignee_edit_action");
$sel->type_ok("assigned_to", $config->{admin_user_login});
$sel->type_ok("comment", "Taking!");
$sel->click_ok("commit");
-$sel->wait_for_page_to_load_ok(WAIT_TIME);
+check_page_load($sel, WAIT_TIME, qq{http://HOSTNAME:8000/bmo/show_bug.cgi?id=$bug2_id});
$sel->is_text_present_ok("Changes submitted for bug $bug2_id");
# Test mass-change.
$sel->click_ok("link=My bugs from QA_Selenium");
-$sel->wait_for_page_to_load_ok(WAIT_TIME);
+screenshot_page($sel, '/app/artifacts/line344.png');
+check_page_load($sel, WAIT_TIME, q{http://HOSTNAME:8000/bmo/buglist.cgi?cmdtype=runnamed&namedcmd=My%20bugs%20from%20QA_Selenium&list_id=19});
+screenshot_page($sel, '/app/artifacts/line346.png');
$sel->title_is("Bug List: My bugs from QA_Selenium");
+screenshot_page($sel, '/app/artifacts/line348.png');
$sel->is_text_present_ok("2 bugs found");
+screenshot_page($sel, '/app/artifacts/line350.png');
$sel->click_ok("link=Change Several Bugs at Once");
-$sel->wait_for_page_to_load_ok(WAIT_TIME);
+check_page_load($sel, WAIT_TIME, q{http://HOSTNAME:8000/bmo/buglist.cgi?email1=admin%40mozilla.test&email2=QA-Selenium-TEST%40mozilla.test&emailassigned_to1=1&emailassigned_to2=1&emailcc2=1&emailqa_contact2=1&emailreporter2=1&emailtype1=exact&emailtype2=exact&product=TestProduct&query_format=advanced&order=priority%2Cbug_severity&tweak=1&list_id=20});
$sel->title_is("Bug List");
$sel->click_ok("check_all");
$sel->type_ok("comment", 'Mass change"');
$sel->select_ok("bug_status", "label=RESOLVED");
$sel->select_ok("resolution", "label=WORKSFORME");
$sel->click_ok("commit");
-$sel->wait_for_page_to_load_ok(WAIT_TIME);
+check_page_load($sel, WAIT_TIME, q{http://HOSTNAME:8000/bmo/process_bug.cgi});
$sel->title_is("Bugs processed");
$sel->click_ok("link=bug $bug1_id");
-$sel->wait_for_page_to_load_ok(WAIT_TIME);
+check_page_load($sel, WAIT_TIME, qq{http://HOSTNAME:8000/bmo/show_bug.cgi?id=$bug1_id});
$sel->title_like(qr/$bug1_id /);
$sel->selected_label_is("resolution", "WORKSFORME");
$sel->select_ok("resolution", "label=INVALID");
$sel->click_ok("commit");
-$sel->wait_for_page_to_load_ok(WAIT_TIME);
+check_page_load($sel, WAIT_TIME, qq{http://HOSTNAME:8000/bmo/show_bug.cgi?id=$bug1_id});
$sel->is_text_present_ok("Changes submitted for bug $bug1_id");
$sel->click_ok("link=bug $bug1_id");
-$sel->wait_for_page_to_load_ok(WAIT_TIME);
+check_page_load($sel, WAIT_TIME, qq{http://HOSTNAME:8000/bmo/show_bug.cgi?id=$bug1_id});
$sel->title_like(qr/$bug1_id /);
$sel->selected_label_is("resolution", "INVALID");
$sel->click_ok("link=History");
-$sel->wait_for_page_to_load_ok(WAIT_TIME);
+check_page_load($sel, WAIT_TIME, qq{http://HOSTNAME:8000/bmo/show_activity.cgi?id=$bug1_id});
$sel->title_is("Changes made to bug $bug1_id");
$sel->is_text_present_ok("URL foo.cgi?action=bar");
$sel->is_text_present_ok("Severity critical blocker");
@@ -431,10 +438,10 @@ foreach my $params (["no_token_single_bug", ""], ["invalid_token_single_bug", "&
$sel->title_is("Suspicious Action");
$sel->is_text_present_ok($token ? "an invalid token" : "web browser directly");
$sel->click_ok("confirm");
- $sel->wait_for_page_to_load_ok(WAIT_TIME);
+ check_page_load($sel, WAIT_TIME, qq{http://HOSTNAME:8000/bmo/show_bug.cgi?id=$bug1_id});
$sel->is_text_present_ok("Changes submitted for bug $bug1_id");
$sel->click_ok("link=bug $bug1_id");
- $sel->wait_for_page_to_load_ok(WAIT_TIME);
+ check_page_load($sel, WAIT_TIME, qq{http://HOSTNAME:8000/bmo/show_bug.cgi?id=$bug1_id});
$sel->title_like(qr/^$bug1_id /);
$sel->is_text_present_ok($comment);
}
@@ -446,16 +453,16 @@ foreach my $params (["no_token_mass_change", ""], ["invalid_token_mass_change",
$sel->title_is("Suspicious Action");
$sel->is_text_present_ok("no valid token for the buglist_mass_change action");
$sel->click_ok("confirm");
- $sel->wait_for_page_to_load_ok(WAIT_TIME);
+ check_page_load($sel, WAIT_TIME, q{http://HOSTNAME:8000/bmo/process_bug.cgi});
$sel->title_is("Bugs processed");
foreach my $bug_id ($bug1_id, $bug2_id) {
$sel->click_ok("link=bug $bug_id");
- $sel->wait_for_page_to_load_ok(WAIT_TIME);
+ check_page_load($sel, WAIT_TIME, qq{http://HOSTNAME:8000/bmo/show_bug.cgi?id=$bug_id});
$sel->title_like(qr/^$bug_id /);
$sel->is_text_present_ok($comment);
next if $bug_id == $bug2_id;
$sel->go_back_ok();
- $sel->wait_for_page_to_load_ok(WAIT_TIME);
+ check_page_load($sel, WAIT_TIME, q{http://HOSTNAME:8000/bmo/process_bug.cgi});
$sel->title_is("Bugs processed");
}
}
@@ -463,26 +470,26 @@ foreach my $params (["no_token_mass_change", ""], ["invalid_token_mass_change",
# Now move these bugs out of our radar.
$sel->click_ok("link=My bugs from QA_Selenium");
-$sel->wait_for_page_to_load_ok(WAIT_TIME);
+check_page_load($sel, WAIT_TIME, q{http://HOSTNAME:8000/bmo/buglist.cgi?cmdtype=runnamed&namedcmd=My%20bugs%20from%20QA_Selenium&list_id=21});
$sel->title_is("Bug List: My bugs from QA_Selenium");
$sel->is_text_present_ok("2 bugs found");
$sel->click_ok("link=Change Several Bugs at Once");
-$sel->wait_for_page_to_load_ok(WAIT_TIME);
+check_page_load($sel, WAIT_TIME, q{http://HOSTNAME:8000/bmo/buglist.cgi?email1=admin%40mozilla.test&email2=QA-Selenium-TEST%40mozilla.test&emailassigned_to1=1&emailassigned_to2=1&emailcc2=1&emailqa_contact2=1&emailreporter2=1&emailtype1=exact&emailtype2=exact&product=TestProduct&query_format=advanced&order=priority%2Cbug_severity&tweak=1&list_id=22});
$sel->title_is("Bug List");
$sel->click_ok("check_all");
$sel->type_ok("comment", "Reassigning to the reporter");
$sel->type_ok("assigned_to", $config->{QA_Selenium_TEST_user_login});
$sel->click_ok("commit");
-$sel->wait_for_page_to_load_ok(WAIT_TIME);
+check_page_load($sel, WAIT_TIME, q{http://HOSTNAME:8000/bmo/process_bug.cgi});
$sel->title_is("Bugs processed");
# Now delete the saved search.
$sel->click_ok("link=My bugs from QA_Selenium");
-$sel->wait_for_page_to_load_ok(WAIT_TIME);
+check_page_load($sel, WAIT_TIME, q{http://HOSTNAME:8000/bmo/buglist.cgi?cmdtype=runnamed&namedcmd=My%20bugs%20from%20QA_Selenium&list_id=23});
$sel->title_is("Bug List: My bugs from QA_Selenium");
$sel->click_ok("link=Forget Search 'My bugs from QA_Selenium'");
-$sel->wait_for_page_to_load_ok(WAIT_TIME);
+check_page_load($sel, WAIT_TIME, q{http://HOSTNAME:8000/bmo/buglist.cgi?cmdtype=dorem&remaction=forget&namedcmd=My%20bugs%20from%20QA_Selenium&token=1531926582-f228fa8ebc2f2b3970f2a791e54534ec&list_id=24});
$sel->title_is("Search is gone");
$sel->is_text_present_ok("OK, the My bugs from QA_Selenium search is gone");
@@ -495,10 +502,10 @@ sub clear_canedit_on_testproduct {
edit_product($sel, "TestProduct");
$sel->click_ok("link=Edit Group Access Controls:");
- $sel->wait_for_page_to_load_ok(WAIT_TIME);
+check_page_load($sel, WAIT_TIME, q{http://HOSTNAME:8000/bmo/editproducts.cgi?action=editgroupcontrols&product=TestProduct});
$sel->title_is("Edit Group Controls for TestProduct");
$sel->uncheck_ok("canedit_$master_gid");
$sel->click_ok("submit");
- $sel->wait_for_page_to_load_ok(WAIT_TIME);
+check_page_load($sel, WAIT_TIME, q{http://HOSTNAME:8000/bmo/editproducts.cgi});
$sel->title_is("Update group access controls for TestProduct");
}
diff --git a/qa/t/test_shutdown.t b/qa/t/test_shutdown.t
deleted file mode 100644
index dc5cabd4a..000000000
--- a/qa/t/test_shutdown.t
+++ /dev/null
@@ -1,72 +0,0 @@
-# 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.
-
-use strict;
-use warnings;
-use lib qw(lib ../../lib ../../local/lib/perl5);
-
-use Test::More "no_plan";
-
-use QA::Util;
-
-my ($sel, $config) = get_selenium();
-
-log_in($sel, $config, 'admin');
-set_parameters($sel, { "General" => {shutdownhtml => {type => "text",
- value => "I'm down (set by test_shutdown.t)" }
- } });
-
-# None of the following pages should be accessible when Bugzilla is down.
-
-my @pages = qw(admin attachment buglist chart colchange config createaccount
- describecomponents describekeywords duplicates
- editclassifications editcomponents editfields editflagtypes
- editgroups editkeywords editmilestones editproducts editsettings
- editusers editvalues editversions editwhines editworkflow
- enter_bug index long_list page post_bug process_bug query quips
- relogin report reports request sanitycheck search_plugin
- show_activity show_bug showattachment showdependencygraph
- showdependencytree sidebar summarize_time token userprefs votes
- xml xmlrpc);
-
-foreach my $page (@pages) {
- $sel->open_ok("/$config->{bugzilla_installation}/${page}.cgi");
- $sel->title_is("Bugzilla is Down");
-}
-
-# Those have parameters passed to the page, so we put them here separately.
-
-@pages = ("query.cgi?format=report-table", "query.cgi?format=report-graph",
- "votes.cgi?action=show_user", "votes.cgi?action=show_bug");
-
-foreach my $page (@pages) {
- $sel->open_ok("/$config->{bugzilla_installation}/$page");
- $sel->title_is("Bugzilla is Down");
-}
-
-# Clear 'shutdownhtml', to re-enable Bugzilla.
-# At this point, the admin has been logged out. We cannot use log_in(),
-# nor set_parameters(), due to shutdownhtml being active.
-
-$sel->open_ok("/$config->{bugzilla_installation}/editparams.cgi");
-$sel->title_is("Log in to Bugzilla");
-$sel->type_ok("Bugzilla_login", $config->{admin_user_login}, "Enter admin login name");
-$sel->type_ok("Bugzilla_password", $config->{admin_user_passwd}, "Enter admin password");
-$sel->click_ok("log_in");
-$sel->wait_for_page_to_load_ok(WAIT_TIME);
-$sel->title_is("Configuration: General");
-$sel->type_ok("shutdownhtml", "");
-$sel->click_ok('//input[@type="submit" and @value="Save Changes"]', undef, "Save Changes");
-$sel->wait_for_page_to_load_ok(WAIT_TIME);
-$sel->title_is("Parameters Updated");
-
-# Accessing index.cgi should work again now.
-
-$sel->click_ok('//*[@id="header-title"]//a');
-$sel->wait_for_page_to_load_ok(WAIT_TIME);
-$sel->title_is("Bugzilla Main Page");
-logout($sel);
diff --git a/scripts/entrypoint.pl b/scripts/entrypoint.pl
index c48582606..f5c52f6a6 100755
--- a/scripts/entrypoint.pl
+++ b/scripts/entrypoint.pl
@@ -78,7 +78,6 @@ sub cmd_demo {
sub cmd_httpd {
check_data_dir();
wait_for_db();
- check_httpd_env();
my $httpd_exit_f = run_cereal_and_httpd();
assert_httpd()->get();
@@ -208,8 +207,6 @@ sub cmd_test_bmo {
sub run_prove {
my (%param) = @_;
- check_httpd_env();
-
my $prove_cmd = $param{prove_cmd};
my $prove_dir = $param{prove_dir};
assert_httpd()->then(sub {
@@ -273,16 +270,6 @@ sub check_env {
die 'Missing required environmental variables: ', join(', ', @missing_env), "\n";
}
}
-sub check_httpd_env {
- check_env(qw(
- HTTPD_StartServers
- HTTPD_MinSpareServers
- HTTPD_MaxSpareServers
- HTTPD_ServerLimit
- HTTPD_MaxClients
- HTTPD_MaxRequestsPerChild
- ))
-}
sub fix_path {
$ENV{PATH} = "/app/local/bin:$ENV{PATH}";
diff --git a/scripts/generate_conduit_data.pl b/scripts/generate_conduit_data.pl
index 541afb52a..627f3a199 100755
--- a/scripts/generate_conduit_data.pl
+++ b/scripts/generate_conduit_data.pl
@@ -27,19 +27,19 @@ my $admin_email = shift || 'admin@mozilla.bugs';
Bugzilla->set_user( Bugzilla::User->check( { name => $admin_email } ) );
##########################################################################
-# Create Conduit Test User
+# Create Conduit Test Users
##########################################################################
my $conduit_login = $ENV{CONDUIT_USER_LOGIN} || 'conduit@mozilla.bugs';
my $conduit_password = $ENV{CONDUIT_USER_PASSWORD} || 'password123456789!';
my $conduit_api_key = $ENV{CONDUIT_USER_API_KEY} || '';
-print "creating conduit user account...\n";
+print "creating conduit developer user account...\n";
if ( !Bugzilla::User->new( { name => $conduit_login } ) ) {
my $new_user = Bugzilla::User->create(
{
login_name => $conduit_login,
- realname => 'Conduit Test User',
+ realname => 'Conduit Developer',
cryptpassword => $conduit_password
},
);
@@ -48,12 +48,38 @@ if ( !Bugzilla::User->new( { name => $conduit_login } ) ) {
Bugzilla::User::APIKey->create_special(
{
user_id => $new_user->id,
- description => 'API key for Conduit User',
+ description => 'API key for Conduit Developer',
api_key => $conduit_api_key
}
);
}
}
+
+my $conduit_reviewer_login = $ENV{CONDUIT_REVIEWER_USER_LOGIN} || 'conduit-reviewer@mozilla.bugs';
+my $conduit_reviewer_password = $ENV{CONDUIT_REVIEWER_USER_PASSWORD} || 'password123456789!';
+my $conduit_reviewer_api_key = $ENV{CONDUIT_REVIEWER_USER_API_KEY} || '';
+
+print "creating conduit reviewer user account...\n";
+if ( !Bugzilla::User->new( { name => $conduit_reviewer_login } ) ) {
+ my $new_user = Bugzilla::User->create(
+ {
+ login_name => $conduit_reviewer_login,
+ realname => 'Conduit Reviewer',
+ cryptpassword => $conduit_reviewer_password
+ },
+ );
+
+ if ($conduit_reviewer_api_key) {
+ Bugzilla::User::APIKey->create_special(
+ {
+ user_id => $new_user->id,
+ description => 'API key for Conduit Reviewer',
+ api_key => $conduit_reviewer_api_key
+ }
+ );
+ }
+}
+
##########################################################################
# Create Phabricator Automation Bot
##########################################################################
@@ -88,6 +114,7 @@ if ( !Bugzilla::User->new( { name => $phab_login } ) ) {
my @users_groups = (
{ user => 'conduit@mozilla.bugs', group => 'editbugs' },
{ user => 'conduit@mozilla.bugs', group => 'core-security' },
+ { user => 'conduit-reviewer@mozilla.bugs', group => 'editbugs' },
{ user => 'phab-bot@bmo.tld', group => 'editbugs' },
{ user => 'phab-bot@bmo.tld', group => 'core-security' },
);
diff --git a/scripts/remove-non-public-data.pl b/scripts/remove-non-public-data.pl
index ce7948dd0..47c123464 100755
--- a/scripts/remove-non-public-data.pl
+++ b/scripts/remove-non-public-data.pl
@@ -155,7 +155,7 @@ foreach my $view (sort @{ $dbh->selectcol_arrayref("SHOW FULL TABLES IN $db_name
# drop tables/columns
-my @tables = map { lc } sort @{ $dbh->selectcol_arrayref("SHOW TABLES") };
+my @tables = sort @{ $dbh->selectcol_arrayref("SHOW TABLES") };
foreach my $table (@tables) {
if (exists $whitelist{$table}) {
my @drop_columns;
diff --git a/skins/standard/describecomponents.css b/skins/standard/describecomponents.css
new file mode 100644
index 000000000..cf5c1a98d
--- /dev/null
+++ b/skins/standard/describecomponents.css
@@ -0,0 +1,97 @@
+/* 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. */
+
+.product {
+ margin: 40px auto;
+ max-width: 960px;
+ font-size: 14px;
+ line-height: 1.5;
+}
+
+.product > header,
+.product .instructions {
+ margin: 0 auto;
+ max-width: 800px;
+ text-align: center;
+}
+
+.product h1 {
+ margin: 0;
+ font-size: 48px;
+ font-weight: normal;
+}
+
+.product > header p {
+ font-size: 16px;
+}
+
+.product .instructions p {
+ font-size: 14px;
+ font-style: italic;
+}
+
+.component {
+ display: flex;
+ align-items: center;
+ margin: 8px 0;
+ border: 1px solid #CCC;
+ border-radius: 4px;
+ padding: 16px;
+ background-color: #FFF;
+ box-shadow: 0 0 4px #CCC;
+}
+
+.component.highlight {
+ margin: 0;
+ padding: 1em 0;
+ background-color: lightgreen;
+}
+
+.component header {
+ flex: none;
+ margin-right: 16px;
+ width: 240px;
+}
+
+.component h2 {
+ margin: 0;
+ font-size: 24px;
+ font-weight: normal;
+}
+
+.component div {
+ flex: auto;
+}
+
+.component p {
+ margin: 0;
+ font-size: 16px;
+}
+
+.component ul {
+ display: flex;
+ margin: 8px 0 0;
+ border-top: 1px solid #DDD;
+ padding: 8px 0 0;
+ list-style: none;
+ font-size: 14px;
+ color: #999;
+}
+
+.component li {
+ margin: 0 16px 0 0;
+ padding: 0;
+}
+
+.component footer {
+ flex: none;
+ margin-left: 16px;
+}
+
+.component footer:empty {
+ display: none;
+}
diff --git a/skins/standard/global.css b/skins/standard/global.css
index 48d79366a..d004f3fbe 100644
--- a/skins/standard/global.css
+++ b/skins/standard/global.css
@@ -162,15 +162,13 @@
#header .links a:hover,
#header .links a:focus,
#header-tools-menu-button:hover,
- #header-tools-menu-button:focus,
- #header .dropdown-content a.active {
+ #header-tools-menu-button:focus {
background-color: rgba(0, 0, 0, .05) !important;
}
#header .title a:active,
#header .links a:active,
- #header-tools-menu-button:active,
- #header .dropdown-content a:active {
+ #header-tools-menu-button:active {
background-color: rgba(0, 0, 0, .1) !important;
}
@@ -266,168 +264,6 @@
transition: none;
}
- #header .dropdown-content {
- top: calc(100% + 4px);
- border-color: #BBB #999 #777;
- border-radius: 4px;
- padding: 4px 0;
- min-width: 160px;
- max-width: 240px;
- background-color: #FCFCFC;
- box-shadow: 0 2px 8px rgba(0,0,0,.3);
- }
-
- #header .dropdown-content.right {
- left: -4px;
- }
-
- #header .dropdown-content.left {
- right: -4px;
- }
-
- #header .dropdown-content::before,
- #header .dropdown-content::after {
- content: '';
- display: block;
- width: 0;
- height: 0;
- position: absolute;
- border-width: 8px;
- border-color: transparent;
- border-style: solid;
- }
-
- #header .dropdown-content.right::before,
- #header .dropdown-content.right::after {
- left: 11px;
- }
-
- #header .dropdown-content.left::before,
- #header .dropdown-content.left::after {
- right: 11px;
- }
-
- #header .dropdown-content::before {
- top: -17px;
- border-bottom-color: #BBB;
- }
-
- #header .dropdown-content::after {
- top: -16px;
- border-bottom-color: #FFF;
- }
-
- #header .dropdown-content a,
- #header .dropdown-content li > div {
- padding: 2px 16px;
- line-height: 1.5;
- white-space: normal;
- color: inherit !important;
- background-color: transparent;
- }
-
- #header .dropdown-panel {
- padding: 0 !important;
- width: 400px;
- max-width: none !important;
- }
-
- #header .dropdown-panel header {
- border-bottom: 1px solid #CCC;
- }
-
- #header .dropdown-panel h2 {
- margin: 0;
- padding: 8px 12px;
- font-size: 14px;
- line-height: 100%;
- font-weight: normal;
- }
-
- #header .dropdown-panel ul {
- overflow-y: auto;
- margin: 0;
- padding: 0;
- max-height: 480px;
- list-style-type: none;
- }
-
- #header .dropdown-panel li:not(:last-child) {
- border-bottom: 1px solid #CCC;
- }
-
- #header .dropdown-panel li a {
- padding: 12px !important;
- }
-
- #header .dropdown-panel li a:hover {
- background-color: rgba(0, 0, 0, .05) !important;
- }
-
- #header .dropdown-panel li a * {
- pointer-events: none;
- }
-
- #header .dropdown-panel .notifications a {
- overflow: hidden;
- }
-
- #header .dropdown-panel .notifications img {
- float: left;
- border-radius: 50%;
- width: 40px;
- height: 40px;
- }
-
- #header .dropdown-panel .notifications img ~ * {
- display: block;
- margin-left: 52px;
- }
-
- #header .dropdown-panel .notifications label {
- overflow: hidden;
- max-height: 40px;
- }
-
- #header .dropdown-panel .notifications strong {
- font-weight: 600;
- }
-
- #header .dropdown-panel .notifications time {
- font-size: 12px;
- color: #999;
- }
-
- #header .dropdown-panel .notifications .secure .icon {
- display: inline;
- font-size: 16px;
- vertical-align: text-bottom;
- }
-
- #header .dropdown-panel .notifications .secure .icon::before {
- content: '\E88D';
- }
-
- #header .dropdown-panel .loading,
- #header .dropdown-panel .empty {
- display: flex;
- align-items: center;
- justify-content: center;
- height: 240px;
- line-height: 150%;
- text-align: center;
- }
-
- #header .dropdown-panel footer {
- border-top: 1px solid #CCC;
- text-align: center;
- }
-
- #header .dropdown-panel footer a {
- padding: 8px 16px !important;
- line-height: 100% !important;
- }
-
#header-search h2 {
position: absolute;
left: -99999px;
@@ -533,12 +369,6 @@
color: #666;
}
- #header .dropdown-separator {
- height: 0;
- margin: 4px 0;
- border-color: #BBB;
- }
-
#header-login .mini-popup {
position: absolute;
top: 48px;
@@ -1715,7 +1545,31 @@ button[disabled], input[type=submit][disabled], input[type=button][disabled], bu
background-image: -webkit-linear-gradient(#bfc7cd,#9ca3aa);
background-image: linear-gradient(#bfc7cd,#9ca3aa);
box-shadow: 0 1px 0 0 rgba(0,0,0,0.2),inset 0 -1px 0 0 rgba(0,0,0,0.3);
- cursor: pointer;
+ pointer-events: none;
+}
+
+button.minor {
+ background-color: #eee;
+ background-image: linear-gradient(#fcfcfc, #eee);
+ color: #000;
+ font-size: inherit;
+ font-weight: 500;
+ padding: 4px 8px;
+ margin-bottom: 1px;
+ text-shadow: none;
+ -web-kit-box-shadow: 0 1px 0 0 rgba(0,0,0,0.1), inset 0 -1px 0 0 rgba(0,0,0,0.1);
+ -moz-box-shadow: 0 1px 0 0 rgba(0,0,0,0.1), inset 0 -1px 0 0 rgba(0,0,0,0.1);
+ box-shadow: 0 1px 0 0 rgba(0,0,0,0.1), inset 0 -1px 0 0 rgba(0,0,0,0.1), inset 0 0 1px 0 rgba(0,0,0,0.1);
+}
+
+button.minor:hover {
+ -webkit-box-shadow: 0 1px 0 0 rgba(0,0,0,0.2), inset 0 -1px 0 0 rgba(0,0,0,0.3), inset 0 12px 24px 2px #ddd;
+ -moz-box-shadow: 0 1px 0 0 rgba(0,0,0,0.2), inset 0 -1px 0 0 rgba(0,0,0,0.3), inset 0 12px 24px 2px #ddd;
+ box-shadow: 0 1px 0 0 rgba(0,0,0,0.1), inset 0 -1px 0 0 rgba(0,0,0,0.1), inset 0 12px 24px 2px #ddd;
+}
+
+button.minor[disabled] {
+ color: #999;
}
.notransition {
@@ -1861,50 +1715,253 @@ a.controller {
/******************/
/* Dropdown Menus */
/******************/
-
/* The container <div> - needed to position the dropdown content */
.dropdown {
- position: relative;
- display: inline-block;
+ position: relative;
+ display: inline-block;
+}
+
+.dropdown-button {
+ cursor: pointer;
+}
+
+.dropdown-button * {
+ pointer-events: none;
}
/* Dropdown Content (Hidden by Default) */
.dropdown-content {
- position: absolute;
- background-color: #eee;
- min-width: 120px;
- z-index: 1;
- text-align: left;
- margin: 0;
- padding: 0;
- border: 1px solid #ddd;
- -webkit-box-shadow: 1px 1px 5px rgba(0, 0, 0, 0.1);
- -moz-box-shadow: 1px 1px 5px rgba(0, 0, 0, 0.1);
- box-shadow: 1px 1px 5px rgba(0, 0, 0, 0.1);
- list-style: none;
- right: 0px;
+ position: absolute;
+ top: calc(100% + 4px);
+ right: 0;
+ z-index: 1;
+ margin: 0;
+ border-width: 1px;
+ border-style: solid;
+ border-color: #BBB #999 #777;
+ border-radius: 4px;
+ padding: 4px 0;
+ min-width: 160px;
+ max-width: 400px;
+ background-color: #FCFCFC;
+ box-shadow: 0 2px 8px rgba(0, 0, 0, .3);
+ text-align: left;
}
.dropdown-content.menu-up {
- bottom: 100%;
+ top: auto;
+ bottom: calc(100% + 4px);
}
-.dropdown-separator {
- border-bottom: 1px solid #ddd;
+.dropdown-content.right {
+ left: -4px;
}
-/* Links inside the dropdown */
-.dropdown-content a {
- white-space: nowrap;
- background-color: #eee;
- color: black !important;
- padding: 4px 8px;
- text-decoration: none !important;
- display: block;
+.dropdown-content.left {
+ right: -4px;
}
-/* Change color of dropdown links on hover */
-.dropdown-content li .active {
- text-decoration: none;
- background-color: #39f;
+.dropdown-content::before,
+.dropdown-content::after {
+ content: '';
+ display: block;
+ width: 0;
+ height: 0;
+ position: absolute;
+ border-width: 8px;
+ border-color: transparent;
+ border-style: solid;
+}
+
+.dropdown-content.right::before,
+.dropdown-content.right::after {
+ left: 11px;
+}
+
+.dropdown-content.left::before,
+.dropdown-content.left::after {
+ right: 11px;
+}
+
+.dropdown-content:not(.menu-up)::before {
+ top: -17px;
+ border-bottom-color: #BBB;
+}
+
+.dropdown-content:not(.menu-up)::after {
+ top: -16px;
+ border-bottom-color: #FFF;
+}
+
+.dropdown-content.menu-up::before {
+ bottom: -17px;
+ border-top-color: #BBB;
+}
+
+.dropdown-content.menu-up::after {
+ bottom: -16px;
+ border-top-color: #FFF;
+}
+
+.dropdown-content ul,
+.dropdown-content li {
+ margin: 0;
+ padding: 0;
+ list-style: none;
+}
+
+.dropdown-content [role="menuitem"],
+.dropdown-content [role="option"],
+.dropdown-content li > div {
+ display: block;
+ box-sizing: border-box;
+ padding: 2px 16px;
+ width: 100%;
+ color: #555;
+ line-height: 1.5;
+ white-space: nowrap;
+ background: none transparent;
+}
+
+.dropdown-content [role="menuitem"],
+.dropdown-content [role="option"] {
+ outline: 0;
+ text-decoration: none;
+ cursor: pointer;
+}
+
+.dropdown-content [role="menuitem"]:hover,
+.dropdown-content [role="menuitem"]:focus,
+.dropdown-content [role="menuitem"]:active,
+.dropdown-content [role="menuitem"].active,
+.dropdown-content [role="option"]:hover,
+.dropdown-content [role="option"]:focus,
+.dropdown-content [role="option"]:active,
+.dropdown-content [role="option"].active {
+ color: #333;
+ background-color: rgba(0, 0, 0, .1) !important;
+}
+
+.dropdown-content button[role="menuitem"] {
+ -moz-appearance: none;
+ -webkit-appearance: none;
+ appearance: none;
+ outline: 0;
+ border: 0;
+ border-radius: 0;
+ box-shadow: none;
+ font-weight: normal;
+ text-align: left;
+}
+
+.dropdown-content button[role="menuitem"]::-moz-focus-inner {
+ border: 0;
+}
+
+.dropdown-content [role="separator"] {
+ height: 0;
+ margin: 4px 0 !important;
+ border-bottom: 1px solid #BBB;
+}
+
+.dropdown-panel {
+ padding: 0 !important;
+ width: 400px;
+ max-width: none !important;
+}
+
+.dropdown-panel header {
+ border-bottom: 1px solid #CCC;
+}
+
+.dropdown-panel h2 {
+ margin: 0;
+ padding: 8px 12px;
+ font-size: 14px;
+ line-height: 100%;
+ font-weight: normal;
+}
+
+.dropdown-panel ul {
+ overflow-y: auto;
+ margin: 0;
+ padding: 0;
+ max-height: 480px;
+ list-style-type: none;
+}
+
+.dropdown-panel li:not(:last-child) {
+ border-bottom: 1px solid #CCC;
+}
+
+.dropdown-panel li a {
+ padding: 12px !important;
+}
+
+.dropdown-panel li a:hover {
+ background-color: rgba(0, 0, 0, .05) !important;
+}
+
+.dropdown-panel li a * {
+ pointer-events: none;
+}
+
+.dropdown-panel .notifications a {
+ overflow: hidden;
+}
+
+.dropdown-panel .notifications img {
+ float: left;
+ border-radius: 50%;
+ width: 40px;
+ height: 40px;
+}
+
+.dropdown-panel .notifications img ~ * {
+ display: block;
+ margin-left: 52px;
+}
+
+.dropdown-panel .notifications label {
+ overflow: hidden;
+ max-height: 40px;
+}
+
+.dropdown-panel .notifications strong {
+ font-weight: 600;
+}
+
+.dropdown-panel .notifications time {
+ font-size: 12px;
+ color: #999;
+}
+
+.dropdown-panel .notifications .secure .icon {
+ display: inline;
+ font-size: 16px;
+ vertical-align: text-bottom;
+}
+
+.dropdown-panel .notifications .secure .icon::before {
+ content: '\E88D';
+}
+
+.dropdown-panel .loading,
+.dropdown-panel .empty {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ height: 240px;
+ line-height: 150%;
+ text-align: center;
+}
+
+.dropdown-panel footer {
+ border-top: 1px solid #CCC;
+ text-align: center;
+}
+
+.dropdown-panel footer a {
+ padding: 8px 16px !important;
+ line-height: 100% !important;
}
diff --git a/skins/standard/reports.css b/skins/standard/reports.css
deleted file mode 100644
index 205946550..000000000
--- a/skins/standard/reports.css
+++ /dev/null
@@ -1,97 +0,0 @@
-/* The contents of this file are subject to the Mozilla Public
- * License Version 1.1 (the "License"); you may not use this file
- * except in compliance with the License. You may obtain a copy of
- * the License at http://www.mozilla.org/MPL/
- *
- * Software distributed under the License is distributed on an "AS
- * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
- * implied. See the License for the specific language governing
- * rights and limitations under the License.
- *
- * The Original Code is the Bugzilla Bug Tracking System.
- *
- * The Initial Developer of the Original Code is Everything Solved,
- * Inc. Portions created by the Initial Developer are Copyright (C)
- * 2009 the Initial Developer. All Rights Reserved.
- *
- * Contributor(s):
- * Max Kanat-Alexander <mkanat@bugzilla.org>
- */
-
-/* describecomponents.cgi */
-
-#components_header_table {
- margin-bottom: 1em;
-}
-
-.product_container {
- width: 65%;
-}
-
-.product_name {
- font-weight: bold;
- font-size: 150%;
- margin: 0;
-}
-
-.product_desc {
- /* This is padding instead of margin because it looks better
- * with the scrollbar. */
- padding: 0 2em;
- font-style: italic;
- max-height: 5em;
- overflow: auto;
-}
-
-.instructions {
- font-weight: bold;
- font-size: 105%;
- padding-right: 1em;
-}
-
-.components_header {
- margin: 0;
- font-size: 140%;
- font-weight: bold;
-}
-
-.component_table {
- margin-top: -1em;
- margin-left: 2em;
-}
-
-.component_table thead th {
- padding-right: 1em;
- vertical-align: bottom;
- text-align: left;
-}
-
-.component_table td {
- border-bottom: 1px dotted gray;
-}
-
-.component_table td.component_assignee,
-.component_table td.component_qa_contact
-{
- border: none;
- padding-top: .5em;
-}
-
-.component_name {
- font-size: 115%;
- font-weight: bold;
- padding-right: 1em;
- vertical-align: middle;
- min-width: 8em;
-}
-
-.component_description {
- padding-bottom: .5em;
- color: #333;
-}
-
-.component_hilite {
- background-color: lightgreen;
- margin: 0;
- padding: 1em 0;
-}
diff --git a/t/phabbugz.t b/t/phabbugz.t
new file mode 100644
index 000000000..ba2f35e1d
--- /dev/null
+++ b/t/phabbugz.t
@@ -0,0 +1,243 @@
+#!/usr/bin/perl
+# 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.
+use strict;
+use warnings;
+use 5.10.1;
+use lib qw( . lib local/lib/perl5 );
+use Bugzilla;
+
+BEGIN { Bugzilla->extensions };
+
+use Test::More;
+use Test2::Tools::Mock;
+use Data::Dumper;
+use JSON::MaybeXS;
+use Carp;
+use Try::Tiny;
+
+use ok 'Bugzilla::Extension::PhabBugz::Feed';
+use ok 'Bugzilla::Extension::PhabBugz::Util', qw( get_attachment_revisions );
+can_ok('Bugzilla::Extension::PhabBugz::Feed', 'group_query');
+
+our @group_members;
+our @project_members;
+
+
+my $User = mock 'Bugzilla::Extension::PhabBugz::User' => (
+ add_constructor => [
+ 'fake_new' => 'hash',
+ ],
+ override => [
+ 'match' => sub { [ mock() ] },
+ ],
+);
+
+my $Feed = mock 'Bugzilla::Extension::PhabBugz::Feed' => (
+ override => [
+ get_group_members => sub {
+ return [ map { Bugzilla::Extension::PhabBugz::User->fake_new(%$_) } @group_members ];
+ }
+ ]
+);
+
+my $Project = mock 'Bugzilla::Extension::PhabBugz::Project' => (
+ override_constructor => [
+ new_from_query => 'ref_copy',
+ ],
+ override => [
+ 'members' => sub {
+ return [ map { Bugzilla::Extension::PhabBugz::User->fake_new(%$_) } @project_members ];
+ }
+ ]
+);
+
+local Bugzilla->params->{phabricator_enabled} = 1;
+local Bugzilla->params->{phabricator_api_key} = 'FAKE-API-KEY';
+local Bugzilla->params->{phabricator_base_uri} = 'http://fake.fabricator.tld';
+
+my $Bugzilla = mock 'Bugzilla' => (
+ override => [
+ 'dbh' => sub { mock() },
+ 'user' => sub { Bugzilla::User->new({ name => 'phab-bot@bmo.tld' }) },
+ ],
+);
+
+my $BugzillaGroup = mock 'Bugzilla::Group' => (
+ add_constructor => [
+ 'fake_new' => 'hash',
+ ],
+ override => [
+ 'match' => sub { [ Bugzilla::Group->fake_new(id => 1, name => 'firefox-security' ) ] },
+ ],
+);
+
+my $BugzillaUser = mock 'Bugzilla::User' => (
+ add_constructor => [
+ 'fake_new' => 'hash',
+ ],
+ override => [
+ 'new' => sub {
+ my ($class, $hash) = @_;
+ if ($hash->{name} eq 'phab-bot@bmo.tld') {
+ return $class->fake_new( id => 8_675_309, login_name => 'phab-bot@bmo.tld', realname => 'Fake PhabBot' );
+ }
+ else {
+ }
+ },
+ 'match' => sub { [ mock() ] },
+ ],
+);
+
+
+my $feed = Bugzilla::Extension::PhabBugz::Feed->new;
+
+# Same members in both
+do {
+ my $UserAgent = mock 'LWP::UserAgent' => (
+ override => [
+ 'post' => sub {
+ my ($self, $url, $params) = @_;
+ my $data = decode_json($params->{params});
+ is_deeply($data->{transactions}, [], 'no-op');
+ return mock({is_error => 0, content => '{}'});
+ },
+ ],
+ );
+ local @group_members = (
+ { phid => 'foo' },
+ );
+ local @project_members = (
+ { phid => 'foo' },
+ );
+ $feed->group_query;
+};
+
+# Project has members not in group
+do {
+ my $UserAgent = mock 'LWP::UserAgent' => (
+ override => [
+ 'post' => sub {
+ my ($self, $url, $params) = @_;
+ my $data = decode_json($params->{params});
+ my $expected = [ { type => 'members.remove', value => ['foo'] } ];
+ is_deeply($data->{transactions}, $expected, 'remove foo');
+ return mock({is_error => 0, content => '{}'});
+ },
+ ]
+ );
+ local @group_members = ();
+ local @project_members = (
+ { phid => 'foo' },
+ );
+ $feed->group_query;
+};
+
+# Group has members not in project
+do {
+ my $UserAgent = mock 'LWP::UserAgent' => (
+ override => [
+ 'post' => sub {
+ my ($self, $url, $params) = @_;
+ my $data = decode_json($params->{params});
+ my $expected = [ { type => 'members.add', value => ['foo'] } ];
+ is_deeply($data->{transactions}, $expected, 'add foo');
+ return mock({is_error => 0, content => '{}'});
+ },
+ ]
+ );
+ local @group_members = (
+ { phid => 'foo' },
+ );
+ local @project_members = (
+ );
+ $feed->group_query;
+};
+
+do {
+ my $Revision = mock 'Bugzilla::Extension::PhabBugz::Revision' => (
+ override => [
+ 'update' => sub { 1 },
+ ],
+ );
+ my $UserAgent = mock 'LWP::UserAgent' => (
+ override => [
+ 'post' => sub {
+ my ($self, $url, $params) = @_;
+ if ($url =~ /differential\.revision\.search/) {
+ my $content = <<JSON;
+{
+ "error_info": null,
+ "error_code": null,
+ "result": {
+ "data": [
+ {
+ "id": 9999,
+ "type": "DREV",
+ "phid": "PHID-DREV-uozm3ggfp7e7uoqegmc3",
+ "fields": {
+ "title": "Added .arcconfig",
+ "summary": "Added .arcconfig",
+ "authorPHID": "PHID-USER-4wigy3sh5fc5t74vapwm",
+ "dateCreated": 1507666113,
+ "dateModified": 1508514027,
+ "policy": {
+ "view": "public",
+ "edit": "admin"
+ },
+ "bugzilla.bug-id": "23",
+ "status": {
+ "value": "needs-review",
+ "name": "Needs Review",
+ "closed": false,
+ "color.ansi": "magenta"
+ }
+ },
+ "attachments": {
+ "reviewers": {
+ "reviewers": []
+ },
+ "subscribers": {
+ "subscriberPHIDs": [],
+ "subscriberCount": 0,
+ "viewerIsSubscribed": true
+ },
+ "projects": {
+ "projectPHIDs": []
+ }
+ }
+ }
+ ]
+ }
+}
+JSON
+ return mock { is_error => 0, content => $content };
+ }
+ else {
+ return mock { is_error => 1, message => "bad request" };
+ }
+ },
+ ],
+ );
+ my $bug = mock {
+ bug_id => 23,
+ attachments => [
+ mock {
+ contenttype => 'text/x-phabricator-request',
+ filename => 'phabricator-D9999-url.txt',
+ },
+ ]
+ };
+ my $revisions = get_attachment_revisions($bug);
+ is(ref($revisions), 'ARRAY', 'it is an array ref');
+ isa_ok($revisions->[0], 'Bugzilla::Extension::PhabBugz::Revision');
+ is($revisions->[0]->bug_id, 23, 'Bugzila ID is 23');
+ ok( try { $revisions->[0]->update }, 'update revision');
+
+};
+
+done_testing; \ No newline at end of file
diff --git a/template/en/default/admin/params/editparams.html.tmpl b/template/en/default/admin/params/editparams.html.tmpl
index 838bff7ef..6e8bc2257 100644
--- a/template/en/default/admin/params/editparams.html.tmpl
+++ b/template/en/default/admin/params/editparams.html.tmpl
@@ -21,7 +21,6 @@
[%# INTERFACE:
# panels: array of hashes representing the panels available.
# param_changed: array of parameters which have been changed.
- # shutdown_is_active: boolean; is true when 'shutdownhtml' has been turned on.
#%]
[% PROCESS global/variables.none.tmpl %]
diff --git a/template/en/default/admin/params/general.html.tmpl b/template/en/default/admin/params/general.html.tmpl
index f1b86e19f..05e8f09a2 100644
--- a/template/en/default/admin/params/general.html.tmpl
+++ b/template/en/default/admin/params/general.html.tmpl
@@ -48,11 +48,6 @@
_ " &quot;on&quot;, you must re-run <kbd>checksetup.pl</kbd> immediately"
_ " afterward.</p>",
- shutdownhtml =>
- "If this field is non-empty, then $terms.Bugzilla will be completely"
- _ " disabled and this text will be displayed instead of all the"
- _ " $terms.Bugzilla pages.",
-
announcehtml =>
"If this field is non-empty, then $terms.Bugzilla will"
_ " display whatever is in this field at the top of every"
diff --git a/template/en/default/global/field-descs.none.tmpl b/template/en/default/global/field-descs.none.tmpl
index 3e0a528dc..cb6240c29 100644
--- a/template/en/default/global/field-descs.none.tmpl
+++ b/template/en/default/global/field-descs.none.tmpl
@@ -162,13 +162,11 @@ if ( $stash->get("in_template_var") ) {
# database. If you want to override this for your language
# or your installation, just use a hook. %]
my $bug_fields = $stash->get("bug_fields");
- unless ( Bugzilla->params->{shutdownhtml} ) {
- foreach my $bz_field ( values %$bug_fields ) {
- $vars->{field_descs}{$bz_field->name} //= $bz_field->description;
- }
-
- $context->process("bug/field-help.none.tmpl");
+ foreach my $bz_field ( values %$bug_fields ) {
+ $vars->{field_descs}{$bz_field->name} //= $bz_field->description;
}
+
+ $context->process("bug/field-help.none.tmpl");
}
[% END %]
diff --git a/template/en/default/global/header.html.tmpl b/template/en/default/global/header.html.tmpl
index 17cac3cb8..efb8d4407 100644
--- a/template/en/default/global/header.html.tmpl
+++ b/template/en/default/global/header.html.tmpl
@@ -307,7 +307,7 @@
</li>
[% END %]
[% IF Param('docs_urlbase') %]
- <li role="separator" class="dropdown-separator"></li>
+ <li role="separator"></li>
<li role="presentation">
<a href="[% docs_urlbase FILTER html %]" role="menuitem" tabindex="-1">Documentation</a>
</li>
@@ -328,14 +328,14 @@
</button>
<ul class="dropdown-content left" id="header-account-menu" role="menu" style="display:none;">
<li role="presentation">
- <div href="user_profile" class="account-label">
+ <div class="account-label">
<div class="name">[% user.name FILTER html %]</div>
<div class="email">[% user.login FILTER html %]</div>
</div>
</li>
- <li role="separator" class="dropdown-separator"></li>
+ <li role="separator"></li>
<li role="presentation">
- <a href="user_profile" role="menuitem" tabindex="-1">My Profile</a>
+ <a href="user_profile?user_id=[% user.id FILTER none %]" role="menuitem" tabindex="-1">My Profile</a>
</li>
<li role="presentation">
<a href="page.cgi?id=user_activity.html&amp;action=run&amp;who=[% user.login FILTER uri %]" role="menuitem"
@@ -345,7 +345,7 @@
<a href="userprefs.cgi" role="menuitem" tabindex="-1">Preferences</a>
</li>
[% IF user.authorizer.can_logout %]
- <li role="separator" class="dropdown-separator"></li>
+ <li role="separator"></li>
<li role="presentation">
<a href="index.cgi?logout=1" role="menuitem" tabindex="-1">Log out</a>
</li>
diff --git a/template/en/default/global/messages.html.tmpl b/template/en/default/global/messages.html.tmpl
index b2430b7ed..278b0f161 100644
--- a/template/en/default/global/messages.html.tmpl
+++ b/template/en/default/global/messages.html.tmpl
@@ -56,6 +56,8 @@
The login is now [% otheruser.login FILTER html %].
[% ELSIF field == 'realname' %]
The real name has been updated.
+ [% ELSIF field == 'nickname' %]
+ The nickname is now [% otheruser.nick FILTER html %].
[% ELSIF field == 'cryptpassword' %]
A new password has been set.
[% ELSIF field == 'disabledtext' %]
@@ -138,7 +140,7 @@
[% ELSIF message_tag == "buglist_updated_named_query" %]
[% title = "Search updated" %]
- Your search named <code><a
+ Your search named <code><a
href="buglist.cgi?cmdtype=runnamed&amp;namedcmd=[% queryname FILTER uri %]"
>[% queryname FILTER html %]</a></code> has been updated.
@@ -296,22 +298,22 @@
[% ELSIF message_tag == "email_change_canceled_reinstated" %]
[% title = "Cancel Request to Change Email Address" %]
The request to change the email address for the
- account [%+ old_email FILTER html %] to
+ account [%+ old_email FILTER html %] to
[%+ new_email FILTER html %] has been canceled.
Your old account settings have been reinstated.
[% ELSIF message_tag == "extension_created" %]
An extension named [% name FILTER html %] has been created
- in [% path FILTER html %]. Make sure you change "YOUR NAME" and
+ in [% path FILTER html %]. Make sure you change "YOUR NAME" and
"YOUR EMAIL ADDRESS" in the code to your name and your email address.
[% ELSIF message_tag == "field_value_created" %]
[% title = "New Field Value Created" %]
- The value <em>[% value.name FILTER html %]</em> has been added as a
+ The value <em>[% value.name FILTER html %]</em> has been added as a
valid choice for the <em>[% field.description FILTER html %]</em>
(<em>[% field.name FILTER html %]</em>) field.
[% IF field.name == "bug_status" %]
- You should now visit the <a href="editworkflow.cgi">status workflow
+ You should now visit the <a href="editworkflow.cgi">status workflow
page</a> to include your new [% terms.bug %] status.
[% END %]
@@ -329,7 +331,7 @@
(<em>[% field.name FILTER html %]</em>) field has been changed:
<ul>
[% IF changes.value %]
- <li>Field value updated to
+ <li>Field value updated to
<em>[% changes.value.1 FILTER html %]</em>.
[% IF value.is_default %]
(Note that this value is the default for this field. All
@@ -338,16 +340,16 @@
</li>
[% END %]
[% IF changes.sortkey %]
- <li>Sortkey updated to
+ <li>Sortkey updated to
<em>[% changes.sortkey.1 FILTER html %]</em>.</li>
[% END %]
[% IF changes.visibility_value_id %]
[% IF value.visibility_value.defined %]
- <li>It only appears when
+ <li>It only appears when
[%+ value.field.value_field.description FILTER html %] is set to
'[%+ value.visibility_value.name FILTER html %]'.</li>
[% ELSE %]
- <li>It now always appears, no matter what
+ <li>It now always appears, no matter what
[%+ value.field.value_field.description FILTER html %] is set to.
</li>
[% END %]
@@ -533,7 +535,7 @@
Reading users...
[% ELSIF message_tag == "migrate_translating_bugs" %]
- Converting [% terms.bug %] values to be appropriate for
+ Converting [% terms.bug %] values to be appropriate for
[%+ terms.Bugzilla %]...
[% ELSIF message_tag == "migrate_user_created" %]
@@ -592,12 +594,6 @@
No changes made.
[% END %]
- [% IF shutdown_is_active == 1 %]
- <hr>
- [% terms.Bugzilla %] has now been shut down. To re-enable the system,
- clear the <em>shutdownhtml</em> field.
- [% END%]
-
[% ELSIF message_tag == "password_change_canceled" %]
[% title = "Cancel Request to Change Password" %]
Your request has been canceled.
@@ -729,7 +725,7 @@
[% ELSIF message_tag == "install_fk_invalid" %]
ERROR: There are invalid values for the [% column FILTER html %] column in the [% table FILTER html %]
- table. (These values do not exist in the [% foreign_table FILTER html %] table, in the
+ table. (These values do not exist in the [% foreign_table FILTER html %] table, in the
[%+ foreign_column FILTER html %] column.)
Before continuing with checksetup, you will need to fix these values,
@@ -787,8 +783,8 @@
to be editable by both you and the web server must be world writable, and
other files (including the localconfig file which stores your database
password) must be world readable. This means that _anyone_ who can obtain
- local access to this machine can do whatever they want to your
- [%+ terms.Bugzilla %] installation, and is probably also able to run
+ local access to this machine can do whatever they want to your
+ [%+ terms.Bugzilla %] installation, and is probably also able to run
arbitrary Perl code as the user that the web server runs as.
You really, really, really need to change this setting.
@@ -796,10 +792,10 @@
[% ELSIF message_tag == "install_webservergroup_not_in" %]
Warning: you have entered a value for the "webservergroup" parameter in
- localconfig, but you are not either a) running this script as [% constants.ROOT_USER FILTER html %];
- or b) a member of this group. This can cause permissions problems and
+ localconfig, but you are not either a) running this script as [% constants.ROOT_USER FILTER html %];
+ or b) a member of this group. This can cause permissions problems and
decreased security. If you experience problems running [% terms.Bugzilla %]
- scripts, log in as [% constants.ROOT_USER FILTER html %] and re-run this script, become a
+ scripts, log in as [% constants.ROOT_USER FILTER html %] and re-run this script, become a
member of the group, or remove the value of the "webservergroup" parameter.
[% ELSIF message_tag == "install_webservergroup_windows" %]
@@ -834,7 +830,7 @@
products you can choose from.
[% ELSIF message_tag == "remaining_time_zeroed" %]
- The [% field_descs.remaining_time FILTER html %] field has been
+ The [% field_descs.remaining_time FILTER html %] field has been
set to zero automatically as part of closing this [% terms.bug %]
or moving it from one closed state to another.
@@ -854,21 +850,21 @@
[% ELSIF message_tag == "sudo_started" %]
[% title = "Sudo session started" %]
- The sudo session has been started. For the next 6 hours, or until you
- end the session, everything you do you do as the user you are
+ The sudo session has been started. For the next 6 hours, or until you
+ end the session, everything you do you do as the user you are
impersonating ([% target FILTER html %]).
-
+
[% ELSIF message_tag == "sudo_ended" %]
[% title = "Sudo session complete" %]
- The sudo session has been ended. From this point forward, everything you
+ The sudo session has been ended. From this point forward, everything you
do you do as yourself.
[% ELSIF message_tag == "series_created" %]
[% title = "Series Created" %]
The series <em>[% series.category FILTER html %] /
- [%+ series.subcategory FILTER html %] /
+ [%+ series.subcategory FILTER html %] /
[%+ series.name FILTER html %]</em>
- has been created. Note that you may need to wait up to
+ has been created. Note that you may need to wait up to
[%+ series.frequency * 2 %] days before there will be enough data for a
chart of this series to be produced.
@@ -879,14 +875,6 @@
[%+ series.name FILTER html %]</em>
has been deleted.
- [% ELSIF message_tag == "shutdown" %]
- [% title = "$terms.Bugzilla is Down" %]
- [% Param("shutdownhtml") %]
- [% IF userid %]
- <p>For security reasons, you have been logged out automatically.
- The cookie that was remembering your login is now gone.
- [% END %]
-
[% ELSIF message_tag == "tag_updated" %]
[% title = "Tag Updated" %]
The '<a href="buglist.cgi?tag=[% tag FILTER uri %]">[% tag FILTER html %]</a>'
@@ -906,7 +894,7 @@
Some flags could not be set. Please check your changes.
[% ELSIF message_tag == "user_match_failed" %]
- You entered a username that did not match any known
+ You entered a username that did not match any known
[% terms.Bugzilla %] users, so we have instead left
the [% match_field FILTER html %] field blank.
diff --git a/template/en/default/reports/components.html.tmpl b/template/en/default/reports/components.html.tmpl
index b2a21ccc1..f8b0f3f80 100644
--- a/template/en/default/reports/components.html.tmpl
+++ b/template/en/default/reports/components.html.tmpl
@@ -17,6 +17,7 @@
#
# Contributor(s): Bradley Baetz <bbaetz@student.usyd.edu.au>
# Max Kanat-Alexander <mkanat@bugzilla.org>
+ # Kohei Yoshino <kohei.yoshino@gmail.com>
#%]
[%# INTERFACE:
@@ -29,53 +30,36 @@
Components for [% product.name FILTER html %]
[% END %]
-[% PROCESS global/header.html.tmpl
- style_urls = [ "skins/standard/reports.css" ]
- title = title
+[% DEFAULT
+ style_urls = [ "skins/standard/describecomponents.css" ]
+ javascript_urls = []
+ title = title
+ show_default_people = 1
%]
-[% IF Param("useqacontact") %]
- [% numcols = 3 %]
-[% ELSE %]
- [% numcols = 2 %]
-[% END %]
-
-<h2>[% mark FILTER html %]</h2>
-
-<table cellpadding="0" cellspacing="0" id="components_header_table">
- <tr>
- <td class="instructions">
- Select a component to see open [% terms.bugs %] in that component:
- </td>
- <td class="product_container">
- <span class="product_name">[% product.name FILTER html %]</span>
- <div class="product_desc">
- [% product.description FILTER html_light %]
- </div>
- </td>
- </tr>
-</table>
+[% Hook.process('start') %]
-<span class="components_header">Components</span>
+[% PROCESS global/header.html.tmpl
+ style_urls = style_urls
+ javascript_urls = javascript_urls
+ title = title
+%]
-<table summary="Components table"
- class="component_table" cellspacing="0" cellpadding="0">
- <thead>
- <tr>
- <th>&nbsp;</th>
- <th>Default Assignee</th>
- [% IF Param("useqacontact") %]
- <th>Default QA Contact</th>
+<section class="product">
+ <header>
+ <h1>[% product.name FILTER html %]</h1>
+ <p>[% product.description FILTER html_light %]</p>
+ [% Hook.process('product_header') %]
+ </header>
+ <div class="instructions">
+ <p>Select a component to see open [% terms.bugs %] in that component:</p>
+ </div>
+ <div class="list">
+ [% FOREACH comp = product.components %]
+ [% INCLUDE describe_comp %]
[% END %]
- </tr>
- </thead>
-
- <tbody>
- [% FOREACH comp = product.components %]
- [% INCLUDE describe_comp %]
- [% END %]
- </tbody>
-</table>
+ </div>
+</section>
[% PROCESS global/footer.html.tmpl %]
@@ -84,27 +68,24 @@
[%############################################################################%]
[% BLOCK describe_comp %]
- <tr id="[% comp.name FILTER html %]"
- [%- IF comp.name == component_mark %] class="component_hilite"[% END %]>
- <td rowspan="2" class="component_name">
- <a name="[% comp.name FILTER html %]"
- href="buglist.cgi?product=
- [%- product.name FILTER uri %]&amp;component=
- [%- comp.name FILTER uri %]&amp;resolution=---">
- [% comp.name FILTER html %]</a>
- </td>
- <td class="component_assignee">
- [% INCLUDE global/user.html.tmpl who = comp.default_assignee %]
- </td>
- [% IF Param("useqacontact") %]
- <td class="component_qa_contact">
- [% INCLUDE global/user.html.tmpl who = comp.default_qa_contact %]
- </td>
- [% END %]
- </tr>
- <tr[% IF comp.name == component_mark %] class="component_hilite"[% END %]>
- <td colspan="[% numcols - 1 %]" class="component_description">
- [% comp.description FILTER html_light %]
- </td>
- </tr>
+ <section id="[% comp.name FILTER html %]" class="component[%- IF comp.name == component_mark %] highlight[% END %]">
+ <header>
+ <h2><a href="buglist.cgi?product=[%- product.name FILTER uri %]&amp;component=
+ [%- comp.name FILTER uri %]&amp;resolution=---">[% comp.name FILTER html %]</a></h2>
+ </header>
+ <div>
+ <p class="description">[% comp.description FILTER html_light %]</p>
+ [% IF show_default_people %]
+ <ul>
+ <li>Assignee: [% INCLUDE global/user.html.tmpl who = comp.default_assignee %]</li>
+ [% IF Param("useqacontact") %]
+ <li>QA: [% INCLUDE global/user.html.tmpl who = comp.default_qa_contact %]</li>
+ [% END %]
+ </ul>
+ [% END %]
+ </div>
+ <footer>
+ [% Hook.process('component_footer', 'reports/components.html.tmpl') %]
+ </footer>
+ </section>
[% END %]
diff --git a/template/en/default/reports/menu.html.tmpl b/template/en/default/reports/menu.html.tmpl
index 5e19b1209..f5e9c4664 100644
--- a/template/en/default/reports/menu.html.tmpl
+++ b/template/en/default/reports/menu.html.tmpl
@@ -28,7 +28,6 @@
[% PROCESS global/header.html.tmpl
title = "Reporting and Charting Kitchen"
doc_section = "reporting.html"
- style_urls = ['skins/standard/reports.css']
%]
<p>
@@ -70,14 +69,14 @@
<ul>
[% IF feature_enabled('old_charts') %]
<li id="old_charts">
- <strong><a href="reports.cgi">Old Charts</a></strong> -
+ <strong><a href="reports.cgi">Old Charts</a></strong> -
plot the status and/or resolution of [% terms.bugs %] against
time, for each product in your database.
</li>
[% END %]
[% IF feature_enabled('new_charts') AND user.in_group(Param("chartgroup")) %]
<li id="new_charts">
- <strong><a href="chart.cgi">New Charts</a></strong> -
+ <strong><a href="chart.cgi">New Charts</a></strong> -
plot any arbitrary search against time. Far more powerful.
</li>
[% END %]
diff --git a/template/en/default/setup/strings.txt.pl b/template/en/default/setup/strings.txt.pl
index 8726a8b13..363a2d5fd 100644
--- a/template/en/default/setup/strings.txt.pl
+++ b/template/en/default/setup/strings.txt.pl
@@ -342,12 +342,19 @@ InnoDB is disabled in your MySQL installation.
Bugzilla requires InnoDB to be enabled.
Please enable it and then re-run checksetup.pl.
END
+ mysql_innodb_settings => <<'END',
+Bugzilla requires the following MySQL InnoDB settings:
+innodb_file_format = Barracuda
+innodb_file_per_table = 1
+innodb_large_prefix = 1
+END
mysql_index_renaming => <<'END',
We are about to rename old indexes. The estimated time to complete
renaming is ##minutes## minutes. You cannot interrupt this action once
it has begun. If you would like to cancel, press Ctrl-C now...
(Waiting 45 seconds...)
END
+ mysql_row_format_conversion => "Converting ##table## to row format ##format##.",
mysql_utf8_conversion => <<'END',
WARNING: We are about to convert your table storage format to UTF-8. This
allows Bugzilla to correctly store and sort international characters.
diff --git a/vagrant_support/hypnotoad b/vagrant_support/hypnotoad
index af6a600f3..5e8dc910e 100755
--- a/vagrant_support/hypnotoad
+++ b/vagrant_support/hypnotoad
@@ -90,6 +90,7 @@ stop ()
restart ()
{
+ stop
start
}
diff --git a/vagrant_support/my.cnf b/vagrant_support/my.cnf
index 1daa4d745..1f3d13546 100644
--- a/vagrant_support/my.cnf
+++ b/vagrant_support/my.cnf
@@ -25,14 +25,15 @@ thread_cache_size = 50
table_definition_cache = 1024
table_open_cache = 2048
-innodb_flush_method = O_DIRECT
-innodb_log_files_in_group = 2
-innodb_log_file_size = 256M
-innodb_flush_log_at_trx_commit = 2
-innodb_file_per_table = 1
innodb_buffer_pool_size = 1G
-innodb_flush_neighbors = 0
+innodb_file_format = Barracuda
+innodb_file_per_table = 1
innodb_flush_log_at_trx_commit = 2
+innodb_flush_method = O_DIRECT
+innodb_flush_neighbors = 0
+innodb_large_prefix = 1
+innodb_log_file_size = 256M
+innodb_log_files_in_group = 2
log_error = /var/lib/mysql/mysql-error.log
log_queries_not_using_indexes = 0
diff --git a/vagrant_support/playbook.yml b/vagrant_support/playbook.yml
index 82fbba4a2..3dd320f0b 100644
--- a/vagrant_support/playbook.yml
+++ b/vagrant_support/playbook.yml
@@ -170,9 +170,6 @@
dest: /usr/local/bin/cpanm
mode: '0755'
- - name: install more recent Apache2::SizeLimit
- cpanm: name=Apache2::SizeLimit executable=/usr/local/bin/cpanm
-
- name: 'check /opt/bmo (failure is ok)'
shell: test -d /opt/bmo && test -f /opt/bmo/local/lib/perl5/Plack.pm
register: opt_bmo