X-Git-Url: http://git.freeside.biz/gitweb/?p=freeside.git;a=blobdiff_plain;f=rt%2Flib%2FRT%2FAttachment.pm;h=07fdea3b294ff9f251a4a5d352444b5bd6119607;hp=2ed5201628019000db09c2feadb3f8726205f1b4;hb=e9e0cf0989259b94d9758eceff448666a2e5a5cc;hpb=2041a9143fac20b79ead4a1ae01224dedf5b27c2 diff --git a/rt/lib/RT/Attachment.pm b/rt/lib/RT/Attachment.pm index 2ed520162..07fdea3b2 100755 --- a/rt/lib/RT/Attachment.pm +++ b/rt/lib/RT/Attachment.pm @@ -1,135 +1,860 @@ -# BEGIN LICENSE BLOCK -# -# Copyright (c) 1996-2003 Jesse Vincent -# -# (Except where explictly superceded by other copyright notices) -# +# BEGIN BPS TAGGED BLOCK {{{ +# +# COPYRIGHT: +# +# This software is Copyright (c) 1996-2014 Best Practical Solutions, LLC +# +# +# (Except where explicitly superseded by other copyright notices) +# +# +# LICENSE: +# # 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 ) -# WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST. -# -# !! DO NOT EDIT THIS FILE !! # +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 or visit their web page on the internet at +# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html. +# +# +# CONTRIBUTION SUBMISSION POLICY: +# +# (The following paragraph is not intended to limit the rights granted +# to you to modify and distribute this software under the terms of +# the GNU General Public License and is only of importance to you if +# you choose to contribute your changes and enhancements to the +# community by submitting them to Best Practical Solutions, LLC.) +# +# By intentionally submitting any modifications, corrections or +# derivatives to this work, or any other work intended for use with +# Request Tracker, to Best Practical Solutions, LLC, you confirm that +# you are the copyright holder for those contributions and you grant +# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable, +# royalty-free, perpetual, license to use, copy, create derivative +# works based on those contributions, and sublicense and distribute +# those contributions and any derivatives thereof. +# +# END BPS TAGGED BLOCK }}} + +=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 + + + +=cut + + +package RT::Attachment; +use base 'RT::Record'; + +sub Table {'Attachments'} + + + use strict; +use warnings; + + +use RT::Transaction; +use MIME::Base64; +use MIME::QuotedPrint; +use MIME::Body; +use RT::Util 'mime_recommended_filename'; + +sub _OverlayAccessible { + { + TransactionId => { 'read'=>1, 'public'=>1, 'write' => 0 }, + MessageId => { 'read'=>1, 'write' => 0 }, + Parent => { 'read'=>1, 'write' => 0 }, + ContentType => { 'read'=>1, 'write' => 0 }, + Subject => { 'read'=>1, 'write' => 0 }, + Content => { 'read'=>1, 'write' => 0 }, + ContentEncoding => { 'read'=>1, 'write' => 0 }, + Headers => { 'read'=>1, 'write' => 0 }, + Filename => { 'read'=>1, 'write' => 0 }, + Creator => { 'read'=>1, 'auto'=>1, }, + Created => { 'read'=>1, 'auto'=>1, }, + }; +} +=head2 Create -=head1 NAME +Create a new attachment. Takes a paramhash: + + 'Attachment' Should be a single MIME body with optional subparts + 'Parent' is an optional id of the parent attachment + 'TransactionId' is the mandatory id of the transaction this attachment is associated with.; -RT::Attachment +=cut +sub Create { + my $self = shift; + 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 + unless ( $args{'TransactionId'} ) { + $RT::Logger->crit( "RT::Attachment->Create couldn't, as you didn't specify a transaction" ); + return (0); + } + + # If we possibly can, collapse it to a singlepart + $Attachment->make_singlepart; + + # Get the subject + my $Subject = $Attachment->head->get( 'subject', 0 ); + $Subject = '' unless defined $Subject; + chomp $Subject; + utf8::decode( $Subject ) unless utf8::is_utf8( $Subject ); + + #Get the Message-ID + my $MessageId = $Attachment->head->get( 'Message-ID', 0 ); + defined($MessageId) or $MessageId = ''; + chomp ($MessageId); + $MessageId =~ s/^<(.*?)>$/$1/o; + + #Get the filename + + my $Filename = mime_recommended_filename($Attachment); + + # remove path part. + $Filename =~ s!.*/!! if $Filename; + + # MIME::Head doesn't support perl strings well and can return + # octets which later will be double encoded in low-level code + my $head = $Attachment->head->as_string; + utf8::decode( $head ) unless utf8::is_utf8( $head ); + + # If a message has no bodyhandle, that means that it has subparts (or appears to) + # and we should act accordingly. + unless ( defined $Attachment->bodyhandle ) { + my ($id) = $self->SUPER::Create( + TransactionId => $args{'TransactionId'}, + Parent => $args{'Parent'}, + ContentType => $Attachment->mime_type, + Headers => $head, + MessageId => $MessageId, + Subject => $Subject, + ); + + unless ($id) { + $RT::Logger->crit("Attachment insert failed - ". $RT::Handle->dbh->errstr); + } + + foreach my $part ( $Attachment->parts ) { + my $SubAttachment = RT::Attachment->new( $self->CurrentUser ); + my ($id) = $SubAttachment->Create( + TransactionId => $args{'TransactionId'}, + Parent => $id, + Attachment => $part, + ); + unless ($id) { + $RT::Logger->crit("Attachment insert failed: ". $RT::Handle->dbh->errstr); + } + } + return ($id); + } + + #If it's not multipart + else { + + my ($ContentEncoding, $Body, $ContentType, $Filename) = $self->_EncodeLOB( + $Attachment->bodyhandle->as_string, + $Attachment->mime_type, + $Filename + ); + + my $id = $self->SUPER::Create( + TransactionId => $args{'TransactionId'}, + ContentType => $ContentType, + ContentEncoding => $ContentEncoding, + Parent => $args{'Parent'}, + Headers => $head, + Subject => $Subject, + Content => $Body, + Filename => $Filename, + MessageId => $MessageId, + ); + + unless ($id) { + $RT::Logger->crit("Attachment insert failed: ". $RT::Handle->dbh->errstr); + } + return $id; + } +} -=head1 SYNOPSIS +=head2 Import -=head1 DESCRIPTION +Create an attachment exactly as specified in the named parameters. -=head1 METHODS +=cut + +sub Import { + my $self = shift; + my %args = ( ContentEncoding => 'none', @_ ); + + ( $args{'ContentEncoding'}, $args{'Content'} ) = + $self->_EncodeLOB( $args{'Content'}, $args{'MimeType'} ); + + return ( $self->SUPER::Create(%args) ); +} + +=head2 TransactionObj + +Returns the transaction object asscoiated with this attachment. =cut -package RT::Attachment; -use RT::Record; +sub TransactionObj { + my $self = shift; + + unless ( $self->{_TransactionObj} ) { + $self->{_TransactionObj} = RT::Transaction->new( $self->CurrentUser ); + $self->{_TransactionObj}->Load( $self->TransactionId ); + } + + unless ($self->{_TransactionObj}->Id) { + $RT::Logger->crit( "Attachment ". $self->id + ." can't find transaction ". $self->TransactionId + ." which it is ostensibly part of. That's bad"); + } + return $self->{_TransactionObj}; +} +=head2 ParentObj -use vars qw( @ISA ); -@ISA= qw( RT::Record ); +Returns a parent's L object if this attachment +has a parent, otherwise returns undef. -sub _Init { - my $self = shift; +=cut + +sub ParentObj { + my $self = shift; + return undef unless $self->Parent; - $self->Table('Attachments'); - $self->SUPER::_Init(@_); + my $parent = RT::Attachment->new( $self->CurrentUser ); + $parent->LoadById( $self->Parent ); + return $parent; } +=head2 Children +Returns an L object which is preloaded with +all attachments objects with this attachment's Id as their +C. +=cut + +sub Children { + my $self = shift; + + my $kids = RT::Attachments->new( $self->CurrentUser ); + $kids->ChildrenOf( $self->Id ); + return($kids); +} + +=head2 Content + +Returns the attachment's content. if it's base64 encoded, decode it +before returning it. + +=cut +sub Content { + my $self = shift; + return $self->_DecodeLOB( + $self->ContentType, + $self->ContentEncoding, + $self->_Value('Content', decode_utf8 => 0), + ); +} -=item Create PARAMHASH +=head2 OriginalContent -Create takes a hash of values and creates a row in the database: +Returns the attachment's content as octets before RT's mangling. +Generally this just means restoring text content back to its +original encoding. - 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'. +If the attachment has a C Content-Type, its children attachments +are reconstructed and returned as a string. =cut +sub OriginalContent { + my $self = shift; + # message/* content types represent raw messages. Since we break them + # apart when they come in, we'll reconstruct their child attachments when + # you ask for the OriginalContent of the message/ part. + if ($self->IsMessageContentType) { + # There shouldn't be more than one "subpart" to a message/* attachment + my $child = $self->Children->First; + return $self->Content unless $child and $child->id; + return $child->ContentAsMIME(Children => 1)->as_string; + } + + return $self->Content unless RT::I18N::IsTextualContentType($self->ContentType); + my $enc = $self->OriginalEncoding; + + my $content; + if ( !$self->ContentEncoding || $self->ContentEncoding eq 'none' ) { + $content = $self->_Value('Content', decode_utf8 => 0); + } elsif ( $self->ContentEncoding eq 'base64' ) { + $content = MIME::Base64::decode_base64($self->_Value('Content', decode_utf8 => 0)); + } elsif ( $self->ContentEncoding eq 'quoted-printable' ) { + $content = MIME::QuotedPrint::decode($self->_Value('Content', decode_utf8 => 0)); + } else { + return( $self->loc("Unknown ContentEncoding [_1]", $self->ContentEncoding)); + } + + # Turn *off* the SvUTF8 bits here so decode_utf8 and from_to below can work. + local $@; + Encode::_utf8_off($content); + + if (!$enc || $enc eq '' || $enc eq 'utf8' || $enc eq 'utf-8') { + # If we somehow fail to do the decode, at least push out the raw bits + eval { return( Encode::decode_utf8($content)) } || return ($content); + } + + eval { Encode::from_to($content, 'utf8' => $enc) } if $enc; + if ($@) { + $RT::Logger->error("Could not convert attachment from assumed utf8 to '$enc' :".$@); + } + return $content; +} +=head2 OriginalEncoding -sub Create { +Returns the attachment's original encoding. + +=cut + +sub OriginalEncoding { + my $self = shift; + return $self->GetHeader('X-RT-Original-Encoding'); +} + +=head2 ContentLength + +Returns length of L in bytes. + +=cut + +sub ContentLength { + my $self = shift; + + return undef unless $self->TransactionObj->CurrentUserCanSee; + + my $len = $self->GetHeader('Content-Length'); + unless ( defined $len ) { + use bytes; + no warnings 'uninitialized'; + $len = length($self->Content) || 0; + $self->SetHeader('Content-Length' => $len); + } + return $len; +} + +=head2 Quote + +=cut + +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 (RT::I18N::IsTextualContentType($self->ContentType)) { + $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 = Text::Wrapper->new + ( + 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); +} + +=head2 ContentAsMIME [Children => 1] + +Returns MIME entity built from this attachment. + +If the optional parameter C is set to a true value, the children are +recursively added to the entity. + +=cut + +sub ContentAsMIME { + my $self = shift; + my %opts = ( + Children => 0, + @_ + ); + + my $entity = MIME::Entity->new(); + foreach my $header ($self->SplitHeaders) { + my ($h_key, $h_val) = split /:/, $header, 2; + $entity->head->add( $h_key, RT::Interface::Email::EncodeToMIME( String => $h_val ) ); + } + + # since we want to return original content, let's use original encoding + $entity->head->mime_attr( + "Content-Type.charset" => $self->OriginalEncoding ) + if $self->OriginalEncoding; + + $entity->bodyhandle( + MIME::Body::Scalar->new( $self->OriginalContent ) + ); + + if ($opts{'Children'} and not $self->IsMessageContentType) { + my $children = $self->Children; + while (my $child = $children->Next) { + $entity->make_multipart unless $entity->is_multipart; + $entity->add_part( $child->ContentAsMIME(%opts) ); + } + } + + return $entity; +} + +=head2 IsMessageContentType + +Returns a boolean indicating if the Content-Type of this attachment is a +C subtype. + +=cut + +sub IsMessageContentType { + my $self = shift; + return $self->ContentType =~ m{^\s*message/}i ? 1 : 0; +} + +=head2 Addresses + +Returns a hashref of all addresses related to this attachment. +The keys of the hash are C, C, C, C, C +and C. The values are references to lists of +L objects. + +=cut + +our @ADDRESS_HEADERS = qw(From To Cc Bcc RT-Send-Cc RT-Send-Bcc); + +sub Addresses { 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'}, -); + my %data = (); + my $current_user_address = lc $self->CurrentUser->EmailAddress; + foreach my $hdr (@ADDRESS_HEADERS) { + my @Addresses; + my $line = $self->GetHeader($hdr); + + foreach my $AddrObj ( Email::Address->parse( $line )) { + my $address = $AddrObj->address; + $address = lc RT::User->CanonicalizeEmailAddress($address); + next if $current_user_address eq $address; + next if RT::EmailParser->IsRTAddress($address); + push @Addresses, $AddrObj ; + } + $data{$hdr} = \@Addresses; + } + return \%data; } +=head2 NiceHeaders +Returns a multi-line string of the To, From, Cc, Date and Subject headers. -=item id +=cut + +sub NiceHeaders { + my $self = shift; + my $hdrs = ""; + my @hdrs = $self->_SplitHeaders; + while (my $str = shift @hdrs) { + next unless $str =~ /^(To|From|RT-Send-Cc|Cc|Bcc|Date|Subject):/i; + $hdrs .= $str . "\n"; + $hdrs .= shift( @hdrs ) . "\n" while ($hdrs[0] =~ /^[ \t]+/); + } + return $hdrs; +} + +=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 separated from the ticket update code by +an abstraction barrier that makes it impossible to pass this data directly. + +=cut + +sub Headers { + return join("\n", $_[0]->SplitHeaders); +} + +=head2 EncodedHeaders + +Takes encoding as argument and returns the attachment's headers as octets in encoded +using the encoding. + +This is not protection using quoted printable or base64 encoding. + +=cut + +sub EncodedHeaders { + my $self = shift; + my $encoding = shift || 'utf8'; + return Encode::encode( $encoding, $self->Headers ); +} + +=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 ($self->_SplitHeaders) { + next unless $line =~ /^\Q$tag\E:\s+(.*)$/si; + + #if we find the header, return its value + return ($1); + } + + # we found no header. return an empty string + return undef; +} + +=head2 DelHeader $TAG + +Delete a field from the attachment's headers. -Returns the current value of id. +=cut + +sub DelHeader { + my $self = shift; + my $tag = shift; + + my $newheader = ''; + foreach my $line ($self->_SplitHeaders) { + next if $line =~ /^\Q$tag\E:\s+/i; + $newheader .= "$line\n"; + } + return $self->__Set( Field => 'Headers', Value => $newheader); +} + +=head2 AddHeader $TAG, $VALUE, ... + +Add one or many fields to the attachment's headers. + +=cut + +sub AddHeader { + my $self = shift; + + my $newheader = $self->__Value( 'Headers' ); + while ( my ($tag, $value) = splice @_, 0, 2 ) { + $value = $self->_CanonicalizeHeaderValue($value); + $newheader .= "$tag: $value\n"; + } + return $self->__Set( Field => 'Headers', Value => $newheader); +} + +=head2 SetHeader ( 'Tag', 'Value' ) + +Replace or add a Header to the attachment's headers. + +=cut + +sub SetHeader { + my $self = shift; + my $tag = shift; + my $value = $self->_CanonicalizeHeaderValue(shift); + + my $replaced = 0; + my $newheader = ''; + foreach my $line ( $self->_SplitHeaders ) { + if ( $line =~ /^\Q$tag\E:\s+/i ) { + # replace first instance, skip all the rest + unless ($replaced) { + $newheader .= "$tag: $value\n"; + $replaced = 1; + } + } else { + $newheader .= "$line\n"; + } + } + + $newheader .= "$tag: $value\n" unless $replaced; + $self->__Set( Field => 'Headers', Value => $newheader); +} + +sub _CanonicalizeHeaderValue { + my $self = shift; + my $value = shift; + + $value = '' unless defined $value; + $value =~ s/\s+$//s; + $value =~ s/\r*\n/\n /g; + + return $value; +} + +=head2 SplitHeaders + +Returns an array of this attachment object's headers, with one header +per array entry. Multiple lines are folded. + +B returns C field. + +=cut + +sub SplitHeaders { + my $self = shift; + return (grep !/^RT-Send-Bcc/i, $self->_SplitHeaders(@_) ); +} + +=head2 _SplitHeaders + +Returns an array of this attachment object's headers, with one header +per array entry. multiple lines are folded. + + +=cut + +sub _SplitHeaders { + my $self = shift; + my $headers = (shift || $self->_Value('Headers')); + my @headers; + # XXX TODO: splitting on \n\w is _wrong_ as it treats \n[ as a valid + # continuation, which it isn't. The correct split pattern, per RFC 2822, + # is /\n(?=[^ \t]|\z)/. That is, only "\n " or "\n\t" is a valid + # continuation. Older values of X-RT-GnuPG-Status contain invalid + # continuations and rely on this bogus split pattern, however, so it is + # left as-is for now. + for (split(/\n(?=\w|\z)/,$headers)) { + push @headers, $_; + + } + return(@headers); +} + + +sub Encrypt { + my $self = shift; + + my $txn = $self->TransactionObj; + return (0, $self->loc('Permission Denied')) unless $txn->CurrentUserCanSee; + return (0, $self->loc('Permission Denied')) + unless $txn->TicketObj->CurrentUserHasRight('ModifyTicket'); + return (0, $self->loc('GnuPG integration is disabled')) + unless RT->Config->Get('GnuPG')->{'Enable'}; + return (0, $self->loc('Attachments encryption is disabled')) + unless RT->Config->Get('GnuPG')->{'AllowEncryptDataInDB'}; + + require RT::Crypt::GnuPG; + + my $type = $self->ContentType; + if ( $type =~ /^x-application-rt\/gpg-encrypted/i ) { + return (1, $self->loc('Already encrypted')); + } elsif ( $type =~ /^multipart\//i ) { + return (1, $self->loc('No need to encrypt')); + } else { + $type = qq{x-application-rt\/gpg-encrypted; original-type="$type"}; + } + + my $queue = $txn->TicketObj->QueueObj; + my $encrypt_for; + foreach my $address ( grep $_, + $queue->CorrespondAddress, + $queue->CommentAddress, + RT->Config->Get('CorrespondAddress'), + RT->Config->Get('CommentAddress'), + ) { + my %res = RT::Crypt::GnuPG::GetKeysInfo( $address, 'private' ); + next if $res{'exit_code'} || !$res{'info'}; + %res = RT::Crypt::GnuPG::GetKeysForEncryption( $address ); + next if $res{'exit_code'} || !$res{'info'}; + $encrypt_for = $address; + } + unless ( $encrypt_for ) { + return (0, $self->loc('No key suitable for encryption')); + } + + $self->__Set( Field => 'ContentType', Value => $type ); + $self->SetHeader( 'Content-Type' => $type ); + + my $content = $self->Content; + my %res = RT::Crypt::GnuPG::SignEncryptContent( + Content => \$content, + Sign => 0, + Encrypt => 1, + Recipients => [ $encrypt_for ], + ); + if ( $res{'exit_code'} ) { + return (0, $self->loc('GnuPG error. Contact with administrator')); + } + + my ($status, $msg) = $self->__Set( Field => 'Content', Value => $content ); + unless ( $status ) { + return ($status, $self->loc("Couldn't replace content with encrypted data: [_1]", $msg)); + } + return (1, $self->loc('Successfuly encrypted data')); +} + +sub Decrypt { + my $self = shift; + + my $txn = $self->TransactionObj; + return (0, $self->loc('Permission Denied')) unless $txn->CurrentUserCanSee; + return (0, $self->loc('Permission Denied')) + unless $txn->TicketObj->CurrentUserHasRight('ModifyTicket'); + return (0, $self->loc('GnuPG integration is disabled')) + unless RT->Config->Get('GnuPG')->{'Enable'}; + + require RT::Crypt::GnuPG; + + my $type = $self->ContentType; + if ( $type =~ /^x-application-rt\/gpg-encrypted/i ) { + ($type) = ($type =~ /original-type="(.*)"/i); + $type ||= 'application/octet-stream'; + } else { + return (1, $self->loc('Is not encrypted')); + } + $self->__Set( Field => 'ContentType', Value => $type ); + $self->SetHeader( 'Content-Type' => $type ); + + my $content = $self->Content; + my %res = RT::Crypt::GnuPG::DecryptContent( Content => \$content, ); + if ( $res{'exit_code'} ) { + return (0, $self->loc('GnuPG error. Contact with administrator')); + } + + my ($status, $msg) = $self->__Set( Field => 'Content', Value => $content ); + unless ( $status ) { + return ($status, $self->loc("Couldn't replace content with decrypted data: [_1]", $msg)); + } + return (1, $self->loc('Successfuly decrypted data')); +} + +=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' ) ) { + return ( $self->__Value( $field, @_ ) ); + } + + return undef unless $self->TransactionObj->CurrentUserCanSee; + return $self->__Value( $field, @_ ); +} + +# Transactions don't change. by adding this cache congif directiove, +# we don't lose pathalogically on long tickets. +sub _CacheConfig { + { + 'cache_p' => 1, + 'fast_update_p' => 1, + 'cache_for_sec' => 180, + } +} + + + + +=head2 id + +Returns the current value of id. (In the database, id is stored as int(11).) =cut -=item TransactionId +=head2 TransactionId -Returns the current value of TransactionId. +Returns the current value of TransactionId. (In the database, TransactionId is stored as int(11).) -=item SetTransactionId VALUE +=head2 SetTransactionId VALUE -Set TransactionId to 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).) @@ -137,17 +862,17 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure. =cut -=item Parent +=head2 Parent -Returns the current value of Parent. +Returns the current value of Parent. (In the database, Parent is stored as int(11).) -=item SetParent VALUE +=head2 SetParent VALUE -Set Parent to VALUE. +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).) @@ -155,17 +880,17 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure. =cut -=item MessageId +=head2 MessageId -Returns the current value of MessageId. +Returns the current value of MessageId. (In the database, MessageId is stored as varchar(160).) -=item SetMessageId VALUE +=head2 SetMessageId VALUE -Set MessageId to VALUE. +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).) @@ -173,17 +898,17 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure. =cut -=item Subject +=head2 Subject -Returns the current value of Subject. +Returns the current value of Subject. (In the database, Subject is stored as varchar(255).) -=item SetSubject VALUE +=head2 SetSubject VALUE -Set Subject to VALUE. +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).) @@ -191,17 +916,17 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure. =cut -=item Filename +=head2 Filename -Returns the current value of Filename. +Returns the current value of Filename. (In the database, Filename is stored as varchar(255).) -=item SetFilename VALUE +=head2 SetFilename VALUE -Set Filename to 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).) @@ -209,17 +934,17 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure. =cut -=item ContentType +=head2 ContentType -Returns the current value of ContentType. +Returns the current value of ContentType. (In the database, ContentType is stored as varchar(80).) -=item SetContentType VALUE +=head2 SetContentType VALUE -Set ContentType to VALUE. +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).) @@ -227,17 +952,17 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure. =cut -=item ContentEncoding +=head2 ContentEncoding -Returns the current value of ContentEncoding. +Returns the current value of ContentEncoding. (In the database, ContentEncoding is stored as varchar(80).) -=item SetContentEncoding VALUE +=head2 SetContentEncoding VALUE -Set ContentEncoding to VALUE. +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).) @@ -245,35 +970,35 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure. =cut -=item Content +=head2 Content -Returns the current value of Content. -(In the database, Content is stored as longtext.) +Returns the current value of Content. +(In the database, Content is stored as longblob.) -=item SetContent VALUE +=head2 SetContent VALUE -Set Content to VALUE. +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.) +(In the database, Content will be stored as a longblob.) =cut -=item Headers +=head2 Headers -Returns the current value of Headers. +Returns the current value of Headers. (In the database, Headers is stored as longtext.) -=item SetHeaders VALUE +=head2 SetHeaders VALUE -Set Headers to 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.) @@ -281,18 +1006,18 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure. =cut -=item Creator +=head2 Creator -Returns the current value of Creator. +Returns the current value of Creator. (In the database, Creator is stored as int(11).) =cut -=item Created +=head2 Created -Returns the current value of Created. +Returns the current value of Created. (In the database, Created is stored as datetime.) @@ -300,73 +1025,37 @@ Returns the current value of Created. -sub _ClassAccessible { +sub _CoreAccessible { { - + 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 => ''}, + {read => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''}, + TransactionId => + {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''}, + Parent => + {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, + MessageId => + {read => 1, write => 1, sql_type => 12, length => 160, is_blob => 0, is_numeric => 0, type => 'varchar(160)', default => ''}, + Subject => + {read => 1, write => 1, sql_type => 12, length => 255, is_blob => 0, is_numeric => 0, type => 'varchar(255)', default => ''}, + Filename => + {read => 1, write => 1, sql_type => 12, length => 255, is_blob => 0, is_numeric => 0, type => 'varchar(255)', default => ''}, + ContentType => + {read => 1, write => 1, sql_type => 12, length => 80, is_blob => 0, is_numeric => 0, type => 'varchar(80)', default => ''}, + ContentEncoding => + {read => 1, write => 1, sql_type => 12, length => 80, is_blob => 0, is_numeric => 0, type => 'varchar(80)', default => ''}, + Content => + {read => 1, write => 1, sql_type => -4, length => 0, is_blob => 1, is_numeric => 0, type => 'longblob', default => ''}, + Headers => + {read => 1, write => 1, sql_type => -4, length => 0, is_blob => 1, is_numeric => 0, type => 'longtext', default => ''}, + Creator => + {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, + Created => + {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, 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 - +RT::Base->_ImportOverlays(); 1;