summaryrefslogtreecommitdiffstats
path: root/Bugzilla
diff options
context:
space:
mode:
authorTiago Mello <timello@gmail.com>2010-12-20 23:49:10 +0100
committerTiago Mello <timello@gmail.com>2010-12-20 23:49:10 +0100
commited3e015a1c21061ed9f30cfc3fe3c3e83c0d2fb1 (patch)
tree16a3b61e4fd8f04479bcbe02c8ed4d367b97e9f4 /Bugzilla
parentc9c81ee7156fc47d834317644baa25cec7cf16e4 (diff)
downloadbugzilla-ed3e015a1c21061ed9f30cfc3fe3c3e83c0d2fb1.tar.gz
bugzilla-ed3e015a1c21061ed9f30cfc3fe3c3e83c0d2fb1.tar.xz
Bug 593539: Refactor See Also to use separate modules for each type of URL
r/a=mkanat
Diffstat (limited to 'Bugzilla')
-rw-r--r--Bugzilla/Bug.pm192
-rw-r--r--Bugzilla/BugUrl.pm173
-rw-r--r--Bugzilla/BugUrl/Bugzilla.pm61
-rw-r--r--Bugzilla/BugUrl/Bugzilla/Local.pm105
-rw-r--r--Bugzilla/BugUrl/Debian.pm62
-rw-r--r--Bugzilla/BugUrl/Google.pm64
-rw-r--r--Bugzilla/BugUrl/Launchpad.pm58
-rw-r--r--Bugzilla/Install/DB.pm3
8 files changed, 558 insertions, 160 deletions
diff --git a/Bugzilla/Bug.pm b/Bugzilla/Bug.pm
index 9d41d5c7e..fd0111578 100644
--- a/Bugzilla/Bug.pm
+++ b/Bugzilla/Bug.pm
@@ -49,6 +49,7 @@ use Bugzilla::Component;
use Bugzilla::Group;
use Bugzilla::Status;
use Bugzilla::Comment;
+use Bugzilla::BugUrl;
use List::MoreUtils qw(firstidx uniq);
use List::Util qw(min max first);
@@ -930,6 +931,14 @@ sub update {
}
# See Also
+ foreach my $field_values (@{ $self->{added_see_also} || [] }) {
+ my $class = delete $field_values->{class};
+ $class->insert_create_data($field_values);
+ push @{ $self->see_also }, $field_values->{value};
+ }
+
+ delete $self->{added_see_also};
+
my ($removed_see, $added_see) =
diff_arrays($old_bug->see_also, $self->see_also);
@@ -938,19 +947,13 @@ sub update {
. $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)];
}
- # Call update for the referenced bugs.
- $_->update() foreach @{ $self->{see_also_update} || [] };
-
# 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.
@@ -1193,9 +1196,9 @@ sub send_changes {
}
# Sending emails for the referenced bugs.
- foreach my $ref_bug (@{ $self->{see_also_update} || [] }) {
+ foreach my $ref_bug_id (uniq @{ $self->{see_also_changes} || [] }) {
_send_bugmail({ forced => { changer => $user },
- id => $ref_bug->id }, $vars);
+ id => $ref_bug_id }, $vars);
}
}
@@ -2787,173 +2790,42 @@ sub remove_group {
}
sub add_see_also {
- my ($self, $input, $skip_recursion) = @_;
+ my ($self, $input) = @_;
$input = trim($input);
- if (!$input) {
- ThrowCodeError('param_required',
- { function => 'add_see_also', param => '$input' });
- }
-
- # If a bug id/alias has been taken, then treat it
- # as a link to the local Bugzilla.
- my $local_bug_uri = correct_urlbase() . "show_bug.cgi?id=";
- if ($input =~ m/^\w+$/) {
- $input = $local_bug_uri . $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' });
- }
-
- # This stops the following edge cases from being accepted:
- # * show_bug.cgi?id=1
- # * /show_bug.cgi?id=1
- # * http:///show_bug.cgi?id=1
- if (!$uri->authority or $uri->path !~ m{/}) {
- ThrowUserError('bug_url_invalid',
- { url => $input, reason => 'path_only' });
- }
-
- my $result;
- # Launchpad URLs
- if ($uri->authority =~ /launchpad.net$/) {
- # Launchpad bug URLs can look like various things:
- # https://bugs.launchpad.net/ubuntu/+bug/1234
- # https://launchpad.net/bugs/1234
- # All variations end with either "/bugs/1234" or "/+bug/1234"
- if ($uri->path =~ m|bugs?/(\d+)$|) {
- # This is the shortest standard URL form for Launchpad bugs,
- # and so we reduce all URLs to this.
- $result = "https://launchpad.net/bugs/$1";
- }
- else {
- ThrowUserError('bug_url_invalid',
- { url => $input, reason => 'id' });
- }
- }
- # Google Code URLs
- elsif ($uri->authority =~ /^code.google.com$/i) {
- # Google Code URLs only have one form:
- # http(s)://code.google.com/p/PROJECT_NAME/issues/detail?id=1234
- my $project_name;
- if ($uri->path =~ m|^/p/([^/]+)/issues/detail$|) {
- $project_name = $1;
- } else {
- ThrowUserError('bug_url_invalid',
- { url => $input });
- }
- my $bug_id = $uri->query_param('id');
- detaint_natural($bug_id);
- if (!$bug_id) {
- ThrowUserError('bug_url_invalid',
- { url => $input, reason => 'id' });
- }
- # While Google Code URLs can be either HTTP or HTTPS,
- # always go with the HTTP scheme, as that's the default.
- $result = "http://code.google.com/p/" . $project_name .
- "/issues/detail?id=" . $bug_id;
- }
- # Debian BTS URLs
- elsif ($uri->authority =~ /^bugs.debian.org$/i) {
- # Debian BTS URLs can look like various things:
- # http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1234
- # http://bugs.debian.org/1234
- my $bug_id;
- if ($uri->path =~ m|^/(\d+)$|) {
- $bug_id = $1;
- }
- elsif ($uri->path =~ /bugreport\.cgi$/) {
- $bug_id = $uri->query_param('bug');
- detaint_natural($bug_id);
- }
- if (!$bug_id) {
- ThrowUserError('bug_url_invalid',
- { url => $input, reason => 'id' });
- }
- # This is the shortest standard URL form for Debian BTS URLs,
- # and so we reduce all URLs to this.
- $result = "http://bugs.debian.org/" . $bug_id;
- }
- # Bugzilla URLs
- else {
- 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' });
- }
+ my ($class, $uri) = Bugzilla::BugUrl->class_for($input);
- # 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 $uri_canonical = $uri->canonical;
- $result = $uri_canonical->as_string;
-
- # If this is a link to a local bug (treating the domain
- # case-insensitively and ignoring http(s)://), then also update
- # the other bug to point at this one.
- my $canonical_local = URI->new($local_bug_uri)->canonical;
- if (!$skip_recursion
- and $canonical_local->authority eq $uri_canonical->authority
- and $canonical_local->path eq $uri_canonical->path)
- {
- my $ref_bug = Bugzilla::Bug->check($bug_id);
- if ($ref_bug->id == $self->id) {
- ThrowUserError('see_also_self_reference');
- }
-
- my $product = $ref_bug->product_obj;
- if (!Bugzilla->user->can_edit_product($product->id)) {
- ThrowUserError("product_edit_denied",
- { product => $product->name });
- }
-
- my $ref_input = $local_bug_uri . $self->id;
- if (!grep($ref_input, @{ $ref_bug->see_also })) {
- $ref_bug->add_see_also($ref_input, 'skip recursion');
- push @{ $self->{see_also_update} }, $ref_bug;
- }
- }
+ my $params = { value => $uri, bug_id => $self };
+ $class->check_required_create_fields($params);
- }
+ my $field_values = $class->run_create_validators($params);
+ $uri = $field_values->{value};
+ $field_values->{value} = $uri->as_string;
+ $field_values->{class} = $class;
- if (length($result) > MAX_BUG_URL_LENGTH) {
- ThrowUserError('bug_url_too_long', { url => $result });
+ # If this is a link to a local bug then save the
+ # ref bug id for sending changes email.
+ if ($class->isa('Bugzilla::BugUrl::Bugzilla::Local')) {
+ my $ref_bug = $field_values->{ref_bug};
+ my $self_url = $class->local_uri . $self->id;
+ push @{ $self->{see_also_changes} }, $ref_bug->id
+ if !grep { $_ eq $self_url } @{ $ref_bug->see_also };
}
# 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 }) {
+ my $value = $uri->as_string;
+ if (!grep { lc($_) eq lc($value) } @{ $self->see_also }) {
my $privs;
- my $can = $self->check_can_change_field('see_also', '', $result, \$privs);
+ my $can = $self->check_can_change_field('see_also', '', $value, \$privs);
if (!$can) {
ThrowUserError('illegal_change', { field => 'see_also',
- newvalue => $result,
+ newvalue => $value,
privs => $privs });
}
- push(@{ $self->see_also }, $result);
+ push @{ $self->{added_see_also} }, $field_values;
}
}
diff --git a/Bugzilla/BugUrl.pm b/Bugzilla/BugUrl.pm
new file mode 100644
index 000000000..521ee8193
--- /dev/null
+++ b/Bugzilla/BugUrl.pm
@@ -0,0 +1,173 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Tiago Mello
+# Portions created by Tiago Mello are Copyright (C) 2010
+# Tiago Mello. All Rights Reserved.
+#
+# Contributor(s): Tiago Mello <timello@linux.vnet.ibm.com>
+
+package Bugzilla::BugUrl;
+use strict;
+use base qw(Bugzilla::Object);
+
+use Bugzilla::Util;
+use Bugzilla::Error;
+use Bugzilla::Constants;
+
+use URI::QueryParam;
+
+###############################
+#### Initialization ####
+###############################
+
+use constant DB_TABLE => 'bug_see_also';
+use constant NAME_FIELD => 'value';
+use constant LIST_ORDER => 'id';
+
+use constant DB_COLUMNS => qw(
+ id
+ bug_id
+ value
+);
+
+# This must be strings with the names of the validations,
+# instead of coderefs, because subclasses override these
+# validators with their own.
+use constant VALIDATORS => {
+ value => '_check_value',
+ bug_id => '_check_bug_id',
+};
+
+# This is the order we go through all of subclasses and
+# pick the first one that should handle the url. New
+# subclasses should be added at the end of the list.
+use constant SUB_CLASSES => qw(
+ Bugzilla::BugUrl::Bugzilla::Local
+ Bugzilla::BugUrl::Bugzilla
+ Bugzilla::BugUrl::Launchpad
+ Bugzilla::BugUrl::Google
+ Bugzilla::BugUrl::Debian
+);
+
+###############################
+#### Methods ####
+###############################
+
+sub new {
+ my $class = shift;
+ my $param = shift;
+
+ if (ref $param) {
+ my $bug_id = $param->{bug_id};
+ my $name = $param->{name} || $param->{value};
+ if (!defined $bug_id) {
+ ThrowCodeError('bad_arg',
+ { argument => 'bug_id',
+ function => "${class}::new" });
+ }
+ if (!defined $name) {
+ ThrowCodeError('bad_arg',
+ { argument => 'name',
+ function => "${class}::new" });
+ }
+
+ my $condition = 'bug_id = ? AND value = ?';
+ my @values = ($bug_id, $name);
+ $param = { condition => $condition, values => \@values };
+ }
+
+ unshift @_, $param;
+ return $class->SUPER::new(@_);
+}
+
+# This is an abstract method. It must be overridden
+# in every subclass.
+sub should_handle {
+ my ($class, $input) = @_;
+ ThrowCodeError('unknown_method',
+ { method => "${class}::should_handle" });
+}
+
+sub class_for {
+ my ($class, $value) = @_;
+
+ my $uri = URI->new($value);
+ foreach my $subclass ($class->SUB_CLASSES) {
+ eval "use $subclass";
+ die $@ if $@;
+ return wantarray ? ($subclass, $uri) : $subclass
+ if $subclass->should_handle($uri);
+ }
+
+ ThrowUserError('bug_url_invalid', { url => $value,
+ reason => 'show_bug' });
+}
+
+sub _check_bug_id {
+ my ($class, $bug_id) = @_;
+
+ my $bug;
+ if (blessed $bug_id) {
+ # We got a bug object passed in, use it
+ $bug = $bug_id;
+ $bug->check_is_visible;
+ }
+ else {
+ # We got a bug id passed in, check it and get the bug object
+ $bug = Bugzilla::Bug->check({ id => $bug_id });
+ }
+
+ return $bug->id;
+}
+
+sub _check_value {
+ my ($class, $uri) = @_;
+
+ my $value = $uri->as_string;
+
+ if (!$value) {
+ ThrowCodeError('param_required',
+ { function => 'add_see_also', param => '$value' });
+ }
+
+ # We assume that the URL is an HTTP URL if there is no (something)://
+ # in front.
+ 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://$value");
+ }
+ elsif ($uri->scheme ne 'http' && $uri->scheme ne 'https') {
+ ThrowUserError('bug_url_invalid', { url => $value, reason => 'http' });
+ }
+
+ # This stops the following edge cases from being accepted:
+ # * show_bug.cgi?id=1
+ # * /show_bug.cgi?id=1
+ # * http:///show_bug.cgi?id=1
+ if (!$uri->authority or $uri->path !~ m{/}) {
+ ThrowUserError('bug_url_invalid',
+ { url => $value, reason => 'path_only' });
+ }
+
+ if (length($uri->path) > MAX_BUG_URL_LENGTH) {
+ ThrowUserError('bug_url_too_long', { url => $uri->path });
+ }
+
+ return $uri;
+}
+
+1;
diff --git a/Bugzilla/BugUrl/Bugzilla.pm b/Bugzilla/BugUrl/Bugzilla.pm
new file mode 100644
index 000000000..3957afc9d
--- /dev/null
+++ b/Bugzilla/BugUrl/Bugzilla.pm
@@ -0,0 +1,61 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Tiago Mello
+# Portions created by Tiago Mello are Copyright (C) 2010
+# Tiago Mello. All Rights Reserved.
+#
+# Contributor(s): Tiago Mello <timello@linux.vnet.ibm.com>
+
+package Bugzilla::BugUrl::Bugzilla;
+use strict;
+use base qw(Bugzilla::BugUrl);
+
+use Bugzilla::Error;
+use Bugzilla::Util;
+
+###############################
+#### Methods ####
+###############################
+
+sub should_handle {
+ my ($class, $uri) = @_;
+ return ($uri->path =~ /show_bug\.cgi$/) ? 1 : 0;
+}
+
+sub _check_value {
+ my ($class, $uri) = @_;
+
+ $uri = $class->SUPER::_check_value($uri);
+
+ 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) {
+ my $value = $uri->as_string;
+ ThrowUserError('bug_url_invalid', { url => $value, 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);
+
+ return $uri;
+}
+
+1;
diff --git a/Bugzilla/BugUrl/Bugzilla/Local.pm b/Bugzilla/BugUrl/Bugzilla/Local.pm
new file mode 100644
index 000000000..22812d085
--- /dev/null
+++ b/Bugzilla/BugUrl/Bugzilla/Local.pm
@@ -0,0 +1,105 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Tiago Mello
+# Portions created by Tiago Mello are Copyright (C) 2010
+# Tiago Mello. All Rights Reserved.
+#
+# Contributor(s): Tiago Mello <timello@linux.vnet.ibm.com>
+
+package Bugzilla::BugUrl::Bugzilla::Local;
+use strict;
+use base qw(Bugzilla::BugUrl::Bugzilla);
+
+use Bugzilla::Error;
+use Bugzilla::Util;
+
+###############################
+#### Initialization ####
+###############################
+
+use constant VALIDATOR_DEPENDENCIES => {
+ value => ['bug_id'],
+};
+
+###############################
+#### Methods ####
+###############################
+
+sub insert_create_data {
+ my ($class, $field_values) = @_;
+
+ my $ref_bug = delete $field_values->{ref_bug};
+ my $url = $class->local_uri . $field_values->{bug_id};
+ my $bug_url = $class->SUPER::insert_create_data($field_values);
+
+ # Check if the ref bug has already the url and then,
+ # update the ref bug to point to the current bug.
+ if (!grep { $_ eq $url } @{ $ref_bug->see_also }) {
+ $class->SUPER::insert_create_data(
+ { value => $url, bug_id => $ref_bug->id } );
+ }
+
+ return $bug_url;
+}
+
+sub should_handle {
+ my ($class, $uri) = @_;
+
+ return $uri->as_string =~ m/^\w+$/ ? 1 : 0;
+
+ my $canonical_local = URI->new($class->_local_uri)->canonical;
+
+ # Treating the domain case-insensitively and ignoring http(s)://
+ return ($canonical_local->authority eq $uri->canonical->authority
+ and $canonical_local->path eq $uri->canonical->path) ? 1 : 0;
+}
+
+sub _check_value {
+ my ($class, $uri, undef, $params) = @_;
+
+ # At this point we are going to treat any word as a
+ # bug id/alias to the local Bugzilla.
+ my $value = $uri->as_string;
+ if ($value =~ m/^\w+$/) {
+ $uri = new URI($class->local_uri . $value);
+ } else {
+ # It's not a word, then we have to check
+ # if it's a valid Bugzilla url.
+ $uri = $class->SUPER::_check_value($uri);
+ }
+
+ my $ref_bug_id = $uri->query_param('id');
+ my $ref_bug = Bugzilla::Bug->check($ref_bug_id);
+ my $self_bug_id = $params->{bug_id};
+ $params->{ref_bug} = $ref_bug;
+
+ if ($ref_bug->id == $self_bug_id) {
+ ThrowUserError('see_also_self_reference');
+ }
+
+ my $product = $ref_bug->product_obj;
+ if (!Bugzilla->user->can_edit_product($product->id)) {
+ ThrowUserError("product_edit_denied",
+ { product => $product->name });
+ }
+
+ return $uri;
+}
+
+sub local_uri {
+ return correct_urlbase() . "show_bug.cgi?id=";
+}
+
+1;
diff --git a/Bugzilla/BugUrl/Debian.pm b/Bugzilla/BugUrl/Debian.pm
new file mode 100644
index 000000000..90d61a69a
--- /dev/null
+++ b/Bugzilla/BugUrl/Debian.pm
@@ -0,0 +1,62 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Tiago Mello
+# Portions created by Tiago Mello are Copyright (C) 2010
+# Tiago Mello. All Rights Reserved.
+#
+# Contributor(s): Tiago Mello <timello@linux.vnet.ibm.com>
+
+package Bugzilla::BugUrl::Debian;
+use strict;
+use base qw(Bugzilla::BugUrl);
+
+use Bugzilla::Error;
+use Bugzilla::Util;
+
+###############################
+#### Methods ####
+###############################
+
+sub should_handle {
+ my ($class, $uri) = @_;
+ return ($uri->authority =~ /^bugs.debian.org$/i) ? 1 : 0;
+}
+
+sub _check_value {
+ my $class = shift;
+
+ my $uri = $class->SUPER::_check_value(@_);
+
+ # Debian BTS URLs can look like various things:
+ # http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1234
+ # http://bugs.debian.org/1234
+ my $bug_id;
+ if ($uri->path =~ m|^/(\d+)$|) {
+ $bug_id = $1;
+ }
+ elsif ($uri->path =~ /bugreport\.cgi$/) {
+ $bug_id = $uri->query_param('bug');
+ detaint_natural($bug_id);
+ }
+ if (!$bug_id) {
+ ThrowUserError('bug_url_invalid',
+ { url => $uri->path, reason => 'id' });
+ }
+ # This is the shortest standard URL form for Debian BTS URLs,
+ # and so we reduce all URLs to this.
+ return new URI("http://bugs.debian.org/" . $bug_id);
+}
+
+1;
diff --git a/Bugzilla/BugUrl/Google.pm b/Bugzilla/BugUrl/Google.pm
new file mode 100644
index 000000000..4f6321e74
--- /dev/null
+++ b/Bugzilla/BugUrl/Google.pm
@@ -0,0 +1,64 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Tiago Mello
+# Portions created by Tiago Mello are Copyright (C) 2010
+# Tiago Mello. All Rights Reserved.
+#
+# Contributor(s): Tiago Mello <timello@linux.vnet.ibm.com>
+
+package Bugzilla::BugUrl::Google;
+use strict;
+use base qw(Bugzilla::BugUrl);
+
+use Bugzilla::Error;
+use Bugzilla::Util;
+
+###############################
+#### Methods ####
+###############################
+
+sub should_handle {
+ my ($class, $uri) = @_;
+ return ($uri->authority =~ /^code.google.com$/i) ? 1 : 0;
+}
+
+sub _check_value {
+ my ($class, $uri) = @_;
+
+ $uri = $class->SUPER::_check_value($uri);
+
+ my $value = $uri->as_string;
+ # Google Code URLs only have one form:
+ # http(s)://code.google.com/p/PROJECT_NAME/issues/detail?id=1234
+ my $project_name;
+ if ($uri->path =~ m|^/p/([^/]+)/issues/detail$|) {
+ $project_name = $1;
+ } else {
+ ThrowUserError('bug_url_invalid', { url => $value });
+ }
+ my $bug_id = $uri->query_param('id');
+ detaint_natural($bug_id);
+ if (!$bug_id) {
+ ThrowUserError('bug_url_invalid', { url => $value, reason => 'id' });
+ }
+ # While Google Code URLs can be either HTTP or HTTPS,
+ # always go with the HTTP scheme, as that's the default.
+ $value = "http://code.google.com/p/" . $project_name .
+ "/issues/detail?id=" . $bug_id;
+
+ return new URI($value);
+}
+
+1;
diff --git a/Bugzilla/BugUrl/Launchpad.pm b/Bugzilla/BugUrl/Launchpad.pm
new file mode 100644
index 000000000..bb16b3b30
--- /dev/null
+++ b/Bugzilla/BugUrl/Launchpad.pm
@@ -0,0 +1,58 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Tiago Mello
+# Portions created by Tiago Mello are Copyright (C) 2010
+# Tiago Mello. All Rights Reserved.
+#
+# Contributor(s): Tiago Mello <timello@linux.vnet.ibm.com>
+
+package Bugzilla::BugUrl::Launchpad;
+use strict;
+use base qw(Bugzilla::BugUrl);
+
+use Bugzilla::Error;
+
+###############################
+#### Methods ####
+###############################
+
+sub should_handle {
+ my ($class, $uri) = @_;
+ return ($uri->authority =~ /launchpad.net$/) ? 1 : 0;
+}
+
+sub _check_value {
+ my ($class, $uri) = @_;
+
+ $uri = $class->SUPER::_check_value($uri);
+
+ my $value = $uri->as_string;
+ # Launchpad bug URLs can look like various things:
+ # https://bugs.launchpad.net/ubuntu/+bug/1234
+ # https://launchpad.net/bugs/1234
+ # All variations end with either "/bugs/1234" or "/+bug/1234"
+ if ($uri->path =~ m|bugs?/(\d+)$|) {
+ # This is the shortest standard URL form for Launchpad bugs,
+ # and so we reduce all URLs to this.
+ $value = "https://launchpad.net/bugs/$1";
+ }
+ else {
+ ThrowUserError('bug_url_invalid', { url => $value, reason => 'id' });
+ }
+
+ return new URI($value);
+}
+
+1;
diff --git a/Bugzilla/Install/DB.pm b/Bugzilla/Install/DB.pm
index 47c8873fa..9cbd66f42 100644
--- a/Bugzilla/Install/DB.pm
+++ b/Bugzilla/Install/DB.pm
@@ -645,6 +645,9 @@ sub update_table_definitions {
# 2010-10-09 LpSolit@gmail.com - Bug 451735
_fix_series_indexes();
+ $dbh->bz_add_column('bug_see_also', 'id',
+ {TYPE => 'MEDIUMSERIAL', NOTNULL => 1, PRIMARYKEY => 1});
+
################################################################
# New --TABLE-- changes should go *** A B O V E *** this point #
################################################################