Attaching common files without storing in RT database

From Request Tracker Wiki
Revision as of 04:39, 2 October 2011 by 98.225.26.222 (talk) (→‎Step 1.)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search

This code has been tested on RT4 only.

Some organizations send the same attachments to requestors over and over again. PDF forms and documents are commonly included as responses to customer tickets, but sending the same file over and over takes up space in the RT database. The easiest and recommend solution to this problem is to send a link to the document instead sending a copy of the document itself.

For cases where this solution is unacceptable I have developed a customization that saves space by storing each attachment only once in RT's database. This customization involves creating 3 callbacks, replacing one RT component, and overriding on RT library method. (It's not as hard as it sounds.)

Step 1.

On the ticket update page we will need to be able to select existing attachments for including as part of the response. There a a number of ways you could do this but here we simply have a specific RT ticket that has the attachments included. So create a ticket and attach all your common attachments to it. Then create the callback local/html/Callbacks/attach_common/Ticket/Update.html/AfterWorked with this content:

% if (keys %documents) {

Attach Common Files:

% while (my ($k,$v) = each %documents) {
%   for my $rev ( @$v ) {
%     my $desc = loc("[_1] by [_2]", $rev->CreatedAsString, $m->scomp('/Elements/ShowUser', User => $rev->CreatorObj));
%     my $selected = (grep { $rev->id == $_ } @$ExistingAttachments ) ? 'checked' : ;
   /><%$k%> (<% $desc|n %>)

%     }
%   }

 % } <%INIT> my $Ticket = RT::Ticket->new($RT::SystemUser); $Ticket->Load(123); my $Attachments = $m->comp('/Ticket/Elements/FindAttachments', Ticket => $Ticket); if ($ExistingAttachments && !ref($ExistingAttachments)) { $ExistingAttachments = [ $ExistingAttachments ]; } my %documents; while ( my $attach = $Attachments->Next() ) { next unless ($attach->Filename()); unshift( @{ $documents{ $attach->Filename } }, $attach ); } <%ARGS> $ExistingAttachments => [] In this code "123" is the number of our ticket with common attachments. Now this ticket update page will have a list of checkboxes in the right column that will allow the user to select files to attach.

Step 2.

In this step we need to get RT to save a pointer to the attachment in the ticket transaction instead of the actual attachment. This is accomplished by saving each attachment selected in Step 1 as an attachment with a ContentType of "meta/attachment" and the Content being id of the attachment in the database. Create the callback local/html/Callbacks/attach_common/Ticket/Update.html/BeforeDisplay with this content:

my $existing = $ARGSRef->{ExistingAttachments} || [];
if (!ref($existing)) {
    $existing = [$existing];
}
for my $e (@$existing) {
    my $attach = RT::Attachment->new($RT::SystemUser);
    $attach->Load($e);
    my $file_path = Encode::decode_utf8($attach->Filename);
    my $attachment = MIME::Entity->build(Type => 'meta/attachment', Data => [$e]);
    
    $session{'Attachments'} = {
        %{$session{'Attachments'} || {}},
        $file_path => $attachment,
    };
}

<%ARGS>
$ARGSRef


Step 3.

When RT displays a ticket's transaction history (which includes attachments) it finds all the attachments to display them. We create another callback called local/html/Callbacks/attach_common/Ticket/Elements/ShowTransaction/AfterDescription to replace those search results with a new list of attachments, substituting the meta attachments with the actual attachments using this callback code:

<%PERL>
if (ref($Attachments) && @$Attachments) {
    my $new_attachments = [];
    for my $a (@$Attachments) {
        if ($a->ContentType eq "meta/attachment") {
            my $parent = $a->Parent;
            $a->Load($a->Content);
            $a->{values}{parent} = $parent;
        }
        push @$new_attachments, $a;
    }
    $ARGS{Attachments} = $new_attachments;
}

<%ARGS>
$Attachments => []

Step 4.

RT will complain if someone tries to access an attachment with the wrong transaction number so we copy the file share/html/Ticket/Elements/ShowTransactionAttachments to local/html/Ticket/Elements/ShowTransactionAttachments and replace all occurences of "$Transaction->Id" with "$message->TransactionId".

Step 5.

The last step is to make sure that when RT sends email it includes the actual attachment instead of our meta attachment. Create the file local/lib/RT/Action/SendEmail_Local.pm with this content:

package RT::Action::SendEmail;

use strict;
no warnings qw(redefine);

sub AddAttachment {
    my $self    = shift;
    my $attach  = shift;
    my $MIMEObj = shift || $self->TemplateObj->MIMEObj;

    $attach->Load($attach->Content)
        if ($attach->ContentType eq 'meta/attachment');

    $MIMEObj->attach(
        Type     => $attach->ContentType,
        Charset  => $attach->OriginalEncoding,
        Data     => $attach->OriginalContent,
        Filename => $self->MIMEEncodeString( $attach->Filename ),
        'RT-Attachment:' => $self->TicketObj->Id . "/"
            . $self->TransactionObj->Id . "/"
            . $attach->id,
        Encoding => '-SUGGEST',
    );
}

1;

Done!


NOTE: Though attachments will appear in the ticket history they will not appear in the "Attachments" box. This could be fixed.