summaryrefslogtreecommitdiffstats
path: root/Bugzilla
diff options
context:
space:
mode:
Diffstat (limited to 'Bugzilla')
-rw-r--r--Bugzilla/Bug.pm86
-rw-r--r--Bugzilla/Constants.pm6
-rw-r--r--Bugzilla/DB/Schema.pm12
-rw-r--r--Bugzilla/Field.pm4
-rw-r--r--Bugzilla/Install/Requirements.pm5
-rw-r--r--Bugzilla/Search.pm5
6 files changed, 113 insertions, 5 deletions
diff --git a/Bugzilla/Bug.pm b/Bugzilla/Bug.pm
index 0b4e6e794..5d6584e77 100644
--- a/Bugzilla/Bug.pm
+++ b/Bugzilla/Bug.pm
@@ -47,6 +47,8 @@ use Bugzilla::Status;
use List::Util qw(min);
use Storable qw(dclone);
+use URI;
+use URI::QueryParam;
use base qw(Bugzilla::Object Exporter);
@Bugzilla::Bug::EXPORT = qw(
@@ -746,6 +748,25 @@ sub update {
}
}
+ # See Also
+ my ($removed_see, $added_see) =
+ diff_arrays($old_bug->see_also, $self->see_also);
+
+ if (scalar @$removed_see) {
+ $dbh->do('DELETE FROM bug_see_also WHERE bug_id = ? AND '
+ . $dbh->sql_in('value', [('?') x @$removed_see]),
+ undef, $self->id, @$removed_see);
+ }
+ foreach my $url (@$added_see) {
+ $dbh->do('INSERT INTO bug_see_also (bug_id, value) VALUES (?,?)',
+ undef, $self->id, $url);
+ }
+ # If any changes were found, record it in the activity log
+ if (scalar @$removed_see || scalar @$added_see) {
+ $changes->{see_also} = [join(', ', @$removed_see),
+ join(', ', @$added_see)];
+ }
+
# Log bugs_activity items
# XXX Eventually, when bugs_activity is able to track the dupe_id,
# this code should go below the duplicates-table-updating code below.
@@ -1691,7 +1712,7 @@ sub fields {
reporter_accessible cclist_accessible
classification_id classification
product component version rep_platform op_sys
- bug_status resolution dup_id
+ bug_status resolution dup_id see_also
bug_file_loc status_whiteboard keywords
priority bug_severity target_milestone
dependson blocked votes everconfirmed
@@ -2268,6 +2289,61 @@ sub remove_group {
@$current_groups = grep { $_->id != $group->id } @$current_groups;
}
+sub add_see_also {
+ my ($self, $input) = @_;
+ $input = trim($input);
+
+ # We assume that the URL is an HTTP URL if there is no (something)://
+ # in front.
+ my $uri = new URI($input);
+ if (!$uri->scheme) {
+ # This works better than setting $uri->scheme('http'), because
+ # that creates URLs like "http:domain.com" and doesn't properly
+ # differentiate the path from the domain.
+ $uri = new URI("http://$input");
+ }
+ elsif ($uri->scheme ne 'http' && $uri->scheme ne 'https') {
+ ThrowUserError('bug_url_invalid', { url => $input, reason => 'http' });
+ }
+
+ if ($uri->path !~ /show_bug\.cgi$/) {
+ ThrowUserError('bug_url_invalid',
+ { url => $input, reason => 'show_bug' });
+ }
+ my $bug_id = $uri->query_param('id');
+ # We don't currently allow aliases, because we can't check to see
+ # if somebody's putting both an alias link and a numeric ID link.
+ # When we start validating the URL by accessing the other Bugzilla,
+ # we can allow aliases.
+ detaint_natural($bug_id);
+ if (!$bug_id) {
+ ThrowUserError('bug_url_invalid', { url => $input, reason => 'id' });
+ }
+
+ # Make sure that "id" is the only query parameter.
+ $uri->query("id=$bug_id");
+ # And remove any # part if there is one.
+ $uri->fragment(undef);
+ my $result = $uri->canonical->as_string;
+
+ if (length($result) > MAX_BUG_URL_LENGTH) {
+ ThrowUserError('bug_url_too_long', { url => $result });
+ }
+
+ # We only add the new URI if it hasn't been added yet. URIs are
+ # case-sensitive, but most of our DBs are case-insensitive, so we do
+ # this check case-insensitively.
+ if (!grep { lc($_) eq lc($result) } @{ $self->see_also }) {
+ push(@{ $self->see_also }, $result);
+ }
+}
+
+sub remove_see_also {
+ my ($self, $url) = @_;
+ my $see_also = $self->see_also;
+ @$see_also = grep { lc($_) ne lc($url) } @$see_also;
+}
+
#####################################################################
# Instance Accessors
#####################################################################
@@ -2539,6 +2615,14 @@ sub reporter {
return $self->{'reporter'};
}
+sub see_also {
+ my ($self) = @_;
+ return [] if $self->{'error'};
+ $self->{'see_also'} ||= Bugzilla->dbh->selectcol_arrayref(
+ 'SELECT value FROM bug_see_also WHERE bug_id = ?', undef, $self->id);
+ return $self->{'see_also'};
+}
+
sub status {
my $self = shift;
return undef if $self->{'error'};
diff --git a/Bugzilla/Constants.pm b/Bugzilla/Constants.pm
index d93f91271..1105c27dc 100644
--- a/Bugzilla/Constants.pm
+++ b/Bugzilla/Constants.pm
@@ -124,6 +124,7 @@ use File::Basename;
FIELD_TYPE_TEXTAREA
FIELD_TYPE_DATETIME
FIELD_TYPE_BUG_ID
+ FIELD_TYPE_BUG_URLS
USAGE_MODE_BROWSER
USAGE_MODE_CMDLINE
@@ -156,6 +157,7 @@ use File::Basename;
MAX_COMPONENT_SIZE
MAX_FIELD_VALUE_SIZE
MAX_FREETEXT_LENGTH
+ MAX_BUG_URL_LENGTH
PASSWORD_DIGEST_ALGORITHM
PASSWORD_SALT_LENGTH
@@ -361,6 +363,7 @@ use constant FIELD_TYPE_MULTI_SELECT => 3;
use constant FIELD_TYPE_TEXTAREA => 4;
use constant FIELD_TYPE_DATETIME => 5;
use constant FIELD_TYPE_BUG_ID => 6;
+use constant FIELD_TYPE_BUG_URLS => 7;
# The maximum number of days a token will remain valid.
use constant MAX_TOKEN_AGE => 3;
@@ -447,6 +450,9 @@ use constant MAX_FIELD_VALUE_SIZE => 64;
# Maximum length allowed for free text fields.
use constant MAX_FREETEXT_LENGTH => 255;
+# The longest a bug URL in a BUG_URLS field can be.
+use constant MAX_BUG_URL_LENGTH => 255;
+
# This is the name of the algorithm used to hash passwords before storing
# them in the database. This can be any string that is valid to pass to
# Perl's "Digest" module. Note that if you change this, it won't take
diff --git a/Bugzilla/DB/Schema.pm b/Bugzilla/DB/Schema.pm
index 3a4652c93..a3fefd8d8 100644
--- a/Bugzilla/DB/Schema.pm
+++ b/Bugzilla/DB/Schema.pm
@@ -487,6 +487,17 @@ use constant ABSTRACT_SCHEMA => {
],
},
+ bug_see_also => {
+ FIELDS => [
+ bug_id => {TYPE => 'INT3', NOTNULL => 1},
+ value => {TYPE => 'varchar(255)', NOTNULL => 1},
+ ],
+ INDEXES => [
+ bug_see_also_bug_id_idx => {FIELDS => [qw(bug_id value)],
+ TYPE => 'UNIQUE'},
+ ],
+ },
+
# Keywords
# --------
@@ -1500,7 +1511,6 @@ use constant MULTI_SELECT_VALUE_TABLE => {
],
};
-
#--------------------------------------------------------------------------
=head1 METHODS
diff --git a/Bugzilla/Field.pm b/Bugzilla/Field.pm
index 848daf74a..91e9bb8b4 100644
--- a/Bugzilla/Field.pm
+++ b/Bugzilla/Field.pm
@@ -228,6 +228,8 @@ use constant DEFAULT_FIELDS => (
{name => 'attach_data.thedata', desc => 'Attachment data'},
{name => 'attachments.isurl', desc => 'Attachment is a URL'},
{name => "owner_idle_time", desc => "Time Since Assignee Touched"},
+ {name => 'see_also', desc => "See Also",
+ type => FIELD_TYPE_BUG_URLS},
);
################
@@ -309,7 +311,7 @@ sub _check_type {
my $saved_type = $type;
# The constant here should be updated every time a new,
# higher field type is added.
- (detaint_natural($type) && $type <= FIELD_TYPE_BUG_ID)
+ (detaint_natural($type) && $type <= FIELD_TYPE_BUG_URLS)
|| ThrowCodeError('invalid_customfield_type', { type => $saved_type });
return $type;
}
diff --git a/Bugzilla/Install/Requirements.pm b/Bugzilla/Install/Requirements.pm
index a7ac7fc19..4f17ae053 100644
--- a/Bugzilla/Install/Requirements.pm
+++ b/Bugzilla/Install/Requirements.pm
@@ -122,6 +122,11 @@ sub REQUIRED_MODULES {
module => 'Email::MIME::Modifier',
version => '1.442'
},
+ {
+ package => 'URI',
+ module => 'URI',
+ version => 0
+ },
);
my $all_modules = _get_extension_requirements(
diff --git a/Bugzilla/Search.pm b/Bugzilla/Search.pm
index 47b4e1296..de02115fe 100644
--- a/Bugzilla/Search.pm
+++ b/Bugzilla/Search.pm
@@ -104,8 +104,9 @@ sub init {
my @select_fields =
Bugzilla->get_fields({ type => FIELD_TYPE_SINGLE_SELECT });
- my @multi_select_fields = Bugzilla->get_fields({ type => FIELD_TYPE_MULTI_SELECT,
- obsolete => 0 });
+ my @multi_select_fields = Bugzilla->get_fields({
+ type => [FIELD_TYPE_MULTI_SELECT, FIELD_TYPE_BUG_URLS],
+ obsolete => 0 });
foreach my $field (@select_fields) {
my $name = $field->name;
$special_order{"bugs.$name"} = [ "$name.sortkey", "$name.value" ],