aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.htaccess5
-rw-r--r--Bugzilla/Constants.pm6
-rw-r--r--Bugzilla/Install/Filesystem.pm2
-rw-r--r--Bugzilla/Search/Quicksearch.pm24
-rw-r--r--Bugzilla/Template.pm24
-rwxr-xr-xattachment.cgi2
-rw-r--r--bots.html85
-rwxr-xr-xcustom_buglist.cgi90
-rwxr-xr-xcustom_disabled.cgi41
-rwxr-xr-xcustom_extraperms.cgi73
-rwxr-xr-xcustom_userhistory.cgi135
-rw-r--r--data/.gitignore8
-rw-r--r--docs/en/pdf/Bugzilla-Guide.pdfbin0 -> 518907 bytes
-rwxr-xr-xduplicates.cgi3
-rw-r--r--extensions/Gentoo/Config.pm5
-rw-r--r--extensions/Gentoo/Extension.pm70
-rw-r--r--extensions/Gentoo/template/en/default/hook/bug/create/create-after_cc_field.html.tmpl31
-rw-r--r--extensions/Gentoo/template/en/default/hook/bug/edit-after_cc_field.html.tmpl28
-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.js31
-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/SecureMail/Config.pm49
-rw-r--r--extensions/SecureMail/Extension.pm659
-rw-r--r--extensions/SecureMail/README8
-rw-r--r--extensions/SecureMail/disabled (renamed from extensions/Voting/disabled)0
-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/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.txt22
-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.tmpl2
-rw-r--r--template/en/default/attachment/edit.html.tmpl2
-rw-r--r--template/en/default/bug/create/comment-guided.txt.tmpl2
-rw-r--r--template/en/default/bug/create/create.html.tmpl2
-rw-r--r--template/en/default/bug/create/user-message.html.tmpl4
-rw-r--r--template/en/default/bug/edit.html.tmpl17
-rw-r--r--template/en/default/bug/format_comment.txt.tmpl7
-rw-r--r--template/en/default/bug/show-header.html.tmpl2
-rw-r--r--template/en/default/email/bugmail-header.txt.tmpl7
-rw-r--r--template/en/default/global/common-links.html.tmpl2
-rw-r--r--template/en/default/global/header.html.tmpl2
-rw-r--r--template/en/default/global/user-error.html.tmpl25
-rw-r--r--template/en/default/global/variables.none.tmpl2
-rw-r--r--template/en/default/index.html.tmpl2
-rw-r--r--template/en/default/welcome-admin.html.tmpl2
-rwxr-xr-xuserprefs.cgi3
-rw-r--r--zzz.txt1
93 files changed, 3371 insertions, 42 deletions
diff --git a/.htaccess b/.htaccess
index 2f009697c..6d47416c7 100644
--- a/.htaccess
+++ b/.htaccess
@@ -42,3 +42,8 @@ Options -Indexes
RewriteOptions inherit
RewriteRule ^rest/(.*)$ rest.cgi/$1 [NE]
</IfModule>
+
+<FilesMatch ^custom_buglist.cgi$>
+ allow from 127.0.0.1 94.100.119.160/28
+ deny from all
+</FilesMatch>
diff --git a/Bugzilla/Constants.pm b/Bugzilla/Constants.pm
index b91e007a4..f2fb93297 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;
diff --git a/Bugzilla/Install/Filesystem.pm b/Bugzilla/Install/Filesystem.pm
index 4f133d865..4ed322bc9 100644
--- a/Bugzilla/Install/Filesystem.pm
+++ b/Bugzilla/Install/Filesystem.pm
@@ -162,7 +162,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/Quicksearch.pm b/Bugzilla/Search/Quicksearch.pm
index 830177f8b..f062161d8 100644
--- a/Bugzilla/Search/Quicksearch.pm
+++ b/Bugzilla/Search/Quicksearch.pm
@@ -571,23 +571,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 ce027171b..d5f5034fa 100644
--- a/Bugzilla/Template.pm
+++ b/Bugzilla/Template.pm
@@ -317,7 +317,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
@@ -327,11 +327,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/attachment.cgi b/attachment.cgi
index 5db8f5909..c16e0f012 100755
--- a/attachment.cgi
+++ b/attachment.cgi
@@ -345,7 +345,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
diff --git a/bots.html b/bots.html
new file mode 100644
index 000000000..4794b2651
--- /dev/null
+++ b/bots.html
@@ -0,0 +1,85 @@
+<!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>
+<ul>
+<li>
+ Any direct bug page (more parameters eg for RSS/XML forms are fine):<br />
+ http://bugs.gentoo.org/&lt;BUGID&gt;<br />
+ http://bugs.gentoo.org/show_bug.cgi?id=&lt;BUGID&gt;
+</li>
+<li>
+ Any direct attachment:<br />
+ http://bugs.gentoo.org/attachment.cgi?id=&lt;ATTACHID&gt;
+</li>
+<li>
+ Duplicates report:<br />
+ http://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/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..841c2e00b
--- /dev/null
+++ b/custom_userhistory.cgi
@@ -0,0 +1,135 @@
+#!/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 @bindValues;
+my $query;
+
+print $cgi->header();
+
+my $matchstr = $cgi->param('matchstr');
+my $userid = $cgi->param('userid');
+exit 0 if !defined($matchstr) and !defined($userid);
+
+my $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;
+my $login_name = $matchstr ? $matchstr : user_id_to_login($userid);
+
+if(!$userid || !$login_name) {
+ print "Bad user!<br>";
+ exit(0);
+}
+
+my @bindValues2;
+$query = sprintf
+ '(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 %d) '.
+ 'UNION '.
+ '(SELECT bug_id, bug_when, \'ZZcomment #\' AS field '.
+ 'FROM longdescs '.
+ 'WHERE who=? '.
+ 'ORDER BY bug_when DESC '.
+ 'LIMIT %d) '.
+ '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 %d) '.
+ 'ORDER BY bug_when DESC '.
+ 'LIMIT %d',
+ $limit,$limit,$limit,$limit;
+
+push(@bindValues2, $userid);
+push(@bindValues2, $userid);
+push(@bindValues2, $userid);
+
+#print Dumper($vars);
+printf "%s<br>",$login_name;
+my $actions = $dbh->selectall_arrayref(
+ $query,
+ { Slice => {} },
+ @bindValues2
+);
+
+my $counter = 0;
+foreach my $row (@$actions) {
+ printf "<a href=\"%9d\">%9d</a>: %s %s<br>", $row->{'bug_id'}, $row->{'bug_id'}, $row->{'bug_when'}, $row->{'field'};
+ $counter++;
+}
+printf "History Done. Limit=%d Count=%d<br><br>",$limit,$counter;
+
+$query = '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 p1.userid = ? OR p2.userid = ?
+ORDER BY profiles_when';
+my @bindValues3;
+push(@bindValues3, $userid);
+push(@bindValues3, $userid);
+$actions = $dbh->selectall_arrayref(
+ $query,
+ { Slice => {} },
+ @bindValues3
+);
+
+printf "Applied to %s:<br>",$login_name;
+foreach my $row (@$actions) {
+ printf "%s: by %s: %s%s %s%s<br>", $row->{'profiles_when'}, $row->{'grantor'}, $row->{'oldvalue'} ? '-' : '', $row->{'oldvalue'}, $row->{'newvalue'}? '+' : '', $row->{'newvalue'} if $row->{'grantee_id'} == $userid;
+}
+printf "<br>";
+
+printf "Applied by %s:<br>",$login_name;
+foreach my $row (@$actions) {
+ printf "%s: to %s: %s%s %s%s<br>", $row->{'profiles_when'}, $row->{'grantee'}, $row->{'oldvalue'} ? '-' : '', $row->{'oldvalue'}, $row->{'newvalue'}? '+' : '', $row->{'newvalue'} if $row->{'grantor_id'} == $userid;
+}
+printf "<br>";
+
+$query = '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
+ORDER BY watcher,watched
+';
+$actions = $dbh->selectall_arrayref(
+ $query,
+ { Slice => {} },
+);
+printf "Watchers of %s:<br>", $login_name;
+foreach my $row (@$actions) {
+printf "%s<br>", $row->{'watcher'} if $row->{'watched_id'} == $userid;
+}
+printf "<br>";
+
+printf "Watched by %s:<br>", $login_name;
+foreach my $row (@$actions) {
+printf "%s<br>", $row->{'watched'} if $row->{'watcher_id'} == $userid;
+}
+printf "<br>";
+
+printf "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/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..93301cbdd
--- /dev/null
+++ b/extensions/Gentoo/Extension.pm
@@ -0,0 +1,70 @@
+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 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'
+ );
+
+ $constants->{GENTOO_NODE} = $nodemap{(uname())[1]} ? $nodemap{(uname())[1]} : "unknown";
+ $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..a79c57c39
--- /dev/null
+++ b/extensions/Gentoo/template/en/default/hook/bug/create/create-after_cc_field.html.tmpl
@@ -0,0 +1,31 @@
+<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="hppa@gentoo.org">HPPA</option>
+ <option value="ia64@gentoo.org">IA64</option>
+ <option value="ppc64@gentoo.org">PPC64</option>
+ <option value="ppc@gentoo.org">PPC</option>
+ <option value="sparc@gentoo.org">SPARC</option>
+ <option value="x86@gentoo.org">X86</option>
+ <optgroup label="Unstable arches">
+ <option value="arm64@gentoo.org">ARM64</option>
+ <option value="bsd@gentoo.org">BSD</option>
+ <option value="x86-fbsd@gentoo.org">X86 FBSD</option>
+ <option value="amd64-fbsd@gentoo.org">AMD64 FBSD</option>
+ <option value="m68k@gentoo.org">M68K</option>
+ <option value="mips@gentoo.org">MIPS</option>
+ <option value="s390@gentoo.org">S390</option>
+ <option value="sh@gentoo.org">SH</option>
+ </optgroup>
+ <optgroup label="Other teams">
+ <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..280a81c9c
--- /dev/null
+++ b/extensions/Gentoo/template/en/default/hook/bug/edit-after_cc_field.html.tmpl
@@ -0,0 +1,28 @@
+<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="hppa@gentoo.org">HPPA</option>
+ <option value="ia64@gentoo.org">IA64</option>
+ <option value="ppc64@gentoo.org">PPC64</option>
+ <option value="ppc@gentoo.org">PPC</option>
+ <option value="sparc@gentoo.org">SPARC</option>
+ <option value="x86@gentoo.org">X86</option>
+ <optgroup label="Unstable arches">
+ <option value="arm64@gentoo.org">ARM64</option>
+ <option value="bsd@gentoo.org">BSD</option>
+ <option value="x86-fbsd@gentoo.org">X86 FBSD</option>
+ <option value="amd64-fbsd@gentoo.org">AMD64 FBSD</option>
+ <option value="m68k@gentoo.org">M68K</option>
+ <option value="mips@gentoo.org">MIPS</option>
+ <option value="s390@gentoo.org">S390</option>
+ <option value="sh@gentoo.org">SH</option>
+ </optgroup>
+ <optgroup label="Other teams">
+ <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..99093b8b0
--- /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="http://www.gentoo.org/">Gentoo Home</a>
+ <a href="http://www.gentoo.org/doc/en/index.xml">Documentation</a>
+ <a href="http://forums.gentoo.org/">Forums</a>
+ <a href="http://www.gentoo.org/main/en/lists.xml">Lists</a>
+ <a href="/" class="active">Bugs</a>
+ <a href="http://planet.gentoo.org">Planet</a>
+ <a href="http://store.gentoo.org">Store</a>
+ <a href="http://wiki.gentoo.org">Wiki <span style="font-size: smaller; font-style: italic;">new!</span></a>
+ <strong><a href="http://www.gentoo.org/main/en/where.xml">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..b3e88b46d
--- /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="http://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..2f06e3969
--- /dev/null
+++ b/extensions/Gentoo/web/gentoo.js
@@ -0,0 +1,31 @@
+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;
+ }
+ }
+} \ No newline at end of file
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/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/Voting/disabled b/extensions/SecureMail/disabled
index e69de29bb..e69de29bb 100644
--- a/extensions/Voting/disabled
+++ b/extensions/SecureMail/disabled
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/TypeSniffer/disabled b/extensions/TypeSniffer/disabled
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/extensions/TypeSniffer/disabled
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..3e832535d
--- /dev/null
+++ b/robots-ssl.txt
@@ -0,0 +1,22 @@
+User-agent: *
+Disallow: *
+Disallow: /
+Disallow: /index.cgi
+Disallow: /show_bug.cgi
+Disallow: /attachment.cgi
+Disallow: /data/duplicates.rdf
+Disallow: /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..c7963712e 100644
--- a/template/en/default/attachment/createformcontents.html.tmpl
+++ b/template/en/default/attachment/createformcontents.html.tmpl
@@ -11,7 +11,7 @@
<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/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.html.tmpl b/template/en/default/bug/create/create.html.tmpl
index 61faf1c1a..3e25e9c29 100644
--- a/template/en/default/bug/create/create.html.tmpl
+++ b/template/en/default/bug/create/create.html.tmpl
@@ -347,6 +347,8 @@ TUI_hide_default('attachment_text_field');
%]
</td>
</tr>
+
+ [% Hook.process("after_cc_field") %]
<tr>
<th>Default [% field_descs.cc FILTER html %]:</th>
diff --git a/template/en/default/bug/create/user-message.html.tmpl b/template/en/default/bug/create/user-message.html.tmpl
index 197cf1ad8..a4a90118a 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="http://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..5f6fd60b4 100644
--- a/template/en/default/bug/edit.html.tmpl
+++ b/template/en/default/bug/edit.html.tmpl
@@ -787,6 +787,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 +813,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 +1044,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 +1115,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 +1132,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"
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/show-header.html.tmpl b/template/en/default/bug/show-header.html.tmpl
index 583708492..9ce59130c 100644
--- a/template/en/default/bug/show-header.html.tmpl
+++ b/template/en/default/bug/show-header.html.tmpl
@@ -32,7 +32,7 @@
IF user.id && Param('comment_taggers_group') %]
[% IF bug.defined %]
[% 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/common-links.html.tmpl b/template/en/default/global/common-links.html.tmpl
index 78b4eb80a..e256bb619 100644
--- a/template/en/default/global/common-links.html.tmpl
+++ b/template/en/default/global/common-links.html.tmpl
@@ -11,7 +11,7 @@
<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>
diff --git a/template/en/default/global/header.html.tmpl b/template/en/default/global/header.html.tmpl
index 4d226fd73..3b8dc3a3b 100644
--- a/template/en/default/global/header.html.tmpl
+++ b/template/en/default/global/header.html.tmpl
@@ -218,6 +218,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 7ca8c0298..ada7e4fb8 100644
--- a/template/en/default/global/user-error.html.tmpl
+++ b/template/en/default/global/user-error.html.tmpl
@@ -341,7 +341,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 +691,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..ad8c94d2f 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 %]
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.