This commit was generated by cvs2svn to compensate for changes in r4407,
[freeside.git] / rt / lib / RT / Action / CreateTickets.pm
index 0ab2067..b708f2e 100644 (file)
@@ -1,8 +1,14 @@
-# BEGIN LICENSE BLOCK
+# BEGIN BPS TAGGED BLOCK {{{
 # 
 # 
-# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+# COPYRIGHT:
+#  
+# This software is Copyright (c) 1996-2005 Best Practical Solutions, LLC 
+#                                          <jesse@bestpractical.com>
 # 
 # 
-# (Except where explictly superceded by other copyright notices)
+# (Except where explicitly superseded by other copyright notices)
+# 
+# 
+# LICENSE:
 # 
 # This work is made available to you under the terms of Version 2 of
 # the GNU General Public License. A copy of that license should have
 # 
 # This work is made available to you under the terms of Version 2 of
 # the GNU General Public License. A copy of that license should have
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 # General Public License for more details.
 # 
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 # General Public License for more details.
 # 
-# Unless otherwise specified, all modifications, corrections or
-# extensions to this work which alter its source code become the
-# property of Best Practical Solutions, LLC when submitted for
-# inclusion in the work.
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+# 
 # 
 # 
+# CONTRIBUTION SUBMISSION POLICY:
 # 
 # 
-# END LICENSE BLOCK
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+# 
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+# 
+# END BPS TAGGED BLOCK }}}
 package RT::Action::CreateTickets;
 require RT::Action::Generic;
 
 use strict;
 package RT::Action::CreateTickets;
 require RT::Action::Generic;
 
 use strict;
+use warnings;
 use vars qw/@ISA/;
 @ISA = qw(RT::Action::Generic);
 
 use vars qw/@ISA/;
 @ISA = qw(RT::Action::Generic);
 
@@ -39,9 +62,9 @@ Create one or more tickets according to an externally supplied template.
 
 =head1 SYNOPSIS
 
 
 =head1 SYNOPSIS
 
- ===Create-Ticket: codereview
+ ===Create-Ticket codereview
  Subject: Code review for {$Tickets{'TOP'}->Subject}
  Subject: Code review for {$Tickets{'TOP'}->Subject}
- Depended-On-By: {$Tickets{'TOP'}->Id}
+ Depended-On-By: TOP
  Content: Someone has created a ticket. you should review and approve it,
  so they can finish their work
  ENDOFCONTENT
  Content: Someone has created a ticket. you should review and approve it,
  so they can finish their work
  ENDOFCONTENT
@@ -84,13 +107,13 @@ After each ticket is created, it's stuffed into a hash called %Tickets
 so as to be available during the creation of other tickets during the same 
 ScripAction.  The hash is prepopulated with the ticket which triggered the 
 ScripAction as $Tickets{'TOP'}; you can also access that ticket using the
 so as to be available during the creation of other tickets during the same 
 ScripAction.  The hash is prepopulated with the ticket which triggered the 
 ScripAction as $Tickets{'TOP'}; you can also access that ticket using the
-shorthand $TOP.
+shorthand TOP.
 
 A simple example:
 
  ===Create-Ticket: codereview
  Subject: Code review for {$Tickets{'TOP'}->Subject}
 
 A simple example:
 
  ===Create-Ticket: codereview
  Subject: Code review for {$Tickets{'TOP'}->Subject}
- Depended-On-By: {$Tickets{'TOP'}->Id}
+ Depended-On-By: TOP
  Content: Someone has created a ticket. you should review and approve it,
  so they can finish their work
  ENDOFCONTENT
  Content: Someone has created a ticket. you should review and approve it,
  so they can finish their work
  ENDOFCONTENT
@@ -125,11 +148,11 @@ A convoluted example
          push (@admins, $admin->EmailAddress); 
      }
  }
          push (@admins, $admin->EmailAddress); 
      }
  }
- Queue: Approvals
- Type: Approval
+ Queue: ___Approvals
+ Type: approval
  AdminCc: {join ("\nAdminCc: ",@admins) }
  AdminCc: {join ("\nAdminCc: ",@admins) }
- Depended-On-By: {$Tickets{"TOP"}->Id}
- Refers-To: {$Tickets{"TOP"}->Id}
+ Depended-On-By: TOP
+ Refers-To: TOP
  Subject: Approval for ticket: {$Tickets{"TOP"}->Id} - {$Tickets{"TOP"}->Subject}
  Due: {time + 86400}
  Content-Type: text/plain
  Subject: Approval for ticket: {$Tickets{"TOP"}->Id} - {$Tickets{"TOP"}->Subject}
  Due: {time + 86400}
  Content-Type: text/plain
@@ -139,9 +162,9 @@ A convoluted example
  ENDOFCONTENT
  ===Create-Ticket: two
  Subject: Manager approval
  ENDOFCONTENT
  ===Create-Ticket: two
  Subject: Manager approval
- Depended-On-By: {$Tickets{"TOP"}->Id}
+ Depended-On-By: TOP
  Refers-On: {$Tickets{"approval"}->Id}
  Refers-On: {$Tickets{"approval"}->Id}
- Queue: Approvals
+ Queue: ___Approvals
  Content-Type: text/plain
  Content: 
  Your approval is requred for this ticket, too.
  Content-Type: text/plain
  Content: 
  Your approval is requred for this ticket, too.
@@ -190,7 +213,7 @@ A complete list of acceptable fields for this beastie:
 
 Fields marked with an * are required.
 
 
 Fields marked with an * are required.
 
-Fields marked with a + man have multiple values, simply
+Fields marked with a + may have multiple values, simply
 by repeating the fieldname on a new line with an additional value.
 
 Fields marked with a ! are postponed to be processed after all
 by repeating the fieldname on a new line with an additional value.
 
 Fields marked with a ! are postponed to be processed after all
@@ -220,27 +243,11 @@ ok ($approvalsq->Id, "Created Approvals test queue");
 
 my $approvals = 
 '===Create-Ticket: approval
 
 my $approvals = 
 '===Create-Ticket: approval
-{  my $name = "HR";
-     my $groups = RT::Groups->new($RT::SystemUser);
-   $groups->LimitToUserDefinedGroups();
-   $groups->Limit(FIELD => "Name", OPERATOR => "=", VALUE => "$name");
-   $groups->WithMember($Transaction->CreatorObj->Id);
-
-   my $groupid = $groups->First->Id;
-
-   my $adminccs = RT::Users->new($RT::SystemUser);
-   $adminccs->WhoHaveRight(Right => "AdminGroup", IncludeSystemRights => undef, IncludeSuperusers => 0, IncludeSubgroupMembers => 0, Object => $groups->First);
-
-    my @admins;
-    while (my $admin = $adminccs->Next) {
-        push (@admins, $admin->EmailAddress); 
-    }
-}
-Queue: Approvals
-Type: Approval
+Queue: ___Approvals
+Type: approval
 AdminCc: {join ("\nAdminCc: ",@admins) }
 Depended-On-By: {$Tickets{"TOP"}->Id}
 AdminCc: {join ("\nAdminCc: ",@admins) }
 Depended-On-By: {$Tickets{"TOP"}->Id}
-Refers-To: {$Tickets{"TOP"}->Id}
+Refers-To: TOP 
 Subject: Approval for ticket: {$Tickets{"TOP"}->Id} - {$Tickets{"TOP"}->Subject}
 Due: {time + 86400}
 Content-Type: text/plain
 Subject: Approval for ticket: {$Tickets{"TOP"}->Id} - {$Tickets{"TOP"}->Subject}
 Due: {time + 86400}
 Content-Type: text/plain
@@ -250,11 +257,11 @@ Blah
 ENDOFCONTENT
 ===Create-Ticket: two
 Subject: Manager approval.
 ENDOFCONTENT
 ===Create-Ticket: two
 Subject: Manager approval.
-Depends-On: {$Tickets{"approval"}->Id}
-Queue: Approvals
+Depended-On-By: approval
+Queue: ___Approvals
 Content-Type: text/plain
 Content: 
 Content-Type: text/plain
 Content: 
-Your minion approved this ticket. you ok with that?
+Your minion approved ticket {$Tickets{"TOP"}->Id}. you ok with that?
 ENDOFCONTENT
 ';
 
 ENDOFCONTENT
 ';
 
@@ -281,10 +288,173 @@ ok ($scrip->ConditionObj->Id, "Created the scrip condition");
 ok ($scrip->ActionObj->Id, "Created the scrip action");
 
 my $t = RT::Ticket->new($RT::SystemUser);
 ok ($scrip->ActionObj->Id, "Created the scrip action");
 
 my $t = RT::Ticket->new($RT::SystemUser);
-$t->Create(Subject => "Sample workflow test",
+my($tid, $ttrans, $tmsg) = $t->Create(Subject => "Sample workflow test",
            Owner => "root",
            Queue => $q->Id);
 
            Owner => "root",
            Queue => $q->Id);
 
+ok ($tid,$tmsg);
+
+my $deps = $t->DependsOn;
+is ($deps->Count, 1, "The ticket we created depends on one other ticket");
+my $dependson= $deps->First->TargetObj;
+ok ($dependson->Id, "It depends on a real ticket");
+unlike ($dependson->Subject, qr/{/, "The subject doesn't have braces in it. that means we're interpreting expressions");
+is ($t->ReferredToBy->Count,1, "It's only referred to by one other ticket");
+is ($t->ReferredToBy->First->BaseObj->Id,$t->DependsOn->First->TargetObj->Id, "The same ticket that depends on it refers to it.");
+use RT::Action::CreateTickets;
+my $action =  RT::Action::CreateTickets->new( CurrentUser => $RT::SystemUser);;
+
+# comma-delimited templates
+my $commas = <<"EOF";
+id,Queue,Subject,Owner,Content
+ticket1,General,"foo, bar",root,blah
+ticket2,General,foo bar,root,blah
+ticket3,General,foo' bar,root,blah'boo
+ticket4,General,foo' bar,,blah'boo
+EOF
+
+
+# Comma delimited templates with missing data
+my $sparse_commas = <<"EOF";
+id,Queue,Subject,Owner,Requestor
+ticket14,General,,,bobby
+ticket15,General,,,tommy
+ticket16,General,,suzie,tommy
+ticket17,General,Foo "bar" baz,suzie,tommy
+ticket18,General,'Foo "bar" baz',suzie,tommy
+ticket19,General,'Foo bar' baz,suzie,tommy
+EOF
+
+
+# tab-delimited templates
+my $tabs = <<"EOF";
+id\tQueue\tSubject\tOwner\tContent
+ticket10\tGeneral\t"foo' bar"\troot\tblah'
+ticket11\tGeneral\tfoo, bar\troot\tblah
+ticket12\tGeneral\tfoo' bar\troot\tblah'boo
+ticket13\tGeneral\tfoo' bar\t\tblah'boo
+EOF
+
+my %expected;
+
+$expected{ticket1} = <<EOF;
+Queue: General
+Subject: foo, bar
+Owner: root
+Content: blah
+ENDOFCONTENT
+EOF
+
+$expected{ticket2} = <<EOF;
+Queue: General
+Subject: foo bar
+Owner: root
+Content: blah
+ENDOFCONTENT
+EOF
+
+$expected{ticket3} = <<EOF;
+Queue: General
+Subject: foo' bar
+Owner: root
+Content: blah'boo
+ENDOFCONTENT
+EOF
+
+$expected{ticket4} = <<EOF;
+Queue: General
+Subject: foo' bar
+Owner: 
+Content: blah'boo
+ENDOFCONTENT
+EOF
+
+$expected{ticket10} = <<EOF;
+Queue: General
+Subject: foo' bar
+Owner: root
+Content: blah'
+ENDOFCONTENT
+EOF
+
+$expected{ticket11} = <<EOF;
+Queue: General
+Subject: foo, bar
+Owner: root
+Content: blah
+ENDOFCONTENT
+EOF
+
+$expected{ticket12} = <<EOF;
+Queue: General
+Subject: foo' bar
+Owner: root
+Content: blah'boo
+ENDOFCONTENT
+EOF
+
+$expected{ticket13} = <<EOF;
+Queue: General
+Subject: foo' bar
+Owner: 
+Content: blah'boo
+ENDOFCONTENT
+EOF
+
+
+$expected{'ticket14'} = <<EOF;
+Queue: General
+Subject: 
+Owner: 
+Requestor: bobby
+EOF
+$expected{'ticket15'} = <<EOF;
+Queue: General
+Subject: 
+Owner: 
+Requestor: tommy
+EOF
+$expected{'ticket16'} = <<EOF;
+Queue: General
+Subject: 
+Owner: suzie
+Requestor: tommy
+EOF
+$expected{'ticket17'} = <<EOF;
+Queue: General
+Subject: Foo "bar" baz
+Owner: suzie
+Requestor: tommy
+EOF
+$expected{'ticket18'} = <<EOF;
+Queue: General
+Subject: Foo "bar" baz
+Owner: suzie
+Requestor: tommy
+EOF
+$expected{'ticket19'} = <<EOF;
+Queue: General
+Subject: 'Foo bar' baz
+Owner: suzie
+Requestor: tommy
+EOF
+
+
+
+
+$action->Parse(Content =>$commas);
+$action->Parse(Content =>$sparse_commas);
+$action->Parse(Content => $tabs);
+
+my %got;
+foreach (@{ $action->{'create_tickets'} }) {
+  $got{$_} = $action->{'templates'}->{$_};
+}
+
+foreach my $id ( sort keys %expected ) {
+    ok(exists($got{"create-$id"}), "template exists for $id");
+    is($got{"create-$id"}, $expected{$id}, "template is correct for $id");
+}
 
 =end testing
 
 
 =end testing
 
@@ -300,265 +470,872 @@ perl(1).
 =cut
 
 my %LINKTYPEMAP = (
 =cut
 
 my %LINKTYPEMAP = (
-    MemberOf => { Type => 'MemberOf',
-                  Mode => 'Target', },
-    Members => { Type => 'MemberOf',
-                 Mode => 'Base', },
-    HasMember => { Type => 'MemberOf',
-                   Mode => 'Base', },
-    RefersTo => { Type => 'RefersTo',
-                  Mode => 'Target', },
-    ReferredToBy => { Type => 'RefersTo',
-                      Mode => 'Base', },
-    DependsOn => { Type => 'DependsOn',
-                   Mode => 'Target', },
-    DependedOnBy => { Type => 'DependsOn',
-                      Mode => 'Base', },
+    MemberOf => {
+        Type => 'MemberOf',
+        Mode => 'Target',
+    },
+    Parents => {
+        Type => 'MemberOf',
+        Mode => 'Target',
+    },
+    Members => {
+        Type => 'MemberOf',
+        Mode => 'Base',
+    },
+    Children => {
+        Type => 'MemberOf',
+        Mode => 'Base',
+    },
+    HasMember => {
+        Type => 'MemberOf',
+        Mode => 'Base',
+    },
+    RefersTo => {
+        Type => 'RefersTo',
+        Mode => 'Target',
+    },
+    ReferredToBy => {
+        Type => 'RefersTo',
+        Mode => 'Base',
+    },
+    DependsOn => {
+        Type => 'DependsOn',
+        Mode => 'Target',
+    },
+    DependedOnBy => {
+        Type => 'DependsOn',
+        Mode => 'Base',
+    },
 
 );
 
 # {{{ Scrip methods (Commit, Prepare)
 
 
 );
 
 # {{{ Scrip methods (Commit, Prepare)
 
-# {{{ sub Commit 
+# {{{ sub Commit
 #Do what we need to do and send it out.
 sub Commit {
     my $self = shift;
 #Do what we need to do and send it out.
 sub Commit {
     my $self = shift;
-    my (@links, @postponed);
+
+    # Create all the tickets we care about
+    return (1) unless $self->TicketObj->Type eq 'ticket';
+
+    $self->CreateByTemplate( $self->TicketObj );
+    $self->UpdateByTemplate( $self->TicketObj );
+    return (1);
+}
+
+# }}}
+
+# {{{ sub Prepare
+
+sub Prepare {
+    my $self = shift;
+
+    unless ( $self->TemplateObj ) {
+        $RT::Logger->warning("No template object handed to $self\n");
+    }
+
+    unless ( $self->TransactionObj ) {
+        $RT::Logger->warning("No transaction object handed to $self\n");
+
+    }
+
+    unless ( $self->TicketObj ) {
+        $RT::Logger->warning("No ticket object handed to $self\n");
+
+    }
+
+    $self->Parse( Content => $self->TemplateObj->Content, _ActiveContent => 1);
+    return 1;
+
+}
+
+# }}}
+
+# }}}
+
+sub CreateByTemplate {
+    my $self = shift;
+    my $top  = shift;
+
+    $RT::Logger->debug("In CreateByTemplate");
+
+    my @results;
 
     # XXX: cargo cult programming that works. i'll be back.
     use bytes;
 
 
     # XXX: cargo cult programming that works. i'll be back.
     use bytes;
 
-    # Create all the tickets we care about
-    return(1) unless $self->TicketObj->Type eq 'ticket';
+    local %T::Tickets = %T::Tickets;
+    local $T::TOP = $T::TOP;
+    local $T::ID = $T::ID;
+    $T::Tickets{'TOP'} = $T::TOP = $top if $top;
+
+    my $ticketargs;
+    my ( @links, @postponed );
+    foreach my $template_id ( @{ $self->{'create_tickets'} } ) {
+        $RT::Logger->debug("Workflow: processing $template_id of $T::TOP")
+          if $T::TOP;
+
+        $T::ID    = $template_id;
+        @T::AllID = @{ $self->{'create_tickets'} };
+
+        ( $T::Tickets{$template_id}, $ticketargs ) =
+          $self->ParseLines( $template_id, \@links, \@postponed );
+
+        # Now we have a %args to work with.
+        # Make sure we have at least the minimum set of
+        # reasonable data and do our thang
+
+        my ( $id, $transid, $msg ) =
+          $T::Tickets{$template_id}->Create(%$ticketargs);
+
+        foreach my $res ( split( '\n', $msg ) ) {
+            push @results,
+              $T::Tickets{$template_id}
+              ->loc( "Ticket [_1]", $T::Tickets{$template_id}->Id ) . ': '
+              . $res;
+        }
+        if ( !$id ) {
+            if ( $self->TicketObj ) {
+                $msg =
+                    "Couldn't create related ticket $template_id for "
+                  . $self->TicketObj->Id . " "
+                  . $msg;
+            }
+            else {
+                $msg = "Couldn't create ticket $template_id " . $msg;
+            }
 
 
-    %T::Tickets = ();
+            $RT::Logger->error($msg);
+            next;
+        }
 
 
-    foreach my $template_id ( @{ $self->{'template_order'} } ) {
-       $T::Tickets{'TOP'} = $T::TOP = $self->TicketObj;
-       $RT::Logger->debug("Workflow: processing $template_id of $T::TOP");
+        $RT::Logger->debug("Assigned $template_id with $id");
+        $T::Tickets{$template_id}->SetOriginObj( $self->TicketObj )
+          if $self->TicketObj
+          && $T::Tickets{$template_id}->can('SetOriginObj');
 
 
-       $T::ID = $template_id;
-       @T::AllID = @{ $self->{'template_order'} };
+    }
 
 
-        my $template = Text::Template->new(
-             TYPE   => 'STRING',
-             SOURCE => $self->{'templates'}->{$template_id}
+    $self->PostProcess( \@links, \@postponed );
+
+    return @results;
+}
+
+sub UpdateByTemplate {
+    my $self = shift;
+    my $top  = shift;
+
+    # XXX: cargo cult programming that works. i'll be back.
+    use bytes;
+
+    my @results;
+    local %T::Tickets = %T::Tickets;
+    local $T::ID = $T::ID;
+
+    my $ticketargs;
+    my ( @links, @postponed );
+    foreach my $template_id ( @{ $self->{'update_tickets'} } ) {
+        $RT::Logger->debug("Update Workflow: processing $template_id");
+
+        $T::ID    = $template_id;
+        @T::AllID = @{ $self->{'update_tickets'} };
+
+        ( $T::Tickets{$template_id}, $ticketargs ) =
+          $self->ParseLines( $template_id, \@links, \@postponed );
+
+        # Now we have a %args to work with.
+        # Make sure we have at least the minimum set of
+        # reasonable data and do our thang
+
+        my @attribs = qw(
+          Subject
+          FinalPriority
+          Priority
+          TimeEstimated
+          TimeWorked
+          TimeLeft
+          Status
+          Queue
+          Due
+          Starts
+          Started
+          Resolved
+        );
+
+        my $id = $template_id;
+        $id =~ s/update-(\d+).*/$1/;
+        $T::Tickets{$template_id}->Load($id);
+
+        my $msg;
+        if ( !$T::Tickets{$template_id}->Id ) {
+            $msg = "Couldn't update ticket $template_id " . $msg;
+
+            $RT::Logger->error($msg);
+            next;
+        }
+
+        my $current = $self->GetBaseTemplate( $T::Tickets{$template_id} );
+
+        $template_id =~ m/^update-(.*)/;
+        my $base_id = "base-$1";
+        my $base    = $self->{'templates'}->{$base_id};
+        if ($base) {
+        $base    =~ s/\r//g;
+        $base    =~ s/\n+$//;
+        $current =~ s/\n+$//;
+
+        # If we have no base template, set what we can.
+        if ($base ne $current)  {
+            push @results,
+              "Could not update ticket "
+              . $T::Tickets{$template_id}->Id
+              . ": Ticket has changed";
+            next;
+        }
+        }
+        push @results, $T::Tickets{$template_id}->Update(
+            AttributesRef => \@attribs,
+            ARGSRef       => $ticketargs
         );
 
         );
 
-       $RT::Logger->debug("Workflow: evaluating\n$self->{templates}{$template_id}");
+        push @results,
+          $self->UpdateWatchers( $T::Tickets{$template_id}, $ticketargs );
+
+        next unless exists $ticketargs->{'UpdateType'};
+        if ( $ticketargs->{'UpdateType'} =~ /^(private|public)$/ ) {
+            my ( $Transaction, $Description, $Object ) =
+              $T::Tickets{$template_id}->Comment(
+                CcMessageTo  => $ticketargs->{'Cc'},
+                BccMessageTo => $ticketargs->{'Bcc'},
+                MIMEObj      => $ticketargs->{'MIMEObj'},
+                TimeTaken    => $ticketargs->{'TimeWorked'}
+              );
+            push( @results,
+                $T::Tickets{$template_id}
+                  ->loc( "Ticket [_1]", $T::Tickets{$template_id}->id ) . ': '
+                  . $Description );
+        }
+        elsif ( $ticketargs->{'UpdateType'} eq 'response' ) {
+            my ( $Transaction, $Description, $Object ) =
+              $T::Tickets{$template_id}->Correspond(
+                CcMessageTo  => $ticketargs->{'Cc'},
+                BccMessageTo => $ticketargs->{'Bcc'},
+                MIMEObj      => $ticketargs->{'MIMEObj'},
+                TimeTaken    => $ticketargs->{'TimeWorked'}
+              );
+            push( @results,
+                $T::Tickets{$template_id}
+                  ->loc( "Ticket [_1]", $T::Tickets{$template_id}->id ) . ': '
+                  . $Description );
+        }
+        else {
+            push( @results,
+                $T::Tickets{$template_id}
+                  ->loc("Update type was neither correspondence nor comment.")
+                  . " "
+                  . $T::Tickets{$template_id}->loc("Update not recorded.") );
+        }
+    }
+
+    $self->PostProcess( \@links, \@postponed );
+
+    return @results;
+}
+
+=head2 Parse  TEMPLATE_CONTENT, DEFAULT_QUEUE, DEFAULT_REQEUESTOR ACTIVE
 
 
-       my $err;
-        my $filled_in = $template->fill_in( PACKAGE => 'T', BROKEN => sub {
-           $err = { @_ }->{error};
-       } );
+Parse a template from TEMPLATE_CONTENT
 
 
-       $RT::Logger->debug("Workflow: yielding\n$filled_in");
+If $active is set to true, then we'll use Text::Template to parse the templates,
+allowing you to embed active perl in your templates.
 
 
-       if ($err) {
-           $RT::Logger->error("Ticket creation failed for ".$self->TicketObj->Id." ".$err);
-           while (my ($k, $v) = each %T::X) {
-               $RT::Logger->debug("Eliminating $template_id from ${k}'s parents.");
-               delete $v->{$template_id};
-           }
-           next;
+=cut
+
+sub Parse {
+    my $self          = shift;
+    my %args = ( Content => undef,
+                 Queue => undef,
+                 Requestor => undef,
+                 _ActiveContent => undef,
+                @_);
+
+    if ($args{'_ActiveContent'}) {
+        $self->{'UsePerlTextTemplate'} =1;
+    } else {
+
+        $self->{'UsePerlTextTemplate'} = 0;
+    }
+
+    my @template_order;
+    my $template_id;
+    my ( $queue, $requestor );
+    if ( substr( $args{'Content'}, 0, 3 ) eq '===' ) {
+        $RT::Logger->debug("Line: ===");
+        foreach my $line ( split( /\n/, $args{'Content'} ) ) {
+            $line =~ s/\r$//;
+            $RT::Logger->debug("Line: $line");
+            if ( $line =~ /^===/ ) {
+                if ( $template_id && !$queue && $args{'Queue'} ) {
+                    $self->{'templates'}->{$template_id} .= "Queue: $args{'Queue'}\n";
+                }
+                if ( $template_id && !$requestor && $args{'Requestor'} ) {
+                    $self->{'templates'}->{$template_id} .=
+                      "Requestor: $args{'Requestor'}\n";
+                }
+                $queue     = 0;
+                $requestor = 0;
+            }
+            if ( $line =~ /^===Create-Ticket: (.*)$/ ) {
+                $template_id = "create-$1";
+                $RT::Logger->debug("****  Create ticket: $template_id");
+                push @{ $self->{'create_tickets'} }, $template_id;
+            }
+            elsif ( $line =~ /^===Update-Ticket: (.*)$/ ) {
+                $template_id = "update-$1";
+                $RT::Logger->debug("****  Update ticket: $template_id");
+                push @{ $self->{'update_tickets'} }, $template_id;
+            }
+            elsif ( $line =~ /^===Base-Ticket: (.*)$/ ) {
+                $template_id = "base-$1";
+                $RT::Logger->debug("****  Base ticket: $template_id");
+                push @{ $self->{'base_tickets'} }, $template_id;
+            }
+            elsif ( $line =~ /^===#.*$/ ) {    # a comment
+                next;
+            }
+            else {
+                if ( $line =~ /^Queue:(.*)/i ) {
+                    $queue = 1;
+                    my $value = $1;
+                    $value =~ s/^\s//;
+                    $value =~ s/\s$//;
+                    if ( !$value && $args{'Queue'}) {
+                        $value = $args{'Queue'};
+                        $line  = "Queue: $value";
+                    }
+                }
+                if ( $line =~ /^Requestor:(.*)/i ) {
+                    $requestor = 1;
+                    my $value = $1;
+                    $value =~ s/^\s//;
+                    $value =~ s/\s$//;
+                    if ( !$value && $args{'Requestor'}) {
+                        $value = $args{'Requestor'};
+                        $line  = "Requestor: $value";
+                    }
+                }
+                $self->{'templates'}->{$template_id} .= $line . "\n";
+            }
+        }
+       if ( $template_id && !$queue && $args{'Queue'} ) {
+           $self->{'templates'}->{$template_id} .= "Queue: $args{'Queue'}\n";
        }
        }
+    }
+    elsif ( substr( $args{'Content'}, 0, 2 ) =~ /^id$/i ) {
+        $RT::Logger->debug("Line: id");
+        use Regexp::Common qw(delimited);
+        my $first = substr( $args{'Content'}, 0, index( $args{'Content'}, "\n" ) );
+        $first =~ s/\r$//;
+
+        my $delimiter;
+        if ( $first =~ /\t/ ) {
+            $delimiter = "\t";
+        }
+        else {
+            $delimiter = ',';
+        }
+        my @fields    = split( /$delimiter/, $first );
+        
 
 
-        my %args;
-        my @lines = ( split ( /\n/, $filled_in ) );
-        while ( defined(my $line = shift @lines) ) {
-            if ( $line =~ /^(.*?):(?:\s+(.*))?$/ ) {
-                my $value = $2;
-                my $tag = lc ($1);
-                $tag =~ s/-//g;
-
-               if (ref($args{$tag})) { #If it's an array, we want to push the value
-                   push @{$args{$tag}}, $value;
-               }
-               elsif (defined ($args{$tag})) { #if we're about to get a second value, make it an array
-                   $args{$tag} = [$args{$tag}, $value];
-               }
-               else { #if there's nothing there, just set the value
-                   $args{ $tag } = $value;
-               }
-
-                if ( $tag eq 'content' ) { #just build up the content
-                        # convert it to an array
-                        $args{$tag} = defined($value) ? [ $value."\n" ] : [];
-                      while ( defined(my $l = shift @lines) ) {
-                        last if ($l =~  /^ENDOFCONTENT\s*$/) ;
-                        push @{$args{'content'}}, $l."\n";
+        my $delimiter_re = qr[$delimiter];
+
+        my $delimited = qr[[^$delimiter]+];
+        my $empty     = qr[^[$delimiter](?=[$delimiter])];
+        my $justquoted = qr[$RE{quoted}];
+
+        $args{'Content'} = substr( $args{'Content'}, index( $args{'Content'}, "\n" ) + 1 );
+        $RT::Logger->debug("First: $first");
+
+        my $queue;
+        foreach my $line ( split( /\n/, $args{'Content'} ) ) {
+            next unless $line;
+            $RT::Logger->debug("Line: $line");
+
+            # first item is $template_id
+            my $i = 0;
+            my $template_id;
+            while ($line && $line =~ s/^($justquoted|.*?)(?:$delimiter_re|$)//ix) {
+                if ( $i == 0 ) {
+                    $queue     = 0;
+                    $requestor = 0;
+                    my $tid = $1;
+                    $tid =~ s/^\s//;
+                    $tid =~ s/\s$//;
+                    next unless $tid;
+                   
+                     
+                    if ($tid =~ /^\d+$/) {
+                        $template_id = 'update-' . $tid;
+                        push @{ $self->{'update_tickets'} }, $template_id;
+
+                    } elsif ($tid =~ /^#base-(\d+)$/) {
+
+                        $template_id = 'base-' . $1;
+                        push @{ $self->{'base_tickets'} }, $template_id;
+
+                    } else {
+                        $template_id = 'create-' . $tid;
+                        push @{ $self->{'create_tickets'} }, $template_id;
+                    }
+                    $RT::Logger->debug("template_id: $tid");
+                }
+                else {
+                    my $value = $1;
+                    $value = '' if ( $value =~ /^$delimiter$/ );
+                    if ($value =~ /^$RE{delimited}{-delim=>qq{\'\"}}$/) {
+                        substr($value,0,1) = "";
+                    substr($value,-1,1) = "";
+                    }
+                    my $field = $fields[$i];
+                    next unless $field;
+                    $field =~ s/^\s//;
+                    $field =~ s/\s$//;
+                    if (   $field =~ /Body/i
+                        || $field =~ /Data/i
+                        || $field =~ /Message/i )
+                    {
+                        $field = 'Content';
+                    }
+                    if ( $field =~ /Summary/i ) {
+                        $field = 'Subject';
+                    }
+                    if ( $field =~ /Queue/i ) {
+                        $queue = 1;
+                        if ( !$value && $args{'Queue'} ) {
+                            $value = $args{'Queue'};
                         }
                         }
+                    }
+                    if ( $field =~ /Requestor/i ) {
+                        $requestor = 1;
+                        if ( !$value && $args{'Requestor'} ) {
+                            $value = $args{'Requestor'};
+                        }
+                    }
+                    $self->{'templates'}->{$template_id} .= $field . ": ";
+                    $self->{'templates'}->{$template_id} .= $value || "";
+                    $self->{'templates'}->{$template_id} .= "\n";
+                    $self->{'templates'}->{$template_id} .= "ENDOFCONTENT\n"
+                      if $field =~ /content/i;
                 }
                 }
+                $i++;
             }
             }
-       }
+            if ( !$queue && $args{'Queue'} ) {
+                $self->{'templates'}->{$template_id} .= "Queue: $args{'Queue'}\n";
+            }
+            if ( !$requestor && $args{'Requestor'} ) {
+                $self->{'templates'}->{$template_id} .=
+                  "Requestor: $args{'Requestor'}\n";
+            }
+        }
+    }
+}
 
 
-       foreach my $date qw(due starts started resolved) {
-           my $dateobj = RT::Date->new($RT::SystemUser);
-           next unless $args{$date};
-           if ($args{$date} =~ /^\d+$/) {
-               $dateobj->Set(Format => 'unix', Value => $args{$date});
-           } else {
-               $dateobj->Set(Format => 'unknown', Value => $args{$date});
-           }
-           $args{$date} = $dateobj->ISO;
-       }
-       my $mimeobj = MIME::Entity->new();
-       $mimeobj->build(Type => $args{'contenttype'},
-                       Data => $args{'content'});
-       # Now we have a %args to work with. 
-       # Make sure we have at least the minimum set of 
-       # reasonable data and do our thang
-       $T::Tickets{$template_id} ||= RT::Ticket->new($RT::SystemUser);
-
-       # Deferred processing   
-       push @links, (
-           $T::Tickets{$template_id}, {
-               DependsOn               => $args{'dependson'},
-               DependedOnBy    => $args{'dependedonby'},
-               RefersTo                => $args{'refersto'},
-               ReferredToBy    => $args{'referredtoby'},
-               Members         => $args{'members'},
-               MemberOf                => $args{'memberof'},
-           }
-       );
-
-       push @postponed, (
-           # Status is postponed so we don't violate dependencies
-           $T::Tickets{$template_id}, {
-               Status          => $args{'status'},
-           }
-       );
-
-       $args{'requestor'} ||= $self->TicketObj->Requestors->MemberEmailAddresses;
-
-       my %ticketargs = ( Queue => $args{'queue'},
-                     Subject=> $args{'subject'},
-                   Status => 'new',
-                   Due => $args{'due'},
-                   Starts => $args{'starts'},
-                   Started => $args{'started'},
-                   Resolved => $args{'resolved'},
-                   Owner => $args{'owner'},
-                   Requestor => $args{'requestor'},
-                   Cc => $args{'cc'},
-                   AdminCc=> $args{'admincc'},
-                   TimeWorked =>$args{'timeworked'},
-                   TimeEstimated =>$args{'timeestimated'},
-                   TimeLeft =>$args{'timeleft'},
-                   InitialPriority => $args{'initialpriority'},
-                   FinalPriority => $args{'finalpriority'},
-                   Type => $args{'type'}, 
-                   MIMEObj => $mimeobj);
-
-
-       foreach my $key (keys(%args)) {
-           $key =~ /^customfield-(\d+)$/ or next;
-           $ticketargs{ "CustomField-" . $1 } = $args{$key};
-       }
+sub ParseLines {
+    my $self        = shift;
+    my $template_id = shift;
+    my $links       = shift;
+    my $postponed   = shift;
 
 
-       my ($id, $transid, $msg) = $T::Tickets{$template_id}->Create(%ticketargs);
-       if (!$id) {
-           $RT::Logger->error(
-               "Couldn't create related ticket $template_id for ".
-               $self->TicketObj->Id." ".$msg
-           );
-           next;
-       }
 
 
-       $RT::Logger->debug("Assigned $template_id with $id");
-       $T::Tickets{$template_id}->SetOriginObj($self->TicketObj)
-           if $T::Tickets{$template_id}->can('SetOriginObj');
+    my $content = $self->{'templates'}->{$template_id};
+
+    if ( $self->{'UsePerlTextTemplate'} ) {
+
+        $RT::Logger->debug(
+            "Workflow: evaluating\n$self->{templates}{$template_id}");
+
+        my $template = Text::Template->new(
+            TYPE   => 'STRING',
+            SOURCE => $content
+        );
+
+        my $err;
+        $content = $template->fill_in(
+            PACKAGE => 'T',
+            BROKEN  => sub {
+                $err = {@_}->{error};
+            }
+        );
+
+        $RT::Logger->debug("Workflow: yielding\n$content");
+
+        if ($err) {
+            $RT::Logger->error( "Ticket creation failed: " . $err );
+            while ( my ( $k, $v ) = each %T::X ) {
+                $RT::Logger->debug(
+                    "Eliminating $template_id from ${k}'s parents.");
+                delete $v->{$template_id};
+            }
+            next;
+        }
     }
     }
+    
+    my $TicketObj ||= RT::Ticket->new($self->CurrentUser);
+
+    my %args;
+    my @lines = ( split( /\n/, $content ) );
+    while ( defined( my $line = shift @lines ) ) {
+        if ( $line =~ /^(.*?):(?:\s+)(.*?)(?:\s*)$/ ) {
+            my $value = $2;
+            my $tag   = lc($1);
+            $tag =~ s/-//g;
+
+            if ( ref( $args{$tag} ) )
+            {    #If it's an array, we want to push the value
+                push @{ $args{$tag} }, $value;
+            }
+            elsif ( defined( $args{$tag} ) )
+            {    #if we're about to get a second value, make it an array
+                $args{$tag} = [ $args{$tag}, $value ];
+            }
+            else {    #if there's nothing there, just set the value
+                $args{$tag} = $value;
+            }
 
 
-    # postprocessing: add links
+            if ( $tag eq 'content' ) {    #just build up the content
+                                          # convert it to an array
+                $args{$tag} = defined($value) ? [ $value . "\n" ] : [];
+                while ( defined( my $l = shift @lines ) ) {
+                    last if ( $l =~ /^ENDOFCONTENT\s*$/ );
+                    push @{ $args{'content'} }, $l . "\n";
+                }
+            }
+            else {
 
 
-    while (my $ticket = shift(@links)) {
-       $RT::Logger->debug("Handling links for " . $ticket->Id);
-       my %args = %{shift(@links)};
-
-       foreach my $type ( keys %LINKTYPEMAP ) {
-           next unless (defined $args{$type});
-           foreach my $link (
-               ref( $args{$type} ) ? @{ $args{$type} } : ( $args{$type} ) )
-           {
-               if (!exists $T::Tickets{$link}) {
-                   $RT::Logger->debug("Skipping $type link for $link (non-existent)");
-                   next;
-               }
-               $RT::Logger->debug("Building $type link for $link: " . $T::Tickets{$link}->Id);
-               $link = $T::Tickets{$link}->Id;
-
-               my ( $wval, $wmsg ) = $ticket->AddLink(
-                   Type                          => $LINKTYPEMAP{$type}->{'Type'},
-                   $LINKTYPEMAP{$type}->{'Mode'} => $link,
-                   Silent                        => 1
-               );
-
-               $RT::Logger->warning("AddLink thru $link failed: $wmsg") unless $wval;
-               # push @non_fatal_errors, $wmsg unless ($wval);
-           }
+                # if it's not content, strip leading and trailing spaces
+                if ( $args{$tag} ) {
+                    $args{$tag} =~ s/^\s+//g;
+                    $args{$tag} =~ s/\s+$//g;
+                }
+            }
+        }
+    }
 
 
-       }
+    foreach my $date qw(due starts started resolved) {
+        my $dateobj = RT::Date->new($self->CurrentUser);
+        next unless $args{$date};
+        if ( $args{$date} =~ /^\d+$/ ) {
+            $dateobj->Set( Format => 'unix', Value => $args{$date} );
+        }
+        else {
+            $dateobj->Set( Format => 'unknown', Value => $args{$date} );
+        }
+        $args{$date} = $dateobj->ISO;
     }
 
     }
 
-    # postponed actions -- Status only, currently
-    while (my $ticket = shift(@postponed)) {
-       $RT::Logger->debug("Handling postponed actions for $ticket");
-       my %args = %{shift(@postponed)};
+    $args{'requestor'} ||= $self->TicketObj->Requestors->MemberEmailAddresses
+      if $self->TicketObj;
+
+    $args{'type'} ||= 'ticket';
+
+    my %ticketargs = (
+        Queue           => $args{'queue'},
+        Subject         => $args{'subject'},
+        Status          => 'new',
+        Due             => $args{'due'},
+        Starts          => $args{'starts'},
+        Started         => $args{'started'},
+        Resolved        => $args{'resolved'},
+        Owner           => $args{'owner'},
+        Requestor       => $args{'requestor'},
+        Cc              => $args{'cc'},
+        AdminCc         => $args{'admincc'},
+        TimeWorked      => $args{'timeworked'},
+        TimeEstimated   => $args{'timeestimated'},
+        TimeLeft        => $args{'timeleft'},
+        InitialPriority => $args{'initialpriority'} || 0,
+        FinalPriority   => $args{'finalpriority'} || 0,
+        Type            => $args{'type'},
+    );
 
 
-       $ticket->SetStatus($args{Status}) if defined $args{Status};
+    if ($args{content}) {
+        my $mimeobj = MIME::Entity->new();
+        $mimeobj->build(
+            Type => $args{'contenttype'},
+            Data => $args{'content'}
+        );
+        $ticketargs{MIMEObj} = $mimeobj;
+        $ticketargs{UpdateType} = $args{'updatetype'} if $args{'updatetype'};
     }
 
     }
 
-    return(1);
+    foreach my $key ( keys(%args) ) {
+        $key =~ /^customfield(\d+)$/ or next;
+        $ticketargs{ "CustomField-" . $1 } = $args{$key};
+    }
+
+    $self->GetDeferred( \%args, $template_id, $links, $postponed );
+
+    return $TicketObj, \%ticketargs;
 }
 }
-# }}}
 
 
-# {{{ sub Prepare 
-
-sub Prepare  {
-  my $self = shift;
-  
-  unless ($self->TemplateObj) {
-    $RT::Logger->warning("No template object handed to $self\n");
-  }
-  
-  unless ($self->TransactionObj) {
-    $RT::Logger->warning("No transaction object handed to $self\n");
-    
-  }
-  
-  unless ($self->TicketObj) {
-    $RT::Logger->warning("No ticket object handed to $self\n");
-      
-  }
+sub GetDeferred {
+    my $self      = shift;
+    my $args      = shift;
+    my $id        = shift;
+    my $links     = shift;
+    my $postponed = shift;
+
+    # Deferred processing
+    push @$links,
+      (
+        $id,
+        {
+            DependsOn    => $args->{'dependson'},
+            DependedOnBy => $args->{'dependedonby'},
+            RefersTo     => $args->{'refersto'},
+            ReferredToBy => $args->{'referredtoby'},
+            Children     => $args->{'children'},
+            Parents      => $args->{'parents'},
+        }
+      );
+
+    push @$postponed, (
+
+        # Status is postponed so we don't violate dependencies
+        $id, { Status => $args->{'status'}, }
+    );
+}
 
 
-    
+sub GetUpdateTemplate {
+    my $self = shift;
+    my $t    = shift;
+
+    my $string;
+    $string .= "Queue: " . $t->QueueObj->Name . "\n";
+    $string .= "Subject: " . $t->Subject . "\n";
+    $string .= "Status: " . $t->Status . "\n";
+    $string .= "UpdateType: response\n";
+    $string .= "Content: \n";
+    $string .= "ENDOFCONTENT\n";
+    $string .= "Due: " . $t->DueObj->AsString . "\n";
+    $string .= "Starts: " . $t->StartsObj->AsString . "\n";
+    $string .= "Started: " . $t->StartedObj->AsString . "\n";
+    $string .= "Resolved: " . $t->ResolvedObj->AsString . "\n";
+    $string .= "Owner: " . $t->OwnerObj->Name . "\n";
+    $string .= "Requestor: " . $t->RequestorAddresses . "\n";
+    $string .= "Cc: " . $t->CcAddresses . "\n";
+    $string .= "AdminCc: " . $t->AdminCcAddresses . "\n";
+    $string .= "TimeWorked: " . $t->TimeWorked . "\n";
+    $string .= "TimeEstimated: " . $t->TimeEstimated . "\n";
+    $string .= "TimeLeft: " . $t->TimeLeft . "\n";
+    $string .= "InitialPriority: " . $t->Priority . "\n";
+    $string .= "FinalPriority: " . $t->FinalPriority . "\n";
+
+    foreach my $type ( sort keys %LINKTYPEMAP ) {
+
+        # don't display duplicates
+        if (   $type eq "HasMember"
+            || $type eq "Members"
+            || $type eq "MemberOf" )
+        {
+            next;
+        }
+        $string .= "$type: ";
+
+        my $mode   = $LINKTYPEMAP{$type}->{Mode};
+        my $method = $LINKTYPEMAP{$type}->{Type};
+
+        my $links;
+        while ( my $link = $t->$method->Next ) {
+            $links .= ", " if $links;
+
+            my $object = $mode . "Obj";
+            my $member = $link->$object;
+            $links .= $member->Id if $member;
+        }
+        $string .= $links;
+        $string .= "\n";
+    }
 
 
-my $template_id;
-foreach my $line (split(/\n/,$self->TemplateObj->Content)) {
-        if ($line =~ /^===Create-Ticket: (.*)$/) {
-                $template_id = $1;
-                push @{$self->{'template_order'}},$template_id;
-        } else {
-                $self->{'templates'}->{$template_id} .= $line."\n";
-        }       
-        
-        
+    return $string;
 }
 }
-  
-  return 1;
-  
+
+sub GetBaseTemplate {
+    my $self = shift;
+    my $t    = shift;
+
+    my $string;
+    $string .= "Queue: " . $t->Queue . "\n";
+    $string .= "Subject: " . $t->Subject . "\n";
+    $string .= "Status: " . $t->Status . "\n";
+    $string .= "Due: " . $t->DueObj->Unix . "\n";
+    $string .= "Starts: " . $t->StartsObj->Unix . "\n";
+    $string .= "Started: " . $t->StartedObj->Unix . "\n";
+    $string .= "Resolved: " . $t->ResolvedObj->Unix . "\n";
+    $string .= "Owner: " . $t->Owner . "\n";
+    $string .= "Requestor: " . $t->RequestorAddresses . "\n";
+    $string .= "Cc: " . $t->CcAddresses . "\n";
+    $string .= "AdminCc: " . $t->AdminCcAddresses . "\n";
+    $string .= "TimeWorked: " . $t->TimeWorked . "\n";
+    $string .= "TimeEstimated: " . $t->TimeEstimated . "\n";
+    $string .= "TimeLeft: " . $t->TimeLeft . "\n";
+    $string .= "InitialPriority: " . $t->Priority . "\n";
+    $string .= "FinalPriority: " . $t->FinalPriority . "\n";
+
+    return $string;
 }
 
 }
 
-# }}}
+sub GetCreateTemplate {
+    my $self = shift;
 
 
-# }}}
+    my $string;
+
+    $string .= "Queue: General\n";
+    $string .= "Subject: \n";
+    $string .= "Status: new\n";
+    $string .= "Content: \n";
+    $string .= "ENDOFCONTENT\n";
+    $string .= "Due: \n";
+    $string .= "Starts: \n";
+    $string .= "Started: \n";
+    $string .= "Resolved: \n";
+    $string .= "Owner: \n";
+    $string .= "Requestor: \n";
+    $string .= "Cc: \n";
+    $string .= "AdminCc:\n";
+    $string .= "TimeWorked: \n";
+    $string .= "TimeEstimated: \n";
+    $string .= "TimeLeft: \n";
+    $string .= "InitialPriority: \n";
+    $string .= "FinalPriority: \n";
+
+    foreach my $type ( keys %LINKTYPEMAP ) {
+
+        # don't display duplicates
+        if (   $type eq "HasMember"
+            || $type eq 'Members'
+            || $type eq 'MemberOf' )
+        {
+            next;
+        }
+        $string .= "$type: \n";
+    }
+    return $string;
+}
+
+sub UpdateWatchers {
+    my $self   = shift;
+    my $ticket = shift;
+    my $args   = shift;
+
+    my @results;
+
+    foreach my $type qw(Requestor Cc AdminCc) {
+        my $method  = $type . 'Addresses';
+        my $oldaddr = $ticket->$method;
+    
+    
+        # Skip unless we have a defined field
+        next unless defined $args->{$type};
+        my $newaddr = $args->{$type};
+
+        my @old = split( ', ', $oldaddr );
+        my @new = split( ', ', $newaddr );
+        my %oldhash = map { $_ => 1 } @old;
+        my %newhash = map { $_ => 1 } @new;
+
+        my @add    = grep( !defined $oldhash{$_}, @new );
+        my @delete = grep( !defined $newhash{$_}, @old );
+
+        foreach (@add) {
+            my ( $val, $msg ) = $ticket->AddWatcher(
+                Type  => $type,
+                Email => $_
+            );
+
+            push @results,
+              $ticket->loc( "Ticket [_1]", $ticket->Id ) . ': ' . $msg;
+        }
+
+        foreach (@delete) {
+            my ( $val, $msg ) = $ticket->DeleteWatcher(
+                Type  => $type,
+                Email => $_
+            );
+            push @results,
+              $ticket->loc( "Ticket [_1]", $ticket->Id ) . ': ' . $msg;
+        }
+    }
+    return @results;
+}
+
+sub PostProcess {
+    my $self      = shift;
+    my $links     = shift;
+    my $postponed = shift;
+
+    # postprocessing: add links
+
+    while ( my $template_id = shift(@$links) ) {
+        my $ticket = $T::Tickets{$template_id};
+        $RT::Logger->debug( "Handling links for " . $ticket->Id );
+        my %args = %{ shift(@$links) };
+
+        foreach my $type ( keys %LINKTYPEMAP ) {
+            next unless ( defined $args{$type} );
+            foreach my $link (
+                ref( $args{$type} ) ? @{ $args{$type} } : ( $args{$type} ) )
+            {
+                next unless $link;
+
+                if ($link =~ /^TOP$/i) {
+                    $RT::Logger->debug( "Building $type link for $link: " . $T::Tickets{TOP}->Id );
+                    $link = $T::Tickets{TOP}->Id;
+
+                } 
+                elsif ( $link !~ m/^\d+$/ ) {
+                    my $key = "create-$link";
+                    if ( !exists $T::Tickets{$key} ) {
+                        $RT::Logger->debug( "Skipping $type link for $key (non-existent)");
+                        next;
+                    }
+                    $RT::Logger->debug( "Building $type link for $link: " . $T::Tickets{$key}->Id );
+                    $link = $T::Tickets{$key}->Id;
+                }
+                else {
+                    $RT::Logger->debug("Building $type link for $link");
+                }
+
+                my ( $wval, $wmsg ) = $ticket->AddLink(
+                    Type => $LINKTYPEMAP{$type}->{'Type'},
+                    $LINKTYPEMAP{$type}->{'Mode'} => $link,
+                    Silent                        => 1
+                );
+
+                $RT::Logger->warning("AddLink thru $link failed: $wmsg")
+                  unless $wval;
+
+                # push @non_fatal_errors, $wmsg unless ($wval);
+            }
+
+        }
+    }
+
+    # postponed actions -- Status only, currently
+    while ( my $template_id = shift(@$postponed) ) {
+        my $ticket = $T::Tickets{$template_id};
+        $RT::Logger->debug("Handling postponed actions for ".$ticket->id);
+        my %args = %{ shift(@$postponed) };
+        $ticket->SetStatus( $args{Status} ) if defined $args{Status};
+    }
+
+}
 
 eval "require RT::Action::CreateTickets_Vendor";
 
 eval "require RT::Action::CreateTickets_Vendor";
-die $@ if ($@ && $@ !~ qr{^Can't locate RT/Action/CreateTickets_Vendor.pm});
+die $@ if ( $@ && $@ !~ qr{^Can't locate RT/Action/CreateTickets_Vendor.pm} );
 eval "require RT::Action::CreateTickets_Local";
 eval "require RT::Action::CreateTickets_Local";
-die $@ if ($@ && $@ !~ qr{^Can't locate RT/Action/CreateTickets_Local.pm});
+die $@ if ( $@ && $@ !~ qr{^Can't locate RT/Action/CreateTickets_Local.pm} );
 
 1;
 
 
 1;