import rt 3.8.7
[freeside.git] / rt / lib / RT / Scrip_Overlay.pm
1 # BEGIN BPS TAGGED BLOCK {{{
2
3 # COPYRIGHT:
4
5 # This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
6 #                                          <jesse@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::Scrip - an RT Scrip object
52
53 =head1 SYNOPSIS
54
55   use RT::Scrip;
56
57 =head1 DESCRIPTION
58
59
60 =head1 METHODS
61
62
63 =cut
64
65
66 package RT::Scrip;
67
68 use strict;
69 no warnings qw(redefine);
70
71 # {{{ sub Create
72
73 =head2 Create
74
75 Creates a new entry in the Scrips table. Takes a paramhash with:
76
77         Queue                  => 0,
78         Description            => undef,
79         Template               => undef,
80         ScripAction            => undef,
81         ScripCondition         => undef,
82         CustomPrepareCode      => undef,
83         CustomCommitCode       => undef,
84         CustomIsApplicableCode => undef,
85
86
87
88
89 Returns (retval, msg);
90 retval is 0 for failure or scrip id.  msg is a textual description of what happened.
91
92 =cut
93
94 sub Create {
95     my $self = shift;
96     my %args = (
97         Queue                  => 0,
98         Template               => 0,                     # name or id
99         ScripAction            => 0,                     # name or id
100         ScripCondition         => 0,                     # name or id
101         Stage                  => 'TransactionCreate',
102         Description            => undef,
103         CustomPrepareCode      => undef,
104         CustomCommitCode       => undef,
105         CustomIsApplicableCode => undef,
106         @_
107     );
108
109     unless ( $args{'Queue'} ) {
110         unless ( $self->CurrentUser->HasRight( Object => $RT::System,
111                                                Right  => 'ModifyScrips' ) )
112         {
113             return ( 0, $self->loc('Permission Denied') );
114         }
115         $args{'Queue'} = 0;    # avoid undef sneaking in
116     }
117     else {
118         my $QueueObj = RT::Queue->new( $self->CurrentUser );
119         $QueueObj->Load( $args{'Queue'} );
120         unless ( $QueueObj->id ) {
121             return ( 0, $self->loc('Invalid queue') );
122         }
123         unless ( $QueueObj->CurrentUserHasRight('ModifyScrips') ) {
124             return ( 0, $self->loc('Permission Denied') );
125         }
126         $args{'Queue'} = $QueueObj->id;
127     }
128
129     #TODO +++ validate input
130
131     require RT::ScripAction;
132     return ( 0, $self->loc("Action is mandatory argument") )
133         unless $args{'ScripAction'};
134     my $action = RT::ScripAction->new( $self->CurrentUser );
135     $action->Load( $args{'ScripAction'} );
136     return ( 0, $self->loc( "Action '[_1]' not found", $args{'ScripAction'} ) ) 
137         unless $action->Id;
138
139     require RT::Template;
140     return ( 0, $self->loc("Template is mandatory argument") )
141         unless $args{'Template'};
142     my $template = RT::Template->new( $self->CurrentUser );
143     $template->Load( $args{'Template'} );
144     return ( 0, $self->loc( "Template '[_1]' not found", $args{'Template'} ) )
145         unless $template->Id;
146
147     require RT::ScripCondition;
148     return ( 0, $self->loc("Condition is mandatory argument") )
149         unless $args{'ScripCondition'};
150     my $condition = RT::ScripCondition->new( $self->CurrentUser );
151     $condition->Load( $args{'ScripCondition'} );
152     return ( 0, $self->loc( "Condition '[_1]' not found", $args{'ScripCondition'} ) )
153         unless $condition->Id;
154
155     my ( $id, $msg ) = $self->SUPER::Create(
156         Queue                  => $args{'Queue'},
157         Template               => $template->Id,
158         ScripCondition         => $condition->id,
159         Stage                  => $args{'Stage'},
160         ScripAction            => $action->Id,
161         Description            => $args{'Description'},
162         CustomPrepareCode      => $args{'CustomPrepareCode'},
163         CustomCommitCode       => $args{'CustomCommitCode'},
164         CustomIsApplicableCode => $args{'CustomIsApplicableCode'},
165     );
166     if ( $id ) {
167         return ( $id, $self->loc('Scrip Created') );
168     }
169     else {
170         return ( $id, $msg );
171     }
172 }
173
174 # }}}
175
176 # {{{ sub Delete
177
178 =head2 Delete
179
180 Delete this object
181
182 =cut
183
184 sub Delete {
185     my $self = shift;
186
187     unless ( $self->CurrentUserHasRight('ModifyScrips') ) {
188         return ( 0, $self->loc('Permission Denied') );
189     }
190
191     return ( $self->SUPER::Delete(@_) );
192 }
193
194 # }}}
195
196 # {{{ sub QueueObj
197
198 =head2 QueueObj
199
200 Retuns an RT::Queue object with this Scrip\'s queue
201
202 =cut
203
204 sub QueueObj {
205     my $self = shift;
206
207     if ( !$self->{'QueueObj'} ) {
208         require RT::Queue;
209         $self->{'QueueObj'} = RT::Queue->new( $self->CurrentUser );
210         $self->{'QueueObj'}->Load( $self->__Value('Queue') );
211     }
212     return ( $self->{'QueueObj'} );
213 }
214
215 # }}}
216
217 # {{{ sub ActionObj
218
219 =head2 ActionObj
220
221 Retuns an RT::Action object with this Scrip\'s Action
222
223 =cut
224
225 sub ActionObj {
226     my $self = shift;
227
228     unless ( defined $self->{'ScripActionObj'} ) {
229         require RT::ScripAction;
230
231         $self->{'ScripActionObj'} = RT::ScripAction->new( $self->CurrentUser );
232
233         #TODO: why are we loading Actions with templates like this.
234         # two separate methods might make more sense
235         $self->{'ScripActionObj'}->Load( $self->ScripAction, $self->Template );
236     }
237     return ( $self->{'ScripActionObj'} );
238 }
239
240 # }}}
241
242 # {{{ sub ConditionObj
243
244 =head2 ConditionObj
245
246 Retuns an L<RT::ScripCondition> object with this Scrip's IsApplicable
247
248 =cut
249
250 sub ConditionObj {
251     my $self = shift;
252
253     my $res = RT::ScripCondition->new( $self->CurrentUser );
254     $res->Load( $self->ScripCondition );
255     return $res;
256 }
257
258 # }}}
259
260 # {{{ sub TemplateObj
261
262 =head2 TemplateObj
263
264 Retuns an RT::Template object with this Scrip\'s Template
265
266 =cut
267
268 sub TemplateObj {
269     my $self = shift;
270
271     unless ( defined $self->{'TemplateObj'} ) {
272         require RT::Template;
273         $self->{'TemplateObj'} = RT::Template->new( $self->CurrentUser );
274         $self->{'TemplateObj'}->Load( $self->Template );
275     }
276     return ( $self->{'TemplateObj'} );
277 }
278
279 # }}}
280
281 # {{{ Dealing with this instance of a scrip
282
283 # {{{ sub Apply
284
285 =head2 Apply { TicketObj => undef, TransactionObj => undef}
286
287 This method instantiates the ScripCondition and ScripAction objects for a
288 single execution of this scrip. it then calls the IsApplicable method of the 
289 ScripCondition.
290 If that succeeds, it calls the Prepare method of the
291 ScripAction. If that succeeds, it calls the Commit method of the ScripAction.
292
293 Usually, the ticket and transaction objects passed to this method
294 should be loaded by the SuperUser role
295
296 =cut
297
298
299 # XXX TODO : This code appears to be obsoleted in favor of similar code in Scrips->Apply.
300 # Why is this here? Is it still called?
301
302 sub Apply {
303     my $self = shift;
304     my %args = ( TicketObj      => undef,
305                  TransactionObj => undef,
306                  @_ );
307
308     $RT::Logger->debug("Now applying scrip ".$self->Id . " for transaction ".$args{'TransactionObj'}->id);
309
310     my $ApplicableTransactionObj = $self->IsApplicable( TicketObj      => $args{'TicketObj'},
311                                                         TransactionObj => $args{'TransactionObj'} );
312     unless ( $ApplicableTransactionObj ) {
313         return undef;
314     }
315
316     if ( $ApplicableTransactionObj->id != $args{'TransactionObj'}->id ) {
317         $RT::Logger->debug("Found an applicable transaction ".$ApplicableTransactionObj->Id . " in the same batch with transaction ".$args{'TransactionObj'}->id);
318     }
319
320     #If it's applicable, prepare and commit it
321     $RT::Logger->debug("Now preparing scrip ".$self->Id . " for transaction ".$ApplicableTransactionObj->id);
322     unless ( $self->Prepare( TicketObj      => $args{'TicketObj'},
323                              TransactionObj => $ApplicableTransactionObj )
324       ) {
325         return undef;
326     }
327
328     $RT::Logger->debug("Now commiting scrip ".$self->Id . " for transaction ".$ApplicableTransactionObj->id);
329     unless ( $self->Commit( TicketObj => $args{'TicketObj'},
330                             TransactionObj => $ApplicableTransactionObj)
331       ) {
332         return undef;
333     }
334
335     $RT::Logger->debug("We actually finished scrip ".$self->Id . " for transaction ".$ApplicableTransactionObj->id);
336     return (1);
337
338 }
339
340 # }}}
341
342 # {{{ sub IsApplicable
343
344 =head2 IsApplicable
345
346 Calls the  Condition object\'s IsApplicable method
347
348 Upon success, returns the applicable Transaction object.
349 Otherwise, undef is returned.
350
351 If the Scrip is in the TransactionCreate Stage (the usual case), only test
352 the associated Transaction object to see if it is applicable.
353
354 For Scrips in the TransactionBatch Stage, test all Transaction objects
355 created during the Ticket object's lifetime, and returns the first one
356 that is applicable.
357
358 =cut
359
360 sub IsApplicable {
361     my $self = shift;
362     my %args = ( TicketObj      => undef,
363                  TransactionObj => undef,
364                  @_ );
365
366     my $return;
367     eval {
368
369         my @Transactions;
370
371         if ( $self->Stage eq 'TransactionCreate') {
372             # Only look at our current Transaction
373             @Transactions = ( $args{'TransactionObj'} );
374         }
375         elsif ( $self->Stage eq 'TransactionBatch') {
376             # Look at all Transactions in this Batch
377             @Transactions = @{ $args{'TicketObj'}->TransactionBatch || [] };
378         }
379         else {
380             $RT::Logger->error( "Unknown Scrip stage:" . $self->Stage );
381             return (undef);
382         }
383         my $ConditionObj = $self->ConditionObj;
384         foreach my $TransactionObj ( @Transactions ) {
385             # in TxnBatch stage we can select scrips that are not applicable to all txns
386             my $txn_type = $TransactionObj->Type;
387             next unless( $ConditionObj->ApplicableTransTypes =~ /(?:^|,)(?:Any|\Q$txn_type\E)(?:,|$)/i );
388             # Load the scrip's Condition object
389             $ConditionObj->LoadCondition(
390                 ScripObj       => $self,
391                 TicketObj      => $args{'TicketObj'},
392                 TransactionObj => $TransactionObj,
393             );
394
395             if ( $ConditionObj->IsApplicable() ) {
396                 # We found an application Transaction -- return it
397                 $return = $TransactionObj;
398                 last;
399             }
400         }
401     };
402
403     if ($@) {
404         $RT::Logger->error( "Scrip IsApplicable " . $self->Id . " died. - " . $@ );
405         return (undef);
406     }
407
408             return ($return);
409
410 }
411
412 # }}}
413
414 # {{{ SUb Prepare
415
416 =head2 Prepare
417
418 Calls the action object's prepare method
419
420 =cut
421
422 sub Prepare {
423     my $self = shift;
424     my %args = ( TicketObj      => undef,
425                  TransactionObj => undef,
426                  @_ );
427
428     my $return;
429     eval {
430         $self->ActionObj->LoadAction( ScripObj       => $self,
431                                       TicketObj      => $args{'TicketObj'},
432                                       TransactionObj => $args{'TransactionObj'},
433         );
434
435         $return = $self->ActionObj->Prepare();
436     };
437     if ($@) {
438         $RT::Logger->error( "Scrip Prepare " . $self->Id . " died. - " . $@ );
439         return (undef);
440     }
441         unless ($return) {
442         }
443         return ($return);
444 }
445
446 # }}}
447
448 # {{{ sub Commit
449
450 =head2 Commit
451
452 Calls the action object's commit method
453
454 =cut
455
456 sub Commit {
457     my $self = shift;
458     my %args = ( TicketObj      => undef,
459                  TransactionObj => undef,
460                  @_ );
461
462     my $return;
463     eval {
464         $return = $self->ActionObj->Commit();
465     };
466
467 #Searchbuilder caching isn't perfectly coherent. got to reload the ticket object, since it
468 # may have changed
469     $args{'TicketObj'}->Load( $args{'TicketObj'}->Id );
470
471     if ($@) {
472         $RT::Logger->error( "Scrip Commit " . $self->Id . " died. - " . $@ );
473         return (undef);
474     }
475
476     # Not destroying or weakening hte Action and Condition here could cause a
477     # leak
478
479     return ($return);
480 }
481
482 # }}}
483
484 # }}}
485
486 # {{{ ACL related methods
487
488 # {{{ sub _Set
489
490 # does an acl check and then passes off the call
491 sub _Set {
492     my $self = shift;
493
494     unless ( $self->CurrentUserHasRight('ModifyScrips') ) {
495         $RT::Logger->debug(
496                  "CurrentUser can't modify Scrips for " . $self->Queue . "\n" );
497         return ( 0, $self->loc('Permission Denied') );
498     }
499     return $self->__Set(@_);
500 }
501
502 # }}}
503
504 # {{{ sub _Value
505 # does an acl check and then passes off the call
506 sub _Value {
507     my $self = shift;
508
509     unless ( $self->CurrentUserHasRight('ShowScrips') ) {
510         $RT::Logger->debug( "CurrentUser can't modify Scrips for "
511                             . $self->__Value('Queue')
512                             . "\n" );
513         return (undef);
514     }
515
516     return $self->__Value(@_);
517 }
518
519 # }}}
520
521 # {{{ sub CurrentUserHasRight
522
523 =head2 CurrentUserHasRight
524
525 Helper menthod for HasRight. Presets Principal to CurrentUser then 
526 calls HasRight.
527
528 =cut
529
530 sub CurrentUserHasRight {
531     my $self  = shift;
532     my $right = shift;
533     return ( $self->HasRight( Principal => $self->CurrentUser->UserObj,
534                               Right     => $right ) );
535
536 }
537
538 # }}}
539
540 # {{{ sub HasRight
541
542 =head2 HasRight
543
544 Takes a param-hash consisting of "Right" and "Principal"  Principal is 
545 an RT::User object or an RT::CurrentUser object. "Right" is a textual
546 Right string that applies to Scrips.
547
548 =cut
549
550 sub HasRight {
551     my $self = shift;
552     my %args = ( Right     => undef,
553                  Principal => undef,
554                  @_ );
555
556     if ( $self->SUPER::_Value('Queue') ) {
557         return $args{'Principal'}->HasRight(
558             Right  => $args{'Right'},
559             Object => $self->QueueObj
560         );
561     }
562     else {
563         return $args{'Principal'}->HasRight(
564             Object => $RT::System,
565             Right  => $args{'Right'},
566         );
567     }
568 }
569
570 # }}}
571
572 # }}}
573
574 1;
575