[Rt-commit] rt branch, experimental/allow-loops, created. rt-4.0.14rc1-27-g5374bdd

Alex Vandiver alexmv at bestpractical.com
Tue Jul 23 22:24:59 EDT 2013


The branch, experimental/allow-loops has been created
        at  5374bdd7c39285c3f15e47097dbcf8bb91deb8c6 (commit)

- Log -----------------------------------------------------------------
commit b523f1f2293856346cf101f73dd3230164eace37
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Tue Sep 29 19:38:21 2009 -0400

    Add LinkSelfLoops configuration, to allow internal linking of self loops
    
    Incoming mail messages whose ticket and queue disagree are inspected
    to determine if this is a message from ourself, and then to create or
    associate the incoming mail with a ticket in the queue.
    Add tests for LinkSelfLoops

diff --git a/lib/RT/Action/SendEmail.pm b/lib/RT/Action/SendEmail.pm
index 0ff7e6d..600b800 100644
--- a/lib/RT/Action/SendEmail.pm
+++ b/lib/RT/Action/SendEmail.pm
@@ -568,6 +568,7 @@ sub SetRTSpecialHeaders {
         if ( RT->Config->Get('EmailOutputEncoding') );
     $self->SetReturnAddress();
     $self->SetReferencesHeaders();
+    $self->SetHeader("X-RT-Transaction-Id" => $self->TransactionObj->Id );
 
     unless ( $self->TemplateObj->MIMEObj->head->get('Message-ID') ) {
 
@@ -608,6 +609,7 @@ sub SetRTSpecialHeaders {
     }
 
     $self->SetHeader( 'X-RT-Loop-Prevention', RT->Config->Get('rtname') );
+    $self->SetHeader( 'X-RT-Allow-Self-Loops', 1) if RT->Config->Get('LinkSelfLoops');
     $self->SetHeader( 'RT-Ticket',
         RT->Config->Get('rtname') . " #" . $self->TicketObj->id() );
     $self->SetHeader( 'Managed-by',
diff --git a/lib/RT/EmailParser.pm b/lib/RT/EmailParser.pm
index 2644575..eaa2e2c 100644
--- a/lib/RT/EmailParser.pm
+++ b/lib/RT/EmailParser.pm
@@ -363,6 +363,8 @@ sub CullRTAddresses {
     my $self = shift;
     my @addresses = (@_);
 
+    return @addresses if RT->Config->Get('LinkSelfLoops');
+
     return grep { !$self->IsRTAddress($_) } @addresses;
 }
 
diff --git a/lib/RT/Interface/Email.pm b/lib/RT/Interface/Email.pm
index f259a76..ee307c5 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -108,6 +108,10 @@ Takes a HEAD object of L<MIME::Head> class and returns true if the
 message's been sent by this RT instance. Uses "X-RT-Loop-Prevention"
 field of the head for test.
 
+If C<X-RT-Allow-Self-Loops> is set on the message, and it from our own
+instance, and the C<LinkSelfLoops> configuration option is set,
+returns -1.
+
 =cut
 
 sub CheckForLoops {
@@ -117,12 +121,16 @@ sub CheckForLoops {
     my $RTLoop = $head->get("X-RT-Loop-Prevention") || "";
     chomp ($RTLoop); # remove that newline
     if ( $RTLoop eq RT->Config->Get('rtname') ) {
-        return 1;
+        if ( $head->get('X-RT-Allow-Self-Loops') and RT->Config->Get('LinkSelfLoops') ) {
+            return -1;
+        } else {
+            return 1;
+        }
     }
 
     # TODO: We might not trap the case where RT instance A sends a mail
     # to RT instance B which sends a mail to ...
-    return undef;
+    return 0;
 }
 
 =head2 CheckForSuspiciousSender HEAD
@@ -1455,14 +1463,15 @@ sub Gateway {
 
     my $MessageId = $head->get('Message-ID')
         || "<no-message-id-". time . rand(2000) .'@'. RT->Config->Get('Organization') .'>';
+    chomp $MessageId;
 
     #Pull apart the subject line
     my $Subject = $head->get('Subject') || '';
     chomp $Subject;
     
     # Lets check for mail loops of various sorts.
-    my ($should_store_machine_generated_message, $IsALoop, $result);
-    ( $should_store_machine_generated_message, $ErrorsTo, $result, $IsALoop ) =
+    my ($IsALoop, $result);
+    ( undef, $ErrorsTo, $result, $IsALoop ) =
       _HandleMachineGeneratedMail(
         Message  => $Message,
         ErrorsTo => $ErrorsTo,
@@ -1472,9 +1481,10 @@ sub Gateway {
 
     # Do not pass loop messages to MailPlugins, to make sure the loop
     # is broken, unless $RT::StoreLoops is set.
-    if ($IsALoop && !$should_store_machine_generated_message) {
+    if ($IsALoop > 0) {
         return ( 0, $result, undef );
     }
+
     # }}}
 
     $args{'ticket'} ||= ExtractTicketId( $Message );
@@ -1543,11 +1553,6 @@ sub Gateway {
         );
     }
 
-
-    unless ($should_store_machine_generated_message) {
-        return ( 0, $result, undef );
-    }
-
     # if plugin's updated SystemTicket then update arguments
     $args{'ticket'} = $SystemTicket->Id if $SystemTicket && $SystemTicket->Id;
 
@@ -1555,41 +1560,27 @@ sub Gateway {
 
     if ( !$args{'ticket'} && grep /^(comment|correspond)$/, @actions )
     {
+        # This might be a creation we already saw; check if we've seen
+        # the message-id in this queue recently.
 
-        my @Cc;
-        my @Requestors = ( $CurrentUser->id );
-
-        if (RT->Config->Get('ParseNewMessageForTicketCcs')) {
-            @Cc = ParseCcAddressesFromHead(
-                Head        => $head,
-                CurrentUser => $CurrentUser,
-                QueueObj    => $SystemQueueObj
-            );
+        if (my $other = RecentMessage( $MessageId, queue => $SystemQueueObj->Id )) {
+            warn "Found dup, ticket is @{[$other->Id]}";
+            return ( 1, "Duplicate self-loop delivery (#@{[$other->Id]}))", $other );
         }
 
         $head->replace('X-RT-Interface' => 'Email');
 
-        my ( $id, $Transaction, $ErrStr ) = $Ticket->Create(
-            Queue     => $SystemQueueObj->Id,
-            Subject   => $NewSubject,
-            Requestor => \@Requestors,
-            Cc        => \@Cc,
-            MIMEObj   => $Message
+        my @ret = CreateTicket(
+            CurrentUser => $CurrentUser,
+            Message     => $Message,
+            QueueObj    => $SystemQueueObj,
+            ErrorsTo    => $ErrorsTo,
         );
-        if ( $id == 0 ) {
-            MailError(
-                To          => $ErrorsTo,
-                Subject     => "Ticket creation failed: $Subject",
-                Explanation => $ErrStr,
-                MIMEObj     => $Message
-            );
-            return ( 0, "Ticket creation failed: $ErrStr", $Ticket );
-        }
-
+        return @ret unless $ret[0];
         # strip comments&corresponds from the actions we don't need
         # to record them if we've created the ticket just now
         @actions = grep !/^(comment|correspond)$/, @actions;
-        $args{'ticket'} = $id;
+        ($args{'ticket'}, undef, $Ticket) = @ret;
 
     } elsif ( $args{'ticket'} ) {
 
@@ -1613,10 +1604,36 @@ sub Gateway {
     # }}}
 
     my $unsafe_actions = RT->Config->Get('UnsafeEmailCommands');
+    my $return = $Ticket->Id;
     foreach my $action (@actions) {
 
         #   If the action is comment, add a comment.
         if ( $action =~ /^(?:comment|correspond)$/i ) {
+
+            # Check if this is an internal self-loop
+            if (    $Ticket->QueueObj->Id != $SystemQueueObj->Id
+                and $IsALoop < 0 )
+            {
+                my @ret = LinkSelfLoops(
+                    Ticket      => $Ticket,
+                    QueueObj    => $SystemQueueObj,
+                    Message     => $Message,
+                    ErrorsTo    => $ErrorsTo,
+                    CurrentUser => $CurrentUser,
+                );
+                if (not $ret[0]) {
+                    # If it failed, return the error
+                    return @ret;
+                } elsif (ref $ret[0]) {
+                    # Returns the object to comment on
+                    $Ticket = $ret[0];
+                } else {
+                    # Or a simple true value to drop on the floor
+                    $return = $ret[0];
+                    next;
+                }
+            }
+
             my $method = ucfirst lc $action;
             my ( $status, $msg ) = $Ticket->$method( MIMEObj => $Message );
             unless ($status) {
@@ -1641,9 +1658,176 @@ sub Gateway {
             return ($status, $msg, $Ticket) unless $status == 1;
         }
     }
+    $Ticket->Load($return);
     return ( 1, "Success", $Ticket );
 }
 
+sub RecentMessage {
+    my ($messageid, $type, $id) = @_;
+    my $messages = RT::Attachments->new($RT::SystemUser);
+    $messages->Limit( FIELD => 'messageid', VALUE => $messageid );
+    my $txns = $messages->Join(
+        ALIAS1 => 'main',
+        FIELD1 => 'transactionid',
+        TABLE2 => 'Transactions',
+        FIELD2 => 'id',
+    );
+    $messages->Limit(
+        ALIAS => $txns,
+        FIELD => 'objecttype',
+        VALUE => 'RT::Ticket',
+    );
+    if ( $type eq "queue" ) {
+        my $tickets = $messages->Join(
+            ALIAS1 => $txns,
+            FIELD1 => 'objectid',
+            TABLE2 => 'Tickets',
+            FIELD2 => 'id',
+        );
+        $messages->Limit(
+            ALIAS => $tickets,
+            FIELD => 'queue',
+            VALUE => $id,
+        );
+    } else {
+        $messages->Limit(
+            ALIAS => $txns,
+            FIELD => 'objectid',
+            VALUE => $id,
+        );
+    }
+
+    my $first = $messages->First;
+    return $first ? $first->TransactionObj->Object : undef
+}
+
+
+=head2 LinkSelfLoops
+
+=cut
+
+sub LinkSelfLoops {
+    my %args = (
+        Ticket      => undef,
+        QueueObj    => undef,
+        Message     => undef,
+        ErrorsTo    => undef,
+        CurrentUser => undef,
+        @_
+    );
+
+    # Determine original message-id by looking at our headers, looking
+    # up that txn, and chasing the txn's attachment's headers, etc..
+    my $txn = RT::Transaction->new( $RT::SystemUser );
+    $txn->Load( $args{Message}->head->get('X-RT-Transaction-Id') );
+    my $origid;
+    while ($txn->Id) {
+        last unless my $attach = $txn->Attachments->First;
+        $origid = $attach->MessageId;
+        last unless my $txnid = $attach->GetHeader("X-RT-Transaction-Id");
+        $txn = RT::Transaction->new( $RT::SystemUser );
+        $txn->Load($txnid);
+    }
+
+    die "No original message-id" unless $origid;
+    chomp $origid;
+
+    my $orig = $txn->Attachments->First->ContentAsMIME;
+    for my $header (qw/Subject Content-Type Content-Transfer-Encoding Content-Length MIME-Version/) {
+        next unless $orig->head->count($header);
+        $args{Message}->head->set($header, $orig->head->get($header))
+    }
+    $args{Message}->bodyhandle( $orig->bodyhandle );
+    $args{Message}->parts( [$orig->parts] );
+
+    # Look for linked tickets in the given queue
+    my @links = map {$_->Content}
+        $args{Ticket}->Attributes->Named('InternalLinks-'.$args{QueueObj}->Id);
+
+    if (not @links) {
+        # No existing link between ticket and that queue.  Look for
+        # the message-id in the queue
+        my $other = RecentMessage($origid, queue => $args{QueueObj}->Id);
+        unless ($other) {
+            # If we didn't find it, create it
+            my @ret = CreateTicket(
+                CurrentUser => $args{CurrentUser},
+                Message     => $args{Message},
+                QueueObj    => $args{QueueObj},
+                ErrorsTo    => $args{ErrorsTo},
+            );
+            return @ret unless $ret[0];
+            (undef, undef, $other) = @ret;
+        }
+        # Regardless, link them now.
+        $args{Ticket}->AddAttribute(
+            Name    => "InternalLinks-" . $other->Queue,
+            Content => $other->Id,
+        );
+        $other->AddAttribute(
+            Name    => "InternalLinks-" . $args{Ticket}->Queue,
+            Content => $args{Ticket}->Id,
+        );
+        # ..and drop the message on the floor.
+        return $other->Id;
+    }
+
+    if (@links > 1) {
+        die "More than one link to queue @{[$args{QueueObj}->Name]}: @links";
+    }
+
+    # We found a specific linked ticket in the given queue.  See if it
+    # has seen the original message-id yet -- if so, drop on the floor.
+    my $other = RT::Ticket->new( $args{CurrentUser} );
+    $other->Load(@links);
+    return $args{Ticket}->Id if RecentMessage($origid, ticket => $other->id);
+
+    # Otherwise, the comment/correspond goes on the linked ticket
+    return $other;
+}
+
+sub CreateTicket {
+    my %args = (
+        CurrentUser => undef,
+        Message     => undef,
+        QueueObj    => undef,
+        ErrorsTo    => undef,
+        @_,
+    );
+    my @Cc;
+    my @Requestors = ( $args{CurrentUser}->id );
+
+    if (RT->Config->Get('ParseNewMessageForTicketCcs')) {
+        @Cc = ParseCcAddressesFromHead(
+            Head        => $args{Message}->head,
+            CurrentUser => $args{CurrentUser},
+            QueueObj    => $args{QueueObj},
+        );
+    }
+
+    my $Subject = $args{Message}->head->get('Subject') || '';
+    chomp $Subject;
+
+    my $Ticket = RT::Ticket->new($args{CurrentUser});
+    my ( $id, $Transaction, $ErrStr ) = $Ticket->Create(
+        Queue     => $args{QueueObj}->Id,
+        Subject   => $Subject,
+        Requestor => \@Requestors,
+        Cc        => \@Cc,
+        MIMEObj   => $args{Message},
+    );
+    if ( $id == 0 ) {
+        MailError(
+            To          => $args{ErrorsTo},
+            Subject     => "Ticket creation failed: $Subject",
+            Explanation => $ErrStr,
+            MIMEObj     => $args{Message}
+        );
+        return ( 0, "Ticket creation failed: $ErrStr", $Ticket );
+    }
+    return ($id, "Ticket created", $Ticket);
+}
+
 =head2 GetAuthenticationLevel
 
     # Authentication Level
@@ -1839,13 +2023,13 @@ sub _HandleMachineGeneratedMail {
 
     #If the message is autogenerated, we need to know, so we can not
     # send mail to the sender
-    if ( $IsBounce || $IsSuspiciousSender || $IsAutoGenerated || $IsALoop ) {
+    if ( $IsBounce || $IsSuspiciousSender || $IsAutoGenerated || ($IsALoop > 0) ) {
         $SquelchReplies = 1;
         $ErrorsTo       = $owner_mail;
     }
 
     # Warn someone if it's a loop, before we drop it on the ground
-    if ($IsALoop) {
+    if ($IsALoop > 0) {
         $RT::Logger->crit("RT Received mail (".$args{MessageId}.") from itself.");
 
         #Should we mail it to RTOwner?
@@ -1873,7 +2057,7 @@ sub _HandleMachineGeneratedMail {
         $head->delete('RT-Squelch-Replies-To');
     }
 
-    if ($SquelchReplies) {
+    if ($SquelchReplies and $IsALoop >= 0) {
 
         # Squelch replies to the sender, and also leave a clue to
         # allow us to squelch ALL outbound messages. This way we
diff --git a/t/mail/linkselfloops.t b/t/mail/linkselfloops.t
new file mode 100644
index 0000000..28e48dd
--- /dev/null
+++ b/t/mail/linkselfloops.t
@@ -0,0 +1,742 @@
+use strict;
+use warnings;
+
+use RT::Test config => 'Set( $LinkSelfLoops, 1 );', tests => undef;
+
+my ($baseurl, $m) = RT::Test->started_ok;
+
+# Set up queues
+my $systems = RT::Test->load_or_create_queue(
+    Name              => 'Systems',
+    CorrespondAddress => 'systems at example.com',
+    CommentAddress    => 'systems-comment at example.com',
+);
+ok $systems && $systems->id, 'loaded or created systems queue';
+
+my $helpdesk = RT::Test->load_or_create_queue(
+    Name              => 'Helpdesk',
+    CorrespondAddress => 'helpdesk at example.com',
+    CommentAddress    => 'helpdesk-comment at example.com',
+);
+ok $helpdesk && $helpdesk->id, 'loaded or created helpdesk queue';
+
+# ...and rights
+RT::Test->set_rights(
+    {   Principal => 'Everyone',
+        Right     => [
+            'CreateTicket',
+            'ShowTicket',
+            'SeeQueue',
+            'ReplyToTicket',
+            'CommentOnTicket',
+            'ModifyTicket'
+        ],
+    },
+    {   Principal => 'Privileged',
+        Right     => ['TakeTicket', 'OwnTicket', 'ShowTicketComments'],
+    }
+);
+
+# ...and users
+my $bjoern = RT::Test->load_or_create_user(
+    EmailAddress => 'bjoern at example.com',
+);
+
+my $sven = RT::Test->load_or_create_user(
+    EmailAddress => 'sven at example.com',
+);
+my ($ok, $msg);
+($ok, $msg) = $helpdesk->AddWatcher( Type => 'AdminCc', Email => 'bjoern at example.com' );
+ok($ok, "Added bjoern as admincc on helpdesk - $msg");
+($ok, $msg) = $systems->AddWatcher( Type => 'AdminCc', Email => 'sven at example.com' );
+ok($ok, "Added sven as admincc on systems - $msg");
+
+sub reinsert {
+    my $text = shift;
+    my $mime = parse_mail($text);
+    return (0, 0) unless ($mime->head->get("To")||"") =~ /(helpdesk|systems)(?:-(comment))?\@example\.com/
+        or ($mime->head->get("Cc")||"") =~ /(helpdesk|systems)(?:-(comment))?\@example\.com/
+        or ($mime->head->get("Bcc")||"") =~ /(helpdesk|systems)(?:-(comment))?\@example\.com/;
+
+    my ($queue, $action) = (ucfirst($1), $2 || "correspond");
+    return RT::Test->send_via_mailgate( $text, queue => $queue, action => $action );
+}
+
+sub got_mail_ok {
+    local $Test::Builder::Level = $Test::Builder::Level + 1;
+    my @messages = @_;
+    my @mails = RT::Test->fetch_caught_mails;
+    is( scalar(@mails), scalar(@messages) );
+    for my $msg (@messages) {
+        my $text = shift @mails;
+        my $mail = parse_mail($text);
+        my $fail;
+        for (grep {$_ ne "id"} keys %{$msg}) {
+            like($mail->head->get($_), $msg->{$_}, "$_ contains @{[$msg->{$_}]}") or $fail++;
+        }
+        warn $text if $fail;
+        my ($status, $id) = reinsert($text);
+        if ($msg->{id}) {
+            is ($status >> 8, 0, "The mail gateway exited normally");
+            is ($id, $msg->{id}, "Created ticket");
+        }
+    }
+    warn $_ for @mails;
+}
+
+sub record_ok {
+    local $Test::Builder::Level = $Test::Builder::Level + 1;
+    my %args = (
+        mode => 'comment',
+        on => undef,
+        as => undef,
+        cc => undef,
+        @_,
+    );
+
+    $args{as} ||= $args{on}->CurrentUser;
+
+    my $ticket = RT::Ticket->new( $args{as} );
+    $ticket->Load( ref $args{on} ? $args{on}->Id  : $args{on} );
+    my $method = ucfirst lc $args{mode};
+
+    my $caller = join(":", caller);
+    my $mime = MIME::Entity->build(
+        Data => $caller,
+        "Message-Id" => "$caller\@example.com",
+        Subject => $caller,
+    );
+    my ($id, $status) = $ticket->$method(
+        MIMEObj => $mime,
+        CcMessageTo => $args{cc},
+    );
+    ok($id, "Added $method on @{[$ticket->Id]} as @{[$args{as}->EmailAddress]})");
+}
+
+# =====> SITUATION 1:  Helpdesk queue adds systems@ as a one-time cc on a
+# comment (helpdesk gets all correspondence from systems, as a comment;  
+# systems only gets mail explicitly one-time-CC'd to them)
+
+{
+    # Joe sends mail to helpdesk@
+    my $text = <<EOF;
+From: joe\@example.com
+To: helpdesk\@example.com
+Subject: This is a test of new ticket creation
+Message-Id: first\@example.com
+
+A helpdesk ticket
+EOF
+
+    my ($status, $id) = RT::Test->send_via_mailgate($text, queue => 'Helpdesk');
+    is ($status >> 8, 0, "The mail gateway exited normally");
+    ok ($id, "Created ticket");
+    my ($helpticket, $systicket) = ($id, $id + 1);
+
+    # Sends mail back to Joe, and to Bjoern
+    got_mail_ok( { to => qr/joe\@/ }, { bcc => qr/bjoern\@/} );
+
+    # Ticket #101 is created in the helpdesk queue with joe as the requestor
+    my $ticket = RT::Test->last_ticket( $bjoern );
+    isa_ok ($ticket, 'RT::Ticket');
+    is ($ticket->Id, $id, "correct ticket id");
+    is ($ticket->Subject , 'This is a test of new ticket creation', "Created the ticket");
+
+    # Bjoern adds a comment with systems@ as a one-time CC.
+    record_ok( mode => 'comment', on => $ticket, as => $bjoern, cc => 'systems at example.com');
+
+    # RT sends mail to systems@
+    got_mail_ok(
+        {   from => qr/helpdesk-comment\@/,
+            cc   => qr/systems\@/,
+            id   => $systicket,
+        },
+    );
+
+    # Ticket #102 is created in the system queue with helpdesk-comment@ as the requestor
+    $ticket = RT::Test->last_ticket( $sven );
+    is($ticket->RequestorAddresses, 'helpdesk-comment at example.com');
+
+    # Auto-reply goes back to #101, and to sven
+    got_mail_ok(
+        {   from => qr/systems\@/,
+            to   => qr/helpdesk-comment\@/,
+            id   => $systicket,
+        },
+        {   from => qr/systems\@/,
+            bcc  => qr/sven\@/,
+        },
+    );
+
+    # Auto-reply gets dropped
+    got_mail_ok();
+    is(RT::Test->last_ticket->id, $ticket->id);
+
+    # Ticket #102 is now linked to ticket #101
+
+    # Sven adds a comment on #102
+    record_ok( mode => 'comment', on => $systicket, as => $sven );
+
+    # No mail is sent out (Sven would get it, but NotifyActor is not set)
+    got_mail_ok();
+
+    # Sven adds a correspondence on #102
+    record_ok( mode => 'correspond', on => $systicket, as => $sven );
+
+    # helpdesk-comment is notified
+    got_mail_ok(
+        {   from => qr/systems\@/,
+            to   => qr/helpdesk-comment\@/,
+            id   => $systicket,
+        },
+    );
+
+    # Which notifies Bjoern
+    got_mail_ok(
+        {   from => qr/helpdesk-comment\@/,
+            bcc  => qr/bjoern\@/,
+        },
+    );
+
+    # Bjoern adds a comment on #101
+    record_ok( mode => 'comment', on => $helpticket, as => $bjoern );
+
+    #   AdminCCs on #101 are notified
+    got_mail_ok();
+
+    # Bjoern adds a correspondence on #101
+    record_ok( mode => 'correspond', on => $helpticket, as => $bjoern );
+
+    #   Joe and AdminCCs on #101 are notified
+    got_mail_ok(
+        {   from => qr/helpdesk\@/,
+            to   => qr/joe\@/,
+        },
+    );
+
+    # Bjoern adds a comment on #101, choosing to one-time-CC systems@
+    record_ok( mode => 'comment', on => $helpticket, as => $bjoern, cc => 'systems at example.com' );
+
+    #   AdminCCs on #101 are notified, as is systems@,
+    got_mail_ok(
+        {   from => qr/helpdesk-comment\@/,
+            cc   => qr/systems\@/,
+            id   => $helpticket,
+        },
+    );
+
+    #   which notifies Sven and AdminCCs on #102
+    got_mail_ok(
+        {   from => qr/systems\@/,
+            bcc  => qr/sven\@/,
+        },
+    );
+
+    # Bjoern adds a correspondence on #101, choosing to one-time-CC systems@
+    record_ok( mode => 'correspond', on => $helpticket, as => $bjoern, cc => 'systems at example.com' );
+
+    #   Joe and AdminCCs on #101 are notified, as is systems@,
+    got_mail_ok(
+        {   from => qr/helpdesk\@/,
+            cc   => qr/systems\@/,
+            id   => $helpticket,
+        },
+        {   from => qr/helpdesk\@/,
+            to   => qr/joe\@/,
+        },
+    );
+
+    #   which notifies Sven and AdminCCs on #102
+    got_mail_ok(
+        {   from => qr/systems\@/,
+            bcc  => qr/sven\@/,
+        },
+        {   from => qr/systems\@/,
+            to   => qr/helpdesk-comment\@/,
+            id   => $systicket,
+        },
+    );
+
+    # Mail to helpdesk-comment gets droppped
+    got_mail_ok();
+}
+
+# ======> SITUATION 2:  Helpdesk queue adds systems@ as a one-time cc on
+# a correspondence
+# (The result is identical to the above, with the exception of the
+# original email)
+{
+    # Joe sends mail to helpdesk@
+    my $text = <<EOF;
+From: joe\@example.com
+To: helpdesk\@example.com
+Subject: This is a test of new ticket creation
+Message-Id: second\@example.com
+
+A helpdesk ticket
+EOF
+
+    my ($status, $id) = RT::Test->send_via_mailgate($text, queue => 'Helpdesk');
+    is ($status >> 8, 0, "The mail gateway exited normally");
+    ok ($id, "Created ticket");
+    my ($helpticket, $systicket) = ($id, $id + 1);
+
+    # Sends mail back to Joe, and to Bjoern
+    got_mail_ok( { to => qr/joe\@/ }, { bcc => qr/bjoern\@/} );
+
+    # Ticket #201 is created in the helpdesk queue with joe as the requestor
+    my $ticket = RT::Test->last_ticket( $bjoern );
+    isa_ok ($ticket, 'RT::Ticket');
+    is ($ticket->Id, $id, "correct ticket id");
+    is ($ticket->Subject , 'This is a test of new ticket creation', "Created the ticket");
+
+    # Bjoern adds a correspondence with systems@ as a one-time CC.
+    record_ok( mode => 'correspond', on => $ticket, as => $bjoern, cc => 'systems at example.com');
+
+    # RT sends mail to joe and systems@
+    got_mail_ok(
+        {   from => qr/helpdesk\@/,
+            cc   => qr/systems\@/,
+            id   => $systicket,
+        },
+        {   from => qr/helpdesk\@/,
+            to   => qr/joe\@/,
+        },
+    );
+
+    # Ticket #202 is created in the system queue with helpdesk@ as the requestor
+    $ticket = RT::Test->last_ticket( $sven );
+    is($ticket->RequestorAddresses, 'helpdesk at example.com');
+
+    # Auto-reply goes back to #201, and to sven
+    got_mail_ok(
+        {   from => qr/systems\@/,
+            to   => qr/helpdesk\@/,
+            id   => $systicket,
+        },
+        {   from => qr/systems\@/,
+            bcc  => qr/sven\@/,
+        },
+    );
+
+    # Auto-reply gets dropped
+    got_mail_ok();
+    is(RT::Test->last_ticket->id, $ticket->id);
+
+    # Ticket #202 is linked to ticket #201
+
+    # Sven adds a comment on #202
+    record_ok( mode => 'comment', on => $systicket, as => $sven );
+
+    # No mail is sent out (Sven would get it, but NotifyActor is not set)
+    got_mail_ok();
+
+    # Sven adds a correspondence on #202
+    record_ok( mode => 'correspond', on => $systicket, as => $sven );
+
+    # AdminCCs on #202 are notified, as is helpdesk@,
+    got_mail_ok(
+        {   from => qr/systems\@/,
+            to   => qr/helpdesk\@/,
+            id   => $systicket,
+        },
+    );
+
+    # Which notifies Joe, Bjoern, and AdminCCs on #201
+    got_mail_ok(
+        {   from => qr/helpdesk\@/,
+            bcc  => qr/bjoern\@/,
+        },
+        {   from => qr/helpdesk\@/,
+            to   => qr/joe\@/,
+        },
+    );
+
+    # Bjoern adds a comment on #201
+    record_ok( mode => 'comment', on => $helpticket, as => $bjoern );
+
+    #   AdminCCs on #201 are notified
+    got_mail_ok();
+
+    # Bjoern adds a correspondence on #201
+    record_ok( mode => 'correspond', on => $helpticket, as => $bjoern );
+
+    #   Joe and AdminCCs on #201 are notified
+    got_mail_ok(
+        {   from => qr/helpdesk\@/,
+            to   => qr/joe\@/,
+        },
+    );
+
+    # Bjoern adds a comment on #201, choosing to one-time-CC systems@
+    record_ok( mode => 'comment', on => $helpticket, as => $bjoern, cc => 'systems at example.com' );
+
+    #   AdminCCs on #201 are notified, as is systems@,
+    got_mail_ok(
+        {   from => qr/helpdesk-comment\@/,
+            cc   => qr/systems\@/,
+            id   => $helpticket,
+        },
+    );
+    #   which notifies Sven and AdminCCs on #202 (and the "requestor"
+    # helpdesk@, since it came from helpdesk-comment@, not helpdesk@,
+    # so NotifyActor isn't relevant!)
+    got_mail_ok(
+        {   from => qr/systems\@/,
+            bcc  => qr/sven\@/,
+        },
+        {   from => qr/systems\@/,
+            to   => qr/helpdesk\@/,
+            id   => $systicket,
+        },
+    );
+
+    # Mail to helpdesk gets droppped
+    got_mail_ok();
+
+    # Bjoern adds a correspondence on #201, choosing to one-time-CC systems@
+    record_ok( mode => 'correspond', on => $helpticket, as => $bjoern, cc => 'systems at example.com' );
+    #   Joe and AdminCCs on #201 are notified, as is systems@,
+    got_mail_ok(
+        {   from => qr/helpdesk\@/,
+            cc   => qr/systems\@/,
+            id   => $helpticket,
+        },
+        {   from => qr/helpdesk\@/,
+            to   => qr/joe\@/,
+        },
+    );
+    #   which notifies Sven and AdminCCs on #202
+    got_mail_ok(
+        {   from => qr/systems\@/,
+            bcc  => qr/sven\@/,
+        },
+    );
+
+}
+
+# ======> SITUATION 3:  Helpdesk queue adds systems@ as permanent cc
+# (linked system ticket gets all helpdesk correspondence, but not
+# comments; helpdesk gets correspondence from systems)
+
+{
+    # Joe sends mail to helpdesk@
+    my $text = <<EOF;
+From: joe\@example.com
+To: helpdesk\@example.com
+Subject: This is a test of new ticket creation
+Message-Id: third\@example.com
+
+A helpdesk ticket
+EOF
+
+    my ($status, $id) = RT::Test->send_via_mailgate($text, queue => 'Helpdesk');
+    is ($status >> 8, 0, "The mail gateway exited normally");
+    ok ($id, "Created ticket");
+    my ($helpticket, $systicket) = ($id, $id + 1);
+
+    # Sends mail back to Joe, and to Bjoern
+    got_mail_ok( { to => qr/joe\@/ }, { bcc => qr/bjoern\@/} );
+
+    # Ticket #301 is created in the helpdesk queue with joe as the requestor
+    my $ticket = RT::Test->last_ticket( $bjoern );
+    isa_ok ($ticket, 'RT::Ticket');
+    is ($ticket->Id, $id, "correct ticket id");
+    is ($ticket->Subject , 'This is a test of new ticket creation', "Created the ticket");
+
+    # Bjoern adds systems@ as a cc on the ticket
+    ok($ticket->AddWatcher( Type => 'Cc', Email => 'systems at example.com' ), "Added systems@ as a watcher");
+
+    # Bjoern adds a comment
+    record_ok( mode => 'comment', on => $ticket, as => $bjoern );
+
+    # No mail is sent out (Bjoern would get it, but NotifyActor is not set)
+    got_mail_ok();
+
+    # Bjoern adds a correspondence
+    record_ok( mode => 'correspond', on => $ticket, as => $bjoern );
+
+    #   Joe and AdminCCs on #301 are notified, as is systems@
+    got_mail_ok(
+        {   from => qr/helpdesk\@/,
+            to   => qr/joe\@/,
+            cc   => qr/systems\@/,
+            id   => $systicket,
+        },
+    );
+
+    # Ticket #302 is created in the system queue with helpdesk@ as the requestor
+    $ticket = RT::Test->last_ticket( $sven );
+    is($ticket->RequestorAddresses, 'helpdesk at example.com');
+
+    # Auto-reply goes back to #301, and to sven
+    got_mail_ok(
+        {   from => qr/systems\@/,
+            to   => qr/helpdesk\@/,
+            id   => $systicket,
+        },
+        {   from => qr/systems\@/,
+            bcc  => qr/sven\@/,
+        },
+    );
+
+    # Auto-reply gets dropped
+    got_mail_ok();
+    is(RT::Test->last_ticket->id, $ticket->id);
+
+    # Ticket #302 is linked to ticket #301
+
+    # Sven adds a comment on #302
+    record_ok( mode => 'comment', on => $systicket, as => $sven );
+
+    #   AdminCCs on #302 are notified
+    got_mail_ok();
+
+    # Sven adds a correspondence on #302
+    record_ok( mode => 'correspond', on => $systicket, as => $sven );
+
+    #   AdminCCs on #302 are notified, as is helpdesk@,
+    got_mail_ok(
+        {   from => qr/systems\@/,
+            to   => qr/helpdesk\@/,
+            id   => $systicket,
+        },
+    );
+
+    #   which notifies Bjoern and Joe on #301
+    got_mail_ok(
+        {   from => qr/helpdesk\@/,
+            bcc  => qr/bjoern\@/,
+        },
+        {   from => qr/helpdesk\@/,
+            to   => qr/joe\@/,
+        },
+    );
+
+    # Bjoern adds a comment on #301
+    record_ok( mode => 'comment', on => $helpticket, as => $bjoern );
+
+    #   AdminCCs on #301 are notified
+    got_mail_ok();
+
+    # Bjoern adds a correspondence on #301
+    record_ok( mode => 'correspond', on => $helpticket, as => $bjoern );
+
+    #   Joe and AdminCCs on #301 are notified, as is systems@
+    got_mail_ok(
+        {   from => qr/helpdesk\@/,
+            to   => qr/joe\@/,
+            cc   => qr/systems\@/,
+            id   => $helpticket,
+        },
+    );
+
+    #   which notifies Sven and AdminCCs on #302
+    got_mail_ok(
+        {   from => qr/systems\@/,
+            bcc  => qr/sven\@/,
+        },
+    );
+
+}
+
+# ======> SITUATION 4:  Helpdesk queue adds systems@ as permanent AdminCC
+# (linked system ticket gets all correspondence and comments; helpdesk
+# gets correspondence from systems)
+
+{
+    # Joe sends mail to helpdesk@
+    my $text = <<EOF;
+From: joe\@example.com
+To: helpdesk\@example.com
+Subject: This is a test of new ticket creation
+Message-Id: fourth\@example.com
+
+A helpdesk ticket
+EOF
+
+    my ($status, $id) = RT::Test->send_via_mailgate($text, queue => 'Helpdesk');
+    is ($status >> 8, 0, "The mail gateway exited normally");
+    ok ($id, "Created ticket");
+    my ($helpticket, $systicket) = ($id, $id + 1);
+
+    # Sends mail back to Joe, and to Bjoern
+    got_mail_ok( { to => qr/joe\@/ }, { bcc => qr/bjoern\@/} );
+
+    # Ticket #401 is created in the helpdesk queue with joe as the requestor
+    my $ticket = RT::Test->last_ticket( $bjoern );
+    isa_ok ($ticket, 'RT::Ticket');
+    is ($ticket->Id, $id, "correct ticket id");
+    is ($ticket->Subject , 'This is a test of new ticket creation', "Created the ticket");
+
+    # Bjoern adds systems@ as an AdminCC on the ticket
+    ok($ticket->AddWatcher( Type => 'AdminCc', Email => 'systems at example.com' ), "Added systems@ as a watcher");
+
+    # Bjoern adds a comment
+    record_ok( mode => 'comment', on => $ticket, as => $bjoern );
+
+    #   AdminCCs on #401 are notified, as is systems@
+    got_mail_ok(
+        {   from => qr/helpdesk-comment\@/,
+            bcc  => qr/systems\@/,
+            id   => $systicket,
+        },
+    );
+
+    # Ticket #402 is created in the system queue with helpdesk-comment@ as the requestor
+    $ticket = RT::Test->last_ticket( $sven );
+    is($ticket->RequestorAddresses, 'helpdesk-comment at example.com');
+
+    # Auto-reply goes back to #401, and to sven
+    got_mail_ok(
+        {   from => qr/systems\@/,
+            to   => qr/helpdesk-comment\@/,
+            id   => $systicket,
+        },
+        {   from => qr/systems\@/,
+            bcc  => qr/sven\@/,
+        },
+    );
+
+    # Auto-reply gets dropped
+    got_mail_ok();
+    is(RT::Test->last_ticket->id, $ticket->id);
+
+    # Ticket #402 is linked to ticket #401
+
+    # Sven adds a comment on #402
+    record_ok( mode => 'comment', on => $systicket, as => $sven );
+
+    #   AdminCCs on #402 are notified
+    got_mail_ok();
+
+    # Sven adds a correspondence on #402
+    record_ok( mode => 'correspond', on => $systicket, as => $sven );
+
+    #   AdminCCs on #402 are notified, as is helpdesk-comment@,
+    got_mail_ok(
+        {   from => qr/systems\@/,
+            to   => qr/helpdesk-comment\@/,
+            id   => $systicket,
+        },
+    );
+    #   which notifies Bjoern and AdminCCs on #401
+    got_mail_ok(
+        {   from => qr/helpdesk-comment\@/,
+            bcc  => qr/bjoern\@/,
+        },
+    );
+
+    # Bjoern adds a comment on #401
+    record_ok( mode => 'comment', on => $helpticket, as => $bjoern );
+    #   AdminCCs on #401 are notified, as is systems@
+    got_mail_ok(
+        {   from => qr/helpdesk-comment\@/,
+            bcc  => qr/systems\@/,
+            id   => $helpticket,
+        },
+    );
+    #   which notifies Sven and AdminCCs on #402
+    got_mail_ok(
+        {   from => qr/systems\@/,
+            bcc  => qr/sven\@/,
+        },
+    );
+
+    # Bjoern adds a correspondence on #401
+    record_ok( mode => 'correspond', on => $helpticket, as => $bjoern );
+    #   Joe and AdminCCs on #401 are notified, as is systems@
+    got_mail_ok(
+        {   from => qr/helpdesk\@/,
+            bcc  => qr/systems\@/,
+            id   => $helpticket,
+        },
+        {   from => qr/helpdesk\@/,
+            to   => qr/joe\@/,
+        },
+    );
+
+    #   which notifies Sven and AdminCCs on #402 (and requestor, which is helpdesk-comment)
+    got_mail_ok(
+        {   from => qr/systems\@/,
+            bcc  => qr/sven\@/,
+        },
+        {   from => qr/systems\@/,
+            to   => qr/helpdesk-comment\@/,
+            id   => $systicket,
+        },
+    );
+
+    # helpdesk-comment@ mail gets dropped
+    got_mail_ok();
+}
+
+
+# ======> SITUATION 5:  Helpdesk queue adds systems@ as permanent
+# AdminCC, systems adds helpdesk@ as permanent AdminCC (All
+# correspondence and comment from helpdesk added as comment on system;
+# all correspondence and comment from system added as comment on helpdesk)
+
+{
+    # Joe sends mail to helpdesk@
+    my $text = <<EOF;
+From: joe\@example.com
+To: helpdesk\@example.com
+Subject: This is a test of new ticket creation
+Message-Id: fifth\@example.com
+
+A helpdesk ticket
+EOF
+
+    my ($status, $id) = RT::Test->send_via_mailgate($text, queue => 'Helpdesk');
+    is ($status >> 8, 0, "The mail gateway exited normally");
+    ok ($id, "Created ticket");
+    my ($helpticket, $systicket) = ($id, $id + 1);
+
+    # Sends mail back to Joe, and to Bjoern
+    got_mail_ok( { to => qr/joe\@/ }, { bcc => qr/bjoern\@/} );
+
+    # Ticket #501 is created in the helpdesk queue with joe as the requestor
+    my $ticket = RT::Test->last_ticket( $bjoern );
+    isa_ok ($ticket, 'RT::Ticket');
+    is ($ticket->Id, $id, "correct ticket id");
+    is ($ticket->Subject , 'This is a test of new ticket creation', "Created the ticket");
+
+    # Bjoern adds systems@ as an AdminCC ticket #501
+    # Bjoern adds a comment
+    #   AdminCCs on #501 are notified, as is systems@
+
+    # Ticket #502 is created in the system queue with helpdesk-comment@ as
+    # the requestor
+    # Ticket #502 is linked to ticket #501
+    # Sven takes ticket #502
+    # Sven adds helpdesk@ as an AdminCC on ticket #502
+
+    # Sven adds a comment on #502
+    #   AdminCCs on #502 are notified, as is helpdesk-comment@,
+    #   which adds a comment on #501,
+    #   which notifies Bjoern and AdminCCs on #501
+    # (Note that RT will correctly detect that #502 has seen this comment
+    # already, and _not_loop it back!)
+
+    # Sven adds a correspondence on #502
+    #   AdminCCs on #502 are notified, as is helpdesk-comment@,
+    #   which adds a comment on #501,
+    #   which notifies Bjoern and AdminCCs on #501
+    # (See above note about looping)
+
+    # Bjoern adds a comment on #501
+    #   AdminCCs on #501 are notified, as is systems@
+    #   which adds a comment on #502,
+    #   which notifies Sven and AdminCCs on #502
+    # (See above note about looping)
+
+    # Bjoern adds a correspondence on #501
+    #   Joe and AdminCCs on #501 are notified, as is systems@
+    #   which adds a comment on #502,
+    #   which notifies Sven and AdminCCs on #502
+    # (see above note about looping)
+}
+
+undef $m;
+done_testing;

commit f3e698e7cbe73653a933880854ccb772ffa64e1d
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Thu Dec 17 14:48:00 2009 -0500

    Use ->__Value to walk around possible permissions problems when determining queue

diff --git a/lib/RT/Interface/Email.pm b/lib/RT/Interface/Email.pm
index ee307c5..c87659a 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -1761,11 +1761,11 @@ sub LinkSelfLoops {
         }
         # Regardless, link them now.
         $args{Ticket}->AddAttribute(
-            Name    => "InternalLinks-" . $other->Queue,
+            Name    => "InternalLinks-" . $other->__Value('Queue'),
             Content => $other->Id,
         );
         $other->AddAttribute(
-            Name    => "InternalLinks-" . $args{Ticket}->Queue,
+            Name    => "InternalLinks-" . $args{Ticket}->__Value('Queue'),
             Content => $args{Ticket}->Id,
         );
         # ..and drop the message on the floor.

commit 5374bdd7c39285c3f15e47097dbcf8bb91deb8c6
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Thu Dec 17 14:49:23 2009 -0500

    Add internal links display

diff --git a/share/html/Elements/ShowLinks b/share/html/Elements/ShowLinks
index c06c6f7..f786d2e 100644
--- a/share/html/Elements/ShowLinks
+++ b/share/html/Elements/ShowLinks
@@ -138,6 +138,24 @@ while ( my $link = $depends_on->Next ) {
 </ul>
     </td>
   </tr>
+
+% my $attribs = $Ticket->Attributes;
+% my @internal = map {$_->Content} map {$attribs->Named($_)} grep {/^InternalLinks-/} $attribs->Names;
+% if (@internal) {
+  <tr>
+    <td class="labeltop"><&|/l&>Internal link</&>:</td>
+    <td class="value">
+    <ul>
+% for my $id (@internal) {
+% my $uri = RT::URI->new( $session{'CurrentUser'} );
+% $uri->FromURI($id);
+<li><& ShowLink, URI => $uri &></li>
+% }
+</ul>
+    </td>
+  </tr>
+% }
+
 % # Allow people to add more rows to the table
 % $m->callback( %ARGS );
 </table>

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


More information about the Rt-commit mailing list