diff options
Diffstat (limited to 'rt/lib/RT/Attachment.pm')
-rwxr-xr-x | rt/lib/RT/Attachment.pm | 423 |
1 files changed, 423 insertions, 0 deletions
diff --git a/rt/lib/RT/Attachment.pm b/rt/lib/RT/Attachment.pm new file mode 100755 index 000000000..916ac355e --- /dev/null +++ b/rt/lib/RT/Attachment.pm @@ -0,0 +1,423 @@ +# $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 -- an RT attachment object + +=head1 SYNOPSIS + + use RT::Attachment; + + +=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 + + +=begin testing + +ok (require RT::TestHarness); +ok (require RT::Attachment); + +=end testing + +=cut + +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 + +=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}; +} + +# }}} + +# {{{ sub Create + +=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); + } +} + +# }}} + + +# {{{ sub Content + +=head2 Content + +Returns the attachment's content. if it's base64 encoded, decode it +before returning it. + +=cut + +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); + } +} + + +# }}} + +# {{{ sub Children + +=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); +} + +# }}} + +# {{{ UTILITIES + +# {{{ sub Quote + + + +sub Quote { + my $self=shift; + my %args=(Reply=>undef, # Prefilled reply (i.e. from the KB/FAQ system) + @_); + + 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; + + # Do we need any preformatting (wrapping, that is) of the message? + + # Remove quoted signature. + $body =~ s/\n-- \n(.*)$//s; + + # 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); + } + + $body =~ s/^/> /gm; + + $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); +} +# }}} + +# {{{ sub NiceHeaders - pulls out only the most relevant headers + +=head2 NiceHeaders + +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; +} +# }}} + +# {{{ sub Headers + +=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; +} + + +# }}} + +# {{{ sub GetHeader + +=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; +} +# }}} + +# {{{ sub _Value + +=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); + } + + +} + +# }}} + +# }}} + +1; |