aboutsummaryrefslogtreecommitdiffstats
path: root/extensions
diff options
context:
space:
mode:
Diffstat (limited to 'extensions')
-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
23 files changed, 1023 insertions, 0 deletions
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>