import rt 2.0.14
[freeside.git] / rt / lib / RT / Attachment.pm
1 # $Header: /home/cvs/cvsroot/freeside/rt/lib/RT/Attachment.pm,v 1.1 2002-08-12 06:17:07 ivan Exp $
2 # Copyright 2000 Jesse Vincent <jesse@fsck.com>
3 # Released under the terms of the GNU Public License
4
5 =head1 NAME
6
7   RT::Attachment -- an RT attachment object
8
9 =head1 SYNOPSIS
10
11   use RT::Attachment;
12
13
14 =head1 DESCRIPTION
15
16 This module should never be instantiated directly by client code. it's an internal 
17 module which should only be instantiated through exported APIs in Ticket, Queue and other 
18 similar objects.
19
20
21 =head1 METHODS
22
23
24 =begin testing
25
26 ok (require RT::TestHarness);
27 ok (require RT::Attachment);
28
29 =end testing
30
31 =cut
32
33 package RT::Attachment;
34 use RT::Record;
35 use MIME::Base64;
36 use vars qw|@ISA|;
37 @ISA= qw(RT::Record);
38
39 # {{{ sub _Init
40 sub _Init  {
41     my $self = shift; 
42     $self->{'table'} = "Attachments";
43     return($self->SUPER::_Init(@_));
44 }
45 # }}}
46
47 # {{{ sub _ClassAccessible 
48 sub _ClassAccessible {
49     {
50     TransactionId   => { 'read'=>1, 'public'=>1, },
51     MessageId       => { 'read'=>1, },
52     Parent          => { 'read'=>1, },
53     ContentType     => { 'read'=>1, },
54     Subject         => { 'read'=>1, },
55     Content         => { 'read'=>1, },
56     ContentEncoding => { 'read'=>1, },
57     Headers         => { 'read'=>1, },
58     Filename        => { 'read'=>1, },
59     Creator         => { 'read'=>1, 'auto'=>1, },
60     Created         => { 'read'=>1, 'auto'=>1, },
61   };
62 }
63 # }}}
64
65 # {{{ sub TransactionObj 
66
67 =head2 TransactionObj
68
69 Returns the transaction object asscoiated with this attachment.
70
71 =cut
72
73 sub TransactionObj {
74     require RT::Transaction;
75     my $self=shift;
76     unless (exists $self->{_TransactionObj}) {
77         $self->{_TransactionObj}=RT::Transaction->new($self->CurrentUser);
78         $self->{_TransactionObj}->Load($self->TransactionId);
79     }
80     return $self->{_TransactionObj};
81 }
82
83 # }}}
84
85 # {{{ sub Create 
86
87 =head2 Create
88
89 Create a new attachment. Takes a paramhash:
90     
91     'Attachment' Should be a single MIME body with optional subparts
92     'Parent' is an optional Parent RT::Attachment object
93     'TransactionId' is the mandatory id of the Transaction this attachment is associated with.;
94
95 =cut
96
97 sub Create  {
98     my $self = shift;
99     my ($id);
100     my %args = ( id => 0,
101                  TransactionId => 0,
102                  Parent => 0,
103                  Attachment => undef,
104                  @_
105                );
106     
107     
108     #For ease of reference
109     my $Attachment = $args{'Attachment'};
110     
111     #if we didn't specify a ticket, we need to bail
112     if ( $args{'TransactionId'} == 0) {
113         $RT::Logger->crit("RT::Attachment->Create couldn't, as you didn't specify a transaction\n");
114         return (0);
115         
116     }
117     
118     #If we possibly can, collapse it to a singlepart
119     $Attachment->make_singlepart;
120     
121     #Get the subject
122     my $Subject = $Attachment->head->get('subject',0);
123     defined($Subject) or $Subject = '';
124     chomp($Subject);
125   
126     #Get the filename
127     my $Filename = $Attachment->head->recommended_filename;
128     
129     if ($Attachment->parts) {
130         $id = $self->SUPER::Create(TransactionId => $args{'TransactionId'},
131                                    Parent => 0,
132                                    ContentType  => $Attachment->mime_type,
133                                    Headers => $Attachment->head->as_string,
134                                    Subject => $Subject,
135                                    
136                                   );
137         foreach my $part ($Attachment->parts) { 
138             my $SubAttachment = new RT::Attachment($self->CurrentUser);
139             $SubAttachment->Create(TransactionId => $args{'TransactionId'},
140                                    Parent => $id,
141                                    Attachment => $part,
142                                    ContentType  => $Attachment->mime_type,
143                                    Headers => $Attachment->head->as_string(),
144                                    
145                                   );
146         }
147         return ($id);
148     }
149   
150   
151     #If it's not multipart
152     else {
153         
154         my $ContentEncoding = 'none'; 
155         
156         my $Body = $Attachment->bodyhandle->as_string;
157         
158         #get the max attachment length from RT
159         my $MaxSize = $RT::MaxAttachmentSize;
160         
161         #if the current attachment contains nulls and the 
162         #database doesn't support embedded nulls
163         
164         if ( (! $RT::Handle->BinarySafeBLOBs) &&
165              ( $Body =~ /\x00/ ) ) {
166             # set a flag telling us to mimencode the attachment
167             $ContentEncoding = 'base64';
168             
169             #cut the max attchment size by 25% (for mime-encoding overhead.
170             $RT::Logger->debug("Max size is $MaxSize\n");
171             $MaxSize = $MaxSize * 3/4;  
172         }
173         
174         #if the attachment is larger than the maximum size
175         if (($MaxSize) and ($MaxSize < length($Body))) {
176             # if we're supposed to truncate large attachments
177             if ($RT::TruncateLongAttachments) {
178                 # truncate the attachment to that length.
179                 $Body = substr ($Body, 0, $MaxSize);
180
181             }
182             
183             # elsif we're supposed to drop large attachments on the floor,
184             elsif ($RT::DropLongAttachments) {
185                 # drop the attachment on the floor
186                 $RT::Logger->info("$self: Dropped an attachment of size ". length($Body).
187                                   "\n". "It started: ". substr($Body, 0, 60) . "\n");
188                 return(undef);
189             }
190         }
191         # if we need to mimencode the attachment
192         if ($ContentEncoding eq 'base64') {
193             # base64 encode the attachment
194             $Body = MIME::Base64::encode_base64($Body);
195             
196         }
197         
198         my $id = $self->SUPER::Create(TransactionId => $args{'TransactionId'},
199                                       ContentType  => $Attachment->mime_type,
200                                       ContentEncoding => $ContentEncoding,
201                                       Parent => $args{'Parent'},
202                                       Content => $Body,
203                                       Headers => $Attachment->head->as_string,
204                                       Subject => $Subject,
205                                       Filename => $Filename,
206                                      );
207         return ($id);
208     }
209 }
210
211 # }}}
212
213
214 # {{{ sub Content
215
216 =head2 Content
217
218 Returns the attachment's content. if it's base64 encoded, decode it 
219 before returning it.
220
221 =cut
222
223 sub Content {
224   my $self = shift;
225   if ( $self->ContentEncoding eq 'none' || ! $self->ContentEncoding ) {
226       return $self->_Value('Content');
227   } elsif ( $self->ContentEncoding eq 'base64' ) {
228       return MIME::Base64::decode_base64($self->_Value('Content'));
229   } else {
230       return( "Unknown ContentEncoding ". $self->ContentEncoding);
231   }
232 }
233
234
235 # }}}
236
237 # {{{ sub Children
238
239 =head2 Children
240
241   Returns an RT::Attachments object which is preloaded with all Attachments objects with this Attachment\'s Id as their 'Parent'
242
243 =cut
244
245 sub Children {
246     my $self = shift;
247     
248     my $kids = new RT::Attachments($self->CurrentUser);
249     $kids->ChildrenOf($self->Id);
250     return($kids);
251 }
252
253 # }}}
254
255 # {{{ UTILITIES
256
257 # {{{ sub Quote 
258
259
260
261 sub Quote {
262     my $self=shift;
263     my %args=(Reply=>undef, # Prefilled reply (i.e. from the KB/FAQ system)
264               @_);
265
266     my ($quoted_content, $body, $headers);
267     my $max=0;
268
269     # TODO: Handle Multipart/Mixed (eventually fix the link in the
270     # ShowHistory web template?)
271     if ($self->ContentType =~ m{^(text/plain|message)}i) {
272         $body=$self->Content;
273
274         # Do we need any preformatting (wrapping, that is) of the message?
275
276         # Remove quoted signature.
277         $body =~ s/\n-- \n(.*)$//s;
278
279         # What's the longest line like?
280         foreach (split (/\n/,$body)) {
281             $max=length if ( length > $max);
282         }
283
284         if ($max>76) {
285             require Text::Wrapper;
286             my $wrapper=new Text::Wrapper
287                 (
288                  columns => 70, 
289                  body_start => ($max > 70*3 ? '   ' : ''),
290                  par_start => ''
291                  );
292             $body=$wrapper->wrap($body);
293         }
294
295         $body =~ s/^/> /gm;
296
297         $body = '[' . $self->TransactionObj->CreatorObj->Name() . ' - ' . $self->TransactionObj->CreatedAsString()
298                     . "]:\n\n"
299                 . $body . "\n\n";
300
301     } else {
302         $body = "[Non-text message not quoted]\n\n";
303     }
304     
305     $max=60 if $max<60;
306     $max=70 if $max>78;
307     $max+=2;
308
309     return (\$body, $max);
310 }
311 # }}}
312
313 # {{{ sub NiceHeaders - pulls out only the most relevant headers
314
315 =head2 NiceHeaders
316
317 Returns the To, From, Cc, Date and Subject headers.
318
319 It is a known issue that this breaks if any of these headers are not
320 properly unfolded.
321
322 =cut
323
324 sub NiceHeaders {
325     my $self=shift;
326     my $hdrs="";
327     for (split(/\n/,$self->Headers)) {
328             $hdrs.="$_\n" if /^(To|From|RT-Send-Cc|Cc|Date|Subject): /i
329     }
330     return $hdrs;
331 }
332 # }}}
333
334 # {{{ sub Headers
335
336 =head2 Headers
337
338 Returns this object's headers as a string.  This method specifically
339 removes the RT-Send-Bcc: header, so as to never reveal to whom RT sent a Bcc.
340 We need to record the RT-Send-Cc and RT-Send-Bcc values so that we can actually send
341 out mail. (The mailing rules are seperated from the ticket update code by
342 an abstraction barrier that makes it impossible to pass this data directly
343
344 =cut
345
346 sub Headers {
347     my $self = shift;
348     my $hdrs="";
349     for (split(/\n/,$self->SUPER::Headers)) {
350             $hdrs.="$_\n" unless /^(RT-Send-Bcc): /i
351     }
352     return $hdrs;
353 }
354
355
356 # }}}
357
358 # {{{ sub GetHeader
359
360 =head2 GetHeader ( 'Tag')
361
362 Returns the value of the header Tag as a string. This bypasses the weeding out
363 done in Headers() above.
364
365 =cut
366
367 sub GetHeader {
368     my $self = shift;
369     my $tag = shift;
370     foreach my $line (split(/\n/,$self->SUPER::Headers)) {
371         $RT::Logger->debug( "Does $line match $tag\n");
372         if ($line =~ /^$tag:\s+(.*)$/i) { #if we find the header, return its value
373             return ($1);
374         }
375     }
376     
377     # we found no header. return an empty string
378     return undef;
379 }
380 # }}}
381
382 # {{{ sub _Value 
383
384 =head2 _Value
385
386 Takes the name of a table column.
387 Returns its value as a string, if the user passes an ACL check
388
389 =cut
390
391 sub _Value  {
392
393     my $self = shift;
394     my $field = shift;
395     
396     
397     #if the field is public, return it.
398     if ($self->_Accessible($field, 'public')) {
399         #$RT::Logger->debug("Skipping ACL check for $field\n");
400         return($self->__Value($field));
401         
402     }
403     
404     #If it's a comment, we need to be extra special careful
405     elsif ( (($self->TransactionObj->CurrentUserHasRight('ShowTicketComments')) and
406              ($self->TransactionObj->Type eq 'Comment') )  or
407             ($self->TransactionObj->CurrentUserHasRight('ShowTicket'))) {
408         
409         return($self->__Value($field));
410     }
411     #if they ain't got rights to see, don't let em
412     else {
413             return(undef);
414         }
415         
416     
417 }
418
419 # }}}
420
421 # }}}
422
423 1;