[Rt-commit] rt branch, 4.2/web-attachs-processing, created. rt-4.1.6-405-g8a046fd

Ruslan Zakirov ruz at bestpractical.com
Sat Mar 30 09:36:06 EDT 2013


The branch, 4.2/web-attachs-processing has been created
        at  8a046fd26d46ecf4e00063bf38af978239ecda3b (commit)

- Log -----------------------------------------------------------------
commit 2355a694f251baecf00e2513ee8de8479d669278
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Thu Jan 20 14:47:22 2011 +0300

    add tokens to update/create actions to distinguish attachments

diff --git a/share/html/Search/Bulk.html b/share/html/Search/Bulk.html
index aa5a725..dd711df 100644
--- a/share/html/Search/Bulk.html
+++ b/share/html/Search/Bulk.html
@@ -226,10 +226,12 @@ map ( $ARGS{$_} =~ /^$/ && ( delete $ARGS{$_} ), keys %ARGS );
 
 my (@results);
 
+$ARGS{'Token'} ||= $Token ||= Digest::MD5::md5_hex( rand(1024) );
+
 # deal with deleting uploaded attachments
 foreach my $key (keys %ARGS) {
     if ($key =~ m/^DeleteAttach-(.+)$/) {
-        delete $session{'Attachments'}{$1};
+        delete $session{'Attachments'}{ $Token }{ $1 };
     }
     $session{'Attachments'} = { %{$session{'Attachments'} || {}} };
 }
@@ -241,15 +243,14 @@ if ( defined $ARGS{'Attach'} && length $ARGS{'Attach'} ) { # attachment?
     );
 
     my $file_path = Encode::decode_utf8("$ARGS{'Attach'}");
-    $session{'Attachments'} = {
-        %{$session{'Attachments'} || {}},
-        $file_path => $attachment,
-    };
+    # force session save
+    $session{'Attachments'} = $session{'Attachments'} || {};
+    $session{'Attachments'}{ $Token }{ $file_path } = $attachment;
 }
 
 # delete temporary storage entry to make WebUI clean
-unless (keys %{$session{'Attachments'}} and $ARGS{'UpdateAttach'}) {
-    delete $session{'Attachments'};
+unless (keys %{$session{'Attachments'}{ $Token }} and $ARGS{'UpdateAttach'}) {
+    delete $session{'Attachments'}{ $Token };
 }
 
 $Page ||= 1;
@@ -301,7 +302,8 @@ $Tickets->RedoSearch();
 
 unless ( $ARGS{'AddMoreAttach'} ) {
     # Add session attachments if any to be processed by ProcessUpdateMessage
-    $ARGS{'UpdateAttachments'} = $session{'Attachments'} if ( $session{'Attachments'} );
+    $ARGS{'UpdateAttachments'} = $session{'Attachments'}{ $Token }
+        if $session{'Attachments'}{ $Token };
 
     while ( my $Ticket = $Tickets->Next ) {
         next unless ( $ARGS{ "UpdateTicket" . $Ticket->Id } );
@@ -384,7 +386,7 @@ unless ( $ARGS{'AddMoreAttach'} ) {
     }
 
     # Cleanup WebUI
-    delete $session{'Attachments'};
+    delete $session{'Attachments'}{ $Token };
 }
 
 my $TxnCFs = RT::CustomFields->new( $session{CurrentUser} );
@@ -393,6 +395,7 @@ $TxnCFs->LimitToGlobalOrObjectId( sort keys %queues );
 
 </%INIT>
 <%args>
+$Token => undef
 $Format => undef
 $Page => 1
 $Rows => undef
diff --git a/share/html/SelfService/Display.html b/share/html/SelfService/Display.html
index 23d1c91..40e3092 100644
--- a/share/html/SelfService/Display.html
+++ b/share/html/SelfService/Display.html
@@ -98,17 +98,14 @@ my $Ticket = RT::Ticket->new( $session{'CurrentUser'} );
 
 # store the uploaded attachment in session
 if ( defined $ARGS{'Attach'} && length $ARGS{'Attach'} ) { # attachment?
-    $session{'Attachments'} = {} unless defined $session{'Attachments'};
-
     my $attachment = MakeMIMEEntity(
         AttachmentFieldName => 'Attach'
     );
 
     my $file_path = Encode::decode_utf8("$ARGS{'Attach'}");
-    $session{'Attachments'} = {
-        %{ $session{'Attachments'} || {} },
-        $file_path => $attachment,
-    };
+    # force session save
+    $session{'Attachments'} = $session{'Attachments'} || {};
+    $session{'Attachments'}{ $Token }{ $file_path } = $attachment;
 }
 
 if ( defined ($id[0]) && $id[0] eq 'new' ) {
@@ -130,7 +127,7 @@ if ( defined ($id[0]) && $id[0] eq 'new' ) {
 
 
     ( $Ticket, @results ) =
-    CreateTicket( Attachments => $session{'Attachments'}, %ARGS );
+    CreateTicket( Attachments => $session{'Attachments'}{$Token}, %ARGS );
 
     unless ( $Ticket->id ) {
         $m->comp( 'Error.html', Why => join( "\n", @results ));
@@ -139,8 +136,8 @@ if ( defined ($id[0]) && $id[0] eq 'new' ) {
 
 
         # delete temporary storage entry to make WebUI clean
-        unless ( keys %{ $session{'Attachments'} } and $ARGS{'UpdateAttach'} ) {
-            delete $session{'Attachments'};
+        unless ( keys %{ $session{'Attachments'}{ $Token } } and $ARGS{'UpdateAttach'} ) {
+            delete $session{'Attachments'}{ $Token };
         }
 
     }
@@ -155,24 +152,24 @@ if ( defined ($id[0]) && $id[0] eq 'new' ) {
 
 
     if (
-        $session{'Attachments'}
+        $session{'Attachments'}{ $Token }
         || ( defined $ARGS{'UpdateContent'}
             && $ARGS{'UpdateContent'} ne ''
             && $ARGS{'UpdateContent'} ne "-- \n"
             . $session{'CurrentUser'}->UserObj->Signature )
       )
     {
-        $ARGS{UpdateAttachments} = $session{'Attachments'};
+        $ARGS{UpdateAttachments} = $session{'Attachments'}{ $Token };
     }
     push @results, ProcessUpdateMessage(
         ARGSRef   => \%ARGS,
         TicketObj => $Ticket
     );
-            delete $session{'Attachments'};
+            delete $session{'Attachments'}{ $Token };
 
     # delete temporary storage entry to make WebUI clean
-    unless ( keys %{ $session{'Attachments'} } and $ARGS{'UpdateAttach'} ) {
-        delete $session{'Attachments'};
+    unless ( keys %{ $session{'Attachments'}{ $Token } } and $ARGS{'UpdateAttach'} ) {
+        delete $session{'Attachments'}{ $Token };
     }
 
     my @cfupdates = ProcessObjectCustomFieldUpdates(Object => $Ticket, ARGSRef => \%ARGS);
@@ -219,4 +216,5 @@ if ( defined ($id[0]) && $id[0] eq 'new' ) {
 
 <%ARGS>
 $id => undef
+$Token => ''
 </%ARGS>
diff --git a/share/html/Ticket/Create.html b/share/html/Ticket/Create.html
index 1c78de4..4042b48 100644
--- a/share/html/Ticket/Create.html
+++ b/share/html/Ticket/Create.html
@@ -54,6 +54,7 @@
 
 <form action="<% RT->Config->Get('WebPath') %>/Ticket/Create.html" method="post" enctype="multipart/form-data" name="TicketCreate">
   <input type="hidden" class="hidden" name="id" value="new" />
+  <input type="hidden" class="hidden" name="Token" value="<% $Token %>" />
   
 % $m->callback( CallbackName => 'FormStart', QueueObj => $QueueObj, ARGSRef => \%ARGS );
 
@@ -375,10 +376,12 @@ $QueueObj->Disabled && Abort(loc("Cannot create tickets in a disabled queue."));
 
 my $ticket = RT::Ticket->new($session{'CurrentUser'}); # empty ticket object
 
+$ARGS{'Token'} ||= $Token ||= Digest::MD5::md5_hex( rand(1024) );
+
 # deal with deleting uploaded attachments
 foreach my $key (keys %ARGS) {
     if ($key =~ m/^DeleteAttach-(.+)$/) {
-        delete $session{'Attachments'}{$1};
+        delete $session{'Attachments'}{ $Token }{$1};
     }
     $session{'Attachments'} = { %{$session{'Attachments'} || {}} };
 }
@@ -390,15 +393,13 @@ if ( defined $ARGS{'Attach'} && length $ARGS{'Attach'} ) { # attachment?
     );
 
     my $file_path = Encode::decode_utf8("$ARGS{'Attach'}");
-    $session{'Attachments'} = {
-        %{$session{'Attachments'} || {}},
-        $file_path => $attachment,
-    };
+    $session{'Attachments'} = $session{'Attachments'} || {};
+    $session{'Attachments'}{ $Token }{ $file_path } = $attachment;
 }
 
 # delete temporary storage entry to make WebUI clean
-unless (keys %{$session{'Attachments'}} and $ARGS{'id'} eq 'new') {
-    delete $session{'Attachments'};
+unless (keys %{$session{'Attachments'}{$Token}} and $ARGS{'id'} eq 'new') {
+    delete $session{'Attachments'}{$Token};
 }
 
 my $checks_failure = 0;
@@ -466,6 +467,7 @@ PageMenu->child( details => raw_html =>  q[<a href="#details" onclick="return sw
 </%INIT>
 
 <%ARGS>
+$Token => undef
 $DependsOn => undef
 $DependedOnBy => undef
 $MemberOf => undef
diff --git a/share/html/Ticket/Display.html b/share/html/Ticket/Display.html
index 205f637..147739c 100644
--- a/share/html/Ticket/Display.html
+++ b/share/html/Ticket/Display.html
@@ -133,7 +133,7 @@ if ($ARGS{'id'} eq 'new') {
     }
 
     ($TicketObj, @Actions) = CreateTicket(
-        Attachments => delete $session{'Attachments'},
+        Attachments => delete $session{'Attachments'}{ $ARGS{'Token'} || '' },
         %ARGS,
     );
     unless ( $TicketObj->CurrentUserHasRight('ShowTicket') ) {
@@ -165,14 +165,13 @@ if ($ARGS{'id'} eq 'new') {
                 ARGSRef => \%ARGS, 
                 Actions => \@Actions);
         
-        $ARGS{UpdateAttachments} = $session{'Attachments'};
+        $ARGS{UpdateAttachments} = delete $session{'Attachments'}{ $ARGS{'Token'} || '' };
         push @Actions,
             ProcessUpdateMessage(
             ARGSRef   => \%ARGS,
             Actions   => \@Actions,
             TicketObj => $TicketObj,
             );
-        delete $session{'Attachments'};
 
         #Process status updates
         push @Actions, ProcessTicketWatchers(ARGSRef => \%ARGS, TicketObj => $TicketObj );
diff --git a/share/html/Ticket/Elements/AddAttachments b/share/html/Ticket/Elements/AddAttachments
index eefa38b..a387e93 100644
--- a/share/html/Ticket/Elements/AddAttachments
+++ b/share/html/Ticket/Elements/AddAttachments
@@ -45,11 +45,11 @@
 %# those contributions and any derivatives thereof.
 %#
 %# END BPS TAGGED BLOCK }}}
-% if (exists $session{'Attachments'}) {
+% if ( $attachments ) {
 <tr><td class="label"><&|/l&>Attached file</&>:</td>
 <td>
 <&|/l&>Check box to delete</&><br />
-% foreach my $attach_name (keys %{$session{'Attachments'}}) {
+% foreach my $attach_name ( keys %$attachments ) {
 <input type="checkbox" class="checkbox" id="DeleteAttach-<%$attach_name%>" name="DeleteAttach-<%$attach_name%>" value="1" />
 <label for="DeleteAttach-<%$attach_name%>"><%$attach_name%></label>
 <br />
@@ -61,3 +61,12 @@
 <tr><td class="label"><&|/l&>Attach</&>:</td><td><input name="Attach" type="file" /><input type="submit" class="button" name="AddMoreAttach" value="<&|/l&>Add More Files</&>" /><input type="hidden" class="hidden" name="UpdateAttach" value="1" />
 </td></tr>
 % $m->callback( %ARGS, CallbackName => 'End' );
+<%ARGS>
+$Token => ''
+</%ARGS>
+<%INIT>
+my $attachments;
+if ( exists $session{'Attachments'}{ $Token } && keys %{ $session{'Attachments'}{ $Token } } ) {
+    $attachments = $session{'Attachments'}{ $Token };
+}
+</%INIT>
diff --git a/share/html/Ticket/ModifyAll.html b/share/html/Ticket/ModifyAll.html
index cb2c063..88739d0 100644
--- a/share/html/Ticket/ModifyAll.html
+++ b/share/html/Ticket/ModifyAll.html
@@ -146,10 +146,12 @@ $CanRespond = 1 if ( $Ticket->CurrentUserHasRight('ReplyToTicket') or
 $CanComment = 1 if ( $Ticket->CurrentUserHasRight('CommentOnTicket') or
                      $Ticket->CurrentUserHasRight('ModifyTicket') );
 
+$ARGS{'Token'} ||= $Token ||= Digest::MD5::md5_hex( rand(1024) );
+
 # deal with deleting uploaded attachments
 foreach my $key (keys %ARGS) {
     if ($key =~ m/^DeleteAttach-(.+)$/) {
-        delete $session{'Attachments'}{$1};
+        delete $session{'Attachments'}{$Token}{$1};
     }
     $session{'Attachments'} = { %{$session{'Attachments'} || {}} };
 }
@@ -161,15 +163,14 @@ if ( defined $ARGS{'Attach'} && length $ARGS{'Attach'} ) { # attachment?
     );
 
     my $file_path = Encode::decode_utf8("$ARGS{'Attach'}");
-    $session{'Attachments'} = {
-        %{$session{'Attachments'} || {}},
-        $file_path => $attachment,
-    };
+    # force session save
+    $session{'Attachments'} = $session{'Attachments'} || {};
+    $session{'Attachments'}{ $Token }{ $file_path } = $attachment;
 }
 
 # delete temporary storage entry to make WebUI clean
-unless (keys %{$session{'Attachments'}} and $ARGS{'UpdateAttach'}) {
-    delete $session{'Attachments'};
+unless (keys %{$session{'Attachments'}{$Token}} and $ARGS{'UpdateAttach'}) {
+    delete $session{'Attachments'}{ $Token };
 }
 
 
@@ -210,10 +211,11 @@ unless ($skip_update or $OnlySearchForPeople or $OnlySearchForGroup or $ARGS{'Ad
     push @results, ProcessTicketDates( TicketObj => $Ticket, ARGSRef => \%ARGS);
     
     # Add session attachments if any to be processed by ProcessUpdateMessage
-    $ARGS{'UpdateAttachments'} = $session{'Attachments'} if ( $session{'Attachments'} );
+    $ARGS{'UpdateAttachments'} = $session{'Attachments'}{ $Token }
+        if $session{'Attachments'}{ $Token };
     push @results, ProcessUpdateMessage( TicketObj => $Ticket, ARGSRef=>\%ARGS );
     # Cleanup WebUI
-    delete $session{'Attachments'};
+    delete $session{'Attachments'}{ $Token };
 
     push @results, ProcessTicketBasics( TicketObj => $Ticket, ARGSRef => \%ARGS );
     push @results, ProcessTicketLinks( TicketObj => $Ticket, ARGSRef => \%ARGS);
@@ -242,6 +244,7 @@ unless ($Ticket->CurrentUserHasRight('ShowTicket')) {
 
 
 <%ARGS>
+$Token => undef
 $OnlySearchForPeople => undef
 $OnlySearchForGroup => undef
 $UserField => undef
diff --git a/share/html/Ticket/Update.html b/share/html/Ticket/Update.html
index 706a62a..bf760e2 100644
--- a/share/html/Ticket/Update.html
+++ b/share/html/Ticket/Update.html
@@ -57,6 +57,7 @@
 <input type="hidden" class="hidden" name="QuoteTransaction" value="<% $ARGS{QuoteTransaction}||'' %>" />
 <input type="hidden" class="hidden" name="DefaultStatus" value="<% $DefaultStatus ||''%>" />
 <input type="hidden" class="hidden" name="Action" value="<% $ARGS{Action}||'' %>" />
+<input type="hidden" class="hidden" name="Token" value="<% $Token %>" />
 
 <& /Elements/GnuPG/SignEncryptWidget:ShowIssues, self => $gnupg_widget &>
 
@@ -240,11 +241,12 @@ $CanRespond = 1 if ( $TicketObj->CurrentUserHasRight('ReplyToTicket') or
 $CanComment = 1 if ( $TicketObj->CurrentUserHasRight('CommentOnTicket') or
                      $TicketObj->CurrentUserHasRight('ModifyTicket') ); 
 
+$ARGS{'Token'} ||= $Token ||= Digest::MD5::md5_hex( rand(1024) );
 
 # deal with deleting uploaded attachments
 foreach my $key (keys %ARGS) {
     if ($key =~ m/^DeleteAttach-(.+)$/) {
-        delete $session{'Attachments'}{$1};
+        delete $session{'Attachments'}{$Token}{$1};
     }
     $session{'Attachments'} = { %{$session{'Attachments'} || {}} };
 }
@@ -256,15 +258,14 @@ if ( defined $ARGS{'Attach'} && length $ARGS{'Attach'} ) { # attachment?
     );
 
     my $file_path = Encode::decode_utf8("$ARGS{'Attach'}");
-    $session{'Attachments'} = {
-        %{$session{'Attachments'} || {}},
-        $file_path => $attachment,
-    };
+    # force session save
+    $session{'Attachments'} = $session{'Attachments'} || {};
+    $session{'Attachments'}{ $Token }{ $file_path } = $attachment;
 }
 
 # delete temporary storage entry to make WebUI clean
-unless (keys %{$session{'Attachments'}} and $ARGS{'UpdateAttach'}) {
-    delete $session{'Attachments'};
+unless (keys %{$session{'Attachments'}{$Token}} and $ARGS{'UpdateAttach'}) {
+    delete $session{'Attachments'}{ $Token };
 }
 
 my $gnupg_widget = $m->comp('/Elements/GnuPG/SignEncryptWidget:new', Arguments => \%ARGS );
@@ -328,4 +329,5 @@ if ( !$checks_failure && !$skip_update && exists $ARGS{SubmitTicket} ) {
 $id => undef
 $Action => undef
 $DefaultStatus => undef
+$Token => undef
 </%ARGS>

commit 8c06eb6986990bdf16fae7e7fda3491510490118
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Sat Jan 22 02:34:55 2011 +0300

    process attachments in CreateTicket and UpdateMessage

diff --git a/lib/RT/Interface/Web.pm b/lib/RT/Interface/Web.pm
index 0e1a70e..5c5b8c8 100644
--- a/lib/RT/Interface/Web.pm
+++ b/lib/RT/Interface/Web.pm
@@ -1864,18 +1864,21 @@ sub CreateTicket {
         Interface => RT::Interface::Web::MobileClient() ? 'Mobile' : 'Web',
     );
 
-    if ( $ARGS{'Attachments'} ) {
-        my $rv = $MIMEObj->make_multipart;
-        $RT::Logger->error("Couldn't make multipart message")
-            if !$rv || $rv !~ /^(?:DONE|ALREADY)$/;
+    my @attachments;
+    if ( my $tmp = $session{'Attachments'}{ $ARGS{'Token'} || '' } ) {
+        push @attachments, grep $_, values %$tmp;
 
-        foreach ( values %{ $ARGS{'Attachments'} } ) {
-            unless ($_) {
-                $RT::Logger->error("Couldn't add empty attachemnt");
-                next;
-            }
-            $MIMEObj->add_part($_);
-        }
+        delete $session{'Attachments'}{ $ARGS{'Token'} || '' }
+            unless $ARGS{'KeepAttachments'};
+        $session{'Attachment'} = $session{'Attachment'}
+            if @attachments;
+    }
+    if ( $ARGS{'Attachments'} ) {
+        push @attachments, grep $_, values %{ $ARGS{'Attachments'} };
+    }
+    if ( @attachments ) {
+        $MIMEObj->make_multipart;
+        $MIMEObj->add_part( $_ ) foreach @attachments;
     }
 
     for my $argument (qw(Encrypt Sign)) {
@@ -2045,9 +2048,21 @@ sub ProcessUpdateMessage {
         );
     }
 
-    if ( $args{ARGSRef}->{'UpdateAttachments'} ) {
+    my @attachments;
+    if ( my $tmp = $session{'Attachments'}{ $args{'ARGSRef'}{'Token'} || '' } ) {
+        push @attachments, grep $_, values %$tmp;
+
+        delete $session{'Attachments'}{ $args{'ARGSRef'}{'Token'} || '' }
+            unless $args{'KeepAttachments'};
+        $session{'Attachment'} = $session{'Attachment'}
+            if @attachments;
+    }
+    if ( $args{ARGSRef}{'UpdateAttachments'} ) {
+        push @attachments, grep $_, values %{ $args{ARGSRef}{'UpdateAttachments'} };
+    }
+    if ( @attachments ) {
         $Message->make_multipart;
-        $Message->add_part($_) foreach values %{ $args{ARGSRef}->{'UpdateAttachments'} };
+        $Message->add_part( $_ ) foreach @attachments;
     }
 
     if ( $args{ARGSRef}->{'AttachTickets'} ) {

commit b367ea15476cce674535910623373c59631d0932
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Sat Jan 22 02:36:38 2011 +0300

    ProcessAttachments function in ::Interface::Web

diff --git a/lib/RT/Interface/Web.pm b/lib/RT/Interface/Web.pm
index 5c5b8c8..7080815 100644
--- a/lib/RT/Interface/Web.pm
+++ b/lib/RT/Interface/Web.pm
@@ -1870,7 +1870,7 @@ sub CreateTicket {
 
         delete $session{'Attachments'}{ $ARGS{'Token'} || '' }
             unless $ARGS{'KeepAttachments'};
-        $session{'Attachment'} = $session{'Attachment'}
+        $session{'Attachments'} = $session{'Attachments'}
             if @attachments;
     }
     if ( $ARGS{'Attachments'} ) {
@@ -1994,10 +1994,17 @@ sub ProcessUpdateMessage {
         @_
     );
 
-    if ( $args{ARGSRef}->{'UpdateAttachments'}
-        && !keys %{ $args{ARGSRef}->{'UpdateAttachments'} } )
-    {
-        delete $args{ARGSRef}->{'UpdateAttachments'};
+    my @attachments;
+    if ( my $tmp = $session{'Attachments'}{ $args{'ARGSRef'}{'Token'} || '' } ) {
+        push @attachments, grep $_, values %$tmp;
+
+        delete $session{'Attachments'}{ $args{'ARGSRef'}{'Token'} || '' }
+            unless $args{'KeepAttachments'};
+        $session{'Attachments'} = $session{'Attachments'}
+            if @attachments;
+    }
+    if ( $args{ARGSRef}{'UpdateAttachments'} ) {
+        push @attachments, grep $_, values %{ $args{ARGSRef}{'UpdateAttachments'} };
     }
 
     # Strip the signature
@@ -2011,7 +2018,7 @@ sub ProcessUpdateMessage {
     # If, after stripping the signature, we have no message, move the
     # UpdateTimeWorked into adjusted TimeWorked, so that a later
     # ProcessBasics can deal -- then bail out.
-    if (    not $args{ARGSRef}->{'UpdateAttachments'}
+    if (    not @attachments
         and not length $args{ARGSRef}->{'UpdateContent'} )
     {
         if ( $args{ARGSRef}->{'UpdateTimeWorked'} ) {
@@ -2048,18 +2055,6 @@ sub ProcessUpdateMessage {
         );
     }
 
-    my @attachments;
-    if ( my $tmp = $session{'Attachments'}{ $args{'ARGSRef'}{'Token'} || '' } ) {
-        push @attachments, grep $_, values %$tmp;
-
-        delete $session{'Attachments'}{ $args{'ARGSRef'}{'Token'} || '' }
-            unless $args{'KeepAttachments'};
-        $session{'Attachment'} = $session{'Attachment'}
-            if @attachments;
-    }
-    if ( $args{ARGSRef}{'UpdateAttachments'} ) {
-        push @attachments, grep $_, values %{ $args{ARGSRef}{'UpdateAttachments'} };
-    }
     if ( @attachments ) {
         $Message->make_multipart;
         $Message->add_part( $_ ) foreach @attachments;
@@ -2149,7 +2144,39 @@ sub _ProcessUpdateMessageRecipients {
     }
 }
 
+sub ProcessAttachments {
+    my %args = (
+        ARGSRef => {},
+        Token   => '',
+        @_
+    );
+
+    my $token = $args{'Token'};
+
+    my $update_session = 0;
 
+    # deal with deleting uploaded attachments
+    if ( my $del = $args{'ARGSRef'}{'DeleteAttach'} ) {
+        delete $session{'Attachments'}{ $token }{ $_ }
+            foreach ref $del? @$del : ($del);
+
+        $update_session = 1;
+    }
+
+    # store the uploaded attachment in session
+    my $new = $args{'ARGSRef'}{'Attach'};
+    if ( defined $new && length $new ) {
+        my $attachment = MakeMIMEEntity(
+            AttachmentFieldName => 'Attach'
+        );
+
+        my $file_path = Encode::decode_utf8("$new");
+        $session{'Attachments'}{ $token }{ $file_path } = $attachment;
+
+        $update_session = 1;
+    }
+    $session{'Attachments'} = $session{'Attachments'} if $update_session;
+}
 
 =head2 MakeMIMEEntity PARAMHASH
 

commit 9c5ef63afbaeee41b452cd60c66de0f9b66c716d
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Sat Jan 22 03:15:04 2011 +0300

    indent

diff --git a/share/html/SelfService/Display.html b/share/html/SelfService/Display.html
index 40e3092..cb7e7fb 100644
--- a/share/html/SelfService/Display.html
+++ b/share/html/SelfService/Display.html
@@ -108,9 +108,7 @@ if ( defined $ARGS{'Attach'} && length $ARGS{'Attach'} ) { # attachment?
     $session{'Attachments'}{ $Token }{ $file_path } = $attachment;
 }
 
-if ( defined ($id[0]) && $id[0] eq 'new' ) {
-
-    # {{{ Create a new ticket
+if ( ($id[0]||'') eq 'new' ) {
 
     my $Queue = RT::Queue->new( $session{'CurrentUser'} );
     unless ( $Queue->Load( $ARGS{'Queue'} ) ) {
@@ -131,22 +129,22 @@ if ( defined ($id[0]) && $id[0] eq 'new' ) {
 
     unless ( $Ticket->id ) {
         $m->comp( 'Error.html', Why => join( "\n", @results ));
-              $m->abort();
-        }
+        $m->abort();
+    }
 
 
-        # delete temporary storage entry to make WebUI clean
-        unless ( keys %{ $session{'Attachments'}{ $Token } } and $ARGS{'UpdateAttach'} ) {
-            delete $session{'Attachments'}{ $Token };
-        }
+    # delete temporary storage entry to make WebUI clean
+    unless ( keys %{ $session{'Attachments'}{ $Token } } and $ARGS{'UpdateAttach'} ) {
+        delete $session{'Attachments'}{ $Token };
+    }
 
+}
+else {
+    unless ( $Ticket->Load( $id[0] ) ) {
+        $m->comp( 'Error.html',
+            Why => loc( "Couldn't load ticket '[_1]'", $id ) );
+        $m->abort();
     }
-    else {
-        unless ( $Ticket->Load( $id[0] ) ) {
-            $m->comp( 'Error.html',
-                Why => loc( "Couldn't load ticket '[_1]'", $id ) );
-            $m->abort();
-        }
 
     my ( $code, $msg );
 
@@ -186,31 +184,31 @@ if ( defined ($id[0]) && $id[0] eq 'new' ) {
     }
 
 
-    }
+}
 
-    # This code does automatic redirection if any updates happen.
+# This code does automatic redirection if any updates happen.
 
-    unless ( $Ticket->CurrentUserHasRight('ShowTicket') ) {
-        $m->comp( 'Error.html',
-            Why => loc("No permission to display that ticket") );
-        # XXX: Why abort? then we loose footer //ruz
-        $m->abort();
-    }
+unless ( $Ticket->CurrentUserHasRight('ShowTicket') ) {
+    $m->comp( 'Error.html',
+        Why => loc("No permission to display that ticket") );
+    # XXX: Why abort? then we loose footer //ruz
+    $m->abort();
+}
 
-    if (@results) {
-        # We've done something, so we need to clear the decks to avoid
-        # resubmission on refresh.
-        # But we need to store Actions somewhere too, so we don't lose them.
-        my $key = Digest::MD5::md5_hex(rand(1024));
-        push @{ $session{"Actions"}->{$key}  ||= [] }, @results;
-        $session{'i'}++;
-        RT::Interface::Web::Redirect( RT->Config->Get('WebURL') ."SelfService/Display.html?id=". $Ticket->id."&results=".$key);
-    }
+if (@results) {
+    # We've done something, so we need to clear the decks to avoid
+    # resubmission on refresh.
+    # But we need to store Actions somewhere too, so we don't lose them.
+    my $key = Digest::MD5::md5_hex(rand(1024));
+    push @{ $session{"Actions"}->{$key}  ||= [] }, @results;
+    $session{'i'}++;
+    RT::Interface::Web::Redirect( RT->Config->Get('WebURL') ."SelfService/Display.html?id=". $Ticket->id."&results=".$key);
+}
 
-    my $LinkBasicsTitle = $Ticket->CurrentUserHasRight('ModifyTicket')
-                          || $Ticket->CurrentUserHasRight('ReplyToTicket');
-    my $title_box_link = RT->Config->Get('WebPath')."/SelfService/Update.html?id=".$Ticket->Id;
-    $m->callback(CallbackName => 'BeforeDisplay', Ticket => \$Ticket, ARGSRef => \%ARGS, title_box_link => \$title_box_link);
+my $LinkBasicsTitle = $Ticket->CurrentUserHasRight('ModifyTicket')
+                      || $Ticket->CurrentUserHasRight('ReplyToTicket');
+my $title_box_link = RT->Config->Get('WebPath')."/SelfService/Update.html?id=".$Ticket->Id;
+$m->callback(CallbackName => 'BeforeDisplay', Ticket => \$Ticket, ARGSRef => \%ARGS, title_box_link => \$title_box_link);
 </%INIT>
 
 

commit dd9f42cfdae98a58f7e822dbbd0c2657f97391e4
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Sat Jan 22 02:37:38 2011 +0300

    use ProcessAttachments and shrink repeated code

diff --git a/share/html/Search/Bulk.html b/share/html/Search/Bulk.html
index dd711df..8bb4198 100644
--- a/share/html/Search/Bulk.html
+++ b/share/html/Search/Bulk.html
@@ -50,7 +50,7 @@
 
 <& /Elements/ListActions, actions => \@results &>
 <form method="post" action="<% RT->Config->Get('WebPath') %>/Search/Bulk.html" enctype="multipart/form-data" name="BulkUpdate" id="BulkUpdate">
-% foreach my $var (qw(Query Format OrderBy Order Rows Page SavedChartSearchId)) {
+% foreach my $var (qw(Query Format OrderBy Order Rows Page SavedChartSearchId Token)) {
 <input type="hidden" class="hidden" name="<%$var%>" value="<%$ARGS{$var} || ''%>" />
 %}
 <& /Elements/CollectionList, 
@@ -228,30 +228,7 @@ my (@results);
 
 $ARGS{'Token'} ||= $Token ||= Digest::MD5::md5_hex( rand(1024) );
 
-# deal with deleting uploaded attachments
-foreach my $key (keys %ARGS) {
-    if ($key =~ m/^DeleteAttach-(.+)$/) {
-        delete $session{'Attachments'}{ $Token }{ $1 };
-    }
-    $session{'Attachments'} = { %{$session{'Attachments'} || {}} };
-}
-
-# store the uploaded attachment in session
-if ( defined $ARGS{'Attach'} && length $ARGS{'Attach'} ) { # attachment?
-    my $attachment = MakeMIMEEntity(
-        AttachmentFieldName => 'Attach'
-    );
-
-    my $file_path = Encode::decode_utf8("$ARGS{'Attach'}");
-    # force session save
-    $session{'Attachments'} = $session{'Attachments'} || {};
-    $session{'Attachments'}{ $Token }{ $file_path } = $attachment;
-}
-
-# delete temporary storage entry to make WebUI clean
-unless (keys %{$session{'Attachments'}{ $Token }} and $ARGS{'UpdateAttach'}) {
-    delete $session{'Attachments'}{ $Token };
-}
+ProcessAttachments( ARGSRef => \%ARGS, Token => $Token );
 
 $Page ||= 1;
 
@@ -301,10 +278,6 @@ my %queues;
 $Tickets->RedoSearch();
 
 unless ( $ARGS{'AddMoreAttach'} ) {
-    # Add session attachments if any to be processed by ProcessUpdateMessage
-    $ARGS{'UpdateAttachments'} = $session{'Attachments'}{ $Token }
-        if $session{'Attachments'}{ $Token };
-
     while ( my $Ticket = $Tickets->Next ) {
         next unless ( $ARGS{ "UpdateTicket" . $Ticket->Id } );
 
@@ -313,9 +286,10 @@ unless ( $ARGS{'AddMoreAttach'} ) {
         $queues{ $Ticket->QueueObj->Id }++;
 
         my @updateresults = ProcessUpdateMessage(
-                TicketObj => $Ticket,
-                ARGSRef   => \%ARGS,
-            );
+            TicketObj       => $Ticket,
+            ARGSRef         => \%ARGS,
+            KeepAttachments => 1,
+        );
 
         #Update the basics.
         my @basicresults =
@@ -385,7 +359,6 @@ unless ( $ARGS{'AddMoreAttach'} ) {
         @results = ( @results, @tempresults );
     }
 
-    # Cleanup WebUI
     delete $session{'Attachments'}{ $Token };
 }
 
diff --git a/share/html/SelfService/Display.html b/share/html/SelfService/Display.html
index cb7e7fb..5984d08 100644
--- a/share/html/SelfService/Display.html
+++ b/share/html/SelfService/Display.html
@@ -96,17 +96,7 @@ my @id = ( ref $id eq 'ARRAY' ) ? @{$id} : ($id);
 
 my $Ticket = RT::Ticket->new( $session{'CurrentUser'} );
 
-# store the uploaded attachment in session
-if ( defined $ARGS{'Attach'} && length $ARGS{'Attach'} ) { # attachment?
-    my $attachment = MakeMIMEEntity(
-        AttachmentFieldName => 'Attach'
-    );
-
-    my $file_path = Encode::decode_utf8("$ARGS{'Attach'}");
-    # force session save
-    $session{'Attachments'} = $session{'Attachments'} || {};
-    $session{'Attachments'}{ $Token }{ $file_path } = $attachment;
-}
+ProcessAttachments( Token => $Token, ARGSRef => \%ARGS );
 
 if ( ($id[0]||'') eq 'new' ) {
 
@@ -124,20 +114,12 @@ if ( ($id[0]||'') eq 'new' ) {
     }
 
 
-    ( $Ticket, @results ) =
-    CreateTicket( Attachments => $session{'Attachments'}{$Token}, %ARGS );
+    ( $Ticket, @results ) = CreateTicket( %ARGS );
 
     unless ( $Ticket->id ) {
         $m->comp( 'Error.html', Why => join( "\n", @results ));
         $m->abort();
     }
-
-
-    # delete temporary storage entry to make WebUI clean
-    unless ( keys %{ $session{'Attachments'}{ $Token } } and $ARGS{'UpdateAttach'} ) {
-        delete $session{'Attachments'}{ $Token };
-    }
-
 }
 else {
     unless ( $Ticket->Load( $id[0] ) ) {
@@ -146,44 +128,23 @@ else {
         $m->abort();
     }
 
-    my ( $code, $msg );
-
-
-    if (
-        $session{'Attachments'}{ $Token }
-        || ( defined $ARGS{'UpdateContent'}
-            && $ARGS{'UpdateContent'} ne ''
-            && $ARGS{'UpdateContent'} ne "-- \n"
-            . $session{'CurrentUser'}->UserObj->Signature )
-      )
-    {
-        $ARGS{UpdateAttachments} = $session{'Attachments'}{ $Token };
-    }
     push @results, ProcessUpdateMessage(
         ARGSRef   => \%ARGS,
         TicketObj => $Ticket
     );
-            delete $session{'Attachments'}{ $Token };
-
-    # delete temporary storage entry to make WebUI clean
-    unless ( keys %{ $session{'Attachments'}{ $Token } } and $ARGS{'UpdateAttach'} ) {
-        delete $session{'Attachments'}{ $Token };
-    }
 
     my @cfupdates = ProcessObjectCustomFieldUpdates(Object => $Ticket, ARGSRef => \%ARGS);
     push (@results, @cfupdates);
 
-
     #Update the status
     if (    ( defined $ARGS{'Status'} )
         and $ARGS{'Status'}
         and ( $ARGS{'Status'} ne $Ticket->Status ) )
     {
-        ( $code, $msg ) = $Ticket->SetStatus( $ARGS{'Status'} );
+        my ($code, $msg) = $Ticket->SetStatus( $ARGS{'Status'} );
         push @results, "$msg";
     }
 
-
 }
 
 # This code does automatic redirection if any updates happen.
diff --git a/share/html/Ticket/Create.html b/share/html/Ticket/Create.html
index 4042b48..b75f65a 100644
--- a/share/html/Ticket/Create.html
+++ b/share/html/Ticket/Create.html
@@ -378,29 +378,7 @@ my $ticket = RT::Ticket->new($session{'CurrentUser'}); # empty ticket object
 
 $ARGS{'Token'} ||= $Token ||= Digest::MD5::md5_hex( rand(1024) );
 
-# deal with deleting uploaded attachments
-foreach my $key (keys %ARGS) {
-    if ($key =~ m/^DeleteAttach-(.+)$/) {
-        delete $session{'Attachments'}{ $Token }{$1};
-    }
-    $session{'Attachments'} = { %{$session{'Attachments'} || {}} };
-}
-
-# store the uploaded attachment in session
-if ( defined $ARGS{'Attach'} && length $ARGS{'Attach'} ) { # attachment?
-    my $attachment = MakeMIMEEntity(
-        AttachmentFieldName => 'Attach'
-    );
-
-    my $file_path = Encode::decode_utf8("$ARGS{'Attach'}");
-    $session{'Attachments'} = $session{'Attachments'} || {};
-    $session{'Attachments'}{ $Token }{ $file_path } = $attachment;
-}
-
-# delete temporary storage entry to make WebUI clean
-unless (keys %{$session{'Attachments'}{$Token}} and $ARGS{'id'} eq 'new') {
-    delete $session{'Attachments'}{$Token};
-}
+ProcessAttachments( ARGSRef => \%ARGS, Token => $Token );
 
 my $checks_failure = 0;
 
diff --git a/share/html/Ticket/Display.html b/share/html/Ticket/Display.html
index 147739c..2711549 100644
--- a/share/html/Ticket/Display.html
+++ b/share/html/Ticket/Display.html
@@ -132,10 +132,7 @@ if ($ARGS{'id'} eq 'new') {
         Abort('You have no permission to create tickets in that queue.');
     }
 
-    ($TicketObj, @Actions) = CreateTicket(
-        Attachments => delete $session{'Attachments'}{ $ARGS{'Token'} || '' },
-        %ARGS,
-    );
+    ($TicketObj, @Actions) = CreateTicket( %ARGS );
     unless ( $TicketObj->CurrentUserHasRight('ShowTicket') ) {
         Abort("No permission to view newly created ticket #".$TicketObj->id.".");
     }
@@ -165,13 +162,11 @@ if ($ARGS{'id'} eq 'new') {
                 ARGSRef => \%ARGS, 
                 Actions => \@Actions);
         
-        $ARGS{UpdateAttachments} = delete $session{'Attachments'}{ $ARGS{'Token'} || '' };
-        push @Actions,
-            ProcessUpdateMessage(
+        push @Actions, ProcessUpdateMessage(
             ARGSRef   => \%ARGS,
             Actions   => \@Actions,
             TicketObj => $TicketObj,
-            );
+        );
 
         #Process status updates
         push @Actions, ProcessTicketWatchers(ARGSRef => \%ARGS, TicketObj => $TicketObj );
diff --git a/share/html/Ticket/Elements/AddAttachments b/share/html/Ticket/Elements/AddAttachments
index a387e93..d86ef5b 100644
--- a/share/html/Ticket/Elements/AddAttachments
+++ b/share/html/Ticket/Elements/AddAttachments
@@ -50,8 +50,8 @@
 <td>
 <&|/l&>Check box to delete</&><br />
 % foreach my $attach_name ( keys %$attachments ) {
-<input type="checkbox" class="checkbox" id="DeleteAttach-<%$attach_name%>" name="DeleteAttach-<%$attach_name%>" value="1" />
-<label for="DeleteAttach-<%$attach_name%>"><%$attach_name%></label>
+<input type="checkbox" class="checkbox" name="DeleteAttach" value="<% $attach_name %>" id="DeleteAttach-<%$attach_name%>" />
+<label for="DeleteAttach-<%$attach_name%>"><% $attach_name %></label>
 <br />
 % } # end of foreach
 </td>
diff --git a/share/html/Ticket/ModifyAll.html b/share/html/Ticket/ModifyAll.html
index 88739d0..5e93c3b 100644
--- a/share/html/Ticket/ModifyAll.html
+++ b/share/html/Ticket/ModifyAll.html
@@ -54,6 +54,7 @@
 <form method="post" action="ModifyAll.html" name="TicketModifyAll" enctype="multipart/form-data">
 % $m->callback( CallbackName => 'FormStart', ARGSRef => \%ARGS );
 <input type="hidden" class="hidden" name="id" value="<%$Ticket->Id%>" />
+<input type="hidden" class="hidden" name="Token" value="<% $Token %>" />
 
 <&| /Widgets/TitleBox, title => loc('Modify ticket # [_1]', $Ticket->Id), class=>'ticket-info-basics' &>
 <& Elements/EditBasics, TicketObj => $Ticket, defaults => \%ARGS &>
@@ -148,31 +149,7 @@ $CanComment = 1 if ( $Ticket->CurrentUserHasRight('CommentOnTicket') or
 
 $ARGS{'Token'} ||= $Token ||= Digest::MD5::md5_hex( rand(1024) );
 
-# deal with deleting uploaded attachments
-foreach my $key (keys %ARGS) {
-    if ($key =~ m/^DeleteAttach-(.+)$/) {
-        delete $session{'Attachments'}{$Token}{$1};
-    }
-    $session{'Attachments'} = { %{$session{'Attachments'} || {}} };
-}
-
-# store the uploaded attachment in session
-if ( defined $ARGS{'Attach'} && length $ARGS{'Attach'} ) { # attachment?
-    my $attachment = MakeMIMEEntity(
-        AttachmentFieldName => 'Attach'
-    );
-
-    my $file_path = Encode::decode_utf8("$ARGS{'Attach'}");
-    # force session save
-    $session{'Attachments'} = $session{'Attachments'} || {};
-    $session{'Attachments'}{ $Token }{ $file_path } = $attachment;
-}
-
-# delete temporary storage entry to make WebUI clean
-unless (keys %{$session{'Attachments'}{$Token}} and $ARGS{'UpdateAttach'}) {
-    delete $session{'Attachments'}{ $Token };
-}
-
+ProcessAttachments( ARGSRef => \%ARGS, Token => $Token );
 
 my @results;
 my $skip_update = 0;
@@ -209,14 +186,7 @@ unless ($skip_update or $OnlySearchForPeople or $OnlySearchForGroup or $ARGS{'Ad
     push @results, ProcessTicketWatchers( TicketObj => $Ticket, ARGSRef => \%ARGS);
     push @results, ProcessObjectCustomFieldUpdates( Object => $Ticket, ARGSRef => \%ARGS);
     push @results, ProcessTicketDates( TicketObj => $Ticket, ARGSRef => \%ARGS);
-    
-    # Add session attachments if any to be processed by ProcessUpdateMessage
-    $ARGS{'UpdateAttachments'} = $session{'Attachments'}{ $Token }
-        if $session{'Attachments'}{ $Token };
     push @results, ProcessUpdateMessage( TicketObj => $Ticket, ARGSRef=>\%ARGS );
-    # Cleanup WebUI
-    delete $session{'Attachments'}{ $Token };
-
     push @results, ProcessTicketBasics( TicketObj => $Ticket, ARGSRef => \%ARGS );
     push @results, ProcessTicketLinks( TicketObj => $Ticket, ARGSRef => \%ARGS);
 
diff --git a/share/html/Ticket/Update.html b/share/html/Ticket/Update.html
index bf760e2..56fbdc1 100644
--- a/share/html/Ticket/Update.html
+++ b/share/html/Ticket/Update.html
@@ -243,30 +243,7 @@ $CanComment = 1 if ( $TicketObj->CurrentUserHasRight('CommentOnTicket') or
 
 $ARGS{'Token'} ||= $Token ||= Digest::MD5::md5_hex( rand(1024) );
 
-# deal with deleting uploaded attachments
-foreach my $key (keys %ARGS) {
-    if ($key =~ m/^DeleteAttach-(.+)$/) {
-        delete $session{'Attachments'}{$Token}{$1};
-    }
-    $session{'Attachments'} = { %{$session{'Attachments'} || {}} };
-}
-
-# store the uploaded attachment in session
-if ( defined $ARGS{'Attach'} && length $ARGS{'Attach'} ) { # attachment?
-    my $attachment = MakeMIMEEntity(
-        AttachmentFieldName => 'Attach'
-    );
-
-    my $file_path = Encode::decode_utf8("$ARGS{'Attach'}");
-    # force session save
-    $session{'Attachments'} = $session{'Attachments'} || {};
-    $session{'Attachments'}{ $Token }{ $file_path } = $attachment;
-}
-
-# delete temporary storage entry to make WebUI clean
-unless (keys %{$session{'Attachments'}{$Token}} and $ARGS{'UpdateAttach'}) {
-    delete $session{'Attachments'}{ $Token };
-}
+ProcessAttachments( ARGSRef => \%ARGS, Token => $Token );
 
 my $gnupg_widget = $m->comp('/Elements/GnuPG/SignEncryptWidget:new', Arguments => \%ARGS );
 $m->comp( '/Elements/GnuPG/SignEncryptWidget:Process',
diff --git a/share/html/m/ticket/create b/share/html/m/ticket/create
index 4c0fa9e..261b159 100644
--- a/share/html/m/ticket/create
+++ b/share/html/m/ticket/create
@@ -46,6 +46,7 @@
 %#
 %# END BPS TAGGED BLOCK }}}
 <%ARGS>
+$Token => ''
 $QuoteTransaction => undef
 $CloneTicket => undef
 </%ARGS>
@@ -146,31 +147,9 @@ $m->callback( QueueObj => $QueueObj, title => \$title, results => \@results, ARG
 
 $QueueObj->Disabled && Abort(loc("Cannot create tickets in a disabled queue."));
 
-# deal with deleting uploaded attachments
-foreach my $key (keys %ARGS) {
-    if ($key =~ m/^DeleteAttach-(.+)$/) {
-        delete $session{'Attachments'}{$1};
-    }
-    $session{'Attachments'} = { %{$session{'Attachments'} || {}} };
-}
-
-# store the uploaded attachment in session
-if ( defined $ARGS{'Attach'} && length $ARGS{'Attach'} ) { # attachment?
-    my $attachment = MakeMIMEEntity(
-        AttachmentFieldName => 'Attach'
-    );
+$ARGS{'Token'} ||= $Token ||= Digest::MD5::md5_hex( rand(1024) );
 
-    my $file_path = Encode::decode_utf8("$ARGS{'Attach'}");
-    $session{'Attachments'} = {
-        %{$session{'Attachments'} || {}},
-        $file_path => $attachment,
-    };
-}
-
-# delete temporary storage entry to make WebUI clean
-unless (keys %{$session{'Attachments'}} and $ARGS{'id'} eq 'new') {
-    delete $session{'Attachments'};
-}
+ProcessAttachments( ARGSRef => \%ARGS, Token => $Token );
 
 my $checks_failure = 0;
 
@@ -238,6 +217,7 @@ if ((!exists $ARGS{'AddMoreAttach'}) and (defined($ARGS{'id'}) and $ARGS{'id'} e
 <& /Elements/ListActions, actions => \@results  &>
 <form action="<% RT->Config->Get('WebPath') %>/m/ticket/create" method="post" enctype="multipart/form-data" name="TicketCreate" id="ticket-create">
 <input type="hidden" class="hidden" name="id" value="new" />
+<input type="hidden" class="hidden" name="Token" value="<% $Token %>" />
 % $m->callback( CallbackName => 'FormStart', QueueObj => $QueueObj, ARGSRef => \%ARGS );
 % if ($gnupg_widget) {
 <& /Elements/GnuPG/SignEncryptWidget:ShowIssues, self => $gnupg_widget &>
@@ -326,14 +306,14 @@ $showrows->(
     &>
 <& /Ticket/Elements/EditTransactionCustomFields, %ARGS, QueueObj => $QueueObj &>
 
-% if (exists $session{'Attachments'}) {
+% if ( my $attachments = $session{'Attachments'}{'Token'}) {
 
 <%loc("Attached file") %>
 
 <%loc("Check box to delete")%><br />
-% foreach my $attach_name (keys %{$session{'Attachments'}}) {
-<input type="checkbox" class="checkbox" id="DeleteAttach-<%$attach_name%>" name="DeleteAttach-<%$attach_name%>" value="1" />
-<label for="DeleteAttach-<%$attach_name%>"><%$attach_name%></label><br />
+% foreach my $attach_name ( keys %$attachments ) {
+<input type="checkbox" class="checkbox" name="DeleteAttach" value="<% $attach_name %>" id="DeleteAttach-<% $attach_name %>" />
+<label for="DeleteAttach-<% $attach_name %>"><% $attach_name %></label><br />
 % } # end of foreach
 
 
diff --git a/share/html/m/ticket/reply b/share/html/m/ticket/reply
index 290b2d1..0d3929d 100644
--- a/share/html/m/ticket/reply
+++ b/share/html/m/ticket/reply
@@ -193,34 +193,9 @@ $CanRespond = 1 if ( $t->CurrentUserHasRight('ReplyToTicket') or
 $CanComment = 1 if ( $t->CurrentUserHasRight('CommentOnTicket') or
                      $t->CurrentUserHasRight('ModifyTicket') ); 
 
+$ARGS{'Token'} ||= $Token ||= Digest::MD5::md5_hex( rand(1024) );
 
-# deal with deleting uploaded attachments
-foreach my $key (keys %ARGS) {
-    if ($key =~ m/^DeleteAttach-(.+)$/) {
-        delete $session{'Attachments'}{$1};
-    }
-    $session{'Attachments'} = { %{$session{'Attachments'} || {}} };
-}
-
-# store the uploaded attachment in session
-if ( defined $ARGS{'Attach'} && length $ARGS{'Attach'} ) { # attachment?
-    my $attachment = MakeMIMEEntity(
-        AttachmentFieldName => 'Attach'
-    );
-
-    my $file_path = Encode::decode_utf8("$ARGS{'Attach'}");
-    $session{'Attachments'} = {
-        %{$session{'Attachments'} || {}},
-        $file_path => $attachment,
-    };
-}
-
-# delete temporary storage entry to make WebUI clean
-unless ( keys %{ $session{'Attachments'} }
-    && ( exists $ARGS{'AddMoreAttach'} || exists $ARGS{'SubmitTicket'} ) )
-{
-    delete $session{'Attachments'};
-}
+ProcessAttachments( ARGSRef => \%ARGS, Token => $Token );
 
 # check email addresses for RT's
 {
@@ -261,6 +236,7 @@ if ( !$checks_failure && !$skip_update && exists $ARGS{SubmitTicket} ) {
 
 <%ARGS>
 $id => undef
+$Token => ''
 $Action => 'Respond'
 $DefaultStatus => undef
 </%ARGS>
diff --git a/share/html/m/ticket/show b/share/html/m/ticket/show
index d8d5bc2..480664e 100644
--- a/share/html/m/ticket/show
+++ b/share/html/m/ticket/show
@@ -69,10 +69,7 @@ if ($ARGS{'id'} eq 'new') {
         Abort('You have no permission to create tickets in that queue.');
     }
 
-    ($Ticket, @Actions) = CreateTicket(
-        Attachments => delete $session{'Attachments'},
-        %ARGS,
-    );
+    ($Ticket, @Actions) = CreateTicket( %ARGS );
     unless ( $Ticket->CurrentUserHasRight('ShowTicket') ) {
         Abort("No permission to view newly created ticket #".$Ticket->id.".");
     }
@@ -95,14 +92,12 @@ if ($ARGS{'id'} eq 'new') {
             ARGSRef => \%ARGS, 
             Actions => \@Actions);
     
-    $ARGS{UpdateAttachments} = $session{'Attachments'};
     push @Actions,
         ProcessUpdateMessage(
         ARGSRef   => \%ARGS,
         Actions   => \@Actions,
         TicketObj => $Ticket,
         );
-    delete $session{'Attachments'};
 
     #Process status updates
     push @Actions, ProcessTicketWatchers(ARGSRef => \%ARGS, TicketObj => $Ticket );

commit fb431b0c89a54904e4500f9bd157282f70b1f1c6
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Sat Jan 22 03:26:30 2011 +0300

    use may be redirect

diff --git a/share/html/SelfService/Display.html b/share/html/SelfService/Display.html
index 5984d08..397ebbf 100644
--- a/share/html/SelfService/Display.html
+++ b/share/html/SelfService/Display.html
@@ -156,15 +156,11 @@ unless ( $Ticket->CurrentUserHasRight('ShowTicket') ) {
     $m->abort();
 }
 
-if (@results) {
-    # We've done something, so we need to clear the decks to avoid
-    # resubmission on refresh.
-    # But we need to store Actions somewhere too, so we don't lose them.
-    my $key = Digest::MD5::md5_hex(rand(1024));
-    push @{ $session{"Actions"}->{$key}  ||= [] }, @results;
-    $session{'i'}++;
-    RT::Interface::Web::Redirect( RT->Config->Get('WebURL') ."SelfService/Display.html?id=". $Ticket->id."&results=".$key);
-}
+MaybeRedirectForResults(
+    Actions   => \@results,
+    Path      => '/SelfService/Display.html',
+    Arguments => { 'id' => $Ticket->id },
+);
 
 my $LinkBasicsTitle = $Ticket->CurrentUserHasRight('ModifyTicket')
                       || $Ticket->CurrentUserHasRight('ReplyToTicket');

commit 0b0cc2c703fb093a7cf3915fc3f57e86736615b8
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Sat Jan 22 16:05:59 2011 +0300

    use Abort()

diff --git a/share/html/SelfService/Display.html b/share/html/SelfService/Display.html
index 397ebbf..3a5ee5a 100644
--- a/share/html/SelfService/Display.html
+++ b/share/html/SelfService/Display.html
@@ -102,30 +102,23 @@ if ( ($id[0]||'') eq 'new' ) {
 
     my $Queue = RT::Queue->new( $session{'CurrentUser'} );
     unless ( $Queue->Load( $ARGS{'Queue'} ) ) {
-        $m->comp( 'Error.html', Why => loc('Queue not found') );
-        $m->abort;
+        Abort( loc('Queue not found') );
     }
 
     unless ( $Queue->CurrentUserHasRight('CreateTicket') ) {
-        $m->comp( 'Error.html',
-            Why =>
-              loc('You have no permission to create tickets in that queue.') );
-        $m->abort;
+        Abort( loc('You have no permission to create tickets in that queue.') );
     }
 
 
     ( $Ticket, @results ) = CreateTicket( %ARGS );
 
     unless ( $Ticket->id ) {
-        $m->comp( 'Error.html', Why => join( "\n", @results ));
-        $m->abort();
+        Abort( join "\n", @results );
     }
 }
 else {
     unless ( $Ticket->Load( $id[0] ) ) {
-        $m->comp( 'Error.html',
-            Why => loc( "Couldn't load ticket '[_1]'", $id ) );
-        $m->abort();
+        Abort( loc( "Couldn't load ticket '[_1]'", $id ) );
     }
 
     push @results, ProcessUpdateMessage(
@@ -150,10 +143,7 @@ else {
 # This code does automatic redirection if any updates happen.
 
 unless ( $Ticket->CurrentUserHasRight('ShowTicket') ) {
-    $m->comp( 'Error.html',
-        Why => loc("No permission to display that ticket") );
-    # XXX: Why abort? then we loose footer //ruz
-    $m->abort();
+    Abort( loc("No permission to display that ticket") );
 }
 
 MaybeRedirectForResults(

commit 09cf30acc36589a393d1bacac490224c9461a64d
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Fri Mar 4 11:36:00 2011 +0300

    name a few forms to easier test writing

diff --git a/share/html/SelfService/Create.html b/share/html/SelfService/Create.html
index df2c45d..e688dd8 100644
--- a/share/html/SelfService/Create.html
+++ b/share/html/SelfService/Create.html
@@ -48,7 +48,7 @@
 <& Elements/Header, Title => loc("Create a ticket") &>
 
 <& /Elements/ListActions, actions => \@results &>
-<form action="Create.html" method="post" enctype="multipart/form-data">
+<form action="Create.html" method="post" enctype="multipart/form-data" name="TicketCreate">
 <input type="hidden" class="hidden" name="id" value="new" />
 
 <table width="100%">
diff --git a/share/html/SelfService/Update.html b/share/html/SelfService/Update.html
index 4391114..5b3cfb1 100644
--- a/share/html/SelfService/Update.html
+++ b/share/html/SelfService/Update.html
@@ -51,7 +51,7 @@
 
 % $m->callback(CallbackName => 'BeforeForm', %ARGS, ARGSRef => \%ARGS, Ticket => $Ticket );
 
-<form action="Display.html" method="post" enctype="multipart/form-data">
+<form action="Display.html" method="post" enctype="multipart/form-data" name="TicketUpdate">
 <input type="hidden" class="hidden" name="UpdateType" value="response" />
 <input type="hidden" class="hidden" name="id" value="<%$Ticket->Id%>" />
 <table width="100%">

commit c4fb104345c4ba9b6027b0fdb068656264534c78
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Fri Mar 4 11:37:52 2011 +0300

    SingleFile argument in AddAttachments for SelfService

diff --git a/share/html/SelfService/Update.html b/share/html/SelfService/Update.html
index 5b3cfb1..4467862 100644
--- a/share/html/SelfService/Update.html
+++ b/share/html/SelfService/Update.html
@@ -75,7 +75,7 @@
         </td>
 
     </tr>
-    <& /Ticket/Elements/AddAttachments, %ARGS, TicketObj => $Ticket &>
+    <& /Ticket/Elements/AddAttachments, %ARGS, TicketObj => $Ticket, SingleFile => 1 &>
     <tr><td colspan="2"><& /Elements/EditCustomFields, Object => $Ticket, AsTable => 0 &></td></tr>
 </table>
 <& /Elements/MessageBox, 
diff --git a/share/html/Ticket/Elements/AddAttachments b/share/html/Ticket/Elements/AddAttachments
index d86ef5b..7985581 100644
--- a/share/html/Ticket/Elements/AddAttachments
+++ b/share/html/Ticket/Elements/AddAttachments
@@ -58,11 +58,16 @@
 </tr>
 % } # end of if
 
-<tr><td class="label"><&|/l&>Attach</&>:</td><td><input name="Attach" type="file" /><input type="submit" class="button" name="AddMoreAttach" value="<&|/l&>Add More Files</&>" /><input type="hidden" class="hidden" name="UpdateAttach" value="1" />
+<tr><td class="label"><&|/l&>Attach</&>:</td><td><input name="Attach" type="file" />
+% unless ( $SingleFile ) {
+<input type="submit" class="button" name="AddMoreAttach" value="<&|/l&>Add More Files</&>" />
+% }
+<input type="hidden" class="hidden" name="UpdateAttach" value="1" />
 </td></tr>
 % $m->callback( %ARGS, CallbackName => 'End' );
 <%ARGS>
 $Token => ''
+$SingleFile => 0
 </%ARGS>
 <%INIT>
 my $attachments;

commit d8b02a1e66ddd8b12697c37e5c54eddf4eab493a
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Fri Mar 4 11:39:02 2011 +0300

    get rid of unint warning (self service hits the case)

diff --git a/lib/RT/Interface/Web.pm b/lib/RT/Interface/Web.pm
index 7080815..a43c3f7 100644
--- a/lib/RT/Interface/Web.pm
+++ b/lib/RT/Interface/Web.pm
@@ -2027,7 +2027,7 @@ sub ProcessUpdateMessage {
         return;
     }
 
-    if ( $args{ARGSRef}->{'UpdateSubject'} eq ($args{'TicketObj'}->Subject || '') ) {
+    if ( ($args{ARGSRef}->{'UpdateSubject'}||'') eq ($args{'TicketObj'}->Subject || '') ) {
         $args{ARGSRef}->{'UpdateSubject'} = undef;
     }
 

commit 4e98788ca22c109ce24e9bf4d0306fd3a678d139
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Fri Mar 4 11:39:51 2011 +0300

    more tests to cover attachments uploading through the web UI

diff --git a/lib/RT/Test.pm b/lib/RT/Test.pm
index 99b996b..4e91b60 100644
--- a/lib/RT/Test.pm
+++ b/lib/RT/Test.pm
@@ -804,7 +804,10 @@ sub create_ticket {
     my $self = shift;
     my %args = @_;
 
-    if ($args{Queue} && $args{Queue} =~ /\D/) {
+    if ( blessed $args{'Queue'} ) {
+        $args{Queue} = $args{'Queue'}->id;
+    }
+    elsif ($args{Queue} && $args{Queue} =~ /\D/) {
         my $queue = RT::Queue->new(RT->SystemUser);
         if (my $id = $queue->Load($args{Queue}) ) {
             $args{Queue} = $id;
diff --git a/t/web/attachments.t b/t/web/attachments.t
index ba5c712..312cb1e 100644
--- a/t/web/attachments.t
+++ b/t/web/attachments.t
@@ -1,94 +1,505 @@
 use strict;
 use warnings;
 
-use RT::Test tests => 33;
+use RT::Test tests => 159;
 
 use constant LogoFile => $RT::StaticPath .'/images/bpslogo.png';
 use constant FaviconFile => $RT::StaticPath .'/images/favicon.png';
 use constant TextFile => $RT::MasonComponentRoot .'/NoAuth/css/print.css';
 
-my ($baseurl, $m) = RT::Test->started_ok;
+my ($url, $m) = RT::Test->started_ok;
 ok $m->login, 'logged in';
 
-my $queue = RT::Queue->new(RT->Nobody);
-my $qid = $queue->Load('General');
-ok( $qid, "Loaded General queue" );
-
-$m->form_name('CreateTicketInQueue');
-$m->field('Queue', $qid);
-$m->submit;
-is($m->status, 200, "request successful");
-$m->content_contains("Create a new ticket", 'ticket create page');
-
-$m->form_name('TicketCreate');
-$m->field('Subject', 'Attachments test');
-$m->field('Attach',  LogoFile);
-$m->field('Content', 'Some content');
-$m->submit;
-is($m->status, 200, "request successful");
-
-$m->content_contains('Attachments test', 'we have subject on the page');
-$m->content_contains('Some content', 'and content');
-$m->content_contains('Download bpslogo.png', 'page has file name');
-
-open LOGO, "<", LogoFile or die "Can't open logo file: $!";
-binmode LOGO;
-my $logo_contents = do {local $/; <LOGO>};
-close LOGO;
-$m->follow_link_ok({text => "Download bpslogo.png"});
-is($m->content_type, "image/png");
-is($m->content, $logo_contents, "Binary content matches");
-
-$m->back;
-$m->follow_link_ok({text => 'Reply'}, "reply to the ticket");
-$m->form_name('TicketUpdate');
-$m->field('Attach',  TextFile);
-$m->click('AddMoreAttach');
-is($m->status, 200, "request successful");
-
-$m->form_name('TicketUpdate');
-$m->field('Attach',  FaviconFile);
-$m->field('UpdateContent', 'Message');
-$m->click('SubmitTicket');
-is($m->status, 200, "request successful");
-
-$m->content_contains('Download bpslogo.png', 'page has file name');
-$m->content_contains('Download favicon.png', 'page has file name');
-$m->content_contains('Download print.css', 'page has file name');
-
-$m->follow_link_ok( { text => 'Download bpslogo.png' } );
-is( $m->response->header('Content-Type'), 'image/png', 'Content-Type of png lacks charset' );
-
-$m->back;
-
-$m->follow_link_ok( { text => 'Download print.css' } );
-is( $m->response->header('Content-Type'),
-    'text/css;charset=UTF-8', 'Content-Type of text has charset' );
-
-diag "test mobile ui";
-$m->get_ok( $baseurl . '/m/ticket/create?Queue=' . $qid );
-
-$m->form_name('TicketCreate');
-$m->field('Subject', 'Attachments test');
-$m->field('Attach',  LogoFile);
-$m->field('Content', 'Some content');
-$m->submit;
-is($m->status, 200, "request successful");
-
-$m->content_contains('Attachments test', 'we have subject on the page');
-$m->content_contains('bpslogo.png', 'page has file name');
-
-$m->follow_link_ok({text => 'Reply'}, "reply to the ticket");
-$m->form_name('TicketUpdate');
-$m->field('Attach',  LogoFile);
-$m->click('AddMoreAttach');
-is($m->status, 200, "request successful");
-
-$m->form_name('TicketUpdate');
-$m->field('Attach',  FaviconFile);
-$m->field('UpdateContent', 'Message');
-$m->click('SubmitTicket');
-is($m->status, 200, "request successful");
-
-$m->content_contains('bpslogo.png', 'page has file name');
-$m->content_contains('favicon.png', 'page has file name');
+my $queue = RT::Test->load_or_create_queue( Name => 'General' );
+ok( $queue && $queue->id, "Loaded General queue" );
+
+diag "create a ticket in full interface";
+diag "w/o attachments";
+{
+    $m->goto_create_ticket( $queue );
+    is($m->status, 200, "request successful");
+
+    $m->form_name('TicketCreate');
+    $m->content_contains("Create a new ticket", 'ticket create page');
+    $m->submit;
+    is($m->status, 200, "request successful");
+}
+
+diag "with one attachment";
+{
+    $m->goto_create_ticket( $queue );
+
+    $m->form_name('TicketCreate');
+    $m->field('Subject', 'Attachments test');
+    $m->field('Attach',  LogoFile);
+    $m->field('Content', 'Some content');
+
+    $m->submit;
+    is($m->status, 200, "request successful");
+
+    $m->content_contains('Attachments test', 'we have subject on the page');
+    $m->content_contains('Some content', 'and content');
+    $m->content_contains('Download bpslogo.png', 'page has file name');
+}
+
+diag "with two attachments";
+{
+    $m->goto_create_ticket( $queue );
+
+    $m->form_name('TicketCreate');
+    $m->field('Attach',  LogoFile);
+    $m->click('AddMoreAttach');
+    is($m->status, 200, "request successful");
+
+    $m->form_name('TicketCreate');
+    $m->field('Attach',  FaviconFile);
+    $m->field('Subject', 'Attachments test');
+    $m->field('Content', 'Some content');
+
+    $m->submit;
+    is($m->status, 200, "request successful");
+
+    $m->content_contains('Attachments test', 'we have subject on the page');
+    $m->content_contains('Some content', 'and content');
+    $m->content_contains('Download bpslogo.png', 'page has file name');
+    $m->content_contains('Download favicon.png', 'page has file name');
+}
+
+diag "with one attachment, but delete one along the way";
+{
+    $m->goto_create_ticket( $queue );
+
+    $m->form_name('TicketCreate');
+    $m->field('Attach',  LogoFile);
+    $m->click('AddMoreAttach');
+    is($m->status, 200, "request successful");
+
+    $m->form_name('TicketCreate');
+    $m->field('Attach',  FaviconFile);
+    $m->tick( 'DeleteAttach', LogoFile );
+    $m->field('Subject', 'Attachments test');
+    $m->field('Content', 'Some content');
+
+    $m->submit;
+    is($m->status, 200, "request successful");
+
+    $m->content_contains('Attachments test', 'we have subject on the page');
+    $m->content_contains('Some content', 'and content');
+    $m->content_lacks('Download bpslogo.png', 'page has file name');
+    $m->content_contains('Download favicon.png', 'page has file name');
+}
+
+diag "with one attachment, but delete one along the way";
+{
+    $m->goto_create_ticket( $queue );
+
+    $m->form_name('TicketCreate');
+    $m->field('Attach',  LogoFile);
+    $m->click('AddMoreAttach');
+    is($m->status, 200, "request successful");
+
+    $m->form_name('TicketCreate');
+    $m->tick( 'DeleteAttach', LogoFile );
+    $m->click('AddMoreAttach');
+    is($m->status, 200, "request successful");
+
+    $m->form_name('TicketCreate');
+    $m->field('Attach',  FaviconFile);
+    $m->click('AddMoreAttach');
+    is($m->status, 200, "request successful");
+
+    $m->form_name('TicketCreate');
+    $m->field('Subject', 'Attachments test');
+    $m->field('Content', 'Some content');
+
+    $m->submit;
+    is($m->status, 200, "request successful");
+
+    $m->content_contains('Attachments test', 'we have subject on the page');
+    $m->content_contains('Some content', 'and content');
+    $m->content_lacks('Download bpslogo.png', 'page has file name');
+    $m->content_contains('Download favicon.png', 'page has file name');
+}
+
+diag "reply to a ticket in full interface";
+diag "with one attachment";
+{
+    my $ticket = RT::Test->create_ticket(
+        Queue   => $queue,
+        Subject => 'Attachments test',
+        Content => 'Some content',
+    );
+
+    $m->goto_ticket( $ticket->id );
+    $m->follow_link_ok({text => 'Reply'}, "reply to the ticket");
+    $m->form_name('TicketUpdate');
+    $m->field('Attach',  LogoFile);
+    $m->field('UpdateContent', 'Message');
+    $m->click('SubmitTicket');
+    is($m->status, 200, "request successful");
+
+    $m->content_contains('Download bpslogo.png', 'page has file name');
+}
+
+diag "with two attachments";
+{
+    my $ticket = RT::Test->create_ticket(
+        Queue   => $queue,
+        Subject => 'Attachments test',
+        Content => 'Some content',
+    );
+
+    $m->goto_ticket( $ticket->id );
+    $m->follow_link_ok({text => 'Reply'}, "reply to the ticket");
+    $m->form_name('TicketUpdate');
+    $m->field('Attach',  LogoFile);
+    $m->click('AddMoreAttach');
+    is($m->status, 200, "request successful");
+
+    $m->form_name('TicketUpdate');
+    $m->field('Attach',  FaviconFile);
+    $m->field('UpdateContent', 'Message');
+    $m->click('SubmitTicket');
+    is($m->status, 200, "request successful");
+
+    $m->content_contains('Download bpslogo.png', 'page has file name');
+    $m->content_contains('Download favicon.png', 'page has file name');
+}
+
+diag "with one attachment, delete one along the way";
+{
+    my $ticket = RT::Test->create_ticket(
+        Queue   => $queue,
+        Subject => 'Attachments test',
+        Content => 'Some content',
+    );
+
+    $m->goto_ticket( $ticket->id );
+    $m->follow_link_ok({text => 'Reply'}, "reply to the ticket");
+    $m->form_name('TicketUpdate');
+    $m->field('Attach',  LogoFile);
+    $m->click('AddMoreAttach');
+    is($m->status, 200, "request successful");
+
+    $m->form_name('TicketUpdate');
+    $m->tick('DeleteAttach',  LogoFile);
+    $m->field('Attach',  FaviconFile);
+    $m->field('UpdateContent', 'Message');
+    $m->click('SubmitTicket');
+    is($m->status, 200, "request successful");
+
+    $m->content_lacks('Download bpslogo.png', 'page has file name');
+    $m->content_contains('Download favicon.png', 'page has file name');
+}
+
+diag "jumbo interface";
+diag "with one attachment";
+{
+    my $ticket = RT::Test->create_ticket(
+        Queue   => $queue,
+        Subject => 'Attachments test',
+        Content => 'Some content',
+    );
+
+    $m->goto_ticket( $ticket->id );
+    $m->follow_link_ok({text => 'Jumbo'}, "jumbo the ticket");
+    $m->form_name('TicketModifyAll');
+    $m->field('Attach',  LogoFile);
+    $m->field('UpdateContent', 'Message');
+    $m->click('SubmitTicket');
+    is($m->status, 200, "request successful");
+
+    $m->goto_ticket( $ticket->id );
+    $m->content_contains('Download bpslogo.png', 'page has file name');
+}
+
+diag "with two attachments";
+{
+    my $ticket = RT::Test->create_ticket(
+        Queue   => $queue,
+        Subject => 'Attachments test',
+        Content => 'Some content',
+    );
+
+    $m->goto_ticket( $ticket->id );
+    $m->follow_link_ok({text => 'Jumbo'}, "jumbo the ticket");
+    $m->form_name('TicketModifyAll');
+    $m->field('Attach',  LogoFile);
+    $m->click('AddMoreAttach');
+    is($m->status, 200, "request successful");
+
+    $m->form_name('TicketModifyAll');
+    $m->field('Attach',  FaviconFile);
+    $m->field('UpdateContent', 'Message');
+    $m->click('SubmitTicket');
+    is($m->status, 200, "request successful");
+
+    $m->goto_ticket( $ticket->id );
+    $m->content_contains('Download bpslogo.png', 'page has file name');
+    $m->content_contains('Download favicon.png', 'page has file name');
+}
+
+diag "with one attachment, delete one along the way";
+{
+    my $ticket = RT::Test->create_ticket(
+        Queue   => $queue,
+        Subject => 'Attachments test',
+        Content => 'Some content',
+    );
+
+    $m->goto_ticket( $ticket->id );
+    $m->follow_link_ok({text => 'Jumbo'}, "jumbo the ticket");
+    $m->form_name('TicketModifyAll');
+    $m->field('Attach',  LogoFile);
+    $m->click('AddMoreAttach');
+    is($m->status, 200, "request successful");
+
+    $m->form_name('TicketModifyAll');
+    $m->tick('DeleteAttach',  LogoFile);
+    $m->field('Attach',  FaviconFile);
+    $m->field('UpdateContent', 'Message');
+    $m->click('SubmitTicket');
+    is($m->status, 200, "request successful");
+
+    $m->goto_ticket( $ticket->id );
+    $m->content_lacks('Download bpslogo.png', 'page has file name');
+    $m->content_contains('Download favicon.png', 'page has file name');
+}
+
+diag "bulk update";
+diag "one attachment";
+{
+    my @tickets = RT::Test->create_tickets(
+        {
+            Queue   => $queue,
+            Subject => 'Attachments test',
+            Content => 'Some content',
+        },
+        {},
+        {},
+    );
+    my $query = join ' OR ', map "id=$_", map $_->id, @tickets;
+    $query =~ s/ /%20/g;
+    $m->get_ok( $url . "/Search/Bulk.html?Query=$query&Rows=10" );
+
+    $m->form_name('BulkUpdate');
+    $m->field('Attach',  FaviconFile);
+    $m->field('UpdateContent', 'Message');
+    $m->submit;
+    is($m->status, 200, "request successful");
+
+    foreach my $ticket ( @tickets ) {
+        $m->goto_ticket( $ticket->id );
+        $m->content_lacks('Download bpslogo.png', 'page has file name');
+        $m->content_contains('Download favicon.png', 'page has file name');
+    }
+}
+
+diag "two attachments";
+{
+    my @tickets = RT::Test->create_tickets(
+        {
+            Queue   => $queue,
+            Subject => 'Attachments test',
+            Content => 'Some content',
+        },
+        {},
+        {},
+    );
+    my $query = join ' OR ', map "id=$_", map $_->id, @tickets;
+    $query =~ s/ /%20/g;
+    $m->get_ok( $url . "/Search/Bulk.html?Query=$query&Rows=10" );
+
+    $m->form_name('BulkUpdate');
+    $m->field('Attach',  LogoFile);
+    $m->click('AddMoreAttach');
+    is($m->status, 200, "request successful");
+
+    $m->form_name('BulkUpdate');
+    $m->field('Attach',  FaviconFile);
+    $m->field('UpdateContent', 'Message');
+    $m->submit;
+    is($m->status, 200, "request successful");
+
+    foreach my $ticket ( @tickets ) {
+        $m->goto_ticket( $ticket->id );
+        $m->content_contains('Download bpslogo.png', 'page has file name');
+        $m->content_contains('Download favicon.png', 'page has file name');
+    }
+}
+
+diag "one attachment, delete one along the way";
+{
+    my @tickets = RT::Test->create_tickets(
+        {
+            Queue   => $queue,
+            Subject => 'Attachments test',
+            Content => 'Some content',
+        },
+        {},
+        {},
+    );
+    my $query = join ' OR ', map "id=$_", map $_->id, @tickets;
+    $query =~ s/ /%20/g;
+    $m->get_ok( $url . "/Search/Bulk.html?Query=$query&Rows=10" );
+
+    $m->form_name('BulkUpdate');
+    $m->field('Attach',  LogoFile);
+    $m->click('AddMoreAttach');
+    is($m->status, 200, "request successful");
+
+    $m->form_name('BulkUpdate');
+    $m->tick('DeleteAttach',  LogoFile);
+    $m->field('Attach',  FaviconFile);
+    $m->field('UpdateContent', 'Message');
+    $m->submit;
+    is($m->status, 200, "request successful");
+
+    foreach my $ticket ( @tickets ) {
+        $m->goto_ticket( $ticket->id );
+        $m->content_lacks('Download bpslogo.png', 'page has file name');
+        $m->content_contains('Download favicon.png', 'page has file name');
+    }
+}
+
+diag "self service";
+diag "create with attachment";
+{
+    $m->get_ok( $url . "/SelfService/Create.html?Queue=". $queue->id );
+
+    $m->form_name('TicketCreate');
+    $m->field('Attach',  FaviconFile);
+    $m->field('Content', 'Message');
+    ok(!$m->current_form->find_input('AddMoreAttach'), "one attachment only");
+    $m->submit;
+    is($m->status, 200, "request successful");
+
+    $m->content_contains('Download favicon.png', 'page has file name');
+}
+
+diag "update with attachment";
+{
+    my $ticket = RT::Test->create_ticket(
+        Queue   => $queue,
+        Subject => 'Attachments test',
+        Content => 'Some content',
+    );
+
+    $m->get_ok( $url . "/SelfService/Update.html?id=". $ticket->id );
+    $m->form_name('TicketUpdate');
+    $m->field('Attach',  FaviconFile);
+    $m->field('UpdateContent', 'Message');
+    ok(!$m->current_form->find_input('AddMoreAttach'), "one attachment only");
+    $m->submit;
+    is($m->status, 200, "request successful");
+
+    $m->content_contains('Download favicon.png', 'page has file name');
+}
+
+diag "mobile ui";
+
+diag "simple create + reply";
+{
+    $m->get_ok( $url . '/m/ticket/create?Queue=' . $queue->id );
+
+    $m->form_name('TicketCreate');
+    $m->field('Subject', 'Attachments test');
+    $m->field('Attach',  LogoFile);
+    $m->field('Content', 'Some content');
+    $m->submit;
+    is($m->status, 200, "request successful");
+
+    $m->content_contains('Attachments test', 'we have subject on the page');
+    $m->content_contains('bpslogo.png', 'page has file name');
+
+    $m->follow_link_ok({text => 'Reply'}, "reply to the ticket");
+    $m->form_name('TicketUpdate');
+    $m->field('Attach',  LogoFile);
+    $m->click('AddMoreAttach');
+    is($m->status, 200, "request successful");
+
+    $m->form_name('TicketUpdate');
+    $m->field('Attach',  FaviconFile);
+    $m->field('UpdateContent', 'Message');
+    $m->click('SubmitTicket');
+    is($m->status, 200, "request successful");
+
+    $m->content_contains('bpslogo.png', 'page has file name');
+    $m->content_contains('favicon.png', 'page has file name');
+}
+
+
+diag "check content type and content";
+{
+    $m->goto_create_ticket( $queue );
+
+    $m->form_name('TicketCreate');
+    $m->field('Attach',  LogoFile);
+    $m->click('AddMoreAttach');
+    is($m->status, 200, "request successful");
+
+    $m->form_name('TicketCreate');
+    $m->field('Attach',  TextFile);
+    $m->field('Subject', 'Attachments test');
+    $m->field('Content', 'Some content');
+
+    $m->submit;
+    is($m->status, 200, "request successful");
+
+    $m->content_contains('Attachments test', 'we have subject on the page');
+    $m->content_contains('Some content', 'and content');
+    $m->content_contains('Download bpslogo.png', 'page has file name');
+    $m->content_contains('Download print.css', 'page has file name');
+
+    $m->follow_link_ok({text => "Download bpslogo.png"});
+    is($m->response->header('Content-Type'), 'image/png', 'Content-Type of png lacks charset' );
+    is($m->content_type, "image/png");
+    is($m->content, RT::Test->file_content(LogoFile), "Binary content matches");
+    $m->back;
+
+    $m->follow_link_ok( { text => 'Download print.css' } );
+    is( $m->response->header('Content-Type'),
+        'text/css;charset=UTF-8',
+        'Content-Type of text has charset',
+    );
+    is($m->content_type, "text/css");
+    is($m->content, RT::Test->file_content(TextFile), "Text content matches");
+}
+
+diag "concurent actions";
+my $m2 = RT::Test::Web->new;
+ok $m2->login, 'second login';
+
+diag "update and create";
+{
+    my $ticket = RT::Test->create_ticket(
+        Queue   => $queue,
+        Subject => 'Attachments test',
+        Content => 'Some content',
+    );
+
+    $m2->goto_ticket( $ticket->id );
+    $m2->follow_link_ok({text => 'Reply'}, "reply to the ticket");
+    $m2->form_name('TicketUpdate');
+    $m2->field('Attach',  LogoFile);
+    $m2->click('AddMoreAttach');
+    is($m2->status, 200, "request successful");
+
+    $m->goto_create_ticket( $queue );
+
+    $m->form_name('TicketCreate');
+    $m->field('Attach',  FaviconFile);
+    $m->field('Subject', 'Attachments test');
+    $m->field('Content', 'Some content');
+    $m->submit;
+    is($m->status, 200, "request successful");
+
+    $m->content_lacks('Download bpslogo.png', 'page has file name');
+    $m->content_contains('Download favicon.png', 'page has file name');
+
+    $m2->form_name('TicketUpdate');
+    $m2->click('SubmitTicket');
+    $m2->content_contains('Download bpslogo.png', 'page has file name');
+    $m2->content_lacks('Download favicon.png', 'page has no file name');
+}
+

commit 4defa479fda6de42917c0dad18a9414239f74e34
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Fri Nov 23 13:05:55 2012 +0400

    port CSRF protection over new attachments code

diff --git a/lib/RT/Interface/Web.pm b/lib/RT/Interface/Web.pm
index a43c3f7..31a43b2 100644
--- a/lib/RT/Interface/Web.pm
+++ b/lib/RT/Interface/Web.pm
@@ -1488,7 +1488,7 @@ sub ExpandCSRFToken {
     if ($data->{attach}) {
         my $filename = $data->{attach}{filename};
         my $mime     = $data->{attach}{mime};
-        $HTML::Mason::Commands::session{'Attachments'}{$filename}
+        $HTML::Mason::Commands::session{'Attachments'}{$ARGS->{'Token'}||''}{$filename}
             = $mime;
     }
 
diff --git a/t/web/csrf.t b/t/web/csrf.t
index 753bef5..99f4e03 100644
--- a/t/web/csrf.t
+++ b/t/web/csrf.t
@@ -101,7 +101,6 @@ my $link = $m->find_link(text_regex => qr{resume your request});
 $m->get_ok($broken_url);
 $m->content_like(qr/Queue\s+could not be loaded/);
 $m->title_is('RT Error');
-$m->next_warning_like(qr/Use of uninitialized value/);
 $m->next_warning_like(qr/Queue\s+could not be loaded/);
 $m->no_leftover_warnings_ok;
 

commit 8a046fd26d46ecf4e00063bf38af978239ecda3b
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Fri Nov 23 17:03:30 2012 +0400

    ignore system dependend warning in a test
    
    I don't see uninit warning, but our smoking system
    does. Test for warning we expect and throw red flag
    if it's anything besides uninit and expected.

diff --git a/t/web/csrf.t b/t/web/csrf.t
index 99f4e03..29cec83 100644
--- a/t/web/csrf.t
+++ b/t/web/csrf.t
@@ -101,8 +101,12 @@ my $link = $m->find_link(text_regex => qr{resume your request});
 $m->get_ok($broken_url);
 $m->content_like(qr/Queue\s+could not be loaded/);
 $m->title_is('RT Error');
-$m->next_warning_like(qr/Queue\s+could not be loaded/);
-$m->no_leftover_warnings_ok;
+{ # XXX: on some systems it can be combination of uninit and Queue warns
+    my @warnings = $m->get_warnings;
+    ok grep /Queue\s+could not be loaded/, @warnings;
+    ok !grep !/Queue\s+could not be loaded|Use of uninitialized value/,
+        @warnings;
+}
 
 # The token doesn't work for other pages, or other arguments to the same page.
 $m->add_header(Referer => undef);

-----------------------------------------------------------------------


More information about the Rt-commit mailing list