Upgrade to 4.2.2
[usit-rt.git] / lib / RT / Scrips.pm
CommitLineData
84fb5b46
MKG
1# BEGIN BPS TAGGED BLOCK {{{
2#
3# COPYRIGHT:
4#
320f0092 5# This software is Copyright (c) 1996-2014 Best Practical Solutions, LLC
84fb5b46
MKG
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
67package RT::Scrips;
68
69use strict;
70use warnings;
71
84fb5b46
MKG
72use base 'RT::SearchBuilder';
73
af59614d
MKG
74use RT::Scrip;
75use RT::ObjectScrips;
76
84fb5b46
MKG
77sub Table { 'Scrips'}
78
af59614d
MKG
79sub _Init {
80 my $self = shift;
81
82 $self->{'with_disabled_column'} = 1;
83
84 return ( $self->SUPER::_Init(@_) );
85}
84fb5b46
MKG
86
87=head2 LimitToQueue
88
89Takes a queue id (numerical) as its only argument. Makes sure that
90Scopes it pulls out apply to this queue (or another that you've selected with
91another call to this method
92
93=cut
94
95sub LimitToQueue {
af59614d
MKG
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 );
84fb5b46
MKG
107}
108
109
110=head2 LimitToGlobal
111
112Makes sure that
113Scopes it pulls out apply to all queues (or another that you've selected with
114another call to this method or LimitToQueue
115
116=cut
117
118
119sub LimitToGlobal {
af59614d
MKG
120 my $self = shift;
121 return $self->LimitToQueue(0);
122}
123
124sub LimitToAdded {
125 my $self = shift;
126 return RT::ObjectScrips->new( $self->CurrentUser )
127 ->LimitTargetToAdded( $self => @_ );
128}
129
130sub LimitToNotAdded {
131 my $self = shift;
132 return RT::ObjectScrips->new( $self->CurrentUser )
133 ->LimitTargetToNotAdded( $self => @_ );
134}
135
136sub 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
152Takes a L<RT::Template> object and limits scrips to those that
153use the template.
154
155=cut
156
157sub 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
229sub 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 } );
84fb5b46
MKG
239}
240
241# {{{ sub Next
242
243=head2 Next
244
245Returns the next scrip that this user can see.
246
247=cut
af59614d 248
84fb5b46
MKG
249sub Next {
250 my $self = shift;
af59614d
MKG
251
252
84fb5b46
MKG
253 my $Scrip = $self->SUPER::Next();
254 if ((defined($Scrip)) and (ref($Scrip))) {
255
af59614d
MKG
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 }
84fb5b46
MKG
264 }
265 #if there never was any scrip
266 else {
af59614d
MKG
267 return(undef);
268 }
269
84fb5b46
MKG
270}
271
272=head2 Apply
273
274Run through the relevant scrips. Scrips will run in order based on
275description. (Most common use case is to prepend a number to the description,
276forcing the scrips to run in ascending alphanumerical order.)
277
278=cut
279
280sub 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
298Commit all of this object's prepared scrips
299
300=cut
301
302sub Commit {
303 my $self = shift;
304
84fb5b46
MKG
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
84fb5b46
MKG
316}
317
318
319=head2 Prepare
320
321Only prepare the scrips, returning an array of the scrips we're interested in
322in order of preparation, not execution
323
324=cut
325
326sub 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
84fb5b46
MKG
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
84fb5b46
MKG
368 return (@{$self->Prepared});
369
370};
371
372=head2 Prepared
373
374Returns an arrayref of the scrips this object has prepared
375
376
377=cut
378
379sub Prepared {
380 my $self = shift;
381 return ($self->{'prepared_scrips'} || []);
382}
383
84fb5b46
MKG
384=head2 _SetupSourceObjects { TicketObj , Ticket, Transaction, TransactionObj }
385
386Setup a ticket and transaction for this Scrip collection to work with as it runs through the
387relevant scrips. (Also to figure out which scrips apply)
388
389Returns: nothing
390
391=cut
392
393
394sub _SetupSourceObjects {
395
396 my $self = shift;
397 my %args = (
398 TicketObj => undef,
399 Ticket => undef,
400 Transaction => undef,
401 TransactionObj => undef,
402 @_ );
403
404
dab09ea8
MKG
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 }
84fb5b46
MKG
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
442Find only the apropriate scrips for whatever we're doing now. Order them
443by their description. (Most common use case is to prepend a number to the
444description, forcing the scrips to display and run in ascending alphanumerical
445order.)
446
447=cut
448
449sub _FindScrips {
450 my $self = shift;
451 my %args = (
452 Stage => undef,
453 Type => undef,
454 @_ );
455
456
af59614d
MKG
457 $self->LimitToQueue( $self->{'TicketObj'}->QueueObj->Id );
458 $self->LimitToGlobal;
459 $self->LimitByStage( $args{'Stage'} );
84fb5b46 460
af59614d 461 my $ConditionsAlias = $self->Join(
84fb5b46
MKG
462 ALIAS1 => 'main',
463 FIELD1 => 'ScripCondition',
af59614d
MKG
464 TABLE2 => 'ScripConditions',
465 FIELD2 => 'id',
84fb5b46
MKG
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'} || '') ) {
af59614d
MKG
471 $self->Limit(
472 ALIAS => $ConditionsAlias,
473 FIELD => 'ApplicableTransTypes',
474 OPERATOR => 'LIKE',
475 VALUE => $_,
476 ENTRYAGGREGATOR => 'OR',
477 )
84fb5b46
MKG
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
af59614d 489 $self->ApplySortOrder;
84fb5b46
MKG
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
84fb5b46
MKG
503RT::Base->_ImportOverlays();
504
5051;