import rt 3.8.9
[freeside.git] / rt / lib / RT / Scrip_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::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 =head2 LoadModules
261
262 Loads scrip's condition and action modules.
263
264 =cut
265
266 sub LoadModules {
267     my $self = shift;
268
269     $self->ConditionObj->LoadCondition;
270     $self->ActionObj->LoadAction;
271 }
272
273 # {{{ sub TemplateObj
274
275 =head2 TemplateObj
276
277 Retuns an RT::Template object with this Scrip\'s Template
278
279 =cut
280
281 sub TemplateObj {
282     my $self = shift;
283
284     unless ( defined $self->{'TemplateObj'} ) {
285         require RT::Template;
286         $self->{'TemplateObj'} = RT::Template->new( $self->CurrentUser );
287         $self->{'TemplateObj'}->Load( $self->Template );
288     }
289     return ( $self->{'TemplateObj'} );
290 }
291
292 # }}}
293
294 # {{{ Dealing with this instance of a scrip
295
296 # {{{ sub Apply
297
298 =head2 Apply { TicketObj => undef, TransactionObj => undef}
299
300 This method instantiates the ScripCondition and ScripAction objects for a
301 single execution of this scrip. it then calls the IsApplicable method of the 
302 ScripCondition.
303 If that succeeds, it calls the Prepare method of the
304 ScripAction. If that succeeds, it calls the Commit method of the ScripAction.
305
306 Usually, the ticket and transaction objects passed to this method
307 should be loaded by the SuperUser role
308
309 =cut
310
311
312 # XXX TODO : This code appears to be obsoleted in favor of similar code in Scrips->Apply.
313 # Why is this here? Is it still called?
314
315 sub Apply {
316     my $self = shift;
317     my %args = ( TicketObj      => undef,
318                  TransactionObj => undef,
319                  @_ );
320
321     $RT::Logger->debug("Now applying scrip ".$self->Id . " for transaction ".$args{'TransactionObj'}->id);
322
323     my $ApplicableTransactionObj = $self->IsApplicable( TicketObj      => $args{'TicketObj'},
324                                                         TransactionObj => $args{'TransactionObj'} );
325     unless ( $ApplicableTransactionObj ) {
326         return undef;
327     }
328
329     if ( $ApplicableTransactionObj->id != $args{'TransactionObj'}->id ) {
330         $RT::Logger->debug("Found an applicable transaction ".$ApplicableTransactionObj->Id . " in the same batch with transaction ".$args{'TransactionObj'}->id);
331     }
332
333     #If it's applicable, prepare and commit it
334     $RT::Logger->debug("Now preparing scrip ".$self->Id . " for transaction ".$ApplicableTransactionObj->id);
335     unless ( $self->Prepare( TicketObj      => $args{'TicketObj'},
336                              TransactionObj => $ApplicableTransactionObj )
337       ) {
338         return undef;
339     }
340
341     $RT::Logger->debug("Now commiting scrip ".$self->Id . " for transaction ".$ApplicableTransactionObj->id);
342     unless ( $self->Commit( TicketObj => $args{'TicketObj'},
343                             TransactionObj => $ApplicableTransactionObj)
344       ) {
345         return undef;
346     }
347
348     $RT::Logger->debug("We actually finished scrip ".$self->Id . " for transaction ".$ApplicableTransactionObj->id);
349     return (1);
350
351 }
352
353 # }}}
354
355 # {{{ sub IsApplicable
356
357 =head2 IsApplicable
358
359 Calls the  Condition object\'s IsApplicable method
360
361 Upon success, returns the applicable Transaction object.
362 Otherwise, undef is returned.
363
364 If the Scrip is in the TransactionCreate Stage (the usual case), only test
365 the associated Transaction object to see if it is applicable.
366
367 For Scrips in the TransactionBatch Stage, test all Transaction objects
368 created during the Ticket object's lifetime, and returns the first one
369 that is applicable.
370
371 =cut
372
373 sub IsApplicable {
374     my $self = shift;
375     my %args = ( TicketObj      => undef,
376                  TransactionObj => undef,
377                  @_ );
378
379     my $return;
380     eval {
381
382         my @Transactions;
383
384         if ( $self->Stage eq 'TransactionCreate') {
385             # Only look at our current Transaction
386             @Transactions = ( $args{'TransactionObj'} );
387         }
388         elsif ( $self->Stage eq 'TransactionBatch') {
389             # Look at all Transactions in this Batch
390             @Transactions = @{ $args{'TicketObj'}->TransactionBatch || [] };
391         }
392         else {
393             $RT::Logger->error( "Unknown Scrip stage:" . $self->Stage );
394             return (undef);
395         }
396         my $ConditionObj = $self->ConditionObj;
397         foreach my $TransactionObj ( @Transactions ) {
398             # in TxnBatch stage we can select scrips that are not applicable to all txns
399             my $txn_type = $TransactionObj->Type;
400             next unless( $ConditionObj->ApplicableTransTypes =~ /(?:^|,)(?:Any|\Q$txn_type\E)(?:,|$)/i );
401             # Load the scrip's Condition object
402             $ConditionObj->LoadCondition(
403                 ScripObj       => $self,
404                 TicketObj      => $args{'TicketObj'},
405                 TransactionObj => $TransactionObj,
406             );
407
408             if ( $ConditionObj->IsApplicable() ) {
409                 # We found an application Transaction -- return it
410                 $return = $TransactionObj;
411                 last;
412             }
413         }
414     };
415
416     if ($@) {
417         $RT::Logger->error( "Scrip IsApplicable " . $self->Id . " died. - " . $@ );
418         return (undef);
419     }
420
421             return ($return);
422
423 }
424
425 # }}}
426
427 # {{{ SUb Prepare
428
429 =head2 Prepare
430
431 Calls the action object's prepare method
432
433 =cut
434
435 sub Prepare {
436     my $self = shift;
437     my %args = ( TicketObj      => undef,
438                  TransactionObj => undef,
439                  @_ );
440
441     my $return;
442     eval {
443         $self->ActionObj->LoadAction( ScripObj       => $self,
444                                       TicketObj      => $args{'TicketObj'},
445                                       TransactionObj => $args{'TransactionObj'},
446         );
447
448         $return = $self->ActionObj->Prepare();
449     };
450     if ($@) {
451         $RT::Logger->error( "Scrip Prepare " . $self->Id . " died. - " . $@ );
452         return (undef);
453     }
454         unless ($return) {
455         }
456         return ($return);
457 }
458
459 # }}}
460
461 # {{{ sub Commit
462
463 =head2 Commit
464
465 Calls the action object's commit method
466
467 =cut
468
469 sub Commit {
470     my $self = shift;
471     my %args = ( TicketObj      => undef,
472                  TransactionObj => undef,
473                  @_ );
474
475     my $return;
476     eval {
477         $return = $self->ActionObj->Commit();
478     };
479
480 #Searchbuilder caching isn't perfectly coherent. got to reload the ticket object, since it
481 # may have changed
482     $args{'TicketObj'}->Load( $args{'TicketObj'}->Id );
483
484     if ($@) {
485         $RT::Logger->error( "Scrip Commit " . $self->Id . " died. - " . $@ );
486         return (undef);
487     }
488
489     # Not destroying or weakening hte Action and Condition here could cause a
490     # leak
491
492     return ($return);
493 }
494
495 # }}}
496
497 # }}}
498
499 # {{{ ACL related methods
500
501 # {{{ sub _Set
502
503 # does an acl check and then passes off the call
504 sub _Set {
505     my $self = shift;
506
507     unless ( $self->CurrentUserHasRight('ModifyScrips') ) {
508         $RT::Logger->debug(
509                  "CurrentUser can't modify Scrips for " . $self->Queue . "\n" );
510         return ( 0, $self->loc('Permission Denied') );
511     }
512     return $self->__Set(@_);
513 }
514
515 # }}}
516
517 # {{{ sub _Value
518 # does an acl check and then passes off the call
519 sub _Value {
520     my $self = shift;
521
522     unless ( $self->CurrentUserHasRight('ShowScrips') ) {
523         $RT::Logger->debug( "CurrentUser can't modify Scrips for "
524                             . $self->__Value('Queue')
525                             . "\n" );
526         return (undef);
527     }
528
529     return $self->__Value(@_);
530 }
531
532 # }}}
533
534 # {{{ sub CurrentUserHasRight
535
536 =head2 CurrentUserHasRight
537
538 Helper menthod for HasRight. Presets Principal to CurrentUser then 
539 calls HasRight.
540
541 =cut
542
543 sub CurrentUserHasRight {
544     my $self  = shift;
545     my $right = shift;
546     return ( $self->HasRight( Principal => $self->CurrentUser->UserObj,
547                               Right     => $right ) );
548
549 }
550
551 # }}}
552
553 # {{{ sub HasRight
554
555 =head2 HasRight
556
557 Takes a param-hash consisting of "Right" and "Principal"  Principal is 
558 an RT::User object or an RT::CurrentUser object. "Right" is a textual
559 Right string that applies to Scrips.
560
561 =cut
562
563 sub HasRight {
564     my $self = shift;
565     my %args = ( Right     => undef,
566                  Principal => undef,
567                  @_ );
568
569     if ( $self->SUPER::_Value('Queue') ) {
570         return $args{'Principal'}->HasRight(
571             Right  => $args{'Right'},
572             Object => $self->QueueObj
573         );
574     }
575     else {
576         return $args{'Principal'}->HasRight(
577             Object => $RT::System,
578             Right  => $args{'Right'},
579         );
580     }
581 }
582
583 # }}}
584
585 # }}}
586
587
588 =head2 SetScripAction
589
590 =cut
591
592 sub SetScripAction {
593     my $self  = shift;
594     my $value = shift;
595
596     return ( 0, $self->loc("Action is mandatory argument") ) unless $value;
597
598     require RT::ScripAction;
599     my $action = RT::ScripAction->new( $self->CurrentUser );
600     $action->Load($value);
601     return ( 0, $self->loc( "Action '[_1]' not found", $value ) )
602       unless $action->Id;
603
604     return $self->_Set( Field => 'ScripAction', Value => $action->Id );
605 }
606
607 =head2 SetScripCondition
608
609 =cut
610
611 sub SetScripCondition {
612     my $self  = shift;
613     my $value = shift;
614
615     return ( 0, $self->loc("Condition is mandatory argument") )
616       unless $value;
617
618     require RT::ScripCondition;
619     my $condition = RT::ScripCondition->new( $self->CurrentUser );
620     $condition->Load($value);
621
622     return ( 0, $self->loc( "Condition '[_1]' not found", $value ) )
623       unless $condition->Id;
624
625     return $self->_Set( Field => 'ScripCondition', Value => $condition->Id );
626 }
627
628 =head2 SetTemplate
629
630 =cut
631
632 sub SetTemplate {
633     my $self  = shift;
634     my $value = shift;
635
636     return ( 0, $self->loc("Template is mandatory argument") ) unless $value;
637
638     require RT::Template;
639     my $template = RT::Template->new( $self->CurrentUser );
640     $template->Load($value);
641     return ( 0, $self->loc( "Template '[_1]' not found", $value ) )
642       unless $template->Id;
643
644     return $self->_Set( Field => 'Template', Value => $template->Id );
645 }
646
647 1;
648