Merge branch 'master' of https://github.com/jgoodman/Freeside
[freeside.git] / rt / lib / RT / Scrips.pm
index 5b05e0b..de9d1ea 100755 (executable)
@@ -2,7 +2,7 @@
 #
 # COPYRIGHT:
 #
-# This software is Copyright (c) 1996-2011 Best Practical Solutions, LLC
+# This software is Copyright (c) 1996-2014 Best Practical Solutions, LLC
 #                                          <sales@bestpractical.com>
 #
 # (Except where explicitly superseded by other copyright notices)
 #
 # END BPS TAGGED BLOCK }}}
 
-# Autogenerated by DBIx::SearchBuilder factory (by <jesse@bestpractical.com>)
-# WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST.  
-# 
-# !! DO NOT EDIT THIS FILE !!
-#
-
-use strict;
-
-
 =head1 NAME
 
-  RT::Scrips -- Class Description
+  RT::Scrips - a collection of RT Scrip objects
+
 =head1 SYNOPSIS
 
-  use RT::Scrips
+  use RT::Scrips;
 
 =head1 DESCRIPTION
 
 
 =head1 METHODS
 
+
+
 =cut
 
+
 package RT::Scrips;
 
-use RT::SearchBuilder;
+use strict;
+use warnings;
+
 use RT::Scrip;
 
-use vars qw( @ISA );
-@ISA= qw(RT::SearchBuilder);
+use base 'RT::SearchBuilder';
+
+sub Table { 'Scrips'}
+
+
+=head2 LimitToQueue
+
+Takes a queue id (numerical) as its only argument. Makes sure that 
+Scopes it pulls out apply to this queue (or another that you've selected with
+another call to this method
+
+=cut
+
+sub LimitToQueue  {
+   my $self = shift;
+  my $queue = shift;
+  $self->Limit (ENTRYAGGREGATOR => 'OR',
+               FIELD => 'Queue',
+               VALUE => "$queue")
+      if defined $queue;
+  
+}
+
+
+=head2 LimitToGlobal
+
+Makes sure that 
+Scopes it pulls out apply to all queues (or another that you've selected with
+another call to this method or LimitToQueue
+
+=cut
+
+
+sub LimitToGlobal  {
+   my $self = shift;
+  $self->Limit (ENTRYAGGREGATOR => 'OR',
+               FIELD => 'Queue',
+               VALUE => 0);
+  
+}
+
+# {{{ sub Next 
+
+=head2 Next
 
+Returns the next scrip that this user can see.
 
-sub _Init {
+=cut
+  
+sub Next {
+    my $self = shift;
+    
+    
+    my $Scrip = $self->SUPER::Next();
+    if ((defined($Scrip)) and (ref($Scrip))) {
+
+       if ($Scrip->CurrentUserHasRight('ShowScrips')) {
+           return($Scrip);
+       }
+       
+       #If the user doesn't have the right to show this scrip
+       else {  
+           return($self->Next());
+       }
+    }
+    #if there never was any scrip
+    else {
+       return(undef);
+    }  
+    
+}
+
+=head2 Apply
+
+Run through the relevant scrips.  Scrips will run in order based on 
+description.  (Most common use case is to prepend a number to the description,
+forcing the scrips to run in ascending alphanumerical order.)
+
+=cut
+
+sub Apply {
     my $self = shift;
-    $self->{'table'} = 'Scrips';
-    $self->{'primary_key'} = 'id';
 
+    my %args = ( TicketObj      => undef,
+                 Ticket         => undef,
+                 Transaction    => undef,
+                 TransactionObj => undef,
+                 Stage          => undef,
+                 Type           => undef,
+                 @_ );
+
+    $self->Prepare(%args);
+    $self->Commit();
 
-    return ( $self->SUPER::_Init(@_) );
 }
 
+=head2 Commit
 
-=head2 NewItem
+Commit all of this object's prepared scrips
 
-Returns an empty new RT::Scrip item
+=cut
+
+sub Commit {
+    my $self = shift;
+
+    foreach my $scrip (@{$self->Prepared}) {
+        $RT::Logger->debug(
+            "Committing scrip #". $scrip->id
+            ." on txn #". $self->{'TransactionObj'}->id
+            ." of ticket #". $self->{'TicketObj'}->id
+        );
+
+        $scrip->Commit( TicketObj      => $self->{'TicketObj'},
+                        TransactionObj => $self->{'TransactionObj'} );
+    }
+
+}
+
+
+=head2 Prepare
+
+Only prepare the scrips, returning an array of the scrips we're interested in
+in order of preparation, not execution
 
 =cut
 
-sub NewItem {
+sub Prepare { 
     my $self = shift;
-    return(RT::Scrip->new($self->CurrentUser));
+    my %args = ( TicketObj      => undef,
+                 Ticket         => undef,
+                 Transaction    => undef,
+                 TransactionObj => undef,
+                 Stage          => undef,
+                 Type           => undef,
+                 @_ );
+
+    #We're really going to need a non-acled ticket for the scrips to work
+    $self->_SetupSourceObjects( TicketObj      => $args{'TicketObj'},
+                                Ticket         => $args{'Ticket'},
+                                TransactionObj => $args{'TransactionObj'},
+                                Transaction    => $args{'Transaction'} );
+
+
+    $self->_FindScrips( Stage => $args{'Stage'}, Type => $args{'Type'} );
+
+
+    #Iterate through each script and check it's applicability.
+    while ( my $scrip = $self->Next() ) {
+
+          unless ( $scrip->IsApplicable(
+                                     TicketObj      => $self->{'TicketObj'},
+                                     TransactionObj => $self->{'TransactionObj'}
+                   ) ) {
+                   $RT::Logger->debug("Skipping Scrip #".$scrip->Id." because it isn't applicable");
+                   next;
+               }
+
+        #If it's applicable, prepare and commit it
+          unless ( $scrip->Prepare( TicketObj      => $self->{'TicketObj'},
+                                    TransactionObj => $self->{'TransactionObj'}
+                   ) ) {
+                   $RT::Logger->debug("Skipping Scrip #".$scrip->Id." because it didn't Prepare");
+                   next;
+               }
+        push @{$self->{'prepared_scrips'}}, $scrip;
+
+    }
+
+    return (@{$self->Prepared});
+
+};
+
+=head2 Prepared
+
+Returns an arrayref of the scrips this object has prepared
+
+
+=cut
+
+sub Prepared {
+    my $self = shift;
+    return ($self->{'prepared_scrips'} || []);
 }
 
-        eval "require RT::Scrips_Overlay";
-        if ($@ && $@ !~ qr{^Can't locate RT/Scrips_Overlay.pm}) {
-            die $@;
-        };
+=head2  _SetupSourceObjects { TicketObj , Ticket, Transaction, TransactionObj }
 
-        eval "require RT::Scrips_Vendor";
-        if ($@ && $@ !~ qr{^Can't locate RT/Scrips_Vendor.pm}) {
-            die $@;
-        };
+Setup a ticket and transaction for this Scrip collection to work with as it runs through the 
+relevant scrips.  (Also to figure out which scrips apply)
 
-        eval "require RT::Scrips_Local";
-        if ($@ && $@ !~ qr{^Can't locate RT/Scrips_Local.pm}) {
-            die $@;
-        };
+Returns: nothing
 
+=cut
 
 
+sub _SetupSourceObjects {
 
-=head1 SEE ALSO
+    my $self = shift;
+    my %args = ( 
+            TicketObj => undef,
+            Ticket => undef,
+            Transaction => undef,
+            TransactionObj => undef,
+            @_ );
+
+
+    if ( $args{'TicketObj'} ) {
+        # This loads a clean copy of the Ticket object to ensure that we
+        # don't accidentally escalate the privileges of the passed in
+        # ticket (this function can be invoked from the UI).
+        # We copy the TransactionBatch transactions so that Scrips
+        # running against the new Ticket will have access to them. We
+        # use RanTransactionBatch to guard against running
+        # TransactionBatch Scrips more than once.
+        $self->{'TicketObj'} = RT::Ticket->new( $self->CurrentUser );
+        $self->{'TicketObj'}->Load( $args{'TicketObj'}->Id );
+        if ( $args{'TicketObj'}->TransactionBatch ) {
+            # try to ensure that we won't infinite loop if something dies, triggering DESTROY while 
+            # we have the _TransactionBatch objects;
+            $self->{'TicketObj'}->RanTransactionBatch(1);
+            $self->{'TicketObj'}->{'_TransactionBatch'} = $args{'TicketObj'}->{'_TransactionBatch'};
+        }
+    }
+    else {
+        $self->{'TicketObj'} = RT::Ticket->new( $self->CurrentUser );
+        $self->{'TicketObj'}->Load( $args{'Ticket'} )
+          || $RT::Logger->err("$self couldn't load ticket $args{'Ticket'}");
+    }
+
+    if ( ( $self->{'TransactionObj'} = $args{'TransactionObj'} ) ) {
+        $self->{'TransactionObj'}->CurrentUser( $self->CurrentUser );
+    }
+    else {
+        $self->{'TransactionObj'} = RT::Transaction->new( $self->CurrentUser );
+        $self->{'TransactionObj'}->Load( $args{'Transaction'} )
+          || $RT::Logger->err( "$self couldn't load transaction $args{'Transaction'}");
+    }
+} 
+
+
+
+=head2 _FindScrips
+
+Find only the apropriate scrips for whatever we're doing now.  Order them 
+by their description.  (Most common use case is to prepend a number to the
+description, forcing the scrips to display and run in ascending alphanumerical 
+order.)
 
-This class allows "overlay" methods to be placed
-into the following files _Overlay is for a System overlay by the original author,
-_Vendor is for 3rd-party vendor add-ons, while _Local is for site-local customizations.  
+=cut
 
-These overlay files can contain new subs or subs to replace existing subs in this module.
+sub _FindScrips {
+    my $self = shift;
+    my %args = (
+                 Stage => undef,
+                 Type => undef,
+                 @_ );
+
+
+    $self->LimitToQueue( $self->{'TicketObj'}->QueueObj->Id )
+      ;    #Limit it to  $Ticket->QueueObj->Id
+    $self->LimitToGlobal();
+      # or to "global"
+
+    $self->Limit( FIELD => "Stage", VALUE => $args{'Stage'} );
+
+    my $ConditionsAlias = $self->NewAlias('ScripConditions');
+
+    $self->Join(
+        ALIAS1 => 'main',
+        FIELD1 => 'ScripCondition',
+        ALIAS2 => $ConditionsAlias,
+        FIELD2 => 'id'
+    );
+
+    #We only want things where the scrip applies to this sort of transaction
+    # TransactionBatch stage can define list of transaction
+    foreach( split /\s*,\s*/, ($args{'Type'} || '') ) {
+       $self->Limit(
+           ALIAS           => $ConditionsAlias,
+           FIELD           => 'ApplicableTransTypes',
+           OPERATOR        => 'LIKE',
+           VALUE           => $_,
+           ENTRYAGGREGATOR => 'OR',
+       )
+    }
+
+    # Or where the scrip applies to any transaction
+    $self->Limit(
+        ALIAS           => $ConditionsAlias,
+        FIELD           => 'ApplicableTransTypes',
+        OPERATOR        => 'LIKE',
+        VALUE           => "Any",
+        ENTRYAGGREGATOR => 'OR',
+    );
+
+    # Promise some kind of ordering
+    $self->OrderBy( FIELD => 'Description' );
+
+    # we call Count below, but later we always do search
+    # so just do search and get count from results
+    $self->_DoSearch if $self->{'must_redo_search'};
+
+    $RT::Logger->debug(
+        "Found ". $self->Count ." scrips for $args{'Stage'} stage"
+        ." with applicable type(s) $args{'Type'}"
+        ." for txn #".$self->{TransactionObj}->Id
+        ." on ticket #".$self->{TicketObj}->Id
+    );
+}
 
-Each of these files should begin with the line 
 
-   no warnings qw(redefine);
 
-so that perl does not kick and scream when you redefine a subroutine or variable in your overlay.
 
-RT::Scrips_Overlay, RT::Scrips_Vendor, RT::Scrips_Local
+=head2 NewItem
+
+Returns an empty new RT::Scrip item
 
 =cut
 
+sub NewItem {
+    my $self = shift;
+    return(RT::Scrip->new($self->CurrentUser));
+}
+RT::Base->_ImportOverlays();
 
 1;