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