1 # BEGIN BPS TAGGED BLOCK {{{
5 # This software is Copyright (c) 1996-2012 Best Practical Solutions, LLC
6 # <sales@bestpractical.com>
8 # (Except where explicitly superseded by other copyright notices)
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
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.
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.
30 # CONTRIBUTION SUBMISSION POLICY:
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.)
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.
47 # END BPS TAGGED BLOCK }}}
51 RT::Scrip - an RT Scrip object
74 use RT::ScripCondition;
76 use base 'RT::Record';
84 Creates a new entry in the Scrips table. Takes a paramhash with:
90 ScripCondition => undef,
91 CustomPrepareCode => undef,
92 CustomCommitCode => undef,
93 CustomIsApplicableCode => undef,
98 Returns (retval, msg);
99 retval is 0 for failure or scrip id. msg is a textual description of what happened.
107 Template => 0, # name or id
108 ScripAction => 0, # name or id
109 ScripCondition => 0, # name or id
110 Stage => 'TransactionCreate',
111 Description => undef,
112 CustomPrepareCode => undef,
113 CustomCommitCode => undef,
114 CustomIsApplicableCode => undef,
115 ConditionRules => undef,
116 ActionRules => undef,
120 if ($args{CustomPrepareCode} || $args{CustomCommitCode} || $args{CustomIsApplicableCode}) {
121 unless ( $self->CurrentUser->HasRight( Object => $RT::System,
122 Right => 'ExecuteCode' ) )
124 return ( 0, $self->loc('Permission Denied') );
128 unless ( $args{'Queue'} ) {
129 unless ( $self->CurrentUser->HasRight( Object => $RT::System,
130 Right => 'ModifyScrips' ) )
132 return ( 0, $self->loc('Permission Denied') );
134 $args{'Queue'} = 0; # avoid undef sneaking in
137 my $QueueObj = RT::Queue->new( $self->CurrentUser );
138 $QueueObj->Load( $args{'Queue'} );
139 unless ( $QueueObj->id ) {
140 return ( 0, $self->loc('Invalid queue') );
142 unless ( $QueueObj->CurrentUserHasRight('ModifyScrips') ) {
143 return ( 0, $self->loc('Permission Denied') );
145 $args{'Queue'} = $QueueObj->id;
148 #TODO +++ validate input
150 require RT::ScripAction;
151 return ( 0, $self->loc("Action is mandatory argument") )
152 unless $args{'ScripAction'};
153 my $action = RT::ScripAction->new( $self->CurrentUser );
154 $action->Load( $args{'ScripAction'} );
155 return ( 0, $self->loc( "Action '[_1]' not found", $args{'ScripAction'} ) )
158 require RT::Template;
159 return ( 0, $self->loc("Template is mandatory argument") )
160 unless $args{'Template'};
161 my $template = RT::Template->new( $self->CurrentUser );
162 $template->Load( $args{'Template'} );
163 return ( 0, $self->loc( "Template '[_1]' not found", $args{'Template'} ) )
164 unless $template->Id;
166 require RT::ScripCondition;
167 return ( 0, $self->loc("Condition is mandatory argument") )
168 unless $args{'ScripCondition'};
169 my $condition = RT::ScripCondition->new( $self->CurrentUser );
170 $condition->Load( $args{'ScripCondition'} );
171 return ( 0, $self->loc( "Condition '[_1]' not found", $args{'ScripCondition'} ) )
172 unless $condition->Id;
174 my ( $id, $msg ) = $self->SUPER::Create(
175 Queue => $args{'Queue'},
176 Template => $template->Id,
177 ScripCondition => $condition->id,
178 Stage => $args{'Stage'},
179 ScripAction => $action->Id,
180 Description => $args{'Description'},
181 CustomPrepareCode => $args{'CustomPrepareCode'},
182 CustomCommitCode => $args{'CustomCommitCode'},
183 CustomIsApplicableCode => $args{'CustomIsApplicableCode'},
184 ConditionRules => $args{'ConditionRules'},
185 ActionRules => $args{'ActionRules'},
188 return ( $id, $self->loc('Scrip Created') );
191 return ( $id, $msg );
206 unless ( $self->CurrentUserHasRight('ModifyScrips') ) {
207 return ( 0, $self->loc('Permission Denied') );
210 return ( $self->SUPER::Delete(@_) );
217 Retuns an RT::Queue object with this Scrip's queue
224 if ( !$self->{'QueueObj'} ) {
226 $self->{'QueueObj'} = RT::Queue->new( $self->CurrentUser );
227 $self->{'QueueObj'}->Load( $self->__Value('Queue') );
229 return ( $self->{'QueueObj'} );
236 Retuns an RT::Action object with this Scrip\'s Action
243 unless ( defined $self->{'ScripActionObj'} ) {
244 require RT::ScripAction;
246 $self->{'ScripActionObj'} = RT::ScripAction->new( $self->CurrentUser );
248 #TODO: why are we loading Actions with templates like this.
249 # two separate methods might make more sense
250 $self->{'ScripActionObj'}->Load( $self->ScripAction, $self->Template );
252 return ( $self->{'ScripActionObj'} );
259 Retuns an L<RT::ScripCondition> object with this Scrip's IsApplicable
266 my $res = RT::ScripCondition->new( $self->CurrentUser );
267 $res->Load( $self->ScripCondition );
274 Loads scrip's condition and action modules.
281 $self->ConditionObj->LoadCondition;
282 $self->ActionObj->LoadAction;
288 Retuns an RT::Template object with this Scrip\'s Template
295 unless ( defined $self->{'TemplateObj'} ) {
296 require RT::Template;
297 $self->{'TemplateObj'} = RT::Template->new( $self->CurrentUser );
298 $self->{'TemplateObj'}->Load( $self->Template );
300 return ( $self->{'TemplateObj'} );
306 =head2 Apply { TicketObj => undef, TransactionObj => undef}
308 This method instantiates the ScripCondition and ScripAction objects for a
309 single execution of this scrip. it then calls the IsApplicable method of the
311 If that succeeds, it calls the Prepare method of the
312 ScripAction. If that succeeds, it calls the Commit method of the ScripAction.
314 Usually, the ticket and transaction objects passed to this method
315 should be loaded by the SuperUser role
320 # XXX TODO : This code appears to be obsoleted in favor of similar code in Scrips->Apply.
321 # Why is this here? Is it still called?
325 my %args = ( TicketObj => undef,
326 TransactionObj => undef,
329 $RT::Logger->debug("Now applying scrip ".$self->Id . " for transaction ".$args{'TransactionObj'}->id);
331 my $ApplicableTransactionObj = $self->IsApplicable( TicketObj => $args{'TicketObj'},
332 TransactionObj => $args{'TransactionObj'} );
333 unless ( $ApplicableTransactionObj ) {
337 if ( $ApplicableTransactionObj->id != $args{'TransactionObj'}->id ) {
338 $RT::Logger->debug("Found an applicable transaction ".$ApplicableTransactionObj->Id . " in the same batch with transaction ".$args{'TransactionObj'}->id);
341 #If it's applicable, prepare and commit it
342 $RT::Logger->debug("Now preparing scrip ".$self->Id . " for transaction ".$ApplicableTransactionObj->id);
343 unless ( $self->Prepare( TicketObj => $args{'TicketObj'},
344 TransactionObj => $ApplicableTransactionObj )
349 $RT::Logger->debug("Now commiting scrip ".$self->Id . " for transaction ".$ApplicableTransactionObj->id);
350 unless ( $self->Commit( TicketObj => $args{'TicketObj'},
351 TransactionObj => $ApplicableTransactionObj)
356 $RT::Logger->debug("We actually finished scrip ".$self->Id . " for transaction ".$ApplicableTransactionObj->id);
365 Calls the Condition object\'s IsApplicable method
367 Upon success, returns the applicable Transaction object.
368 Otherwise, undef is returned.
370 If the Scrip is in the TransactionCreate Stage (the usual case), only test
371 the associated Transaction object to see if it is applicable.
373 For Scrips in the TransactionBatch Stage, test all Transaction objects
374 created during the Ticket object's lifetime, and returns the first one
381 my %args = ( TicketObj => undef,
382 TransactionObj => undef,
390 if ( $self->Stage eq 'TransactionCreate') {
391 # Only look at our current Transaction
392 @Transactions = ( $args{'TransactionObj'} );
394 elsif ( $self->Stage eq 'TransactionBatch') {
395 # Look at all Transactions in this Batch
396 @Transactions = @{ $args{'TicketObj'}->TransactionBatch || [] };
399 $RT::Logger->error( "Unknown Scrip stage:" . $self->Stage );
402 my $ConditionObj = $self->ConditionObj;
403 foreach my $TransactionObj ( @Transactions ) {
404 # in TxnBatch stage we can select scrips that are not applicable to all txns
405 my $txn_type = $TransactionObj->Type;
406 next unless( $ConditionObj->ApplicableTransTypes =~ /(?:^|,)(?:Any|\Q$txn_type\E)(?:,|$)/i );
407 # Load the scrip's Condition object
408 $ConditionObj->LoadCondition(
410 TicketObj => $args{'TicketObj'},
411 TransactionObj => $TransactionObj,
414 if ( $ConditionObj->IsApplicable() ) {
415 # We found an application Transaction -- return it
416 $return = $TransactionObj;
423 $RT::Logger->error( "Scrip IsApplicable " . $self->Id . " died. - " . $@ );
435 Calls the action object's prepare method
441 my %args = ( TicketObj => undef,
442 TransactionObj => undef,
447 $self->ActionObj->LoadAction( ScripObj => $self,
448 TicketObj => $args{'TicketObj'},
449 TransactionObj => $args{'TransactionObj'},
452 $return = $self->ActionObj->Prepare();
455 $RT::Logger->error( "Scrip Prepare " . $self->Id . " died. - " . $@ );
467 Calls the action object's commit method
473 my %args = ( TicketObj => undef,
474 TransactionObj => undef,
479 $return = $self->ActionObj->Commit();
482 #Searchbuilder caching isn't perfectly coherent. got to reload the ticket object, since it
484 $args{'TicketObj'}->Load( $args{'TicketObj'}->Id );
487 $RT::Logger->error( "Scrip Commit " . $self->Id . " died. - " . $@ );
491 # Not destroying or weakening hte Action and Condition here could cause a
501 # does an acl check and then passes off the call
510 unless ( $self->CurrentUserHasRight('ModifyScrips') ) {
512 "CurrentUser can't modify Scrips for " . $self->Queue . "\n" );
513 return ( 0, $self->loc('Permission Denied') );
517 if (exists $args{Value}) {
518 if ($args{Field} eq 'CustomIsApplicableCode' || $args{Field} eq 'CustomPrepareCode' || $args{Field} eq 'CustomCommitCode') {
519 unless ( $self->CurrentUser->HasRight( Object => $RT::System,
520 Right => 'ExecuteCode' ) ) {
521 return ( 0, $self->loc('Permission Denied') );
524 elsif ($args{Field} eq 'Queue') {
526 # moving to another queue
527 my $queue = RT::Queue->new( $self->CurrentUser );
528 $queue->Load($args{Value});
529 unless ($queue->Id and $queue->CurrentUserHasRight('ModifyScrips')) {
530 return ( 0, $self->loc('Permission Denied') );
534 unless ($self->CurrentUser->HasRight( Object => RT->System, Right => 'ModifyScrips' )) {
535 return ( 0, $self->loc('Permission Denied') );
539 elsif ($args{Field} eq 'Template') {
540 my $template = RT::Template->new( $self->CurrentUser );
541 $template->Load($args{Value});
542 unless ($template->Id and $template->CurrentUserCanRead) {
543 return ( 0, $self->loc('Permission Denied') );
548 return $self->SUPER::_Set(@_);
552 # does an acl check and then passes off the call
556 unless ( $self->CurrentUserHasRight('ShowScrips') ) {
557 $RT::Logger->debug( "CurrentUser can't modify Scrips for "
558 . $self->__Value('Queue')
563 return $self->__Value(@_);
568 =head2 CurrentUserHasRight
570 Helper menthod for HasRight. Presets Principal to CurrentUser then
575 sub CurrentUserHasRight {
578 return ( $self->HasRight( Principal => $self->CurrentUser->UserObj,
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.
595 my %args = ( Right => undef,
599 if ( $self->SUPER::_Value('Queue') ) {
600 return $args{'Principal'}->HasRight(
601 Right => $args{'Right'},
602 Object => $self->QueueObj
606 return $args{'Principal'}->HasRight(
607 Object => $RT::System,
608 Right => $args{'Right'},
617 This routine compile-checks the custom prepare, commit, and is-applicable code
618 to see if they are syntactically valid Perl. We eval them in a codeblock to
619 avoid actually executing the code.
621 If one of the fields has a compile error, only the first is reported.
623 Returns an (ok, message) pair.
630 for my $method (qw/CustomPrepareCode CustomCommitCode CustomIsApplicableCode/) {
631 my $code = $self->$method;
632 next if !defined($code);
636 eval "sub { $code }";
641 return (0, $self->loc("Couldn't compile [_1] codeblock '[_2]': [_3]", $method, $code, $error));
646 =head2 SetScripAction
654 return ( 0, $self->loc("Action is mandatory argument") ) unless $value;
656 require RT::ScripAction;
657 my $action = RT::ScripAction->new( $self->CurrentUser );
658 $action->Load($value);
659 return ( 0, $self->loc( "Action '[_1]' not found", $value ) )
662 return $self->_Set( Field => 'ScripAction', Value => $action->Id );
665 =head2 SetScripCondition
669 sub SetScripCondition {
673 return ( 0, $self->loc("Condition is mandatory argument") )
676 require RT::ScripCondition;
677 my $condition = RT::ScripCondition->new( $self->CurrentUser );
678 $condition->Load($value);
680 return ( 0, $self->loc( "Condition '[_1]' not found", $value ) )
681 unless $condition->Id;
683 return $self->_Set( Field => 'ScripCondition', Value => $condition->Id );
694 return ( 0, $self->loc("Template is mandatory argument") ) unless $value;
696 require RT::Template;
697 my $template = RT::Template->new( $self->CurrentUser );
698 $template->Load($value);
699 return ( 0, $self->loc( "Template '[_1]' not found", $value ) )
700 unless $template->Id;
702 return $self->_Set( Field => 'Template', Value => $template->Id );
714 Returns the current value of id.
715 (In the database, id is stored as int(11).)
723 Returns the current value of Description.
724 (In the database, Description is stored as varchar(255).)
728 =head2 SetDescription VALUE
731 Set Description to VALUE.
732 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
733 (In the database, Description will be stored as a varchar(255).)
739 =head2 ScripCondition
741 Returns the current value of ScripCondition.
742 (In the database, ScripCondition is stored as int(11).)
746 =head2 SetScripCondition VALUE
749 Set ScripCondition to VALUE.
750 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
751 (In the database, ScripCondition will be stored as a int(11).)
757 =head2 ScripConditionObj
759 Returns the ScripCondition Object which has the id returned by ScripCondition
764 sub ScripConditionObj {
766 my $ScripCondition = RT::ScripCondition->new($self->CurrentUser);
767 $ScripCondition->Load($self->__Value('ScripCondition'));
768 return($ScripCondition);
773 Returns the current value of ScripAction.
774 (In the database, ScripAction is stored as int(11).)
778 =head2 SetScripAction VALUE
781 Set ScripAction to VALUE.
782 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
783 (In the database, ScripAction will be stored as a int(11).)
789 =head2 ScripActionObj
791 Returns the ScripAction Object which has the id returned by ScripAction
798 my $ScripAction = RT::ScripAction->new($self->CurrentUser);
799 $ScripAction->Load($self->__Value('ScripAction'));
800 return($ScripAction);
803 =head2 ConditionRules
805 Returns the current value of ConditionRules.
806 (In the database, ConditionRules is stored as text.)
810 =head2 SetConditionRules VALUE
813 Set ConditionRules to VALUE.
814 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
815 (In the database, ConditionRules will be stored as a text.)
823 Returns the current value of ActionRules.
824 (In the database, ActionRules is stored as text.)
828 =head2 SetActionRules VALUE
831 Set ActionRules to VALUE.
832 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
833 (In the database, ActionRules will be stored as a text.)
839 =head2 CustomIsApplicableCode
841 Returns the current value of CustomIsApplicableCode.
842 (In the database, CustomIsApplicableCode is stored as text.)
846 =head2 SetCustomIsApplicableCode VALUE
849 Set CustomIsApplicableCode to VALUE.
850 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
851 (In the database, CustomIsApplicableCode will be stored as a text.)
857 =head2 CustomPrepareCode
859 Returns the current value of CustomPrepareCode.
860 (In the database, CustomPrepareCode is stored as text.)
864 =head2 SetCustomPrepareCode VALUE
867 Set CustomPrepareCode to VALUE.
868 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
869 (In the database, CustomPrepareCode will be stored as a text.)
875 =head2 CustomCommitCode
877 Returns the current value of CustomCommitCode.
878 (In the database, CustomCommitCode is stored as text.)
882 =head2 SetCustomCommitCode VALUE
885 Set CustomCommitCode to VALUE.
886 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
887 (In the database, CustomCommitCode will be stored as a text.)
895 Returns the current value of Stage.
896 (In the database, Stage is stored as varchar(32).)
900 =head2 SetStage VALUE
904 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
905 (In the database, Stage will be stored as a varchar(32).)
913 Returns the current value of Queue.
914 (In the database, Queue is stored as int(11).)
918 =head2 SetQueue VALUE
922 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
923 (In the database, Queue will be stored as a int(11).)
931 Returns the current value of Template.
932 (In the database, Template is stored as int(11).)
936 =head2 SetTemplate VALUE
939 Set Template to VALUE.
940 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
941 (In the database, Template will be stored as a int(11).)
949 Returns the current value of Creator.
950 (In the database, Creator is stored as int(11).)
958 Returns the current value of Created.
959 (In the database, Created is stored as datetime.)
967 Returns the current value of LastUpdatedBy.
968 (In the database, LastUpdatedBy is stored as int(11).)
976 Returns the current value of LastUpdated.
977 (In the database, LastUpdated is stored as datetime.)
984 sub _CoreAccessible {
988 {read => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''},
990 {read => 1, write => 1, sql_type => 12, length => 255, is_blob => 0, is_numeric => 0, type => 'varchar(255)', default => ''},
992 {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
994 {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
996 {read => 1, write => 1, sql_type => -4, length => 0, is_blob => 1, is_numeric => 0, type => 'text', default => ''},
998 {read => 1, write => 1, sql_type => -4, length => 0, is_blob => 1, is_numeric => 0, type => 'text', default => ''},
999 CustomIsApplicableCode =>
1000 {read => 1, write => 1, sql_type => -4, length => 0, is_blob => 1, is_numeric => 0, type => 'text', default => ''},
1001 CustomPrepareCode =>
1002 {read => 1, write => 1, sql_type => -4, length => 0, is_blob => 1, is_numeric => 0, type => 'text', default => ''},
1004 {read => 1, write => 1, sql_type => -4, length => 0, is_blob => 1, is_numeric => 0, type => 'text', default => ''},
1006 {read => 1, write => 1, sql_type => 12, length => 32, is_blob => 0, is_numeric => 0, type => 'varchar(32)', default => ''},
1008 {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
1010 {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
1012 {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
1014 {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''},
1016 {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
1018 {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''},
1023 RT::Base->_ImportOverlays();