Upgrade to 4.2.8
[usit-rt.git] / lib / RT / Scrips.pm
1 # BEGIN BPS TAGGED BLOCK {{{
2 #
3 # COPYRIGHT:
4 #
5 # This software is Copyright (c) 1996-2014 Best Practical Solutions, LLC
6 #                                          <sales@bestpractical.com>
7 #
8 # (Except where explicitly superseded by other copyright notices)
9 #
10 #
11 # LICENSE:
12 #
13 # This work is made available to you under the terms of Version 2 of
14 # the GNU General Public License. A copy of that license should have
15 # been provided with this software, but in any event can be snarfed
16 # from www.gnu.org.
17 #
18 # This work is distributed in the hope that it will be useful, but
19 # WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
21 # General Public License for more details.
22 #
23 # You should have received a copy of the GNU General Public License
24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
26 # 02110-1301 or visit their web page on the internet at
27 # http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
28 #
29 #
30 # CONTRIBUTION SUBMISSION POLICY:
31 #
32 # (The following paragraph is not intended to limit the rights granted
33 # to you to modify and distribute this software under the terms of
34 # the GNU General Public License and is only of importance to you if
35 # you choose to contribute your changes and enhancements to the
36 # community by submitting them to Best Practical Solutions, LLC.)
37 #
38 # By intentionally submitting any modifications, corrections or
39 # derivatives to this work, or any other work intended for use with
40 # Request Tracker, to Best Practical Solutions, LLC, you confirm that
41 # you are the copyright holder for those contributions and you grant
42 # Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
43 # royalty-free, perpetual, license to use, copy, create derivative
44 # works based on those contributions, and sublicense and distribute
45 # those contributions and any derivatives thereof.
46 #
47 # END BPS TAGGED BLOCK }}}
48
49 =head1 NAME
50
51   RT::Scrips - a collection of RT Scrip objects
52
53 =head1 SYNOPSIS
54
55   use RT::Scrips;
56
57 =head1 DESCRIPTION
58
59
60 =head1 METHODS
61
62
63
64 =cut
65
66
67 package RT::Scrips;
68
69 use strict;
70 use warnings;
71
72 use base 'RT::SearchBuilder';
73
74 use RT::Scrip;
75 use RT::ObjectScrips;
76
77 sub Table { 'Scrips'}
78
79 sub _Init {
80     my $self = shift;
81
82     $self->{'with_disabled_column'} = 1;
83
84     return ( $self->SUPER::_Init(@_) );
85 }
86
87 =head2 LimitToQueue
88
89 Takes a queue id (numerical) as its only argument. Makes sure that 
90 Scopes it pulls out apply to this queue (or another that you've selected with
91 another call to this method
92
93 =cut
94
95 sub LimitToQueue  {
96     my $self = shift;
97     my $queue = shift;
98     return unless defined $queue;
99
100     my $alias = RT::ObjectScrips->new( $self->CurrentUser )
101         ->JoinTargetToThis( $self );
102     $self->Limit(
103         ALIAS => $alias,
104         FIELD => 'ObjectId',
105         VALUE => int $queue,
106     );
107 }
108
109
110 =head2 LimitToGlobal
111
112 Makes sure that 
113 Scopes it pulls out apply to all queues (or another that you've selected with
114 another call to this method or LimitToQueue
115
116 =cut
117
118
119 sub LimitToGlobal  {
120     my $self = shift;
121     return $self->LimitToQueue(0);
122 }
123
124 sub LimitToAdded {
125     my $self = shift;
126     return RT::ObjectScrips->new( $self->CurrentUser )
127         ->LimitTargetToAdded( $self => @_ );
128 }
129
130 sub LimitToNotAdded {
131     my $self = shift;
132     return RT::ObjectScrips->new( $self->CurrentUser )
133         ->LimitTargetToNotAdded( $self => @_ );
134 }
135
136 sub LimitByStage  {
137     my $self = shift;
138     my %args = @_%2? (Stage => @_) : @_;
139     return unless defined $args{'Stage'};
140
141     my $alias = RT::ObjectScrips->new( $self->CurrentUser )
142         ->JoinTargetToThis( $self, %args );
143     $self->Limit(
144         ALIAS => $alias,
145         FIELD => 'Stage',
146         VALUE => $args{'Stage'},
147     );
148 }
149
150 =head2 LimitByTemplate
151
152 Takes a L<RT::Template> object and limits scrips to those that
153 use the template.
154
155 =cut
156
157 sub LimitByTemplate {
158     my $self = shift;
159     my $template = shift;
160
161     $self->Limit( FIELD => 'Template', VALUE => $template->Name );
162
163     if ( $template->Queue ) {
164         # if template is local then we are interested in global and
165         # queue specific scrips
166         $self->LimitToQueue( $template->Queue );
167         $self->LimitToGlobal;
168     }
169     else { # template is global
170
171         # if every queue has a custom version then there
172         # is no scrip that uses the template
173         {
174             my $queues = RT::Queues->new( RT->SystemUser );
175             my $alias = $queues->Join(
176                 TYPE   => 'LEFT',
177                 ALIAS1 => 'main',
178                 FIELD1 => 'id',
179                 TABLE2 => 'Templates',
180                 FIELD2 => 'Queue',
181             );
182             $queues->Limit(
183                 LEFTJOIN   => $alias,
184                 ALIAS      => $alias,
185                 FIELD      => 'Name',
186                 VALUE      => $template->Name,
187             );
188             $queues->Limit(
189                 ALIAS      => $alias,
190                 FIELD      => 'id',
191                 OPERATOR   => 'IS',
192                 VALUE      => 'NULL',
193             );
194             return $self->Limit( FIELD => 'id', VALUE => 0 )
195                 unless $queues->Count;
196         }
197
198         # otherwise it's either a global scrip or application to
199         # a queue with custom version of the template.
200         my $os_alias = RT::ObjectScrips->new( $self->CurrentUser )
201             ->JoinTargetToThis( $self );
202         my $tmpl_alias = $self->Join(
203             TYPE   => 'LEFT',
204             ALIAS1 => $os_alias,
205             FIELD1 => 'ObjectId',
206             TABLE2 => 'Templates',
207             FIELD2 => 'Queue',
208         );
209         $self->Limit(
210             LEFTJOIN => $tmpl_alias, ALIAS => $tmpl_alias, FIELD => 'Name', VALUE => $template->Name,
211         );
212         $self->Limit(
213             LEFTJOIN => $tmpl_alias, ALIAS => $tmpl_alias, FIELD => 'Queue', OPERATOR => '!=', VALUE => 0,
214         );
215
216         $self->_OpenParen('UsedBy');
217         $self->Limit( SUBCLAUSE => 'UsedBy', ALIAS => $os_alias, FIELD => 'ObjectId', VALUE => 0 );
218         $self->Limit(
219             SUBCLAUSE => 'UsedBy',
220             ALIAS => $tmpl_alias,
221             FIELD => 'id',
222             OPERATOR => 'IS',
223             VALUE => 'NULL',
224         );
225         $self->_CloseParen('UsedBy');
226     }
227 }
228
229 sub ApplySortOrder {
230     my $self = shift;
231     my $order = shift || 'ASC';
232     $self->OrderByCols( {
233         ALIAS => RT::ObjectScrips->new( $self->CurrentUser )
234             ->JoinTargetToThis( $self => @_ )
235         ,
236         FIELD => 'SortOrder',
237         ORDER => $order,
238     } );
239 }
240
241 # {{{ sub Next 
242
243 =head2 Next
244
245 Returns the next scrip that this user can see.
246
247 =cut
248
249 sub Next {
250     my $self = shift;
251
252
253     my $Scrip = $self->SUPER::Next();
254     if ((defined($Scrip)) and (ref($Scrip))) {
255
256         if ($Scrip->CurrentUserHasRight('ShowScrips')) {
257             return($Scrip);
258         }
259
260         #If the user doesn't have the right to show this scrip
261         else {
262             return($self->Next());
263         }
264     }
265     #if there never was any scrip
266     else {
267         return(undef);
268     }
269
270 }
271
272 =head2 Apply
273
274 Run through the relevant scrips.  Scrips will run in order based on 
275 description.  (Most common use case is to prepend a number to the description,
276 forcing the scrips to run in ascending alphanumerical order.)
277
278 =cut
279
280 sub Apply {
281     my $self = shift;
282
283     my %args = ( TicketObj      => undef,
284                  Ticket         => undef,
285                  Transaction    => undef,
286                  TransactionObj => undef,
287                  Stage          => undef,
288                  Type           => undef,
289                  @_ );
290
291     $self->Prepare(%args);
292     $self->Commit();
293
294 }
295
296 =head2 Commit
297
298 Commit all of this object's prepared scrips
299
300 =cut
301
302 sub Commit {
303     my $self = shift;
304
305     foreach my $scrip (@{$self->Prepared}) {
306         $RT::Logger->debug(
307             "Committing scrip #". $scrip->id
308             ." on txn #". $self->{'TransactionObj'}->id
309             ." of ticket #". $self->{'TicketObj'}->id
310         );
311
312         $scrip->Commit( TicketObj      => $self->{'TicketObj'},
313                         TransactionObj => $self->{'TransactionObj'} );
314     }
315
316 }
317
318
319 =head2 Prepare
320
321 Only prepare the scrips, returning an array of the scrips we're interested in
322 in order of preparation, not execution
323
324 =cut
325
326 sub Prepare { 
327     my $self = shift;
328     my %args = ( TicketObj      => undef,
329                  Ticket         => undef,
330                  Transaction    => undef,
331                  TransactionObj => undef,
332                  Stage          => undef,
333                  Type           => undef,
334                  @_ );
335
336     #We're really going to need a non-acled ticket for the scrips to work
337     $self->_SetupSourceObjects( TicketObj      => $args{'TicketObj'},
338                                 Ticket         => $args{'Ticket'},
339                                 TransactionObj => $args{'TransactionObj'},
340                                 Transaction    => $args{'Transaction'} );
341
342
343     $self->_FindScrips( Stage => $args{'Stage'}, Type => $args{'Type'} );
344
345
346     #Iterate through each script and check it's applicability.
347     while ( my $scrip = $self->Next() ) {
348
349           unless ( $scrip->IsApplicable(
350                                      TicketObj      => $self->{'TicketObj'},
351                                      TransactionObj => $self->{'TransactionObj'}
352                    ) ) {
353                    $RT::Logger->debug("Skipping Scrip #".$scrip->Id." because it isn't applicable");
354                    next;
355                }
356
357         #If it's applicable, prepare and commit it
358           unless ( $scrip->Prepare( TicketObj      => $self->{'TicketObj'},
359                                     TransactionObj => $self->{'TransactionObj'}
360                    ) ) {
361                    $RT::Logger->debug("Skipping Scrip #".$scrip->Id." because it didn't Prepare");
362                    next;
363                }
364         push @{$self->{'prepared_scrips'}}, $scrip;
365
366     }
367
368     return (@{$self->Prepared});
369
370 };
371
372 =head2 Prepared
373
374 Returns an arrayref of the scrips this object has prepared
375
376
377 =cut
378
379 sub Prepared {
380     my $self = shift;
381     return ($self->{'prepared_scrips'} || []);
382 }
383
384 =head2  _SetupSourceObjects { TicketObj , Ticket, Transaction, TransactionObj }
385
386 Setup a ticket and transaction for this Scrip collection to work with as it runs through the 
387 relevant scrips.  (Also to figure out which scrips apply)
388
389 Returns: nothing
390
391 =cut
392
393
394 sub _SetupSourceObjects {
395
396     my $self = shift;
397     my %args = ( 
398             TicketObj => undef,
399             Ticket => undef,
400             Transaction => undef,
401             TransactionObj => undef,
402             @_ );
403
404
405     if ( $args{'TicketObj'} ) {
406         # This loads a clean copy of the Ticket object to ensure that we
407         # don't accidentally escalate the privileges of the passed in
408         # ticket (this function can be invoked from the UI).
409         # We copy the TransactionBatch transactions so that Scrips
410         # running against the new Ticket will have access to them. We
411         # use RanTransactionBatch to guard against running
412         # TransactionBatch Scrips more than once.
413         $self->{'TicketObj'} = RT::Ticket->new( $self->CurrentUser );
414         $self->{'TicketObj'}->Load( $args{'TicketObj'}->Id );
415         if ( $args{'TicketObj'}->TransactionBatch ) {
416             # try to ensure that we won't infinite loop if something dies, triggering DESTROY while 
417             # we have the _TransactionBatch objects;
418             $self->{'TicketObj'}->RanTransactionBatch(1);
419             $self->{'TicketObj'}->{'_TransactionBatch'} = $args{'TicketObj'}->{'_TransactionBatch'};
420         }
421     }
422     else {
423         $self->{'TicketObj'} = RT::Ticket->new( $self->CurrentUser );
424         $self->{'TicketObj'}->Load( $args{'Ticket'} )
425           || $RT::Logger->err("$self couldn't load ticket $args{'Ticket'}");
426     }
427
428     if ( ( $self->{'TransactionObj'} = $args{'TransactionObj'} ) ) {
429         $self->{'TransactionObj'}->CurrentUser( $self->CurrentUser );
430     }
431     else {
432         $self->{'TransactionObj'} = RT::Transaction->new( $self->CurrentUser );
433         $self->{'TransactionObj'}->Load( $args{'Transaction'} )
434           || $RT::Logger->err( "$self couldn't load transaction $args{'Transaction'}");
435     }
436
437
438
439
440 =head2 _FindScrips
441
442 Find only the apropriate scrips for whatever we're doing now.  Order them 
443 by their description.  (Most common use case is to prepend a number to the
444 description, forcing the scrips to display and run in ascending alphanumerical 
445 order.)
446
447 =cut
448
449 sub _FindScrips {
450     my $self = shift;
451     my %args = (
452                  Stage => undef,
453                  Type => undef,
454                  @_ );
455
456
457     $self->LimitToQueue( $self->{'TicketObj'}->QueueObj->Id );
458     $self->LimitToGlobal;
459     $self->LimitByStage( $args{'Stage'} );
460
461     my $ConditionsAlias = $self->Join(
462         ALIAS1 => 'main',
463         FIELD1 => 'ScripCondition',
464         TABLE2 => 'ScripConditions',
465         FIELD2 => 'id',
466     );
467
468     #We only want things where the scrip applies to this sort of transaction
469     # TransactionBatch stage can define list of transaction
470     foreach( split /\s*,\s*/, ($args{'Type'} || '') ) {
471         $self->Limit(
472             ALIAS           => $ConditionsAlias,
473             FIELD           => 'ApplicableTransTypes',
474             OPERATOR        => 'LIKE',
475             VALUE           => $_,
476             ENTRYAGGREGATOR => 'OR',
477         )
478     }
479
480     # Or where the scrip applies to any transaction
481     $self->Limit(
482         ALIAS           => $ConditionsAlias,
483         FIELD           => 'ApplicableTransTypes',
484         OPERATOR        => 'LIKE',
485         VALUE           => "Any",
486         ENTRYAGGREGATOR => 'OR',
487     );
488
489     $self->ApplySortOrder;
490
491     # we call Count below, but later we always do search
492     # so just do search and get count from results
493     $self->_DoSearch if $self->{'must_redo_search'};
494
495     $RT::Logger->debug(
496         "Found ". $self->Count ." scrips for $args{'Stage'} stage"
497         ." with applicable type(s) $args{'Type'}"
498         ." for txn #".$self->{TransactionObj}->Id
499         ." on ticket #".$self->{TicketObj}->Id
500     );
501 }
502
503 RT::Base->_ImportOverlays();
504
505 1;