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