merging RT 4.0.6
[freeside.git] / rt / lib / RT / Scrips.pm
1 # BEGIN BPS TAGGED BLOCK {{{
2 #
3 # COPYRIGHT:
4 #
5 # This software is Copyright (c) 1996-2012 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 RT::Scrip;
73
74 use base 'RT::SearchBuilder';
75
76 sub Table { 'Scrips'}
77
78
79 =head2 LimitToQueue
80
81 Takes a queue id (numerical) as its only argument. Makes sure that 
82 Scopes it pulls out apply to this queue (or another that you've selected with
83 another call to this method
84
85 =cut
86
87 sub LimitToQueue  {
88    my $self = shift;
89   my $queue = shift;
90  
91   $self->Limit (ENTRYAGGREGATOR => 'OR',
92                 FIELD => 'Queue',
93                 VALUE => "$queue")
94       if defined $queue;
95   
96 }
97
98
99 =head2 LimitToGlobal
100
101 Makes sure that 
102 Scopes it pulls out apply to all queues (or another that you've selected with
103 another call to this method or LimitToQueue
104
105 =cut
106
107
108 sub LimitToGlobal  {
109    my $self = shift;
110  
111   $self->Limit (ENTRYAGGREGATOR => 'OR',
112                 FIELD => 'Queue',
113                 VALUE => 0);
114   
115 }
116
117 # {{{ sub Next 
118
119 =head2 Next
120
121 Returns the next scrip that this user can see.
122
123 =cut
124   
125 sub Next {
126     my $self = shift;
127     
128     
129     my $Scrip = $self->SUPER::Next();
130     if ((defined($Scrip)) and (ref($Scrip))) {
131
132         if ($Scrip->CurrentUserHasRight('ShowScrips')) {
133             return($Scrip);
134         }
135         
136         #If the user doesn't have the right to show this scrip
137         else {  
138             return($self->Next());
139         }
140     }
141     #if there never was any scrip
142     else {
143         return(undef);
144     }   
145     
146 }
147
148 =head2 Apply
149
150 Run through the relevant scrips.  Scrips will run in order based on 
151 description.  (Most common use case is to prepend a number to the description,
152 forcing the scrips to run in ascending alphanumerical order.)
153
154 =cut
155
156 sub Apply {
157     my $self = shift;
158
159     my %args = ( TicketObj      => undef,
160                  Ticket         => undef,
161                  Transaction    => undef,
162                  TransactionObj => undef,
163                  Stage          => undef,
164                  Type           => undef,
165                  @_ );
166
167     $self->Prepare(%args);
168     $self->Commit();
169
170 }
171
172 =head2 Commit
173
174 Commit all of this object's prepared scrips
175
176 =cut
177
178 sub Commit {
179     my $self = shift;
180
181     # RT::Scrips->_SetupSourceObjects will clobber
182     # the CurrentUser, but we need to keep this ticket
183     # so that the _TransactionBatch cache is maintained
184     # and doesn't run twice.  sigh.
185     $self->_StashCurrentUser( TicketObj => $self->{TicketObj} ) if $self->{TicketObj};
186
187     #We're really going to need a non-acled ticket for the scrips to work
188     $self->_SetupSourceObjects( TicketObj      => $self->{'TicketObj'},
189                                 TransactionObj => $self->{'TransactionObj'} );
190     
191     foreach my $scrip (@{$self->Prepared}) {
192         $RT::Logger->debug(
193             "Committing scrip #". $scrip->id
194             ." on txn #". $self->{'TransactionObj'}->id
195             ." of ticket #". $self->{'TicketObj'}->id
196         );
197
198         $scrip->Commit( TicketObj      => $self->{'TicketObj'},
199                         TransactionObj => $self->{'TransactionObj'} );
200     }
201
202     # Apply the bandaid.
203     $self->_RestoreCurrentUser( TicketObj => $self->{TicketObj} ) if $self->{TicketObj};
204 }
205
206
207 =head2 Prepare
208
209 Only prepare the scrips, returning an array of the scrips we're interested in
210 in order of preparation, not execution
211
212 =cut
213
214 sub Prepare { 
215     my $self = shift;
216     my %args = ( TicketObj      => undef,
217                  Ticket         => undef,
218                  Transaction    => undef,
219                  TransactionObj => undef,
220                  Stage          => undef,
221                  Type           => undef,
222                  @_ );
223
224     # RT::Scrips->_SetupSourceObjects will clobber
225     # the CurrentUser, but we need to keep this ticket
226     # so that the _TransactionBatch cache is maintained
227     # and doesn't run twice.  sigh.
228     $self->_StashCurrentUser( TicketObj => $args{TicketObj} ) if $args{TicketObj};
229
230     #We're really going to need a non-acled ticket for the scrips to work
231     $self->_SetupSourceObjects( TicketObj      => $args{'TicketObj'},
232                                 Ticket         => $args{'Ticket'},
233                                 TransactionObj => $args{'TransactionObj'},
234                                 Transaction    => $args{'Transaction'} );
235
236
237     $self->_FindScrips( Stage => $args{'Stage'}, Type => $args{'Type'} );
238
239
240     #Iterate through each script and check it's applicability.
241     while ( my $scrip = $self->Next() ) {
242
243           unless ( $scrip->IsApplicable(
244                                      TicketObj      => $self->{'TicketObj'},
245                                      TransactionObj => $self->{'TransactionObj'}
246                    ) ) {
247                    $RT::Logger->debug("Skipping Scrip #".$scrip->Id." because it isn't applicable");
248                    next;
249                }
250
251         #If it's applicable, prepare and commit it
252           unless ( $scrip->Prepare( TicketObj      => $self->{'TicketObj'},
253                                     TransactionObj => $self->{'TransactionObj'}
254                    ) ) {
255                    $RT::Logger->debug("Skipping Scrip #".$scrip->Id." because it didn't Prepare");
256                    next;
257                }
258         push @{$self->{'prepared_scrips'}}, $scrip;
259
260     }
261
262     # Apply the bandaid.
263     $self->_RestoreCurrentUser( TicketObj => $args{TicketObj} ) if $args{TicketObj};
264
265
266     return (@{$self->Prepared});
267
268 };
269
270 =head2 Prepared
271
272 Returns an arrayref of the scrips this object has prepared
273
274
275 =cut
276
277 sub Prepared {
278     my $self = shift;
279     return ($self->{'prepared_scrips'} || []);
280 }
281
282 =head2 _StashCurrentUser TicketObj => RT::Ticket
283
284 Saves aside the current user of the original ticket that was passed to these scrips.
285 This is used to make sure that we don't accidentally leak the RT_System current user
286 back to the calling code.
287
288 =cut
289
290 sub _StashCurrentUser {
291     my $self = shift;
292     my %args = @_;
293
294     $self->{_TicketCurrentUser} = $args{TicketObj}->CurrentUser;
295 }
296
297 =head2 _RestoreCurrentUser TicketObj => RT::Ticket
298
299 Uses the current user saved by _StashCurrentUser to reset a Ticket object
300 back to the caller's current user and avoid leaking an RT_System ticket to
301 calling code.
302
303 =cut
304
305 sub _RestoreCurrentUser {
306     my $self = shift;
307     my %args = @_;
308     unless ( $self->{_TicketCurrentUser} ) {
309         RT->Logger->debug("Called _RestoreCurrentUser without a stashed current user object");
310         return;
311     }
312     $args{TicketObj}->CurrentUser($self->{_TicketCurrentUser});
313
314 }
315
316 =head2  _SetupSourceObjects { TicketObj , Ticket, Transaction, TransactionObj }
317
318 Setup a ticket and transaction for this Scrip collection to work with as it runs through the 
319 relevant scrips.  (Also to figure out which scrips apply)
320
321 Returns: nothing
322
323 =cut
324
325
326 sub _SetupSourceObjects {
327
328     my $self = shift;
329     my %args = ( 
330             TicketObj => undef,
331             Ticket => undef,
332             Transaction => undef,
333             TransactionObj => undef,
334             @_ );
335
336
337     if ( $self->{'TicketObj'} = $args{'TicketObj'} ) {
338         # This clobbers the passed in TicketObj by turning it into one
339         # whose current user is RT_System.  Anywhere in the Web UI
340         # currently calling into this is thus susceptable to a privilege
341         # leak; the only current call site is ->Apply, which bandaids
342         # over the top of this by re-asserting the CurrentUser
343         # afterwards.
344         $self->{'TicketObj'}->CurrentUser( $self->CurrentUser );
345     }
346     else {
347         $self->{'TicketObj'} = RT::Ticket->new( $self->CurrentUser );
348         $self->{'TicketObj'}->Load( $args{'Ticket'} )
349           || $RT::Logger->err("$self couldn't load ticket $args{'Ticket'}");
350     }
351
352     if ( ( $self->{'TransactionObj'} = $args{'TransactionObj'} ) ) {
353         $self->{'TransactionObj'}->CurrentUser( $self->CurrentUser );
354     }
355     else {
356         $self->{'TransactionObj'} = RT::Transaction->new( $self->CurrentUser );
357         $self->{'TransactionObj'}->Load( $args{'Transaction'} )
358           || $RT::Logger->err( "$self couldn't load transaction $args{'Transaction'}");
359     }
360
361
362
363
364 =head2 _FindScrips
365
366 Find only the apropriate scrips for whatever we're doing now.  Order them 
367 by their description.  (Most common use case is to prepend a number to the
368 description, forcing the scrips to display and run in ascending alphanumerical 
369 order.)
370
371 =cut
372
373 sub _FindScrips {
374     my $self = shift;
375     my %args = (
376                  Stage => undef,
377                  Type => undef,
378                  @_ );
379
380
381     $self->LimitToQueue( $self->{'TicketObj'}->QueueObj->Id )
382       ;    #Limit it to  $Ticket->QueueObj->Id
383     $self->LimitToGlobal();
384       # or to "global"
385
386     $self->Limit( FIELD => "Stage", VALUE => $args{'Stage'} );
387
388     my $ConditionsAlias = $self->NewAlias('ScripConditions');
389
390     $self->Join(
391         ALIAS1 => 'main',
392         FIELD1 => 'ScripCondition',
393         ALIAS2 => $ConditionsAlias,
394         FIELD2 => 'id'
395     );
396
397     #We only want things where the scrip applies to this sort of transaction
398     # TransactionBatch stage can define list of transaction
399     foreach( split /\s*,\s*/, ($args{'Type'} || '') ) {
400         $self->Limit(
401             ALIAS           => $ConditionsAlias,
402             FIELD           => 'ApplicableTransTypes',
403             OPERATOR        => 'LIKE',
404             VALUE           => $_,
405             ENTRYAGGREGATOR => 'OR',
406         )
407     }
408
409     # Or where the scrip applies to any transaction
410     $self->Limit(
411         ALIAS           => $ConditionsAlias,
412         FIELD           => 'ApplicableTransTypes',
413         OPERATOR        => 'LIKE',
414         VALUE           => "Any",
415         ENTRYAGGREGATOR => 'OR',
416     );
417
418     # Promise some kind of ordering
419     $self->OrderBy( FIELD => 'Description' );
420
421     # we call Count below, but later we always do search
422     # so just do search and get count from results
423     $self->_DoSearch if $self->{'must_redo_search'};
424
425     $RT::Logger->debug(
426         "Found ". $self->Count ." scrips for $args{'Stage'} stage"
427         ." with applicable type(s) $args{'Type'}"
428         ." for txn #".$self->{TransactionObj}->Id
429         ." on ticket #".$self->{TicketObj}->Id
430     );
431 }
432
433
434
435
436 =head2 NewItem
437
438 Returns an empty new RT::Scrip item
439
440 =cut
441
442 sub NewItem {
443     my $self = shift;
444     return(RT::Scrip->new($self->CurrentUser));
445 }
446 RT::Base->_ImportOverlays();
447
448 1;