rt 4.0.7
[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     foreach my $scrip (@{$self->Prepared}) {
182         $RT::Logger->debug(
183             "Committing scrip #". $scrip->id
184             ." on txn #". $self->{'TransactionObj'}->id
185             ." of ticket #". $self->{'TicketObj'}->id
186         );
187
188         $scrip->Commit( TicketObj      => $self->{'TicketObj'},
189                         TransactionObj => $self->{'TransactionObj'} );
190     }
191
192 }
193
194
195 =head2 Prepare
196
197 Only prepare the scrips, returning an array of the scrips we're interested in
198 in order of preparation, not execution
199
200 =cut
201
202 sub Prepare { 
203     my $self = shift;
204     my %args = ( TicketObj      => undef,
205                  Ticket         => undef,
206                  Transaction    => undef,
207                  TransactionObj => undef,
208                  Stage          => undef,
209                  Type           => undef,
210                  @_ );
211
212     #We're really going to need a non-acled ticket for the scrips to work
213     $self->_SetupSourceObjects( TicketObj      => $args{'TicketObj'},
214                                 Ticket         => $args{'Ticket'},
215                                 TransactionObj => $args{'TransactionObj'},
216                                 Transaction    => $args{'Transaction'} );
217
218
219     $self->_FindScrips( Stage => $args{'Stage'}, Type => $args{'Type'} );
220
221
222     #Iterate through each script and check it's applicability.
223     while ( my $scrip = $self->Next() ) {
224
225           unless ( $scrip->IsApplicable(
226                                      TicketObj      => $self->{'TicketObj'},
227                                      TransactionObj => $self->{'TransactionObj'}
228                    ) ) {
229                    $RT::Logger->debug("Skipping Scrip #".$scrip->Id." because it isn't applicable");
230                    next;
231                }
232
233         #If it's applicable, prepare and commit it
234           unless ( $scrip->Prepare( TicketObj      => $self->{'TicketObj'},
235                                     TransactionObj => $self->{'TransactionObj'}
236                    ) ) {
237                    $RT::Logger->debug("Skipping Scrip #".$scrip->Id." because it didn't Prepare");
238                    next;
239                }
240         push @{$self->{'prepared_scrips'}}, $scrip;
241
242     }
243
244     return (@{$self->Prepared});
245
246 };
247
248 =head2 Prepared
249
250 Returns an arrayref of the scrips this object has prepared
251
252
253 =cut
254
255 sub Prepared {
256     my $self = shift;
257     return ($self->{'prepared_scrips'} || []);
258 }
259
260 =head2  _SetupSourceObjects { TicketObj , Ticket, Transaction, TransactionObj }
261
262 Setup a ticket and transaction for this Scrip collection to work with as it runs through the 
263 relevant scrips.  (Also to figure out which scrips apply)
264
265 Returns: nothing
266
267 =cut
268
269
270 sub _SetupSourceObjects {
271
272     my $self = shift;
273     my %args = ( 
274             TicketObj => undef,
275             Ticket => undef,
276             Transaction => undef,
277             TransactionObj => undef,
278             @_ );
279
280
281     if ( $args{'TicketObj'} ) {
282         # This loads a clean copy of the Ticket object to ensure that we
283         # don't accidentally escalate the privileges of the passed in
284         # ticket (this function can be invoked from the UI).
285         # We copy the TransactionBatch transactions so that Scrips
286         # running against the new Ticket will have access to them. We
287         # use RanTransactionBatch to guard against running
288         # TransactionBatch Scrips more than once.
289         $self->{'TicketObj'} = RT::Ticket->new( $self->CurrentUser );
290         $self->{'TicketObj'}->Load( $args{'TicketObj'}->Id );
291         if ( $args{'TicketObj'}->TransactionBatch ) {
292             # try to ensure that we won't infinite loop if something dies, triggering DESTROY while 
293             # we have the _TransactionBatch objects;
294             $self->{'TicketObj'}->RanTransactionBatch(1);
295             $self->{'TicketObj'}->{'_TransactionBatch'} = $args{'TicketObj'}->{'_TransactionBatch'};
296         }
297     }
298     else {
299         $self->{'TicketObj'} = RT::Ticket->new( $self->CurrentUser );
300         $self->{'TicketObj'}->Load( $args{'Ticket'} )
301           || $RT::Logger->err("$self couldn't load ticket $args{'Ticket'}");
302     }
303
304     if ( ( $self->{'TransactionObj'} = $args{'TransactionObj'} ) ) {
305         $self->{'TransactionObj'}->CurrentUser( $self->CurrentUser );
306     }
307     else {
308         $self->{'TransactionObj'} = RT::Transaction->new( $self->CurrentUser );
309         $self->{'TransactionObj'}->Load( $args{'Transaction'} )
310           || $RT::Logger->err( "$self couldn't load transaction $args{'Transaction'}");
311     }
312
313
314
315
316 =head2 _FindScrips
317
318 Find only the apropriate scrips for whatever we're doing now.  Order them 
319 by their description.  (Most common use case is to prepend a number to the
320 description, forcing the scrips to display and run in ascending alphanumerical 
321 order.)
322
323 =cut
324
325 sub _FindScrips {
326     my $self = shift;
327     my %args = (
328                  Stage => undef,
329                  Type => undef,
330                  @_ );
331
332
333     $self->LimitToQueue( $self->{'TicketObj'}->QueueObj->Id )
334       ;    #Limit it to  $Ticket->QueueObj->Id
335     $self->LimitToGlobal();
336       # or to "global"
337
338     $self->Limit( FIELD => "Stage", VALUE => $args{'Stage'} );
339
340     my $ConditionsAlias = $self->NewAlias('ScripConditions');
341
342     $self->Join(
343         ALIAS1 => 'main',
344         FIELD1 => 'ScripCondition',
345         ALIAS2 => $ConditionsAlias,
346         FIELD2 => 'id'
347     );
348
349     #We only want things where the scrip applies to this sort of transaction
350     # TransactionBatch stage can define list of transaction
351     foreach( split /\s*,\s*/, ($args{'Type'} || '') ) {
352         $self->Limit(
353             ALIAS           => $ConditionsAlias,
354             FIELD           => 'ApplicableTransTypes',
355             OPERATOR        => 'LIKE',
356             VALUE           => $_,
357             ENTRYAGGREGATOR => 'OR',
358         )
359     }
360
361     # Or where the scrip applies to any transaction
362     $self->Limit(
363         ALIAS           => $ConditionsAlias,
364         FIELD           => 'ApplicableTransTypes',
365         OPERATOR        => 'LIKE',
366         VALUE           => "Any",
367         ENTRYAGGREGATOR => 'OR',
368     );
369
370     # Promise some kind of ordering
371     $self->OrderBy( FIELD => 'Description' );
372
373     # we call Count below, but later we always do search
374     # so just do search and get count from results
375     $self->_DoSearch if $self->{'must_redo_search'};
376
377     $RT::Logger->debug(
378         "Found ". $self->Count ." scrips for $args{'Stage'} stage"
379         ." with applicable type(s) $args{'Type'}"
380         ." for txn #".$self->{TransactionObj}->Id
381         ." on ticket #".$self->{TicketObj}->Id
382     );
383 }
384
385
386
387
388 =head2 NewItem
389
390 Returns an empty new RT::Scrip item
391
392 =cut
393
394 sub NewItem {
395     my $self = shift;
396     return(RT::Scrip->new($self->CurrentUser));
397 }
398 RT::Base->_ImportOverlays();
399
400 1;