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