summaryrefslogtreecommitdiff
path: root/rt/lib/RT/Action
diff options
context:
space:
mode:
authorivan <ivan>2009-12-31 13:16:41 +0000
committerivan <ivan>2009-12-31 13:16:41 +0000
commit63a268637b2d51a8766412617724b9436439deb6 (patch)
treea50f6d4c7829d5c80905e989144317192a44dc90 /rt/lib/RT/Action
parent65a561e3cd8c1ba94f6282f5d2a1cd9783afbd21 (diff)
parentb4b0c7e72d7eaee2fbfc7022022c9698323203dd (diff)
This commit was generated by cvs2svn to compensate for changes in r8690,
which included commits to RCS files with non-trunk default branches.
Diffstat (limited to 'rt/lib/RT/Action')
-rw-r--r--rt/lib/RT/Action/AutoOpen.pm52
-rw-r--r--rt/lib/RT/Action/CreateTickets.pm265
-rw-r--r--rt/lib/RT/Action/EscalatePriority.pm11
-rw-r--r--rt/lib/RT/Action/ExtractSubjectTag.pm103
-rwxr-xr-xrt/lib/RT/Action/LinearEscalate.pm279
-rw-r--r--rt/lib/RT/Action/NotifyGroup.pm209
-rw-r--r--rt/lib/RT/Action/NotifyGroupAsComment.pm91
-rw-r--r--rt/lib/RT/Action/RecordComment.pm9
-rw-r--r--rt/lib/RT/Action/RecordCorrespondence.pm9
-rw-r--r--rt/lib/RT/Action/SetPriority.pm9
-rw-r--r--rt/lib/RT/Action/UserDefined.pm9
11 files changed, 744 insertions, 302 deletions
diff --git a/rt/lib/RT/Action/AutoOpen.pm b/rt/lib/RT/Action/AutoOpen.pm
index 004ed13cc..e1cf0ae7c 100644
--- a/rt/lib/RT/Action/AutoOpen.pm
+++ b/rt/lib/RT/Action/AutoOpen.pm
@@ -1,8 +1,8 @@
# BEGIN BPS TAGGED BLOCK {{{
#
# COPYRIGHT:
-#
-# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+#
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
# <jesse@bestpractical.com>
#
# (Except where explicitly superseded by other copyright notices)
@@ -45,28 +45,25 @@
# those contributions and any derivatives thereof.
#
# END BPS TAGGED BLOCK }}}
-# This Action will open the BASE if a dependent is resolved.
+# This Action will open the BASE if a dependent is resolved.
package RT::Action::AutoOpen;
-require RT::Action::Generic;
use strict;
-use vars qw/@ISA/;
-@ISA=qw(RT::Action::Generic);
+use warnings;
-#Do what we need to do and send it out.
+use base qw(RT::Action);
-#What does this type of Action does
+=head1 DESCRIPTION
-# {{{ sub Describe
-sub Describe {
- my $self = shift;
- return (ref $self );
-}
-# }}}
+Opens a ticket unless it's allready open, but only unless transaction
+L<RT::Transaction/IsInbound is inbound>.
+Doesn't open a ticket if message's head has field C<RT-Control> with
+C<no-autoopen> substring.
+
+=cut
-# {{{ sub Prepare
sub Prepare {
my $self = shift;
@@ -83,22 +80,21 @@ sub Prepare {
return 1;
}
-# }}}
sub Commit {
my $self = shift;
- my $oldstatus = $self->TicketObj->Status();
- $self->TicketObj->__Set( Field => 'Status', Value => 'open' );
- $self->TicketObj->_NewTransaction(
- Type => 'Status',
- Field => 'Status',
- OldValue => $oldstatus,
- NewValue => 'open',
- Data => 'Ticket auto-opened on incoming correspondence'
- );
-
-
- return(1);
+
+ my $oldstatus = $self->TicketObj->Status;
+ $self->TicketObj->__Set( Field => 'Status', Value => 'open' );
+ $self->TicketObj->_NewTransaction(
+ Type => 'Status',
+ Field => 'Status',
+ OldValue => $oldstatus,
+ NewValue => 'open',
+ Data => 'Ticket auto-opened on incoming correspondence'
+ );
+
+ return 1;
}
eval "require RT::Action::AutoOpen_Vendor";
diff --git a/rt/lib/RT/Action/CreateTickets.pm b/rt/lib/RT/Action/CreateTickets.pm
index 40d18d357..4883ae3a8 100644
--- a/rt/lib/RT/Action/CreateTickets.pm
+++ b/rt/lib/RT/Action/CreateTickets.pm
@@ -1,8 +1,8 @@
# BEGIN BPS TAGGED BLOCK {{{
#
# COPYRIGHT:
-#
-# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+#
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
# <jesse@bestpractical.com>
#
# (Except where explicitly superseded by other copyright notices)
@@ -45,13 +45,12 @@
# those contributions and any derivatives thereof.
#
# END BPS TAGGED BLOCK }}}
+
package RT::Action::CreateTickets;
-require RT::Action::Generic;
+use base 'RT::Action';
use strict;
use warnings;
-use vars qw/@ISA/;
-@ISA = qw(RT::Action::Generic);
use MIME::Entity;
@@ -106,10 +105,12 @@ of perl inside the Text::Template using {} delimiters, but that
such sections absolutely can not span a ===Create-Ticket boundary.
After each ticket is created, it's stuffed into a hash called %Tickets
-so as to be available during the creation of other tickets during the same
-ScripAction. The hash is prepopulated with the ticket which triggered the
-ScripAction as $Tickets{'TOP'}; you can also access that ticket using the
-shorthand TOP.
+so as to be available during the creation of other tickets during the
+same ScripAction, using the key 'create-identifier', where
+C<identifier> is the id you put after C<===Create-Ticket:>. The hash
+is prepopulated with the ticket which triggered the ScripAction as
+$Tickets{'TOP'}; you can also access that ticket using the shorthand
+TOP.
A simple example:
@@ -164,8 +165,9 @@ A convoluted example
ENDOFCONTENT
===Create-Ticket: two
Subject: Manager approval
+ Type: approval
Depended-On-By: TOP
- Refers-On: {$Tickets{"approval"}->Id}
+ Refers-To: {$Tickets{"create-approval"}->Id}
Queue: ___Approvals
Content-Type: text/plain
Content:
@@ -236,236 +238,6 @@ Refers-To, RefersTo, refersto, refers-to and r-e-f-er-s-tO will all
be treated as the same thing.
-=begin testing
-
-ok (require RT::Action::CreateTickets);
-use_ok(RT::Scrip);
-use_ok(RT::Template);
-use_ok(RT::ScripAction);
-use_ok(RT::ScripCondition);
-use_ok(RT::Ticket);
-
-my $approvalsq = RT::Queue->new($RT::SystemUser);
-$approvalsq->Create(Name => 'Approvals');
-ok ($approvalsq->Id, "Created Approvals test queue");
-
-
-my $approvals =
-'===Create-Ticket: approval
-Queue: ___Approvals
-Type: approval
-AdminCc: {join ("\nAdminCc: ",@admins) }
-Depended-On-By: {$Tickets{"TOP"}->Id}
-Refers-To: TOP
-Subject: Approval for ticket: {$Tickets{"TOP"}->Id} - {$Tickets{"TOP"}->Subject}
-Due: {time + 86400}
-Content-Type: text/plain
-Content: Your approval is requested for the ticket {$Tickets{"TOP"}->Id}: {$Tickets{"TOP"}->Subject}
-Blah
-Blah
-ENDOFCONTENT
-===Create-Ticket: two
-Subject: Manager approval.
-Depended-On-By: approval
-Queue: ___Approvals
-Content-Type: text/plain
-Content:
-Your minion approved ticket {$Tickets{"TOP"}->Id}. you ok with that?
-ENDOFCONTENT
-';
-
-ok ($approvals =~ /Content/, "Read in the approvals template");
-
-my $apptemp = RT::Template->new($RT::SystemUser);
-$apptemp->Create( Content => $approvals, Name => "Approvals", Queue => "0");
-
-ok ($apptemp->Id);
-
-my $q = RT::Queue->new($RT::SystemUser);
-$q->Create(Name => 'WorkflowTest');
-ok ($q->Id, "Created workflow test queue");
-
-my $scrip = RT::Scrip->new($RT::SystemUser);
-my ($sval, $smsg) =$scrip->Create( ScripCondition => 'On Transaction',
- ScripAction => 'Create Tickets',
- Template => 'Approvals',
- Queue => $q->Id);
-ok ($sval, $smsg);
-ok ($scrip->Id, "Created the scrip");
-ok ($scrip->TemplateObj->Id, "Created the scrip template");
-ok ($scrip->ConditionObj->Id, "Created the scrip condition");
-ok ($scrip->ActionObj->Id, "Created the scrip action");
-
-my $t = RT::Ticket->new($RT::SystemUser);
-my($tid, $ttrans, $tmsg) = $t->Create(Subject => "Sample workflow test",
- Owner => "root",
- Queue => $q->Id);
-
-ok ($tid,$tmsg);
-
-my $deps = $t->DependsOn;
-is ($deps->Count, 1, "The ticket we created depends on one other ticket");
-my $dependson= $deps->First->TargetObj;
-ok ($dependson->Id, "It depends on a real ticket");
-unlike ($dependson->Subject, qr/{/, "The subject doesn't have braces in it. that means we're interpreting expressions");
-is ($t->ReferredToBy->Count,1, "It's only referred to by one other ticket");
-is ($t->ReferredToBy->First->BaseObj->Id,$t->DependsOn->First->TargetObj->Id, "The same ticket that depends on it refers to it.");
-use RT::Action::CreateTickets;
-my $action = RT::Action::CreateTickets->new( CurrentUser => $RT::SystemUser);;
-
-# comma-delimited templates
-my $commas = <<"EOF";
-id,Queue,Subject,Owner,Content
-ticket1,General,"foo, bar",root,blah
-ticket2,General,foo bar,root,blah
-ticket3,General,foo' bar,root,blah'boo
-ticket4,General,foo' bar,,blah'boo
-EOF
-
-
-# Comma delimited templates with missing data
-my $sparse_commas = <<"EOF";
-id,Queue,Subject,Owner,Requestor
-ticket14,General,,,bobby
-ticket15,General,,,tommy
-ticket16,General,,suzie,tommy
-ticket17,General,Foo "bar" baz,suzie,tommy
-ticket18,General,'Foo "bar" baz',suzie,tommy
-ticket19,General,'Foo bar' baz,suzie,tommy
-EOF
-
-
-# tab-delimited templates
-my $tabs = <<"EOF";
-id\tQueue\tSubject\tOwner\tContent
-ticket10\tGeneral\t"foo' bar"\troot\tblah'
-ticket11\tGeneral\tfoo, bar\troot\tblah
-ticket12\tGeneral\tfoo' bar\troot\tblah'boo
-ticket13\tGeneral\tfoo' bar\t\tblah'boo
-EOF
-
-my %expected;
-
-$expected{ticket1} = <<EOF;
-Queue: General
-Subject: foo, bar
-Owner: root
-Content: blah
-ENDOFCONTENT
-EOF
-
-$expected{ticket2} = <<EOF;
-Queue: General
-Subject: foo bar
-Owner: root
-Content: blah
-ENDOFCONTENT
-EOF
-
-$expected{ticket3} = <<EOF;
-Queue: General
-Subject: foo' bar
-Owner: root
-Content: blah'boo
-ENDOFCONTENT
-EOF
-
-$expected{ticket4} = <<EOF;
-Queue: General
-Subject: foo' bar
-Owner:
-Content: blah'boo
-ENDOFCONTENT
-EOF
-
-$expected{ticket10} = <<EOF;
-Queue: General
-Subject: foo' bar
-Owner: root
-Content: blah'
-ENDOFCONTENT
-EOF
-
-$expected{ticket11} = <<EOF;
-Queue: General
-Subject: foo, bar
-Owner: root
-Content: blah
-ENDOFCONTENT
-EOF
-
-$expected{ticket12} = <<EOF;
-Queue: General
-Subject: foo' bar
-Owner: root
-Content: blah'boo
-ENDOFCONTENT
-EOF
-
-$expected{ticket13} = <<EOF;
-Queue: General
-Subject: foo' bar
-Owner:
-Content: blah'boo
-ENDOFCONTENT
-EOF
-
-
-$expected{'ticket14'} = <<EOF;
-Queue: General
-Subject:
-Owner:
-Requestor: bobby
-EOF
-$expected{'ticket15'} = <<EOF;
-Queue: General
-Subject:
-Owner:
-Requestor: tommy
-EOF
-$expected{'ticket16'} = <<EOF;
-Queue: General
-Subject:
-Owner: suzie
-Requestor: tommy
-EOF
-$expected{'ticket17'} = <<EOF;
-Queue: General
-Subject: Foo "bar" baz
-Owner: suzie
-Requestor: tommy
-EOF
-$expected{'ticket18'} = <<EOF;
-Queue: General
-Subject: Foo "bar" baz
-Owner: suzie
-Requestor: tommy
-EOF
-$expected{'ticket19'} = <<EOF;
-Queue: General
-Subject: 'Foo bar' baz
-Owner: suzie
-Requestor: tommy
-EOF
-
-
-
-
-$action->Parse(Content =>$commas);
-$action->Parse(Content =>$sparse_commas);
-$action->Parse(Content => $tabs);
-
-my %got;
-foreach (@{ $action->{'create_tickets'} }) {
- $got{$_} = $action->{'templates'}->{$_};
-}
-
-foreach my $id ( sort keys %expected ) {
- ok(exists($got{"create-$id"}), "template exists for $id");
- is($got{"create-$id"}, $expected{$id}, "template is correct for $id");
-}
-
-=end testing
=head1 AUTHOR
@@ -541,16 +313,16 @@ sub Prepare {
my $self = shift;
unless ( $self->TemplateObj ) {
- $RT::Logger->warning("No template object handed to $self\n");
+ $RT::Logger->warning("No template object handed to $self");
}
unless ( $self->TransactionObj ) {
- $RT::Logger->warning("No transaction object handed to $self\n");
+ $RT::Logger->warning("No transaction object handed to $self");
}
unless ( $self->TicketObj ) {
- $RT::Logger->warning("No ticket object handed to $self\n");
+ $RT::Logger->warning("No ticket object handed to $self");
}
@@ -575,7 +347,6 @@ sub CreateByTemplate {
my @results;
# XXX: cargo cult programming that works. i'll be back.
- use bytes;
local %T::Tickets = %T::Tickets;
local $T::TOP = $T::TOP;
@@ -637,7 +408,6 @@ sub UpdateByTemplate {
my $top = shift;
# XXX: cargo cult programming that works. i'll be back.
- use bytes;
my @results;
local %T::Tickets = %T::Tickets;
@@ -894,7 +664,7 @@ sub ParseLines {
}
);
- $RT::Logger->debug("Workflow: yielding\n$content");
+ $RT::Logger->debug("Workflow: yielding $content");
if ($err) {
$RT::Logger->error( "Ticket creation failed: " . $err );
@@ -990,6 +760,7 @@ sub ParseLines {
TimeLeft => $args{'timeleft'},
InitialPriority => $args{'initialpriority'} || 0,
FinalPriority => $args{'finalpriority'} || 0,
+ SquelchMailTo => $args{'squelchmailto'},
Type => $args{'type'},
);
@@ -1223,7 +994,7 @@ sub GetUpdateTemplate {
my $mode = $LINKTYPEMAP{$type}->{Mode};
my $method = $LINKTYPEMAP{$type}->{Type};
- my $links;
+ my $links = '';
while ( my $link = $t->$method->Next ) {
$links .= ", " if $links;
diff --git a/rt/lib/RT/Action/EscalatePriority.pm b/rt/lib/RT/Action/EscalatePriority.pm
index 46635df05..bf9de92c2 100644
--- a/rt/lib/RT/Action/EscalatePriority.pm
+++ b/rt/lib/RT/Action/EscalatePriority.pm
@@ -1,8 +1,8 @@
# BEGIN BPS TAGGED BLOCK {{{
#
# COPYRIGHT:
-#
-# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+#
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
# <jesse@bestpractical.com>
#
# (Except where explicitly superseded by other copyright notices)
@@ -45,6 +45,7 @@
# those contributions and any derivatives thereof.
#
# END BPS TAGGED BLOCK }}}
+
=head1 NAME
RT::Action::EscalatePriority
@@ -71,11 +72,9 @@ as the ticket heads toward its due date.
package RT::Action::EscalatePriority;
-require RT::Action::Generic;
+use base 'RT::Action';
use strict;
-use vars qw/@ISA/;
-@ISA=qw(RT::Action::Generic);
#Do what we need to do and send it out.
@@ -155,7 +154,7 @@ sub Commit {
my ($val, $msg) = $self->TicketObj->SetPriority($self->{'prio'});
unless ($val) {
- $RT::Logger->debug($self . " $msg\n");
+ $RT::Logger->debug($self . " $msg");
}
}
diff --git a/rt/lib/RT/Action/ExtractSubjectTag.pm b/rt/lib/RT/Action/ExtractSubjectTag.pm
new file mode 100644
index 000000000..4a173ce76
--- /dev/null
+++ b/rt/lib/RT/Action/ExtractSubjectTag.pm
@@ -0,0 +1,103 @@
+# BEGIN BPS TAGGED BLOCK {{{
+#
+# COPYRIGHT:
+#
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+# <jesse@bestpractical.com>
+#
+# (Except where explicitly superseded by other copyright notices)
+#
+#
+# LICENSE:
+#
+# This work is made available to you under the terms of Version 2 of
+# the GNU General Public License. A copy of that license should have
+# been provided with this software, but in any event can be snarfed
+# from www.gnu.org.
+#
+# This work is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+#
+#
+# CONTRIBUTION SUBMISSION POLICY:
+#
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+#
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+#
+# END BPS TAGGED BLOCK }}}
+
+package RT::Action::ExtractSubjectTag;
+use base 'RT::Action';
+use strict;
+
+sub Describe {
+ my $self = shift;
+ return ( ref $self );
+}
+
+sub Prepare {
+ return (1);
+}
+
+sub Commit {
+ my $self = shift;
+ my $Transaction = $self->TransactionObj;
+ my $FirstAttachment = $Transaction->Attachments->First;
+ return 1 unless ($FirstAttachment);
+
+ my $Ticket = $self->TicketObj;
+
+ my $TicketSubject = $self->TicketObj->Subject;
+ my $origTicketSubject = $TicketSubject;
+ my $TransactionSubject = $FirstAttachment->Subject;
+
+ my $match = RT->Config->Get('ExtractSubjectTagMatch');
+ my $nomatch = RT->Config->Get('ExtractSubjectTagNoMatch');
+ TAGLIST: while ( $TransactionSubject =~ /($match)/g ) {
+ my $tag = $1;
+ next if $tag =~ /$nomatch/;
+ foreach my $subject_tag ( RT->System->SubjectTag ) {
+ if ($tag =~ /\[\Q$subject_tag\E\s+\#(\d+)\s*\]/) {
+ next TAGLIST;
+ }
+ }
+ $TicketSubject .= " $tag" unless ( $TicketSubject =~ /\Q$tag\E/ );
+ }
+
+ $self->TicketObj->SetSubject($TicketSubject)
+ if ( $TicketSubject ne $origTicketSubject );
+
+ return (1);
+}
+
+eval "require RT::Action::ExtractSubjectTag_Vendor";
+if ($@ && $@ !~ qr{^Can't locate RT/Action/ExtractSubjectTag_Vendor.pm}) {
+ die $@;
+};
+
+eval "require RT::Action::ExtractSubjectTag_Local";
+if ($@ && $@ !~ qr{^Can't locate RT/Action/ExtractSubjectTag_Local.pm}) {
+ die $@;
+};
+
+1;
diff --git a/rt/lib/RT/Action/LinearEscalate.pm b/rt/lib/RT/Action/LinearEscalate.pm
new file mode 100755
index 000000000..9130f40ca
--- /dev/null
+++ b/rt/lib/RT/Action/LinearEscalate.pm
@@ -0,0 +1,279 @@
+# BEGIN BPS TAGGED BLOCK {{{
+#
+# COPYRIGHT:
+#
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+# <jesse@bestpractical.com>
+#
+# (Except where explicitly superseded by other copyright notices)
+#
+#
+# LICENSE:
+#
+# This work is made available to you under the terms of Version 2 of
+# the GNU General Public License. A copy of that license should have
+# been provided with this software, but in any event can be snarfed
+# from www.gnu.org.
+#
+# This work is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+#
+#
+# CONTRIBUTION SUBMISSION POLICY:
+#
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+#
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+#
+# END BPS TAGGED BLOCK }}}
+
+=head1 NAME
+
+RT::Action::LinearEscalate - will move a ticket's priority toward its final priority.
+
+=head1 This vs. RT::Action::EscalatePriority
+
+This action doesn't change priority if due date is not set.
+
+This action honor the Starts date.
+
+This action can apply changes silently.
+
+This action can replace EscalatePriority completly. If you want to tickets
+that have been created without Due date then you can add scrip that sets
+default due date. For example a week then priorities of your tickets will
+escalate linearly during the week from intial value towards final.
+
+=head1 This vs. LinearEscalate from the CPAN
+
+This action is an integration of the module from the CPAN into RT's core
+that's happened in RT 3.8. If you're upgrading from 3.6 and have been using
+module from the CPAN with old version of RT then you should uninstall it
+and use this one.
+
+However, this action doesn't support control over config. Read </CONFIGURATION>
+to find out ways to deal with it.
+
+=head1 DESCRIPTION
+
+LinearEscalate is a ScripAction that will move a ticket's priority
+from its initial priority to its final priority linearly as
+the ticket approaches its due date.
+
+It's intended to be called by an RT escalation tool. One such tool is called
+rt-crontool and is located in $RTHOME/bin (see C<rt-crontool -h> for more details).
+
+=head1 USAGE
+
+Once the ScripAction is installed, the following script in "cron"
+will get tickets to where they need to be:
+
+ rt-crontool --search RT::Search::FromSQL --search-arg \
+ "(Status='new' OR Status='open' OR Status = 'stalled')" \
+ --action RT::Action::LinearEscalate
+
+The Starts date is associated with intial ticket's priority or
+the Created field if the former is not set. End of interval is
+the Due date. Tickets without due date B<are not updated>.
+
+=head1 CONFIGURATION
+
+Initial and Final priorities are controlled by queue's options
+and can be defined using the web UI via Configuration tab. This
+action should handle correctly situations when initial priority
+is greater than final.
+
+LinearEscalate's behavior can be controlled by two options:
+
+=over 4
+
+=item RecordTransaction - defaults to false and if option is true then
+causes the tool to create a transaction on the ticket when it is escalated.
+
+=item UpdateLastUpdated - which defaults to true and updates the LastUpdated
+field when the ticket is escalated, otherwise don't touch anything.
+
+=back
+
+You cannot set "UpdateLastUpdated" to false unless "RecordTransaction"
+is also false. Well, you can, but we'll just ignore you.
+
+You can set this options using either in F<RT_SiteConfig.pm>, as action
+argument in call to the rt-crontool or in DB if you want to use the action
+in scrips.
+
+From a shell you can use the following command:
+
+ rt-crontool --search RT::Search::FromSQL --search-arg \
+ "(Status='new' OR Status='open' OR Status = 'stalled')" \
+ --action RT::Action::LinearEscalate \
+ --action-arg "RecordTransaction: 1"
+
+This ScripAction uses RT's internal _Set or __Set calls to set ticket
+priority without running scrips or recording a transaction on each
+update, if it's been said to.
+
+=cut
+
+package RT::Action::LinearEscalate;
+
+use strict;
+use warnings;
+use base qw(RT::Action);
+
+our $VERSION = '0.06';
+
+#Do what we need to do and send it out.
+
+#What does this type of Action does
+
+sub Describe {
+ my $self = shift;
+ my $class = ref($self) || $self;
+ return "$class will move a ticket's priority toward its final priority.";
+}
+
+sub Prepare {
+ my $self = shift;
+
+ my $ticket = $self->TicketObj;
+
+ my $due = $ticket->DueObj->Unix;
+ unless ( $due > 0 ) {
+ $RT::Logger->debug('Due is not set. Not escalating.');
+ return 1;
+ }
+
+ my $priority_range = ($ticket->FinalPriority ||0) - ($ticket->InitialPriority ||0);
+ unless ( $priority_range ) {
+ $RT::Logger->debug('Final and Initial priorities are equal. Not escalating.');
+ return 1;
+ }
+
+ if ( $ticket->Priority >= $ticket->FinalPriority && $priority_range > 0 ) {
+ $RT::Logger->debug('Current priority is greater than final. Not escalating.');
+ return 1;
+ }
+ elsif ( $ticket->Priority <= $ticket->FinalPriority && $priority_range < 0 ) {
+ $RT::Logger->debug('Current priority is lower than final. Not escalating.');
+ return 1;
+ }
+
+ # TODO: compute the number of business days until the ticket is due
+
+ # now we know we have a due date. for every day that passes,
+ # increment priority according to the formula
+
+ my $starts = $ticket->StartsObj->Unix;
+ $starts = $ticket->CreatedObj->Unix unless $starts > 0;
+ my $now = time;
+
+ # do nothing if we didn't reach starts or created date
+ if ( $starts > $now ) {
+ $RT::Logger->debug('Starts(Created) is in future. Not escalating.');
+ return 1;
+ }
+
+ $due = $starts + 1 if $due <= $starts; # +1 to avoid div by zero
+
+ my $percent_complete = ($now-$starts)/($due - $starts);
+
+ my $new_priority = int($percent_complete * $priority_range) + ($ticket->InitialPriority || 0);
+ $new_priority = $ticket->FinalPriority if $new_priority > $ticket->FinalPriority;
+ $self->{'new_priority'} = $new_priority;
+
+ return 1;
+}
+
+sub Commit {
+ my $self = shift;
+
+ my $new_value = $self->{'new_priority'};
+ return 1 unless defined $new_value;
+
+ my $ticket = $self->TicketObj;
+ # if the priority hasn't changed do nothing
+ return 1 if $ticket->Priority == $new_value;
+
+ # override defaults from argument
+ my ($record, $update) = (0, 1);
+ {
+ my $arg = $self->Argument || '';
+ if ( $arg =~ /RecordTransaction:\s*(\d+)/i ) {
+ $record = $1;
+ $RT::Logger->debug("Overrode RecordTransaction: $record");
+ }
+ if ( $arg =~ /UpdateLastUpdated:\s*(\d+)/i ) {
+ $update = $1;
+ $RT::Logger->debug("Overrode UpdateLastUpdated: $update");
+ }
+ $update = 1 if $record;
+ }
+
+ $RT::Logger->debug(
+ 'Linearly escalating priority of ticket #'. $ticket->Id
+ .' from '. $ticket->Priority .' to '. $new_value
+ .' and'. ($record? '': ' do not') .' record a transaction'
+ .' and'. ($update? '': ' do not') .' touch last updated field'
+ );
+
+ my ( $val, $msg );
+ unless ( $record ) {
+ unless ( $update ) {
+ ( $val, $msg ) = $ticket->__Set(
+ Field => 'Priority',
+ Value => $new_value,
+ );
+ }
+ else {
+ ( $val, $msg ) = $ticket->_Set(
+ Field => 'Priority',
+ Value => $new_value,
+ RecordTransaction => 0,
+ );
+ }
+ }
+ else {
+ ( $val, $msg ) = $ticket->SetPriority( $new_value );
+ }
+
+ unless ($val) {
+ $RT::Logger->error( "Couldn't set new priority value: $msg" );
+ return (0, $msg);
+ }
+ return 1;
+}
+
+eval "require RT::Action::LinearEscalate_Vendor";
+die $@ if ( $@ && $@ !~ qr{^Can't locate RT/Action/LinearEscalate_Vendor.pm} );
+eval "require RT::Action::LinearEscalate_Local";
+die $@ if ( $@ && $@ !~ qr{^Can't locate RT/Action/LinearEscalate_Local.pm} );
+
+1;
+
+=head1 AUTHORS
+
+Kevin Riggle E<lt>kevinr@bestpractical.comE<gt>
+
+Ruslan Zakirov E<lt>ruz@bestpractical.comE<gt>
+
+=cut
diff --git a/rt/lib/RT/Action/NotifyGroup.pm b/rt/lib/RT/Action/NotifyGroup.pm
new file mode 100644
index 000000000..6b830cb86
--- /dev/null
+++ b/rt/lib/RT/Action/NotifyGroup.pm
@@ -0,0 +1,209 @@
+# BEGIN BPS TAGGED BLOCK {{{
+#
+# COPYRIGHT:
+#
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+# <jesse@bestpractical.com>
+#
+# (Except where explicitly superseded by other copyright notices)
+#
+#
+# LICENSE:
+#
+# This work is made available to you under the terms of Version 2 of
+# the GNU General Public License. A copy of that license should have
+# been provided with this software, but in any event can be snarfed
+# from www.gnu.org.
+#
+# This work is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+#
+#
+# CONTRIBUTION SUBMISSION POLICY:
+#
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+#
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+#
+# END BPS TAGGED BLOCK }}}
+
+=head1 NAME
+
+RT::Action::NotifyGroup - RT Action that sends notifications to groups and/or users
+
+=head1 DESCRIPTION
+
+RT action module that allow you to notify particular groups and/or users.
+Distribution is shipped with C<rt-email-group-admin> script that
+is command line tool for managing NotifyGroup scrip actions. For more
+more info see its documentation.
+
+=cut
+
+package RT::Action::NotifyGroup;
+
+use strict;
+use warnings;
+use base qw(RT::Action::Notify);
+
+require RT::User;
+require RT::Group;
+
+=head1 METHODS
+
+=head2 SetRecipients
+
+Sets the recipients of this message to Groups and/or Users.
+
+=cut
+
+sub SetRecipients {
+ my $self = shift;
+
+ my $arg = $self->Argument;
+ foreach( $self->__SplitArg( $arg ) ) {
+ $self->_HandleArgument( $_ );
+ }
+
+ my $creator = $self->TransactionObj->CreatorObj->EmailAddress();
+ unless( $RT::NotifyActor ) {
+ @{ $self->{'To'} } = grep ( !/^\Q$creator\E$/, @{ $self->{'To'} } );
+ }
+
+ $self->{'seen_ueas'} = {};
+
+ return 1;
+}
+
+sub _HandleArgument {
+ my $self = shift;
+ my $instance = shift;
+
+ if ( $instance !~ /\D/ ) {
+ my $obj = RT::Principal->new( $self->CurrentUser );
+ $obj->Load( $instance );
+ return $self->_HandlePrincipal( $obj );
+ }
+
+ my $group = RT::Group->new( $self->CurrentUser );
+ $group->LoadUserDefinedGroup( $instance );
+ # to check disabled and so on
+ return $self->_HandlePrincipal( $group->PrincipalObj )
+ if $group->id;
+
+ require Email::Address;
+
+ my $user = RT::User->new( $self->CurrentUser );
+ if ( $instance =~ /^$Email::Address::addr_spec$/ ) {
+ $user->LoadByEmail( $instance );
+ return $self->__PushUserAddress( $instance )
+ unless $user->id;
+ } else {
+ $user->Load( $instance );
+ }
+ return $self->_HandlePrincipal( $user->PrincipalObj )
+ if $user->id;
+
+ $RT::Logger->error(
+ "'$instance' is not principal id, group name, user name,"
+ ." user email address or any email address"
+ );
+
+ return;
+}
+
+sub _HandlePrincipal {
+ my $self = shift;
+ my $obj = shift;
+ unless( $obj->id ) {
+ $RT::Logger->error( "Couldn't load principal #$obj" );
+ return;
+ }
+ if( $obj->Disabled ) {
+ $RT::Logger->info( "Principal #$obj is disabled => skip" );
+ return;
+ }
+ if( !$obj->PrincipalType ) {
+ $RT::Logger->crit( "Principal #$obj has empty type" );
+ } elsif( lc $obj->PrincipalType eq 'user' ) {
+ $self->__HandleUserArgument( $obj->Object );
+ } elsif( lc $obj->PrincipalType eq 'group' ) {
+ $self->__HandleGroupArgument( $obj->Object );
+ } else {
+ $RT::Logger->info( "Principal #$obj has unsupported type" );
+ }
+ return;
+}
+
+sub __HandleUserArgument {
+ my $self = shift;
+ my $obj = shift;
+
+ my $uea = $obj->EmailAddress;
+ unless( $uea ) {
+ $RT::Logger->warning( "User #". $obj->id ." has no email address" );
+ return;
+ }
+ $self->__PushUserAddress( $uea );
+}
+
+sub __HandleGroupArgument {
+ my $self = shift;
+ my $obj = shift;
+
+ my $members = $obj->UserMembersObj;
+ while( my $m = $members->Next ) {
+ $self->__HandleUserArgument( $m );
+ }
+}
+
+sub __SplitArg {
+ return grep length, map {s/^\s+//; s/\s+$//; $_} split /,/, $_[1];
+}
+
+sub __PushUserAddress {
+ my $self = shift;
+ my $uea = shift;
+ push @{ $self->{'To'} }, $uea unless $self->{'seen_ueas'}{ $uea }++;
+ return;
+}
+
+
+=head1 AUTHOR
+
+Ruslan U. Zakirov E<lt>ruz@bestpractical.comE<gt>
+
+L<RT::Action::NotifyGroupAsComment>, F<rt-email-group-admin>
+
+=cut
+
+eval "require RT::Action::NotifyGroup_Vendor";
+if ($@ && $@ !~ qr{^Can't locate RT/Action/NotifyGroup_Vendor.pm}) {
+ die $@;
+};
+
+eval "require RT::Action::NotifyGroup_Local";
+if ($@ && $@ !~ qr{^Can't locate RT/Action/NotifyGroup_Local.pm}) {
+ die $@;
+};
+
+1;
diff --git a/rt/lib/RT/Action/NotifyGroupAsComment.pm b/rt/lib/RT/Action/NotifyGroupAsComment.pm
new file mode 100644
index 000000000..bee0a01a1
--- /dev/null
+++ b/rt/lib/RT/Action/NotifyGroupAsComment.pm
@@ -0,0 +1,91 @@
+# BEGIN BPS TAGGED BLOCK {{{
+#
+# COPYRIGHT:
+#
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+# <jesse@bestpractical.com>
+#
+# (Except where explicitly superseded by other copyright notices)
+#
+#
+# LICENSE:
+#
+# This work is made available to you under the terms of Version 2 of
+# the GNU General Public License. A copy of that license should have
+# been provided with this software, but in any event can be snarfed
+# from www.gnu.org.
+#
+# This work is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+#
+#
+# CONTRIBUTION SUBMISSION POLICY:
+#
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+#
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+#
+# END BPS TAGGED BLOCK }}}
+
+=head1 NAME
+
+RT::Action::NotifyGroupAsComment - RT Action that sends notifications to groups and/or users as comment
+
+=head1 DESCRIPTION
+
+This is subclass of L<RT::Action::NotifyGroup> that send comments instead of replies.
+See C<rt-email-group-admin> and L<RT::Action::NotifyGroup> docs for more info.
+
+=cut
+
+package RT::Action::NotifyGroupAsComment;
+
+use strict;
+use warnings;
+
+use RT::Action::NotifyGroup;
+
+use base qw(RT::Action::NotifyGroup);
+
+sub SetReturnAddress {
+ my $self = shift;
+ $self->{'comment'} = 1;
+ return $self->SUPER::SetReturnAddress( @_, is_comment => 1 );
+}
+
+=head1 AUTHOR
+
+Ruslan U. Zakirov E<lt>ruz@bestpractical.comE<gt>
+
+=cut
+
+eval "require RT::Action::NotifyGroupAsComment_Vendor";
+if ($@ && $@ !~ qr{^Can't locate RT/Action/NotifyGroupAsComment_Vendor.pm}) {
+ die $@;
+};
+
+eval "require RT::Action::NotifyGroupAsComment_Local";
+if ($@ && $@ !~ qr{^Can't locate RT/Action/NotifyGroupAsComment_Local.pm}) {
+ die $@;
+};
+
+1;
diff --git a/rt/lib/RT/Action/RecordComment.pm b/rt/lib/RT/Action/RecordComment.pm
index c0256d6d7..bac17e96e 100644
--- a/rt/lib/RT/Action/RecordComment.pm
+++ b/rt/lib/RT/Action/RecordComment.pm
@@ -1,8 +1,8 @@
# BEGIN BPS TAGGED BLOCK {{{
#
# COPYRIGHT:
-#
-# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+#
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
# <jesse@bestpractical.com>
#
# (Except where explicitly superseded by other copyright notices)
@@ -45,11 +45,10 @@
# those contributions and any derivatives thereof.
#
# END BPS TAGGED BLOCK }}}
+
package RT::Action::RecordComment;
-require RT::Action::Generic;
+use base 'RT::Action';
use strict;
-use vars qw/@ISA/;
-@ISA = qw(RT::Action::Generic);
=head1 NAME
diff --git a/rt/lib/RT/Action/RecordCorrespondence.pm b/rt/lib/RT/Action/RecordCorrespondence.pm
index 10a890e4e..044893b97 100644
--- a/rt/lib/RT/Action/RecordCorrespondence.pm
+++ b/rt/lib/RT/Action/RecordCorrespondence.pm
@@ -1,8 +1,8 @@
# BEGIN BPS TAGGED BLOCK {{{
#
# COPYRIGHT:
-#
-# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+#
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
# <jesse@bestpractical.com>
#
# (Except where explicitly superseded by other copyright notices)
@@ -45,11 +45,10 @@
# those contributions and any derivatives thereof.
#
# END BPS TAGGED BLOCK }}}
+
package RT::Action::RecordCorrespondence;
-require RT::Action::Generic;
+use base 'RT::Action';
use strict;
-use vars qw/@ISA/;
-@ISA = qw(RT::Action::Generic);
=head1 NAME
diff --git a/rt/lib/RT/Action/SetPriority.pm b/rt/lib/RT/Action/SetPriority.pm
index b4c8ee199..9b0838926 100644
--- a/rt/lib/RT/Action/SetPriority.pm
+++ b/rt/lib/RT/Action/SetPriority.pm
@@ -1,8 +1,8 @@
# BEGIN BPS TAGGED BLOCK {{{
#
# COPYRIGHT:
-#
-# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+#
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
# <jesse@bestpractical.com>
#
# (Except where explicitly superseded by other copyright notices)
@@ -45,12 +45,11 @@
# those contributions and any derivatives thereof.
#
# END BPS TAGGED BLOCK }}}
+
package RT::Action::SetPriority;
-require RT::Action::Generic;
+use base 'RT::Action';
use strict;
-use vars qw/@ISA/;
-@ISA=qw(RT::Action::Generic);
#Do what we need to do and send it out.
diff --git a/rt/lib/RT/Action/UserDefined.pm b/rt/lib/RT/Action/UserDefined.pm
index 7bf6eee51..80ef49224 100644
--- a/rt/lib/RT/Action/UserDefined.pm
+++ b/rt/lib/RT/Action/UserDefined.pm
@@ -1,8 +1,8 @@
# BEGIN BPS TAGGED BLOCK {{{
#
# COPYRIGHT:
-#
-# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+#
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
# <jesse@bestpractical.com>
#
# (Except where explicitly superseded by other copyright notices)
@@ -45,14 +45,11 @@
# those contributions and any derivatives thereof.
#
# END BPS TAGGED BLOCK }}}
-
package RT::Action::UserDefined;
-use RT::Action::Generic;
+use base 'RT::Action';
use strict;
-use vars qw/@ISA/;
-@ISA = qw(RT::Action::Generic);
=head2 Prepare