X-Git-Url: http://git.freeside.biz/gitweb/?a=blobdiff_plain;f=rt%2Flib%2FRT%2FScrips.pm;h=193399d8ca032e548236f3be149db17776efbd94;hb=7322f2afedcc2f427e997d1535a503613a83f088;hp=cff1a5331d060137a62d0c082a4a88f4cd7f7b6f;hpb=8103c1fc1b2c27a6855feadf26f91b980a54bc52;p=freeside.git diff --git a/rt/lib/RT/Scrips.pm b/rt/lib/RT/Scrips.pm index cff1a5331..193399d8c 100755 --- a/rt/lib/RT/Scrips.pm +++ b/rt/lib/RT/Scrips.pm @@ -1,40 +1,40 @@ # BEGIN BPS TAGGED BLOCK {{{ -# +# # COPYRIGHT: -# -# This software is Copyright (c) 1996-2007 Best Practical Solutions, LLC -# -# +# +# This software is Copyright (c) 1996-2016 Best Practical Solutions, LLC +# +# # (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 # been provided with this software, but in any event can be snarfed # from www.gnu.org. -# +# # This work is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. -# +# # 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., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301 or visit their web page on the internet at -# http://www.gnu.org/copyleft/gpl.html. -# -# +# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html. +# +# # CONTRIBUTION SUBMISSION POLICY: -# +# # (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 @@ -43,97 +43,445 @@ # 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 }}} -# Autogenerated by DBIx::SearchBuilder factory (by ) -# WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST. -# -# !! DO NOT EDIT THIS FILE !! # - -use strict; - +# END BPS TAGGED BLOCK }}} =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 RT::Scrip; +use strict; +use warnings; -use vars qw( @ISA ); -@ISA= qw(RT::SearchBuilder); +use base 'RT::SearchBuilder'; +use RT::Scrip; +use RT::ObjectScrips; + +sub Table { 'Scrips'} sub _Init { my $self = shift; - $self->{'table'} = 'Scrips'; - $self->{'primary_key'} = 'id'; + $self->{'with_disabled_column'} = 1; return ( $self->SUPER::_Init(@_) ); } +=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; + return unless defined $queue; + + my $alias = RT::ObjectScrips->new( $self->CurrentUser ) + ->JoinTargetToThis( $self ); + $self->Limit( + ALIAS => $alias, + FIELD => 'ObjectId', + VALUE => int $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; + return $self->LimitToQueue(0); +} + +sub LimitToAdded { + my $self = shift; + return RT::ObjectScrips->new( $self->CurrentUser ) + ->LimitTargetToAdded( $self => @_ ); +} + +sub LimitToNotAdded { + my $self = shift; + return RT::ObjectScrips->new( $self->CurrentUser ) + ->LimitTargetToNotAdded( $self => @_ ); +} + +sub LimitByStage { + my $self = shift; + my %args = @_%2? (Stage => @_) : @_; + return unless defined $args{'Stage'}; + + my $alias = RT::ObjectScrips->new( $self->CurrentUser ) + ->JoinTargetToThis( $self, %args ); + $self->Limit( + ALIAS => $alias, + FIELD => 'Stage', + VALUE => $args{'Stage'}, + ); +} + +=head2 LimitByTemplate + +Takes a L object and limits scrips to those that +use the template. + +=cut + +sub LimitByTemplate { + my $self = shift; + my $template = shift; + + $self->Limit( FIELD => 'Template', VALUE => $template->Name ); + + if ( $template->Queue ) { + # if template is local then we are interested in global and + # queue specific scrips + $self->LimitToQueue( $template->Queue ); + $self->LimitToGlobal; + } + else { # template is global + + # if every queue has a custom version then there + # is no scrip that uses the template + { + my $queues = RT::Queues->new( RT->SystemUser ); + my $alias = $queues->Join( + TYPE => 'LEFT', + ALIAS1 => 'main', + FIELD1 => 'id', + TABLE2 => 'Templates', + FIELD2 => 'Queue', + ); + $queues->Limit( + LEFTJOIN => $alias, + ALIAS => $alias, + FIELD => 'Name', + VALUE => $template->Name, + ); + $queues->Limit( + ALIAS => $alias, + FIELD => 'id', + OPERATOR => 'IS', + VALUE => 'NULL', + ); + return $self->Limit( FIELD => 'id', VALUE => 0 ) + unless $queues->Count; + } + + # otherwise it's either a global scrip or application to + # a queue with custom version of the template. + my $os_alias = RT::ObjectScrips->new( $self->CurrentUser ) + ->JoinTargetToThis( $self ); + my $tmpl_alias = $self->Join( + TYPE => 'LEFT', + ALIAS1 => $os_alias, + FIELD1 => 'ObjectId', + TABLE2 => 'Templates', + FIELD2 => 'Queue', + ); + $self->Limit( + LEFTJOIN => $tmpl_alias, ALIAS => $tmpl_alias, FIELD => 'Name', VALUE => $template->Name, + ); + $self->Limit( + LEFTJOIN => $tmpl_alias, ALIAS => $tmpl_alias, FIELD => 'Queue', OPERATOR => '!=', VALUE => 0, + ); + + $self->_OpenParen('UsedBy'); + $self->Limit( SUBCLAUSE => 'UsedBy', ALIAS => $os_alias, FIELD => 'ObjectId', VALUE => 0 ); + $self->Limit( + SUBCLAUSE => 'UsedBy', + ALIAS => $tmpl_alias, + FIELD => 'id', + OPERATOR => 'IS', + VALUE => 'NULL', + ); + $self->_CloseParen('UsedBy'); + } +} + +sub ApplySortOrder { + my $self = shift; + my $order = shift || 'ASC'; + $self->OrderByCols( { + ALIAS => RT::ObjectScrips->new( $self->CurrentUser ) + ->JoinTargetToThis( $self => @_ ) + , + FIELD => 'SortOrder', + ORDER => $order, + } ); +} + +=head2 AddRecord + +Overrides the collection to ensure that only scrips the user can see are +returned. + +=cut + +sub AddRecord { + my $self = shift; + my ($record) = @_; + + return unless $record->CurrentUserHasRight('ShowScrips'); + return $self->SUPER::AddRecord( $record ); +} + +=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.) -=head2 NewItem +=cut + +sub Apply { + my $self = shift; -Returns an empty new RT::Scrip item + my %args = ( TicketObj => undef, + Ticket => undef, + Transaction => undef, + TransactionObj => undef, + Stage => undef, + Type => undef, + @_ ); + + $self->Prepare(%args); + $self->Commit(); + +} + +=head2 Commit + +Commit all of this object's prepared scrips =cut -sub NewItem { +sub Commit { my $self = shift; - return(RT::Scrip->new($self->CurrentUser)); + + 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'} ); + } + } - eval "require RT::Scrips_Overlay"; - if ($@ && $@ !~ qr{^Can't locate RT/Scrips_Overlay.pm}) { - die $@; - }; - eval "require RT::Scrips_Vendor"; - if ($@ && $@ !~ qr{^Can't locate RT/Scrips_Vendor.pm}) { - die $@; - }; +=head2 Prepare + +Only prepare the scrips, returning an array of the scrips we're interested in +in order of preparation, not execution + +=cut + +sub Prepare { + my $self = shift; + 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; + } - eval "require RT::Scrips_Local"; - if ($@ && $@ !~ qr{^Can't locate RT/Scrips_Local.pm}) { - die $@; - }; + #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}); +}; -=head1 SEE ALSO +=head2 Prepared -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. +Returns an arrayref of the scrips this object has prepared -These overlay files can contain new subs or subs to replace existing subs in this module. -Each of these files should begin with the line +=cut + +sub Prepared { + my $self = shift; + return ($self->{'prepared_scrips'} || []); +} - no warnings qw(redefine); +=head2 _SetupSourceObjects { TicketObj , Ticket, Transaction, TransactionObj } -so that perl does not kick and scream when you redefine a subroutine or variable in your overlay. +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) -RT::Scrips_Overlay, RT::Scrips_Vendor, RT::Scrips_Local +Returns: nothing =cut +sub _SetupSourceObjects { + + 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 appropriate scrips for whatever we're doing now. Order +them by the SortOrder field from the ObjectScrips table. + +=cut + +sub _FindScrips { + my $self = shift; + my %args = ( + Stage => undef, + Type => undef, + @_ ); + + + $self->LimitToQueue( $self->{'TicketObj'}->QueueObj->Id ); + $self->LimitToGlobal; + $self->LimitByStage( $args{'Stage'} ); + + my $ConditionsAlias = $self->Join( + ALIAS1 => 'main', + FIELD1 => 'ScripCondition', + TABLE2 => 'ScripConditions', + 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', + ); + + $self->ApplySortOrder; + + # 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 + ); +} + +RT::Base->_ImportOverlays(); + 1;