summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Bugzilla/Attachment.pm324
-rwxr-xr-xattachment.cgi271
-rw-r--r--js/attachment.js106
-rwxr-xr-xpost_bug.cgi17
-rw-r--r--skins/standard/create_attachment.css36
-rw-r--r--template/en/default/attachment/create.html.tmpl210
-rw-r--r--template/en/default/attachment/createformcontents.html.tmpl100
-rw-r--r--template/en/default/bug/create/create.html.tmpl52
-rw-r--r--template/en/default/global/messages.html.tmpl6
9 files changed, 670 insertions, 452 deletions
diff --git a/Bugzilla/Attachment.pm b/Bugzilla/Attachment.pm
index 00c7ee470..40231aa6c 100644
--- a/Bugzilla/Attachment.pm
+++ b/Bugzilla/Attachment.pm
@@ -19,6 +19,7 @@
#
# Contributor(s): Terry Weissman <terry@mozilla.org>
# Myk Melez <myk@mozilla.org>
+# Marc Schumann <wurblzap@gmail.com>
use strict;
@@ -49,9 +50,11 @@ that users upload to the Bugzilla server.
# This module requires that its caller have said "require globals.pl"
# to import relevant functions from that script.
+use Bugzilla::Error;
use Bugzilla::Flag;
-use Bugzilla::Config qw(:locations);
+use Bugzilla::Config qw(:locations Param);
use Bugzilla::User;
+use Bugzilla::Util qw(trick_taint);
sub get {
my $invocant = shift;
@@ -310,7 +313,7 @@ sub data {
close(AH);
}
}
-
+
return $self->{data};
}
@@ -377,8 +380,8 @@ sub flags {
return $self->{flags};
}
-# Instance methods; no POD documentation here yet because the only one so far
-# is private.
+# Instance methods; no POD documentation here yet because the only ones so far
+# are private.
sub _get_local_filename {
my $self = shift;
@@ -387,6 +390,84 @@ sub _get_local_filename {
return "$attachdir/$hash/attachment." . $self->id;
}
+sub _validate_filename {
+ my ($throw_error) = @_;
+ my $cgi = Bugzilla->cgi;
+ defined $cgi->upload('data')
+ || ($throw_error ? ThrowUserError("file_not_specified") : return 0);
+
+ my $filename = $cgi->upload('data');
+
+ # Remove path info (if any) from the file name. The browser should do this
+ # for us, but some are buggy. This may not work on Mac file names and could
+ # mess up file names with slashes in them, but them's the breaks. We only
+ # use this as a hint to users downloading attachments anyway, so it's not
+ # a big deal if it munges incorrectly occasionally.
+ $filename =~ s/^.*[\/\\]//;
+
+ # Truncate the filename to 100 characters, counting from the end of the
+ # string to make sure we keep the filename extension.
+ $filename = substr($filename, -100, 100);
+
+ return $filename;
+}
+
+sub _validate_data {
+ my ($throw_error, $hr_vars) = @_;
+ my $cgi = Bugzilla->cgi;
+ my $maxsize = $cgi->param('ispatch') ? Param('maxpatchsize') : Param('maxattachmentsize');
+ $maxsize *= 1024; # Convert from K
+ my $fh;
+ # Skip uploading into a local variable if the user wants to upload huge
+ # attachments into local files.
+ if (!$cgi->param('bigfile')) {
+ $fh = $cgi->upload('data');
+ }
+ my $data;
+
+ # We could get away with reading only as much as required, except that then
+ # we wouldn't have a size to print to the error handler below.
+ if (!$cgi->param('bigfile')) {
+ # enable 'slurp' mode
+ local $/;
+ $data = <$fh>;
+ }
+
+ $data
+ || ($cgi->param('bigfile'))
+ || ($throw_error ? ThrowUserError("zero_length_file") : return 0);
+
+ # Windows screenshots are usually uncompressed BMP files which
+ # makes for a quick way to eat up disk space. Let's compress them.
+ # We do this before we check the size since the uncompressed version
+ # could easily be greater than maxattachmentsize.
+ if (Param('convert_uncompressed_images')
+ && $cgi->param('contenttype') eq 'image/bmp') {
+ require Image::Magick;
+ my $img = Image::Magick->new(magick=>'bmp');
+ $img->BlobToImage($data);
+ $img->set(magick=>'png');
+ my $imgdata = $img->ImageToBlob();
+ $data = $imgdata;
+ $cgi->param('contenttype', 'image/png');
+ $$hr_vars->{'convertedbmp'} = 1;
+ }
+
+ # Make sure the attachment does not exceed the maximum permitted size
+ my $len = $data ? length($data) : 0;
+ if ($maxsize && $len > $maxsize) {
+ my $vars = { filesize => sprintf("%.0f", $len/1024) };
+ if ($cgi->param('ispatch')) {
+ $throw_error ? ThrowUserError("patch_too_large", $vars) : return 0;
+ }
+ else {
+ $throw_error ? ThrowUserError("file_too_large", $vars) : return 0;
+ }
+ }
+
+ return $data || '';
+}
+
=pod
=head2 Class Methods
@@ -402,8 +483,6 @@ Params: C<$bug_id> - integer - the ID of the bug for which
Returns: a reference to an array of attachment objects.
-=back
-
=cut
sub get_attachments_by_bug {
@@ -416,4 +495,237 @@ sub get_attachments_by_bug {
return $attachments;
}
+=pod
+
+=item C<validate_is_patch()>
+
+Description: validates the "patch" flag passed in by CGI.
+
+Returns: 1 on success.
+
+=cut
+
+sub validate_is_patch {
+ my ($class, $throw_error) = @_;
+ my $cgi = Bugzilla->cgi;
+
+ # Set the ispatch flag to zero if it is undefined, since the UI uses
+ # an HTML checkbox to represent this flag, and unchecked HTML checkboxes
+ # do not get sent in HTML requests.
+ $cgi->param('ispatch', $cgi->param('ispatch') ? 1 : 0);
+
+ # Set the content type to text/plain if the attachment is a patch.
+ $cgi->param('contenttype', 'text/plain') if $cgi->param('ispatch');
+
+ return 1;
+}
+
+=pod
+
+=item C<validate_description()>
+
+Description: validates the description passed in by CGI.
+
+Returns: 1 on success.
+
+=cut
+
+sub validate_description {
+ my ($class, $throw_error) = @_;
+ my $cgi = Bugzilla->cgi;
+
+ $cgi->param('description')
+ || ($throw_error ? ThrowUserError("missing_attachment_description") : return 0);
+
+ return 1;
+}
+
+=pod
+
+=item C<validate_content_type()>
+
+Description: validates the content type passed in by CGI.
+
+Returns: 1 on success.
+
+=cut
+
+sub validate_content_type {
+ my ($class, $throw_error) = @_;
+ my $cgi = Bugzilla->cgi;
+
+ if (!defined $cgi->param('contenttypemethod')) {
+ $throw_error ? ThrowUserError("missing_content_type_method") : return 0;
+ }
+ elsif ($cgi->param('contenttypemethod') eq 'autodetect') {
+ my $contenttype =
+ $cgi->uploadInfo($cgi->param('data'))->{'Content-Type'};
+ # The user asked us to auto-detect the content type, so use the type
+ # specified in the HTTP request headers.
+ if ( !$contenttype ) {
+ $throw_error ? ThrowUserError("missing_content_type") : return 0;
+ }
+ $cgi->param('contenttype', $contenttype);
+ }
+ elsif ($cgi->param('contenttypemethod') eq 'list') {
+ # The user selected a content type from the list, so use their
+ # selection.
+ $cgi->param('contenttype', $cgi->param('contenttypeselection'));
+ }
+ elsif ($cgi->param('contenttypemethod') eq 'manual') {
+ # The user entered a content type manually, so use their entry.
+ $cgi->param('contenttype', $cgi->param('contenttypeentry'));
+ }
+ else {
+ $throw_error ?
+ ThrowCodeError("illegal_content_type_method",
+ { contenttypemethod => $cgi->param('contenttypemethod') }) :
+ return 0;
+ }
+
+ if ( $cgi->param('contenttype') !~
+ /^(application|audio|image|message|model|multipart|text|video)\/.+$/ ) {
+ $throw_error ?
+ ThrowUserError("invalid_content_type",
+ { contenttype => $cgi->param('contenttype') }) :
+ return 0;
+ }
+
+ return 1;
+}
+
+=pod
+
+=item C<insert_attachment_for_bug($throw_error, $bug_id, $user, $timestamp, $hr_vars)>
+
+Description: inserts an attachment from CGI input for the given bug.
+
+Params: C<$bug_id> - integer - the ID of the bug for which
+ to insert the attachment.
+ C<$user> - Bugzilla::User object - the user we're inserting an
+ attachment for.
+ C<$timestamp> - scalar - timestamp of the insert as returned
+ by SELECT NOW().
+ C<$hr_vars> - hash reference - reference to a hash of template
+ variables.
+
+Returns: the ID of the new attachment.
+
+=back
+
+=cut
+
+sub insert_attachment_for_bug {
+ my ($class, $throw_error, $bug_id, $user, $timestamp, $hr_vars) = @_;
+
+ my $cgi = Bugzilla->cgi;
+ my $dbh = Bugzilla->dbh;
+ my $attachurl = $cgi->param('attachurl') || '';
+ my $data;
+ my $filename;
+ my $contenttype;
+ my $isurl;
+ $class->validate_is_patch($throw_error) || return 0;
+ $class->validate_description($throw_error) || return 0;
+
+ if (($attachurl =~ /^(http|https|ftp):\/\/\S+/)
+ && !(defined $cgi->upload('data'))) {
+ $filename = '';
+ $data = $attachurl;
+ $isurl = 1;
+ $contenttype = 'text/plain';
+ $cgi->param('ispatch', 0);
+ $cgi->delete('bigfile');
+ }
+ else {
+ $filename = _validate_filename($throw_error) || return 0;
+ # need to validate content type before data as
+ # we now check the content type for image/bmp in _validate_data()
+ unless ($cgi->param('ispatch')) {
+ $class->validate_content_type($throw_error) || return 0;
+ }
+ $data = _validate_data($hr_vars, $throw_error) || return 0;
+ $contenttype = $cgi->param('contenttype');
+
+ # These are inserted using placeholders so no need to panic
+ trick_taint($filename);
+ trick_taint($contenttype);
+ $isurl = 0;
+ }
+
+ # The order of these function calls is important, as both Flag::validate
+ # and FlagType::validate assume User::match_field has ensured that the
+ # values in the requestee fields are legitimate user email addresses.
+ my $match_status = Bugzilla::User::match_field($cgi, {
+ '^requestee(_type)?-(\d+)$' => { 'type' => 'multi' },
+ }, MATCH_SKIP_CONFIRM);
+
+ $$hr_vars->{'match_field'} = 'requestee';
+ if ($match_status == USER_MATCH_FAILED) {
+ $$hr_vars->{'message'} = 'user_match_failed';
+ }
+ elsif ($match_status == USER_MATCH_MULTIPLE) {
+ $$hr_vars->{'message'} = 'user_match_multiple';
+ }
+
+ # FlagType::validate() and Flag::validate() should not detect
+ # any reference to existing flags when creating a new attachment.
+ # Setting the third param to -1 will force this function to check this
+ # point.
+ # XXX needs $throw_error treatment
+ Bugzilla::Flag::validate($cgi, $bug_id, -1);
+ Bugzilla::FlagType::validate($cgi, $bug_id, -1);
+
+ # Escape characters in strings that will be used in SQL statements.
+ my $description = $cgi->param('description');
+ trick_taint($description);
+ my $isprivate = $cgi->param('isprivate') ? 1 : 0;
+
+ # Insert the attachment into the database.
+ my $sth = $dbh->do(
+ "INSERT INTO attachments
+ (bug_id, creation_ts, filename, description,
+ mimetype, ispatch, isurl, isprivate, submitter_id)
+ VALUES (?,?,?,?,?,?,?,?,?)", undef, ($bug_id, $timestamp, $filename,
+ $description, $contenttype, $cgi->param('ispatch'),
+ $isurl, $isprivate, $user->id));
+ # Retrieve the ID of the newly created attachment record.
+ my $attachid = $dbh->bz_last_key('attachments', 'attach_id');
+
+ # We only use $data here in this INSERT with a placeholder,
+ # so it's safe.
+ $sth = $dbh->prepare("INSERT INTO attach_data
+ (id, thedata) VALUES ($attachid, ?)");
+ trick_taint($data);
+ $sth->bind_param(1, $data, $dbh->BLOB_TYPE);
+ $sth->execute();
+
+ # If the file is to be stored locally, stream the file from the webserver
+ # to the local file without reading it into a local variable.
+ if ($cgi->param('bigfile')) {
+ my $fh = $cgi->upload('data');
+ my $hash = ($attachid % 100) + 100;
+ $hash =~ s/.*(\d\d)$/group.$1/;
+ mkdir "$attachdir/$hash", 0770;
+ chmod 0770, "$attachdir/$hash";
+ open(AH, ">$attachdir/$hash/attachment.$attachid");
+ binmode AH;
+ my $sizecount = 0;
+ my $limit = (Param("maxlocalattachment") * 1048576);
+ while (<$fh>) {
+ print AH $_;
+ $sizecount += length($_);
+ if ($sizecount > $limit) {
+ close AH;
+ close $fh;
+ unlink "$attachdir/$hash/attachment.$attachid";
+ $throw_error ? ThrowUserError("local_file_too_large") : return 0;
+ }
+ }
+ close AH;
+ close $fh;
+ }
+ return $attachid;
+}
+
1;
diff --git a/attachment.cgi b/attachment.cgi
index 087b267e1..6fa5ea942 100755
--- a/attachment.cgi
+++ b/attachment.cgi
@@ -26,6 +26,7 @@
# Max Kanat-Alexander <mkanat@bugzilla.org>
# Greg Hendricks <ghendricks@novell.com>
# Frédéric Buclin <LpSolit@gmail.com>
+# Marc Schumann <wurblzap@gmail.com>
################################################################################
# Script Initialization
@@ -251,64 +252,6 @@ sub validateCanChangeBug
{ bug_id => $bugid });
}
-sub validateDescription
-{
- $cgi->param('description')
- || ThrowUserError("missing_attachment_description");
-}
-
-sub validateIsPatch
-{
- # Set the ispatch flag to zero if it is undefined, since the UI uses
- # an HTML checkbox to represent this flag, and unchecked HTML checkboxes
- # do not get sent in HTML requests.
- $cgi->param('ispatch', $cgi->param('ispatch') ? 1 : 0);
-
- # Set the content type to text/plain if the attachment is a patch.
- $cgi->param('contenttype', 'text/plain') if $cgi->param('ispatch');
-}
-
-sub validateContentType
-{
- if (!defined $cgi->param('contenttypemethod'))
- {
- ThrowUserError("missing_content_type_method");
- }
- elsif ($cgi->param('contenttypemethod') eq 'autodetect')
- {
- my $contenttype = $cgi->uploadInfo($cgi->param('data'))->{'Content-Type'};
- # The user asked us to auto-detect the content type, so use the type
- # specified in the HTTP request headers.
- if ( !$contenttype )
- {
- ThrowUserError("missing_content_type");
- }
- $cgi->param('contenttype', $contenttype);
- }
- elsif ($cgi->param('contenttypemethod') eq 'list')
- {
- # The user selected a content type from the list, so use their selection.
- $cgi->param('contenttype', $cgi->param('contenttypeselection'));
- }
- elsif ($cgi->param('contenttypemethod') eq 'manual')
- {
- # The user entered a content type manually, so use their entry.
- $cgi->param('contenttype', $cgi->param('contenttypeentry'));
- }
- else
- {
- ThrowCodeError("illegal_content_type_method",
- { contenttypemethod => $cgi->param('contenttypemethod') });
- }
-
- if ( $cgi->param('contenttype') !~
- /^(application|audio|image|message|model|multipart|text|video)\/.+$/ )
- {
- ThrowUserError("invalid_content_type",
- { contenttype => $cgi->param('contenttype') });
- }
-}
-
sub validateIsObsolete
{
# Set the isobsolete flag to zero if it is undefined, since the UI uses
@@ -325,82 +268,6 @@ sub validatePrivate
$cgi->param('isprivate', $cgi->param('isprivate') ? 1 : 0);
}
-sub validateData
-{
- my $maxsize = $cgi->param('ispatch') ? Param('maxpatchsize') : Param('maxattachmentsize');
- $maxsize *= 1024; # Convert from K
- my $fh;
- # Skip uploading into a local variable if the user wants to upload huge
- # attachments into local files.
- if (!$cgi->param('bigfile'))
- {
- $fh = $cgi->upload('data');
- }
- my $data;
-
- # We could get away with reading only as much as required, except that then
- # we wouldn't have a size to print to the error handler below.
- if (!$cgi->param('bigfile'))
- {
- # enable 'slurp' mode
- local $/;
- $data = <$fh>;
- }
-
- $data
- || ($cgi->param('bigfile'))
- || ThrowUserError("zero_length_file");
-
- # Windows screenshots are usually uncompressed BMP files which
- # makes for a quick way to eat up disk space. Let's compress them.
- # We do this before we check the size since the uncompressed version
- # could easily be greater than maxattachmentsize.
- if (Param('convert_uncompressed_images') && $cgi->param('contenttype') eq 'image/bmp'){
- require Image::Magick;
- my $img = Image::Magick->new(magick=>'bmp');
- $img->BlobToImage($data);
- $img->set(magick=>'png');
- my $imgdata = $img->ImageToBlob();
- $data = $imgdata;
- $cgi->param('contenttype', 'image/png');
- $vars->{'convertedbmp'} = 1;
- }
-
- # Make sure the attachment does not exceed the maximum permitted size
- my $len = $data ? length($data) : 0;
- if ($maxsize && $len > $maxsize) {
- my $vars = { filesize => sprintf("%.0f", $len/1024) };
- if ($cgi->param('ispatch')) {
- ThrowUserError("patch_too_large", $vars);
- } else {
- ThrowUserError("file_too_large", $vars);
- }
- }
-
- return $data || '';
-}
-
-sub validateFilename
-{
- defined $cgi->upload('data')
- || ThrowUserError("file_not_specified");
-
- my $filename = $cgi->upload('data');
-
- # Remove path info (if any) from the file name. The browser should do this
- # for us, but some are buggy. This may not work on Mac file names and could
- # mess up file names with slashes in them, but them's the breaks. We only
- # use this as a hint to users downloading attachments anyway, so it's not
- # a big deal if it munges incorrectly occasionally.
- $filename =~ s/^.*[\/\\]//;
-
- # Truncate the filename to 100 characters, counting from the end of the string
- # to make sure we keep the filename extension.
- $filename = substr($filename, -100, 100);
-
- return $filename;
-}
-
sub validateObsolete
{
my @obsolete_ids = ();
@@ -501,7 +368,7 @@ sub view
{
$cgi->param('contenttypemethod', 'manual');
$cgi->param('contenttypeentry', $cgi->param('content_type'));
- validateContentType();
+ Bugzilla::Attachment->validate_content_type(THROW_ERROR);
$contenttype = $cgi->param('content_type');
}
@@ -915,130 +782,30 @@ sub enter
# Insert a new attachment into the database.
sub insert
{
- my $userid = Bugzilla->user->id;
+ my $dbh = Bugzilla->dbh;
+ my $user = Bugzilla->user;
# Retrieve and validate parameters
my $bugid = $cgi->param('bugid');
ValidateBugID($bugid);
validateCanChangeBug($bugid);
ValidateComment(scalar $cgi->param('comment'));
- my $attachurl = $cgi->param('attachurl') || '';
- my $data;
- my $filename;
- my $contenttype;
- my $isurl;
- validateIsPatch();
- validateDescription();
- my $dbh = Bugzilla->dbh;
-
- if (($attachurl =~ /^(http|https|ftp):\/\/\S+/)
- && !(defined $cgi->upload('data'))) {
- $filename = '';
- $data = $attachurl;
- $isurl = 1;
- $contenttype = 'text/plain';
- $cgi->param('ispatch', 0);
- $cgi->delete('bigfile');
- } else {
- $filename = validateFilename();
- # need to validate content type before data as
- # we now check the content type for image/bmp in validateData()
- validateContentType() unless $cgi->param('ispatch');
- $data = validateData();
- $contenttype = $cgi->param('contenttype');
-
- # These are inserted using placeholders so no need to panic
- trick_taint($filename);
- trick_taint($contenttype);
- $isurl = 0;
- }
+ my ($timestamp) = Bugzilla->dbh->selectrow_array("SELECT NOW()");
+ my $attachid =
+ Bugzilla::Attachment->insert_attachment_for_bug(THROW_ERROR,
+ $bugid, $user,
+ $timestamp, \$vars);
+ my $isprivate = $cgi->param('isprivate') ? 1 : 0;
my @obsolete_ids = ();
@obsolete_ids = validateObsolete() if $cgi->param('obsolete');
- # The order of these function calls is important, as both Flag::validate
- # and FlagType::validate assume User::match_field has ensured that the
- # values in the requestee fields are legitimate user email addresses.
- my $match_status = Bugzilla::User::match_field($cgi, {
- '^requestee(_type)?-(\d+)$' => { 'type' => 'multi' },
- }, MATCH_SKIP_CONFIRM);
-
- $vars->{'match_field'} = 'requestee';
- if ($match_status == USER_MATCH_FAILED) {
- $vars->{'message'} = 'user_match_failed';
- }
- elsif ($match_status == USER_MATCH_MULTIPLE) {
- $vars->{'message'} = 'user_match_multiple';
- }
-
- # FlagType::validate() and Flag::validate() should not detect
- # any reference to existing flags when creating a new attachment.
- # Setting the third param to -1 will force this function to check this point.
- Bugzilla::Flag::validate($cgi, $bugid, -1);
- Bugzilla::FlagType::validate($cgi, $bugid, -1);
-
- # Escape characters in strings that will be used in SQL statements.
- my $description = $cgi->param('description');
- trick_taint($description);
- my $isprivate = $cgi->param('isprivate') ? 1 : 0;
-
- # Figure out when the changes were made.
- my ($timestamp) = Bugzilla->dbh->selectrow_array("SELECT NOW()");
-
- # Insert the attachment into the database.
- my $sth = $dbh->do(
- "INSERT INTO attachments
- (bug_id, creation_ts, filename, description,
- mimetype, ispatch, isurl, isprivate, submitter_id)
- VALUES (?,?,?,?,?,?,?,?,?)", undef, ($bugid, $timestamp, $filename,
- $description, $contenttype, $cgi->param('ispatch'),
- $isurl, $isprivate, $userid));
- # Retrieve the ID of the newly created attachment record.
- my $attachid = $dbh->bz_last_key('attachments', 'attach_id');
-
- # We only use $data here in this INSERT with a placeholder,
- # so it's safe.
- $sth = $dbh->prepare("INSERT INTO attach_data
- (id, thedata) VALUES ($attachid, ?)");
- trick_taint($data);
- $sth->bind_param(1, $data, $dbh->BLOB_TYPE);
- $sth->execute();
-
-
- # If the file is to be stored locally, stream the file from the webserver
- # to the local file without reading it into a local variable.
- if ($cgi->param('bigfile'))
- {
- my $fh = $cgi->upload('data');
- my $hash = ($attachid % 100) + 100;
- $hash =~ s/.*(\d\d)$/group.$1/;
- mkdir "$attachdir/$hash", 0770;
- chmod 0770, "$attachdir/$hash";
- open(AH, ">$attachdir/$hash/attachment.$attachid");
- binmode AH;
- my $sizecount = 0;
- my $limit = (Param("maxlocalattachment") * 1048576);
- while (<$fh>) {
- print AH $_;
- $sizecount += length($_);
- if ($sizecount > $limit) {
- close AH;
- close $fh;
- unlink "$attachdir/$hash/attachment.$attachid";
- ThrowUserError("local_file_too_large");
- }
- }
- close AH;
- close $fh;
- }
-
-
# Insert a comment about the new attachment into the database.
my $comment = "Created an attachment (id=$attachid)\n" .
$cgi->param('description') . "\n";
$comment .= ("\n" . $cgi->param('comment')) if defined $cgi->param('comment');
- AppendComment($bugid, $userid, $comment, $isprivate, $timestamp);
+ AppendComment($bugid, $user->id, $comment, $isprivate, $timestamp);
# Make existing attachments obsolete.
my $fieldid = get_field_id('attachments.isobsolete');
@@ -1052,7 +819,7 @@ sub insert
$dbh->do("INSERT INTO bugs_activity (bug_id, attach_id, who, bug_when,
fieldid, removed, added)
VALUES (?,?,?,?,?,?,?)", undef,
- $bugid, $obsolete_id, $userid, $timestamp, $fieldid, 0, 1);
+ $bugid, $obsolete_id, $user->id, $timestamp, $fieldid, 0, 1);
}
# Assign the bug to the user, if they are allowed to take it
@@ -1071,7 +838,7 @@ sub insert
"ON profiles.userid = bugs.assigned_to " .
"WHERE bugs.bug_id = ?", undef, $bugid);
- my @newvalues = ($userid, "ASSIGNED", "", 1, Bugzilla->user->login);
+ my @newvalues = ($user->id, "ASSIGNED", "", 1, $user->login);
# Make sure the person we are taking the bug from gets mail.
$owner = $oldvalues[4];
@@ -1092,7 +859,7 @@ sub insert
for (my $i = 0; $i < 4; $i++) {
if ($oldvalues[$i] ne $newvalues[$i]) {
LogActivityEntry($bugid, $fields[$i], $oldvalues[$i],
- $newvalues[$i], $userid, $timestamp);
+ $newvalues[$i], $user->id, $timestamp);
}
}
}
@@ -1101,11 +868,11 @@ sub insert
Bugzilla::Flag::process($bugid, $attachid, $timestamp, $cgi);
# Define the variables and functions that will be passed to the UI template.
- $vars->{'mailrecipients'} = { 'changer' => Bugzilla->user->login,
+ $vars->{'mailrecipients'} = { 'changer' => $user->login,
'owner' => $owner };
$vars->{'bugid'} = $bugid;
$vars->{'attachid'} = $attachid;
- $vars->{'description'} = $description;
+ $vars->{'description'} = $cgi->param('description');
$vars->{'contenttypemethod'} = $cgi->param('contenttypemethod');
$vars->{'contenttype'} = $cgi->param('contenttype');
@@ -1180,9 +947,9 @@ sub update
my ($attach_id, $bugid) = validateID();
validateCanEdit($attach_id);
validateCanChangeAttachment($attach_id);
- validateDescription();
- validateIsPatch();
- validateContentType() unless $cgi->param('ispatch');
+ Bugzilla::Attachment->validate_description(THROW_ERROR);
+ Bugzilla::Attachment->validate_is_patch(THROW_ERROR);
+ Bugzilla::Attachment->validate_content_type(THROW_ERROR) unless $cgi->param('ispatch');
validateIsObsolete();
validatePrivate();
my $dbh = Bugzilla->dbh;
diff --git a/js/attachment.js b/js/attachment.js
new file mode 100644
index 000000000..6baaad50e
--- /dev/null
+++ b/js/attachment.js
@@ -0,0 +1,106 @@
+/* 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 Netscape Communications
+ * Corporation. Portions created by Netscape are
+ * Copyright (C) 1998 Netscape Communications Corporation. All
+ * Rights Reserved.
+ *
+ * Contributor(s): Myk Melez <myk@mozilla.org>
+ * Joel Peshkin <bugreport@peshkin.net>
+ * Erik Stambaugh <erik@dasbistro.com>
+ * Marc Schumann <wurblzap@gmail.com>
+ */
+
+function updateCommentPrivacy(checkbox) {
+ var text_elem = document.getElementById('comment');
+ if (checkbox.checked) {
+ text_elem.className='bz_private';
+ } else {
+ text_elem.className='';
+ }
+}
+
+function setContentTypeDisabledState(form)
+{
+ var isdisabled = false;
+ if (form.ispatch.checked)
+ isdisabled = true;
+
+ for (var i=0 ; i<form.contenttypemethod.length ; i++)
+ form.contenttypemethod[i].disabled = isdisabled;
+
+ form.contenttypeselection.disabled = isdisabled;
+ form.contenttypeentry.disabled = isdisabled;
+}
+
+function URLFieldHandler() {
+ var field_attachurl = document.getElementById("attachurl");
+ var greyfields = new Array("data", "ispatch", "autodetect",
+ "list", "manual", "bigfile",
+ "contenttypeselection",
+ "contenttypeentry");
+ var i;
+ if (field_attachurl.value.match(/^\s*$/)) {
+ for (i = 0; i < greyfields.length; i++) {
+ thisfield = document.getElementById(greyfields[i]);
+ if (thisfield) {
+ thisfield.removeAttribute("disabled");
+ }
+ }
+ } else {
+ for (i = 0; i < greyfields.length; i++) {
+ thisfield = document.getElementById(greyfields[i]);
+ if (thisfield) {
+ thisfield.setAttribute("disabled", "disabled");
+ }
+ }
+ }
+}
+
+function DataFieldHandler() {
+ var field_data = document.getElementById("data");
+ var greyfields = new Array("attachurl");
+ if (field_data.value.match(/^\s*$/)) {
+ var i;
+ for (i = 0; i < greyfields.length; i++) {
+ thisfield = document.getElementById(greyfields[i]);
+ if (thisfield) {
+ thisfield.removeAttribute("disabled");
+ }
+ }
+ } else {
+ for (i = 0; i < greyfields.length; i++) {
+ thisfield = document.getElementById(greyfields[i]);
+ if (thisfield) {
+ thisfield.setAttribute("disabled", "disabled");
+ }
+ }
+ }
+}
+
+function clearAttachmentFields() {
+ var element;
+
+ document.getElementById('data').value = '';
+ DataFieldHandler();
+ if (element = document.getElementById('bigfile'))
+ element.checked = '';
+ if (element = document.getElementById('attachurl')) {
+ element.value = '';
+ URLFieldHandler();
+ }
+ document.getElementById('description').value = '';
+ document.getElementById('ispatch').checked = '';
+ if (element = document.getElementById('isprivate'))
+ element.checked = '';
+}
diff --git a/post_bug.cgi b/post_bug.cgi
index 5f3f91c3b..99b6e533b 100755
--- a/post_bug.cgi
+++ b/post_bug.cgi
@@ -22,12 +22,14 @@
# Dan Mosedale <dmose@mozilla.org>
# Joe Robins <jmrobins@tgix.com>
# Gervase Markham <gerv@gerv.net>
+# Marc Schumann <wurblzap@gmail.com>
use strict;
use lib qw(.);
require "globals.pl";
use Bugzilla;
+use Bugzilla::Attachment;
use Bugzilla::Constants;
use Bugzilla::Util;
use Bugzilla::Bug;
@@ -546,6 +548,21 @@ $dbh->do("UPDATE bugs SET creation_ts = ? WHERE bug_id = ?",
$dbh->bz_unlock_tables();
+# Add an attachment if requested.
+if (defined($cgi->upload('data')) || $cgi->param('attachurl')) {
+ $cgi->param('isprivate', $cgi->param('commentprivacy'));
+ Bugzilla::Attachment->insert_attachment_for_bug(!THROW_ERROR,
+ $id, $user, $timestamp,
+ \$vars)
+ || ($vars->{'message'} = 'attachment_creation_failed');
+
+ # Determine if Patch Viewer is installed, for Diff link
+ eval {
+ require PatchReader;
+ $vars->{'patchviewerinstalled'} = 1;
+ };
+}
+
# Email everyone the details of the new bug
$vars->{'mailrecipients'} = {'changer' => $user->login};
diff --git a/skins/standard/create_attachment.css b/skins/standard/create_attachment.css
new file mode 100644
index 000000000..dcb19836d
--- /dev/null
+++ b/skins/standard/create_attachment.css
@@ -0,0 +1,36 @@
+ /* 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.
+ *
+ * Contributor(s): Myk Melez <myk@mozilla.org>
+ * Joel Peshkin <bugreport@peshkin.net>
+ * Erik Stambaugh <erik@dasbistro.com>
+ * Marc Schumann <wurblzap@gmail.com>
+ */
+
+table.attachment_entry th {
+ text-align: right;
+ vertical-align: baseline;
+ white-space: nowrap;
+}
+
+table.attachment_entry td {
+ text-align: left;
+ vertical-align: baseline;
+ padding-bottom: 5px;
+}
+
+table#flags th,
+table#flags td {
+ text-align: left;
+ vertical-align: baseline;
+ font-size: small;
+}
diff --git a/template/en/default/attachment/create.html.tmpl b/template/en/default/attachment/create.html.tmpl
index 6da5b0c19..180fc5c49 100644
--- a/template/en/default/attachment/create.html.tmpl
+++ b/template/en/default/attachment/create.html.tmpl
@@ -19,6 +19,7 @@
# Contributor(s): Myk Melez <myk@mozilla.org>
# Joel Peshkin <bugreport@peshkin.net>
# Erik Stambaugh <erik@dasbistro.com>
+ # Marc Schumann <wurblzap@gmail.com>
#%]
[% PROCESS global/variables.none.tmpl %]
@@ -33,180 +34,19 @@
title = title
h1 = h1
h2 = h2
- style = "
- table.attachment_entry th {
- text-align: right;
- vertical-align: baseline;
- white-space: nowrap;
- }
-
- table.attachment_entry td {
- text-align: left;
- vertical-align: baseline;
- padding-bottom: 5px;
- }
-
- table#flags th, table#flags td {
- text-align: left;
- vertical-align: baseline;
- font-size: small;
- }
- "
- onload="setContentTypeDisabledState();"
+ onload="setContentTypeDisabledState(document.entryform);"
+ style_urls = [ 'skins/standard/create_attachment.css' ]
+ javascript_urls = [ "js/attachment.js" ]
%]
-[% IF Param("allow_attach_url") %]
- <script type="text/javascript">
-
- function URLFieldHandler() {
- var field_attachurl = document.getElementById("attachurl");
- var greyfields = new Array("data", "ispatch", "autodetect",
- "list", "manual", "bigfile",
- "contenttypeselection",
- "contenttypeentry");
- var i;
- if (field_attachurl.value.match(/^\s*$/)) {
- for (i = 0; i < greyfields.length; i++) {
- thisfield = document.getElementById(greyfields[i]);
- if (thisfield) {
- thisfield.removeAttribute("disabled");
- }
- }
- } else {
- for (i = 0; i < greyfields.length; i++) {
- thisfield = document.getElementById(greyfields[i]);
- if (thisfield) {
- thisfield.setAttribute("disabled", "disabled");
- }
- }
- }
- }
-
- function DataFieldHandler() {
- var field_data = document.getElementById("data");
- var greyfields = new Array("attachurl");
- if (field_data.value.match(/^\s*$/)) {
- var i;
- for (i = 0; i < greyfields.length; i++) {
- thisfield = document.getElementById(greyfields[i]);
- if (thisfield) {
- thisfield.removeAttribute("disabled");
- }
- }
- } else {
- for (i = 0; i < greyfields.length; i++) {
- thisfield = document.getElementById(greyfields[i]);
- if (thisfield) {
- thisfield.setAttribute("disabled", "disabled");
- }
- }
- }
- }
-
- </script>
-[% END %]
-<script type="text/javascript">
- function updateCommentPrivacy(checkbox) {
- var text_elem = document.getElementById('comment');
- if (checkbox.checked) {
- text_elem.className='bz_private';
- } else {
- text_elem.className='';
- }
- }
-
-</script>
-
-
<form name="entryform" method="post" action="attachment.cgi" enctype="multipart/form-data">
<input type="hidden" name="bugid" value="[% bugid %]">
<input type="hidden" name="action" value="insert">
<table class="attachment_entry">
- <tr>
- <th><label for="data">File:</label></th>
- <td>
- <em>Enter the path to the file on your computer.</em><br>
- <input type="file" id="data" name="data" size="50"
- [% IF Param("allow_attach_url") %]
- onchange="DataFieldHandler()"
- [% END %]
- >
- </td>
- </tr>
- [% IF Param("maxlocalattachment") %]
- <tr>
- <th>BigFile:</th>
- <td>
- <input type="checkbox" id="bigfile"
- name="bigfile" value="bigfile">
- <label for="bigfile">
- Big File - Stored locally and may be purged
- </label>
- </td>
- </tr>
- [% END %]
- [% IF Param("allow_attach_url") %]
- <tr>
- <th><label for="attachurl">AttachURL:</label></th>
- <td>
- <em>URL to be attached instead.</em><br>
- <input type="text" id="attachurl" name="attachurl" size="60"
- maxlength="2000"
- onkeyup="URLFieldHandler()" onblur="URLFieldHandler()">
- </td>
- </tr>
- [% END %]
- <tr>
- <th><label for="description">Description:</label></th>
- <td>
- <em>Describe the attachment briefly.</em><br>
- <input type="text" id="description" name="description" size="60" maxlength="200">
- </td>
- </tr>
- <tr>
- <th></th>
- <td>
- </td>
- </tr>
- <tr>
- <th>Content Type:</th>
- <td>
- <em>If the attachment is a patch, check the box below.</em><br>
- <input type="checkbox" id="ispatch" name="ispatch" value="1"
- onchange="setContentTypeDisabledState();">
- <label for="ispatch">patch</label><br><br>
+ [% PROCESS attachment/createformcontents.html.tmpl %]
- <em>Otherwise, choose a method for determining the content type.</em><br>
- <input type="radio" id="autodetect"
- name="contenttypemethod" value="autodetect" checked="checked">
- <label for="autodetect">auto-detect</label><br>
- <input type="radio" id="list"
- name="contenttypemethod" value="list">
- <label for="list">select from list:</label>
- <select name="contenttypeselection" id="contenttypeselection"
- onchange="this.form.contenttypemethod[1].checked = true;">
- [% PROCESS "attachment/content-types.html.tmpl" %]
- </select><br>
- <input type="radio" id="manual"
- name="contenttypemethod" value="manual">
- <label for="manual">enter manually:</label>
- <input type="text" name="contenttypeentry" id="contenttypeentry"
- size="30" maxlength="200"
- onchange="if (this.value) this.form.contenttypemethod[2].checked = true;">
- </td>
- </tr>
- [% IF (Param("insidergroup") && UserInGroup(Param("insidergroup"))) %]
- <tr>
- <th>Privacy:</th>
- <td>
- <em>If the attachment is private, check the box below.</em><br>
- <input type="checkbox" name="isprivate" id="isprivate"
- value="1" onClick="updateCommentPrivacy(this)">
- <label for="isprivate">Private</label>
- </td>
- </tr>
- [% END %]
+ [%# Additional fields for attachments on existing bugs: %]
<tr>
<th>Obsoletes:</th>
<td>
@@ -238,14 +78,6 @@
</tr>
[% END %]
<tr>
- <td> </td>
- <td>
- [% IF flag_types.size > 0 %]
- [% PROCESS "flag/list.html.tmpl" bug_id=bugid attach_id=attachid %]<br>
- [% END %]
- </td>
- </tr>
- <tr>
<th><label for="comment">Comment:</label></th>
<td>
<em>(optional) Add a comment about this attachment to the [% terms.bug %].</em><br>
@@ -259,6 +91,17 @@
%]
</td>
</tr>
+ [% IF (Param("insidergroup") && UserInGroup(Param("insidergroup"))) %]
+ <tr>
+ <th>Privacy:</th>
+ <td>
+ <em>If the attachment is private, check the box below.</em><br>
+ <input type="checkbox" name="isprivate" id="isprivate"
+ value="1" onClick="updateCommentPrivacy(this)">
+ <label for="isprivate">Private</label>
+ </td>
+ </tr>
+ [% END %]
<tr>
<th>&nbsp;</th>
<td><input type="submit" value="Submit"></td>
@@ -267,23 +110,4 @@
</form>
-<script type="text/javascript">
- <!--
- function setContentTypeDisabledState()
- {
- var entryform = document.entryform;
-
- var isdisabled = false;
- if (entryform.ispatch.checked)
- isdisabled = true;
-
- for (var i=0 ; i<entryform.contenttypemethod.length ; i++)
- entryform.contenttypemethod[i].disabled = isdisabled;
-
- entryform.contenttypeselection.disabled = isdisabled;
- entryform.contenttypeentry.disabled = isdisabled;
- }
- //-->
-</script>
-
[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/attachment/createformcontents.html.tmpl b/template/en/default/attachment/createformcontents.html.tmpl
new file mode 100644
index 000000000..2056490c9
--- /dev/null
+++ b/template/en/default/attachment/createformcontents.html.tmpl
@@ -0,0 +1,100 @@
+[%# 1.0@bugzilla.org %]
+[%# 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 Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Myk Melez <myk@mozilla.org>
+ # Joel Peshkin <bugreport@peshkin.net>
+ # Erik Stambaugh <erik@dasbistro.com>
+ # Marc Schumann <wurblzap@gmail.com>
+ #%]
+
+<tr>
+ <th><label for="data">File</label>:</th>
+ <td>
+ <em>Enter the path to the file on your computer.</em><br>
+ <input type="file" id="data" name="data" size="50"
+ [% IF Param("allow_attach_url") %]
+ onchange="DataFieldHandler()"
+ [% END %]
+ >
+ </td>
+</tr>
+[% IF Param("maxlocalattachment") %]
+<tr>
+ <th>BigFile:</th>
+ <td>
+ <input type="checkbox" id="bigfile"
+ name="bigfile" value="bigfile">
+ <label for="bigfile">
+ Big File - Stored locally and may be purged
+ </label>
+ </td>
+</tr>
+[% END %]
+[% IF Param("allow_attach_url") %]
+<tr>
+ <th><label for="attachurl">AttachURL</label>:</th>
+ <td>
+ <em>URL to be attached instead.</em><br>
+ <input type="text" id="attachurl" name="attachurl" size="60"
+ maxlength="2000"
+ onkeyup="URLFieldHandler()" onblur="URLFieldHandler()">
+ </td>
+</tr>
+[% END %]
+<tr>
+ <th><label for="description">Description</label>:</th>
+ <td>
+ <em>Describe the attachment briefly.</em><br>
+ <input type="text" id="description" name="description" size="60" maxlength="200">
+ </td>
+</tr>
+<tr>
+ <th>Content Type:</th>
+ <td>
+ <em>If the attachment is a patch, check the box below.</em><br>
+ <input type="checkbox" id="ispatch" name="ispatch" value="1"
+ onchange="setContentTypeDisabledState(this.form);">
+ <label for="ispatch">patch</label><br><br>
+
+ <em>Otherwise, choose a method for determining the content type.</em><br>
+ <input type="radio" id="autodetect"
+ name="contenttypemethod" value="autodetect" checked="checked">
+ <label for="autodetect">auto-detect</label><br>
+ <input type="radio" id="list"
+ name="contenttypemethod" value="list">
+ <label for="list">select from list</label>:
+ <select name="contenttypeselection" id="contenttypeselection"
+ onchange="this.form.contenttypemethod[1].checked = true;">
+ [% PROCESS "attachment/content-types.html.tmpl" %]
+ </select><br>
+ <input type="radio" id="manual"
+ name="contenttypemethod" value="manual">
+ <label for="manual">enter manually</label>:
+ <input type="text" name="contenttypeentry" id="contenttypeentry"
+ size="30" maxlength="200"
+ onchange="if (this.value) this.form.contenttypemethod[2].checked = true;">
+ </td>
+</tr>
+<tr>
+ <td> </td>
+ <td>
+ [% IF flag_types && flag_types.size > 0 %]
+ [% PROCESS "flag/list.html.tmpl" bug_id=bugid attach_id=attachid %]<br>
+ [% END %]
+ </td>
+</tr>
diff --git a/template/en/default/bug/create/create.html.tmpl b/template/en/default/bug/create/create.html.tmpl
index a0b186072..38656b813 100644
--- a/template/en/default/bug/create/create.html.tmpl
+++ b/template/en/default/bug/create/create.html.tmpl
@@ -19,12 +19,15 @@
# Contributor(s): Gervase Markham <gerv@gerv.net>
# Ville Skyttä <ville.skytta@iki.fi>
# Shane H. W. Travis <travis@sedsystems.ca>
+ # Marc Schumann <wurblzap@gmail.com>
#%]
[% PROCESS "global/field-descs.none.tmpl" %]
[% PROCESS global/header.html.tmpl
title = "Enter $terms.Bug: $product.name"
+ style_urls = [ 'skins/standard/create_attachment.css' ]
+ javascript_urls = [ "js/attachment.js" ]
onload="set_assign_to();"
%]
@@ -86,10 +89,24 @@ function set_assign_to() {
[% END %]
}
}
+
+function handleWantsAttachment(wants_attachment) {
+ if (wants_attachment) {
+ document.getElementById('attachment_false').style.display = 'none';
+ document.getElementById('attachment_true').style.display = 'block';
+ }
+ else {
+ document.getElementById('attachment_false').style.display = 'block';
+ document.getElementById('attachment_true').style.display = 'none';
+ clearAttachmentFields();
+ }
+}
+
-->
</script>
-<form name="Create" id="Create" method="post" action="post_bug.cgi">
+<form name="Create" id="Create" method="post" action="post_bug.cgi"
+ enctype="multipart/form-data">
<input type="hidden" name="product" value="[% product.name FILTER html %]">
<input type="hidden" name="token" value="[% token FILTER html %]">
@@ -316,6 +333,7 @@ function set_assign_to() {
[%- END %]
[% INCLUDE global/textarea.html.tmpl
name = 'comment'
+ id = 'comment'
minrows = 10
maxrows = 25
cols = constants.COMMENT_COLS
@@ -341,6 +359,38 @@ function set_assign_to() {
<input type="hidden" name="commentprivacy" value="0">
[% END %]
+ <tr>
+ <th align="right" valign="top">Attachment:</th>
+ <td colspan="3">
+ <script type="text/javascript">
+ <!--
+ document.write( '<div id="attachment_false">'
+ + '<input type="button" value="Add an attachment" '
+ + 'onClick="handleWantsAttachment(true)"> '
+ + '<em style="display: none">This button has no '
+ + 'functionality for you because your browser does '
+ + 'not support CSS or does not use it.</em>'
+ + '</div>'
+ + '<div id="attachment_true" style="display: none">'
+ + '<input type="button" '
+ + 'value="Don\'t add an attachment " '
+ + 'onClick="handleWantsAttachment(false)">');
+ //-->
+ </script>
+ <fieldset>
+ <legend>Add an attachment</legend>
+ <table class="attachment_entry">
+ [% PROCESS attachment/createformcontents.html.tmpl %]
+ </table>
+ </fieldset>
+ <script type="text/javascript">
+ <!--
+ document.write('</div>');
+ //-->
+ </script>
+ </td>
+ </tr>
+
[% IF UserInGroup('editbugs') %]
[% IF use_keywords %]
<tr>
diff --git a/template/en/default/global/messages.html.tmpl b/template/en/default/global/messages.html.tmpl
index 6730e8835..bb8a08fdd 100644
--- a/template/en/default/global/messages.html.tmpl
+++ b/template/en/default/global/messages.html.tmpl
@@ -100,6 +100,12 @@
The user account [% otheruser.login FILTER html %] has been deleted
successfully.
+ [% ELSIF message_tag == "attachment_creation_failed" %]
+ The [% terms.bug %] was created successfully, but attachment creation
+ failed.
+ Please add your attachment by clicking the "Create a New Attachment" link
+ below.
+
[% ELSIF message_tag == "buglist_adding_field" %]
[% title = "Adding field to search page..." %]
[% link = "Click here if the page does not redisplay automatically." %]