aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.htaccess16
-rw-r--r--Bugzilla/BugUrl.pm1
-rw-r--r--Bugzilla/BugUrl/Debian.pm7
-rw-r--r--Bugzilla/BugUrl/Flyspray.pm36
-rw-r--r--Bugzilla/CGI.pm3
-rw-r--r--Bugzilla/Constants.pm10
-rw-r--r--Bugzilla/Install/Filesystem.pm2
-rw-r--r--Bugzilla/Search.pm4
-rw-r--r--Bugzilla/Search/Quicksearch.pm28
-rw-r--r--Bugzilla/Template.pm24
-rw-r--r--Bugzilla/WebService/Product.pm1
-rwxr-xr-xattachment.cgi10
-rw-r--r--bots.html86
-rwxr-xr-xbuglist.cgi1
-rwxr-xr-xcustom_buglist.cgi90
-rwxr-xr-xcustom_disabled.cgi41
-rwxr-xr-xcustom_extraperms.cgi73
-rwxr-xr-xcustom_userhistory.cgi323
-rw-r--r--data/.gitignore8
-rw-r--r--docs/en/pdf/Bugzilla-Guide.pdfbin0 -> 518907 bytes
-rwxr-xr-xduplicates.cgi3
-rwxr-xr-xenter_bug.cgi2
-rw-r--r--extensions/Gentoo/Config.pm5
-rw-r--r--extensions/Gentoo/Extension.pm72
-rw-r--r--extensions/Gentoo/template/en/default/hook/bug/create/create-after_cc_field.html.tmpl32
-rw-r--r--extensions/Gentoo/template/en/default/hook/bug/edit-after_cc_field.html.tmpl29
-rw-r--r--extensions/Gentoo/template/en/default/hook/email/bugmail-start.txt.tmpl2
-rw-r--r--extensions/Gentoo/template/en/default/hook/global/header-additional_header.html.tmpl1
-rw-r--r--extensions/Gentoo/template/en/default/hook/global/header-after_body_start.html.tmpl18
-rw-r--r--extensions/Gentoo/template/en/default/hook/global/user-error-errors.html.tmpl5
-rw-r--r--extensions/Gentoo/template/en/default/hook/index-intro.html.tmpl4
-rw-r--r--extensions/Gentoo/template/en/default/hook/index-outro.html.tmpl3
-rw-r--r--extensions/Gentoo/template/en/default/hook/pages/resolution.html.tmpl51
-rw-r--r--extensions/Gentoo/web/gentoo-header-bar-bg.pngbin0 -> 210 bytes
-rw-r--r--extensions/Gentoo/web/gentoo.js50
-rw-r--r--extensions/Gentoo/web/gentoo_org.pngbin0 -> 47830 bytes
-rw-r--r--extensions/InlineHistory/Config.pm13
-rw-r--r--extensions/InlineHistory/Extension.pm218
-rw-r--r--extensions/InlineHistory/README10
-rw-r--r--extensions/InlineHistory/template/en/default/hook/bug/comments-aftercomments.html.tmpl160
-rw-r--r--extensions/InlineHistory/template/en/default/hook/bug/comments-comment_banner.html.tmpl13
-rw-r--r--extensions/InlineHistory/template/en/default/hook/bug/show-header-end.html.tmpl12
-rw-r--r--extensions/InlineHistory/template/en/default/hook/global/setting-descs-settings.none.tmpl11
-rw-r--r--extensions/InlineHistory/web/inline-history.js397
-rw-r--r--extensions/InlineHistory/web/style.css35
-rw-r--r--extensions/MoreBugUrl/Extension.pm1
-rw-r--r--extensions/MoreBugUrl/lib/Phabricator.pm41
-rw-r--r--extensions/MoreBugUrl/lib/Savane.pm11
-rw-r--r--extensions/MoreBugUrl/template/en/default/hook/global/user-error-bug_url_invalid_tracker.html.tmpl1
-rw-r--r--extensions/SecureMail/Config.pm49
-rw-r--r--extensions/SecureMail/Extension.pm659
-rw-r--r--extensions/SecureMail/README8
-rw-r--r--extensions/SecureMail/template/en/default/account/email/encryption-required.txt.tmpl15
-rw-r--r--extensions/SecureMail/template/en/default/account/email/securemail-test.txt.tmpl23
-rw-r--r--extensions/SecureMail/template/en/default/account/prefs/securemail.html.tmpl40
-rw-r--r--extensions/SecureMail/template/en/default/hook/account/prefs/prefs-tabs.html.tmpl28
-rw-r--r--extensions/SecureMail/template/en/default/hook/account/prefs/securemail-moreinfo.html.tmpl8
-rw-r--r--extensions/SecureMail/template/en/default/hook/admin/groups/create-field.html.tmpl25
-rw-r--r--extensions/SecureMail/template/en/default/hook/admin/groups/edit-field.html.tmpl27
-rw-r--r--extensions/SecureMail/template/en/default/hook/global/user-error-errors.html.tmpl27
-rw-r--r--extensions/SecureMail/template/en/default/pages/securemail/help.html.tmpl130
-rw-r--r--extensions/TypeSniffer/Config.pm40
-rw-r--r--extensions/TypeSniffer/Extension.pm75
-rw-r--r--extensions/TypeSniffer/disabled (renamed from extensions/MoreBugUrl/disabled)0
-rw-r--r--extensions/Voting/disabled0
-rw-r--r--favicon.icobin0 -> 4846 bytes
-rw-r--r--images/favicon.icobin4150 -> 4846 bytes
-rw-r--r--images/ranks/bugs-rank-at.pngbin0 -> 893 bytes
-rw-r--r--images/ranks/bugs-rank-dev.pngbin0 -> 910 bytes
-rw-r--r--images/ranks/bugs-rank-infra.pngbin0 -> 901 bytes
-rw-r--r--images/ranks/bugs-rank-sec.pngbin0 -> 875 bytes
-rw-r--r--mod_perl.pl3
-rwxr-xr-xrecompile.sh5
-rw-r--r--robots-ssl.txt21
-rw-r--r--robots.txt20
-rwxr-xr-xrunstats.sh36
-rwxr-xr-xshow_bug.cgi3
-rw-r--r--skins/contrib/Gentoo/buglist.css24
-rw-r--r--skins/contrib/Gentoo/global.css331
-rw-r--r--skins/contrib/Gentoo/index.css33
-rw-r--r--skins/standard/global.css69
-rw-r--r--skins/standard/index/file-a-bug.pngbin3534 -> 6395 bytes
-rw-r--r--skins/standard/index/help.pngbin4111 -> 7021 bytes
-rw-r--r--skins/standard/index/new-account.pngbin4082 -> 7018 bytes
-rw-r--r--skins/standard/index/search.pngbin4828 -> 7912 bytes
-rw-r--r--template/en/default/account/create.html.tmpl4
-rw-r--r--template/en/default/admin/users/edit.html.tmpl5
-rw-r--r--template/en/default/admin/users/list.html.tmpl4
-rw-r--r--template/en/default/attachment/createformcontents.html.tmpl12
-rw-r--r--template/en/default/attachment/edit.html.tmpl2
-rw-r--r--template/en/default/attachment/list.html.tmpl3
-rw-r--r--template/en/default/bug/comments.html.tmpl2
-rw-r--r--template/en/default/bug/create/comment-guided.txt.tmpl2
-rw-r--r--template/en/default/bug/create/create-guided.html.tmpl69
-rw-r--r--template/en/default/bug/create/create.html.tmpl24
-rw-r--r--template/en/default/bug/create/user-message.html.tmpl4
-rw-r--r--template/en/default/bug/edit.html.tmpl62
-rw-r--r--template/en/default/bug/field-help.none.tmpl4
-rw-r--r--template/en/default/bug/format_comment.txt.tmpl7
-rw-r--r--template/en/default/bug/navigate.html.tmpl4
-rw-r--r--template/en/default/bug/process/verify-new-product.html.tmpl13
-rw-r--r--template/en/default/bug/show-header.html.tmpl7
-rw-r--r--template/en/default/email/bugmail-header.txt.tmpl7
-rw-r--r--template/en/default/global/choose-product.html.tmpl48
-rw-r--r--template/en/default/global/common-links.html.tmpl3
-rw-r--r--template/en/default/global/header.html.tmpl2
-rw-r--r--template/en/default/global/user-error.html.tmpl30
-rw-r--r--template/en/default/global/variables.none.tmpl2
-rw-r--r--template/en/default/index.html.tmpl4
-rw-r--r--template/en/default/list/edit-multiple.html.tmpl7
-rw-r--r--template/en/default/pages/fields.html.tmpl63
-rw-r--r--template/en/default/welcome-admin.html.tmpl2
-rwxr-xr-xuserprefs.cgi3
-rw-r--r--zzz.txt1
114 files changed, 3897 insertions, 166 deletions
diff --git a/.htaccess b/.htaccess
index 2f009697c..229d7385a 100644
--- a/.htaccess
+++ b/.htaccess
@@ -42,3 +42,19 @@ Options -Indexes
RewriteOptions inherit
RewriteRule ^rest/(.*)$ rest.cgi/$1 [NE]
</IfModule>
+
+<FilesMatch ^custom_buglist.cgi$>
+ <IfModule mod_version.c>
+ <IfVersion < 2.4>
+ Allow from localhost 94.100.119.160/28
+ Deny from all
+ </IfVersion>
+ <IfVersion >= 2.4>
+ Require host localhost 94.100.119.160/28
+ </IfVersion>
+ </IfModule>
+ <IfModule !mod_version.c>
+ Allow from localhost 94.100.119.160/28
+ Deny from all
+ </IfModule>
+</FilesMatch>
diff --git a/Bugzilla/BugUrl.pm b/Bugzilla/BugUrl.pm
index 1d75fe8f1..1fe8b3d0c 100644
--- a/Bugzilla/BugUrl.pm
+++ b/Bugzilla/BugUrl.pm
@@ -56,6 +56,7 @@ use constant SUB_CLASSES => qw(
Bugzilla::BugUrl::Bugzilla::Local
Bugzilla::BugUrl::Bugzilla
Bugzilla::BugUrl::Launchpad
+ Bugzilla::BugUrl::Flyspray
Bugzilla::BugUrl::Google
Bugzilla::BugUrl::Debian
Bugzilla::BugUrl::JIRA
diff --git a/Bugzilla/BugUrl/Debian.pm b/Bugzilla/BugUrl/Debian.pm
index 2b611aa57..b726b0b5a 100644
--- a/Bugzilla/BugUrl/Debian.pm
+++ b/Bugzilla/BugUrl/Debian.pm
@@ -23,7 +23,10 @@ sub should_handle {
# Debian BTS URLs can look like various things:
# http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1234
# http://bugs.debian.org/1234
- return (lc($uri->authority) eq 'bugs.debian.org'
+ # https://debbugs.gnu.org/cgi/bugreport.cgi?bug=123
+ # https://debbugs.gnu.org/123
+ return ((lc($uri->authority) eq 'bugs.debian.org'
+ or lc($uri->authority) eq 'debbugs.gnu.org')
and (($uri->path =~ /bugreport\.cgi$/
and $uri->query_param('bug') =~ m|^\d+$|)
or $uri->path =~ m|^/\d+$|)) ? 1 : 0;
@@ -37,7 +40,7 @@ sub _check_value {
# This is the shortest standard URL form for Debian BTS URLs,
# and so we reduce all URLs to this.
$uri->path =~ m|^/(\d+)$| || $uri->query_param('bug') =~ m|^(\d+)$|;
- $uri = new URI("http://bugs.debian.org/$1");
+ $uri = new URI('https://' . $uri->authority . '/' . $1);
return $uri;
}
diff --git a/Bugzilla/BugUrl/Flyspray.pm b/Bugzilla/BugUrl/Flyspray.pm
new file mode 100644
index 000000000..86583fc4b
--- /dev/null
+++ b/Bugzilla/BugUrl/Flyspray.pm
@@ -0,0 +1,36 @@
+# 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::BugUrl::Flyspray;
+use strict;
+use base qw(Bugzilla::BugUrl);
+
+###############################
+#### Methods ####
+###############################
+
+sub should_handle {
+ my ($class, $uri) = @_;
+
+ # Flyspray URLs look like the following:
+ # https://bugs.flyspray.org/task/1237
+ # https://bugs.archlinux.org/task/44825
+ return ($uri->path_query =~ m|/task/\d+$|) ? 1 : 0;
+}
+
+sub _check_value {
+ my $class = shift;
+
+ my $uri = $class->SUPER::_check_value(@_);
+
+ # Remove any # part if there is one.
+ $uri->fragment(undef);
+
+ return $uri;
+}
+
+1;
diff --git a/Bugzilla/CGI.pm b/Bugzilla/CGI.pm
index 9b1ff9235..30c530b8c 100644
--- a/Bugzilla/CGI.pm
+++ b/Bugzilla/CGI.pm
@@ -403,10 +403,11 @@ sub header {
$headers{'-strict_transport_security'} = $sts_opts;
}
- # Add X-Frame-Options header to prevent framing and subsequent
+ # Add X-Frame-Options & CSP headers to prevent framing and subsequent
# possible clickjacking problems.
unless ($self->url_is_attachment_base) {
$headers{'-x_frame_options'} = 'SAMEORIGIN';
+ $headers{'-content_security_policy'} = "frame-ancestors 'self'";
}
# Add X-XSS-Protection header to prevent simple XSS attacks
diff --git a/Bugzilla/Constants.pm b/Bugzilla/Constants.pm
index edaa8baa5..7c387c82e 100644
--- a/Bugzilla/Constants.pm
+++ b/Bugzilla/Constants.pm
@@ -308,7 +308,11 @@ use constant SAVE_NUM_SEARCHES => 10;
# The column width for comment textareas and comments in bugmails.
use constant COMMENT_COLS => 80;
# Used in _check_comment(). Gives the max length allowed for a comment.
-use constant MAX_COMMENT_LENGTH => 65535;
+use constant MAX_COMMENT_LENGTH => 16384;
+
+# The minimum and maximum length of comment tags.
+use constant MIN_COMMENT_TAG_LENGTH => 3;
+use constant MAX_COMMENT_TAG_LENGTH => 24;
# The minimum and maximum length of comment tags.
use constant MIN_COMMENT_TAG_LENGTH => 3;
@@ -452,8 +456,8 @@ use constant LOGIN_LOCKOUT_INTERVAL => 30;
use constant ACCOUNT_CHANGE_INTERVAL => 10;
# The maximum number of seconds the Strict-Transport-Security header
-# will remain valid. Default is one week.
-use constant MAX_STS_AGE => 604800;
+# will remain valid. Default is one month.
+use constant MAX_STS_AGE => 15768000;
# Protocols which are considered as safe.
use constant SAFE_PROTOCOLS => ('afs', 'cid', 'ftp', 'gopher', 'http', 'https',
diff --git a/Bugzilla/Install/Filesystem.pm b/Bugzilla/Install/Filesystem.pm
index d30ae18dc..a96fd59aa 100644
--- a/Bugzilla/Install/Filesystem.pm
+++ b/Bugzilla/Install/Filesystem.pm
@@ -161,7 +161,7 @@ sub FILESYSTEM {
'sanitycheck.pl' => { perms => WS_EXECUTE },
'checksetup.pl' => { perms => OWNER_EXECUTE },
'runtests.pl' => { perms => OWNER_EXECUTE },
- 'jobqueue.pl' => { perms => OWNER_EXECUTE },
+ 'jobqueue.pl' => { perms => WS_EXECUTE },
'migrate.pl' => { perms => OWNER_EXECUTE },
'install-module.pl' => { perms => OWNER_EXECUTE },
'clean-bug-user-last-visit.pl' => { perms => WS_EXECUTE },
diff --git a/Bugzilla/Search.pm b/Bugzilla/Search.pm
index 646f949f5..0694dd98c 100644
--- a/Bugzilla/Search.pm
+++ b/Bugzilla/Search.pm
@@ -2169,7 +2169,9 @@ sub _timestamp_translate {
my $value = $args->{value};
my $dbh = Bugzilla->dbh;
- return if $value !~ /^(?:[\+\-]?\d+[hdwmy]s?|now)$/i;
+ # Force parsing of all dates & times, so that we filter weird values out
+ # from users.
+ #return if $value !~ /^(?:[\+\-]?\d+[hdwmy]s?|now)$/i;
$value = SqlifyDate($value);
# By default, the time is appended to the date, which we don't always want.
diff --git a/Bugzilla/Search/Quicksearch.pm b/Bugzilla/Search/Quicksearch.pm
index 830177f8b..4f57b4ebc 100644
--- a/Bugzilla/Search/Quicksearch.pm
+++ b/Bugzilla/Search/Quicksearch.pm
@@ -142,7 +142,7 @@ sub quicksearch {
$searchstring =~ s/(^[\s,]+|[\s,]+$)//g;
ThrowUserError('buglist_parameters_required') unless ($searchstring);
- if ($searchstring =~ m/^[0-9,\s]*$/) {
+ if ($searchstring =~ m/^#?[0-9,\s]*$/) {
_bug_numbers_only($searchstring);
}
else {
@@ -301,6 +301,8 @@ sub _bug_numbers_only {
my $cgi = Bugzilla->cgi;
# Allow separation by comma or whitespace.
$searchstring =~ s/[,\s]+/,/g;
+ # Trim the leading # if used.
+ $searchstring =~ s/^#//;
if ($searchstring !~ /,/ && !i_am_webservice()) {
# Single bug number; shortcut to show_bug.cgi.
@@ -571,23 +573,23 @@ sub _special_field_syntax {
sub _default_quicksearch_word {
my ($word, $negate) = @_;
- if (!grep { lc($word) eq $_ } PRODUCT_EXCEPTIONS and length($word) > 2) {
- addChart('product', 'substring', $word, $negate);
- }
+# if (!grep { lc($word) eq $_ } PRODUCT_EXCEPTIONS and length($word) > 2) {
+# addChart('product', 'substring', $word, $negate);
+# }
- if (!grep { lc($word) eq $_ } COMPONENT_EXCEPTIONS and length($word) > 2) {
- addChart('component', 'substring', $word, $negate);
- }
+# if (!grep { lc($word) eq $_ } COMPONENT_EXCEPTIONS and length($word) > 2) {
+# addChart('component', 'substring', $word, $negate);
+# }
- my @legal_keywords = map($_->name, Bugzilla::Keyword->get_all);
- if (grep { lc($word) eq lc($_) } @legal_keywords) {
- addChart('keywords', 'substring', $word, $negate);
- }
+# my @legal_keywords = map($_->name, Bugzilla::Keyword->get_all);
+# if (grep { lc($word) eq lc($_) } @legal_keywords) {
+# addChart('keywords', 'substring', $word, $negate);
+# }
addChart('alias', 'substring', $word, $negate);
addChart('short_desc', 'substring', $word, $negate);
- addChart('status_whiteboard', 'substring', $word, $negate);
- addChart('content', 'matches', _matches_phrase($word), $negate) if $fulltext;
+# addChart('status_whiteboard', 'substring', $word, $negate);
+# addChart('content', 'matches', _matches_phrase($word), $negate) if $fulltext;
}
sub _handle_urls {
diff --git a/Bugzilla/Template.pm b/Bugzilla/Template.pm
index 7294e27c1..48270b129 100644
--- a/Bugzilla/Template.pm
+++ b/Bugzilla/Template.pm
@@ -316,7 +316,7 @@ sub get_attachment_link {
# Prevent code injection in the title.
$title = html_quote(clean_text($title));
- $link_text =~ s/ \[details\]$//;
+ $link_text =~ s/ \[details(?:, diff)?\]$//;
my $linkval = "attachment.cgi?id=$attachid";
# If the attachment is a patch, try to link to the diff rather
@@ -326,11 +326,20 @@ sub get_attachment_link {
$patchlink = '&amp;action=diff';
}
- # Whitespace matters here because these links are in <pre> tags.
- return qq|<span class="$className">|
- . qq|<a href="${linkval}${patchlink}" name="attach_${attachid}" title="$title">$link_text</a>|
- . qq| <a href="${linkval}&amp;action=edit" title="$title">[details]</a>|
- . qq|</span>|;
+ if ($patchlink) {
+ # Whitespace matters here because these links are in <pre> tags.
+ return qq|<span class="$className">|
+ . qq|<a href="${linkval}" name="attach_${attachid}" title="$title">$link_text</a>|
+ . qq| [<a href="${linkval}&amp;action=edit" title="$title">details</a>, <a href="${linkval}${patchlink}" title="$title">diff</a>]|
+ . qq|</span>|;
+ }
+ else {
+ # Whitespace matters here because these links are in <pre> tags.
+ return qq|<span class="$className">|
+ . qq|<a href="${linkval}" name="attach_${attachid}" title="$title">$link_text</a>|
+ . qq| [<a href="${linkval}&amp;action=edit" title="$title">details</a>]|
+ . qq|</span>|;
+ }
}
else {
return qq{$link_text};
@@ -1029,6 +1038,9 @@ sub create {
# Allow templates to access the "correct" URLBase value
'urlbase' => sub { return Bugzilla::Util::correct_urlbase(); },
+ 'httpbase' => sub { return Bugzilla->params->{'urlbase'}; },
+ 'sslbase' => sub { return Bugzilla->params->{'sslbase'}; },
+ 'ssl_redirect' => sub { return Bugzilla->params->{'ssl_redirect'}; },
# Allow templates to access docs url with users' preferred language
# We fall back to English if documentation in the preferred
diff --git a/Bugzilla/WebService/Product.pm b/Bugzilla/WebService/Product.pm
index 94348a161..7d9e7f181 100644
--- a/Bugzilla/WebService/Product.pm
+++ b/Bugzilla/WebService/Product.pm
@@ -31,6 +31,7 @@ use constant PUBLIC_METHODS => qw(
get
get_accessible_products
get_enterable_products
+ get_products
get_selectable_products
update
);
diff --git a/attachment.cgi b/attachment.cgi
index 4cd9229fb..3f0ff22ba 100755
--- a/attachment.cgi
+++ b/attachment.cgi
@@ -347,7 +347,7 @@ sub view {
local $Encode::Encoding{'MIME-Q'}->{'bpl'} = 10000;
$filename = encode('MIME-Q', $filename);
- my $disposition = Bugzilla->params->{'allow_attachment_display'} ? 'inline' : 'attachment';
+ my $disposition = (Bugzilla->params->{'allow_attachment_display'} || $contenttype eq "text/plain") ? 'inline' : 'attachment';
# Don't send a charset header with attachments--they might not be UTF-8.
# However, we do allow people to explicitly specify a charset if they
@@ -355,13 +355,7 @@ sub view {
if ($contenttype !~ /\bcharset=/i) {
# In order to prevent Apache from adding a charset, we have to send a
# charset that's a single space.
- $cgi->charset(' ');
- if (Bugzilla->feature('detect_charset') && $contenttype =~ /^text\//) {
- my $encoding = detect_encoding($attachment->data);
- if ($encoding) {
- $cgi->charset(find_encoding($encoding)->mime_name);
- }
- }
+ $cgi->charset('UTF-8');
}
print $cgi->header(-type=>"$contenttype; name=\"$filename\"",
-content_disposition=> "$disposition; filename=\"$filename\"",
diff --git a/bots.html b/bots.html
new file mode 100644
index 000000000..e6fceb6a5
--- /dev/null
+++ b/bots.html
@@ -0,0 +1,86 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
+<head>
+<title>Policies and links for accessing Gentoo Bugzilla with an automated bot/spider/code</title>
+</head>
+<body>
+<h1>Policies and links for accessing Gentoo Bugzilla with an automated bot/spider/code</h1>
+<h2>Policies</h2>
+<h3>What URLs are bots and automated scripts allowed to hit?</h3>
+<p>Unless you have a listed exception, the URLs below are the ONLY ones you are allowed to hit with bots.</p>
+<p><tt>robots.txt</tt> is your friend. It reflects these rules in a more detailed manner.</p>
+<p>HTTPS should always be used, non-HTTP requests will be redirected to upgrade.</p>
+<ul>
+<li>
+ Any direct bug page (more parameters eg for RSS/XML forms are fine):<br />
+ https://bugs.gentoo.org/&lt;BUGID&gt;<br />
+ https://bugs.gentoo.org/show_bug.cgi?id=&lt;BUGID&gt;
+</li>
+<li>
+ Any direct attachment:<br />
+ https://bugs.gentoo.org/attachment.cgi?id=&lt;ATTACHID&gt;
+</li>
+<li>
+ Duplicates report:<br />
+ https://bugs.gentoo.org/data/duplicates.rdf
+</li>
+<li>
+ Any URL in the "Bug listing links" section of this page.
+</li>
+</ul>
+
+<h3>Exceptions</h3>
+<p>We do have some exceptions to the above. If you would like to apply for one,
+please <a
+href="mailto:bugzilla@gentoo.org?subject=Bot%20Exception%20Request">contact
+us</a>, stating your case clearly.
+</p>
+<ul>
+<li>pybugz</li>
+<li>rbot-bugzilla</li>
+<li>supybot bugzilla plugin</li>
+<li>Eclipse Mylyn</li>
+</ul>
+
+
+<h2>Bug listing links</h2>
+<p>Each of the links below is updated every 2 hours. They expose EVERY public
+bug, generated as a static file list.</p><p>Beware, as some of them are very
+large. If you want to explicitly fetch compressed versions, use
+<tt>.htmlgz</tt> as the suffix instead of <tt>.html</tt>. Browsers with
+compression support will also be transparently redirected to the compressed
+version.</p>
+
+<ul>
+<li><a href='/data/cached/buglist-UNCONFIRMED.html'>All bugs with status UNCONFIRMED.</a></li>
+<li><a href='/data/cached/buglist-CONFIRMED.html'>All bugs with status CONFIRMED.</a></li>
+<li><a href='/data/cached/buglist-IN_PROGRESS.html'>All bugs with status IN_PROGRESS.</a></li>
+
+<li><a href='/data/cached/buglist-RESOLVED-CANTFIX.html'>All bugs with status RESOLVED and resolution CANTFIX.</a></li>
+<li><a href='/data/cached/buglist-RESOLVED-FIXED.html'>All bugs with status RESOLVED and resolution FIXED.</a></li>
+<li><a href='/data/cached/buglist-RESOLVED-INVALID.html'>All bugs with status RESOLVED and resolution INVALID.</a></li>
+<li><a href='/data/cached/buglist-RESOLVED-LATER.html'>All bugs with status RESOLVED and resolution LATER.</a></li>
+<li><a href='/data/cached/buglist-RESOLVED-NEEDINFO.html'>All bugs with status RESOLVED and resolution NEEDINFO.</a></li>
+<li><a href='/data/cached/buglist-RESOLVED-REMIND.html'>All bugs with status RESOLVED and resolution REMIND.</a></li>
+<li><a href='/data/cached/buglist-RESOLVED-TEST-REQUEST.html'>All bugs with status RESOLVED and resolution TEST-REQUEST.</a></li>
+<li><a href='/data/cached/buglist-RESOLVED-UPSTREAM.html'>All bugs with status RESOLVED and resolution UPSTREAM.</a></li>
+<li><a href='/data/cached/buglist-RESOLVED-WONTFIX.html'>All bugs with status RESOLVED and resolution WONTFIX.</a></li>
+<li><a href='/data/cached/buglist-RESOLVED-WORKSFORME.html'>All bugs with status RESOLVED and resolution WORKSFORME.</a></li>
+<li><a href='/data/cached/buglist-RESOLVED-OBSOLETE.html'>All bugs with status RESOLVED and resolution OBSOLETE.</a></li>
+
+<li><a href='/data/cached/buglist-VERIFIED-CANTFIX.html'>All bugs with status VERIFIED and resolution CANTFIX.</a></li>
+<li><a href='/data/cached/buglist-VERIFIED-FIXED.html'>All bugs with status VERIFIED and resolution FIXED.</a></li>
+<li><a href='/data/cached/buglist-VERIFIED-INVALID.html'>All bugs with status VERIFIED and resolution INVALID.</a></li>
+<li><a href='/data/cached/buglist-VERIFIED-LATER.html'>All bugs with status VERIFIED and resolution LATER.</a></li>
+<li><a href='/data/cached/buglist-VERIFIED-NEEDINFO.html'>All bugs with status VERIFIED and resolution NEEDINFO.</a></li>
+<li><a href='/data/cached/buglist-VERIFIED-REMIND.html'>All bugs with status VERIFIED and resolution REMIND.</a></li>
+<li><a href='/data/cached/buglist-VERIFIED-TEST-REQUEST.html'>All bugs with status VERIFIED and resolution TEST-REQUEST.</a></li>
+<li><a href='/data/cached/buglist-VERIFIED-UPSTREAM.html'>All bugs with status VERIFIED and resolution UPSTREAM.</a></li>
+<li><a href='/data/cached/buglist-VERIFIED-WONTFIX.html'>All bugs with status VERIFIED and resolution WONTFIX.</a></li>
+<li><a href='/data/cached/buglist-VERIFIED-WORKSFORME.html'>All bugs with status VERIFIED and resolution WORKSFORME.</a></li>
+<li><a href='/data/cached/buglist-VERIFIED-OBSOLETE.html'>All bugs with status VERIFIED and resolution OBSOLETE.</a></li>
+
+</ul>
+
+</body>
+</html>
diff --git a/buglist.cgi b/buglist.cgi
index daee34c9b..719bb9639 100755
--- a/buglist.cgi
+++ b/buglist.cgi
@@ -831,6 +831,7 @@ foreach my $row (@$data) {
push(@bugs, $bug);
# Add id to list for checking for bug privacy later
+ detaint_natural($bug->{'bug_id'});
push(@bugidlist, $bug->{'bug_id'});
# Compute time tracking info.
diff --git a/custom_buglist.cgi b/custom_buglist.cgi
new file mode 100755
index 000000000..2e383fd74
--- /dev/null
+++ b/custom_buglist.cgi
@@ -0,0 +1,90 @@
+#!/usr/bin/perl -wT
+# This copy of buglist is simple and stupid, just for bots
+use strict;
+
+use lib qw(. lib);
+
+use Data::Dumper;
+use DateTime;
+use Bugzilla;
+use Bugzilla::Constants;
+use Bugzilla::Util;
+
+my $cgi = Bugzilla->cgi;
+my $template = Bugzilla->template;
+my $vars = {};
+my $dbh = Bugzilla->switch_to_shadow_db();
+my @bindValues;
+
+print $cgi->header(-type=>'text/html');
+my $reso = $cgi->param('reso');
+my $status = $cgi->param('status');
+my $since = $cgi->param('since');
+$status = undef unless defined($status);
+$reso = undef unless defined($reso);
+$since = undef unless defined($since) and $since =~ /^[0-9]+/;
+
+trick_taint($reso) if defined($reso);
+trick_taint($status) if defined($status);
+trick_taint($since) if defined($since);
+
+# SELECT profiles.login_name,bugs.bug_id,short_desc,priority,resolution,rep_platform,bug_status,bug_severity,profilesb.login_name AS reporter FROM bugs LEFT JOIN profiles ON bugs.assigned_to = profiles.userid LEFT JOIN profiles AS profilesb ON bugs.reporter = profilesb.userid LEFT JOIN bug_group_map ON bug_group_map.bug_id = bugs.bug_id WHERE bugs.bug_id=160786 AND (bug_group_map.group_id IS NULL OR bug_group_map.group_id!=24)
+
+my @bindValues2;
+my ($reso_sql, $status_sql, $since_sql);
+my ($reso_desc, $status_desc, $since_desc);
+$reso_sql = $status_sql = $since_sql = '1';
+$reso_desc = $status_desc = $since_desc = '';
+if(defined($reso)) {
+ $reso_sql = 'resolution=?';
+ push(@bindValues2, $reso);
+ $reso_desc = 'with resolution '.$reso;
+}
+if(defined($status)) {
+ $status_sql = 'bug_status=?';
+ push(@bindValues2, $status);
+ $status_desc = 'with status '.$status;
+}
+if(defined($since)) {
+ $since_sql = 'since=?';
+ push(@bindValues2, $since);
+ $since_desc = 'since '.$since;
+}
+my $query2 = sprintf 'SELECT
+ bugs.bug_id, short_desc, priority,
+ resolution, bug_status, bug_severity
+ FROM bugs
+ LEFT JOIN bug_group_map ON bug_group_map.bug_id = bugs.bug_id
+ WHERE bug_group_map.group_id IS NULL
+ AND %s
+ AND %s
+ AND %s',
+ $reso_sql,
+ $status_sql,
+ $since_sql;
+
+
+#print Dumper($vars);
+my $title = sprintf "Bug listing %s %s %s as at %s",$status_desc,$reso_desc,$since_desc,DateTime->now->strftime("%Y/%m/%d %H:%M:%S");
+print '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">'."\n";
+printf "<head><title>$title</title></head>\n";
+printf "<body><h1>%s</h1>\n",$title;
+my $actions = $dbh->selectall_arrayref(
+ $query2,
+ { Slice => {} },
+ @bindValues2
+);
+
+my $counter = 0;
+print "<div><ul>";
+foreach my $row (@$actions) {
+ printf "<li><a href='%s%d'>Bug:%d - \"<em>%s</em>\" status:%s resolution:%s severity:%s</a></li>\n",
+ correct_urlbase(),
+ $row->{'bug_id'}, $row->{'bug_id'},
+ html_quote($row->{'short_desc'}),
+ $row->{'bug_status'},
+ $row->{'resolution'},
+ $row->{'bug_severity'};
+ $counter++;
+}
+printf "</ul>Done. Count=%d</div></body></html>\n",$counter;
diff --git a/custom_disabled.cgi b/custom_disabled.cgi
new file mode 100755
index 000000000..2c134c7f0
--- /dev/null
+++ b/custom_disabled.cgi
@@ -0,0 +1,41 @@
+#!/usr/bin/perl -wT
+use strict;
+
+use lib qw(. lib);
+
+use Bugzilla;
+use Bugzilla::Constants;
+
+my $cgi = Bugzilla->cgi;
+my $vars = {};
+my $user = Bugzilla->login(LOGIN_REQUIRED);
+my $dbh = Bugzilla->switch_to_shadow_db();
+
+print $cgi->header(-type=>'text/html');
+
+$user->in_group('admin')
+ || $user->in_group('editusers')
+ || $user->in_group('gentoo-dev')
+ || ThrowUserError('auth_failure', {action => 'access', object => 'administrative_pages'});
+
+my $query = 'SELECT DISTINCT userid, login_name, realname, disabledtext, disable_mail ' .
+ 'FROM profiles '.
+ 'WHERE LENGTH(profiles.disabledtext) > 0';
+$vars->{'users'} = $dbh->selectall_arrayref($query, { Slice => {} });
+
+#use Data::Dumper;
+#print Dumper($vars);
+
+foreach my $user (@{$vars->{'users'}}) {
+ next if($user->{'realname'} =~ m/\(RETIRED\)$/ and $user->{'disabledtext'} =~ m/retired/i);
+
+ $user->{'disabledtext'} =~ s/\n/<br>/g;
+
+ # Add bug links
+ $user->{'disabledtext'} =~ s/(bug (\d+(#c\d+)?))/<a href="\/$2">$1<\/a>/g;
+
+ printf("Login=<a href=\"/editusers.cgi?action=edit&userid=%i\">%s</a><br>", $user->{'userid'}, $user->{'login_name'});
+ printf("Real Name=%s<br>", $user->{'realname'});
+ printf("Bugmail Disabled: %s<br>", $user->{'disable_mail'} eq 1 ? "Yes" : "No");
+ printf("Disabled Text=%s<br><br>", $user->{'disabledtext'});
+}
diff --git a/custom_extraperms.cgi b/custom_extraperms.cgi
new file mode 100755
index 000000000..01ffcbe76
--- /dev/null
+++ b/custom_extraperms.cgi
@@ -0,0 +1,73 @@
+#!/usr/bin/perl -wT
+use strict;
+
+use lib qw(. lib);
+
+use Bugzilla;
+use Bugzilla::Constants;
+use Bugzilla::Error;
+use Bugzilla::Util;
+
+my $cgi = Bugzilla->cgi;
+my $user = Bugzilla->login(LOGIN_REQUIRED);
+my $dbh = Bugzilla->switch_to_shadow_db();
+
+my @bindValues;
+
+print $cgi->header(-type=>'text/html');
+
+$user->in_group('admin')
+ || $user->in_group('editusers')
+ || $user->in_group('gentoo-dev')
+ || ThrowUserError('auth_failure', {action => 'access', object => 'administrative_pages'});
+
+my $sql_archtesters = "
+SELECT
+profiles.login_name
+FROM
+ profiles
+ JOIN user_group_map ON user_id=profiles.userid
+ JOIN groups ON groups.id=group_id
+WHERE
+ user_id IN (SELECT user_id FROM user_group_map WHERE group_id=31)
+ AND group_id != 7
+ AND profiles.login_name NOT LIKE '%\@gentoo.org'
+GROUP BY login_name
+ORDER BY login_name;";
+my $sql_otherperm = "
+SELECT
+ profiles.login_name,
+ groups.name AS group_name
+FROM
+ profiles
+ JOIN user_group_map ON user_id=profiles.userid
+ JOIN groups ON groups.id=group_id
+WHERE
+ user_id NOT IN (SELECT user_id FROM user_group_map WHERE group_id=31)
+ AND group_id != 7
+ AND profiles.login_name NOT LIKE '%\@gentoo.org'
+ AND groups.name != 'saved-searches'
+GROUP BY login_name
+ORDER BY login_name;";
+
+my $users;
+$users = $dbh->selectall_arrayref(
+ $sql_archtesters,
+ { Slice => {} },
+ @bindValues
+);
+
+printf "<h3>Arch Testers that are not \@gentoo.org</h3>\n";
+foreach my $row (@$users) {
+ printf "<a href='%scustom_userhistory.cgi?matchstr=%s'>%s</a><br />\n", correct_urlbase(), $row->{'login_name'}, $row->{'login_name'};
+}
+
+$users = $dbh->selectall_arrayref(
+ $sql_otherperm,
+ { Slice => {} },
+ @bindValues
+);
+printf "<h3>Users with Other Groups</h3>\n";
+foreach my $row (@$users) {
+ printf "<a href='%scustom_userhistory.cgi?matchstr=%s'>%s</a>: %s<br />\n", correct_urlbase(), $row->{'login_name'}, $row->{'login_name'}, $row->{'group_name'};
+}
diff --git a/custom_userhistory.cgi b/custom_userhistory.cgi
new file mode 100755
index 000000000..f47076e6b
--- /dev/null
+++ b/custom_userhistory.cgi
@@ -0,0 +1,323 @@
+#!/usr/bin/perl -wT
+use strict;
+
+use lib qw(. lib);
+
+use Data::Dumper;
+use Bugzilla;
+use Bugzilla::Constants;
+use Bugzilla::Util;
+use Bugzilla::User;
+
+my $cgi = Bugzilla->cgi;
+my $vars = {};
+my $myuser = Bugzilla->login(LOGIN_REQUIRED);
+my $dbh = Bugzilla->switch_to_shadow_db();
+my ($query, $matchstr, $userid, $limit, $login_name);
+
+print $cgi->header();
+
+$matchstr = $cgi->param('matchstr');
+$userid = $cgi->param('userid');
+$userid = undef unless defined($userid) and $userid =~ /^\d+$/;
+if(!defined($matchstr) and !defined($userid)) {
+ print "No search parameters specified!<br/>\n";
+ print "Put <tt>matchstr</tt> or <tt>userid</tt> in the URL parameters.<br/>\n";
+ exit(0);
+}
+exit 0 if !defined($matchstr) and !defined($userid);
+
+$limit = $cgi->param('limit');
+$limit = 50 unless defined($limit) and $limit =~ /^\d+$/;
+
+trick_taint($matchstr) if defined($matchstr);
+trick_taint($userid) if defined($userid);
+trick_taint($limit);
+
+$userid = $matchstr ? login_to_id($matchstr) : $userid;
+$login_name = $matchstr ? $matchstr : Bugzilla::User->new($userid)->login;
+
+if(!$userid || !$login_name) {
+ print "Bad user!<br/>";
+ exit(0);
+}
+
+$query = qq{
+(SELECT
+ bug_id,
+ bug_when,
+ fielddefs.name AS field
+ FROM
+ bugs_activity
+ JOIN fielddefs ON bugs_activity.fieldid=fielddefs.id
+ WHERE
+ who=?
+ ORDER BY
+ bug_when DESC
+ LIMIT $limit)
+UNION
+(SELECT
+ bug_id,
+ bug_when,
+ 'ZZcomment #' AS field
+ FROM
+ longdescs
+ WHERE
+ who=?
+ ORDER BY
+ bug_when DESC
+ LIMIT $limit)
+UNION
+(SELECT
+ bug_id,
+ creation_ts AS bug_when,
+ CONCAT('ZZattachment #', attach_id) AS field
+ FROM
+ attachments
+ WHERE
+ submitter_id=?
+ ORDER BY
+ creation_ts DESC
+ LIMIT $limit)
+ORDER BY bug_when DESC
+LIMIT $limit};
+my $actions = $dbh->selectall_arrayref(
+ $query,
+ { Slice => {} },
+ ($userid, $userid, $userid),
+);
+
+#print Dumper($vars);
+printf "<h1>Custom User History: %s</h1>\n", $login_name;
+print "<table>\n";
+printf "<tr><th>login_name</th><td>%s</td></tr>\n",$login_name;
+printf "<tr><th>userid</th><td>%s</td></tr>\n",$userid;
+print "</table>\n";
+
+sub show_bug_url {
+ return "/show_bug.cgi?id=".shift;
+}
+
+print q{
+<hr/>
+<h2>Bug History</h2>
+<table>
+ <tr>
+ <th>Timestamp</th>
+ <th>BugID</th>
+ <th>Field</th>
+ </tr>
+};
+my $counter = 0;
+foreach my $row (@$actions) {
+ $counter++;
+ my $url = show_bug_url($row->{'bug_id'});
+ (my $message = qq{
+ <tr>
+ <td>$row->{'bug_when'}</td>
+ <td><a href="${url}">$row->{'bug_id'}</a></td>
+ <td>$row->{'field'}</td>
+ </tr>
+ }) =~ s/^\t{1}//mg;
+ print $message;
+}
+print qq{
+</table>
+History Done<br />
+Limit=${limit}<br />
+Count=${counter}<br />
+};
+
+$query = q{
+SELECT
+ p2.userid AS grantor_id,
+ p1.userid AS grantee_id,
+ p2.login_name AS grantor,
+ p1.login_name AS grantee,
+ profiles_when,
+ oldvalue,
+ newvalue
+FROM
+ profiles p1
+ JOIN profiles_activity ON p1.userid=profiles_activity.userid
+ JOIN profiles p2 ON p2.userid=who
+WHERE
+ FALSE
+ OR p1.userid = ?
+ OR p2.userid = ?
+ORDER BY
+ profiles_when};
+$actions = $dbh->selectall_arrayref(
+ $query,
+ { Slice => {} },
+ ($userid, $userid),
+);
+
+print qq{
+<hr/>
+<h2>Profile Activity</h2>
+<h3>Applied to ${login_name}:</h3>
+<table>
+ <tr>
+ <th>Timestamp</th>
+ <th>Grantor</th>
+ <th>Grantee</th>
+ <th>Oldvalue</th>
+ <th>Newvalue</th>
+ </tr>
+};
+foreach my $row (@$actions) {
+ next unless $row->{'grantee_id'} == $userid;
+ (my $message = qq{
+ <tr>
+ <td>$row->{'profiles_when'}</td>
+ <td>$row->{'grantor'}</td>
+ <td>$row->{'grantee'}</td>
+ <td>$row->{'oldvalue'}</td>
+ <td>$row->{'newvalue'}</td>
+ </tr>
+ }) =~ s/^\t{1}//mg;
+ print $message;
+}
+
+print qq{
+</table>
+
+<h3>Applied by ${login_name}:</h3>
+<table>
+ <tr>
+ <th>Timestamp</th>
+ <th>Grantor</th>
+ <th>Grantee</th>
+ <th>Oldvalue</th>
+ <th>Newvalue</th>
+ </tr>
+};
+foreach my $row (@$actions) {
+ next unless $row->{'grantor_id'} == $userid;
+ (my $message = qq{
+ <tr>
+ <td>$row->{'profiles_when'}</td>
+ <td>$row->{'grantor'}</td>
+ <td>$row->{'grantee'}</td>
+ <td>$row->{'oldvalue'}</td>
+ <td>$row->{'newvalue'}</td>
+ </tr>
+ }) =~ s/^\t{1}//mg;
+ print $message;
+}
+print "</table>\n";
+
+$query = q{
+SELECT
+ p1.userid AS watcher_id,
+ p2.userid AS watched_id,
+ p1.login_name AS watcher,
+ p2.login_name AS watched
+FROM
+ profiles p1
+ JOIN watch ON p1.userid=watch.watcher
+ JOIN profiles p2 ON p2.userid=watch.watched
+WHERE
+ FALSE
+ OR p1.userid = ?
+ OR p2.userid = ?
+ORDER BY
+ watcher,watched
+};
+$actions = $dbh->selectall_arrayref(
+ $query,
+ { Slice => {} },
+ ($userid, $userid),
+);
+print "<hr/><h2>Watch status</h2>\n";
+printf "<h3>Watchers of %s:</h3>\n", $login_name;
+foreach my $row (@$actions) {
+printf "%s<br/>\n", $row->{'watcher'} if $row->{'watched_id'} == $userid;
+}
+printf "<br/>\n";
+
+printf "<h3>Watched by %s:</h3>", $login_name;
+foreach my $row (@$actions) {
+printf "%s<br/>\n", $row->{'watched'} if $row->{'watcher_id'} == $userid;
+}
+printf "<br/>\n";
+
+
+$query = q{
+SELECT
+ at_time,
+ user_id,
+ profiles.login_name AS login_name,
+ class,
+ object_id,
+ field
+FROM
+ audit_log
+ LEFT JOIN profiles ON profiles.userid=audit_log.user_id
+WHERE
+ FALSE
+ OR audit_log.user_id=?
+ OR (audit_log.class = 'Bugzilla::User' AND audit_log.object_id=?)
+ORDER BY
+ at_time
+};
+my $audits = $dbh->selectall_arrayref(
+ $query,
+ { Slice => {} },
+ ($userid, $userid),
+);
+
+print qq{<hr/>
+<h2>Audit log</h2>
+<h3>Changes by ${login_name}:</h3>
+<table>
+ <tr>
+ <th>Timestamp</th>
+ <th>User</th>
+ <th>Class/ID</th>
+ <th>Field</th>
+ </tr>
+};
+foreach my $row (@$audits) {
+ next unless $row->{'user_id'} == $userid;
+ (my $message = qq{
+ <tr>
+ <td><tt>$row->{'at_time'}</tt></td>
+ <td><tt>$row->{'login_name'}</tt> ($row->{'user_id'})</td>
+ <td><tt>$row->{'class'}/$row->{'object_id'}</tt></td>
+ <td><tt>$row->{'field'}</tt></td>
+ </tr>
+ }) =~ s/^\t{1}//mg;
+ print $message;
+}
+
+print qq{
+</table>
+
+<h3>Changes to ${login_name}:</h3>
+<table>
+ <tr>
+ <th>Timestamp</th>
+ <th>User</th>
+ <th>Class/ID</th>
+ <th>Field</th>
+ </tr>
+};
+foreach my $row (@$audits) {
+ next unless $row->{'object_id'} == $userid && $row->{'class'} eq 'Bugzilla::User';
+ (my $message = qq{
+ <tr>
+ <td><tt>$row->{'at_time'}</tt></td>
+ <td><tt>$row->{'login_name'}</tt> ($row->{'user_id'})</td>
+ <td><tt>$row->{'class'}/$row->{'object_id'}</tt></td>
+ <td><tt>$row->{'field'}</tt></td>
+ </tr>
+ }) =~ s/^\t{1}//mg;
+ print $message;
+}
+print qq{
+</table>
+<hr/>
+Done.<br/>
+};
diff --git a/data/.gitignore b/data/.gitignore
new file mode 100644
index 000000000..daad48236
--- /dev/null
+++ b/data/.gitignore
@@ -0,0 +1,8 @@
+bugzilla-update.xml
+jobqueue.pl.pid
+mailer.testfile
+params
+mining/
+cached/
+template/
+extensions/
diff --git a/docs/en/pdf/Bugzilla-Guide.pdf b/docs/en/pdf/Bugzilla-Guide.pdf
new file mode 100644
index 000000000..79351a81e
--- /dev/null
+++ b/docs/en/pdf/Bugzilla-Guide.pdf
Binary files differ
diff --git a/duplicates.cgi b/duplicates.cgi
index c1bf036be..88159539d 100755
--- a/duplicates.cgi
+++ b/duplicates.cgi
@@ -30,7 +30,8 @@ use constant DEFAULTS => {
# reporting fixed bugs when they get newer versions of the software,
# but if the bug is determined to be erroneous, people will still
# keep reporting it, so we do need to show it here.
- fully_exclude_status => ['CLOSED'],
+ # gentoo bug 166593
+ fully_exclude_status => [], # ['CLOSED'],
partly_exclude_status => ['VERIFIED'],
except_resolution => ['INVALID', 'WONTFIX'],
changedsince => 7,
diff --git a/enter_bug.cgi b/enter_bug.cgi
index e03a88528..0a74af830 100755
--- a/enter_bug.cgi
+++ b/enter_bug.cgi
@@ -208,7 +208,7 @@ if ($cloned_bug_id) {
$vars->{'short_desc'} = $cloned_bug->short_desc;
$vars->{'bug_file_loc'} = $cloned_bug->bug_file_loc;
$vars->{'keywords'} = $cloned_bug->keywords;
- $vars->{'dependson'} = join (", ", $cloned_bug_id, @{$cloned_bug->dependson});
+ $vars->{'dependson'} = join (", ", @{$cloned_bug->dependson});
$vars->{'blocked'} = join (", ", @{$cloned_bug->blocked});
$vars->{'deadline'} = $cloned_bug->deadline;
$vars->{'estimated_time'} = $cloned_bug->estimated_time;
diff --git a/extensions/Gentoo/Config.pm b/extensions/Gentoo/Config.pm
new file mode 100644
index 000000000..c61020bde
--- /dev/null
+++ b/extensions/Gentoo/Config.pm
@@ -0,0 +1,5 @@
+package Bugzilla::Extension::Gentoo;
+use strict;
+use constant NAME => 'Gentoo';
+
+__PACKAGE__->NAME;
diff --git a/extensions/Gentoo/Extension.pm b/extensions/Gentoo/Extension.pm
new file mode 100644
index 000000000..936026678
--- /dev/null
+++ b/extensions/Gentoo/Extension.pm
@@ -0,0 +1,72 @@
+package Bugzilla::Extension::Gentoo;
+use strict;
+use base qw(Bugzilla::Extension);
+
+#use Bugzilla::Install::Filesystem qw(CGI_READ OWNER_EXECUTE WS_SERVE DIR_WS_SERVE);
+use Bugzilla::Constants qw(bz_locations);
+use Bugzilla::Error qw(ThrowUserError);
+
+use POSIX qw(uname);
+
+our $VERSION = '1.0';
+
+sub install_filesystem {
+ my ($self, $args) = @_;
+
+ my $dirs = $args->{'create_dirs'};
+ my $files = $args->{'files'};
+ my $recurse_dirs = $args->{'recurse_dirs'};
+ my $htaccess = $args->{'htaccess'};
+
+ my $datadir = bz_locations()->{'datadir'};
+
+ $dirs->{"${datadir}/cached"} = { perms => 0750 };
+
+ $files->{"zzz.txt"} = { perms => 0644 };
+ $files->{"robots-ssl.txt"} = { perms => 0644 };
+ $files->{"bots.html"} = { perms => 0644 };
+ $files->{"favicon.ico"} = { perms => 0644 };
+ $files->{"runstats.sh"} = { perms => 0700 };
+ $files->{"recompile.sh"} = { perms => 0700 };
+
+ $recurse_dirs->{"$datadir/cached"} = {
+ files => 0640, dirs => 0750
+ };
+
+ $recurse_dirs->{"images/ranks"} = {
+ files => 0640, dirs => 0750
+ };
+
+ $htaccess->{"$datadir/cached/.htaccess"} = {
+ perms => 0640, contents => <<EOT
+# Allow access to the cached stuff
+Allow from all
+EOT
+ };
+}
+
+sub template_before_create {
+ my ($self, $args) = @_;
+
+ my $config = $args->{config};
+ my $constants = $config->{CONSTANTS};
+
+ my %nodemap = (
+ 'yellowbishop' => 'bugs-web1',
+ 'yellowleg' => 'bugs-web2'
+ );
+
+ my $hostname = (uname())[1];
+ $constants->{GENTOO_NODE} = $nodemap{$hostname} ? $nodemap{$hostname} : "[$hostname]";
+ $constants->{GENTOO_APPEND_VERSION} = "+";
+}
+
+sub user_check_account_creation {
+ my ($self, $args) = @_;
+
+ my $login = $args->{login};
+
+ ThrowUserError('restricted_email_address', {addr => $login}) if $login =~ m/.+\@gentoo\.org$/;
+}
+
+__PACKAGE__->NAME;
diff --git a/extensions/Gentoo/template/en/default/hook/bug/create/create-after_cc_field.html.tmpl b/extensions/Gentoo/template/en/default/hook/bug/create/create-after_cc_field.html.tmpl
new file mode 100644
index 000000000..eb625e6bf
--- /dev/null
+++ b/extensions/Gentoo/template/en/default/hook/bug/create/create-after_cc_field.html.tmpl
@@ -0,0 +1,32 @@
+<tr>
+ <td>
+ <button onclick="add_arches_create()" type="button">Add arches:</button>
+ </td>
+ <td>
+ <select name="addarches" multiple size="5" class="arch-selector">
+ <option value="alpha@gentoo.org">ALPHA</option>
+ <option value="amd64@gentoo.org">AMD64</option>
+ <option value="arm@gentoo.org">ARM</option>
+ <option value="arm64@gentoo.org">ARM64</option>
+ <option value="ia64@gentoo.org">IA64</option>
+ <option value="ppc@gentoo.org">PPC</option>
+ <option value="ppc64@gentoo.org">PPC64</option>
+ <option value="x86@gentoo.org">X86</option>
+ <optgroup label="exp profiles">
+ <option value="hppa@gentoo.org">HPPA</option>
+ <option value="m68k@gentoo.org">M68K</option>
+ <option value="s390@gentoo.org">S390</option>
+ <option value="sh@gentoo.org">SH</option>
+ <option value="sparc@gentoo.org">SPARC</option>
+ </optgroup>
+ <optgroup label="pure ~arch">
+ <option value="mips@gentoo.org">MIPS</option>
+ <option value="riscv@gentoo">RISC-V</option>
+ </optgroup>
+ <optgroup label="other teams">
+ <option value="prefix@gentoo.org">Prefix</option>
+ <option value="release@gentoo.org">Release</option>
+ </optgroup>
+ </select>
+ </td>
+</div>
diff --git a/extensions/Gentoo/template/en/default/hook/bug/edit-after_cc_field.html.tmpl b/extensions/Gentoo/template/en/default/hook/bug/edit-after_cc_field.html.tmpl
new file mode 100644
index 000000000..71253cab4
--- /dev/null
+++ b/extensions/Gentoo/template/en/default/hook/bug/edit-after_cc_field.html.tmpl
@@ -0,0 +1,29 @@
+<br />
+<div>
+ <button onclick="add_arches_edit()" type="button">Add arches:</button>
+ <select name="addarches" multiple size="5" class="arch-selector">
+ <option value="alpha@gentoo.org">ALPHA</option>
+ <option value="amd64@gentoo.org">AMD64</option>
+ <option value="arm@gentoo.org">ARM</option>
+ <option value="arm64@gentoo.org">ARM64</option>
+ <option value="ia64@gentoo.org">IA64</option>
+ <option value="ppc@gentoo.org">PPC</option>
+ <option value="ppc64@gentoo.org">PPC64</option>
+ <option value="x86@gentoo.org">X86</option>
+ <optgroup label="exp profiles">
+ <option value="hppa@gentoo.org">HPPA</option>
+ <option value="m68k@gentoo.org">M68K</option>
+ <option value="s390@gentoo.org">S390</option>
+ <option value="sh@gentoo.org">SH</option>
+ <option value="sparc@gentoo.org">SPARC</option>
+ </optgroup>
+ <optgroup label="pure ~arch">
+ <option value="mips@gentoo.org">MIPS</option>
+ <option value="riscv@gentoo.org">RISC-V</option>
+ </optgroup>
+ <optgroup label="other teams">
+ <option value="prefix@gentoo.org">Prefix</option>
+ <option value="release@gentoo.org">Release</option>
+ </optgroup>
+ </select>
+</div>
diff --git a/extensions/Gentoo/template/en/default/hook/email/bugmail-start.txt.tmpl b/extensions/Gentoo/template/en/default/hook/email/bugmail-start.txt.tmpl
new file mode 100644
index 000000000..70815a5e1
--- /dev/null
+++ b/extensions/Gentoo/template/en/default/hook/email/bugmail-start.txt.tmpl
@@ -0,0 +1,2 @@
+DO NOT REPLY TO THIS EMAIL. Also, do not reply via email to the person
+whose email is mentioned below. To comment on this bug, please visit:
diff --git a/extensions/Gentoo/template/en/default/hook/global/header-additional_header.html.tmpl b/extensions/Gentoo/template/en/default/hook/global/header-additional_header.html.tmpl
new file mode 100644
index 000000000..a1a946dc7
--- /dev/null
+++ b/extensions/Gentoo/template/en/default/hook/global/header-additional_header.html.tmpl
@@ -0,0 +1 @@
+<script language="JavaScript" src="extensions/Gentoo/web/gentoo.js"></script> \ No newline at end of file
diff --git a/extensions/Gentoo/template/en/default/hook/global/header-after_body_start.html.tmpl b/extensions/Gentoo/template/en/default/hook/global/header-after_body_start.html.tmpl
new file mode 100644
index 000000000..d059fef48
--- /dev/null
+++ b/extensions/Gentoo/template/en/default/hook/global/header-after_body_start.html.tmpl
@@ -0,0 +1,18 @@
+<div id="gentoo-bar">
+ <a href="/" title="Go to the Gentoo Bugzilla homepage" class="home-link">
+ <img src="extensions/Gentoo/web/gentoo_org.png" alt="Gentoo Websites Logo" />
+ </a>
+
+ <div>
+ Go to:
+ <a href="https://www.gentoo.org/">Gentoo Home</a>
+ <a href="https://www.gentoo.org/support/documentation/">Documentation</a>
+ <a href="https://forums.gentoo.org/">Forums</a>
+ <a href="https://www.gentoo.org/get-involved/mailing-lists/">Lists</a>
+ <a href="/" class="active">Bugs</a>
+ <a href="https://planet.gentoo.org/">Planet</a>
+ <a href="https://www.gentoo.org/inside-gentoo/stores/">Store</a>
+ <a href="https://wiki.gentoo.org/">Wiki</a>
+ <strong><a href="https://www.gentoo.org/downloads/">Get Gentoo!</a></strong>
+ </div>
+</div>
diff --git a/extensions/Gentoo/template/en/default/hook/global/user-error-errors.html.tmpl b/extensions/Gentoo/template/en/default/hook/global/user-error-errors.html.tmpl
new file mode 100644
index 000000000..60001e69e
--- /dev/null
+++ b/extensions/Gentoo/template/en/default/hook/global/user-error-errors.html.tmpl
@@ -0,0 +1,5 @@
+[% IF error == "restricted_email_address" %]
+ [% title = "Using @gentoo.org email addresses have been restricted" %]
+ If you're a Gentoo developer, either contact your recruiter, [%
+ Param("maintainer") %] or join us in #gentoo-infra and ask us to change your mail address.
+[% END %]
diff --git a/extensions/Gentoo/template/en/default/hook/index-intro.html.tmpl b/extensions/Gentoo/template/en/default/hook/index-intro.html.tmpl
new file mode 100644
index 000000000..55eead5f5
--- /dev/null
+++ b/extensions/Gentoo/template/en/default/hook/index-intro.html.tmpl
@@ -0,0 +1,4 @@
+<p>Welcome to the Gentoo Bug tracking system.</p>
+<p>Before reporting bugs, please ensure that you read the <a href="https://www.gentoo.org/doc/en/bugzilla-howto.xml">Gentoo Bug Reporting Guide</a>.</p>
+<p>Do you run a spider, bot or some automated process against the Gentoo bugzilla? Please read the <a href="/bots.html">Bot Policy</a>.</p>
+<hr width="100%" />
diff --git a/extensions/Gentoo/template/en/default/hook/index-outro.html.tmpl b/extensions/Gentoo/template/en/default/hook/index-outro.html.tmpl
new file mode 100644
index 000000000..392906128
--- /dev/null
+++ b/extensions/Gentoo/template/en/default/hook/index-outro.html.tmpl
@@ -0,0 +1,3 @@
+<hr width="100%" />
+<p><a href="/buglist.cgi?query_format=advanced&chfieldfrom=-1D&chfieldto=Now&chfield=%5BBug+creation%5D&order=bugs.bug_id">View Bugs Reported in the last 24 hours</a> |
+<a href="/buglist.cgi?query_format=advanced&chfieldfrom=-12h&chfieldto=Now&chfield=%5BBug+creation%5D&order=bugs.bug_id">View Bugs Reported in the last 12 hours</a></p>
diff --git a/extensions/Gentoo/template/en/default/hook/pages/resolution.html.tmpl b/extensions/Gentoo/template/en/default/hook/pages/resolution.html.tmpl
new file mode 100644
index 000000000..69f299e7a
--- /dev/null
+++ b/extensions/Gentoo/template/en/default/hook/pages/resolution.html.tmpl
@@ -0,0 +1,51 @@
+ <dt>
+ [% display_value("resolution", "CANTFIX") FILTER html %]
+ </dt>
+ <dd>
+ The [% terms.bug %] might have been reproduced successfully, however
+ there is no viable fix for it. Either there is no fix at all, or
+ applying an existing fix would introduce new disadvantages that
+ outweigh its advantages. If a viable fix appears later, the [% terms.bug %] can
+ be reponened.
+ </dd>
+
+ <dt>
+ [% display_value("resolution", "NEEDINFO") FILTER html %]
+ </dt>
+ <dd>
+ The information contained in the [% terms.bug %] is not sufficient
+ to efficiently trace the origin of the problem. Usually, the person
+ closing the [% terms.bug %] with this resolution will also note what
+ further information is required. Once that information is provided,
+ the [% terms.bug %] can be reponened.
+ </dd>
+
+ <dt>
+ [% display_value("resolution", "TEST-REQUEST") FILTER html %]
+ </dt>
+ <dd>
+ A fix for the issue described in the [% terms.bug %] was provided.
+ The author requests users who were affected by the issue to test
+ the provided fix and report any success or failures.
+ When the problem is confirmedly fixed,
+ the [% terms.bug %] becomes [% display_value("resolution", "FIXED") FILTER html %],
+ if the problem still exists, it can be reopened.
+ </dd>
+
+ <dt>
+ [% display_value("resolution", "UPSTREAM") FILTER html %]
+ </dt>
+ <dd>
+ The [% terms.bug %] describes an issue that is should be fixed by
+ the upstream, or original author or provider of the subject of the [% terms.bug %].
+ Addressing the issue on the level of the Gentoo project ("downstream")
+ is either not possible or merely a workaround not worth implementing.
+ Once the issue was resolved by upstream, the [% terms.bug %] can
+ become [% display_value("resolution", "FIXED") FILTER html %].
+ </dd>
+ <dt>
+ [% display_value("resolution", "OBSOLETE") FILTER html %]
+ </dt>
+ <dd>
+ This [% terms.bug %] has been made obsolete.
+ </dd>
diff --git a/extensions/Gentoo/web/gentoo-header-bar-bg.png b/extensions/Gentoo/web/gentoo-header-bar-bg.png
new file mode 100644
index 000000000..9e7b3c2b0
--- /dev/null
+++ b/extensions/Gentoo/web/gentoo-header-bar-bg.png
Binary files differ
diff --git a/extensions/Gentoo/web/gentoo.js b/extensions/Gentoo/web/gentoo.js
new file mode 100644
index 000000000..044dac1cd
--- /dev/null
+++ b/extensions/Gentoo/web/gentoo.js
@@ -0,0 +1,50 @@
+function add_arches_edit() {
+ var i;
+
+ for(i=0;i<document.changeform.addarches.options.length;i++) {
+ if(document.changeform.addarches.options[i].selected) {
+ if(!document.changeform.newcc.value) {
+ document.changeform.newcc.value = document.changeform.addarches.options[i].value;
+ } else {
+ document.changeform.newcc.value = document.changeform.newcc.value + "," + document.changeform.addarches.options[i].value;
+ }
+ // Deselect the item...not necessary
+ document.changeform.addarches.options[i].selected = false;
+ }
+ }
+}
+
+function add_arches_create() {
+ var i;
+
+ for(i=0;i<document.Create.addarches.options.length;i++) {
+ if(document.Create.addarches.options[i].selected) {
+ if(!document.Create.cc.value) {
+ document.Create.cc.value = document.Create.addarches.options[i].value;
+ } else {
+ document.Create.cc.value = document.Create.cc.value + "," + document.Create.addarches.options[i].value;
+ }
+ // Deselect the item...not necessary
+ document.Create.addarches.options[i].selected = false;
+ }
+ }
+}
+
+function shorten_addressbar_url() {
+ if (history.state === null) {
+ var url = window.location;
+ if (url.pathname == '/show_bug.cgi') {
+ var searchRegex = /^[?]id=(\d+)(&(.*))?$/;
+ var m = url.search.match(searchRegex);
+ if (m !== null) {
+ var newUrl = m[1];
+ if (m[2])
+ newUrl += '?' + m[3];
+ newUrl += url.hash;
+ history.replaceState('1', '', newUrl);
+ }
+ }
+ }
+}
+
+shorten_addressbar_url();
diff --git a/extensions/Gentoo/web/gentoo_org.png b/extensions/Gentoo/web/gentoo_org.png
new file mode 100644
index 000000000..a790fe908
--- /dev/null
+++ b/extensions/Gentoo/web/gentoo_org.png
Binary files differ
diff --git a/extensions/InlineHistory/Config.pm b/extensions/InlineHistory/Config.pm
new file mode 100644
index 000000000..3834bd81d
--- /dev/null
+++ b/extensions/InlineHistory/Config.pm
@@ -0,0 +1,13 @@
+# 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::InlineHistory;
+use strict;
+
+use constant NAME => 'InlineHistory';
+
+__PACKAGE__->NAME;
diff --git a/extensions/InlineHistory/Extension.pm b/extensions/InlineHistory/Extension.pm
new file mode 100644
index 000000000..c88ff74ed
--- /dev/null
+++ b/extensions/InlineHistory/Extension.pm
@@ -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.
+
+package Bugzilla::Extension::InlineHistory;
+use strict;
+use base qw(Bugzilla::Extension);
+
+use Bugzilla::User::Setting;
+use Bugzilla::Constants;
+use Bugzilla::Attachment;
+
+our $VERSION = '1.6';
+
+# don't show inline history for bugs with lots of changes
+use constant MAXIMUM_ACTIVITY_COUNT => 500;
+
+# don't show really long values
+use constant MAXIMUM_VALUE_LENGTH => 256;
+
+sub template_before_create {
+ my ($self, $args) = @_;
+ $args->{config}->{FILTERS}->{ih_short_value} = sub {
+ my ($str) = @_;
+ return length($str) <= MAXIMUM_VALUE_LENGTH
+ ? $str
+ : substr($str, 0, MAXIMUM_VALUE_LENGTH - 3) . '...';
+ };
+}
+
+sub template_before_process {
+ my ($self, $args) = @_;
+ my $file = $args->{'file'};
+ my $vars = $args->{'vars'};
+
+ return if $file ne 'bug/edit.html.tmpl';
+
+ my $user = Bugzilla->user;
+ my $dbh = Bugzilla->dbh;
+ return unless $user->id && $user->settings->{'inline_history'}->{'value'} eq 'on';
+
+ # note: bug/edit.html.tmpl doesn't support multiple bugs
+ my $bug = exists $vars->{'bugs'} ? $vars->{'bugs'}[0] : $vars->{'bug'};
+ my $bug_id = $bug->id;
+
+ # build bug activity
+ my ($activity) = $bug->can('get_activity')
+ ? $bug->get_activity()
+ : Bugzilla::Bug::GetBugActivity($bug_id);
+ $activity = _add_duplicates($bug_id, $activity);
+
+ if (scalar @$activity > MAXIMUM_ACTIVITY_COUNT) {
+ $activity = [];
+ $vars->{'ih_activity'} = 0;
+ $vars->{'ih_activity_max'} = 1;
+ return;
+ }
+
+ # prime caches with objects already loaded
+ my %user_cache;
+ foreach my $comment (@{$bug->comments}) {
+ $user_cache{$comment->{author}->login} = $comment->{author};
+ }
+
+ my %attachment_cache;
+ foreach my $attachment (@{$bug->attachments}) {
+ $attachment_cache{$attachment->id} = $attachment;
+ }
+
+ # build a list of bugs we need to check visibility of, so we can check with a single query
+ my %visible_bug_ids;
+
+ # augment and tweak
+ foreach my $operation (@$activity) {
+ # make operation.who an object
+ $user_cache{$operation->{who}} ||= Bugzilla::User->new({ name => $operation->{who} });
+ $operation->{who} = $user_cache{$operation->{who}};
+
+ for (my $i = 0; $i < scalar(@{$operation->{changes}}); $i++) {
+ my $change = $operation->{changes}->[$i];
+
+ # make an attachment object
+ if ($change->{attachid}) {
+ $change->{attach} = $attachment_cache{$change->{attachid}};
+ }
+
+ # empty resolutions are displayed as --- by default
+ # make it explicit here to enable correct display of the change
+ if ($change->{fieldname} eq 'resolution') {
+ $change->{removed} = '---' if $change->{removed} eq '';
+ $change->{added} = '---' if $change->{added} eq '';
+ }
+
+ # make boolean fields true/false instead of 1/0
+ my ($table, $field) = ('bugs', $change->{fieldname});
+ if ($field =~ /^([^\.]+)\.(.+)$/) {
+ ($table, $field) = ($1, $2);
+ }
+ my $column = $dbh->bz_column_info($table, $field);
+ if ($column && $column->{TYPE} eq 'BOOLEAN') {
+ $change->{removed} = '';
+ $change->{added} = $change->{added} ? 'true' : 'false';
+ }
+
+ my $field_obj;
+ if ($change->{fieldname} =~ /^cf_/) {
+ $field_obj = Bugzilla::Field->new({ name => $change->{fieldname}, cache => 1 });
+ }
+
+ # identify buglist changes
+ if ($change->{fieldname} eq 'blocked' ||
+ $change->{fieldname} eq 'dependson' ||
+ $change->{fieldname} eq 'dupe' ||
+ ($field_obj && $field_obj->type == FIELD_TYPE_BUG_ID)
+ ) {
+ $change->{buglist} = 1;
+ foreach my $what (qw(removed added)) {
+ my @buglist = split(/[\s,]+/, $change->{$what});
+ foreach my $id (@buglist) {
+ if ($id && $id =~ /^\d+$/) {
+ $visible_bug_ids{$id} = 1;
+ }
+ }
+ }
+ }
+
+ # split multiple flag changes (must be processed last)
+ if ($change->{fieldname} eq 'flagtypes.name') {
+ my @added = split(/, /, $change->{added});
+ my @removed = split(/, /, $change->{removed});
+ next if scalar(@added) <= 1 && scalar(@removed) <= 1;
+ # remove current change
+ splice(@{$operation->{changes}}, $i, 1);
+ # restructure into added/removed for each flag
+ my %flags;
+ foreach my $added (@added) {
+ my ($value, $name) = $added =~ /^((.+).)$/;
+ $flags{$name}{added} = $value;
+ $flags{$name}{removed} |= '';
+ }
+ foreach my $removed (@removed) {
+ my ($value, $name) = $removed =~ /^((.+).)$/;
+ $flags{$name}{added} |= '';
+ $flags{$name}{removed} = $value;
+ }
+ # clone current change, modify and insert
+ foreach my $flag (sort keys %flags) {
+ my $flag_change = {};
+ foreach my $key (keys %$change) {
+ $flag_change->{$key} = $change->{$key};
+ }
+ $flag_change->{removed} = $flags{$flag}{removed};
+ $flag_change->{added} = $flags{$flag}{added};
+ splice(@{$operation->{changes}}, $i, 0, $flag_change);
+ }
+ $i--;
+ }
+ }
+ }
+
+ $user->visible_bugs([keys %visible_bug_ids]);
+
+ $vars->{'ih_activity'} = $activity;
+}
+
+sub _add_duplicates {
+ # insert 'is a dupe of this bug' comment to allow js to display
+ # as activity
+
+ my ($bug_id, $activity) = @_;
+
+ # we're ignoring pre-bugzilla 3.0 ".. has been marked as a duplicate .."
+ # comments because searching each comment's text is expensive. these
+ # legacy comments will not be visible at all in the bug's comment/activity
+ # stream. bug 928786 deals with migrating those comments to be stored as
+ # CMT_HAS_DUPE instead.
+
+ my $dbh = Bugzilla->dbh;
+ my $sth = $dbh->prepare("
+ SELECT profiles.login_name, " .
+ $dbh->sql_date_format('bug_when', '%Y.%m.%d %H:%i:%s') . ",
+ extra_data
+ FROM longdescs
+ INNER JOIN profiles ON profiles.userid = longdescs.who
+ WHERE bug_id = ? AND type = ?
+ ORDER BY bug_when
+ ");
+ $sth->execute($bug_id, CMT_HAS_DUPE);
+
+ while (my($who, $when, $dupe_id) = $sth->fetchrow_array) {
+ my $entry = {
+ 'when' => $when,
+ 'who' => $who,
+ 'changes' => [
+ {
+ 'removed' => '',
+ 'added' => $dupe_id,
+ 'attachid' => undef,
+ 'fieldname' => 'dupe',
+ 'dupe' => 1,
+ }
+ ],
+ };
+ push @$activity, $entry;
+ }
+
+ return [ sort { $a->{when} cmp $b->{when} } @$activity ];
+}
+
+sub install_before_final_checks {
+ my ($self, $args) = @_;
+ add_setting('inline_history', ['on', 'off'], 'off');
+}
+
+__PACKAGE__->NAME;
diff --git a/extensions/InlineHistory/README b/extensions/InlineHistory/README
new file mode 100644
index 000000000..fce7b5366
--- /dev/null
+++ b/extensions/InlineHistory/README
@@ -0,0 +1,10 @@
+InlineHistory inserts bug activity inline with the comments when viewing a bug.
+It was derived from the Bugzilla Tweaks Addon by Ehasn Akhgari.
+
+For technical and performance reasons it is only available to logged in users,
+and is enabled by a User Preference.
+
+It works with an unmodified install of Bugzilla 4.0, 4.2, and 4.4.
+
+If you have modified your show_bug template, the javascript in
+web/inline-history.js may need to be updated to suit your installation.
diff --git a/extensions/InlineHistory/template/en/default/hook/bug/comments-aftercomments.html.tmpl b/extensions/InlineHistory/template/en/default/hook/bug/comments-aftercomments.html.tmpl
new file mode 100644
index 000000000..6fb5d9810
--- /dev/null
+++ b/extensions/InlineHistory/template/en/default/hook/bug/comments-aftercomments.html.tmpl
@@ -0,0 +1,160 @@
+[%# 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.
+ #%]
+
+[% RETURN UNLESS ih_activity %]
+[%# this div exists to allow bugzilla-tweaks to detect when we're active %]
+<div id="inline-history-ext"></div>
+
+<script>
+ var ih_activity = new Array();
+ var ih_activity_flags = new Array();
+ var ih_activity_sort_order = '[% user.settings.comment_sort_order.value FILTER js %]';
+ [% FOREACH operation = ih_activity %]
+ var html = '';
+ [% has_cc = 0 %]
+ [% has_flag = 0 %]
+ [% changer_identity = operation.who.identity %]
+ [% changer_login = operation.who.login %]
+ [% change_date = operation.when FILTER time %]
+
+ [% FOREACH change = operation.changes %]
+ [%# track flag changes %]
+ [% IF change.fieldname == 'flagtypes.name' && change.added != '' %]
+ [% new_flags = change.added.split('[ ,]+') %]
+ [% FOREACH new_flag IN new_flags %]
+ var item = new Array(5);
+ item[0] = '[% changer_login FILTER js %]';
+ item[1] = '[% change_date FILTER js %]';
+ item[2] = '[% change.attachid FILTER js %]';
+ item[3] = '[% new_flag FILTER js %]';
+ item[4] = '[% changer_identity FILTER js %]';
+ ih_activity_flags.push(item);
+ [% has_flag = 1 %]
+ [% END %]
+ [% END %]
+
+ [%# wrap CC changes in a span for toggling visibility %]
+ [% IF change.fieldname == 'cc' %]
+ html += '<span class="ih_cc">';
+ [% has_cc = 1 %]
+ [% END %]
+
+ [%# make attachment changes better %]
+ [% IF change.attachid %]
+ html += '<a '
+ + 'href="attachment.cgi?id=[% change.attachid FILTER none %]&amp;action=edit" '
+ + 'title="[% change.attach.description FILTER html FILTER js %]" '
+ + 'class="[% "bz_obsolete" IF change.attach.isobsolete %]"'
+ + '>Attachment #[% change.attachid FILTER none %]</a> - ';
+ [% END %]
+
+ [%# buglists need to be displayed differently, as we shouldn't use strike-out %]
+ [% IF change.buglist %]
+ [% IF change.dupe %]
+ [% label = 'Duplicate of this ' _ terms.bug %]
+ [% ELSE %]
+ [% label = field_descs.${change.fieldname} %]
+ [% END %]
+ [% IF change.added != '' %]
+ html += '[% label FILTER js %]: ';
+ [% PROCESS add_change value = change.added %]
+ [% END %]
+ [% IF change.removed != '' %]
+ [% "html += '<br>';" IF change.added != '' %]
+ html += 'No longer [% label FILTER lcfirst FILTER js %]: ';
+ [% PROCESS add_change value = change.removed %]
+ [% END %]
+ [% ELSE %]
+ [% IF change.fieldname == 'longdescs.isprivate' %]
+ [%# reference the comment that was made private/public in the field label %]
+ html += '<a href="#c[% change.comment.count FILTER js %]">'
+ + 'Comment [% change.comment.count FILTER js %]</a> is private: ';
+ [% ELSE %]
+ [%# normal label %]
+ html += '[% field_descs.${change.fieldname} FILTER js %]: ';
+ [% END %]
+ [% IF change.removed != '' %]
+ [% IF change.added == '' %]
+ html += '<span class="ih_deleted">';
+ [% END %]
+ [% PROCESS add_change value = change.removed %]
+ [% IF change.added == '' %]
+ html += '</span>';
+ [% ELSE %]
+ html += ' &rarr; ';
+ [% END %]
+ [% END %]
+ [% PROCESS add_change value = change.added %]
+ [% END %]
+ [% "html += '<br>';" UNLESS loop.last %]
+
+ [% IF change.fieldname == 'cc' %]
+ html += '</span>';
+ [% END %]
+ [% END %]
+
+ [% changer_id = operation.who.id %]
+ [% UNLESS user_cache.$changer_id %]
+ [% user_cache.$changer_id = BLOCK %]
+ [% INCLUDE global/user.html.tmpl who = operation.who %]
+ [% END %]
+ [% END %]
+
+ var user_image = '
+ [%~ who = operation.who %]
+ [% Hook.process('user-image', 'bug/comments.html.tmpl') FILTER js %]';
+
+ var item = new Array(7);
+ item[0] = '[% changer_login FILTER js %]';
+ item[1] = '[% change_date FILTER js %]';
+ item[2] = html;
+ item[3] = '<div class="bz_comment_head">'
+ + '<span class="bz_comment_user">'
+ + user_image
+ + '[% user_cache.$changer_id FILTER js %]'
+ + '</span>'
+ + '<span class="bz_comment_time"> ' + item[1] + ' </span>'
+ + '</div>';
+ item[4] = [% IF has_cc && (operation.changes.size == 1) %]true[% ELSE %]false[% END %];
+ item[5] = [% IF change.dupe %][% change.added FILTER js %][% ELSE %]0[% END %];
+ item[6] = [% IF has_flag %]true[% ELSE %]false[% END %];
+ ih_activity[[% loop.index %]] = item;
+ [% END %]
+ inline_history.init();
+</script>
+
+[% BLOCK add_change %]
+ html += '[%~%]
+ [% IF change.fieldname == 'estimated_time' ||
+ change.fieldname == 'remaining_time' ||
+ change.fieldname == 'work_time' %]
+ [% PROCESS formattimeunit time_unit = value FILTER html FILTER js %]
+ [% ELSIF change.buglist %]
+ [% value FILTER bug_list_link FILTER js %]
+ [% ELSIF change.fieldname == 'bug_file_loc' %]
+ [%~%]<a href="[% value FILTER html FILTER js %]" target="_blank"
+ [%~ ' onclick="return inline_history.confirmUnsafeUrl(this.href)"'
+ UNLESS is_safe_url(value) %]>
+ [%~%][% value FILTER ih_short_value FILTER html FILTER js %]</a>
+ [% ELSIF change.fieldname == 'see_also' %]
+ [% FOREACH see_also = value.split(', ') %]
+ [%~%]<a href="[% see_also FILTER html FILTER js %]" target="_blank">
+ [%~%][% see_also FILTER html FILTER js %]</a>
+ [%- ", " IF NOT loop.last %]
+ [% END %]
+ [% ELSIF change.fieldname == 'assigned_to' ||
+ change.fieldname == 'reporter' ||
+ change.fieldname == 'qa_contact' ||
+ change.fieldname == 'cc' ||
+ change.fieldname == 'flagtypes.name' %]
+ [% value FILTER email FILTER js %]
+ [% ELSE %]
+ [% value FILTER ih_short_value FILTER html FILTER js %]
+ [% END %]
+ [%~ %]';
+[% END %]
diff --git a/extensions/InlineHistory/template/en/default/hook/bug/comments-comment_banner.html.tmpl b/extensions/InlineHistory/template/en/default/hook/bug/comments-comment_banner.html.tmpl
new file mode 100644
index 000000000..133005f4f
--- /dev/null
+++ b/extensions/InlineHistory/template/en/default/hook/bug/comments-comment_banner.html.tmpl
@@ -0,0 +1,13 @@
+[%# This Source Code Form is subject to the terms of the Mozilla Public
+ # License, v. 2.0. If a copy of the MPL was not distributed with this
+ # file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ #
+ # This Source Code Form is "Incompatible With Secondary Licenses", as
+ # defined by the Mozilla Public License, v. 2.0.
+ #%]
+
+[% IF ih_activity_max %]
+<p>
+ <i>This [% terms.bug %] contains too many changes to be displayed inline.</i>
+</p>
+[% END %]
diff --git a/extensions/InlineHistory/template/en/default/hook/bug/show-header-end.html.tmpl b/extensions/InlineHistory/template/en/default/hook/bug/show-header-end.html.tmpl
new file mode 100644
index 000000000..7e54b8380
--- /dev/null
+++ b/extensions/InlineHistory/template/en/default/hook/bug/show-header-end.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.
+ #%]
+
+[% IF user.id && user.settings.inline_history.value == "on" %]
+ [% style_urls.push('extensions/InlineHistory/web/style.css') %]
+ [% javascript_urls.push('extensions/InlineHistory/web/inline-history.js') %]
+[% END %]
diff --git a/extensions/InlineHistory/template/en/default/hook/global/setting-descs-settings.none.tmpl b/extensions/InlineHistory/template/en/default/hook/global/setting-descs-settings.none.tmpl
new file mode 100644
index 000000000..e1ff4c0f6
--- /dev/null
+++ b/extensions/InlineHistory/template/en/default/hook/global/setting-descs-settings.none.tmpl
@@ -0,0 +1,11 @@
+[%# 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.
+ #%]
+
+[%
+ setting_descs.inline_history = "When viewing a $terms.bug, show all $terms.bug activity",
+%]
diff --git a/extensions/InlineHistory/web/inline-history.js b/extensions/InlineHistory/web/inline-history.js
new file mode 100644
index 000000000..c59c2932c
--- /dev/null
+++ b/extensions/InlineHistory/web/inline-history.js
@@ -0,0 +1,397 @@
+/* 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. */
+
+var inline_history = {
+ _ccDivs: null,
+ _hasAttachmentFlags: false,
+ _hasBugFlags: false,
+
+ init: function() {
+ Dom = YAHOO.util.Dom;
+
+ var reDuplicate = /^\*\*\* \S+ \d+ has been marked as a duplicate of this/;
+ var reBugId = /show_bug\.cgi\?id=(\d+)/;
+ var reHours = /Additional hours worked: \d+\.\d+/;
+
+ var comments = Dom.getElementsByClassName("bz_comment", 'div', 'comments');
+ for (var i = 1, il = comments.length; i < il; i++) {
+ // remove 'has been marked as a duplicate of this bug' comments
+ var textDiv = Dom.getElementsByClassName('bz_comment_text', 'pre', comments[i]);
+ if (textDiv) {
+ var match = reDuplicate.exec(textDiv[0].textContent || textDiv[0].innerText);
+ if (match) {
+ // grab the comment and bug number from the element
+ var comment = comments[i];
+ var number = comment.id.substr(1);
+ var time = this.trim(Dom.getElementsByClassName('bz_comment_time', 'span', comment)[0].innerHTML);
+ var dupeId = 0;
+ match = reBugId.exec(Dom.get('comment_text_' + number).innerHTML);
+ if (match)
+ dupeId = match[1];
+ // remove the element
+ comment.parentNode.removeChild(comment);
+ // update the html for the history item to include the comment number
+ if (dupeId == 0)
+ continue;
+ for (var j = 0, jl = ih_activity.length; j < jl; j++) {
+ var item = ih_activity[j];
+ if (item[5] == dupeId && item[1] == time) {
+ // insert comment number and link into the header
+ item[3] = item[3].substr(0, item[3].length - 6) // remove trailing </div>
+ // add comment number
+ + '<span class="bz_comment_number" id="c' + number + '">'
+ + '<a href="#c' + number + '">Comment ' + number + '</a>'
+ + '</span>'
+ + '</div>';
+ break;
+ }
+ }
+ }
+ }
+
+ // remove 'Additional hours worked: ' comments
+ var commentNodes = comments[i].childNodes;
+ for (var j = 0, jl = commentNodes.length; j < jl; j++) {
+ if (reHours.exec(commentNodes[j].textContent)) {
+ comments[i].removeChild(commentNodes[j]);
+ // Remove the <br> before it
+ comments[i].removeChild(commentNodes[j-1]);
+ break;
+ }
+ }
+ }
+
+ // ensure new items are placed immediately after the last comment
+ if (!comments.length) return;
+ var lastCommentDiv = comments[comments.length - 1];
+
+ // insert activity into the correct location
+ var commentTimes = Dom.getElementsByClassName('bz_comment_time', 'span', 'comments');
+ for (var i = 0, il = ih_activity.length; i < il; i++) {
+ var item = ih_activity[i];
+ // item[0] : who
+ // item[1] : when
+ // item[2] : change html
+ // item[3] : header html
+ // item[4] : bool; cc-only
+ // item[5] : int; dupe bug id (or 0)
+ // item[6] : bool; is flag
+ var user = item[0];
+ var time = item[1];
+
+ var reachedEnd = false;
+ var start_index = ih_activity_sort_order == 'newest_to_oldest_desc_first' ? 1 : 0;
+ for (var j = start_index, jl = commentTimes.length; j < jl; j++) {
+ var commentHead = commentTimes[j].parentNode;
+ var mainUser = Dom.getElementsByClassName('email', 'a', commentHead)[0].href.substr(7);
+ var text = commentTimes[j].textContent || commentTimes[j].innerText;
+ var mainTime = this.trim(text);
+
+ if (ih_activity_sort_order == 'oldest_to_newest' ? time > mainTime : time < mainTime) {
+ if (j < commentTimes.length - 1) {
+ continue;
+ } else {
+ reachedEnd = true;
+ }
+ }
+
+ var inline = (mainUser == user && time == mainTime);
+ var currentDiv = document.createElement("div");
+
+ // place ih_cc class on parent container if it's the only child
+ var containerClass = '';
+ if (item[4]) {
+ item[2] = item[2].replace('"ih_cc"', '""');
+ containerClass = 'ih_cc';
+ }
+
+ if (inline) {
+ // assume that the change was made by the same user
+ commentHead.parentNode.appendChild(currentDiv);
+ currentDiv.innerHTML = item[2];
+ Dom.addClass(currentDiv, 'ih_inlinehistory');
+ Dom.addClass(currentDiv, containerClass);
+ if (item[6])
+ this.setFlagChangeID(item, commentHead.parentNode.id);
+
+ } else {
+ // the change was made by another user
+ if (!reachedEnd) {
+ var parentDiv = commentHead.parentNode;
+ var previous = this.previousElementSibling(parentDiv);
+ if (previous && previous.className.indexOf("ih_history") >= 0) {
+ currentDiv = this.previousElementSibling(parentDiv);
+ } else {
+ parentDiv.parentNode.insertBefore(currentDiv, parentDiv);
+ }
+ } else {
+ var parentDiv = commentHead.parentNode;
+ var next = this.nextElementSibling(parentDiv);
+ if (next && next.className.indexOf("ih_history") >= 0) {
+ currentDiv = this.nextElementSibling(parentDiv);
+ } else {
+ lastCommentDiv.parentNode.insertBefore(currentDiv, lastCommentDiv.nextSibling);
+ }
+ }
+
+ var itemContainer = document.createElement('div');
+ itemContainer.className = 'ih_history_item ' + containerClass;
+ itemContainer.id = 'h' + i;
+ itemContainer.innerHTML = item[3] + '<div class="ih_history_change">' + item[2] + '</div>';
+
+ if (ih_activity_sort_order == 'oldest_to_newest') {
+ currentDiv.appendChild(itemContainer);
+ } else {
+ currentDiv.insertBefore(itemContainer, currentDiv.firstChild);
+ }
+ currentDiv.setAttribute("class", "bz_comment ih_history");
+ if (item[6])
+ this.setFlagChangeID(item, 'h' + i);
+ }
+ break;
+ }
+ }
+
+ // find comment blocks which only contain cc changes, shift the ih_cc
+ var historyDivs = Dom.getElementsByClassName('ih_history', 'div', 'comments');
+ for (var i = 0, il = historyDivs.length; i < il; i++) {
+ var historyDiv = historyDivs[i];
+ var itemDivs = Dom.getElementsByClassName('ih_history_item', 'div', historyDiv);
+ var ccOnly = true;
+ for (var j = 0, jl = itemDivs.length; j < jl; j++) {
+ if (!Dom.hasClass(itemDivs[j], 'ih_cc')) {
+ ccOnly = false;
+ break;
+ }
+ }
+ if (ccOnly) {
+ for (var j = 0, jl = itemDivs.length; j < jl; j++) {
+ Dom.removeClass(itemDivs[j], 'ih_cc');
+ }
+ Dom.addClass(historyDiv, 'ih_cc');
+ }
+ }
+
+ if (this._hasAttachmentFlags)
+ this.linkAttachmentFlags();
+ if (this._hasBugFlags)
+ this.linkBugFlags();
+
+ ih_activity = undefined;
+ ih_activity_flags = undefined;
+
+ this._ccDivs = Dom.getElementsByClassName('ih_cc', '', 'comments');
+ this.hideCC();
+ YAHOO.util.Event.onDOMReady(this.addCCtoggler);
+ },
+
+ setFlagChangeID: function(changeItem, id) {
+ // put the ID for the change into ih_activity_flags
+ for (var i = 0, il = ih_activity_flags.length; i < il; i++) {
+ var flagItem = ih_activity_flags[i];
+ // flagItem[0] : who.login
+ // flagItem[1] : when
+ // flagItem[2] : attach id
+ // flagItem[3] : flag
+ // flagItem[4] : who.identity
+ // flagItem[5] : change div id
+ if (flagItem[0] == changeItem[0] && flagItem[1] == changeItem[1]) {
+ // store the div
+ flagItem[5] = id;
+ // tag that we have flags to process
+ if (flagItem[2]) {
+ this._hasAttachmentFlags = true;
+ } else {
+ this._hasBugFlags = true;
+ }
+ // don't break as there may be multiple flag changes at once
+ }
+ }
+ },
+
+ linkAttachmentFlags: function() {
+ var rows = Dom.get('attachment_table').getElementsByTagName('tr');
+ for (var i = 0, il = rows.length; i < il; i++) {
+
+ // deal with attachments with flags only
+ var tr = rows[i];
+ if (!tr.id || tr.id == 'a0')
+ continue;
+ var attachFlagTd = Dom.getElementsByClassName('bz_attach_flags', 'td', tr);
+ if (attachFlagTd.length == 0)
+ continue;
+ attachFlagTd = attachFlagTd[0];
+
+ // get the attachment id
+ var attachId = 0;
+ var anchors = tr.getElementsByTagName('a');
+ for (var j = 0, jl = anchors.length; j < jl; j++) {
+ var match = anchors[j].href.match(/attachment\.cgi\?id=(\d+)/);
+ if (match) {
+ attachId = match[1];
+ break;
+ }
+ }
+ if (!attachId)
+ continue;
+
+ var html = '';
+
+ // there may be multiple flags, split by <br>
+ var attachFlags = attachFlagTd.innerHTML.split('<br>');
+ for (var j = 0, jl = attachFlags.length; j < jl; j++) {
+ var match = attachFlags[j].match(/^\s*(<span.+\/span>):([^\?\-\+]+[\?\-\+])([\s\S]*)/);
+ if (!match) continue;
+ var setterSpan = match[1];
+ var flag = this.trim(match[2].replace('\u2011', '-', 'g'));
+ var requestee = this.trim(match[3]);
+ var requesteeLogin = '';
+
+ match = setterSpan.match(/title="([^"]+)"/);
+ if (!match) continue;
+ var setterIdentity = this.htmlDecode(match[1]);
+
+ if (requestee) {
+ match = requestee.match(/title="([^"]+)"/);
+ if (!match) continue;
+ requesteeLogin = this.htmlDecode(match[1]);
+ match = requesteeLogin.match(/<([^>]+)>/);
+ if (match)
+ requesteeLogin = match[1];
+ }
+
+ var flagValue = requestee ? flag + '(' + requesteeLogin + ')' : flag;
+ // find the id for this change
+ var found = false;
+ for (var k = 0, kl = ih_activity_flags.length; k < kl; k++) {
+ flagItem = ih_activity_flags[k];
+ if (
+ flagItem[2] == attachId
+ && flagItem[3] == flagValue
+ && flagItem[4] == setterIdentity
+ ) {
+ html +=
+ setterSpan + ': '
+ + '<a href="#' + flagItem[5] + '">' + flag + '</a> '
+ + requestee + '<br>';
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ // something went wrong, insert the flag unlinked
+ html += attachFlags[j] + '<br>';
+ }
+ }
+
+ if (html)
+ attachFlagTd.innerHTML = html;
+ }
+ },
+
+ linkBugFlags: function() {
+ var flags = Dom.get('flags');
+ if (!flags) return;
+ var rows = flags.getElementsByTagName('tr');
+ for (var i = 0, il = rows.length; i < il; i++) {
+ var cells = rows[i].getElementsByTagName('td');
+ if (!cells[1]) continue;
+
+ var match = cells[0].innerHTML.match(/title="([^"]+)"/);
+ if (!match) continue;
+ var setterIdentity = this.htmlDecode(match[1]);
+
+ var flagValue = cells[2].getElementsByTagName('select');
+ if (!flagValue.length) continue;
+ flagValue = flagValue[0].value;
+
+ var flagLabel = cells[1].getElementsByTagName('label');
+ if (!flagLabel.length) continue;
+ flagLabel = flagLabel[0];
+ var flagName = this.trim(flagLabel.innerHTML).replace('\u2011', '-', 'g');
+
+ for (var j = 0, jl = ih_activity_flags.length; j < jl; j++) {
+ flagItem = ih_activity_flags[j];
+ if (
+ !flagItem[2]
+ && flagItem[3] == flagName + flagValue
+ && flagItem[4] == setterIdentity
+ ) {
+ flagLabel.innerHTML =
+ '<a href="#' + flagItem[5] + '">' + flagName + '</a>';
+ break;
+ }
+ }
+ }
+ },
+
+ hideCC: function() {
+ Dom.addClass(this._ccDivs, 'ih_hidden');
+ },
+
+ showCC: function() {
+ Dom.removeClass(this._ccDivs, 'ih_hidden');
+ },
+
+ addCCtoggler: function() {
+ var ul = Dom.getElementsByClassName('bz_collapse_expand_comments');
+ if (ul.length == 0)
+ return;
+ ul = ul[0];
+ var a = document.createElement('a');
+ a.href = 'javascript:void(0)';
+ a.id = 'ih_toggle_cc';
+ YAHOO.util.Event.addListener(a, 'click', function(e) {
+ if (Dom.get('ih_toggle_cc').innerHTML == 'Show CC Changes') {
+ a.innerHTML = 'Hide CC Changes';
+ inline_history.showCC();
+ } else {
+ a.innerHTML = 'Show CC Changes';
+ inline_history.hideCC();
+ }
+ });
+ a.innerHTML = 'Show CC Changes';
+ var li = document.createElement('li');
+ li.appendChild(a);
+ ul.appendChild(li);
+ },
+
+ confirmUnsafeUrl: function(url) {
+ return confirm(
+ 'This is considered an unsafe URL and could possibly be harmful.\n'
+ + 'The full URL is:\n\n' + url + '\n\nContinue?');
+ },
+
+ previousElementSibling: function(el) {
+ if (el.previousElementSibling)
+ return el.previousElementSibling;
+ while (el = el.previousSibling) {
+ if (el.nodeType == 1)
+ return el;
+ }
+ },
+
+ nextElementSibling: function(el) {
+ if (el.nextElementSibling)
+ return el.nextElementSibling;
+ while (el = el.nextSibling) {
+ if (el.nodeType == 1)
+ return el;
+ }
+ },
+
+ htmlDecode: function(v) {
+ if (!v.match(/&/)) return v;
+ var e = document.createElement('textarea');
+ e.innerHTML = v;
+ return e.value;
+ },
+
+ trim: function(s) {
+ return s.replace(/^\s+|\s+$/g, '');
+ }
+}
diff --git a/extensions/InlineHistory/web/style.css b/extensions/InlineHistory/web/style.css
new file mode 100644
index 000000000..af76eba82
--- /dev/null
+++ b/extensions/InlineHistory/web/style.css
@@ -0,0 +1,35 @@
+/* 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. */
+
+.ih_history {
+ background: none !important;
+ color: #444;
+}
+
+.ih_inlinehistory {
+ font-weight: normal;
+ font-size: small;
+ color: #444;
+ border-top: 1px dotted #C8C8BA;
+ padding-top: 5px;
+}
+
+.bz_comment.ih_history {
+ padding: 5px 5px 0px 5px
+}
+
+.ih_history_item {
+ margin-bottom: 5px;
+}
+
+.ih_hidden {
+ display: none;
+}
+
+.ih_deleted {
+ text-decoration: line-through;
+}
diff --git a/extensions/MoreBugUrl/Extension.pm b/extensions/MoreBugUrl/Extension.pm
index 18507f8d1..0a4223e19 100644
--- a/extensions/MoreBugUrl/Extension.pm
+++ b/extensions/MoreBugUrl/Extension.pm
@@ -22,6 +22,7 @@ use constant MORE_SUB_CLASSES => qw(
Bugzilla::Extension::MoreBugUrl::PHP
Bugzilla::Extension::MoreBugUrl::Redmine
Bugzilla::Extension::MoreBugUrl::Savane
+ Bugzilla::Extension::MoreBugUrl::Phabricator
);
# We need to update bug_see_also table because both
diff --git a/extensions/MoreBugUrl/lib/Phabricator.pm b/extensions/MoreBugUrl/lib/Phabricator.pm
new file mode 100644
index 000000000..818d9af8f
--- /dev/null
+++ b/extensions/MoreBugUrl/lib/Phabricator.pm
@@ -0,0 +1,41 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+# This Source Code Form is "Incompatible With Secondary Licenses", as
+# defined by the Mozilla Public License, v. 2.0.
+
+package Bugzilla::Extension::MoreBugUrl::Phabricator;
+
+use 5.10.1;
+use strict;
+use warnings;
+
+use parent qw(Bugzilla::BugUrl);
+
+###############################
+#### Methods ####
+###############################
+
+sub should_handle {
+ my ($class, $uri) = @_;
+ return ($uri->path =~ m|^/T\d+$|) ? 1 : 0;
+}
+
+sub _check_value {
+ my $class = shift;
+
+ my $uri = $class->SUPER::_check_value(@_);
+
+ # Phabricator URLs have only one form:
+ # http://example.com/T111
+
+ # Make sure there are no query parameters.
+ $uri->query(undef);
+ # And remove any # part if there is one.
+ $uri->fragment(undef);
+
+ return $uri;
+}
+
+1;
diff --git a/extensions/MoreBugUrl/lib/Savane.pm b/extensions/MoreBugUrl/lib/Savane.pm
index efda1fa4f..5b35bbf7d 100644
--- a/extensions/MoreBugUrl/lib/Savane.pm
+++ b/extensions/MoreBugUrl/lib/Savane.pm
@@ -19,7 +19,12 @@ use parent qw(Bugzilla::BugUrl);
sub should_handle {
my ($class, $uri) = @_;
- return ($uri->as_string =~ m|/bugs/(index\.php)?\?\d+$|) ? 1 : 0;
+ # Savane URLs look like the following (the index.php is optional):
+ # https://savannah.gnu.org/bugs/index.php?107657
+ # https://savannah.gnu.org/patch/index.php?107657
+ # https://savannah.gnu.org/support/index.php?107657
+ # https://savannah.gnu.org/task/index.php?107657
+ return ($uri->as_string =~ m|/(bugs\|patch\|support\|task)/(index\.php)?\?\d+$|) ? 1 : 0;
}
sub _check_value {
@@ -27,10 +32,6 @@ sub _check_value {
my $uri = $class->SUPER::_check_value(@_);
- # Savane URLs have only two forms:
- # http://gna.org/bugs/index.php?12345
- # http://gna.org/bugs/?12345
-
# And remove any # part if there is one.
$uri->fragment(undef);
diff --git a/extensions/MoreBugUrl/template/en/default/hook/global/user-error-bug_url_invalid_tracker.html.tmpl b/extensions/MoreBugUrl/template/en/default/hook/global/user-error-bug_url_invalid_tracker.html.tmpl
index 7e544ef21..2ac6f89a5 100644
--- a/extensions/MoreBugUrl/template/en/default/hook/global/user-error-bug_url_invalid_tracker.html.tmpl
+++ b/extensions/MoreBugUrl/template/en/default/hook/global/user-error-bug_url_invalid_tracker.html.tmpl
@@ -14,3 +14,4 @@
<li>A b[% %]ug on b[% %]ugs.php.net.</li>
<li>An issue in a Redmine installation.</li>
<li>A b[% %]ug in a Savane installation.</li>
+<li>A task in a Phabricator installation.</li>
diff --git a/extensions/SecureMail/Config.pm b/extensions/SecureMail/Config.pm
new file mode 100644
index 000000000..f1975c1c1
--- /dev/null
+++ b/extensions/SecureMail/Config.pm
@@ -0,0 +1,49 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# 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 SecureMail Extension
+#
+# The Initial Developer of the Original Code is Mozilla.
+# Portions created by Mozilla are Copyright (C) 2008 Mozilla Corporation.
+# All Rights Reserved.
+#
+# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
+# Gervase Markham <gerv@gerv.net>
+
+package Bugzilla::Extension::SecureMail;
+use strict;
+
+use constant NAME => 'SecureMail';
+
+use constant REQUIRED_MODULES => [
+ {
+ package => 'Crypt-OpenPGP',
+ module => 'Crypt::OpenPGP',
+ # 1.02 added the ability for new() to take KeyRing objects for the
+ # PubRing argument.
+ version => '1.02',
+ # 1.04 hangs - https://rt.cpan.org/Public/Bug/Display.html?id=68018
+ # blacklist => [ '1.04' ],
+ },
+ {
+ package => 'Crypt-SMIME',
+ module => 'Crypt::SMIME',
+ version => 0,
+ },
+ {
+ package => 'HTML-Tree',
+ module => 'HTML::Tree',
+ version => 0,
+ }
+];
+
+__PACKAGE__->NAME;
diff --git a/extensions/SecureMail/Extension.pm b/extensions/SecureMail/Extension.pm
new file mode 100644
index 000000000..492ce0cb6
--- /dev/null
+++ b/extensions/SecureMail/Extension.pm
@@ -0,0 +1,659 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# 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 SecureMail Extension
+#
+# The Initial Developer of the Original Code is the Mozilla Foundation.
+# Portions created by Mozilla are Copyright (C) 2008 Mozilla Foundation.
+# All Rights Reserved.
+#
+# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
+# Gervase Markham <gerv@gerv.net>
+
+package Bugzilla::Extension::SecureMail;
+use strict;
+use base qw(Bugzilla::Extension);
+
+use Bugzilla::Attachment;
+use Bugzilla::Comment;
+use Bugzilla::Group;
+use Bugzilla::Object;
+use Bugzilla::User;
+use Bugzilla::Util qw(correct_urlbase trim trick_taint is_7bit_clean);
+use Bugzilla::Error;
+use Bugzilla::Mailer;
+
+use Crypt::OpenPGP::Armour;
+use Crypt::OpenPGP::KeyRing;
+use Crypt::OpenPGP;
+use Crypt::SMIME;
+use Email::MIME::ContentType qw(parse_content_type);
+use Encode;
+use HTML::Tree;
+
+our $VERSION = '0.5';
+
+use constant SECURE_NONE => 0;
+use constant SECURE_BODY => 1;
+use constant SECURE_ALL => 2;
+
+##############################################################################
+# Creating new columns
+#
+# secure_mail boolean in the 'groups' table - whether to send secure mail
+# public_key text in the 'profiles' table - stores public key
+##############################################################################
+sub install_update_db {
+ my ($self, $args) = @_;
+
+ my $dbh = Bugzilla->dbh;
+ $dbh->bz_add_column('groups', 'secure_mail',
+ {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 0});
+ $dbh->bz_add_column('profiles', 'public_key', { TYPE => 'LONGTEXT' });
+}
+
+##############################################################################
+# Maintaining new columns
+##############################################################################
+
+BEGIN {
+ *Bugzilla::Group::secure_mail = \&_group_secure_mail;
+ *Bugzilla::User::public_key = \&_user_public_key;
+}
+
+sub _group_secure_mail { return $_[0]->{'secure_mail'}; }
+
+# We want to lazy-load the public_key.
+sub _user_public_key {
+ my $self = shift;
+ if (!exists $self->{public_key}) {
+ ($self->{public_key}) = Bugzilla->dbh->selectrow_array(
+ "SELECT public_key FROM profiles WHERE userid = ?",
+ undef,
+ $self->id
+ );
+ }
+ return $self->{public_key};
+}
+
+# Make sure generic functions know about the additional fields in the user
+# and group objects.
+sub object_columns {
+ my ($self, $args) = @_;
+ my $class = $args->{'class'};
+ my $columns = $args->{'columns'};
+
+ if ($class->isa('Bugzilla::Group')) {
+ push(@$columns, 'secure_mail');
+ }
+}
+
+# Plug appropriate validators so we can check the validity of the two
+# fields created by this extension, when new values are submitted.
+sub object_validators {
+ my ($self, $args) = @_;
+ my %args = %{ $args };
+ my ($invocant, $validators) = @args{qw(class validators)};
+
+ if ($invocant->isa('Bugzilla::Group')) {
+ $validators->{'secure_mail'} = \&Bugzilla::Object::check_boolean;
+ }
+ elsif ($invocant->isa('Bugzilla::User')) {
+ $validators->{'public_key'} = sub {
+ my ($self, $value) = @_;
+ $value = trim($value) || '';
+
+ return $value if $value eq '';
+
+ if ($value =~ /PUBLIC KEY/) {
+ # PGP keys must be ASCII-armoured.
+ if (!Crypt::OpenPGP::Armour->unarmour($value)) {
+ ThrowUserError('securemail_invalid_key',
+ { errstr => Crypt::OpenPGP::Armour->errstr });
+ }
+ }
+ elsif ($value =~ /BEGIN CERTIFICATE/) {
+ # S/MIME Keys must be in PEM format (Base64-encoded X.509)
+ #
+ # Crypt::SMIME seems not to like tainted values - it claims
+ # they aren't scalars!
+ trick_taint($value);
+
+ my $smime = Crypt::SMIME->new();
+ eval {
+ $smime->setPublicKey([$value]);
+ };
+ if ($@) {
+ ThrowUserError('securemail_invalid_key',
+ { errstr => $@ });
+ }
+ }
+ else {
+ ThrowUserError('securemail_invalid_key');
+ }
+
+ return $value;
+ };
+ }
+}
+
+# When creating a 'group' object, set up the secure_mail field appropriately.
+sub object_before_create {
+ my ($self, $args) = @_;
+ my $class = $args->{'class'};
+ my $params = $args->{'params'};
+
+ if ($class->isa('Bugzilla::Group')) {
+ $params->{secure_mail} = Bugzilla->cgi->param('secure_mail');
+ }
+}
+
+# On update, make sure the updating process knows about our new columns.
+sub object_update_columns {
+ my ($self, $args) = @_;
+ my $object = $args->{'object'};
+ my $columns = $args->{'columns'};
+
+ if ($object->isa('Bugzilla::Group')) {
+ # This seems like a convenient moment to extract this value...
+ $object->set('secure_mail', Bugzilla->cgi->param('secure_mail'));
+
+ push(@$columns, 'secure_mail');
+ }
+ elsif ($object->isa('Bugzilla::User')) {
+ push(@$columns, 'public_key');
+ }
+}
+
+# Handle the setting and changing of the public key.
+sub user_preferences {
+ my ($self, $args) = @_;
+ my $tab = $args->{'current_tab'};
+ my $save = $args->{'save_changes'};
+ my $handled = $args->{'handled'};
+ my $vars = $args->{'vars'};
+ my $params = Bugzilla->input_params;
+
+ return unless $tab eq 'securemail';
+
+ # Create a new user object so we don't mess with the main one, as we
+ # don't know where it's been...
+ my $user = new Bugzilla::User(Bugzilla->user->id);
+
+ if ($save) {
+ $user->set('public_key', $params->{'public_key'});
+ $user->update();
+
+ # Send user a test email
+ if ($user->public_key) {
+ _send_test_email($user);
+ $vars->{'test_email_sent'} = 1;
+ }
+ }
+
+ $vars->{'public_key'} = $user->public_key;
+
+ # Set the 'handled' scalar reference to true so that the caller
+ # knows the panel name is valid and that an extension took care of it.
+ $$handled = 1;
+}
+
+sub template_before_process {
+ my ($self, $args) = @_;
+ my $file = $args->{'file'};
+ my $vars = $args->{'vars'};
+
+ # Bug dependency emails contain the subject of the dependent bug
+ # right before the diffs when a status has gone from open/closed
+ # or closed/open. We need to sanitize the subject of change.blocker
+ # similar to how we do referenced bugs
+ return unless
+ $file eq 'email/bugmail.html.tmpl'
+ || $file eq 'email/bugmail.txt.tmpl';
+
+ if (defined $vars->{diffs}) {
+ foreach my $change (@{ $vars->{diffs} }) {
+ next if !defined $change->{blocker};
+ if (grep($_->secure_mail, @{ $change->{blocker}->groups_in })) {
+ $change->{blocker}->{short_desc} = "(Secure bug)";
+ }
+ }
+ }
+}
+
+sub _send_test_email {
+ my ($user) = @_;
+ my $template = Bugzilla->template_inner($user->settings->{'lang'}->{'value'});
+
+ my $vars = {
+ to_user => $user->email,
+ };
+
+ my $msg = "";
+ $template->process("account/email/securemail-test.txt.tmpl", $vars, \$msg)
+ || ThrowTemplateError($template->error());
+
+ MessageToMTA($msg);
+}
+
+##############################################################################
+# Encrypting the email
+##############################################################################
+sub mailer_before_send {
+ my ($self, $args) = @_;
+
+ my $email = $args->{'email'};
+ my $body = $email->body;
+
+ # Decide whether to make secure.
+ # This is a bit of a hack; it would be nice if it were more clear
+ # what sort a particular email is.
+ my $is_bugmail = $email->header('X-Bugzilla-Status') ||
+ $email->header('X-Bugzilla-Type') eq 'request';
+ my $is_passwordmail = !$is_bugmail && ($body =~ /cfmpw.*cxlpw/s);
+ my $is_test_email = $email->header('X-Bugzilla-Type') =~ /securemail-test/ ? 1 : 0;
+ my $is_whine_email = $email->header('X-Bugzilla-Type') eq 'whine' ? 1 : 0;
+ my $encrypt_header = $email->header('X-Bugzilla-Encrypt') ? 1 : 0;
+
+ if ($is_bugmail
+ || $is_passwordmail
+ || $is_test_email
+ || $is_whine_email
+ || $encrypt_header
+ ) {
+ # Convert the email's To address into a User object
+ my $login = $email->header('To');
+ my $emailsuffix = Bugzilla->params->{'emailsuffix'};
+ $login =~ s/$emailsuffix$//;
+ my $user = new Bugzilla::User({ name => $login });
+
+ # Default to secure. (Of course, this means if this extension has a
+ # bug, lots of people are going to get bugmail falsely claiming their
+ # bugs are secure and they need to add a key...)
+ my $make_secure = SECURE_ALL;
+
+ if ($is_bugmail) {
+ # This is also a bit of a hack, but there's no header with the
+ # bug ID in. So we take the first number in the subject.
+ my ($bug_id) = ($email->header('Subject') =~ /\[\D+(\d+)\]/);
+ my $bug = new Bugzilla::Bug($bug_id);
+ if (!_should_secure_bug($bug)) {
+ $make_secure = SECURE_NONE;
+ }
+ # If the insider group has securemail enabled..
+ my $insider_group = Bugzilla::Group->new({ name => Bugzilla->params->{'insidergroup'} });
+ if ($insider_group
+ && $insider_group->secure_mail
+ && $make_secure == SECURE_NONE)
+ {
+ my $comment_is_private = Bugzilla->dbh->selectcol_arrayref(
+ "SELECT isprivate FROM longdescs WHERE bug_id=? ORDER BY bug_when",
+ undef, $bug_id);
+ # Encrypt if there are private comments on an otherwise public bug
+ while ($body =~ /[\r\n]--- Comment #(\d+)/g) {
+ my $comment_number = $1;
+ if ($comment_number && $comment_is_private->[$comment_number]) {
+ $make_secure = SECURE_BODY;
+ last;
+ }
+ }
+ # Encrypt if updating a private attachment without a comment
+ if ($email->header('X-Bugzilla-Changed-Fields')
+ && $email->header('X-Bugzilla-Changed-Fields') =~ /Attachment #(\d+)/)
+ {
+ my $attachment = Bugzilla::Attachment->new($1);
+ if ($attachment && $attachment->isprivate) {
+ $make_secure = SECURE_BODY;
+ }
+ }
+ }
+ }
+ elsif ($is_passwordmail) {
+ # Mail is made unsecure only if the user does not have a public
+ # key and is not in any security groups. So specifying a public
+ # key OR being in a security group means the mail is kept secure
+ # (but, as noted above, the check is the other way around because
+ # we default to secure).
+ if ($user &&
+ !$user->public_key &&
+ !grep($_->secure_mail, @{ $user->groups }))
+ {
+ $make_secure = SECURE_NONE;
+ }
+ }
+ elsif ($is_whine_email) {
+ # When a whine email has one or more secure bugs in the body, then
+ # encrypt the entire email body. Subject can be left alone as it
+ # comes from the whine settings.
+ $make_secure = _should_secure_whine($email) ? SECURE_BODY : SECURE_NONE;
+ }
+ elsif ($encrypt_header) {
+ # Templates or code may set the X-Bugzilla-Encrypt header to
+ # trigger encryption of emails. Remove that header from the email.
+ $email->header_set('X-Bugzilla-Encrypt');
+ }
+
+ # If finding the user fails for some reason, but we determine we
+ # should be encrypting, we want to make the mail safe. An empty key
+ # does that.
+ my $public_key = $user ? $user->public_key : '';
+
+ # Check if the new bugmail prefix should be added to the subject.
+ my $add_new = ($email->header('X-Bugzilla-Type') eq 'new' &&
+ $user &&
+ $user->settings->{'bugmail_new_prefix'}->{'value'} eq 'on') ? 1 : 0;
+
+ if ($make_secure == SECURE_NONE) {
+ # Filter the bug_links in HTML email in case the bugs the links
+ # point are "secured" bugs and the user may not be able to see
+ # the summaries.
+ _filter_bug_links($email);
+ }
+ else {
+ _make_secure($email, $public_key, $is_bugmail && $make_secure == SECURE_ALL, $add_new);
+ }
+ }
+}
+
+# Custom hook for bugzilla.mozilla.org (see bug 752400)
+sub bugmail_referenced_bugs {
+ my ($self, $args) = @_;
+ # Sanitise subjects of referenced bugs.
+ my $referenced_bugs = $args->{'referenced_bugs'};
+ # No need to sanitise subjects if the entire email will be secured.
+ return if _should_secure_bug($args->{'updated_bug'});
+ # Replace the subject if required
+ foreach my $ref (@$referenced_bugs) {
+ if (grep($_->secure_mail, @{ $ref->{'bug'}->groups_in })) {
+ $ref->{'short_desc'} = "(Secure bug)";
+ }
+ }
+}
+
+sub _should_secure_bug {
+ my ($bug) = @_;
+ # If there's a problem with the bug, err on the side of caution and mark it
+ # as secure.
+ return
+ !$bug
+ || $bug->{'error'}
+ || grep($_->secure_mail, @{ $bug->groups_in });
+}
+
+sub _should_secure_whine {
+ my ($email) = @_;
+ my $should_secure = 0;
+ $email->walk_parts(sub {
+ my $part = shift;
+ my $content_type = $part->content_type;
+ return if !$content_type || $content_type !~ /^text\/plain/;
+ my $body = $part->body;
+ my @bugids = $body =~ /Bug (\d+):/g;
+ foreach my $id (@bugids) {
+ $id = trim($id);
+ next if !$id;
+ my $bug = new Bugzilla::Bug($id);
+ if ($bug && _should_secure_bug($bug)) {
+ $should_secure = 1;
+ last;
+ }
+ }
+ });
+ return $should_secure ? 1 : 0;
+}
+
+sub _make_secure {
+ my ($email, $key, $sanitise_subject, $add_new) = @_;
+
+ # Add header showing this email has been secured
+ $email->header_set('X-Bugzilla-Secure-Email', 'Yes');
+
+ my $subject = $email->header('Subject');
+ my ($bug_id) = $subject =~ /\[\D+(\d+)\]/;
+
+ my $key_type = 0;
+ if ($key && $key =~ /PUBLIC KEY/) {
+ $key_type = 'PGP';
+ }
+ elsif ($key && $key =~ /BEGIN CERTIFICATE/) {
+ $key_type = 'S/MIME';
+ }
+
+ if ($key_type eq 'PGP') {
+ ##################
+ # PGP Encryption #
+ ##################
+
+ my $pubring = new Crypt::OpenPGP::KeyRing(Data => $key);
+ my $pgp = new Crypt::OpenPGP(PubRing => $pubring);
+
+ if (scalar $email->parts > 1) {
+ my $old_boundary = $email->{ct}{attributes}{boundary};
+ my $to_encrypt = "Content-Type: " . $email->content_type . "\n\n";
+
+ # We need to do some fix up of each part for proper encoding and then
+ # stringify all parts for encrypting. We have to retain the old
+ # boundaries as well so that the email client can reconstruct the
+ # original message properly.
+ $email->walk_parts(\&_fix_encoding);
+
+ $email->walk_parts(sub {
+ my ($part) = @_;
+ if ($sanitise_subject) {
+ _insert_subject($part, $subject);
+ }
+ return if $part->parts > 1; # Top-level
+ $to_encrypt .= "--$old_boundary\n" . $part->as_string . "\n";
+ });
+ $to_encrypt .= "--$old_boundary--";
+
+ # Now create the new properly formatted PGP parts containing the
+ # encrypted original message
+ my @new_parts = (
+ Email::MIME->create(
+ attributes => {
+ content_type => 'application/pgp-encrypted',
+ encoding => '7bit',
+ },
+ body => "Version: 1\n",
+ ),
+ Email::MIME->create(
+ attributes => {
+ content_type => 'application/octet-stream',
+ filename => 'encrypted.asc',
+ disposition => 'inline',
+ encoding => '7bit',
+ },
+ body => _pgp_encrypt($pgp, $to_encrypt)
+ ),
+ );
+ $email->parts_set(\@new_parts);
+ my $new_boundary = $email->{ct}{attributes}{boundary};
+ # Redo the old content type header with the new boundaries
+ # and other information needed for PGP
+ $email->header_set("Content-Type",
+ "multipart/encrypted; " .
+ "protocol=\"application/pgp-encrypted\"; " .
+ "boundary=\"$new_boundary\"");
+ }
+ else {
+ _fix_encoding($email);
+ if ($sanitise_subject) {
+ _insert_subject($email, $subject);
+ }
+ $email->body_set(_pgp_encrypt($pgp, $email->body));
+ }
+ }
+
+ elsif ($key_type eq 'S/MIME') {
+ #####################
+ # S/MIME Encryption #
+ #####################
+
+ $email->walk_parts(\&_fix_encoding);
+
+ if ($sanitise_subject) {
+ $email->walk_parts(sub { _insert_subject($_[0], $subject) });
+ }
+
+ my $smime = Crypt::SMIME->new();
+ my $encrypted;
+
+ eval {
+ $smime->setPublicKey([$key]);
+ $encrypted = $smime->encrypt($email->as_string());
+ };
+
+ if (!$@) {
+ # We can't replace the Email::MIME object, so we have to swap
+ # out its component parts.
+ my $enc_obj = new Email::MIME($encrypted);
+ $email->header_obj_set($enc_obj->header_obj());
+ $email->parts_set([]);
+ $email->body_set($enc_obj->body());
+ $email->content_type_set('application/pkcs7-mime');
+ $email->charset_set('UTF-8') if Bugzilla->params->{'utf8'};
+ }
+ else {
+ $email->body_set('Error during Encryption: ' . $@);
+ }
+ }
+ else {
+ # No encryption key provided; send a generic, safe email.
+ my $template = Bugzilla->template;
+ my $message;
+ my $vars = {
+ 'urlbase' => correct_urlbase(),
+ 'bug_id' => $bug_id,
+ 'maintainer' => Bugzilla->params->{'maintainer'}
+ };
+
+ $template->process('account/email/encryption-required.txt.tmpl',
+ $vars, \$message)
+ || ThrowTemplateError($template->error());
+
+ $email->parts_set([]);
+ $email->content_type_set('text/plain');
+ $email->body_set($message);
+ }
+
+ if ($sanitise_subject) {
+ # This is designed to still work if the admin changes the word
+ # 'bug' to something else. However, it could break if they change
+ # the format of the subject line in another way.
+ my $new = $add_new ? ' New:' : '';
+ my $product = $email->header('X-Bugzilla-Product');
+ my $component = $email->header('X-Bugzilla-Component');
+ # Note: the $bug_id is required within the parentheses in order to keep
+ # gmail's threading algorithm happy.
+ $subject =~ s/($bug_id\])\s+(.*)$/$1$new (Secure bug $bug_id in $product :: $component)/;
+ $email->header_set('Subject', $subject);
+ }
+}
+
+sub _pgp_encrypt {
+ my ($pgp, $text) = @_;
+ # "@" matches every key in the public key ring, which is fine,
+ # because there's only one key in our keyring.
+ #
+ # We use the CAST5 cipher because the Rijndael (AES) module doesn't
+ # like us for some reason I don't have time to debug fully.
+ # ("key must be an untainted string scalar")
+ my $encrypted = $pgp->encrypt(Data => $text,
+ Recipients => "@",
+ Cipher => 'CAST5',
+ Armour => 1);
+ if (!defined $encrypted) {
+ return 'Error during Encryption: ' . $pgp->errstr;
+ }
+ return $encrypted;
+}
+
+# Insert the subject into the part's body, as the subject of the message will
+# be sanitised.
+# XXX this incorrectly assumes all parts of the message are the body
+# we should only alter parts who's parent is multipart/alternative
+sub _insert_subject {
+ my ($part, $subject) = @_;
+ my $content_type = $part->content_type or return;
+ if ($content_type =~ /^text\/plain/) {
+ if (!is_7bit_clean($subject)) {
+ $part->encoding_set('quoted-printable');
+ }
+ $part->body_str_set("Subject: $subject\015\012\015\012" . $part->body_str);
+ }
+ elsif ($content_type =~ /^text\/html/) {
+ my $tree = HTML::Tree->new->parse_content($part->body_str);
+ my $body = $tree->look_down(qw(_tag body));
+ $body->unshift_content(['div', "Subject: $subject"], ['br']);
+ _set_body_from_tree($part, $tree);
+ }
+}
+
+sub _fix_encoding {
+ my $part = shift;
+
+ # don't touch the top-level part of multi-part mail
+ return if $part->parts > 1;
+
+ # nothing to do if the part already has a charset
+ my $ct = parse_content_type($part->content_type);
+ my $charset = $ct->{attributes}{charset}
+ ? $ct->{attributes}{charset}
+ : '';
+ return unless !$charset || $charset eq 'us-ascii';
+
+ if (Bugzilla->params->{utf8}) {
+ $part->charset_set('UTF-8');
+ my $raw = $part->body_raw;
+ if (utf8::is_utf8($raw)) {
+ utf8::encode($raw);
+ $part->body_set($raw);
+ }
+ }
+ $part->encoding_set('quoted-printable');
+}
+
+sub _filter_bug_links {
+ my ($email) = @_;
+ $email->walk_parts(sub {
+ my $part = shift;
+ _fix_encoding($part);
+ my $content_type = $part->content_type;
+ return if !$content_type || $content_type !~ /text\/html/;
+ my $tree = HTML::Tree->new->parse_content($part->body_str);
+ my @links = $tree->look_down( _tag => q{a}, class => qr/bz_bug_link/ );
+ my $updated = 0;
+ foreach my $link (@links) {
+ my $href = $link->attr('href');
+ my ($bug_id) = $href =~ /\Qshow_bug.cgi?id=\E(\d+)/;
+ my $bug = new Bugzilla::Bug($bug_id);
+ if ($bug && _should_secure_bug($bug)) {
+ $link->attr('title', '(secure bug)');
+ $link->attr('class', 'bz_bug_link');
+ $updated = 1;
+ }
+ }
+ if ($updated) {
+ _set_body_from_tree($part, $tree);
+ }
+ });
+}
+
+sub _set_body_from_tree {
+ my ($part, $tree) = @_;
+ $part->body_set($tree->as_HTML);
+ $part->charset_set('UTF-8') if Bugzilla->params->{'utf8'};
+ $part->encoding_set('quoted-printable');
+}
+
+__PACKAGE__->NAME;
diff --git a/extensions/SecureMail/README b/extensions/SecureMail/README
new file mode 100644
index 000000000..ac3484291
--- /dev/null
+++ b/extensions/SecureMail/README
@@ -0,0 +1,8 @@
+This extension should be placed in a directory called "SecureMail" in the
+Bugzilla extensions/ directory. After installing it, remove the file
+"disabled" (if present) and then run checksetup.pl.
+
+Instructions for user key formats:
+
+S/MIME Keys must be in PEM format - i.e. Base64-encoded text, with BEGIN CERTIFICATE
+PGP keys must be ASCII-armoured - i.e. text, with BEGIN PGP PUBLIC KEY.
diff --git a/extensions/SecureMail/template/en/default/account/email/encryption-required.txt.tmpl b/extensions/SecureMail/template/en/default/account/email/encryption-required.txt.tmpl
new file mode 100644
index 000000000..f3710bb17
--- /dev/null
+++ b/extensions/SecureMail/template/en/default/account/email/encryption-required.txt.tmpl
@@ -0,0 +1,15 @@
+This email would have contained sensitive information, but you have not set
+a PGP/GPG key or SMIME certificate in the "Secure Mail" section of your user
+preferences.
+
+[% IF bug_id %]
+In order to receive the full text of similar mails in the future, please
+go to:
+[%+ urlbase %]userprefs.cgi?tab=securemail
+and provide a key or certificate.
+
+You can see this bug's current state at:
+[%+ urlbase %]show_bug.cgi?id=[% bug_id %]
+[% ELSE %]
+You will have to contact [% maintainer %] to reset your password.
+[% END %]
diff --git a/extensions/SecureMail/template/en/default/account/email/securemail-test.txt.tmpl b/extensions/SecureMail/template/en/default/account/email/securemail-test.txt.tmpl
new file mode 100644
index 000000000..e4f4c9242
--- /dev/null
+++ b/extensions/SecureMail/template/en/default/account/email/securemail-test.txt.tmpl
@@ -0,0 +1,23 @@
+[%# This Source Code Form is subject to the terms of the Mozilla Public
+ # License, v. 2.0. If a copy of the MPL was not distributed with this
+ # file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ #
+ # This Source Code Form is "Incompatible With Secondary Licenses", as
+ # defined by the Mozilla Public License, v. 2.0.
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+
+From: [% Param('mailfrom') %]
+To: [% to_user %]
+Subject: [% terms.Bugzilla %] SecureMail Test Email
+X-Bugzilla-Type: securemail-test
+
+Congratulations! If you can read this, then your SecureMail encryption
+key uploaded to [% terms.Bugzilla %] is working properly.
+
+To update your SecureMail preferences at any time, please go to:
+[%+ urlbase %]userprefs.cgi?tab=securemail
+
+Sincerely,
+Your Friendly [% terms.Bugzilla %] Administrator
diff --git a/extensions/SecureMail/template/en/default/account/prefs/securemail.html.tmpl b/extensions/SecureMail/template/en/default/account/prefs/securemail.html.tmpl
new file mode 100644
index 000000000..db595a23f
--- /dev/null
+++ b/extensions/SecureMail/template/en/default/account/prefs/securemail.html.tmpl
@@ -0,0 +1,40 @@
+[%# 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 the Mozilla Corporation.
+ # Portions created by the Initial Developer are Copyright (C) 2008 the
+ # Initial Developer. All Rights Reserved.
+ #
+ # Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
+ #%]
+
+[% IF test_email_sent %]
+ <div id="message">
+ An encrypted test email has been sent to your address.
+ </div>
+[% END %]
+
+<p>Some [% terms.bugs %] in this [% terms.Bugzilla %] are in groups the administrator has
+deemed 'secure'. This means emails containing information about those [% terms.bugs %]
+will only be sent encrypted. Enter your PGP/GPG public key or
+SMIME certificate here to receive full update emails for such [% terms.bugs %].</p>
+
+<p>If you are a member of a secure group, or if you enter a key here, your password reset email will also be sent to you encrypted. If you are a member of a secure group and do not enter a key, you will not be able to reset your password without the assistance of an administrator.</p>
+
+<p><a href="page.cgi?id=securemail/help.html">More help is available</a>.</p>
+
+[% Hook.process('moreinfo') %]
+
+<textarea id="public_key" name="public_key" cols="72" rows="12">
+ [%- public_key FILTER html %]</textarea>
+
+<p>Submitting valid changes will automatically send an encrypted test email to your address.</p>
diff --git a/extensions/SecureMail/template/en/default/hook/account/prefs/prefs-tabs.html.tmpl b/extensions/SecureMail/template/en/default/hook/account/prefs/prefs-tabs.html.tmpl
new file mode 100644
index 000000000..70a40e592
--- /dev/null
+++ b/extensions/SecureMail/template/en/default/hook/account/prefs/prefs-tabs.html.tmpl
@@ -0,0 +1,28 @@
+[%# -*- Mode: perl; indent-tabs-mode: nil -*-
+ #
+ # 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 SecureMail Extension
+ #
+ # The Initial Developer of the Original Code is Mozilla.
+ # Portions created by Mozilla are Copyright (C) 2008 Mozilla Corporation.
+ # All Rights Reserved.
+ #
+ # Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
+ # Gervase Markham <gerv@gerv.net>
+ #%]
+
+[% tabs = tabs.import([{
+ name => "securemail",
+ label => "Secure Mail",
+ link => "userprefs.cgi?tab=securemail",
+ saveable => 1
+ }]) %]
diff --git a/extensions/SecureMail/template/en/default/hook/account/prefs/securemail-moreinfo.html.tmpl b/extensions/SecureMail/template/en/default/hook/account/prefs/securemail-moreinfo.html.tmpl
new file mode 100644
index 000000000..fcd155f89
--- /dev/null
+++ b/extensions/SecureMail/template/en/default/hook/account/prefs/securemail-moreinfo.html.tmpl
@@ -0,0 +1,8 @@
+<p>
+ For more information about the SecureMail Bugzilla Extension or on obtaining a key and how to get it into the right format,
+ see the <a href="[% urlbase FILTER none %]page.cgi?id=securemail/help.html">SecureMail Help Page</a>.
+</p>
+<p>
+ <b>Note that Securemail will only be sent for groups which have had the "send secure mail" bit enabled. Currently, that's not
+ any of them - so adding a key here will make no changes to your bugmail. Yet.</b>
+</p>
diff --git a/extensions/SecureMail/template/en/default/hook/admin/groups/create-field.html.tmpl b/extensions/SecureMail/template/en/default/hook/admin/groups/create-field.html.tmpl
new file mode 100644
index 000000000..27c644d02
--- /dev/null
+++ b/extensions/SecureMail/template/en/default/hook/admin/groups/create-field.html.tmpl
@@ -0,0 +1,25 @@
+[%# 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 the Mozilla Corporation.
+ # Portions created by the Initial Developer are Copyright (C) 2008 the
+ # Initial Developer. All Rights Reserved.
+ #
+ # Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
+ #%]
+<tr>
+ <th>Secure Bugmail:</th>
+ <td colspan="3">
+ <input type="checkbox" id="secure_mail" name="secure_mail"
+ [% ' checked="checked"' IF group.secure_mail %]>
+ </td>
+</tr>
diff --git a/extensions/SecureMail/template/en/default/hook/admin/groups/edit-field.html.tmpl b/extensions/SecureMail/template/en/default/hook/admin/groups/edit-field.html.tmpl
new file mode 100644
index 000000000..253fed29e
--- /dev/null
+++ b/extensions/SecureMail/template/en/default/hook/admin/groups/edit-field.html.tmpl
@@ -0,0 +1,27 @@
+[%# 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 the Mozilla Corporation.
+ # Portions created by the Initial Developer are Copyright (C) 2008 the
+ # Initial Developer. All Rights Reserved.
+ #
+ # Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
+ #%]
+[% IF group.is_bug_group || group.name == Param('insidergroup') %]
+ <tr>
+ <th>Secure Bugmail:</th>
+ <td>
+ <input type="checkbox" id="secure_mail" name="secure_mail"
+ [% ' checked="checked"' IF group.secure_mail %]>
+ </td>
+ </tr>
+[% END %]
diff --git a/extensions/SecureMail/template/en/default/hook/global/user-error-errors.html.tmpl b/extensions/SecureMail/template/en/default/hook/global/user-error-errors.html.tmpl
new file mode 100644
index 000000000..46b093674
--- /dev/null
+++ b/extensions/SecureMail/template/en/default/hook/global/user-error-errors.html.tmpl
@@ -0,0 +1,27 @@
+[%# 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 the Mozilla Corporation.
+ # Portions created by the Initial Developer are Copyright (C) 2008 the
+ # Initial Developer. All Rights Reserved.
+ #
+ # Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
+ #%]
+
+[% IF error == "securemail_invalid_key" %]
+ [% title = "Invalid Public Key" %]
+ We were unable to read the public key that you entered. Make sure
+ that you are entering either an ASCII-armored PGP/GPG public key,
+ including the "BEGIN PGP PUBLIC KEY BLOCK" and "END PGP PUBLIC KEY BLOCK"
+ lines, or a PEM format (Base64-encoded X.509) S/MIME key, including the
+ BEGIN CERTIFICATE and END CERTIFICATE lines.<br><br>[% errstr FILTER html %]
+[% END %]
diff --git a/extensions/SecureMail/template/en/default/pages/securemail/help.html.tmpl b/extensions/SecureMail/template/en/default/pages/securemail/help.html.tmpl
new file mode 100644
index 000000000..e6ef02927
--- /dev/null
+++ b/extensions/SecureMail/template/en/default/pages/securemail/help.html.tmpl
@@ -0,0 +1,130 @@
+[%#
+ # 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 SecureMail Extension.
+ #
+ # The Initial Developer of the Original Code is the Mozilla Foundation.
+ # Portions created by Mozilla are Copyright (C) 2008 Mozilla Foundation.
+ # All Rights Reserved.
+ #
+ # Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
+ # Gervase Markham <gerv@gerv.net>
+ # Dave Lawrence <dkl@mozilla.com>
+ #%]
+
+[% PROCESS global/header.html.tmpl
+ title = "SecureMail Help"
+%]
+
+[% terms.Bugzilla %] considers certain groups as "secure". If a [% terms.bug %] is in one of those groups, [% terms.Bugzilla %] will not send unencrypted
+email about it. To receive encrypted email rather than just a "something changed" placeholder, you must provide either
+a S/MIME or a GPG/PGP key on the <a href="[% urlbase FILTER none %]userprefs.cgi?tab=securemail">SecureMail preferences tab</a>.<br>
+<br>
+In addition, if you have uploaded a S/MIME or GPG/PGP key using the <a href="[% urlbase FILTER none %]userprefs.cgi?tab=securemail">
+SecureMail preferences tab</a>, if you request your password to be reset, [% terms.Bugzilla %] will send the reset email encrypted and you will
+be required to decrypt it to view the reset instructions.
+
+<h2>S/MIME</h2>
+
+<b>S/MIME Keys must be in PEM format - i.e. Base64-encoded text, with the first line containing BEGIN CERTIFICATE.</b></p>
+
+<p>
+S/MIME certificates can be obtained from a number of providers. You can get a free one from <a href="https://www.startssl.com/?app=12">StartCom</a>.
+Once you have it, <a href="https://www.startssl.com/?app=25#52">export it from your browser as a .p12 file and import it into your mail client</a>.
+You'll need to provide a password when you export - pick a strong one, and then back up the .p12 file somewhere safe.</p>
+
+<p>Import on Thunderbird as follows:</p>
+
+<ul>
+<li>Open Preferences in Thunderbird.</li>
+<li>Activate the Advanced pane.</li>
+<li>Activate the Certificates tab.</li>
+<li>Press the button View Certificates.</li>
+<li>Press the Import button.</li>
+<li>Open your .p12 file.</li>
+<li>Enter the password for unlocking the .p12 if asked.</li>
+</ul>
+
+<p>
+Then, you need to convert it to a .pem file. Here are two possible ways to do this.</p>
+
+<h3>Thunderbird</h3>
+
+<ul>
+<li>Open Preferences in Thunderbird.</li>
+<li>Activate the Advanced pane.</li>
+<li>Activate the Certificates tab.</li>
+<li>Press the button View Certificates.</li>
+<li>Select the line in the tree widget that represents the certificate you imported.</li>
+<li>Press the View button.</li>
+<li>Activate the Details tab.</li>
+<li>Press the Export button.</li>
+<li>Choose where to save the .pem file.</li>
+</ul>
+
+<p>Paste the contents of the .pem file into the SecureMail text field in [% terms.Bugzilla %].</p>
+
+<h3>OpenSSL</h3>
+
+<p>Or, if you have OpenSSL installed, do the following:</p>
+
+<p>
+<code>openssl pkcs12 -in certificate.p12 -out certificate.pem -nodes -nokeys</code></p>
+
+<p>
+Open the .pem file in a text editor. You can recognise the public key because
+it starts "BEGIN CERTIFICATE" and ends "END CERTIFICATE" and
+has an appropriate friendly name (e.g. "StartCom Free Certificate Member's StartCom Ltd. ID").</p>
+
+<p>Paste the contents of the .pem file into the SecureMail text field in [% terms.Bugzilla %].</p>
+
+<h2>PGP</h2>
+
+<b>PGP keys must be ASCII-armoured - i.e. text, with the first line containing BEGIN PGP PUBLIC KEY.</b></p>
+
+<p>
+If you already have your own PGP key in a keyring, skip straight to step 3. Otherwise:</p>
+
+<ol>
+
+<li>Install the GPG suite of utilities for your operating system, either using your package manager or downloaded from <a href="http://www.gnupg.org/download/index.en.html">gnupg.org</a>.</p>
+
+<li><p>Generate a private key.</p>
+
+<p><code>gpg --gen-key</code></p>
+
+<p>
+You’ll have to answer several questions:</p>
+
+<p>
+<ul>
+ <li>What kind and size of key you want; the defaults are probably good enough.</li>
+ <li>How long the key should be valid; you can safely choose a non-expiring key.</li>
+ <li>Your real name and e-mail address; these are necessary for identifying your key in a larger set of keys.</li>
+ <li>A comment for your key; the comment can be empty.</li>
+ <li>A passphrase. Whatever you do, don’t forget it! Your key, and all your encrypted files, will be useless if you do.</li>
+</ul>
+
+<li><p>Generate an ASCII version of your public key.</p>
+
+<p><code>gpg --armor --output pubkey.txt --export 'Your Name'</code></p>
+
+<p>Paste the contents of pubkey.txt into the SecureMail text field in [% terms.Bugzilla %].
+
+<li>Configure your email client to use your associated private key to decrypt the encrypted emails. For Thunderbird, you need the <a href="https://addons.mozilla.org/en-us/thunderbird/addon/enigmail/">Enigmail</a> extension.</p>
+</ol>
+
+<p>
+Further reading: <a href="http://www.madboa.com/geek/gpg-quickstart">GPG Quickstart</a>.
+
+[% PROCESS global/footer.html.tmpl %]
+
+
diff --git a/extensions/TypeSniffer/Config.pm b/extensions/TypeSniffer/Config.pm
new file mode 100644
index 000000000..6ad03b362
--- /dev/null
+++ b/extensions/TypeSniffer/Config.pm
@@ -0,0 +1,40 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# 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 TypeSniffer Bugzilla Extension.
+#
+# The Initial Developer of the Original Code is The Mozilla Foundation.
+# Portions created by the Initial Developer are Copyright (C) 2010 the
+# Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+# Gervase Markham <gerv@mozilla.org>
+
+package Bugzilla::Extension::TypeSniffer;
+use strict;
+
+use constant NAME => 'TypeSniffer';
+
+use constant REQUIRED_MODULES => [
+ {
+ package => 'File-MimeInfo',
+ module => 'File::MimeInfo::Magic',
+ version => '0'
+ },
+ {
+ package => 'IO-stringy',
+ module => 'IO::Scalar',
+ version => '0'
+ },
+];
+
+__PACKAGE__->NAME; \ No newline at end of file
diff --git a/extensions/TypeSniffer/Extension.pm b/extensions/TypeSniffer/Extension.pm
new file mode 100644
index 000000000..b788b5426
--- /dev/null
+++ b/extensions/TypeSniffer/Extension.pm
@@ -0,0 +1,75 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# 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 TypeSniffer Bugzilla Extension.
+#
+# The Initial Developer of the Original Code is The Mozilla Foundation.
+# Portions created by the Initial Developer are Copyright (C) 2010 the
+# Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+# Gervase Markham <gerv@mozilla.org>
+
+package Bugzilla::Extension::TypeSniffer;
+use strict;
+use base qw(Bugzilla::Extension);
+
+use File::MimeInfo::Magic;
+use IO::Scalar;
+
+our $VERSION = '0.02';
+################################################################################
+# This extension uses magic to guess MIME types for data where the browser has
+# told us it's application/octet-stream (probably because there's no file
+# extension, or it's a text type with a non-.txt file extension).
+################################################################################
+sub attachment_process_data {
+ my ($self, $args) = @_;
+ my $attributes = $args->{'attributes'};
+ my $params = Bugzilla->input_params;
+
+ # If we have autodetected application/octet-stream from the Content-Type
+ # header, let's have a better go using a sniffer.
+ if ($params->{'contenttypemethod'} eq 'autodetect' &&
+ $attributes->{'mimetype'} eq 'application/octet-stream')
+ {
+ # data attribute can be either scalar data or filehandle
+ # bugzilla.org/docs/3.6/en/html/api/Bugzilla/Attachment.html#create
+ my $fh = $attributes->{'data'};
+ if (!ref($fh)) {
+ my $data = $attributes->{'data'};
+ $fh = new IO::Scalar \$data;
+ }
+ else {
+ # CGI.pm sends us an Fh that isn't actually an IO::Handle, but
+ # has a method for getting an actual handle out of it.
+ if (!$fh->isa('IO::Handle')) {
+ $fh = $fh->handle;
+ # ->handle returns an literal IO::Handle, even though the
+ # underlying object is a file. So we rebless it to be a proper
+ # IO::File object so that we can call ->seek on it and so on.
+ # Just in case CGI.pm fixes this some day, we check ->isa first.
+ if (!$fh->isa('IO::File')) {
+ bless $fh, 'IO::File';
+ }
+ }
+ }
+
+ my $mimetype = mimetype($fh);
+ $fh->seek(0, 0);
+ if ($mimetype) {
+ $attributes->{'mimetype'} = $mimetype;
+ }
+ }
+}
+
+__PACKAGE__->NAME;
diff --git a/extensions/MoreBugUrl/disabled b/extensions/TypeSniffer/disabled
index e69de29bb..e69de29bb 100644
--- a/extensions/MoreBugUrl/disabled
+++ b/extensions/TypeSniffer/disabled
diff --git a/extensions/Voting/disabled b/extensions/Voting/disabled
deleted file mode 100644
index e69de29bb..000000000
--- a/extensions/Voting/disabled
+++ /dev/null
diff --git a/favicon.ico b/favicon.ico
new file mode 100644
index 000000000..11b76af2b
--- /dev/null
+++ b/favicon.ico
Binary files differ
diff --git a/images/favicon.ico b/images/favicon.ico
index c8ade39ad..11b76af2b 100644
--- a/images/favicon.ico
+++ b/images/favicon.ico
Binary files differ
diff --git a/images/ranks/bugs-rank-at.png b/images/ranks/bugs-rank-at.png
new file mode 100644
index 000000000..347074121
--- /dev/null
+++ b/images/ranks/bugs-rank-at.png
Binary files differ
diff --git a/images/ranks/bugs-rank-dev.png b/images/ranks/bugs-rank-dev.png
new file mode 100644
index 000000000..125b665f6
--- /dev/null
+++ b/images/ranks/bugs-rank-dev.png
Binary files differ
diff --git a/images/ranks/bugs-rank-infra.png b/images/ranks/bugs-rank-infra.png
new file mode 100644
index 000000000..32d99c3b0
--- /dev/null
+++ b/images/ranks/bugs-rank-infra.png
Binary files differ
diff --git a/images/ranks/bugs-rank-sec.png b/images/ranks/bugs-rank-sec.png
new file mode 100644
index 000000000..4fbbc0f5a
--- /dev/null
+++ b/images/ranks/bugs-rank-sec.png
Binary files differ
diff --git a/mod_perl.pl b/mod_perl.pl
index f0de2e553..80dff2304 100644
--- a/mod_perl.pl
+++ b/mod_perl.pl
@@ -55,7 +55,8 @@ use Apache2::SizeLimit;
# This means that every httpd child will die after processing
# a CGI if it is taking up more than 45MB of RAM all by itself,
# not counting RAM it is sharing with the other httpd processes.
-Apache2::SizeLimit->set_max_unshared_size(45_000);
+#Apache2::SizeLimit->set_max_unshared_size(45_000);
+Apache2::SizeLimit->set_max_unshared_size(500_000);
my $cgi_path = Bugzilla::Constants::bz_locations()->{'cgi_path'};
diff --git a/recompile.sh b/recompile.sh
new file mode 100755
index 000000000..fd5a489b3
--- /dev/null
+++ b/recompile.sh
@@ -0,0 +1,5 @@
+#!/bin/sh
+
+#chmod 0600 zzz.txt
+#sleep 20
+git pull && { perl checksetup.pl && apache2ctl graceful; }
diff --git a/robots-ssl.txt b/robots-ssl.txt
new file mode 100644
index 000000000..808943e32
--- /dev/null
+++ b/robots-ssl.txt
@@ -0,0 +1,21 @@
+User-agent: *
+Allow: /
+Allow: /index.cgi
+Allow: /show_bug.cgi
+Allow: /attachment.cgi
+Allow: /data/duplicates.rdf
+Allow: /data/cached/
+Disallow: /query.cgi
+Disallow: /enter_bug.cgi
+Disallow: /userprefs.cgi
+Disallow: /params.cgi
+Disallow: /editsettings.cgi
+Disallow: /report.cgi
+Disallow: /request.cgi
+Disallow: /votes.cgi
+Disallow: /showdependencygraph.cgi
+Disallow: /showdependencytree.cgi
+Disallow: /data/webdot/
+Disallow: /buglist.cgi
+Disallow: /custom_buglist.cgi
+Disallow: /show_activity.cgi
diff --git a/robots.txt b/robots.txt
index 0f823cb24..808943e32 100644
--- a/robots.txt
+++ b/robots.txt
@@ -1,3 +1,21 @@
User-agent: *
+Allow: /
Allow: /index.cgi
-Disallow: /
+Allow: /show_bug.cgi
+Allow: /attachment.cgi
+Allow: /data/duplicates.rdf
+Allow: /data/cached/
+Disallow: /query.cgi
+Disallow: /enter_bug.cgi
+Disallow: /userprefs.cgi
+Disallow: /params.cgi
+Disallow: /editsettings.cgi
+Disallow: /report.cgi
+Disallow: /request.cgi
+Disallow: /votes.cgi
+Disallow: /showdependencygraph.cgi
+Disallow: /showdependencytree.cgi
+Disallow: /data/webdot/
+Disallow: /buglist.cgi
+Disallow: /custom_buglist.cgi
+Disallow: /show_activity.cgi
diff --git a/runstats.sh b/runstats.sh
new file mode 100755
index 000000000..3d6b33f66
--- /dev/null
+++ b/runstats.sh
@@ -0,0 +1,36 @@
+#!/bin/sh
+
+cd /var/www/bugs.gentoo.org/htdocs
+
+custom_buglist="https://bugs.gentoo.org/custom_buglist.cgi"
+
+[ ! -d "data/cached/" ] && mkdir -p data/cached/
+outpath="/var/www/bugs.gentoo.org/htdocs/data/cached"
+
+./collectstats.pl
+
+dofile() {
+ url="$1"
+ outfile="$2"
+ tmp="${outfile}.$$"
+ #echo $url
+# wget -q "$url" -O "${tmp}" --header 'Host: bugs.gentoo.org'
+ curl -sS --resolve bugs.gentoo.org:443:127.0.0.1 "${url}" -o "${tmp}"
+ if [ $? -eq 0 ]; then
+ gzip -9 <"${tmp}" >"${tmp}.gz"
+ mv -f "${tmp}" "${outfile}"
+ mv -f "${tmp}.gz" "${outfile}gz"
+ else
+ rm -f "${tmp}" "${tmp}.gz" "${outfile}" "${outfile}gz"
+ fi
+}
+
+for status in RESOLVED VERIFIED ; do
+ for reso in FIXED INVALID WONTFIX LATER REMIND WORKSFORME CANTFIX NEEDINFO TEST-REQUEST UPSTREAM OBSOLETE; do
+ dofile "$custom_buglist?reso=${reso}&status=${status}" ${outpath}/buglist-${status}-${reso}.html
+ done
+done
+
+for status in UNCONFIRMED CONFIRMED IN_PROGRESS ; do
+ dofile "$custom_buglist?status=${status}" ${outpath}/buglist-${status}.html
+done
diff --git a/show_bug.cgi b/show_bug.cgi
index f69cae740..9e31fc4a7 100755
--- a/show_bug.cgi
+++ b/show_bug.cgi
@@ -61,12 +61,14 @@ if ($single) {
}
}
} else {
+ my $count = 0;
foreach my $id ($cgi->param('id')) {
# Be kind enough and accept URLs of the form: id=1,2,3.
my @ids = split(/,/, $id);
my @check_bugs;
foreach my $bug_id (@ids) {
+ last if $count == 100;
next unless $bug_id;
my $bug = new Bugzilla::Bug({ id => $bug_id, cache => 1 });
if (!$bug->{error}) {
@@ -75,6 +77,7 @@ if ($single) {
else {
push(@illegal_bugs, { bug_id => trim($bug_id), error => $bug->{error} });
}
+ $count++;
}
$user->visible_bugs(\@check_bugs);
diff --git a/skins/contrib/Gentoo/buglist.css b/skins/contrib/Gentoo/buglist.css
new file mode 100644
index 000000000..2e14368b1
--- /dev/null
+++ b/skins/contrib/Gentoo/buglist.css
@@ -0,0 +1,24 @@
+/* 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 Mike Schrag.
+ * Portions created by Marc Schumann are Copyright (c) 2007 Mike Schrag.
+ * All rights reserved.
+ *
+ * Contributor(s): Mike Schrag <mschrag@pobox.com>
+ * Byron Jones <bugzilla@glob.com.au>
+ * Marc Schumann <wurblzap@gmail.com>
+ */
+
+tr.bz_bugitem:hover {
+ background-color: #ccccff;
+}
diff --git a/skins/contrib/Gentoo/global.css b/skins/contrib/Gentoo/global.css
new file mode 100644
index 000000000..20f79a607
--- /dev/null
+++ b/skins/contrib/Gentoo/global.css
@@ -0,0 +1,331 @@
+/* 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 Mike Schrag.
+ * Portions created by Marc Schumann are Copyright (c) 2007 Mike Schrag.
+ * All rights reserved.
+ *
+ * Contributor(s): Mike Schrag <mschrag@pobox.com>
+ * Byron Jones <bugzilla@glob.com.au>
+ * Marc Schumann <wurblzap@gmail.com>
+ * Frédéric Buclin <LpSolit@gmail.com>
+ */
+
+body {
+ background: #D2D0D4;
+ font-family: Helvetica, Arial, Geneva;
+ padding: 1.5em;
+ margin: 0;
+}
+
+a img {
+ border: none !important;
+}
+
+/* page title */
+
+#titles {
+ -webkit-border-top-left-radius: 5px;
+ -webkit-border-top-right-radius: 5px;
+ -moz-border-radius-topleft: 5px;
+ -moz-border-radius-topright: 5px;
+ border-top-left-radius: 5px;
+ border-top-right-radius: 5px;
+ background-color: #54487A;
+}
+
+#header .links, #footer {
+ background-color: #8076A1;
+ color: #ddd;
+}
+
+#header {
+ -webkit-border-bottom-right-radius: 5px;
+ -webkit-border-bottom-left-radius: 5px;
+ -moz-border-radius-bottomright: 5px;
+ -moz-border-radius-bottomleft: 5px;
+ border-bottom-right-radius: 5px;
+ border-bottom-left-radius: 5px;
+ border: none;
+}
+
+#header .links {
+ border-bottom: 1px solid #67539B;
+ border-left: 1px solid #67539B;
+ border-right: 1px solid #67539B;
+ -webkit-border-bottom-right-radius: 5px;
+ -webkit-border-bottom-left-radius: 5px;
+ -moz-border-radius-bottomright: 5px;
+ -moz-border-radius-bottomleft: 5px;
+ border-bottom-right-radius: 5px;
+ border-bottom-left-radius: 5px;
+}
+
+#header a, #footer a {
+ color: white;
+ text-decoration: none;
+}
+
+#header a:hover, #footer a:hover {
+ text-decoration: underline;
+}
+
+/* body */
+
+#bugzilla-body {
+ background: #f0f0f0;
+ color: black;
+ border: 1px solid #747e93;
+ padding: 10px;
+ font-size: 10pt;
+ -webkit-border-radius: 5px;
+ -moz-border-radius: 5px;
+ border-radius: 5px;
+}
+
+a {
+ color: #54487A;
+}
+a:hover {
+ color: #28203C;
+}
+a:visited {
+ color: #4f6ba3;
+}
+
+hr {
+ border-color: #969696;
+ border-style: dashed;
+ border-width: 1px;
+ margin-top: 10px;
+}
+
+/* edit */
+
+#bugzilla-body th {
+ font-weight: bold;
+ vertical-align: top;
+ white-space: nowrap;
+}
+
+#bug-form td {
+ padding-top: 2px;
+}
+
+/* attachments */
+
+#attachment-list {
+ border: 2px solid #c8c8ba;
+ font-size: 9pt;
+}
+
+#attachment-list th {
+ background-color: #e6e6d8;
+ border: none;
+ border-bottom: 1px solid #c8c8ba;
+ text-align: left;
+}
+
+#attachment-list th a {
+ color: #646456;
+}
+
+#attachment-list td {
+ border: none;
+}
+
+#attachment-list-actions td {
+ border-top: 1px solid #c8c8ba;
+}
+
+/************/
+/* Comments */
+/************/
+
+#comments th {
+ font-size: 9pt;
+ font-weight: bold;
+ padding-top: 5px;
+ padding-right: 5px;
+ padding-bottom: 10px;
+ text-align: right;
+ vertical-align: top;
+ white-space: nowrap;
+}
+
+#comments td {
+ padding-top: 2px;
+}
+
+.reply-button a {
+ padding-left: 2px;
+ padding-right: 2px;
+}
+
+.bz_comment {
+ background-color: #e8e8e8;
+ margin: 1px 1px 10px 1px;
+ border-width: 1px;
+ border-style: solid;
+ border-color: #c8c8ba;
+ padding: 5px;
+ font-size: 9pt;
+}
+
+.bz_comment_head, .bz_first_comment_head {
+ margin: 0; padding: 0;
+ background-color: transparent;
+ font-weight: bold;
+}
+
+.bz_comment_user {
+ margin-left: 0;
+}
+
+.bz_comment.bz_private {
+ background-color: #f0e8e8;
+ border-color: #f8c8ba;
+}
+
+.comment_rule {
+ display: none;
+}
+
+/* footer */
+
+#footer {
+ border: 1px solid #67539B;
+ width: 100%;
+ -webkit-border-radius: 5px;
+ -moz-border-radius: 5px;
+ border-radius: 5px;
+}
+
+#footer #links-actions,
+#footer #links-edit,
+#footer #links-saved,
+#footer #links-special {
+ margin-top: 2ex;
+}
+
+#footer .links {
+ border-spacing: 30px;
+ margin-bottom: 2ex;
+}
+
+.separator {
+ color: #cccccc;
+}
+
+/* tabs */
+
+.tabbed .tabbody {
+ background: #f8f8f8;
+ padding: 1em;
+ border-style: solid;
+ border-color: #000000;
+ border-width: 0 3px 3px 1px;
+}
+
+.tabs {
+ margin: 0;
+ padding: 0;
+ border-collapse: collapse;
+}
+
+.tabs td {
+ background: #c8c8c8;
+ border-width: 1px;
+}
+
+.tabs td.selected {
+ background: #f8f8f8;
+ border-width: 1px 3px 0 1px;
+}
+
+.tabs td.spacer {
+ background: transparent;
+ border-top: none;
+ border-left: none;
+ border-right: none;
+}
+
+/* other */
+
+.bz_row_odd {
+ background-color: #f0f0f0;
+}
+
+/* Rules specific for printing */
+@media print {
+ #header,
+ #footer,
+ .navigation {
+ display: none;
+ }
+
+ body {
+ background-image: none;
+ background-color: #ffffff;
+ }
+
+ #bugzilla-body {
+ border: none;
+ margin: 0;
+ padding: 0;
+ }
+}
+
+/* other custom stuff */
+select.arch-selector {
+ vertical-align: top;
+}
+
+/* these settings are dusk/gentoo theme specific */
+div#gentoo-bar {
+ background: url(../../../extensions/Gentoo/web/gentoo-header-bar-bg.png) repeat-x;
+ margin-bottom: 2em;
+ margin-top: -1.5em;
+ margin-left: -1.5em;
+ margin-right: -1.5em;
+}
+
+.important-message {
+ border: 2px solid #A40000;
+ background-color: white;
+ padding: .25em;
+ display: inline-block;
+ margin-bottom: 1em;
+ margin-top: 1em;
+}
+
+tr.hilight td, tr.hilight th {
+ font-size: 12pt;
+ padding-top: .5em;
+ padding-bottom: .5em;
+}
+
+tr.vspace td {
+ padding-bottom: 2em;
+}
+
+/* marking restricted security bugs */
+body.bz_group_Security #bugzilla-body {
+ border: 1px solid #A40000;
+}
+
+body.bz_group_Security .bz_alias_short_desc_container:before {
+ content: "CONFIDENTIAL";
+ color: #A40000;
+}
+
+/* vim: set expandtab ts=4: */
diff --git a/skins/contrib/Gentoo/index.css b/skins/contrib/Gentoo/index.css
new file mode 100644
index 000000000..ffb245da9
--- /dev/null
+++ b/skins/contrib/Gentoo/index.css
@@ -0,0 +1,33 @@
+/*
+ * Custom rules for index.css.
+ * The rules you put here override rules in that stylesheet.
+ */
+
+ div#page-index .outro
+ {
+ clear:both;
+ }
+
+ #page-index {
+ max-width: 60%;
+ margin-left: 2em;
+ width: 95%;
+ }
+
+ #page-index table {
+ margin: 0;
+ width: 95%;
+ }
+
+ .intro, .outro {
+ width: auto;
+ }
+
+ .intro {
+ text-align: left;
+ }
+
+ .bz_common_actions_container {
+ width: 490px;
+ margin: auto;
+ } \ No newline at end of file
diff --git a/skins/standard/global.css b/skins/standard/global.css
index be285984d..fea45d1c6 100644
--- a/skins/standard/global.css
+++ b/skins/standard/global.css
@@ -591,6 +591,10 @@ div.user_match {
background-image: none;
background-color: #fff;
}
+
+ div#gentoo-bar {
+ display: none;
+ }
}
/**************/
@@ -739,6 +743,71 @@ pre.field_textarea_readonly {
background-color: #FFEBEB;
}
+/* Gentoo bar */
+body {
+ margin: 1em;
+}
+
+div#gentoo-bar {
+ 0background: url(../../extensions/Gentoo/web/gentoo-header-bar-bg.png) repeat-x;
+ margin-bottom: 2em;
+ margin-top: -1em;
+ margin-left: -1em;
+ margin-right: -1em;
+}
+
+div#gentoo-bar img {
+ border: none !important;
+}
+
+div#gentoo-bar div {
+ padding: .25em;
+}
+
+div#gentoo-bar a.home-link {
+ margin-right: .5em;
+ float: left;
+}
+
+div#gentoo-bar div a {
+ text-decoration: none;
+ padding: .25em;
+ padding-left: .5em;
+ padding-right: .5em;
+ color: #231A3F;
+}
+
+div#gentoo-bar div a:hover {
+ background-color: #A898DD;
+ color: #45347B;
+}
+
+div#gentoo-bar div a.active {
+ background-color: white;
+ font-weight: bold;
+}
+
+/* Marking Security bugs further */
+body.bz_group_Security table.edit_form:before {
+ content: "This bug is a confidential Security issue. Do NOT disclose any of the information. Do NOT commit any fixed ebuilds or distfiles to any repository until the embargo is lifted. Contact security@gentoo.org with any questions.";
+ color: #A40000;
+ font-weight: bold;
+}
+
+body.bz_product_Gentoo_Security #add_comment:after {
+ content: "Note: Please do not mark this bug as resolved after bumping or stabilizing. The Security Team will take care of that. Thanks.";
+ border: 1px solid #3465A4;
+ font-weight: bold;
+ padding: .25em;
+ background-color: white;
+ color: #3465A4;
+ display: block;
+}
+
+body.bz_product_Gentoo_Security #add_comment {
+ margin-bottom: 2em;
+}
+
form th {
text-align: right;
}
diff --git a/skins/standard/index/file-a-bug.png b/skins/standard/index/file-a-bug.png
index cf4c941b6..a3e48c79d 100644
--- a/skins/standard/index/file-a-bug.png
+++ b/skins/standard/index/file-a-bug.png
Binary files differ
diff --git a/skins/standard/index/help.png b/skins/standard/index/help.png
index 6f9035b64..15d8c01d2 100644
--- a/skins/standard/index/help.png
+++ b/skins/standard/index/help.png
Binary files differ
diff --git a/skins/standard/index/new-account.png b/skins/standard/index/new-account.png
index 4ad9ff203..520da209d 100644
--- a/skins/standard/index/new-account.png
+++ b/skins/standard/index/new-account.png
Binary files differ
diff --git a/skins/standard/index/search.png b/skins/standard/index/search.png
index 8d33ebd1e..a41998902 100644
--- a/skins/standard/index/search.png
+++ b/skins/standard/index/search.png
Binary files differ
diff --git a/template/en/default/account/create.html.tmpl b/template/en/default/account/create.html.tmpl
index 5711a726f..fe266e7e0 100644
--- a/template/en/default/account/create.html.tmpl
+++ b/template/en/default/account/create.html.tmpl
@@ -61,6 +61,10 @@
secondary account or free web email service (such as Gmail, Yahoo,
Hotmail, or similar) to avoid receiving spam at your primary email address.
</p>
+<p>
+<b>Please do not use temporary/throwaway email addresses to register, they cause
+too many mail bounces later when developers follow up bugs.</b>
+</p>
[% END %]
<form id="account_creation_form" method="get" action="createaccount.cgi">
diff --git a/template/en/default/admin/users/edit.html.tmpl b/template/en/default/admin/users/edit.html.tmpl
index 2b28aa2d3..144ab0bae 100644
--- a/template/en/default/admin/users/edit.html.tmpl
+++ b/template/en/default/admin/users/edit.html.tmpl
@@ -123,7 +123,10 @@
or <a href="editusers.cgi?action=activity&amp;userid=[% otheruser.id %]"
title="View Account History for '
- [%- otheruser.login FILTER html %]'">View Account History</a>
+ [%- otheruser.login FILTER html %]'">View Account History</a> / <a
+ href="custom_userhistory.cgi?userid=[%- otheruser.id FILTER html %]"
+ title="Custom Account History for '[%- otheruser.login FILTER html
+ %]'">Custom Account History</a>
</p>
</form>
<p>
diff --git a/template/en/default/admin/users/list.html.tmpl b/template/en/default/admin/users/list.html.tmpl
index f90996882..36d8ad137 100644
--- a/template/en/default/admin/users/list.html.tmpl
+++ b/template/en/default/admin/users/list.html.tmpl
@@ -44,6 +44,10 @@
'&amp;userid=%%userid%%' _
listselectionurlparams
}
+ {heading => 'Custom History'
+ content => 'View'
+ contentlink => 'custom_userhistory.cgi?userid=%%userid%%'
+ }
]
%]
diff --git a/template/en/default/attachment/createformcontents.html.tmpl b/template/en/default/attachment/createformcontents.html.tmpl
index 48e4f4af0..e38ab27a9 100644
--- a/template/en/default/attachment/createformcontents.html.tmpl
+++ b/template/en/default/attachment/createformcontents.html.tmpl
@@ -8,10 +8,20 @@
[% max_local = Param('maxlocalattachment') * 1024 %]
[% max_limit = Param('maxattachmentsize')> max_local ? Param('maxattachmentsize') : max_local %]
+<tr>
+ <td class="comment" colspan="2">
+ <ol>
+ <li>Make sure that your user has permissions to read the file you are trying to attach. If you don't, you will most likel get a confusing error message.</li>
+ <li>Please attach build logs and other informational files as <em>plain text</em>. If build log is larger than [% max_limit FILTER html %] KB, please compress it using compression tool such as bzip2 or xz (<em>without</em> tar).</li>
+ <li>Patches, ebuilds and other copyrightable files meant for integration must conform to the Gentoo <a rel='external' href='https://www.gentoo.org/glep/glep-0076.html'>copyright policy</a>. Such conformance must be explicitly acknowledged through <a rel='external' href='https://www.gentoo.org/glep/glep-0076.html#certificate-of-origin'>GCO</a> sign-off, confirmed with <em>your real name</em>.</li>
+ <li>Please attach ebuild updates and new packages preferably as git-format patches with explanatory commit messages (see: <a rel='external' href='https://www.gentoo.org/glep/glep-0066.html'>GLEP 66</a>) and a GCO sign-off. Alternatively, please include the sign-off in a comment when attaching non-patch format files.</li>
+ </ol>
+ </td>
+</tr>
<tr class="attachment_data">
<th><label for="data">File</label>:</th>
<td>
- <em>Enter the path to the file on your computer</em> (or
+ <em>Enter the path to the file on your computer ([%+ Param('maxattachmentsize') %] KB limit)</em> (or
<a id="attachment_data_controller" href="javascript:TUI_toggle_class('attachment_text_field');
javascript:TUI_toggle_class('attachment_data')"
>paste text as attachment</a>).<br>
diff --git a/template/en/default/attachment/edit.html.tmpl b/template/en/default/attachment/edit.html.tmpl
index 184cdde05..8a149cb01 100644
--- a/template/en/default/attachment/edit.html.tmpl
+++ b/template/en/default/attachment/edit.html.tmpl
@@ -152,7 +152,7 @@
<div id="attachment_view_window">
[% IF !attachment.datasize %]
<div><b>The content of this attachment has been deleted.</b></div>
- [% ELSIF !Param("allow_attachment_display") %]
+ [% ELSIF !Param("allow_attachment_display") && !attachment.contenttype.match('^text\/plain$') %]
<div id="view_disabled">
<p><b>
The attachment is not viewable in your browser due to security
diff --git a/template/en/default/attachment/list.html.tmpl b/template/en/default/attachment/list.html.tmpl
index 9676dd67b..a7205ddcd 100644
--- a/template/en/default/attachment/list.html.tmpl
+++ b/template/en/default/attachment/list.html.tmpl
@@ -75,7 +75,8 @@ function toggle_display(link) {
<span class="bz_attach_extra_info">
[% IF attachment.datasize %]
- ([% attachment.datasize FILTER unitconvert %],
+ ([% attachment.filename FILTER html %],
+ [% attachment.datasize FILTER unitconvert %],
[% IF attachment.ispatch %]
patch)
[% ELSE %]
diff --git a/template/en/default/bug/comments.html.tmpl b/template/en/default/bug/comments.html.tmpl
index 931716f76..d82864524 100644
--- a/template/en/default/bug/comments.html.tmpl
+++ b/template/en/default/bug/comments.html.tmpl
@@ -161,7 +161,7 @@
<span class="bz_comment_number">
<a
- href="show_bug.cgi?id=[% bug.bug_id %]#c[% comment.count %]">
+ href="#c[% comment.count %]">
[%- comment_label FILTER html %]</a>
</span>
diff --git a/template/en/default/bug/create/comment-guided.txt.tmpl b/template/en/default/bug/create/comment-guided.txt.tmpl
index e85a36469..67b1f2aaa 100644
--- a/template/en/default/bug/create/comment-guided.txt.tmpl
+++ b/template/en/default/bug/create/comment-guided.txt.tmpl
@@ -7,8 +7,6 @@
#%]
[% USE Bugzilla %]
[% cgi = Bugzilla.cgi %]
-User-Agent: [%+ cgi.user_agent() %]
-Build Identifier: [%+ cgi.param("buildid") %]
[%+ cgi.param("comment") IF cgi.param("comment") %]
diff --git a/template/en/default/bug/create/create-guided.html.tmpl b/template/en/default/bug/create/create-guided.html.tmpl
index 1adae4588..8d3e2b02a 100644
--- a/template/en/default/bug/create/create-guided.html.tmpl
+++ b/template/en/default/bug/create/create-guided.html.tmpl
@@ -38,6 +38,8 @@ function PutDescription() {
}
</script>
+[% INCLUDE 'bug/create/user-message.html.tmpl' %]
+
<h3 id="step1">Step 1 of 3 - has your [% terms.bug %] already been reported?</h3>
<p class="warning">
@@ -144,9 +146,6 @@ function PutDescription() {
</td>
</tr>
- [%# We override rep_platform and op_sys for simplicity. %]
- [% rep_platform = [ "PC", "Macintosh", "All", "Other" ] %]
-
<tr class="guided_form_field">
<th>[% field_descs.rep_platform FILTER html %]</th>
<td>
@@ -154,9 +153,6 @@ function PutDescription() {
</td>
</tr>
- [% op_sys = [ "Windows XP", "Windows Vista", "Windows 7", "Windows 8",
- "Mac OS X", "Linux", "All", "Other" ] %]
-
<tr>
<th>Operating System</th>
<td>
@@ -164,32 +160,16 @@ function PutDescription() {
</td>
</tr>
- [%# Accept URL parameter build ID for non-browser products %]
- [% IF cgi.param("buildid") %]
- [% buildid = cgi.param("buildid") %]
- [% END %]
-
- <tr class="guided_form_field">
- <th>Build Identifier</th>
- <td>
- <input type="text" size="80" name="buildid" value="[% buildid FILTER html %]">
- <p>
- This should identify the exact version of the product you were using.
- If the above field is blank or you know it is incorrect, copy the
- version text from the product's Help |
- About menu (for browsers this will begin with "Mozilla/5.0...").
- If the product won't start, instead paste the complete URL you downloaded
- it from.
- </p>
- </td>
- </tr>
-
<tr>
<th>URL</th>
<td>
<input type="text" size="80" name="bug_file_loc" value="http://">
<p>
- URL that demonstrates the problem you are seeing (optional).
+ URL that demonstrates the problem you are seeing (optional).<br>
+ <span style="color: red">You should never use URL to point to pastebins
+ for error messages, logs, emerge --info output, screenshots or similar
+ information.<br>Instead, these should always be attached to the
+ bug.</span>
</p>
</td>
</tr>
@@ -202,6 +182,7 @@ function PutDescription() {
<p>
A sentence which summarises the problem.
Please be descriptive and use lots of keywords.
+ Include the <b>full</b> package atom with the version!
</p>
<p>
<kbd>
@@ -210,14 +191,14 @@ function PutDescription() {
<br>
<kbd>
<span class="good">Good example</span>:
- crash if I close the mail window while checking for new POP mail
+ mail-client/evolution-2.8.3-r2: crash if I close the mail window while checking for new POP mail
</kbd>
</p>
</td>
</tr>
<tr>
- <th>Details</th>
+ <th>Description</th>
<td>
[% INCLUDE global/textarea.html.tmpl
name = 'comment'
@@ -318,22 +299,14 @@ function PutDescription() {
cols = constants.COMMENT_COLS
%]
<p>
- Add any additional information you feel may be
- relevant to this [% terms.bug %], such as the <b>theme</b> you were
- using (does the [% terms.bug %] still occur
- with the default theme?), or special
- information about <b>your computer's configuration</b>. Any information
- longer than a few lines, such as a <b>stack trace</b> or <b>HTML
- testcase</b>, should be added
- using the "Add an Attachment" link on the [% terms.bug %], after
- it is filed. If you believe that it's relevant, please also include
- your build configuration, obtained by typing <kbd>about:buildconfig</kbd>
- into your URL bar.
- <br>
- <br>
- If you are reporting a crash, note the module in
- which the software crashed (e.g., <kbd>Application Violation in
- gkhtml.dll</kbd>).
+ Add any additional information you feel may be relevant to this
+ [% terms.bug %], such as what other <b>programs you had running</b>,
+ and/or
+ information about <b>your computer's configuration</b>. Any information
+ longer
+ than a few lines, such as a <b>stack trace</b>, should be added using the
+ "Create a new Attachment" link on the bug, after it is filed.<br />
+ <b>Please paste all information from 'emerge --info' in this section!</b>
</p>
</td>
</tr>
@@ -342,6 +315,9 @@ function PutDescription() {
<th>[% field_descs.bug_severity FILTER html %]</th>
<td>
<select name="bug_severity">
+ <option name="blocker" value="blocker">
+ Blocker: Blocks development and/or testing work of Gentoo as a whole
+ </option>
<option name="critical" value="critical">
Critical: The software crashes, hangs, or causes you to
lose data.
@@ -360,7 +336,8 @@ function PutDescription() {
misaligned text.
</option>
<option name="enhancement" value="enhancement">
- Enhancement: Request for new feature or enhancement.
+ Enhancement: Request for new feature or enhancement, including ebuild
+ submissions.
</option>
</select>
<p>
diff --git a/template/en/default/bug/create/create.html.tmpl b/template/en/default/bug/create/create.html.tmpl
index 61faf1c1a..35c2a9964 100644
--- a/template/en/default/bug/create/create.html.tmpl
+++ b/template/en/default/bug/create/create.html.tmpl
@@ -82,6 +82,8 @@ TUI_hide_default('attachment_text_field');
onsubmit="return validateEnterBug(this)">
<input type="hidden" name="product" value="[% product.name FILTER html %]">
<input type="hidden" name="token" value="[% token FILTER html %]">
+<input type="hidden" name="version"
+ value="[% default.version FILTER html %]">
<table>
<tbody>
@@ -184,21 +186,6 @@ TUI_hide_default('attachment_text_field');
</tr>
<tr>
- [% INCLUDE "bug/field-label.html.tmpl"
- field = bug_fields.version editable = 1 rowspan = 4
- %]
- <td rowspan="4">
- <select name="version" id="version" size="5" aria-required="true"
- class="required">
- [%- FOREACH v = version %]
- [% NEXT IF NOT v.is_active %]
- <option value="[% v.name FILTER html %]"
- [% ' selected="selected"' IF v.name == default.version %]>[% v.name FILTER html -%]
- </option>
- [%- END %]
- </select>
- </td>
-
[% INCLUDE bug/field.html.tmpl
bug = default, field = bug_fields.bug_severity, editable = 1,
value = default.bug_severity %]
@@ -347,6 +334,8 @@ TUI_hide_default('attachment_text_field');
%]
</td>
</tr>
+
+ [% Hook.process("after_cc_field") %]
<tr>
<th>Default [% field_descs.cc FILTER html %]:</th>
@@ -468,11 +457,6 @@ TUI_hide_default('attachment_text_field');
<td colspan="3">
[% defaultcontent = BLOCK %]
- [% IF cloned_bug_id %]
-+++ This [% terms.bug %] was initially created as a clone of [% terms.Bug %] #[% cloned_bug_id %] +++
-
-
- [% END %]
[%-# We are within a BLOCK. The comment will be correctly HTML-escaped
# by global/textarea.html.tmpl. So we must not escape the comment here. %]
[% comment FILTER none %]
diff --git a/template/en/default/bug/create/user-message.html.tmpl b/template/en/default/bug/create/user-message.html.tmpl
index 197cf1ad8..485051312 100644
--- a/template/en/default/bug/create/user-message.html.tmpl
+++ b/template/en/default/bug/create/user-message.html.tmpl
@@ -16,7 +16,7 @@
#%]
Before reporting [% terms.abug %], please read the
-<a href="page.cgi?id=bug-writing.html">
+<a href="https://www.gentoo.org/doc/en/bugzilla-howto.xml">
[% terms.bug %] writing guidelines</a>, please look at the list of
<a href="duplicates.cgi">most frequently reported [% terms.bugs %]</a>, and please
-<a href="query.cgi">search</a> for the [% terms.bug %].
+<a href="query.cgi?format=specific">search</a> (<a href="query.cgi">advanced</a>) for the [% terms.bug %].
diff --git a/template/en/default/bug/edit.html.tmpl b/template/en/default/bug/edit.html.tmpl
index b8abe6bc5..dcadb89d1 100644
--- a/template/en/default/bug/edit.html.tmpl
+++ b/template/en/default/bug/edit.html.tmpl
@@ -260,13 +260,6 @@
editable = bug.check_can_change_field('component', 0, 1)
%]
</tr>
- <tr>
- [% INCLUDE "bug/field-label.html.tmpl"
- field = bug_fields.version
- editable = bug.check_can_change_field('version', 0, 1) %]
-
- [% PROCESS select selname => "version" %]
- </tr>
[%############%]
[%# PLATFORM #%]
[%############%]
@@ -787,6 +780,7 @@
multiple => 5
%]
</div>
+ [% Hook.process("after_cc_field", 'bug/edit.html.tmpl') %]
[% END %]
[% IF bug.cc.size %]
<select id="cc" multiple="multiple" size="5" [% 'name="cc"' IF bug.user.canedit %]>
@@ -812,7 +806,7 @@
[% END %]
[% END %]
</div>
- [% IF user.id || bug.cc.size %]
+ [% IF !user.id && bug.cc.size %]
<script type="text/javascript">
hideEditableField( 'cc_edit_area_showhide_container',
'cc_edit_area',
@@ -1043,7 +1037,10 @@
[%############################################################################%]
[% BLOCK section_timetracking %]
- <table class="bz_time_tracking_table">
+ <div id="bz_time_tracking_showhide_container" class="bz_default_hidden">
+ (<a href="#" id="bz_time_tracking_showhide">show time tracking data</a>)
+ </div>
+ <table class="bz_time_tracking_table" id="bz_time_tracking_table">
<tr>
[% INCLUDE "bug/field-label.html.tmpl"
field = bug_fields.estimated_time, editable = 1
@@ -1111,6 +1108,13 @@
</td>
</tr>
</table>
+ <script type="text/javascript">
+ hideEditableField( 'bz_time_tracking_showhide_container',
+ 'bz_time_tracking_table',
+ 'bz_time_tracking_showhide_container',
+ '',
+ '');
+ </script>
[% END %]
[%############################################################################%]
@@ -1121,7 +1125,7 @@
<div id="add_comment" class="bz_section_additional_comments">
[% IF user.id %]
<label for="comment" accesskey="c"><b>Additional
- <u>C</u>omments</b></label>:
+ <u>C</u>omments</b></label>: <em>(this is where you put emerge --info)</em>
[% IF user.is_insider && bug.check_can_change_field('longdesc', 0, 1) %]
<input type="checkbox" name="comment_is_private" value="1"
@@ -1167,6 +1171,44 @@
</table>
</td></tr></table>
+ [% IF feature_enabled('jsonrpc') AND bug.assigned_to.login == 'bug-wranglers@gentoo.org' %]
+ [%# This seciton mostly left unindented to make updates from create.html.tmpl easier. %]
+ <table>
+ <tr id="possible_duplicates_container">
+ <th>Possible<br>Duplicates:</th>
+ <td colspan="3">
+ <div id="possible_duplicates"></div>
+ <script type="text/javascript">
+ var dt_columns = [
+ { key: "id", label: "[% field_descs.bug_id FILTER js %]",
+ formatter: YAHOO.bugzilla.dupTable.formatBugLink },
+ { key: "summary",
+ label: "[% field_descs.short_desc FILTER js %]",
+ formatter: "text" },
+ { key: "status",
+ label: "[% field_descs.bug_status FILTER js %]",
+ formatter: YAHOO.bugzilla.dupTable.formatStatus },
+ { key: "update_token", label: '',
+ formatter: YAHOO.bugzilla.dupTable.formatCcButton }
+ ];
+ YAHOO.bugzilla.dupTable.addCcMessage = "Add Me to the CC List";
+ YAHOO.bugzilla.dupTable.init({
+ container: 'possible_duplicates',
+ columns: dt_columns,
+ product_name: '[% product.name FILTER js %]',
+ summary_field: 'short_desc',
+ options: {
+ MSG_LOADING: 'Searching for possible duplicates...',
+ MSG_EMPTY: 'No possible duplicates found.',
+ SUMMARY: 'Possible Duplicates'
+ }
+ });
+ </script>
+ </td>
+ </tr>
+ </table><br>
+ [% END %]
+
[%# For logged-out users %]
[% ELSE %]
<table>
diff --git a/template/en/default/bug/field-help.none.tmpl b/template/en/default/bug/field-help.none.tmpl
index 2b6096547..af6c6ac96 100644
--- a/template/en/default/bug/field-help.none.tmpl
+++ b/template/en/default/bug/field-help.none.tmpl
@@ -152,10 +152,6 @@ target_milestone =>
"The $vars.field_descs.target_milestone field is used to define when the"
_ " engineer the $terms.bug is assigned to expects to fix it.",
-version =>
- "The version field defines the version of the software the"
- _ " $terms.bug was found in.",
-
votes =>
"Some $terms.bugs can be voted for, and you can limit your search to"
_ " $terms.bugs with more than a certain number of votes.",
diff --git a/template/en/default/bug/format_comment.txt.tmpl b/template/en/default/bug/format_comment.txt.tmpl
index b8d67429f..3e0218caf 100644
--- a/template/en/default/bug/format_comment.txt.tmpl
+++ b/template/en/default/bug/format_comment.txt.tmpl
@@ -30,7 +30,12 @@ X[% comment_body %]
[% ELSIF comment.type == constants.CMT_ATTACHMENT_CREATED %]
Created attachment [% comment.extra_data %]
[% IF is_bugmail %]
- --> [% urlbase _ "attachment.cgi?id=" _ comment.extra_data _ "&action=edit" %]
+ [% IF ( httpbase && sslbase ) && ssl_redirect == 0 %]
+ --> Clear-Text: [% httpbase _ "attachment.cgi?id=" _ comment.extra_data %]
+ --> Secure: [% sslbase _ "attachment.cgi?id=" _ comment.extra_data %]
+ [% ELSE %]
+ --> [% urlbase _ "attachment.cgi?id=" _ comment.extra_data %]
+ [% END %]
[% END %]
[%+ comment.attachment.description %]
diff --git a/template/en/default/bug/navigate.html.tmpl b/template/en/default/bug/navigate.html.tmpl
index b5e3ba7a2..21db1128f 100644
--- a/template/en/default/bug/navigate.html.tmpl
+++ b/template/en/default/bug/navigate.html.tmpl
@@ -17,6 +17,10 @@
<li>&nbsp;-&nbsp;<a href="enter_bug.cgi?cloned_bug_id=
[% bug.bug_id FILTER uri %]">Clone This
[% terms.Bug %]</a></li>
+ <li>&nbsp;-&nbsp;<a href="enter_bug.cgi?cloned_bug_id=
+ [% bug.bug_id FILTER uri %]&product=
+ [% bug.product FILTER uri %]">Clone In The Same
+ Product</a></li>
[%# Links to more things users can do with this bug. %]
[% Hook.process("links") %]
<li>&nbsp;-&nbsp;<a href="#">Top of page </a></li>
diff --git a/template/en/default/bug/process/verify-new-product.html.tmpl b/template/en/default/bug/process/verify-new-product.html.tmpl
index c562bf54d..affd6b674 100644
--- a/template/en/default/bug/process/verify-new-product.html.tmpl
+++ b/template/en/default/bug/process/verify-new-product.html.tmpl
@@ -60,19 +60,6 @@
<table>
<tr>
<td>
- <b>Version:</b><br>
- [% IF versions.size == 1 %]
- [% SET default_version = versions.0 %]
- [% ELSE %]
- [% SET default_version = defaults.version %]
- [% END %]
- [% PROCESS "global/select-menu.html.tmpl"
- name="version"
- options=versions
- default=default_version
- size=10 %]
- </td>
- <td>
<b>Component:</b><br>
[% IF components.size == 1 %]
[% SET default_component = components.0 %]
diff --git a/template/en/default/bug/show-header.html.tmpl b/template/en/default/bug/show-header.html.tmpl
index 583708492..a5479c7af 100644
--- a/template/en/default/bug/show-header.html.tmpl
+++ b/template/en/default/bug/show-header.html.tmpl
@@ -31,8 +31,13 @@
[% javascript_urls.push('js/comment-tagging.js')
IF user.id && Param('comment_taggers_group') %]
[% IF bug.defined %]
+ [%# These deps added for duplicate handling in edit.html.templ %]
+ [% IF feature_enabled('jsonrpc') AND bug.assigned_to.login == 'bug-wranglers@gentoo.org' %]
+ [% yui.push('datatable') %]
+ [% javascript_urls.push('js/bug.js') %]
+ [% END %]
[% header = "$terms.Bug&nbsp;$bug.bug_id" %]
- [% header_addl_info = "Last modified: $filtered_timestamp" %]
+ [% header_addl_info = "Last modified: $filtered_timestamp node ${constants.GENTOO_NODE}" %]
[% unfiltered_title = "$bug.bug_id – " %]
[% IF bug.alias.size %]
[% unfiltered_title = unfiltered_title _ "(" _ bug.alias.join(', ') _ ") " %]
diff --git a/template/en/default/email/bugmail-header.txt.tmpl b/template/en/default/email/bugmail-header.txt.tmpl
index 286c70bcd..52e24563e 100644
--- a/template/en/default/email/bugmail-header.txt.tmpl
+++ b/template/en/default/email/bugmail-header.txt.tmpl
@@ -10,14 +10,21 @@
[% isnew = bug.lastdiffed ? 0 : 1 %]
[% show_new = isnew
&& (to_user.settings.bugmail_new_prefix.value == 'on') %]
+[% cc = [] %]
+[% FOREACH cc_user IN bug.cc_users %]
+[% cc.push(cc_user.login_name) %]
+[% END %]
From: [% Param('mailfrom') %]
To: [% to_user.email %]
Subject: [[% terms.Bug %] [%+ bug.id %]] [% 'New: ' IF show_new %][%+ bug.short_desc %]
Date: [% date %]
+Reply-To: DO NOT REPLY <devnull@localhost.invalid>
X-Bugzilla-Reason: [% reasonsheader %]
X-Bugzilla-Type: [% bugmailtype %]
X-Bugzilla-Watch-Reason: [% reasonswatchheader %]
[%+ INCLUDE "email/header-common.txt.tmpl" %]
X-Bugzilla-Changed-Fields: [% changedfields.join(" ") %]
+X-Bugzilla-Reporter: [% bug.reporter.login_name %]
+X-Bugzilla-CC: [% cc.join(", ") %]
[%+ threadingmarker %]
diff --git a/template/en/default/global/choose-product.html.tmpl b/template/en/default/global/choose-product.html.tmpl
index a1582532a..f27055e66 100644
--- a/template/en/default/global/choose-product.html.tmpl
+++ b/template/en/default/global/choose-product.html.tmpl
@@ -32,8 +32,56 @@
[% previous_params = Bugzilla.cgi.canonicalise_query('classification', 'product') %]
<h2>[% h2 FILTER html %]</h2>
+[% IF target == "enter_bug.cgi" %]
+<div class="important-message">
+ Please consult the <a href="https://www.gentoo.org/doc/en/bugzilla-howto.xml#doc_chap6">Bug Reporting Guide</a>
+ on how to choose the proper product for your issue.
+</div>
+[% END %]
<table id="choose_product">
+[% IF target == "enter_bug.cgi" %]
+ <tr><td colspan="2">If you are <strong>unsure</strong>, please file your bug in this product:</td></tr>
+ <tr class="hilight">
+ <th align="right" valign="top">
+ <a href="[% target %]?product=Gentoo%20Linux
+ [%- IF previous_params %]&amp;[% previous_params FILTER none %][% END -%]">
+ Gentoo&nbsp;Linux</a>:&nbsp;
+ </th>
+ <td valign="top"><b>The Gentoo Linux Distribution &ndash; Ebuilds and System related issues</b></td>
+ </tr>
+ <tr class="vspace">
+ <td>&nbsp;</td>
+ <td>
+ <strong>Always attach the output of <tt>emerge --info</tt> to your bug report!</strong>
+ <br /><br />
+ Before reporting a bug, please make sure the issue you are about to file
+ is not the result of a misconfiguration on your part.<br />
+ Our other support venues (for instance the
+ <a href="https://forums.gentoo.org/">Forums</a> or
+ <a href="https://www.gentoo.org/main/en/irc.xml">IRC channels</a>)
+ can help you find out whether the issue warrants a bug report.
+ <br /><br />
+ Examples for bugs in this product:
+ <ul>
+ <li>New package and version bump requests</li>
+ <li>Compile errors (please follow the instructions contained in the error message!)</li>
+ <li>Application crashes (be sure to have a <a href="https://www.gentoo.org/proj/en/qa/backtraces.xml">backtrace</a> available)</li>
+ <li>General issues regarding your Gentoo system</li>
+ </ul>
+
+ Examples for bugs that should <span style="color: #a40000;">NOT</span> be filed here:
+ <ul>
+ <li>Security updates (use <em>Gentoo Security</em> below)</li>
+ <li>Documentation updates (use <em>Documentation</em> below)</li>
+ <li>Issues regarding our website and infrastructure (use <em>Gentoo Infrastructure</em> or <em>Website www.gentoo.org</em> below)</li>
+ <li>Issues about <em>OpenRC</em>, <em>genkernel</em>, or <em>catalyst</em> (use <em>Hosted projects</em> below)</li>
+ </ul>
+ </td></tr>
+ <tr>
+ <td colspan="2"><hr />Or, use one of the following products <strong>if you know that your choice is correct:</strong></td>
+ </tr>
+[% END %]
[% FOREACH c = classifications %]
[% IF c.object %]
diff --git a/template/en/default/global/common-links.html.tmpl b/template/en/default/global/common-links.html.tmpl
index 78b4eb80a..c003b85b0 100644
--- a/template/en/default/global/common-links.html.tmpl
+++ b/template/en/default/global/common-links.html.tmpl
@@ -11,9 +11,10 @@
<ul class="links">
<li><a href="./">Home</a></li>
- <li><span class="separator">| </span><a href="enter_bug.cgi">New</a></li>
+ <li><span class="separator">| </span><a href="enter_bug.cgi?format=guided">New</a>&ndash;<a href="enter_bug.cgi">[Ex]</a></li>
<li><span class="separator">| </span><a href="describecomponents.cgi">Browse</a></li>
<li><span class="separator">| </span><a href="query.cgi">Search</a></li>
+ <li><span class="separator">| </span><a href="https://wiki.gentoo.org/wiki/Foundation:Privacy_Policy">Privacy Policy</a></li>
<li class="form">
<span class="separator">| </span>
diff --git a/template/en/default/global/header.html.tmpl b/template/en/default/global/header.html.tmpl
index bd40fff88..287209582 100644
--- a/template/en/default/global/header.html.tmpl
+++ b/template/en/default/global/header.html.tmpl
@@ -219,6 +219,8 @@
[%+ class FILTER css_class_quote %]
[% END %] yui-skin-sam">
+[% Hook.process("after_body_start") %]
+
<div id="header">
[% INCLUDE global/banner.html.tmpl %]
diff --git a/template/en/default/global/user-error.html.tmpl b/template/en/default/global/user-error.html.tmpl
index 69afaf46a..cc1ad1994 100644
--- a/template/en/default/global/user-error.html.tmpl
+++ b/template/en/default/global/user-error.html.tmpl
@@ -283,14 +283,15 @@
<ul>
<li><code>show_bug.cgi</code> in a Bugzilla
installation.</li>
- <li>A b[% %]ug on launchpad.net</li>
+ <li>A b[% %]ug on launchpad.net.</li>
<li>An issue on code.google.com.</li>
- <li>A b[% %]ug on b[% %]ugs.debian.org.</li>
+ <li>A b[% %]ug on b[% %]ugs.debian.org or deb[% %]ugs.gnu.org.</li>
<li>An issue in a JIRA installation.</li>
<li>A ticket in a Trac installation.</li>
<li>A b[% %]ug in a MantisBT installation.</li>
<li>A b[% %]ug on sourceforge.net.</li>
<li>An issue/pull request on github.com.</li>
+ <li>A task on a Flyspray tracking system.</li>
[% Hook.process('bug_url_invalid_tracker') %]
</ul>
[% END %]
@@ -341,7 +342,27 @@
[% ELSIF error == "comment_too_long" %]
[% title = "Comment Too Long" %]
Comments cannot be longer than
- [%+ constants.MAX_COMMENT_LENGTH FILTER html %] characters.
+ [%+ constants.MAX_COMMENT_LENGTH FILTER html %] characters. If you need to
+ post contents of files or logs, use the attachment feature instead.
+
+ [% ELSIF error == "comment_tag_disabled" %]
+ [% title = "Comment Tagging Disabled" %]
+ The comment tagging is not enabled.
+
+ [% ELSIF error == "comment_tag_invalid" %]
+ [% title = "Invalid Comment Tag" %]
+ The comment tag "[% tag FILTER html %]" contains invalid characters or
+ words.
+
+ [% ELSIF error == "comment_tag_too_long" %]
+ [% title = "Comment Tag Too Long" %]
+ Comment tags cannot be longer than
+ [%+ constants.MAX_COMMENT_TAG_LENGTH FILTER html %] characters.
+
+ [% ELSIF error == "comment_tag_too_short" %]
+ [% title = "Comment Tag Too Short" %]
+ Comment tags must be at least
+ [%+ constants.MIN_COMMENT_TAG_LENGTH FILTER html %] characters.
[% ELSIF error == "comment_tag_disabled" %]
[% title = "Comment Tagging Disabled" %]
@@ -671,7 +692,8 @@
kilobytes (KB) in size. Attachments cannot be more than
[%# Hack to get the max value between both limits %]
[%+ max_limit.nsort.last FILTER html %] KB. <br>
- We recommend that you store your attachment elsewhere
+ We recommend that you try to compress the file by using bzip2, gzip, tar or
+ lzma alternatively you may want to store your attachment elsewhere
and then paste the URL to this file on the attachment
creation page in the appropriate text field, which you can access by
clicking the "paste text as attachment" link.
diff --git a/template/en/default/global/variables.none.tmpl b/template/en/default/global/variables.none.tmpl
index 4587d229f..e624c9f96 100644
--- a/template/en/default/global/variables.none.tmpl
+++ b/template/en/default/global/variables.none.tmpl
@@ -27,7 +27,7 @@
"comment" => "comment",
"comments" => "comments",
"zeroSearchResults" => "Zarro Boogs found",
- "Bugzilla" => "Bugzilla"
+ "Bugzilla" => "Gentoo's Bugzilla"
}
%]
diff --git a/template/en/default/index.html.tmpl b/template/en/default/index.html.tmpl
index 84a5b7d5c..9cf0dfb1d 100644
--- a/template/en/default/index.html.tmpl
+++ b/template/en/default/index.html.tmpl
@@ -13,7 +13,7 @@
[% PROCESS global/header.html.tmpl
title = "$terms.Bugzilla Main Page"
header = "Main Page"
- header_addl_info = "version $constants.BUGZILLA_VERSION"
+ header_addl_info = "version ${constants.BUGZILLA_VERSION}${constants.GENTOO_APPEND_VERSION} node ${constants.GENTOO_NODE}"
%]
[% IF release %]
@@ -78,7 +78,7 @@
[% IF user.id %]
href="userprefs.cgi"><span>User Preferences</span></a>
[% ELSIF Param('createemailregexp') && user.authorizer.user_can_create_account %]
- href="createaccount.cgi"><span>Open a New Account</span></a>
+ href="createaccount.cgi"><span>New Account</span></a>
[% ELSE %]
href="?GoAheadAndLogIn=1"><span>Log In</span></a>
[% END %]
diff --git a/template/en/default/list/edit-multiple.html.tmpl b/template/en/default/list/edit-multiple.html.tmpl
index e581f0892..878f9954b 100644
--- a/template/en/default/list/edit-multiple.html.tmpl
+++ b/template/en/default/list/edit-multiple.html.tmpl
@@ -61,13 +61,6 @@
%]
</td>
- <th><label for="version">Version:</label></th>
- <td>
- [% PROCESS selectmenu menuname = "version"
- menuitems = versions
- property = "" %]
- </td>
-
</tr>
<tr>
diff --git a/template/en/default/pages/fields.html.tmpl b/template/en/default/pages/fields.html.tmpl
index 8db61c519..6ac25e4f4 100644
--- a/template/en/default/pages/fields.html.tmpl
+++ b/template/en/default/pages/fields.html.tmpl
@@ -102,7 +102,7 @@
<dd class="resolved">
A resolution has been performed, and it is awaiting verification by
QA. From here [% terms.bugs %] are either reopened and given some
- open status, or are verified by QA and marked
+ open status, or are verified by an impacted user or QA and marked
<b>[% display_value("bug_status", "VERIFIED") FILTER html %]</b>.
</dd>
@@ -164,7 +164,66 @@
behavior would occur. If more information appears later,
the [% terms.bug %] can be reopened.
</dd>
-
+
+ <dt class="cantfix">
+ [% display_value("resolution", "CANTFIX") FILTER html %]
+ </dt>
+ <dd class="cantfix">
+ Fixing this [% terms.bug %] is not possible, and the reasoning
+ should be detailed by the closer. Generally speaking, fixing the
+ [% terms.bug %] might be technically unfeasible, require an
+ extraordinary (unreasonable) amount of effort, be disallowed for
+ legal/social reasons, or not within bounds of the current framework.
+ If circumstances change later on that affect the justification, the
+ [% terms.bug %] can be reopened and reconsidered.
+ </dd>
+
+ <dt class="needinfo">
+ [% display_value("resolution", "NEEDINFO") FILTER html %]
+ </dt>
+ <dd class="needinfo">
+ Before investigation can proceed further, the reporter needs to
+ provide more information. This often includes clarification of
+ already posted details, adding missing log files, or running some
+ tests to help debug the situation. Once the requested information
+ has been provided, the [% terms.bug %] should be re-opened. This
+ status is frequently used when the expectation is the submitter
+ has disappeared and won't follow up, or to help clear out open
+ reports that still need triaging.
+ </dd>
+
+ <dt class="testrequest">
+ [% display_value("resolution", "TEST-REQUEST") FILTER html %]
+ </dt>
+ <dd class="testrequest">
+ The [% terms.bug %] is thought to be fixed, but we would like
+ someone who is affected to verify the fix. If the [% terms.bug %]
+ is not actually fixed, then the report should be reopened and more
+ details posted (explaining why people believe the [% terms.bug %]
+ is not actually fixed).
+ </dd>
+
+ <dt class="upstream">
+ [% display_value("resolution", "UPSTREAM") FILTER html %]
+ </dt>
+ <dd class="upstream">
+ The requested [% terms.bug %] is considered to be out of the purview
+ of the distro and should be submitted/discussed directly with the
+ respective upstream project. This could include a number of things
+ such as changing default configuration options or behavior, adding
+ new options or functionality, or deleting support for older systems.
+ </dd>
+
+ <dt class="obsolete">
+ [% display_value("resolution", "OBSOLETE") FILTER html %]
+ </dt>
+ <dd class="obsolete">
+ Due to a variety of reasons, the [% terms.bug %] as reported is no
+ longer relevant. It might apply to older versions of a package and
+ the newer versions rewrote (implicitly fixing) the related code, or
+ perhaps support was dropped entirely.
+ </dd>
+
[% Hook.process('resolution') %]
</dl>
</td>
diff --git a/template/en/default/welcome-admin.html.tmpl b/template/en/default/welcome-admin.html.tmpl
index a9b30f68a..7d7b6ab59 100644
--- a/template/en/default/welcome-admin.html.tmpl
+++ b/template/en/default/welcome-admin.html.tmpl
@@ -14,7 +14,7 @@
[% PROCESS global/header.html.tmpl
title = title
- header_addl_info = "version $constants.BUGZILLA_VERSION"
+ header_addl_info = "version ${constants.BUGZILLA_VERSION}${constants.GENTOO_APPEND_VERSION} node ${constants.GENTOO_NODE}"
%]
<div id="welcome-admin">
diff --git a/userprefs.cgi b/userprefs.cgi
index 089708d03..10c62657b 100755
--- a/userprefs.cgi
+++ b/userprefs.cgi
@@ -116,6 +116,9 @@ sub SaveAccount {
check_email_syntax($new_login_name);
is_available_username($new_login_name)
|| ThrowUserError("account_exists", {email => $new_login_name});
+ if(!$user->in_group("editusers")) {
+ ThrowUserError('restricted_email_address', {addr => $new_login_name}) if $new_login_name =~ m/[^\@]+\@gentoo\.org$/ or $user->login =~ m/[^\@]+\@gentoo\.org$/;
+ }
$vars->{'email_token'} = Bugzilla::Token::IssueEmailChangeToken($new_login_name);
$vars->{'email_changes_saved'} = 1;
diff --git a/zzz.txt b/zzz.txt
new file mode 100644
index 000000000..4021ce1fc
--- /dev/null
+++ b/zzz.txt
@@ -0,0 +1 @@
+This is a marker for keepalived. Later on we will run a status system here.