diff options
Diffstat (limited to 'rt/lib/RT/Attachment.pm')
-rwxr-xr-x | rt/lib/RT/Attachment.pm | 601 |
1 files changed, 326 insertions, 275 deletions
diff --git a/rt/lib/RT/Attachment.pm b/rt/lib/RT/Attachment.pm index 2ed520162..916ac355e 100755 --- a/rt/lib/RT/Attachment.pm +++ b/rt/lib/RT/Attachment.pm @@ -1,372 +1,423 @@ -# BEGIN LICENSE BLOCK -# -# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com> -# -# (Except where explictly superceded by other copyright notices) -# -# This work is made available to you under the terms of Version 2 of -# the GNU General Public License. A copy of that license should have -# been provided with this software, but in any event can be snarfed -# from www.gnu.org. -# -# This work is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# Unless otherwise specified, all modifications, corrections or -# extensions to this work which alter its source code become the -# property of Best Practical Solutions, LLC when submitted for -# inclusion in the work. -# -# -# END LICENSE BLOCK -# Autogenerated by DBIx::SearchBuilder factory (by <jesse@bestpractical.com>) -# WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST. -# -# !! DO NOT EDIT THIS FILE !! -# - -use strict; - +# $Header: /home/cvs/cvsroot/freeside/rt/lib/RT/Attachment.pm,v 1.1 2002-08-12 06:17:07 ivan Exp $ +# Copyright 2000 Jesse Vincent <jesse@fsck.com> +# Released under the terms of the GNU Public License =head1 NAME -RT::Attachment - + RT::Attachment -- an RT attachment object =head1 SYNOPSIS -=head1 DESCRIPTION + use RT::Attachment; -=head1 METHODS -=cut - -package RT::Attachment; -use RT::Record; - - -use vars qw( @ISA ); -@ISA= qw( RT::Record ); - -sub _Init { - my $self = shift; - - $self->Table('Attachments'); - $self->SUPER::_Init(@_); -} +=head1 DESCRIPTION +This module should never be instantiated directly by client code. it's an internal +module which should only be instantiated through exported APIs in Ticket, Queue and other +similar objects. +=head1 METHODS -=item Create PARAMHASH +=begin testing -Create takes a hash of values and creates a row in the database: +ok (require RT::TestHarness); +ok (require RT::Attachment); - int(11) 'TransactionId'. - int(11) 'Parent'. - varchar(160) 'MessageId'. - varchar(255) 'Subject'. - varchar(255) 'Filename'. - varchar(80) 'ContentType'. - varchar(80) 'ContentEncoding'. - longtext 'Content'. - longtext 'Headers'. +=end testing =cut - - - -sub Create { - my $self = shift; - my %args = ( - TransactionId => '0', - Parent => '0', - MessageId => '', - Subject => '', - Filename => '', - ContentType => '', - ContentEncoding => '', - Content => '', - Headers => '', - - @_); - $self->SUPER::Create( - TransactionId => $args{'TransactionId'}, - Parent => $args{'Parent'}, - MessageId => $args{'MessageId'}, - Subject => $args{'Subject'}, - Filename => $args{'Filename'}, - ContentType => $args{'ContentType'}, - ContentEncoding => $args{'ContentEncoding'}, - Content => $args{'Content'}, - Headers => $args{'Headers'}, -); - +package RT::Attachment; +use RT::Record; +use MIME::Base64; +use vars qw|@ISA|; +@ISA= qw(RT::Record); + +# {{{ sub _Init +sub _Init { + my $self = shift; + $self->{'table'} = "Attachments"; + return($self->SUPER::_Init(@_)); } +# }}} +# {{{ sub _ClassAccessible +sub _ClassAccessible { + { + TransactionId => { 'read'=>1, 'public'=>1, }, + MessageId => { 'read'=>1, }, + Parent => { 'read'=>1, }, + ContentType => { 'read'=>1, }, + Subject => { 'read'=>1, }, + Content => { 'read'=>1, }, + ContentEncoding => { 'read'=>1, }, + Headers => { 'read'=>1, }, + Filename => { 'read'=>1, }, + Creator => { 'read'=>1, 'auto'=>1, }, + Created => { 'read'=>1, 'auto'=>1, }, + }; +} +# }}} +# {{{ sub TransactionObj -=item id - -Returns the current value of id. -(In the database, id is stored as int(11).) - - -=cut - - -=item TransactionId - -Returns the current value of TransactionId. -(In the database, TransactionId is stored as int(11).) - - - -=item SetTransactionId VALUE - - -Set TransactionId to VALUE. -Returns (1, 'Status message') on success and (0, 'Error Message') on failure. -(In the database, TransactionId will be stored as a int(11).) +=head2 TransactionObj +Returns the transaction object asscoiated with this attachment. =cut +sub TransactionObj { + require RT::Transaction; + my $self=shift; + unless (exists $self->{_TransactionObj}) { + $self->{_TransactionObj}=RT::Transaction->new($self->CurrentUser); + $self->{_TransactionObj}->Load($self->TransactionId); + } + return $self->{_TransactionObj}; +} -=item Parent - -Returns the current value of Parent. -(In the database, Parent is stored as int(11).) - - - -=item SetParent VALUE +# }}} +# {{{ sub Create -Set Parent to VALUE. -Returns (1, 'Status message') on success and (0, 'Error Message') on failure. -(In the database, Parent will be stored as a int(11).) +=head2 Create +Create a new attachment. Takes a paramhash: + + 'Attachment' Should be a single MIME body with optional subparts + 'Parent' is an optional Parent RT::Attachment object + 'TransactionId' is the mandatory id of the Transaction this attachment is associated with.; =cut +sub Create { + my $self = shift; + my ($id); + my %args = ( id => 0, + TransactionId => 0, + Parent => 0, + Attachment => undef, + @_ + ); + + + #For ease of reference + my $Attachment = $args{'Attachment'}; + + #if we didn't specify a ticket, we need to bail + if ( $args{'TransactionId'} == 0) { + $RT::Logger->crit("RT::Attachment->Create couldn't, as you didn't specify a transaction\n"); + return (0); + + } + + #If we possibly can, collapse it to a singlepart + $Attachment->make_singlepart; + + #Get the subject + my $Subject = $Attachment->head->get('subject',0); + defined($Subject) or $Subject = ''; + chomp($Subject); + + #Get the filename + my $Filename = $Attachment->head->recommended_filename; + + if ($Attachment->parts) { + $id = $self->SUPER::Create(TransactionId => $args{'TransactionId'}, + Parent => 0, + ContentType => $Attachment->mime_type, + Headers => $Attachment->head->as_string, + Subject => $Subject, + + ); + foreach my $part ($Attachment->parts) { + my $SubAttachment = new RT::Attachment($self->CurrentUser); + $SubAttachment->Create(TransactionId => $args{'TransactionId'}, + Parent => $id, + Attachment => $part, + ContentType => $Attachment->mime_type, + Headers => $Attachment->head->as_string(), + + ); + } + return ($id); + } + + + #If it's not multipart + else { + + my $ContentEncoding = 'none'; + + my $Body = $Attachment->bodyhandle->as_string; + + #get the max attachment length from RT + my $MaxSize = $RT::MaxAttachmentSize; + + #if the current attachment contains nulls and the + #database doesn't support embedded nulls + + if ( (! $RT::Handle->BinarySafeBLOBs) && + ( $Body =~ /\x00/ ) ) { + # set a flag telling us to mimencode the attachment + $ContentEncoding = 'base64'; + + #cut the max attchment size by 25% (for mime-encoding overhead. + $RT::Logger->debug("Max size is $MaxSize\n"); + $MaxSize = $MaxSize * 3/4; + } + + #if the attachment is larger than the maximum size + if (($MaxSize) and ($MaxSize < length($Body))) { + # if we're supposed to truncate large attachments + if ($RT::TruncateLongAttachments) { + # truncate the attachment to that length. + $Body = substr ($Body, 0, $MaxSize); + + } + + # elsif we're supposed to drop large attachments on the floor, + elsif ($RT::DropLongAttachments) { + # drop the attachment on the floor + $RT::Logger->info("$self: Dropped an attachment of size ". length($Body). + "\n". "It started: ". substr($Body, 0, 60) . "\n"); + return(undef); + } + } + # if we need to mimencode the attachment + if ($ContentEncoding eq 'base64') { + # base64 encode the attachment + $Body = MIME::Base64::encode_base64($Body); + + } + + my $id = $self->SUPER::Create(TransactionId => $args{'TransactionId'}, + ContentType => $Attachment->mime_type, + ContentEncoding => $ContentEncoding, + Parent => $args{'Parent'}, + Content => $Body, + Headers => $Attachment->head->as_string, + Subject => $Subject, + Filename => $Filename, + ); + return ($id); + } +} -=item MessageId - -Returns the current value of MessageId. -(In the database, MessageId is stored as varchar(160).) - - +# }}} -=item SetMessageId VALUE +# {{{ sub Content -Set MessageId to VALUE. -Returns (1, 'Status message') on success and (0, 'Error Message') on failure. -(In the database, MessageId will be stored as a varchar(160).) +=head2 Content +Returns the attachment's content. if it's base64 encoded, decode it +before returning it. =cut - -=item Subject - -Returns the current value of Subject. -(In the database, Subject is stored as varchar(255).) - +sub Content { + my $self = shift; + if ( $self->ContentEncoding eq 'none' || ! $self->ContentEncoding ) { + return $self->_Value('Content'); + } elsif ( $self->ContentEncoding eq 'base64' ) { + return MIME::Base64::decode_base64($self->_Value('Content')); + } else { + return( "Unknown ContentEncoding ". $self->ContentEncoding); + } +} -=item SetSubject VALUE +# }}} +# {{{ sub Children -Set Subject to VALUE. -Returns (1, 'Status message') on success and (0, 'Error Message') on failure. -(In the database, Subject will be stored as a varchar(255).) +=head2 Children + Returns an RT::Attachments object which is preloaded with all Attachments objects with this Attachment\'s Id as their 'Parent' =cut +sub Children { + my $self = shift; + + my $kids = new RT::Attachments($self->CurrentUser); + $kids->ChildrenOf($self->Id); + return($kids); +} -=item Filename - -Returns the current value of Filename. -(In the database, Filename is stored as varchar(255).) - - - -=item SetFilename VALUE - - -Set Filename to VALUE. -Returns (1, 'Status message') on success and (0, 'Error Message') on failure. -(In the database, Filename will be stored as a varchar(255).) - - -=cut +# }}} +# {{{ UTILITIES -=item ContentType +# {{{ sub Quote -Returns the current value of ContentType. -(In the database, ContentType is stored as varchar(80).) +sub Quote { + my $self=shift; + my %args=(Reply=>undef, # Prefilled reply (i.e. from the KB/FAQ system) + @_); -=item SetContentType VALUE + my ($quoted_content, $body, $headers); + my $max=0; + # TODO: Handle Multipart/Mixed (eventually fix the link in the + # ShowHistory web template?) + if ($self->ContentType =~ m{^(text/plain|message)}i) { + $body=$self->Content; -Set ContentType to VALUE. -Returns (1, 'Status message') on success and (0, 'Error Message') on failure. -(In the database, ContentType will be stored as a varchar(80).) + # Do we need any preformatting (wrapping, that is) of the message? + # Remove quoted signature. + $body =~ s/\n-- \n(.*)$//s; -=cut + # What's the longest line like? + foreach (split (/\n/,$body)) { + $max=length if ( length > $max); + } + if ($max>76) { + require Text::Wrapper; + my $wrapper=new Text::Wrapper + ( + columns => 70, + body_start => ($max > 70*3 ? ' ' : ''), + par_start => '' + ); + $body=$wrapper->wrap($body); + } -=item ContentEncoding + $body =~ s/^/> /gm; -Returns the current value of ContentEncoding. -(In the database, ContentEncoding is stored as varchar(80).) + $body = '[' . $self->TransactionObj->CreatorObj->Name() . ' - ' . $self->TransactionObj->CreatedAsString() + . "]:\n\n" + . $body . "\n\n"; + } else { + $body = "[Non-text message not quoted]\n\n"; + } + + $max=60 if $max<60; + $max=70 if $max>78; + $max+=2; + return (\$body, $max); +} +# }}} -=item SetContentEncoding VALUE +# {{{ sub NiceHeaders - pulls out only the most relevant headers +=head2 NiceHeaders -Set ContentEncoding to VALUE. -Returns (1, 'Status message') on success and (0, 'Error Message') on failure. -(In the database, ContentEncoding will be stored as a varchar(80).) +Returns the To, From, Cc, Date and Subject headers. +It is a known issue that this breaks if any of these headers are not +properly unfolded. =cut +sub NiceHeaders { + my $self=shift; + my $hdrs=""; + for (split(/\n/,$self->Headers)) { + $hdrs.="$_\n" if /^(To|From|RT-Send-Cc|Cc|Date|Subject): /i + } + return $hdrs; +} +# }}} -=item Content - -Returns the current value of Content. -(In the database, Content is stored as longtext.) - - - -=item SetContent VALUE - +# {{{ sub Headers -Set Content to VALUE. -Returns (1, 'Status message') on success and (0, 'Error Message') on failure. -(In the database, Content will be stored as a longtext.) +=head2 Headers +Returns this object's headers as a string. This method specifically +removes the RT-Send-Bcc: header, so as to never reveal to whom RT sent a Bcc. +We need to record the RT-Send-Cc and RT-Send-Bcc values so that we can actually send +out mail. (The mailing rules are seperated from the ticket update code by +an abstraction barrier that makes it impossible to pass this data directly =cut +sub Headers { + my $self = shift; + my $hdrs=""; + for (split(/\n/,$self->SUPER::Headers)) { + $hdrs.="$_\n" unless /^(RT-Send-Bcc): /i + } + return $hdrs; +} -=item Headers - -Returns the current value of Headers. -(In the database, Headers is stored as longtext.) - - - -=item SetHeaders VALUE - - -Set Headers to VALUE. -Returns (1, 'Status message') on success and (0, 'Error Message') on failure. -(In the database, Headers will be stored as a longtext.) - - -=cut +# }}} -=item Creator +# {{{ sub GetHeader -Returns the current value of Creator. -(In the database, Creator is stored as int(11).) +=head2 GetHeader ( 'Tag') +Returns the value of the header Tag as a string. This bypasses the weeding out +done in Headers() above. =cut +sub GetHeader { + my $self = shift; + my $tag = shift; + foreach my $line (split(/\n/,$self->SUPER::Headers)) { + $RT::Logger->debug( "Does $line match $tag\n"); + if ($line =~ /^$tag:\s+(.*)$/i) { #if we find the header, return its value + return ($1); + } + } + + # we found no header. return an empty string + return undef; +} +# }}} -=item Created +# {{{ sub _Value -Returns the current value of Created. -(In the database, Created is stored as datetime.) +=head2 _Value +Takes the name of a table column. +Returns its value as a string, if the user passes an ACL check =cut +sub _Value { + my $self = shift; + my $field = shift; + + + #if the field is public, return it. + if ($self->_Accessible($field, 'public')) { + #$RT::Logger->debug("Skipping ACL check for $field\n"); + return($self->__Value($field)); + + } + + #If it's a comment, we need to be extra special careful + elsif ( (($self->TransactionObj->CurrentUserHasRight('ShowTicketComments')) and + ($self->TransactionObj->Type eq 'Comment') ) or + ($self->TransactionObj->CurrentUserHasRight('ShowTicket'))) { + + return($self->__Value($field)); + } + #if they ain't got rights to see, don't let em + else { + return(undef); + } + + +} -sub _ClassAccessible { - { - - id => - {read => 1, type => 'int(11)', default => ''}, - TransactionId => - {read => 1, write => 1, type => 'int(11)', default => '0'}, - Parent => - {read => 1, write => 1, type => 'int(11)', default => '0'}, - MessageId => - {read => 1, write => 1, type => 'varchar(160)', default => ''}, - Subject => - {read => 1, write => 1, type => 'varchar(255)', default => ''}, - Filename => - {read => 1, write => 1, type => 'varchar(255)', default => ''}, - ContentType => - {read => 1, write => 1, type => 'varchar(80)', default => ''}, - ContentEncoding => - {read => 1, write => 1, type => 'varchar(80)', default => ''}, - Content => - {read => 1, write => 1, type => 'longtext', default => ''}, - Headers => - {read => 1, write => 1, type => 'longtext', default => ''}, - Creator => - {read => 1, auto => 1, type => 'int(11)', default => '0'}, - Created => - {read => 1, auto => 1, type => 'datetime', default => ''}, - - } -}; - - - eval "require RT::Attachment_Overlay"; - if ($@ && $@ !~ qr{^Can't locate RT/Attachment_Overlay.pm}) { - die $@; - }; - - eval "require RT::Attachment_Vendor"; - if ($@ && $@ !~ qr{^Can't locate RT/Attachment_Vendor.pm}) { - die $@; - }; - - eval "require RT::Attachment_Local"; - if ($@ && $@ !~ qr{^Can't locate RT/Attachment_Local.pm}) { - die $@; - }; - - - - -=head1 SEE ALSO - -This class allows "overlay" methods to be placed -into the following files _Overlay is for a System overlay by the original author, -_Vendor is for 3rd-party vendor add-ons, while _Local is for site-local customizations. - -These overlay files can contain new subs or subs to replace existing subs in this module. - -If you'll be working with perl 5.6.0 or greater, each of these files should begin with the line - - no warnings qw(redefine); - -so that perl does not kick and scream when you redefine a subroutine or variable in your overlay. - -RT::Attachment_Overlay, RT::Attachment_Vendor, RT::Attachment_Local - -=cut +# }}} +# }}} 1; |