# -*- 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 Netscape Communications # Corporation. Portions created by Netscape are # Copyright (C) 1998 Netscape Communications Corporation. All # Rights Reserved. # # Contributor(s): Terry Weissman # Myk Melez use strict; package Bugzilla::Attachment; =head1 NAME Bugzilla::Attachment - a file related to a bug that a user has uploaded to the Bugzilla server =head1 SYNOPSIS use Bugzilla::Attachment; # Get the attachment with the given ID. my $attachment = Bugzilla::Attachment->get($attach_id); # Get the attachments with the given IDs. my $attachments = Bugzilla::Attachment->get_list($attach_ids); =head1 DESCRIPTION This module defines attachment objects, which represent files related to bugs that users upload to the Bugzilla server. =cut # This module requires that its caller have said "require globals.pl" # to import relevant functions from that script. use Bugzilla::Flag; use Bugzilla::Config qw(:locations); use Bugzilla::User; sub get { my $invocant = shift; my $id = shift; my $attachments = _retrieve([$id]); my $self = $attachments->[0]; bless($self, ref($invocant) || $invocant) if $self; return $self; } sub get_list { my $invocant = shift; my $ids = shift; my $attachments = _retrieve($ids); foreach my $attachment (@$attachments) { bless($attachment, ref($invocant) || $invocant); } return $attachments; } sub _retrieve { my ($ids) = @_; return [] if scalar(@$ids) == 0; my @columns = ( 'attachments.attach_id AS id', 'attachments.bug_id AS bug_id', 'attachments.description AS description', 'attachments.mimetype AS contenttype', 'attachments.submitter_id AS _attacher_id', Bugzilla->dbh->sql_date_format('attachments.creation_ts', '%Y.%m.%d %H:%i') . " AS attached", 'attachments.filename AS filename', 'attachments.ispatch AS ispatch', 'attachments.isurl AS isurl', 'attachments.isobsolete AS isobsolete', 'attachments.isprivate AS isprivate' ); my $columns = join(", ", @columns); my $records = Bugzilla->dbh->selectall_arrayref("SELECT $columns FROM attachments WHERE attach_id IN (" . join(",", @$ids) . ")", { Slice => {} }); return $records; } =pod =head2 Instance Properties =over =item C the unique identifier for the attachment =back =cut sub id { my $self = shift; return $self->{id}; } =over =item C the ID of the bug to which the attachment is attached =back =cut # XXX Once Bug.pm slims down sufficiently this should become a reference # to a bug object. sub bug_id { my $self = shift; return $self->{bug_id}; } =over =item C user-provided text describing the attachment =back =cut sub description { my $self = shift; return $self->{description}; } =over =item C the attachment's MIME media type =back =cut sub contenttype { my $self = shift; return $self->{contenttype}; } =over =item C the user who attached the attachment =back =cut sub attacher { my $self = shift; return $self->{attacher} if exists $self->{attacher}; $self->{attacher} = new Bugzilla::User($self->{_attacher_id}); return $self->{attacher}; } =over =item C the date and time on which the attacher attached the attachment =back =cut sub attached { my $self = shift; return $self->{attached}; } =over =item C the name of the file the attacher attached =back =cut sub filename { my $self = shift; return $self->{filename}; } =over =item C whether or not the attachment is a patch =back =cut sub ispatch { my $self = shift; return $self->{ispatch}; } =over =item C whether or not the attachment is obsolete =back =cut sub isobsolete { my $self = shift; return $self->{isobsolete}; } =over =item C whether or not the attachment is private =back =cut sub isprivate { my $self = shift; return $self->{isprivate}; } =over =item C the content of the attachment =back =cut sub data { my $self = shift; return $self->{data} if exists $self->{data}; # First try to get the attachment data from the database. ($self->{data}) = Bugzilla->dbh->selectrow_array("SELECT thedata FROM attach_data WHERE id = ?", undef, $self->{id}); # If there's no attachment data in the database, the attachment is stored # in a local file, so retrieve it from there. if (length($self->{data}) == 0) { if (open(AH, $self->_get_local_filename())) { binmode AH; $self->{data} = ; close(AH); } } return $self->{data}; } =over =item C the length (in characters) of the attachment content =back =cut # datasize is a property of the data itself, and it's unclear whether we should # expose it at all, since you can easily derive it from the data itself: in TT, # attachment.data.size; in Perl, length($attachment->{data}). But perhaps # it makes sense for performance reasons, since accessing the data forces it # to get retrieved from the database/filesystem and loaded into memory, # while datasize avoids loading the attachment into memory, calling SQL's # LENGTH() function or stat()ing the file instead. I've left it in for now. sub datasize { my $self = shift; return $self->{datasize} if exists $self->{datasize}; # If we have already retrieved the data, return its size. return length($self->{data}) if exists $self->{data}; ($self->{datasize}) = Bugzilla->dbh->selectrow_array("SELECT LENGTH(thedata) FROM attach_data WHERE id = ?", undef, $self->{id}); # If there's no attachment data in the database, the attachment # is stored in a local file, so retrieve its size from the file. if ($self->{datasize} == 0) { if (open(AH, $self->_get_local_filename())) { binmode AH; $self->{datasize} = (stat(AH))[7]; close(AH); } } return $self->{datasize}; } =over =item C flags that have been set on the attachment =back =cut sub flags { my $self = shift; return $self->{flags} if exists $self->{flags}; $self->{flags} = Bugzilla::Flag::match({ attach_id => $self->id, is_active => 1 }); return $self->{flags}; } # Instance methods; no POD documentation here yet because the only one so far # is private. sub _get_local_filename { my $self = shift; my $hash = ($self->id % 100) + 100; $hash =~ s/.*(\d\d)$/group.$1/; return "$attachdir/$hash/attachment." . $self->id; } =pod =head2 Class Methods =over =item C Description: retrieves and returns the attachments for the given bug. Params: C<$bug_id> - integer - the ID of the bug for which to retrieve and return attachments. Returns: a reference to an array of attachment objects. =back =cut sub get_attachments_by_bug { my ($class, $bug_id) = @_; my $attach_ids = Bugzilla->dbh->selectcol_arrayref("SELECT attach_id FROM attachments WHERE bug_id = ? ORDER BY attach_id", undef, $bug_id); my $attachments = Bugzilla::Attachment->get_list($attach_ids); return $attachments; } 1;