aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Bugzilla/Attachment.pm8
-rw-r--r--Bugzilla/Auth/Verify/LDAP.pm4
-rw-r--r--Bugzilla/Bug.pm14
-rw-r--r--Bugzilla/Chart.pm2
-rw-r--r--Bugzilla/DB/Schema.pm3
-rw-r--r--Bugzilla/Install/DB.pm4
-rw-r--r--Bugzilla/Keyword.pm28
-rw-r--r--Bugzilla/Migrate.pm1
-rw-r--r--Bugzilla/Template.pm7
-rw-r--r--Bugzilla/User.pm8
-rw-r--r--Bugzilla/Util.pm1
-rw-r--r--Bugzilla/WebService/Bug.pm1
-rwxr-xr-xattachment.cgi1
-rwxr-xr-xbuglist.cgi39
-rw-r--r--docs/en/rst/administering/keywords.rst6
-rwxr-xr-xeditkeywords.cgi1
-rwxr-xr-xeditusers.cgi7
-rw-r--r--extensions/Mageia/Config.pm28
-rw-r--r--extensions/Mageia/Extension.pm201
-rw-r--r--extensions/Mageia/lib/Util.pm146
-rwxr-xr-xextensions/Mageia/sync_LDAP_groups.pl27
-rw-r--r--extensions/Mageia/template/en/default/bug/create/comment-guided.txt.tmpl10
-rw-r--r--extensions/Mageia/template/en/default/bug/create/create-guided.html.tmpl299
-rw-r--r--extensions/Mageia/template/en/default/global/banner.html.tmpl11
-rw-r--r--extensions/Mageia/template/en/default/hook/README5
-rw-r--r--extensions/Mageia/template/en/default/hook/account/auth/login-small-additional_methods.html.tmpl23
-rw-r--r--extensions/Mageia/template/en/default/hook/account/prefs/account-field.html.tmpl10
-rw-r--r--extensions/Mageia/template/en/default/hook/admin/sanitycheck/messages-statuses.html.tmpl27
-rw-r--r--extensions/Mageia/template/en/default/hook/bug/comments-a_comment-end.html.tmpl76
-rw-r--r--extensions/Mageia/template/en/default/hook/global/header-start.html.tmpl10
-rw-r--r--extensions/Mageia/template/en/default/hook/global/setting-descs-settings.none.tmpl9
-rw-r--r--extensions/Mageia/template/en/default/hook/global/variables-end.none.tmpl9
-rw-r--r--extensions/Mageia/template/en/default/hook/index-additional_links.html.tmpl14
-rw-r--r--extensions/Mageia/template/en/default/mageia/README16
-rw-r--r--extensions/Mageia/web/README7
-rw-r--r--extensions/Mageia/web/style.css52
-rw-r--r--extensions/MoreBugUrl/Extension.pm1
-rw-r--r--extensions/MoreBugUrl/disabled0
-rw-r--r--extensions/MoreBugUrl/lib/Phabricator.pm41
-rw-r--r--extensions/MoreBugUrl/template/en/default/hook/global/user-error-bug_url_invalid_tracker.html.tmpl1
-rwxr-xr-xreports.cgi2
-rw-r--r--skins/standard/buglist.css39
-rw-r--r--t/010dependencies.t2
-rw-r--r--template/en/default/admin/keywords/edit.html.tmpl11
-rw-r--r--template/en/default/admin/keywords/list.html.tmpl56
-rw-r--r--template/en/default/admin/users/list.html.tmpl29
-rw-r--r--template/en/default/admin/users/search.html.tmpl11
-rw-r--r--template/en/default/admin/users/userdata.html.tmpl4
-rw-r--r--template/en/default/attachment/edit.html.tmpl9
-rw-r--r--template/en/default/bug/create/create.html.tmpl2
-rw-r--r--template/en/default/bug/edit.html.tmpl25
-rw-r--r--template/en/default/bug/navigate.html.tmpl2
-rw-r--r--template/en/default/email/bugmail.html.tmpl4
-rw-r--r--template/en/default/email/bugmail.txt.tmpl3
-rw-r--r--template/en/default/global/user-error.html.tmpl2
-rw-r--r--template/en/default/list/list.html.tmpl18
-rw-r--r--template/en/default/list/table.html.tmpl1
-rw-r--r--template/en/default/reports/keywords.html.tmpl73
58 files changed, 1350 insertions, 101 deletions
diff --git a/Bugzilla/Attachment.pm b/Bugzilla/Attachment.pm
index 33183797b..14c81193d 100644
--- a/Bugzilla/Attachment.pm
+++ b/Bugzilla/Attachment.pm
@@ -501,9 +501,8 @@ sub _check_content_type {
# If we have autodetected application/octet-stream from the Content-Type
# header, let's have a better go using a sniffer if available.
- if (defined Bugzilla->input_params->{contenttypemethod}
- && Bugzilla->input_params->{contenttypemethod} eq 'autodetect'
- && $content_type eq 'application/octet-stream'
+ if ((Bugzilla->input_params->{contenttypemethod} // '') eq 'autodetect'
+ && ($content_type eq 'application/octet-stream' || $content_type =~ m{text/x-})
&& Bugzilla->feature('typesniffer'))
{
import File::MimeInfo::Magic qw(mimetype);
@@ -534,8 +533,7 @@ sub _check_content_type {
# Make sure patches are viewable in the browser
if (!ref($invocant)
- && defined Bugzilla->input_params->{contenttypemethod}
- && Bugzilla->input_params->{contenttypemethod} eq 'autodetect'
+ && (Bugzilla->input_params->{contenttypemethod} // '') eq 'autodetect'
&& $content_type =~ m{text/x-(?:diff|patch)})
{
$params->{ispatch} = 1;
diff --git a/Bugzilla/Auth/Verify/LDAP.pm b/Bugzilla/Auth/Verify/LDAP.pm
index e37f55793..3063a19a9 100644
--- a/Bugzilla/Auth/Verify/LDAP.pm
+++ b/Bugzilla/Auth/Verify/LDAP.pm
@@ -26,6 +26,7 @@ use Net::LDAP::Util qw(escape_filter_value);
use constant admin_can_create_account => 0;
use constant user_can_create_account => 0;
+use constant extern_id_used => 1;
sub check_credentials {
my ($self, $params) = @_;
@@ -163,7 +164,8 @@ sub ldap {
ThrowCodeError("ldap_server_not_defined") unless @servers;
foreach (@servers) {
- $self->{ldap} = new Net::LDAP(trim($_));
+ # The 'raw' attribute is used to make sure that data is correctly decoded.
+ $self->{ldap} = new Net::LDAP(trim($_), raw => ';binary');
last if $self->{ldap};
}
ThrowCodeError("ldap_connect_failed", { server => join(", ", @servers) })
diff --git a/Bugzilla/Bug.pm b/Bugzilla/Bug.pm
index 8b4493f85..00231c1c5 100644
--- a/Bugzilla/Bug.pm
+++ b/Bugzilla/Bug.pm
@@ -1848,6 +1848,20 @@ sub _check_keywords {
my $obj = Bugzilla::Keyword->check($keyword);
$keywords{$obj->id} = $obj;
}
+
+ my %old_kw_id;
+ if (blessed $invocant) {
+ my @old_keywords = @{$invocant->keyword_objects};
+ %old_kw_id = map { $_->id => 1 } @old_keywords;
+ }
+
+ foreach my $keyword (values %keywords) {
+ next if $keyword->is_active || exists $old_kw_id{$keyword->id};
+
+ ThrowUserError('value_inactive',
+ { value => $keyword->name, class => ref $keyword });
+ }
+
return [values %keywords];
}
diff --git a/Bugzilla/Chart.pm b/Bugzilla/Chart.pm
index 3c69006aa..2189084fa 100644
--- a/Bugzilla/Chart.pm
+++ b/Bugzilla/Chart.pm
@@ -408,7 +408,7 @@ sub generateDateProgression {
$dateto += (2 * $oneday) / 3;
while ($datefrom < $dateto) {
- push (@progression, time2str("%Y-%m-%d", $datefrom));
+ push (@progression, time2str("%d.%m.%y", $datefrom));
$datefrom += $oneday;
}
diff --git a/Bugzilla/DB/Schema.pm b/Bugzilla/DB/Schema.pm
index d1c1dc7e9..d2742bc73 100644
--- a/Bugzilla/DB/Schema.pm
+++ b/Bugzilla/DB/Schema.pm
@@ -587,6 +587,8 @@ use constant ABSTRACT_SCHEMA => {
PRIMARYKEY => 1},
name => {TYPE => 'varchar(64)', NOTNULL => 1},
description => {TYPE => 'MEDIUMTEXT', NOTNULL => 1},
+ is_active => {TYPE => 'BOOLEAN', NOTNULL => 1,
+ DEFAULT => 'TRUE'},
],
INDEXES => [
keyworddefs_name_idx => {FIELDS => ['name'],
@@ -604,7 +606,6 @@ use constant ABSTRACT_SCHEMA => {
REFERENCES => {TABLE => 'keyworddefs',
COLUMN => 'id',
DELETE => 'CASCADE'}},
-
],
INDEXES => [
keywords_bug_id_idx => {FIELDS => [qw(bug_id keywordid)],
diff --git a/Bugzilla/Install/DB.pm b/Bugzilla/Install/DB.pm
index ed2539251..f7fd8b2ab 100644
--- a/Bugzilla/Install/DB.pm
+++ b/Bugzilla/Install/DB.pm
@@ -732,6 +732,10 @@ sub update_table_definitions {
# 2015-12-16 LpSolit@gmail.com - Bug 1232578
_sanitize_audit_log_table();
+ # 2014-11-18 dylan@mozilla.com - Bug 69267
+ $dbh->bz_add_column('keyworddefs', 'is_active',
+ {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'TRUE'});
+
################################################################
# New --TABLE-- changes should go *** A B O V E *** this point #
################################################################
diff --git a/Bugzilla/Keyword.pm b/Bugzilla/Keyword.pm
index afa93e1e9..ef044a0c5 100644
--- a/Bugzilla/Keyword.pm
+++ b/Bugzilla/Keyword.pm
@@ -26,6 +26,7 @@ use constant DB_COLUMNS => qw(
keyworddefs.id
keyworddefs.name
keyworddefs.description
+ keyworddefs.is_active
);
use constant DB_TABLE => 'keyworddefs';
@@ -33,11 +34,13 @@ use constant DB_TABLE => 'keyworddefs';
use constant VALIDATORS => {
name => \&_check_name,
description => \&_check_description,
+ is_active => \&_check_is_active,
};
use constant UPDATE_COLUMNS => qw(
name
description
+ is_active
);
###############################
@@ -62,6 +65,7 @@ sub bug_count {
sub set_name { $_[0]->set('name', $_[1]); }
sub set_description { $_[0]->set('description', $_[1]); }
+sub set_is_active { $_[0]->set('is_active', $_[1]); }
###############################
#### Subroutines ######
@@ -125,6 +129,10 @@ sub _check_description {
return $desc;
}
+sub _check_is_active { return $_[1] ? 1 : 0 }
+
+sub is_active { return $_[0]->{is_active} }
+
1;
__END__
@@ -145,13 +153,13 @@ Bugzilla::Keyword - A Keyword that can be added to a bug.
Bugzilla::Keyword represents a keyword that can be added to a bug.
-This implements all standard C<Bugzilla::Object> methods. See
+This implements all standard C<Bugzilla::Object> methods. See
L<Bugzilla::Object> for more details.
-=head1 SUBROUTINES
+=head1 METHODS
-This is only a list of subroutines specific to C<Bugzilla::Keyword>.
-See L<Bugzilla::Object> for more subroutines that this object
+This is only a list of methods specific to C<Bugzilla::Keyword>.
+See L<Bugzilla::Object> for more methods that this object
implements.
=over
@@ -166,6 +174,18 @@ implements.
Returns: A reference to an array of Keyword objects, or an empty
arrayref if there are no keywords.
+=item C<is_active>
+
+ Description: Indicates if the keyword may be used on a bug
+ Params: none
+ Returns: a boolean value that is true if the keyword can be applied to bugs.
+
+=item C<set_is_active($is_active)>
+
+ Description: Set the is_active property to a boolean value
+ Params: the new value of the is_active property.
+ Returns: nothing
+
=back
=cut
diff --git a/Bugzilla/Migrate.pm b/Bugzilla/Migrate.pm
index 7865c842d..8db40031c 100644
--- a/Bugzilla/Migrate.pm
+++ b/Bugzilla/Migrate.pm
@@ -27,6 +27,7 @@ use Bugzilla::Version;
use Data::Dumper;
use Date::Parse;
use DateTime;
+use DateTime::TimeZone;
use Fcntl qw(SEEK_SET);
use File::Basename;
use List::Util qw(first);
diff --git a/Bugzilla/Template.pm b/Bugzilla/Template.pm
index decffe1e8..75ab17298 100644
--- a/Bugzilla/Template.pm
+++ b/Bugzilla/Template.pm
@@ -1121,11 +1121,16 @@ sub create {
# Whether or not keywords are enabled, in this Bugzilla.
'use_keywords' => sub { return Bugzilla::Keyword->any_exist; },
- # All the keywords.
+ # All the keywords
'all_keywords' => sub {
return [map { $_->name } Bugzilla::Keyword->get_all()];
},
+ # All the active keywords
+ 'active_keywords' => sub {
+ return [map { $_->name } grep { $_->is_active } Bugzilla::Keyword->get_all()];
+ },
+
'feature_enabled' => sub { return Bugzilla->feature(@_); },
# field_descs can be somewhat slow to generate, so we generate
diff --git a/Bugzilla/User.pm b/Bugzilla/User.pm
index 77e6cebb0..1768d7c48 100644
--- a/Bugzilla/User.pm
+++ b/Bugzilla/User.pm
@@ -2132,10 +2132,16 @@ sub wants_mail {
$relationship = REL_ANY;
}
+ my $wants_mail;
+ Bugzilla::Hook::process('user_wants_mail', { events => $events,
+ relationship => $relationship,
+ wants_mail => \$wants_mail });
+ return $wants_mail if defined $wants_mail;
+
# Skip DB query if relationship is explicit
return 1 if $relationship == REL_GLOBAL_WATCHER;
- my $wants_mail = grep { $self->mail_settings->{$relationship}{$_} } @$events;
+ $wants_mail = grep { $self->mail_settings->{$relationship}{$_} } @$events;
return $wants_mail ? 1 : 0;
}
diff --git a/Bugzilla/Util.pm b/Bugzilla/Util.pm
index 57ce5f6b6..9976b12d6 100644
--- a/Bugzilla/Util.pm
+++ b/Bugzilla/Util.pm
@@ -32,6 +32,7 @@ use Bugzilla::Error;
use Date::Parse;
use Date::Format;
+use DateTime::TimeZone;
use Digest;
use Email::Address;
use List::Util qw(first);
diff --git a/Bugzilla/WebService/Bug.pm b/Bugzilla/WebService/Bug.pm
index b07d3cb01..34e6b6662 100644
--- a/Bugzilla/WebService/Bug.pm
+++ b/Bugzilla/WebService/Bug.pm
@@ -245,6 +245,7 @@ sub _legal_field_values {
elsif ($field_name eq 'keywords') {
my @legal_keywords = Bugzilla::Keyword->get_all;
foreach my $value (@legal_keywords) {
+ next unless $value->is_active;
push (@result, {
name => $self->type('string', $value->name),
description => $self->type('string', $value->description),
diff --git a/attachment.cgi b/attachment.cgi
index 4cd9229fb..57cc8548f 100755
--- a/attachment.cgi
+++ b/attachment.cgi
@@ -503,6 +503,7 @@ sub insert {
# Get the filehandle of the attachment.
my $data_fh = $cgi->upload('data');
my $attach_text = $cgi->param('attach_text');
+ utf8::encode($attach_text) if $attach_text;
my $attachment = Bugzilla::Attachment->create(
{bug => $bug,
diff --git a/buglist.cgi b/buglist.cgi
index daee34c9b..ea6aa6ee5 100755
--- a/buglist.cgi
+++ b/buglist.cgi
@@ -858,9 +858,44 @@ if (@bugidlist) {
while (my ($bug_id, $min_membercontrol) = $sth->fetchrow_array()) {
$min_membercontrol{$bug_id} = $min_membercontrol || CONTROLMAPNA;
}
+
+ my $involved_bugs = {};
+ my $involved_new_bugs = {};
+
+ if ($user->id) {
+ $involved_bugs =
+ $dbh->selectall_hashref('SELECT bugs.bug_id, CASE WHEN last_visit_ts < delta_ts THEN 1 ELSE 0 END AS updated
+ FROM bugs
+ INNER JOIN bug_user_last_visit
+ ON bugs.bug_id = bug_user_last_visit.bug_id
+ WHERE user_id = ?
+ AND ' . $dbh->sql_in('bugs.bug_id', \@bugidlist),
+ 'bug_id', undef, $user->id);
+
+ my ($new_bugs) = diff_arrays(\@bugidlist, [keys %$involved_bugs]);
+ if (@$new_bugs) {
+ my $involved_new_bugs_list =
+ $dbh->selectcol_arrayref('SELECT bugs.bug_id
+ FROM bugs
+ LEFT JOIN cc
+ ON cc.bug_id = bugs.bug_id AND who = ?
+ WHERE (reporter = ? OR assigned_to = ? OR qa_contact = ? OR who = ?)
+ AND ' . $dbh->sql_in('bugs.bug_id', $new_bugs),
+ undef, ($user->id) x 5);
+ %$involved_new_bugs = map { $_ => 1 } @$involved_new_bugs_list;
+ }
+ }
+
foreach my $bug (@bugs) {
- next unless defined($min_membercontrol{$bug->{'bug_id'}});
- if ($min_membercontrol{$bug->{'bug_id'}} == CONTROLMAPMANDATORY) {
+ my $bug_id = $bug->{bug_id};
+ if (my $involved_bug = $involved_bugs->{$bug_id}) {
+ $bug->{last_visit_status} = $involved_bug->{updated} ? 'updated' : 'up_to_date';
+ }
+ elsif ($user->id) {
+ $bug->{last_visit_status} = $involved_new_bugs->{$bug_id} ? 'new' : 'unknown';
+ }
+ next unless defined $min_membercontrol{$bug_id};
+ if ($min_membercontrol{$bug_id} == CONTROLMAPMANDATORY) {
$bug->{'secure_mode'} = 'implied';
}
else {
diff --git a/docs/en/rst/administering/keywords.rst b/docs/en/rst/administering/keywords.rst
index 245bcbe4c..d61a5aea5 100644
--- a/docs/en/rst/administering/keywords.rst
+++ b/docs/en/rst/administering/keywords.rst
@@ -10,7 +10,5 @@ must be fixed by the next release—this keyword can make tracking those
bugs much easier. Keywords are global, rather than per product.
Keywords can be created, edited, or deleted by clicking the "Keywords"
-link in the admin page. There are two fields for each keyword—the keyword
-itself and a brief description. Currently keywords cannot be marked obsolete
-to prevent future usage.
-
+link in the admin page. There are three fields for each keyword—the keyword
+itself, a brief description, and if the keyword is active or not.
diff --git a/editkeywords.cgi b/editkeywords.cgi
index 01f30dbed..e6b62bec3 100755
--- a/editkeywords.cgi
+++ b/editkeywords.cgi
@@ -110,6 +110,7 @@ if ($action eq 'update') {
$keyword->set_all({
name => scalar $cgi->param('name'),
description => scalar $cgi->param('description'),
+ is_active => scalar $cgi->param('is_active'),
});
my $changes = $keyword->update();
diff --git a/editusers.cgi b/editusers.cgi
index 37665b12d..7f0d2e205 100755
--- a/editusers.cgi
+++ b/editusers.cgi
@@ -72,7 +72,7 @@ if ($action eq 'search') {
my $grouprestrict = $cgi->param('grouprestrict') || '0';
# 0 = disabled only, 1 = enabled only, 2 = everyone
my $is_enabled = $cgi->param('is_enabled') // 2;
- my $query = 'SELECT DISTINCT userid, login_name, realname, is_enabled, ' .
+ my $query = 'SELECT DISTINCT userid, login_name, realname, extern_id, is_enabled, ' .
$dbh->sql_date_format('last_seen_date', '%Y-%m-%d') . ' AS last_seen_date ' .
'FROM profiles';
my @bindValues;
@@ -129,6 +129,8 @@ if ($action eq 'search') {
$expr = "profiles.userid";
} elsif ($matchvalue eq 'realname') {
$expr = "profiles.realname";
+ } elsif ($matchvalue eq 'extern_id') {
+ $expr = "profiles.extern_id";
} else {
$expr = "profiles.login_name";
}
@@ -158,7 +160,8 @@ if ($action eq 'search') {
# Handle selection by group.
if ($grouprestrict eq '1') {
- my $grouplist = join(',',
+ my $direct_membership = $cgi->param('group_direct_membership');
+ my $grouplist = $direct_membership ? $group->id : join(',',
@{Bugzilla::Group->flatten_group_membership($group->id)});
$query .= " $nextCondition ugm.group_id IN($grouplist) ";
}
diff --git a/extensions/Mageia/Config.pm b/extensions/Mageia/Config.pm
new file mode 100644
index 000000000..94f2da811
--- /dev/null
+++ b/extensions/Mageia/Config.pm
@@ -0,0 +1,28 @@
+# 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::Mageia;
+
+use 5.16.0;
+use strict;
+use warnings;
+
+use constant NAME => 'Mageia';
+
+use constant REQUIRED_MODULES => [
+ # Required to sync LDAP groups with Bugzilla groups.
+ {
+ package => 'perl-ldap',
+ module => 'Net::LDAP',
+ version => 0
+ }
+];
+
+use constant OPTIONAL_MODULES => [
+];
+
+__PACKAGE__->NAME;
diff --git a/extensions/Mageia/Extension.pm b/extensions/Mageia/Extension.pm
new file mode 100644
index 000000000..257e7782a
--- /dev/null
+++ b/extensions/Mageia/Extension.pm
@@ -0,0 +1,201 @@
+# 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::Mageia;
+
+use 5.16.0;
+use strict;
+use warnings;
+
+use parent qw(Bugzilla::Extension);
+
+use Bugzilla::Constants qw(EVT_CC REL_GLOBAL_WATCHER);
+use Bugzilla::Bug qw(LogActivityEntry);
+use Bugzilla::Field qw(get_field_id);
+use Bugzilla::User qw();
+use Bugzilla::User::Setting qw(add_setting);
+use Bugzilla::Extension::Mageia::Util qw(compare_datetimes sync_ldap_groups_check);
+
+use Email::Address;
+use Encode qw(encode);
+
+# Let's match the Bugzilla version it's written for.
+our $VERSION = '5.0';
+
+# sysadmin-bugs@ml.mageia.org user ID = 175.
+use constant SYSADMIN_USER_ID => 175;
+
+use constant MGA_SETTINGS => {
+ mga_inline_history => { options => ['on', 'off'], default => 'on' },
+};
+
+sub bug_end_of_create_validators {
+ my ($self, $args) = @_;
+
+ # If a user enters 'validated_update' as keyword,
+ # automatically CC sysadmin-bugs@ml.mageia.org.
+ my $keywords = $args->{params}->{keywords};
+
+ if (grep { $_->name eq 'validated_update' } @$keywords) {
+ my $cc_list = $args->{params}->{cc};
+ if (!grep { $_ == SYSADMIN_USER_ID } @$cc_list) {
+ push(@$cc_list, SYSADMIN_USER_ID);
+ }
+ }
+}
+
+sub bug_end_of_update {
+ my ($self, $args) = @_;
+ my $bug = $args->{bug};
+ my $dbh = Bugzilla->dbh;
+ my $user = Bugzilla->user;
+
+ my $new_keywords_str;
+ # Call exists to avoid autovivification of $args->{changes} if it does not exist.
+ # Else $bug->update() always sees the bug as being edited.
+ if (exists $args->{changes} && exists $args->{changes}->{keywords}) {
+ $new_keywords_str = $args->{changes}->{keywords}->[1];
+ }
+
+ # If a user enters 'validated_update' as keyword, automatically
+ # CC sysadmin-bugs@ml.mageia.org.
+ if ($new_keywords_str) {
+ my @new_keywords = split(/[,\s]+/, $new_keywords_str);
+ if (grep { $_ eq 'validated_update' } @new_keywords
+ and !grep { $_->id == SYSADMIN_USER_ID } @{$bug->cc_users})
+ {
+ # Safer to clear the cache and let Bugzilla regenerate them if needed.
+ delete $bug->{cc_users};
+ delete $bug->{cc};
+ $dbh->do('INSERT INTO cc (bug_id, who) VALUES (?, ?)',
+ undef, ($bug->id, SYSADMIN_USER_ID));
+
+ # We also have to update the bug history to reflect this change.
+ my $changed_cc = $args->{changes}->{cc};
+ my $sysadmin_login = Bugzilla::User->new(SYSADMIN_USER_ID)->login;
+
+ if ($changed_cc->[1]) {
+ $changed_cc->[1] .= ", $sysadmin_login";
+ }
+ else {
+ $changed_cc->[1] = $sysadmin_login;
+ }
+
+ my $field_id = get_field_id('cc');
+ $dbh->do('DELETE FROM bugs_activity
+ WHERE bug_id = ? AND bug_when = ? AND fieldid = ?',
+ undef, ($bug->id, $args->{timestamp}, $field_id));
+
+ LogActivityEntry($bug->id, 'cc', $changed_cc->[0] // '', $changed_cc->[1],
+ $user->id, $args->{timestamp});
+ }
+ }
+}
+
+sub bug_format_comment {
+ my ($self, $args) = @_;
+ my $regexes = $args->{'regexes'};
+ # The Mageia DB was initially using SQL_ASCII as encoding (which means "no encoding").
+ # The move to UTF8 caused some characters to be badly decoded, which we fix here.
+ # Convert "é" into "é".
+ push(@$regexes, { match => qr/\xc3\xa9/, replace => "\xe9" });
+}
+
+sub enter_bug_entrydefaultvars {
+ my ($self, $vars) = @_;
+ my $cgi = Bugzilla->cgi;
+
+ # By default, users should get the guided form when reporting a new bug.
+ # Pass &normal=1 to the URL to get the official bug form.
+ my $format = $cgi->param('normal') ? '' : 'guided';
+ $cgi->param('format', $format);
+}
+
+sub install_before_final_checks {
+ # A hook in Bugzilla::Install::SETTINGS() would be much cleaner. :(
+ my %settings = %{MGA_SETTINGS()};
+ foreach my $setting (keys %settings) {
+ add_setting($setting, $settings{$setting}->{options}, $settings{$setting}->{default});
+ }
+}
+
+sub mailer_before_send {
+ my ($self, $args) = @_;
+ my $email = $args->{email};
+
+ # Include the changer's name in the "From:" field.
+ if (my $changer = $email->header('X-Bugzilla-Who')) {
+ $changer = Bugzilla::User->new({ name => $changer });
+ return unless $changer;
+ my $address = Email::Address->new($changer->name, $email->header('From'));
+ $email->header_set('From', encode('MIME-Header', $address->format));
+ }
+}
+
+sub sanitycheck_check {
+ my ($self, $args) = @_;
+ &{$args->{status}}('ldap_check_group_membership');
+ sync_ldap_groups_check($args->{status});
+}
+
+sub sanitycheck_repair {
+ my ($self, $args) = @_;
+ if (Bugzilla->cgi->param('sync_ldap_groups')) {
+ &{$args->{status}}('ldap_repair_start');
+ sync_ldap_groups_check($args->{status}, 1);
+ &{$args->{status}}('ldap_repair_end');
+ }
+}
+
+sub template_before_process {
+ my ($self, $args) = @_;
+ _inline_history($args) if $args->{file} eq 'bug/comments.html.tmpl';
+}
+
+sub _inline_history {
+ my $args = shift;
+ my $user = Bugzilla->user;
+ return if $user->setting('mga_inline_history') eq 'off';
+ # Only display the bug history in chronological order.
+ return if $user->setting('comment_sort_order') ne 'oldest_to_newest';
+
+ my $bug = $args->{vars}->{bug};
+ my ($history) = $bug->get_activity();
+ # Filter private comments that the user is not allowed to see.
+ my @comments = grep { !$_->is_private || $user->is_insider || $user->id == $_->author->id } @{ $bug->comments };
+ my %h_times = map { $_->{when} => $_ } @$history;
+ my %c_times = map { $_->creation_ts => $_ } @comments;
+ my @all_times = sort { compare_datetimes($a, $b) } (keys %c_times, keys %h_times);
+ my $curr_comment;
+
+ foreach my $time (@all_times) {
+ if ($c_times{$time}) {
+ $curr_comment = $c_times{$time};
+ $curr_comment->{inline_history} //= [];
+ }
+ if (my $activity = $h_times{$time}) {
+ # If we have no visible comment to attach the activity to, then ignore it.
+ # This can happen if the initial comment (comment 0) is private.
+ next unless $curr_comment;
+ $activity->{after} = compare_datetimes($time, $curr_comment->creation_ts);
+ $activity->{who} = new Bugzilla::User({ name => $activity->{who}, cache => 1 });
+ push(@{ $curr_comment->{inline_history} }, $activity);
+ }
+ }
+}
+
+sub user_wants_mail {
+ my ($self, $args) = @_;
+ return unless $args->{relationship} == REL_GLOBAL_WATCHER;
+
+ my $wants_mail = $args->{wants_mail};
+ my @events = @{ $args->{events} };
+ # Do not email global watchers if the single change is about the CC list.
+ $$wants_mail = (scalar(@events) == 1 && $events[0] == EVT_CC) ? 0 : 1;
+}
+
+__PACKAGE__->NAME;
diff --git a/extensions/Mageia/lib/Util.pm b/extensions/Mageia/lib/Util.pm
new file mode 100644
index 000000000..b70d39818
--- /dev/null
+++ b/extensions/Mageia/lib/Util.pm
@@ -0,0 +1,146 @@
+# 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::Mageia::Util;
+
+use 5.16.0; # required to use fc() for string comparison.
+use strict;
+use warnings;
+
+use Bugzilla::Auth::Verify::LDAP;
+use Bugzilla::Group;
+use Bugzilla::User;
+use Bugzilla::Util qw(diff_arrays);
+
+use parent qw(Exporter);
+
+our @EXPORT = qw(compare_datetimes sync_ldap_groups_check);
+
+# Use Mageia Robot user ID = 2122 for LDAP groups sync.
+use constant LDAP_SYNC_USER_ID => 2122;
+
+# This file can be loaded by your extension via
+# "use Bugzilla::Extension::Mageia::Util". You can put functions
+# used by your extension in here. (Make sure you also list them in
+# @EXPORT.)
+
+# Return -1 if $t1 < $t2, 0 if $t1 == $t2, 1 if $t1 > $t2, undef if a date is invalid.
+sub compare_datetimes {
+ my ($t1, $t2) = @_;
+ my (@time1, @time2);
+ if ($t1 =~ /^(\d{4})[\.-](\d{2})[\.-](\d{2})(?: (\d{2}):(\d{2}):(\d{2}))?$/) {
+ @time1 = ($1, $2, $3, $4, $5, $6);
+ }
+ if ($t2 =~ /^(\d{4})[\.-](\d{2})[\.-](\d{2})(?: (\d{2}):(\d{2}):(\d{2}))?$/) {
+ @time2 = ($1, $2, $3, $4, $5, $6);
+ }
+ return undef unless scalar(@time1) && scalar(@time2);
+
+ for my $i (0..5) {
+ $t1 = $time1[$i] // 0;
+ $t2 = $time2[$i] // 0;
+ next if $t1 == $t2;
+ return $t1 <=> $t2;
+ }
+ return 0;
+}
+
+sub sync_ldap_groups_check {
+ my ($status, $repair) = @_;
+ my $ldap = Bugzilla::Auth::Verify::LDAP->new();
+ $ldap->_bind_ldap_for_search;
+
+ my $result = $ldap->ldap->search(
+ base => 'ou=Group,dc=mageia,dc=org',
+ filter => '(objectClass=groupOfNames)',
+ attrs => ['cn', 'member']
+ );
+
+ if ($result->is_error) {
+ &$status('ldap_sync_alert', { ldap_error => $result->error }, 'alert');
+ return;
+ }
+
+ my (%groups, %users);
+ my $dbh = Bugzilla->dbh;
+
+ my %bz_groups = map { $_->name => $_ } grep { $_->name =~ /^mga-/ } Bugzilla::Group->get_all;
+
+ foreach my $ldap_group ($result->entries) {
+ my $group = $ldap_group->get_value('cn');
+ next unless $bz_groups{$group};
+
+ $groups{$group} = {};
+
+ foreach my $user ($ldap_group->get_value('member')) {
+ unless (exists $users{$user}) {
+ my $result = $ldap->ldap->search(
+ base => $user,
+ scope => 'base',
+ filter => '(objectClass=inetOrgPerson)',
+ attrs => ['uid', 'mail']
+ );
+ if ($result->is_error) {
+ &$status('ldap_sync_alert', { ldap_error => $result->error }, 'alert');
+ next;
+ }
+
+ # Some entries are not valid user accounts.
+ my $ldap_user = $result->entry(0) or next;
+ my $uid = $ldap_user->get_value('uid');
+ my $mail = $ldap_user->get_value('mail');
+ # Some accounts lack an email address. They cannot log into Bugzilla.
+ next unless $uid && $mail;
+
+ # Ignore the LDAP account if it does not exist in Bugzilla.
+ # extern_id may differ from UID as UID is always lowercase,
+ # so we also check the email address to validate the account.
+ my $bz_user = Bugzilla::User->new({ extern_id => $uid })
+ || Bugzilla::User->new({ name => $mail });
+ next unless $bz_user && fc($bz_user->extern_id) eq fc($uid);
+ $users{$user} = $bz_user;
+ }
+ $groups{$group}->{$users{$user}->extern_id} = $users{$user};
+ }
+ }
+
+ my $current_user = Bugzilla->user;
+ my $ldap_sync_needs_repair = 0;
+
+ if ($repair) {
+ # We need to log in as a super user in order to update group memberships.
+ # Bugzilla needs a valid user ID to log changes, though.
+ my $super_user = Bugzilla::User->super_user;
+ $super_user->{userid} = LDAP_SYNC_USER_ID;
+ Bugzilla->set_user($super_user);
+ }
+
+ foreach my $group (sort keys %groups) {
+ my %members = map { $_->extern_id => $_ } @{$bz_groups{$group}->members_direct};
+ my ($removed, $added) = diff_arrays([keys %members], [keys %{$groups{$group}}]);
+ if ($repair) {
+ foreach my $old_user (@$removed) {
+ $members{$old_user}->set_groups({ remove => [$group] });
+ $members{$old_user}->update;
+ }
+
+ foreach my $new_user (@$added) {
+ $groups{$group}->{$new_user}->set_groups({ add => [$group] });
+ $groups{$group}->{$new_user}->update;
+ }
+ }
+ elsif (@$removed || @$added) {
+ &$status('ldap_sync_group_mismatch_alert', { group => $group, old_users => $removed, new_users => $added }, 'alert');
+ $ldap_sync_needs_repair = 1;
+ }
+ }
+ # Restore the original user.
+ Bugzilla->set_user($current_user) if $repair;
+ &$status('ldap_sync_repair_link') if $ldap_sync_needs_repair;
+}
+
+1;
diff --git a/extensions/Mageia/sync_LDAP_groups.pl b/extensions/Mageia/sync_LDAP_groups.pl
new file mode 100755
index 000000000..77a99ee9a
--- /dev/null
+++ b/extensions/Mageia/sync_LDAP_groups.pl
@@ -0,0 +1,27 @@
+#!/usr/bin/perl -T
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+# This Source Code Form is "Incompatible With Secondary Licenses", as
+# defined by the Mozilla Public License, v. 2.0.
+
+use 5.14.0;
+use strict;
+use warnings;
+
+use lib qw(. lib);
+
+use Bugzilla;
+BEGIN { Bugzilla->extensions() }
+use Bugzilla::Extension::Mageia::Util qw(sync_ldap_groups_check);
+
+# See Status() in sanitycheck.cgi.
+sub status {
+ my ($san_tag, $vars, $alert) = @_;
+ return unless $alert && $san_tag eq 'ldap_sync_alert';
+
+ say 'LDAP error: ' . $vars->{ldap_error};
+}
+
+sync_ldap_groups_check(\&status, 1);
diff --git a/extensions/Mageia/template/en/default/bug/create/comment-guided.txt.tmpl b/extensions/Mageia/template/en/default/bug/create/comment-guided.txt.tmpl
new file mode 100644
index 000000000..7b5018e6d
--- /dev/null
+++ b/extensions/Mageia/template/en/default/bug/create/comment-guided.txt.tmpl
@@ -0,0 +1,10 @@
+[%# This Source Code Form is subject to the terms of the Mozilla Public
+ # License, v. 2.0. If a copy of the MPL was not distributed with this
+ # file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ #
+ # This Source Code Form is "Incompatible With Secondary Licenses", as
+ # defined by the Mozilla Public License, v. 2.0.
+ #%]
+
+[% USE Bugzilla %]
+[% Bugzilla.cgi.param("comment") %]
diff --git a/extensions/Mageia/template/en/default/bug/create/create-guided.html.tmpl b/extensions/Mageia/template/en/default/bug/create/create-guided.html.tmpl
new file mode 100644
index 000000000..c672a4cf5
--- /dev/null
+++ b/extensions/Mageia/template/en/default/bug/create/create-guided.html.tmpl
@@ -0,0 +1,299 @@
+[%# 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.
+ #%]
+
+[%# INTERFACE:
+ # This template has the same interface as create.html.tmpl
+ #%]
+
+[% PROCESS global/header.html.tmpl
+ title = "Enter $terms.ABug"
+ onload = "PutDescription()"
+ style_urls = ['skins/standard/bug.css']
+ javascript_urls = ['js/util.js', 'js/field.js']
+ yui = ['autocomplete']
+ %]
+
+[% defaultcontent = "Description of problem:\n\n\n" _
+ "Version-Release number of selected component (if applicable):\n\n\n" _
+ "How reproducible:\n\n\nSteps to Reproduce:\n1.\n2.\n3.\n" %]
+
+[%# This script displays the descriptions for selected components. %]
+<script type="text/javascript">
+var descriptions = [
+[% FOREACH c = product.components %]
+ [% NEXT IF NOT c.is_active %]
+ '[% c.description FILTER js %]',
+[% END %]
+];
+
+function PutDescription() {
+ var description = document.getElementById('description');
+ var componentIndex = document.getElementById('component').selectedIndex;
+ YAHOO.util.Dom.removeClass("description", "bz_default_hidden");
+ if (componentIndex != -1) {
+ description.innerHTML = descriptions[componentIndex];
+ }
+}
+
+function CheckDetails(e) {
+ if (e.form.comment.value == '[% defaultcontent FILTER js FILTER html %]') {
+ alert('Please enter some details about this [% terms.bug %].');
+ e.form.comment.focus();
+ return false;
+ }
+ return true;
+}
+</script>
+
+<p>
+ Submit [% terms.abug %] using the
+ <a href="enter_bug.cgi?product=[% product.name FILTER html %]&amp;normal=1">expert [% terms.bug %] form</a>.
+</p>
+
+<form id="guided_form" method="post" action="post_bug.cgi">
+ <input type="hidden" name="format" value="guided">
+ <input type="hidden" name="priority" value="[% default.priority FILTER html %]">
+ <input type="hidden" name="token" value="[% token FILTER html %]">
+
+<table>
+ <tr>
+ [% INCLUDE "bug/field-label.html.tmpl" field = bug_fields.product %]
+ <td>
+ <input type="hidden" name="product" value="[% product.name FILTER html %]">
+ [% product.name FILTER html %]
+ </td>
+ </tr>
+
+ <tr>
+ [% INCLUDE "bug/field-label.html.tmpl" field = bug_fields.component editable = 1 %]
+ <td>
+ <select name="component" id="component" size="5" onchange="PutDescription()"
+ aria-required="true" class="required" required>
+ [% IF NOT default.component_ %]
+ [% default.component_ = "RPM Packages" %]
+ [% END %]
+ [% FOREACH c = product.components %]
+ [% NEXT IF NOT c.is_active %]
+ <option value="[% c.name FILTER html %]"
+ [%+ 'selected="selected"' IF c.name == default.component_ %]>
+ [% c.name FILTER html %]
+ </option>
+ [% END %]
+ </select>
+
+ <div id="description" class="comment bz_default_hidden">
+ Select a component to see its description here.
+ </div>
+
+ <p>
+ The area where the problem occurs.
+ To pick the right component, you could use the same one as
+ similar [% terms.bugs %] you found in your search, or read the full list of
+ <a href="describecomponents.cgi?product=[% product.name FILTER uri %]"
+ target="_blank" >component descriptions</a> (opens in new window) if
+ you need more help.
+ </p>
+ </td>
+ </tr>
+
+ <tr>
+ [% INCLUDE "bug/field-label.html.tmpl" field = bug_fields.version editable = 1 %]
+ <td>
+ <select name="version" id="version" size="5" aria-required="true" class="required" required>
+ [% FOREACH v = version %]
+ [% NEXT IF NOT v.is_active %]
+ <option value="[% v.name FILTER html %]"
+ [% ' selected="selected"' IF v.name == default.version %]>[% v.name FILTER html -%]
+ </option>
+ [% END %]
+ </select>
+
+ <p>
+ The version in which the problem occurs.
+ </p>
+ </td>
+ </tr>
+
+ <tr>
+ [% INCLUDE bug/field.html.tmpl
+ bug = default, field = bug_fields.rep_platform, editable = 1,
+ value = default.rep_platform %]
+ </tr>
+
+ <tr>
+ [% INCLUDE "bug/field-label.html.tmpl" field = bug_fields.cf_rpmpkg editable = 1 %]
+ <td>
+ <input size="80" name="cf_rpmpkg" id="cf_rpmpkg" value="[% default.cf_rpmpkg FILTER html %]">
+ <p>
+ This is where you can identify exactly which RPM package is involved in
+ this [% terms.bug %] report. For instance, if you know the problem you
+ are having is with the program <tt>mysqld</tt>, then execute
+ <tt>rpm -qif /usr/sbin/mysqld</tt>. This will tell you the name and version
+ of the RPM package (i.e. mariadb-core-10.1.24-1.mga6) as well as other
+ information. In particular, you are looking for the "Source RPM" field
+ (i.e. mariadb-10.1.24-1.mga6.src.rpm) -- this is the information you should
+ provide here. Alternatively, you may use
+ <tt>rpm -qf /usr/sbin/mysqld --qf '%{SOURCERPM}\n'</tt> to obtain the
+ information. If you do not know the location of the program in question,
+ use <tt>rpm -qif `which mysqld`</tt>. Please enter that information above.
+ </p>
+ </td>
+ </tr>
+
+ <tr>
+ [% INCLUDE "bug/field-label.html.tmpl" field = bug_fields.bug_file_loc editable = 1 %]
+ <td>
+ <input id="bug_file_loc" name="bug_file_loc" placeholder="https://" size="80"
+ [% IF bug_file_loc != "http://" %] value="[% bug_file_loc FILTER html %]"[% END %]>
+ <p>
+ URL that demonstrates the problem you are submitting (optional).
+ </p>
+ </td>
+ </tr>
+
+ <tr>
+ [% INCLUDE "bug/field-label.html.tmpl" field = bug_fields.short_desc editable = 1 %]
+ <td>
+ <input name="short_desc" id="short_desc" size="80" maxlength="255"
+ value="[% short_desc FILTER html %]" spellcheck="true"
+ aria-required="true" class="required" required>
+ <p>
+ A sentence which summarizes the problem.
+ Please be descriptive and use lots of keywords.
+ </p>
+ <p>
+ <kbd>
+ <span class="bad">Bad example</span>: mail crashed
+ </kbd>
+ <br>
+ <kbd>
+ <span class="good">Good example</span>:
+ crash in Evolution while checking for new POP mail
+ </kbd>
+ </p>
+ </td>
+ </tr>
+
+ <tr>
+ <th id="field_label_details" class="field_label required">Details</th>
+ <td>
+ [% INCLUDE global/textarea.html.tmpl
+ id = 'comment'
+ name = 'comment'
+ minrows = 13
+ cols = constants.COMMENT_COLS
+ mandatory = 1
+ defaultcontent = comment || defaultcontent
+ %]
+
+ [% IF user.is_insider %]
+ <br>
+ <input type="checkbox" id="comment_is_private" name="comment_is_private"
+ [% ' checked="checked"' IF comment_is_private %]
+ onClick="updateCommentTagControl(this, 'comment')">
+ <label for="comment_is_private">
+ Initial description is private (visible only to members
+ of the <strong>[% Param('insidergroup') FILTER html %]</strong> group)
+ </label>
+ <script>
+ updateCommentTagControl(document.getElementById('comment_is_private'), 'comment');
+ </script>
+ [% END %]
+ <p>
+ Expand on the Summary. Please be as specific as possible about what is wrong.
+ </p>
+ <p>
+ <kbd>
+ <span class="bad">Bad example</span>: I can't seem to login to the system. Please help!
+ </kbd>
+ <br>
+ <kbd>
+ <span class="good">Good example</span>: Description of problem:
+ <br><br>
+ I'm unable to login to the system via ssh. The /var/log/messages log
+ indicates there is a problem with the pam module pam_ldap, but the
+ /etc/pam.d/system-auth file doesn't contain that module and I'm not
+ using LDAP. I looked at /etc/pam.d/sshd and it does contain that module
+ but I'm not sure how it got there, unless it was due to the super-spiffy
+ super-ldap-mojo package I installed yesterday.
+ <br><br>
+ Version-Release number of selected component (if applicable):
+ <br><br>
+ openldap-2.4.45-2.mga6, pam-1.3.0-5.mga6
+ <br><br>
+ How reproducible:
+ <br><br>
+ Every time I attempt to login.
+ <br><br>
+ Steps to Reproduce:<br>
+ 1. ssh user@host<br>
+ 2. see the rejection
+ </kbd>
+ </p>
+ </td>
+ </tr>
+
+ <tr>
+ [% INCLUDE "bug/field-label.html.tmpl" field = bug_fields.bug_severity editable = 1 %]
+ <td>
+ <select name="bug_severity">
+ <option name="critical" value="critical">
+ Critical: The software crashes, hangs, or causes you to
+ lose data.
+ </option>
+ <option name="major" value="major">
+ Major: A major feature is broken.
+ </option>
+ <option name="normal" value="normal" selected="selected">
+ Normal: It's [% terms.abug %] that should be fixed.
+ </option>
+ <option name="minor" value="minor">
+ Minor: Minor loss of function, and there's an easy workaround.
+ </option>
+ <option name="enhancement" value="enhancement">
+ Enhancement: Request for new feature or enhancement.
+ </option>
+ </select>
+ <p>
+ Indicate how serious the problem is, or if your [% terms.bug %] is a
+ request for a new feature.
+ </p>
+ </td>
+ </tr>
+
+ <tr>
+ [% INCLUDE "bug/field-label.html.tmpl" field = bug_fields.assigned_to editable = 1 %]
+ <td>
+ [% INCLUDE global/userselect.html.tmpl
+ id => "assigned_to"
+ name => "assigned_to"
+ value => assigned_to
+ disabled => assigned_to_disabled
+ size => 30
+ emptyok => 1
+ %]
+ <p>Leave blank to assign to the default component owner.</p>
+ </td>
+ </tr>
+
+ [% Hook.process('form') %]
+</table>
+
+<p>
+ <input type="submit" id="report" value="Submit [% terms.Bug %] Report"
+ onclick="var res = CheckDetails(this); return res">
+</p>
+
+<p>
+ That's it! Thanks very much. You'll be notified by email about any progress
+ that is made on fixing your [% terms.bug %]. Thank you for choosing Mageia!
+</p>
+
+</form>
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/extensions/Mageia/template/en/default/global/banner.html.tmpl b/extensions/Mageia/template/en/default/global/banner.html.tmpl
new file mode 100644
index 000000000..76f1d123e
--- /dev/null
+++ b/extensions/Mageia/template/en/default/global/banner.html.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.
+ #%]
+
+<script src="https://nav.mageia.org/js/" type="text/javascript"></script>
+
+<div id="banner"></div>
diff --git a/extensions/Mageia/template/en/default/hook/README b/extensions/Mageia/template/en/default/hook/README
new file mode 100644
index 000000000..e6c4add58
--- /dev/null
+++ b/extensions/Mageia/template/en/default/hook/README
@@ -0,0 +1,5 @@
+Template hooks go in this directory. Template hooks are called in normal
+Bugzilla templates like [% Hook.process('some-hook') %].
+More information about them can be found in the documentation of
+Bugzilla::Extension. (Do "perldoc Bugzilla::Extension" from the main
+Bugzilla directory to see that documentation.) \ No newline at end of file
diff --git a/extensions/Mageia/template/en/default/hook/account/auth/login-small-additional_methods.html.tmpl b/extensions/Mageia/template/en/default/hook/account/auth/login-small-additional_methods.html.tmpl
new file mode 100644
index 000000000..675af49f1
--- /dev/null
+++ b/extensions/Mageia/template/en/default/hook/account/auth/login-small-additional_methods.html.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.
+ #%]
+
+[% UNLESS user.id %]
+ [% UNLESS Param('createemailregexp') AND user.authorizer.user_can_create_account %]
+ <li id="new_account_container[% qs_suffix FILTER html %]">
+ <span class="separator">| </span>
+ <a href="https://identity.mageia.org/register">New&nbsp;Account</a>
+ </li>
+ [% END %]
+
+ [% UNLESS user.authorizer.can_change_password %]
+ <li id="forgot_container[% qs_suffix FILTER html %]">
+ <span class="separator">| </span>
+ <a href="https://identity.mageia.org/forgot_password">Forgot Password</a>
+ </li>
+ [% END %]
+[% END %]
diff --git a/extensions/Mageia/template/en/default/hook/account/prefs/account-field.html.tmpl b/extensions/Mageia/template/en/default/hook/account/prefs/account-field.html.tmpl
new file mode 100644
index 000000000..700f06a69
--- /dev/null
+++ b/extensions/Mageia/template/en/default/hook/account/prefs/account-field.html.tmpl
@@ -0,0 +1,10 @@
+[% IF Param('user_verify_class') == "LDAP" %]
+ <tr>
+ <th></th>
+ <td>(Changes will be undone when you log out. Use the link below for permanent changes.)</td>
+ </tr>
+ <tr>
+ <th>Edit account (email address, password, real name):</th>
+ <td><a href="https://identity.mageia.org" target="_blank">Visit identity.mageia.org</a></td>
+ </tr>
+[% END %]
diff --git a/extensions/Mageia/template/en/default/hook/admin/sanitycheck/messages-statuses.html.tmpl b/extensions/Mageia/template/en/default/hook/admin/sanitycheck/messages-statuses.html.tmpl
new file mode 100644
index 000000000..19155048b
--- /dev/null
+++ b/extensions/Mageia/template/en/default/hook/admin/sanitycheck/messages-statuses.html.tmpl
@@ -0,0 +1,27 @@
+[%# 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 san_tag == "ldap_check_group_membership" %]
+ Checking group membership for LDAP groups.
+[% ELSIF san_tag == "ldap_repair_start" %]
+ OK, now fixing Bugzilla group memberships based on LDAP groups.
+[% ELSIF san_tag == "ldap_repair_end" %]
+ Bugzilla group memberships synchronization completed.
+[% ELSIF san_tag == "ldap_sync_alert" %]
+ LDAP error: [% ldap_error FILTER html %]
+[% ELSIF san_tag == "ldap_sync_group_mismatch_alert" %]
+ Group [% group FILTER html %] is out of sync:<br>
+ - [% old_users.size %] users are no longer in this group and should be removed:
+ [%+ old_users.join(", ") FILTER html %].<br>
+ - [% new_users.size %] users are not yet in this group and should be added:
+ [%+ new_users.join(", ") FILTER html %].
+[% ELSIF san_tag == "ldap_sync_repair_link" %]
+ <a href="sanitycheck.cgi?sync_ldap_groups=1&amp;token=
+ [%- issue_hash_token(['sanitycheck']) FILTER uri %]">Synchronize Bugzilla groups
+ with LDAP groups (LDAP -> Bugzilla).</a>
+[% END %]
diff --git a/extensions/Mageia/template/en/default/hook/bug/comments-a_comment-end.html.tmpl b/extensions/Mageia/template/en/default/hook/bug/comments-a_comment-end.html.tmpl
new file mode 100644
index 000000000..cbe11b4ca
--- /dev/null
+++ b/extensions/Mageia/template/en/default/hook/bug/comments-a_comment-end.html.tmpl
@@ -0,0 +1,76 @@
+[%# 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 IF user.setting('mga_inline_history') == 'off' %]
+
+[% FOREACH activity = comment.inline_history %]
+ [% IF activity.after %]
+ </div>
+ <div class="bz_comment bz_inline_history">
+ <div class="bz_comment_head">
+ <span class="bz_comment_user">
+ [%# No need to recreate the exact same template if we already have it. %]
+ [% who_id = activity.who.id %]
+ [% UNLESS user_cache.$who_id %]
+ [% user_cache.$who_id = BLOCK %]
+ [% INCLUDE global/user.html.tmpl who = activity.who %]
+ [% END %]
+ [% END %]
+ [% user_cache.$who_id FILTER none %]
+ </span>
+
+ <span class="bz_comment_user_images">
+ [% FOREACH group = activity.who.groups_with_icon %]
+ <img src="[% group.icon_url FILTER html %]"
+ alt="[% group.name FILTER html %]"
+ title="[% group.name FILTER html %] - [% group.description FILTER html %]">
+ [% END %]
+ </span>
+
+ <span class="bz_comment_time">
+ [%+ activity.when FILTER time %]
+ </span>
+ </div>
+ [% ELSE %]
+ <hr>
+ [% END %]
+
+ <p class="bz_inline_history">
+ [% FOREACH change = activity.changes %]
+ [% IF change.attachid AND field_descs.${change.fieldname}.match('^Attachment') %]
+ <a href="attachment.cgi?id=[% change.attachid FILTER html %]&amp;action=edit">
+ [% field_descs.${change.fieldname}.replace('^Attachment', "Attachment ${change.attachid}</a>") FILTER none %]:
+ [% ELSIF activity.comment_id AND field_descs.${change.fieldname}.match('^Comment') %]
+ [% comment_num = change.comment.count %]
+ <a href="show_bug.cgi?id=[% bug.id FILTER html %]#c[% comment_num FILTER html %]"
+ onclick="document.getElementById('c[% comment_num FILTER html %]').classList.add('bz_comment_hilite')">
+ [% field_descs.${change.fieldname}.replace('^Comment', "Comment $comment_num</a>") FILTER none %]:
+ [% ELSE %]
+ [% field_descs.${change.fieldname} FILTER html %]:
+ [% END %]
+ <span class="change_removed">[% PROCESS format_field_value value = change.removed %]</span> =>
+ <span class="change_added">[% PROCESS format_field_value value = change.added %]</span><br>
+ [% END %]
+ </p>
+[% END %]
+
+[% BLOCK format_field_value %]
+ [% IF value.length %]
+ [% IF change.fieldname == 'assigned_to' ||
+ change.fieldname == 'reporter' ||
+ change.fieldname == 'qa_contact' ||
+ change.fieldname == 'cc' ||
+ change.fieldname == 'flagtypes.name' %]
+ [% display_value(change.fieldname, value) FILTER email FILTER html %]
+ [% ELSE %]
+ [% display_value(change.fieldname, value) FILTER html FILTER html_line_break %]
+ [% END %]
+ [% ELSE %]
+ (none)
+ [% END %]
+[% END %]
diff --git a/extensions/Mageia/template/en/default/hook/global/header-start.html.tmpl b/extensions/Mageia/template/en/default/hook/global/header-start.html.tmpl
new file mode 100644
index 000000000..01fab7fab
--- /dev/null
+++ b/extensions/Mageia/template/en/default/hook/global/header-start.html.tmpl
@@ -0,0 +1,10 @@
+[%# This Source Code Form is subject to the terms of the Mozilla Public
+ # License, v. 2.0. If a copy of the MPL was not distributed with this
+ # file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ #
+ # This Source Code Form is "Incompatible With Secondary Licenses", as
+ # defined by the Mozilla Public License, v. 2.0.
+ #%]
+
+[% favicon_url = "https://www.mageia.org/g/favicon.png" %]
+[% style_urls.push("extensions/Mageia/web/style.css") %]
diff --git a/extensions/Mageia/template/en/default/hook/global/setting-descs-settings.none.tmpl b/extensions/Mageia/template/en/default/hook/global/setting-descs-settings.none.tmpl
new file mode 100644
index 000000000..5b1da08aa
--- /dev/null
+++ b/extensions/Mageia/template/en/default/hook/global/setting-descs-settings.none.tmpl
@@ -0,0 +1,9 @@
+[%# 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.mga_inline_history = "Display inline history in $terms.bug reports" %]
diff --git a/extensions/Mageia/template/en/default/hook/global/variables-end.none.tmpl b/extensions/Mageia/template/en/default/hook/global/variables-end.none.tmpl
new file mode 100644
index 000000000..4f651a5ae
--- /dev/null
+++ b/extensions/Mageia/template/en/default/hook/global/variables-end.none.tmpl
@@ -0,0 +1,9 @@
+[%# 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.
+ #%]
+
+[% terms.Bugzilla = "Mageia Bugzilla" %]
diff --git a/extensions/Mageia/template/en/default/hook/index-additional_links.html.tmpl b/extensions/Mageia/template/en/default/hook/index-additional_links.html.tmpl
new file mode 100644
index 000000000..3c2585887
--- /dev/null
+++ b/extensions/Mageia/template/en/default/hook/index-additional_links.html.tmpl
@@ -0,0 +1,14 @@
+[%# This Source Code Form is subject to the terms of the Mozilla Public
+ # License, v. 2.0. If a copy of the MPL was not distributed with this
+ # file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ #
+ # This Source Code Form is "Incompatible With Secondary Licenses", as
+ # defined by the Mozilla Public License, v. 2.0.
+ #%]
+
+[% UNLESS user.id %]
+ <p id="create_account">
+ Don't have an account on [% terms.Bugzilla %]?
+ <a href="https://identity.mageia.org/register">Create one</a>.
+ </p>
+[% END %]
diff --git a/extensions/Mageia/template/en/default/mageia/README b/extensions/Mageia/template/en/default/mageia/README
new file mode 100644
index 000000000..099d1a41a
--- /dev/null
+++ b/extensions/Mageia/template/en/default/mageia/README
@@ -0,0 +1,16 @@
+Normal templates go in this directory. You can load them in your
+code like this:
+
+use Bugzilla::Error;
+my $template = Bugzilla->template;
+$template->process('mageia/some-template.html.tmpl')
+ or ThrowTemplateError($template->error());
+
+That would be how to load a file called some-template.html.tmpl that
+was in this directory.
+
+Note that you have to be careful that the full path of your template
+never conflicts with a template that exists in Bugzilla or in
+another extension, or your template might override that template. That's why
+we created this directory called 'mageia' for you, so you
+can put your templates in here to help avoid conflicts. \ No newline at end of file
diff --git a/extensions/Mageia/web/README b/extensions/Mageia/web/README
new file mode 100644
index 000000000..c3c41862f
--- /dev/null
+++ b/extensions/Mageia/web/README
@@ -0,0 +1,7 @@
+Web-accessible files, like JavaScript, CSS, and images go in this
+directory. You can reference them directly in your HTML. For example,
+if you have a file called "style.css" and your extension is called
+"Mageia", you would put it in "extensions/Mageia/web/style.css", and then
+you could link to it in HTML like:
+
+<link href="extensions/Mageia/web/style.css" rel="stylesheet" type="text/css">
diff --git a/extensions/Mageia/web/style.css b/extensions/Mageia/web/style.css
new file mode 100644
index 000000000..daffc088a
--- /dev/null
+++ b/extensions/Mageia/web/style.css
@@ -0,0 +1,52 @@
+/* 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.
+ */
+
+body {
+ margin: 0;
+ padding-top: 0;
+ background-color: #fff !important;
+}
+
+#create_account {
+ border: solid 1px;
+ background-color: #ffd386;
+ color: #9b1a1a;
+ padding: 1em;
+ text-align: center;
+ max-width: 30em;
+ margin: 1em auto;
+}
+
+#guided_form {
+ padding: 1em;
+}
+
+#guided_form .field_label {
+ white-space: nowrap;
+}
+
+.bz_inline_history {
+ font-style: italic;
+ background-color: #fff !important;
+}
+
+div.bz_comment_hilite {
+ border: solid 3px;
+}
+
+.bz_comment_hilite pre {
+ background-color: inherit;
+}
+
+.change_removed {
+ color: darkred;
+}
+
+.change_added {
+ color: darkgreen;
+}
diff --git a/extensions/MoreBugUrl/Extension.pm b/extensions/MoreBugUrl/Extension.pm
index 18507f8d1..0a4223e19 100644
--- a/extensions/MoreBugUrl/Extension.pm
+++ b/extensions/MoreBugUrl/Extension.pm
@@ -22,6 +22,7 @@ use constant MORE_SUB_CLASSES => qw(
Bugzilla::Extension::MoreBugUrl::PHP
Bugzilla::Extension::MoreBugUrl::Redmine
Bugzilla::Extension::MoreBugUrl::Savane
+ Bugzilla::Extension::MoreBugUrl::Phabricator
);
# We need to update bug_see_also table because both
diff --git a/extensions/MoreBugUrl/disabled b/extensions/MoreBugUrl/disabled
deleted file mode 100644
index e69de29bb..000000000
--- a/extensions/MoreBugUrl/disabled
+++ /dev/null
diff --git a/extensions/MoreBugUrl/lib/Phabricator.pm b/extensions/MoreBugUrl/lib/Phabricator.pm
new file mode 100644
index 000000000..818d9af8f
--- /dev/null
+++ b/extensions/MoreBugUrl/lib/Phabricator.pm
@@ -0,0 +1,41 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+# This Source Code Form is "Incompatible With Secondary Licenses", as
+# defined by the Mozilla Public License, v. 2.0.
+
+package Bugzilla::Extension::MoreBugUrl::Phabricator;
+
+use 5.10.1;
+use strict;
+use warnings;
+
+use parent qw(Bugzilla::BugUrl);
+
+###############################
+#### Methods ####
+###############################
+
+sub should_handle {
+ my ($class, $uri) = @_;
+ return ($uri->path =~ m|^/T\d+$|) ? 1 : 0;
+}
+
+sub _check_value {
+ my $class = shift;
+
+ my $uri = $class->SUPER::_check_value(@_);
+
+ # Phabricator URLs have only one form:
+ # http://example.com/T111
+
+ # Make sure there are no query parameters.
+ $uri->query(undef);
+ # And remove any # part if there is one.
+ $uri->fragment(undef);
+
+ return $uri;
+}
+
+1;
diff --git a/extensions/MoreBugUrl/template/en/default/hook/global/user-error-bug_url_invalid_tracker.html.tmpl b/extensions/MoreBugUrl/template/en/default/hook/global/user-error-bug_url_invalid_tracker.html.tmpl
index 7e544ef21..2ac6f89a5 100644
--- a/extensions/MoreBugUrl/template/en/default/hook/global/user-error-bug_url_invalid_tracker.html.tmpl
+++ b/extensions/MoreBugUrl/template/en/default/hook/global/user-error-bug_url_invalid_tracker.html.tmpl
@@ -14,3 +14,4 @@
<li>A b[% %]ug on b[% %]ugs.php.net.</li>
<li>An issue in a Redmine installation.</li>
<li>A b[% %]ug in a Savane installation.</li>
+<li>A task in a Phabricator installation.</li>
diff --git a/reports.cgi b/reports.cgi
index 89dee1c9a..9fc2b6722 100755
--- a/reports.cgi
+++ b/reports.cgi
@@ -170,7 +170,7 @@ sub generate_chart {
my @line = split /\|/;
my $date = $line[0];
my ($yy, $mm, $dd) = $date =~ /^\d{2}(\d{2})(\d{2})(\d{2})$/;
- push @{$data{DATE}}, "$mm/$dd/$yy";
+ push @{$data{DATE}}, "$dd.$mm.$yy";
for my $i (1 .. $#fields) {
my $field = $fields[$i];
diff --git a/skins/standard/buglist.css b/skins/standard/buglist.css
index e58baabfd..bebc4b465 100644
--- a/skins/standard/buglist.css
+++ b/skins/standard/buglist.css
@@ -118,6 +118,45 @@ tr.bz_secure_mode_implied td.first-child {
tr.bz_secure_mode_manual td.first-child {
}
+div.bz_last_visit_description {
+ padding: 1em 0;
+}
+div.bz_last_visit_description span {
+ padding-left: 1em;
+ white-space: nowrap;
+}
+tr.bz_new_since_last_visit td.first-child:before,
+span.bz_new_since_last_visit:before {
+ color: #ed2;
+ content: "\25CF";
+ font-size: large;
+}
+tr.bz_updated_since_last_visit td.first-child:before,
+span.bz_updated_since_last_visit:before {
+ color: #c00;
+ content: "\25CF";
+ font-size: large;
+}
+tr.bz_up_to_date_since_last_visit td.first-child:before,
+span.bz_up_to_date_since_last_visit:before {
+ color: #0c0;
+ content: "\25CF";
+ font-size: large;
+}
+tr.bz_unknown_since_last_visit td.first-child:before,
+span.bz_unknown_since_last_visit:before {
+ color: #aaa;
+ content: "\25CF";
+ font-size: large;
+}
+/* Prevent wrapping of the colored disk besides the bug ID. */
+tr.bz_new_since_last_visit td.first-child,
+tr.bz_updated_since_last_visit td.first-child,
+tr.bz_up_to_date_since_last_visit td.first-child,
+tr.bz_unknown_since_last_visit td.first-child {
+ white-space: nowrap;
+}
+
td.bz_estimated_time_column,
td.bz_remaining_time_column,
td.bz_actual_time_column,
diff --git a/t/010dependencies.t b/t/010dependencies.t
index afd29a652..f565554d8 100644
--- a/t/010dependencies.t
+++ b/t/010dependencies.t
@@ -69,7 +69,7 @@ foreach my $module (keys %mods) {
$used =~ s#/#::#g;
$used =~ s#\.pm$##;
$used =~ s#\$module#[^:]+#;
- $used =~ s#\${[^}]+}#[^:]+#;
+ $used =~ s#\$\{[^}]+}#[^:]+#;
$used =~ s#[" ]##g;
push(@use, grep(/^\Q$used\E$/, keys %mods));
}
diff --git a/template/en/default/admin/keywords/edit.html.tmpl b/template/en/default/admin/keywords/edit.html.tmpl
index 23158d36f..7e6617a84 100644
--- a/template/en/default/admin/keywords/edit.html.tmpl
+++ b/template/en/default/admin/keywords/edit.html.tmpl
@@ -18,14 +18,19 @@
<form method="post" action="editkeywords.cgi">
<table id="admin_table_edit">
<tr>
- <th>Name:</th>
- <td><input size="64" maxlength="64" name="name"
+ <th><label for="name">Name:</label></th>
+ <td><input size="64" maxlength="64" name="name" id="name"
value="[% keyword.name FILTER html %]" required></td>
</tr>
<tr>
- <th>Description:</th>
+ <th><label for="is_active">Enabled For [% terms.Bugs %]</label></th>
+ <td><input id="is_active" name="is_active" type="checkbox" [% "checked" IF keyword.is_active %]></td>
+ </tr>
+ <tr>
+ <th><label for="decription">Description:</label></th>
<td>
[% INCLUDE global/textarea.html.tmpl
+ id = 'description'
name = 'description'
minrows = 4
cols = 64
diff --git a/template/en/default/admin/keywords/list.html.tmpl b/template/en/default/admin/keywords/list.html.tmpl
index c3f4a5292..9d920036e 100644
--- a/template/en/default/admin/keywords/list.html.tmpl
+++ b/template/en/default/admin/keywords/list.html.tmpl
@@ -10,6 +10,7 @@
# keywords: array keyword objects having the properties:
# - id: number. The ID of the keyword.
# - name: string. The name of the keyword.
+ # - is_active: boolean. true if the keyword can be used.
# - description: string. The description of the keyword.
# - bug_count: number. The number of bugs with the keyword.
#%]
@@ -20,34 +21,39 @@
%]
[% columns = [
- {
- name => "name"
- heading => "Edit keyword..."
- contentlink => "editkeywords.cgi?action=edit&amp;id=%%id%%"
- },
- {
- name => "description"
- heading => "Description"
- allow_html_content => 1
- },
- {
- name => "bug_count"
- heading => "$terms.Bugs"
- class => "right"
- contentlink => "buglist.cgi?keywords=%%name%%"
- },
- {
- heading => "Action"
- content => "Delete"
- contentlink => "editkeywords.cgi?action=del&amp;id=%%id%%"
- }
- ]
+ {
+ name => "name"
+ heading => "Edit keyword..."
+ contentlink => "editkeywords.cgi?action=edit&amp;id=%%id%%"
+ },
+ {
+ name => "description"
+ heading => "Description"
+ allow_html_content => 1
+ },
+ {
+ name => "is_active",
+ heading => "Active",
+ yesno_field => 1
+ },
+ {
+ name => "bug_count"
+ heading => "$terms.Bugs"
+ class => "right"
+ contentlink => "buglist.cgi?keywords=%%name%%"
+ },
+ {
+ heading => "Action"
+ content => "Delete"
+ contentlink => "editkeywords.cgi?action=del&amp;id=%%id%%"
+ }
+]
%]
[% PROCESS admin/table.html.tmpl
- columns = columns
- data = keywords
- footer = footer_row
+ columns = columns
+ data = keywords
+ footer = footer_row
%]
<p><a href="editkeywords.cgi?action=add">Add a new keyword</a></p>
diff --git a/template/en/default/admin/users/list.html.tmpl b/template/en/default/admin/users/list.html.tmpl
index f90996882..2dbee7451 100644
--- a/template/en/default/admin/users/list.html.tmpl
+++ b/template/en/default/admin/users/list.html.tmpl
@@ -35,18 +35,29 @@
{name => 'realname'
heading => 'Real name'
}
- {name => 'last_seen_date'
- heading => 'Last Login'
- }
- {heading => 'Account History'
- content => 'View'
- contentlink => 'editusers.cgi?action=activity' _
- '&amp;userid=%%userid%%' _
- listselectionurlparams
- }
]
%]
+[% IF default_authorizer.extern_id_used %]
+ [% columns.push({name => 'extern_id'
+ heading => 'External ID'
+ }
+ )
+ %]
+[% END %]
+
+[% columns.push({name => 'last_seen_date'
+ heading => 'Last Login'
+ }
+ {heading => 'Account History'
+ content => 'View'
+ contentlink => 'editusers.cgi?action=activity' _
+ '&amp;userid=%%userid%%' _
+ listselectionurlparams
+ }
+ )
+%]
+
[% IF Param('allowuserdeletion') && editusers %]
[% columns.push({heading => 'Action'
content => 'Delete'
diff --git a/template/en/default/admin/users/search.html.tmpl b/template/en/default/admin/users/search.html.tmpl
index 17477a012..da61a8a01 100644
--- a/template/en/default/admin/users/search.html.tmpl
+++ b/template/en/default/admin/users/search.html.tmpl
@@ -32,6 +32,9 @@
<option value="login_name">login name</option>
<option value="realname">real name</option>
<option value="userid">user id</option>
+ [% IF default_authorizer.extern_id_used %]
+ <option value="extern_id">external id</option>
+ [% END %]
</select>
<label for="matchstr">matching</label>
<input size="32" name="matchstr" id="matchstr" autofocus>
@@ -45,14 +48,18 @@
</p>
[% IF restrictablegroups.size %]
- <p><input type="checkbox" name="grouprestrict" value="1" id="grouprestrict">
+ <p><input type="checkbox" name="grouprestrict" value="1" id="grouprestrict"
+ onchange="document.forms['f'].group_direct_membership.disabled=!this.checked">
<label for="grouprestrict">Restrict to users belonging to group</label>
<select name="groupid"
- onchange="document.forms['f'].grouprestrict.checked=true">
+ onchange="document.forms['f'].grouprestrict.checked=true;
+ document.forms['f'].group_direct_membership.disabled=false">
[% FOREACH group = restrictablegroups %]
<option value="[% group.id FILTER html %]">[% group.name FILTER html %]</option>
[% END %]
</select></p>
+ <p><input type="checkbox" name="group_direct_membership" value="1" id="group_direct_membership" disabled>
+ <label for="group_direct_membership">Restrict to users who are direct members of this group</label></p>
[% END %]
<p>
diff --git a/template/en/default/admin/users/userdata.html.tmpl b/template/en/default/admin/users/userdata.html.tmpl
index c08cd0018..705168380 100644
--- a/template/en/default/admin/users/userdata.html.tmpl
+++ b/template/en/default/admin/users/userdata.html.tmpl
@@ -65,7 +65,6 @@
# if we ever have a login system that can create accounts through
# createaccount.cgi but can't change passwords.
#%]
- [% password_complexity = Param('password_complexity') %]
[% IF editusers %]
[% IF user.authorizer.can_change_password %]
<tr>
@@ -73,7 +72,8 @@
<td>
<input type="password" size="16" name="password" id="password"
value="" autocomplete="off" [% 'required' IF !editform %]>
- [% IF password_complexity == "mixed_letters" %]
+ [% password_complexity = Param('password_complexity') %]
+ [% IF password_complexity == "mixed_letters" %]
(Password must contain at least one UPPER and one lowercase letter.)
[% ELSIF password_complexity == "letters_numbers" %]
(Password must contain at least one UPPER and one lower case letter and a number.)
diff --git a/template/en/default/attachment/edit.html.tmpl b/template/en/default/attachment/edit.html.tmpl
index 184cdde05..570b611b2 100644
--- a/template/en/default/attachment/edit.html.tmpl
+++ b/template/en/default/attachment/edit.html.tmpl
@@ -188,9 +188,12 @@
[% END %]
</a>
</p>
- [% ELSIF attachment.contenttype == "text/html" %]
+ [% ELSIF attachment.contenttype.match('^text/') %]
[%# For security reasons (clickjacking, embedded scripts), we never
- # render HTML pages from here. The source code is displayed instead. %]
+ # render HTML, XML or SVG pages directly. The source code for all
+ # text/* MIME types is displayed instead. If someone tries to abuse
+ # Bugzilla by manually editing the MIME type, it will be caught
+ # by the iframe below, thanks to its 'sandbox' attribute. %]
[% INCLUDE global/textarea.html.tmpl
id = 'viewFrame'
minrows = 10
@@ -199,6 +202,8 @@
readonly = 'readonly'
%]
[% ELSE %]
+ [%# The 'sandbox' attribute causes all scripts and form submissions
+ # embedded in the attachment to be disabled, for security reasons. %]
<iframe id="viewFrame" src="attachment.cgi?id=[% attachment.id %]" sandbox>
<b>You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
<a href="attachment.cgi?id=[% attachment.id %]">View the attachment on a separate page</a>.</b>
diff --git a/template/en/default/bug/create/create.html.tmpl b/template/en/default/bug/create/create.html.tmpl
index 61faf1c1a..d76c082cc 100644
--- a/template/en/default/bug/create/create.html.tmpl
+++ b/template/en/default/bug/create/create.html.tmpl
@@ -537,7 +537,7 @@ TUI_hide_default('attachment_text_field');
<tr>
[% INCLUDE bug/field.html.tmpl
bug = default, field = bug_fields.keywords, editable = 1,
- value = keywords, possible_values = all_keywords,
+ value = keywords, possible_values = active_keywords,
desc_url = "describekeywords.cgi", value_span = 3
%]
</tr>
diff --git a/template/en/default/bug/edit.html.tmpl b/template/en/default/bug/edit.html.tmpl
index b8abe6bc5..e269d438d 100644
--- a/template/en/default/bug/edit.html.tmpl
+++ b/template/en/default/bug/edit.html.tmpl
@@ -329,20 +329,25 @@
[%# Importance (priority and severity) #%]
[%###############################################################%]
<tr>
- <th class="field_label">
- [% can_edit_priority = bug.check_can_change_field('priority', 0, 1) %]
- <label [% IF can_edit_priority %]for="priority"[% END %] accesskey="i">
- <a href="page.cgi?id=fields.html#importance"><u>I</u>mportance</a></label>:
- </th>
- <td>
- [% INCLUDE bug/field.html.tmpl
+ [% can_edit_priority = bug.check_can_change_field('priority', 0, 1) %]
+ [% INCLUDE "bug/field-label.html.tmpl"
+ field = bug_fields.priority
+ editable = can_edit_priority
+ %]
+ <td class="field_value">
+ [% INCLUDE bug/field.html.tmpl
bug = bug, field = bug_fields.priority,
no_tds = 1, value = bug.priority
editable = can_edit_priority %]
- [%+ INCLUDE bug/field.html.tmpl
+
+ [% can_edit_severity = bug.check_can_change_field('bug_severity', 0, 1) %]
+ <label [% IF can_edit_severity %]for="bug_severity"[% END %] class="field_label">
+ <a href="page.cgi?id=fields.html#bug_severity" class="field_help_link"
+ title="[% help_html.bug_severity FILTER txt FILTER collapse FILTER html %]">Severity</a>:</label>
+ [%+ INCLUDE bug/field.html.tmpl
bug = bug, field = bug_fields.bug_severity,
no_tds = 1, value = bug.bug_severity
- editable = bug.check_can_change_field('bug_severity', 0, 1) %]
+ editable = can_edit_severity %]
[% Hook.process('after_importance', 'bug/edit.html.tmpl') %]
</td>
</tr>
@@ -544,7 +549,7 @@
[% INCLUDE bug/field.html.tmpl
bug = bug, field = bug_fields.keywords, value = bug.keywords
editable = bug.check_can_change_field("keywords", 0, 1),
- desc_url = "describekeywords.cgi", possible_values = all_keywords
+ desc_url = "describekeywords.cgi", possible_values = active_keywords
%]
</tr>
[% END %]
diff --git a/template/en/default/bug/navigate.html.tmpl b/template/en/default/bug/navigate.html.tmpl
index b5e3ba7a2..8d2478479 100644
--- a/template/en/default/bug/navigate.html.tmpl
+++ b/template/en/default/bug/navigate.html.tmpl
@@ -15,7 +15,7 @@
<li>&nbsp;-&nbsp;<a href="show_bug.cgi?ctype=xml&amp;id=
[% bug.bug_id FILTER uri %]">XML</a></li>
<li>&nbsp;-&nbsp;<a href="enter_bug.cgi?cloned_bug_id=
- [% bug.bug_id FILTER uri %]">Clone This
+ [% bug.bug_id FILTER uri %]&amp;normal=1">Clone This
[% terms.Bug %]</a></li>
[%# Links to more things users can do with this bug. %]
[% Hook.process("links") %]
diff --git a/template/en/default/email/bugmail.html.tmpl b/template/en/default/email/bugmail.html.tmpl
index fd348a656..12c20d264 100644
--- a/template/en/default/email/bugmail.html.tmpl
+++ b/template/en/default/email/bugmail.html.tmpl
@@ -48,6 +48,10 @@
[% END %]
<hr>
+ <span>
+ Do not reply by email to add a comment. Emails will be discarded.<br>
+ Use the link at the top of this email and log into [% terms.Bugzilla %] instead.
+ </span>
<span>You are receiving this mail because:</span>
<ul>
diff --git a/template/en/default/email/bugmail.txt.tmpl b/template/en/default/email/bugmail.txt.tmpl
index 1f04db7b1..336f40415 100644
--- a/template/en/default/email/bugmail.txt.tmpl
+++ b/template/en/default/email/bugmail.txt.tmpl
@@ -32,6 +32,9 @@ Referenced [% terms.Bugs %]:
[% END %]
[% END %]
-- [%# Protect the trailing space of the signature marker %]
+Do not reply by email to add a comment. Emails will be discarded.
+Use the link at the top of this email and log into [% terms.Bugzilla %] instead.
+
You are receiving this mail because:
[% SET reason_lines = [] %]
[% FOREACH reason = reasons %]
diff --git a/template/en/default/global/user-error.html.tmpl b/template/en/default/global/user-error.html.tmpl
index 69afaf46a..dcf4fc789 100644
--- a/template/en/default/global/user-error.html.tmpl
+++ b/template/en/default/global/user-error.html.tmpl
@@ -1433,7 +1433,7 @@
Either you mis-typed the name or that user has not yet registered
for a [% terms.Bugzilla %] account.
[% ELSIF class == "Bugzilla::Keyword" %]
- See the list of available <a href="describekeywords.cgi">keywords</a>.
+ See the list of available <a href="describekeywords.cgi?show_inactive_keywords=1">keywords</a>.
[% END %]
[% ELSIF error == "old_password_incorrect" %]
diff --git a/template/en/default/list/list.html.tmpl b/template/en/default/list/list.html.tmpl
index 368cd9c08..321716873 100644
--- a/template/en/default/list/list.html.tmpl
+++ b/template/en/default/list/list.html.tmpl
@@ -131,6 +131,24 @@
[% END %]
</li>
[% END %]
+
+[% IF user.id AND bugs.size %]
+ <div class="bz_last_visit_description">
+ <span class="bz_new_since_last_visit">
+ [% terms.Bugs %] you are involved in, but which you never visited or did not visit
+ in the last [% Param('last_visit_keep_days') %] days
+ </span>
+ <span class="bz_updated_since_last_visit">
+ [% terms.Bugs %] you are involved in, which have been updated since your last visit
+ </span>
+ <span class="bz_up_to_date_since_last_visit">
+ [% terms.Bugs %] you are involved in, which got no updates since your last visit
+ </span>
+ <span class="bz_unknown_since_last_visit">
+ [% terms.Bugs %] you are not involved in
+ </span>
+ </div>
+[% END %]
</ul>
<hr>
diff --git a/template/en/default/list/table.html.tmpl b/template/en/default/list/table.html.tmpl
index d3abc9b21..f6ca804e8 100644
--- a/template/en/default/list/table.html.tmpl
+++ b/template/en/default/list/table.html.tmpl
@@ -168,6 +168,7 @@
[%+ "bz_$bug.resolution" FILTER css_class_quote IF bug.resolution -%]
[%+ "bz_secure" IF bug.secure_mode -%]
[%+ "bz_secure_mode_$bug.secure_mode" FILTER css_class_quote IF bug.secure_mode -%]
+ [%+ "bz_${bug.last_visit_status}_since_last_visit" FILTER css_class_quote IF bug.last_visit_status -%]
[%+ count % 2 == 1 ? "bz_row_odd" : "bz_row_even" -%]
">
diff --git a/template/en/default/reports/keywords.html.tmpl b/template/en/default/reports/keywords.html.tmpl
index 598979d33..6e7ad0c50 100644
--- a/template/en/default/reports/keywords.html.tmpl
+++ b/template/en/default/reports/keywords.html.tmpl
@@ -18,36 +18,67 @@
title = "$terms.Bugzilla Keyword Descriptions"
style_urls = ['skins/standard/admin.css']
%]
+[% cgi = Bugzilla.cgi %]
+[% show_inactive_keywords = cgi.param("show_inactive_keywords") %]
-<table id="admin_table">
+<script>
+ $(document).ready(function () {
+ var show_inactive_keywords = [% show_inactive_keywords ? "true" : "false" FILTER none %];
+ link = $("#keywords_show_hide"),
+ rows = $("tr.keyword_inactive");
+
+ link.click(function (event) {
+ if (show_inactive_keywords) {
+ show_inactive_keywords = false;
+ rows.show();
+ link.html("Hide inactive keywords");
+ }
+ else {
+ show_inactive_keywords = true;
+ rows.hide();
+ link.html("Show inactive keywords");
+ }
+ event.preventDefault();
+ }).click();
+ });
+</script>
+
+<p>
+ <a href="[% urlbase FILTER html %]?show_inactive_keywords=[% show_inactive_keywords ? "1" : "0" FILTER none %]"
+ id="keywords_show_hide">[% show_inactive_keywords ? "Show" : "Hide" FILTER html %] inactive keywords</a>
+</p>
+
+<table id="admin_table" class="describe_keywords">
<tr class="column_header">
<th>Name</th>
<th>Description</th>
+ <th>Active</th>
<th>Open [% terms.Bugs %]</th>
<th>Total [% terms.Bugs %]</th>
</tr>
[% FOREACH keyword = keywords %]
- <tr id="[% keyword.name FILTER html %]">
- <td>[% keyword.name FILTER html %]</td>
- <td>[% keyword.description FILTER html_light %]</td>
- <td class="center">
- [% IF keyword.bug_count > 0 %]
- <a href="buglist.cgi?keywords=[% keyword.name FILTER uri %]&amp;resolution=---">
- Search</a>
- [% ELSE %]
- none
- [% END %]
- </td>
- <td class="right">
- [% IF keyword.bug_count > 0 %]
- <a href="buglist.cgi?keywords=[% keyword.name FILTER uri %]">
- [% keyword.bug_count %]</a>
- [% ELSE %]
- none
- [% END %]
- </td>
- </tr>
+ <tr id="[% keyword.name FILTER html %]" class="[% keyword.is_active ? "keyword_active" : "keyword_inactive" FILTER html %]">
+ <td>[% keyword.name FILTER html %]</td>
+ <td>[% keyword.description FILTER html_light %]</td>
+ <td>[% keyword.is_active ? "Yes" : "No" FILTER html %]</td>
+ <td class="center">
+ [% IF keyword.bug_count > 0 %]
+ <a href="buglist.cgi?keywords=[% keyword.name FILTER uri %]&amp;resolution=---">
+ Search</a>
+ [% ELSE %]
+ none
+ [% END %]
+ </td>
+ <td class="right">
+ [% IF keyword.bug_count > 0 %]
+ <a href="buglist.cgi?keywords=[% keyword.name FILTER uri %]">
+ [% keyword.bug_count %]</a>
+ [% ELSE %]
+ none
+ [% END %]
+ </td>
+ </tr>
[% END %]
</table>