WIP: attachment->object

classes
Vitaliy Filippov 2016-12-22 22:01:45 +03:00
parent af9cf8806e
commit 97257aa8a1
10 changed files with 183 additions and 279 deletions

View File

@ -1,24 +1,10 @@
# The contents of this file are subject to the Mozilla Public # Bug attachment class (based on GenericObject)
# License Version 1.1 (the "License"); you may not use this file # License: MPL 1.1
# except in compliance with the License. You may obtain a copy of # Contributor(s): Vitaliy Filippov <vitalif@mail.ru>
# the License at http://www.mozilla.org/MPL/ # Terry Weissman <terry@mozilla.org>
# # Myk Melez <myk@mozilla.org>
# Software distributed under the License is distributed on an "AS # Marc Schumann <wurblzap@gmail.com>
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or # Frédéric Buclin <LpSolit@gmail.com>
# 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): Terry Weissman <terry@mozilla.org>
# Myk Melez <myk@mozilla.org>
# Marc Schumann <wurblzap@gmail.com>
# Frédéric Buclin <LpSolit@gmail.com>
use strict; use strict;
@ -61,63 +47,24 @@ use Bugzilla::Hook;
use LWP::MediaTypes; use LWP::MediaTypes;
use MIME::Base64; use MIME::Base64;
use base qw(Bugzilla::Object); use base qw(Bugzilla::GenericObject);
###############################
#### Initialization ####
###############################
use constant DB_TABLE => 'attachments'; use constant DB_TABLE => 'attachments';
use constant ID_FIELD => 'attach_id'; use constant ID_FIELD => 'attach_id';
use constant LIST_ORDER => ID_FIELD; use constant LIST_ORDER => ID_FIELD;
use constant NAME_FIELD => 'attach_id';
use constant CLASS_NAME => 'attachment';
sub DB_COLUMNS use constant OVERRIDE_SETTERS => {
{ bug => \&_set_bug,
my $dbh = Bugzilla->dbh; bug_id => \&_set_bug,
return qw( description => \&_set_description,
attach_id
bug_id
description
filename
isobsolete
ispatch
isprivate
mimetype
modification_time
submitter_id),
$dbh->sql_date_format('attachments.creation_ts') . ' AS creation_ts',
'creation_ts AS creation_ts_orig';
}
use constant REQUIRED_CREATE_FIELDS => qw(
bug
data
description
filename
mimetype
);
use constant UPDATE_COLUMNS => qw(
description
filename
isobsolete
ispatch
isprivate
mimetype
);
use constant VALIDATORS => {
bug => \&_check_bug,
description => \&_check_description,
ispatch => \&Bugzilla::Object::check_boolean, ispatch => \&Bugzilla::Object::check_boolean,
isprivate => \&_check_is_private, isprivate => \&_set_isprivate,
mimetype => \&_check_content_type, mimetype => \&_set_mimetype,
store_in_file => \&_check_store_in_file, store_in_file => \&_check_store_in_file,
}; filename => \&_check_filename,
isobsolete => \&_set_isobsolete,
use constant UPDATE_VALIDATORS => {
filename => \&_check_filename,
isobsolete => \&Bugzilla::Object::check_boolean,
}; };
=pod =pod
@ -206,16 +153,6 @@ already set, grouped by flag type.
=cut =cut
sub bug_id { $_[0]->{bug_id} }
sub description { $_[0]->{description} }
sub contenttype { $_[0]->{mimetype} }
sub attached { $_[0]->{creation_ts} }
sub modification_time { $_[0]->{modification_time} }
sub filename { $_[0]->{filename} }
sub ispatch { $_[0]->{ispatch} }
sub isobsolete { $_[0]->{isobsolete} }
sub isprivate { $_[0]->{isprivate} }
sub bug sub bug
{ {
my $self = shift; my $self = shift;
@ -416,23 +353,23 @@ sub flag_types
return $self->{flag_types}; return $self->{flag_types};
} }
sub set_flags
{
my ($self, $flags, $new_flags, $comment) = @_;
Bugzilla::Flag->set_flag($self, $_) foreach (@$flags, @$new_flags);
$self->{flag_notify_comment} = $comment;
}
############################### ###############################
#### Validators ###### #### Validators ######
############################### ###############################
sub set_content_type { $_[0]->set('mimetype', $_[1]); } sub _set_isobsolete
sub set_description { $_[0]->set('description', $_[1]); }
sub set_filename { $_[0]->set('filename', $_[1]); }
sub set_is_patch { $_[0]->set('ispatch', $_[1]); }
sub set_is_private { $_[0]->set('isprivate', $_[1]); }
sub set_is_obsolete
{ {
my ($self, $obsolete) = @_; my ($self, $obsolete) = @_;
my $old = $self->isobsolete; my $old = $self->{_old_self} ? $self->{_old_self}->{isobsolete} : 0;
$self->set('isobsolete', $obsolete); my $new = Bugzilla::Object::check_boolean($self, $obsolete);
my $new = $self->isobsolete;
# If the attachment is being marked as obsolete, cancel pending requests. # If the attachment is being marked as obsolete, cancel pending requests.
if ($new && $old != $new) if ($new && $old != $new)
@ -446,24 +383,21 @@ sub set_is_obsolete
@{$flagtype->{flags}} = grep { !$flag_ids{$_->id} } @{$flagtype->{flags}}; @{$flagtype->{flags}} = grep { !$flag_ids{$_->id} } @{$flagtype->{flags}};
} }
} }
return $new;
} }
sub set_flags sub _set_bug
{ {
my ($self, $flags, $new_flags, $comment) = @_; my ($self, $bug) = @_;
Bugzilla::Flag->set_flag($self, $_) foreach (@$flags, @$new_flags); return undef if $self->id;
$self->{flag_notify_comment} = $comment;
}
sub _check_bug
{
my ($invocant, $bug) = @_;
my $user = Bugzilla->user; my $user = Bugzilla->user;
$bug = ref $invocant ? $invocant->bug : $bug;
$user->can_edit_bug($bug->id) || ThrowUserError("illegal_attachment_edit_bug", { bug_id => $bug->id }); $user->can_edit_bug($bug->id) || ThrowUserError("illegal_attachment_edit_bug", { bug_id => $bug->id });
return $bug; $self->{bug} = $bug;
$self->{bug_id} = $bug->id;
return undef;
} }
sub _legal_content_type sub _legal_content_type
@ -473,11 +407,11 @@ sub _legal_content_type
return $content_type =~ /^($legal_types)\/.+$/; return $content_type =~ /^($legal_types)\/.+$/;
} }
sub _check_content_type sub _set_mimetype
{ {
my ($invocant, $content_type) = @_; my ($self, $content_type) = @_;
$content_type = 'text/plain' if ref $invocant && $invocant->ispatch; $content_type = 'text/plain' if $self->ispatch;
$content_type = trim($content_type); $content_type = trim($content_type);
if (!$content_type || !_legal_content_type($content_type)) if (!$content_type || !_legal_content_type($content_type))
{ {
@ -488,9 +422,9 @@ sub _check_content_type
return $content_type; return $content_type;
} }
sub _check_data sub _set_data
{ {
my ($invocant, $params) = @_; my ($self, $params) = @_;
my $data; my $data;
if ($params->{base64_content}) if ($params->{base64_content})
@ -521,47 +455,20 @@ sub _check_data
return $data if ref $data; return $data if ref $data;
$data || ThrowUserError('zero_length_file'); $data || ThrowUserError('zero_length_file');
# Make sure the attachment does not exceed the maximum permitted size.
my $len = length($data);
my $max_size = $params->{store_in_file} || Bugzilla->params->{force_attach_bigfile}
? Bugzilla->params->{maxlocalattachment} * 1048576
: Bugzilla->params->{maxattachmentsize} * 1024;
if ($len > $max_size)
{
my $vars = { filesize => sprintf("%.0f", $len/1024) };
if ($params->{ispatch})
{
ThrowUserError('patch_too_large', $vars);
}
elsif ($params->{store_in_file})
{
ThrowUserError('local_file_too_large');
}
else
{
ThrowUserError('file_too_large', $vars);
}
}
return $data; return $data;
} }
sub _check_description sub _set_description
{ {
my ($invocant, $description) = @_; my ($self, $description) = @_;
$description = trim($description); $description = trim($description);
$description || ThrowUserError('missing_attachment_description'); $description || ThrowUserError('missing_attachment_description');
return $description; return $description;
} }
sub _check_filename sub _set_filename
{ {
my ($invocant, $filename, undef, $params) = @_; my ($self, $filename) = @_;
if ($params && $params->{base64_content})
{
$filename = $params->{description};
}
$filename = trim($filename); $filename = trim($filename);
$filename || ThrowUserError('file_not_specified'); $filename || ThrowUserError('file_not_specified');
@ -581,12 +488,12 @@ sub _check_filename
return $filename; return $filename;
} }
sub _check_is_private sub _set_isprivate
{ {
my ($invocant, $is_private) = @_; my ($self, $is_private) = @_;
$is_private = $is_private ? 1 : 0; $is_private = $is_private ? 1 : 0;
if ((ref $invocant ? ($invocant->isprivate != $is_private) : $is_private) && !Bugzilla->user->is_insider) my $old = $self->{_old_self} ? $self->{_old_self}->isprivate : 0;
if ($old != $is_private && !Bugzilla->user->is_insider)
{ {
ThrowUserError('user_not_insider'); ThrowUserError('user_not_insider');
} }
@ -776,138 +683,120 @@ Returns: The new attachment object.
=cut =cut
sub create sub _before_update
{ {
my $class = shift; my $self = shift;
my $dbh = Bugzilla->dbh; $self->SUPER::_before_update($changes);
$self->{modification_time} = $self->{delta_ts};
$class->check_required_create_fields(@_); if (!$self->id)
my $params = $class->run_create_validators(@_);
# Extract everything which is not a valid column name.
my $bug = delete $params->{bug};
$params->{bug_id} = $bug->id;
my $fh = delete $params->{data};
my $store_in_file = delete $params->{store_in_file};
if (Bugzilla->params->{force_attach_bigfile})
{ {
# Force uploading into files instead of DB when force_attach_bigfile = On $self->{submitter_id} = Bugzilla->user->id || ThrowCodeError('invalid_user');
$store_in_file = 1;
} }
if (!$self->id && $self->{data} && !ref $self->{data})
my $attachment = $class->insert_create_data($params);
my $attachid = $attachment->id;
# If the file is to be stored locally, stream the file from the web server
# to the local file without reading it into a local variable.
if ($store_in_file)
{ {
my $attachdir = bz_locations()->{attachdir}; $self->set('filename', $self->{description});
my $hash = ($attachid % 100) + 100; # Make sure the attachment does not exceed the maximum permitted size.
$hash =~ s/.*(\d\d)$/group.$1/; my $len = length($self->{data});
mkdir "$attachdir/$hash", 0770; my $max_size = $params->{store_in_file} || Bugzilla->params->{force_attach_bigfile}
chmod 0770, "$attachdir/$hash"; ? Bugzilla->params->{maxlocalattachment} * 1048576
open(AH, '>', "$attachdir/$hash/attachment.$attachid") or die "Could not write into $attachdir/$hash/attachment.$attachid: $!"; : Bugzilla->params->{maxattachmentsize} * 1024;
binmode AH; if ($len > $max_size)
if (ref $fh)
{ {
my $limit = Bugzilla->params->{maxlocalattachment} * 1048576; my $vars = { filesize => sprintf("%.0f", $len/1024) };
my $sizecount = 0; if ($self->{ispatch})
while (<$fh>)
{ {
print AH $_; ThrowUserError('patch_too_large', $vars);
$sizecount += length($_);
if ($sizecount > $limit)
{
close AH;
close $fh;
unlink "$attachdir/$hash/attachment.$attachid";
ThrowUserError("local_file_too_large");
}
} }
close $fh; elsif ($self->{store_in_file})
{
ThrowUserError('local_file_too_large');
}
else
{
ThrowUserError('file_too_large', $vars);
}
}
}
}
sub _after_update
{
my $self = shift;
my ($changes) = @_;
if (!$self->id && $self->{data})
{
my $fh = delete $self->{data};
my $store_in_file = delete $self->{store_in_file};
if (Bugzilla->params->{force_attach_bigfile})
{
# Force uploading into files instead of DB when force_attach_bigfile = On
$store_in_file = 1;
}
my $attachid = $self->id;
# If the file is to be stored locally, stream the file from the web server
# to the local file without reading it into a local variable.
if ($store_in_file)
{
my $attachdir = bz_locations()->{attachdir};
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") or die "Could not write into $attachdir/$hash/attachment.$attachid: $!";
binmode AH;
if (ref $fh)
{
my $limit = Bugzilla->params->{maxlocalattachment} * 1048576;
my $sizecount = 0;
while (<$fh>)
{
print AH $_;
$sizecount += length($_);
if ($sizecount > $limit)
{
close AH;
close $fh;
unlink "$attachdir/$hash/attachment.$attachid";
ThrowUserError("local_file_too_large");
}
}
close $fh;
}
else
{
print AH $fh;
}
close AH;
} }
else else
{ {
print AH $fh; # We only use $fh here in this INSERT with a placeholder, so it's safe.
my $sth = $dbh->prepare("INSERT INTO attach_data (id, thedata) VALUES ($attachid, ?)");
trick_taint($fh);
$sth->bind_param(1, $fh, $dbh->BLOB_TYPE);
$sth->execute();
} }
close AH; Bugzilla::Hook::process('attachment_post_create', { attachment => $self });
}
else
{
# We only use $fh here in this INSERT with a placeholder, so it's safe.
my $sth = $dbh->prepare("INSERT INTO attach_data (id, thedata) VALUES ($attachid, ?)");
trick_taint($fh);
$sth->bind_param(1, $fh, $dbh->BLOB_TYPE);
$sth->execute();
} }
Bugzilla::Hook::process('attachment_post_create', { attachment => $attachment }); my $old_self = $self->{_old_self};
my ($removed, $added) = Bugzilla::Flag->update_flags($self, $old_self, $self->{delta_ts}, $self->{flag_notify_comment});
# Return the new attachment object.
return $attachment;
}
sub run_create_validators
{
my ($class, $params) = @_;
# Let's validate the attachment content first as it may
# alter some other attachment attributes.
$params->{data} = $class->_check_data($params);
$params = $class->SUPER::run_create_validators($params);
$params->{filename} = $class->_check_filename($params->{filename}, 'filename', $params);
$params->{creation_ts} ||= Bugzilla->dbh->selectrow_array('SELECT LOCALTIMESTAMP(0)');
$params->{modification_time} = $params->{creation_ts};
$params->{submitter_id} = Bugzilla->user->id || ThrowCodeError('invalid_user');
delete $params->{base64_content};
return $params;
}
sub update
{
my $self = shift;
my $dbh = Bugzilla->dbh;
my $user = Bugzilla->user;
my $timestamp = shift || $dbh->selectrow_array('SELECT LOCALTIMESTAMP(0)');
my ($changes, $old_self) = $self->SUPER::update(@_);
my ($removed, $added) = Bugzilla::Flag->update_flags($self, $old_self, $timestamp, $self->{flag_notify_comment});
if ($removed || $added) if ($removed || $added)
{ {
$changes->{'flagtypes.name'} = [$removed, $added]; $changes->{'flagtypes.name'} = [$removed, $added];
} }
delete $self->{flag_notify_comment}; delete $self->{flag_notify_comment};
# Log activity
my $c;
foreach my $field (keys %$changes)
{
$c = $changes->{$field};
$field = "attachments.$field" unless $field eq 'flagtypes.name';
Bugzilla::Bug::LogActivityEntry(
$self->bug_id, $field, $c->[0], $c->[1],
$user->id, $timestamp, $self->id
);
}
if (scalar keys %$changes) if (scalar keys %$changes)
{ {
$dbh->do(
'UPDATE attachments SET modification_time = ? WHERE attach_id = ?',
undef, $timestamp, $self->id
);
$dbh->do( $dbh->do(
'UPDATE bugs SET delta_ts = ? WHERE bug_id = ?', 'UPDATE bugs SET delta_ts = ? WHERE bug_id = ?',
undef, $timestamp, $self->bug_id undef, $self->{delta_ts}, $self->bug_id
); );
} }
return $changes; $self->SUPER::_after_update($changes);
} }
=pod =pod

View File

@ -192,14 +192,6 @@ use constant DEFAULT_FIELDS => (map { my $i = 0; $_ = { (map { (DEFAULT_FIELD_CO
# Comment (never stored in bugs_activity...) # Comment (never stored in bugs_activity...)
[ 'longdesc', 'Comment', 0, 0, 0 ], [ 'longdesc', 'Comment', 0, 0, 0 ],
# Attachment fields
[ 'attachments.description', 'Attachment description', 0, 0, 0 ],
[ 'attachments.filename', 'Attachment filename', 0, 0, 0 ],
[ 'attachments.mimetype', 'Attachment mime type', 0, 0, 0 ],
[ 'attachments.ispatch', 'Attachment is patch', 0, 0, 0 ],
[ 'attachments.isobsolete', 'Attachment is obsolete', 0, 0, 0 ],
[ 'attachments.isprivate', 'Attachment is private', 0, 0, 0 ],
)); ));
# Tweaks allowed for standard field properties # Tweaks allowed for standard field properties

View File

@ -874,11 +874,29 @@ WHERE description LIKE \'%[CC:%]%\'');
$dbh->do( $dbh->do(
'INSERT INTO objects_activity (class_id, object_id, field_id, who, change_ts, removed, added)'. 'INSERT INTO objects_activity (class_id, object_id, field_id, who, change_ts, removed, added)'.
' SELECT '.Bugzilla->get_class('comment')->id.', comment_id, '. ' SELECT '.Bugzilla->get_class('comment')->id.', comment_id, '.
Bugzilla->get_class_field('comment', 'thetext')->id.', who, bug_when, oldthetext, thetext'. Bugzilla->get_class_field('thetext', 'comment')->id.', who, bug_when, oldthetext, thetext'.
' FROM longdescs_history' ' FROM longdescs_history'
); );
$dbh->bz_drop_table('longdescs_history'); $dbh->bz_drop_table('longdescs_history');
} }
# Migrate attachments.* from bugs_activity to objects_activity
if (Bugzilla->get_field('attachments.description'))
{
my $cid = Bugzilla->get_class('attachment')->id;
$dbh->do(
'INSERT INTO objects_activity (class_id, object_id, field_id, who, change_ts, removed, added)'.
' SELECT '.$cid.', a.attach_id, f2.id, a.who, a.bug_when, a.removed, a.added'.
' FROM bugs_activity a, fielddefs f, fielddefs f2 WHERE f.class_id=1'.
' AND f.name LIKE \'attachments.%\' AND a.fieldid=f.id AND f2.class_id='.$cid.' AND f2.name=SUBSTR(f.name, 13)'
);
$dbh->do(
'DELETE FROM bugs_activity WHERE fieldid IN (SELECT f.id FROM fielddefs f WHERE f.class_id=1'.
' AND f.name LIKE \'attachments.%\')'
);
$dbh->do(
'DELETE FROM fielddefs WHERE f.class_id=1 AND f.name LIKE \'attachments.%\''
);
}
_move_old_defaults($old_params); _move_old_defaults($old_params);

View File

@ -537,7 +537,8 @@ sub STATIC_COLUMNS
# Search-only fields that were previously in fielddefs # Search-only fields that were previously in fielddefs
foreach my $col (qw(requestees.login_name setters.login_name longdescs.isprivate content commenter foreach my $col (qw(requestees.login_name setters.login_name longdescs.isprivate content commenter
owner_idle_time attachments.submitter days_elapsed percentage_complete)) owner_idle_time attachments.submitter attachments.description attachments.filename attachments.mimetype
attachments.ispatch attachments.isobsolete attachments.isprivate days_elapsed percentage_complete))
{ {
$columns->{$col}->{title} = Bugzilla->messages->{field_descs}->{$col}; $columns->{$col}->{title} = Bugzilla->messages->{field_descs}->{$col};
$columns->{$col}->{nobuglist} = 1; $columns->{$col}->{nobuglist} = 1;

View File

@ -582,14 +582,16 @@ sub insert
my $attachment = Bugzilla::Attachment->create({ my $attachment = Bugzilla::Attachment->create({
bug => $bug, bug => $bug,
creation_ts => $timestamp, creation_ts => $timestamp,
data => $data,
description => $ARGS->{description}, description => $ARGS->{description},
filename => $filename, filename => $filename,
ispatch => $ispatch, ispatch => $ispatch,
isprivate => $ARGS->{isprivate}, isprivate => $ARGS->{isprivate},
mimetype => $content_type, mimetype => $content_type,
store_in_file => $ARGS->{bigfile}, data => {
base64_content => $ARGS->{base64_content}, data => $data,
store_in_file => $ARGS->{bigfile},
base64_content => $ARGS->{base64_content},
},
}); });
foreach my $obsolete_attachment (@obsolete_attachments) foreach my $obsolete_attachment (@obsolete_attachments)
@ -726,12 +728,14 @@ sub update
if ($can_edit) if ($can_edit)
{ {
$attachment->set_description($ARGS->{description}); $attachment->set_all({
$attachment->set_is_patch($ARGS->{ispatch}); description => $ARGS->{description},
$attachment->set_content_type($ARGS->{contenttypeentry}); ispatch => $ARGS->{ispatch},
$attachment->set_is_obsolete($ARGS->{isobsolete}); mimetype => $ARGS->{contenttypeentry},
$attachment->set_is_private($ARGS->{isprivate}); isobsolete => $ARGS->{isobsolete},
$attachment->set_filename($ARGS->{filename}); isprivate => $ARGS->{isprivate},
filename => $ARGS->{filename},
});
# Now make sure the attachment has not been edited since we loaded the page. # Now make sure the attachment has not been edited since we loaded the page.
if (defined $ARGS->{delta_ts} && $ARGS->{delta_ts} ne $attachment->modification_time) if (defined $ARGS->{delta_ts} && $ARGS->{delta_ts} ne $attachment->modification_time)

View File

@ -56,7 +56,7 @@
</tr> </tr>
<tr> <tr>
<td valign="top">Creation Date:</td> <td valign="top">Creation Date:</td>
<td valign="top">[% a.attached FILTER time %]</td> <td valign="top">[% a.creation_ts FILTER time %]</td>
</tr> </tr>
</table> </table>

View File

@ -89,9 +89,9 @@
<label for="contenttypeentry">MIME Type:</label> <label for="contenttypeentry">MIME Type:</label>
<input type="text" size="20" class="block[% editable_or_hide %]" <input type="text" size="20" class="block[% editable_or_hide %]"
id="contenttypeentry" name="contenttypeentry" id="contenttypeentry" name="contenttypeentry"
value="[% attachment.contenttype FILTER html %]" /> value="[% attachment.mimetype FILTER html %]" />
[% IF !can_edit %] [% IF !can_edit %]
[%+ attachment.contenttype FILTER truncate(25) FILTER html %] [%+ attachment.mimetype FILTER truncate(25) FILTER html %]
[% END %] [% END %]
</div> </div>
@ -206,7 +206,7 @@
minrows = 10 minrows = 10
cols = 80 cols = 80
wrap = 'soft' wrap = 'soft'
defaultcontent = (attachment.contenttype.match('^text\/')) ? defaultcontent = (attachment.mimetype.match('^text\/')) ?
attachment.data.replace('(.*\n|.+)', '>$1') : undef attachment.data.replace('(.*\n|.+)', '>$1') : undef
%] %]
<iframe id="viewFrame" src="attachment.cgi?id=[% attachment.id %]" style="height: 400px; width: 100%;"> <iframe id="viewFrame" src="attachment.cgi?id=[% attachment.id %]" style="height: 400px; width: 100%;">
@ -237,7 +237,7 @@
<td id="noview" width="50%"> <td id="noview" width="50%">
<p><b> <p><b>
Attachment is not viewable in your browser because its MIME type Attachment is not viewable in your browser because its MIME type
([% attachment.contenttype FILTER html %]) is not one that your browser is ([% attachment.mimetype FILTER html %]) is not one that your browser is
able to display. able to display.
</b></p> </b></p>
<p><b> <p><b>

View File

@ -38,7 +38,7 @@
[% IF attachment.isobsolete %] [% IF attachment.isobsolete %]
[% obsolete_attachments = obsolete_attachments + 1 %] [% obsolete_attachments = obsolete_attachments + 1 %]
[% END %] [% END %]
<tr class="[% "bz_contenttype_" _ attachment.contenttype | css_class_quote %] <tr class="[% "bz_contenttype_" _ attachment.mimetype | css_class_quote %]
[% " bz_patch" IF attachment.ispatch %] [% " bz_patch" IF attachment.ispatch %]
[% " bz_private" IF attachment.isprivate %] [% " bz_private" IF attachment.isprivate %]
[% " bz_tr_obsolete bz_default_hidden" IF attachment.isobsolete %]"> [% " bz_tr_obsolete bz_default_hidden" IF attachment.isobsolete %]">
@ -60,7 +60,7 @@
[% IF attachment.ispatch %] [% IF attachment.ispatch %]
patch) patch)
[% ELSE %] [% ELSE %]
[%+ attachment.contenttype FILTER html %]) [%+ attachment.mimetype FILTER html %])
[% END %] [% END %]
[% ELSE %] [% ELSE %]
(<em>deleted</em>) (<em>deleted</em>)
@ -69,7 +69,7 @@
<br /> <br />
<a href="#attach_[% attachment.id %]" <a href="#attach_[% attachment.id %]"
title="Go to the comment associated with the attachment"> title="Go to the comment associated with the attachment">
[%- attachment.attached FILTER time %]</a>, [%- attachment.creation_ts FILTER time %]</a>,
[% INCLUDE global/user.html.tmpl who = attachment.attacher %] [% INCLUDE global/user.html.tmpl who = attachment.attacher %]
</span> </span>

View File

@ -54,11 +54,11 @@
[% IF a.ispatch %] [% IF a.ispatch %]
<i>patch</i> <i>patch</i>
[% ELSE %] [% ELSE %]
[% a.contenttype FILTER html %] [% a.mimetype FILTER html %]
[% END %] [% END %]
</td> </td>
<td valign="top">[% a.attached FILTER time %]</td> <td valign="top">[% a.creation_ts FILTER time %]</td>
<td valign="top">[% a.datasize FILTER unitconvert %]</td> <td valign="top">[% a.datasize FILTER unitconvert %]</td>
<td valign="top"> <td valign="top">

View File

@ -94,11 +94,11 @@
ispatch="[% a.ispatch | xml %]" ispatch="[% a.ispatch | xml %]"
isprivate="[% a.isprivate | xml %]"> isprivate="[% a.isprivate | xml %]">
<attachid>[% a.id %]</attachid> <attachid>[% a.id %]</attachid>
<date>[% a.attached | time("%Y-%m-%d %T %z") | xml %]</date> <date>[% a.creation_ts | time("%Y-%m-%d %T %z") | xml %]</date>
<delta_ts>[% a.modification_time | time("%Y-%m-%d %T %z") | xml %]</delta_ts> <delta_ts>[% a.modification_time | time("%Y-%m-%d %T %z") | xml %]</delta_ts>
<desc>[% a.description | xml %]</desc> <desc>[% a.description | xml %]</desc>
<filename>[% a.filename | xml %]</filename> <filename>[% a.filename | xml %]</filename>
<type>[% a.contenttype | xml %]</type> <type>[% a.mimetype | xml %]</type>
<size>[% a.datasize | xml %]</size> <size>[% a.datasize | xml %]</size>
<attacher>[% a.attacher.email | email | xml %]</attacher> <attacher>[% a.attacher.email | email | xml %]</attacher>
[%# This is here so automated clients can still use attachment.cgi %] [%# This is here so automated clients can still use attachment.cgi %]