https://rt-wiki.bestpractical.com/api.php?action=feedcontributions&user=Tharn&feedformat=atomRequest Tracker Wiki - User contributions [en]2024-03-29T11:31:08ZUser contributionsMediaWiki 1.37.2https://rt-wiki.bestpractical.com/index.php?title=AddTicketHistoryToMail&diff=26482AddTicketHistoryToMail2017-01-20T18:11:17Z<p>Tharn: undo html entity conversion & wrap in <pre></p>
<hr />
<div>Original author: Michael Markstaller - IFCO<br />
<br />
Code cleanups: [[RuslanZakirov]]<br />
<br />
Require: Text::Wrapper perl module<br />
<br />
<pre style="white-space: pre;"><br />
Subject: Resolved: {$Ticket->Subject}<br />
<br />
Dear customer,<br />
<br />
your issue regarding<br />
<br />
"{$Ticket->Subject()}",<br />
<br />
has been resolved in our ticket system. See below for history of this ticket.<br />
<br />
If you have any further questions or concerns, please reply to this<br />
message to reopen the ticket.<br />
<br />
regards<br />
<br />
{$Ticket->OwnerObj->RealName || $Ticket->OwnerObj->Name}<br />
<br />
your-company-fooprint<br />
<br />
<br />
----------------------------------------------------------------<br />
Your initial request was<br />
----------------------------------------------------------------<br />
<br />
{$Ticket->Transactions->First->Content()}<br />
<br />
----------------------------------------------------------------<br />
Complete Ticket History<br />
----------------------------------------------------------------<br />
<br />
{<br />
my $resolved_message = '';<br />
my $last_content = '';<br />
<br />
my $transactions = $Ticket->Transactions;<br />
<br />
<br />
$transactions->Limit( FIELD => 'Type', VALUE => 'Correspond' );<br />
# If for some reason you want to expose internal comments to end users,<br />
# probably a bad idea, comment out the previous line and uncomment these:<br />
# $transactions->Limit( FIELD => 'Type', VALUE => 'Correspond', ENTRYAGGREGATOR => 'OR', OPERATOR => '=' );<br />
# $transactions->Limit( FIELD => 'Type', VALUE => 'Comment', ENTRYAGGREGATOR => 'OR', OPERATOR => '=' );<br />
<br />
<br />
while (my $transaction = $transactions->Next) {<br />
my $attachments = $transaction->Attachments;<br />
<br />
while (my $message = $attachments->Next) {<br />
next unless $message->ContentType =~<br />
m!^(text/html|text/plain|message|text$)!i;<br />
<br />
my $content = $message->Content;<br />
$content =~ s/>(([^ >]|\n)*)>//g; # strip HTML tags from text/html<br />
next unless $content;<br />
<br />
next if $last_content eq $content;<br />
$last_content = $content;<br />
<br />
my $subject = ($message->Subject || $Ticket->Subject);<br />
<br />
my $wrapper = Text::Wrapper->new(columns=>70);<br />
$content = $wrapper->wrap($content);<br />
<br />
$resolved_message .= "Subject: ";<br />
$resolved_message .= $subject;<br />
$resolved_message .= "\n";<br />
$resolved_message .= "From: ";<br />
$resolved_message .= $message->CreatorObj->RealName || $message->CreatorObj->EmailAddress;<br />
$resolved_message .= "\n";<br />
$resolved_message .= "Time: ";<br />
$resolved_message .= $message->CreatedObj->AsString;<br />
$resolved_message .= "\n";<br />
$resolved_message .= "\n";<br />
$resolved_message .= "$content\n";<br />
$resolved_message .= "------------------------------------------------\n";<br />
}<br />
}<br />
$resolved_message;<br />
}<br />
<br />
-------------------------------------------------------------------------<br />
Common Information<br />
-------------------------------------------------------------------------<br />
<br />
There is no need to reply to this message unless you want to RE-OPEN your<br />
ticket with ID [{$rtname} #{$Ticket->id}].<br />
<br />
If you want to simply add a COMMENT to this ticket without re-opening the ticket, click below:<br />
mailto:rt-comment@yourdomain.com?subject=[{$rtname}%20#{$Ticket->id()}]&body=%20<br />
<br />
Please note:<br />
- ALWAYS include the string [{$rtname} #{$Ticket->id}] in the subject line of all future correspondence about this issue.<br />
- Do NOT attach or include the content of previous emails already sent to you by rt.<br />
</pre></div>Tharnhttps://rt-wiki.bestpractical.com/index.php?title=MultipleOutgoingEmailAddresses&diff=26371MultipleOutgoingEmailAddresses2016-08-21T18:30:15Z<p>Tharn: </p>
<hr />
<div>== Description ==<br />
<br />
This is [[Template]] part that allow use multiple outgoing email addresses for same queue<br />
<br />
== Where do you need it? ==<br />
<br />
For example you have [mailto:support@default.com support@default.com] and [mailto:support@second.com support@second.com] and wish track requests in one queue with no effect for user. You set mail relaying to [mailto:support@default.com support@default.com] from [mailto:support@second.com support@second.com]. Queue is working but RT uses [mailto:support@default.com support@default.com] as outgoing address for all request. Users are confused usual. He wrote to one address and gets answer from another. With this example you can track down<br />
<br />
<pre><br />
{use vars qw($replyto); # define global var for this template<br />
# original 'To:'<br />
my $orig_to = $Ticket-&gt;Transactions-&gt;First-&gt;Message-&gt;First-&gt;GetHeader('To');<br />
$replyto = $orig_to || 'support@default.com'; # default if 'To:' was unspecified.<br />
return '';}From: {$replyto}<br />
Sender: {$replyto}<br />
Reply-To: {$replyto}<br />
<br />
</pre><br />
<br />
'''NOTE:''' Don't add <u>any</u> character between '}' and 'From'<br />
<br />
See also [[UseActorAsSender]]</div>Tharnhttps://rt-wiki.bestpractical.com/index.php?title=EmailGroup&diff=26370EmailGroup2016-08-17T21:18:19Z<p>Tharn: </p>
<hr />
<div>= RT 3.8 =<br />
<br />
You can use RT <del>sbin/rt-notify-group-admin</del> <code>rt-email-group-admin</code> to create scrip actions to notify groups and or users.<br />
<br />
<del>It looks like the above script no longer exists, and instead was replaced by the concept of 'Queue Watchers' at some point. See the Queue Admin page to select watchers.</del><br />
<br />
The script is in <code>RTHOME/sbin</code> (confirmed in 3.8.6 and up to 3.8.8) and can be used to create new actions that can notify an existing Group or a group of Users. Useful when you need to notify a group only when a special condition is met (i.e. send email to users in the group Dispatchers when a new ticket is created).<br />
<br />
The 'Queue Watchers' feature is instead useful when you want a Group (or a User) to automatically be CC or AdminCC of each ticket that belongs to the watched queue.<br />
<br />
= RT 3.6 and older =<br />
<br />
Above script is part of an extension that has been integrated into RT 3.8. In 3.6 and older versions you can install [http://search.cpan.org/dist/RT-Action-NotifyGroup RT-Action-NotifyGroup] from CPAN.<br />
<br />
= Using custom code in Templates =<br />
<br />
== Description ==<br />
<br />
This [[Template]] will email the members of an RT [[Group]]. The default template code is for when a new ticket is created, but you can change it to include whatever message you would like.<br />
<br />
== Author/Contributors ==<br />
<br />
Original author: Jeff Hoover (jhoov)<br />
<br />
The majority of the perl code was taken from the [[OnCreateAddGroupCc]] [[ScripAction]].<br />
<br />
== How To Use ==<br />
<br />
These instructions assume you want to be emailed when a new ticket is created:<br />
<br />
1. Create a new template with the code below, replacing the $[[GroupName]] variable with the name of the RT group you want to email.<br />
<br />
2. Create a scrip that uses the template:<br />
<br />
Description: On Create Notify IT<br />
Condition: On Create<br />
Action: Notify Other Recipients<br />
Template: &lt;your template from step 1&gt;<br />
Stage: TransactionCreate<br />
<br />
== The Template ==<br />
<br />
<pre><br />
To: {<br />
my $GroupName = 'IT';<br />
<br />
# instantiate a group object<br />
my $addGroupObj = RT::Group-&gt;new($RT::SystemUser);<br />
$addGroupObj-&gt;LoadUserDefinedGroup($GroupName);<br />
return undef unless $addGroupObj;<br />
my $addGroupMembersObj = $addGroupObj-&gt;UserMembersObj;<br />
<br />
my $res = '';<br />
# walk through members of group<br />
while ( my $userObj = $addGroupMembersObj-&gt;Next) {<br />
my $email = $userObj-&gt;EmailAddress;<br />
next unless $email; # email can be empty<br />
<br />
$res .= ', ' if $res;<br />
$res .= $email;<br />
}<br />
$res;<br />
}<br />
RT-Attach-Message: yes<br />
<br />
A new ticket was created in the {$Ticket-&gt;QueueObj-&gt;Name} queue.<br />
<br />
Subject: {$Transaction-&gt;Subject || $Ticket-&gt;Subject || "(No subject given)"}<br />
Requestors: {$Ticket-&gt;RequestorAddresses}<br />
Ticket &lt;URL: {$RT::WebURL}Ticket/Display.html?id={$Ticket-&gt;id} &gt;<br />
<br />
{$Transaction-&gt;Content()}<br />
<br />
</pre><br />
<br />
'''NOTE:''' Don't add any characters before '{' or after '}'.<br />
<br />
Or, in RT 4 you can just use this:<br />
<br />
<nowiki><br />
To: { my $group = RT::Group->new( $RT::SystemUser ); $group->LoadUserDefinedGroup("group name here"); $group->MemberEmailAddressesAsString; }<br />
...<br />
</nowiki></div>Tharnhttps://rt-wiki.bestpractical.com/index.php?title=AddAdminCc&diff=26369AddAdminCc2016-08-17T21:14:51Z<p>Tharn: </p>
<hr />
<div>Contributed by [[JohannesVerelst]] Overview<br />
<br />
This scrip will add the emailaddress '[mailto:somebody@somewhere.com somebody@somewhere.com]' as an [[AdminCc]] (it needs to be available as a user already). You can use this if you reroute tickets between queues, and do not want to set [[AdminCc]] on all those queues. Scrip<br />
<br />
Description: AddAdminCC<br />
Condition: On Create<br />
Action: User defined<br />
Template: Global template: blank<br />
Stage: TransactionCreate<br />
Custom Condition:<br />
Custom action preparation code: return 1;<br />
Custom action cleanup code:<br />
my $admincclist = $self-&gt;TicketObj-&gt;AdminCc;<br />
<br />
my $user = RT::User-&gt;new($RT::SystemUser);<br />
$user-&gt;LoadByEmail('somebody@somewhere.com');<br />
<br />
$admincclist-&gt;AddMember($user-&gt;Id);<br />
<br />
<br />
Modification<br />
<br />
Modification: Adding someone as an [[AdminCc]] for a ticket if it moves from a specific queue to another specific queue. - By Mike Patterson. Help From Stephen Turner on mailing list.<br />
<br />
If a ticket moves from our "web" queue (#5) to our "systems" queue (#10) I want "[mailto:myuser@myuniversity.edu myuser@myuniversity.edu]" to become an [[AdminCc]] for that ticket. I create this scrip on the queue I'm moving the ticket into. Scrip<br />
<br />
Description: AddAdminCCIfFromWeb<br />
Condition: On Queue Change<br />
Action: User defined<br />
Template: Global template: blank<br />
Stage: TransactionCreate<br />
Custom Condition:<br />
Custom action preparation code: return 1;<br />
Custom action cleanup code:<br />
<pre><br />
my $admincclist = $self-&gt;TicketObj-&gt;AdminCc;<br />
my $user = RT::User-&gt;new($RT::SystemUser);<br />
if ($self-&gt;TransactionObj-&gt;Type eq "Set"<br />
&amp;&amp; $self-&gt;TransactionObj-&gt;Field eq "Queue"<br />
&amp;&amp; $self-&gt;TransactionObj-&gt;OldValue eq "5"<br />
) {<br />
$user-&gt;LoadByEmail('!myuser@myuniversity.edu');<br />
$admincclist-&gt;AddMember($user-&gt;Id);<br />
} else {<br />
return 0;<br />
}<br />
</pre><br />
<br />
A More Refined Version of That Idea<br />
<br />
You can also use the verbosely-named RT::Extension::[[AddAdminCcsOnQueueChange]] (from CPAN) to do exactly what its name implies – add all the [[AdminCcs]] on the current queue to the ticket when it moves to a new queue. Another Modification<br />
<br />
Modification: If you have many, weird aliases for a single Queue, RT sometimes adds those aliases to the CC of the ticket. Then when you correspond, it "bounces" because it trys to send to itself (the requester never sees this though). This scrip will remove these known aliases from the CC of a ticket Scrip<br />
<br />
Description: DeleteCC<br />
Condition: On Create<br />
Action: User defined<br />
Template: Global template: blank<br />
Stage: TransactionCreate<br />
Custom Condition:<br />
Custom action preparation code: return 1;<br />
Custom action cleanup code:<br />
<br />
$self-&gt;TicketObj-&gt;DeleteWatcher(Type=&gt;"Cc",Email=&gt;"somethingreallyweird\@domain.com");<br />
<br />
Another Modification<br />
<br />
by [[JFenal]].<br />
<br />
If you have a workflow between queues (Unix-&gt;Storage and back for instance), and having differents groups having rights on their only queues, you may allow ticket owners to still see the ticket they transfered by automatically adding them as [[AdminCc]] to the ticket on queue change.<br />
<br />
You will need then to set global rights for [[AdminCc]] role such as [[ShowTicket]], and maybe more, depending of your own setup : Scrip<br />
<br />
Description: AddAsAdminCcOnQueueChange<br />
Condition: On Queue Change<br />
Action: User defined<br />
Template: Global template: blank<br />
Stage: TransactionCreate<br />
Custom Condition:<br />
Custom action preparation code: return 1;<br />
Custom action cleanup code:<br />
<br />
<pre> <br />
my $owner = $self-&gt;TicketObj-&gt;Owner;<br />
return 1 if $owner == $RT::Nobody-&gt;id;<br />
my $admincclist = $self-&gt;TicketObj-&gt;AdminCc;<br />
<br />
if ($self-&gt;TransactionObj-&gt;Type eq "Set"<br />
&amp;&amp; $self-&gt;TransactionObj-&gt;Field eq "Queue"<br />
&amp;&amp; $self-&gt;TransactionObj-&gt;OldValue eq "8" # 8: Storage queue -&gt; any queue<br />
) {<br />
my ($status, $msg) = $admincclist-&gt;AddMember($owner);<br />
unless( $status ) {<br />
$RT::Logger-&gt;warning( "can't add ticket owner as AdminCc : $msg" );<br />
return undef;<br />
}<br />
}<br />
else {<br />
return 0;<br />
}<br />
<br />
</pre><br />
<br />
You need to set this scrip as a global scrip, as [[OnQueueChange]] scrips on a queue B are executed when entering B from A, not leaving queue B. Thus, if you need to add [[AdminCc]] when going to more than one queue, you would need to set this scrip on many queue. A mess to administer... Here, you could manage that only by testing $self-&gt;[[TransactionObj]]-&gt;[[OldValue]] and $self-&gt;[[TransactionObj]]-&gt;Value and take appropriate action. Another Modification<br />
<br />
by Riccardo Capecchi<br />
<br />
Small change from the idea of the former, on queue change we needed to set all old watchers of the old queue as new [[AdminCc]] for the ticket, so they can follow the ticket also in different queue where they have no rights.<br />
<br />
You will need then to set global rights for [[AdminCc]] role such as [[ShowTicket]], and maybe more, depending of your own setup : Scrip<br />
<br />
Description: AddAsAdminCcOnQueueChange<br />
Condition: On Queue Change<br />
Action: User defined<br />
Template: Global template: blank<br />
Stage: TransactionCreate<br />
Custom Condition:<br />
Custom action preparation code: return 1;<br />
Custom action cleanup code:<br />
my $OldQueueID = $self-&gt;TransactionObj-&gt;OldValue;<br />
my $OldQueue = new RT::Queue($RT::SystemUser);<br />
$OldQueue-&gt;Load($OldQueueID);<br />
my @oldadmincclist = $OldQueue-&gt;AdminCc-&gt;MemberEmailAddresses;<br />
foreach my $addr (@oldadmincclist) {<br />
my ($success, $msg)= $self-&gt;TicketObj-&gt;AddWatcher(<br />
Type =&gt; "AdminCc",<br />
Email =&gt; $addr);<br />
if (! $success) {<br />
$RT::Logger-&gt;info($msg);<br />
return undef;<br />
}<br />
}<br />
return 1;<br />
<br />
<br />
You can set this as global, then will work for any change of queue or set as scrip for only some queue, it depend on your workflow..</div>Tharnhttps://rt-wiki.bestpractical.com/index.php?title=OnToOrCC&diff=26368OnToOrCC2016-08-17T21:11:10Z<p>Tharn: </p>
<hr />
<div>'''Purpose:''' We have multiple mailboxes to accept requests to a single RT system. There are certain scripts that I want to execute for some mailboxes but not others. For example we have a paging scrip for off hours but don't want to page for a certain mailbox.<br />
<br />
This condition scrip searches the headers for a particular email account in the TO or CC line of the email. If it finds a match it exits out and does not execute. Of course you can flip the result bit so it does do something.<br />
<br />
'''Author:''' macnlos AT comcast DOT net - I'm on the RT Users List<br />
<br />
'''Condition:''' User Defined<br />
<br />
'''Custom Condition:'''<br />
<br />
<pre><br />
# Parse out if the email was sent to Clarity and exclude<br />
my $ContentObj = $self-&gt;TransactionObj-&gt;Attachments-&gt;First();<br />
my $Headers = $ContentObj-&gt;{values}{headers};<br />
if ($Headers =~ /^To: .*ACCOUNT.*/im) {<br />
return 0;<br />
}<br />
if ($Headers =~ /^CC: .*ACCOUNT.*/im) {<br />
return 0;<br />
}<br />
<br />
return 1;<br />
</pre><br />
<br />
'''Code Explanation:''' This condition grabs the first attachment and gets the header block. It then searches through the headers looking for a line that starts with "To:" or "CC:" and then for a particular ACCOUNT.<br />
<br />
The /im on the regex makes it case-insensitive and treats each line in the multi-line block as and individual line. If you don't have the /m then the ^ anchor will only go on the very first line.</div>Tharnhttps://rt-wiki.bestpractical.com/index.php?title=AutoreplyOrCorrespondence&diff=26367AutoreplyOrCorrespondence2016-08-16T12:53:18Z<p>Tharn: </p>
<hr />
<div>This template code checks to see if the creator is the same as one of the requestors on ticket creation. If so it loads and processes the Autoreply template searching first in the current queue and if there isn't one there resorting to the global version.<br />
<br />
If the requestor is not the creator then it processes the Correspondence template instead doing the same search as above.<br />
<br />
The effect of using this template is that if a ticket is created on behalf of a customer they receive the message you create (on ticket creation) as if it is a reply rather than the default cryptic auto-reply.<br />
<br />
It assumes there are at least global templates 'Autoreply' and 'Correspondence'.<br />
<br />
<br />
<br />
<pre><br />
{<br />
use RT::Template;<br />
my $creator_name = $Ticket->CreatorObj->Name;<br />
my $requestors = $Ticket->Requestors->UserMembersObj;<br />
my ( $c_requestor, $r_count ) = ( 0, 0 );<br />
while ( my $r = $requestors->Next() ) {<br />
if ( $r->Name eq $creator_name ) {<br />
$c_requestor++;<br />
}<br />
else {<br />
$r_count++;<br />
}<br />
}<br />
my $template = new RT::Template($RT::SystemUser);<br />
my $template_name;<br />
<br />
#if the creator is not a requestor or <br />
#there is more than one requestor ( who's not the creator )<br />
#use the Correspondence template<br />
if ( ! $c_requestor || $r_count ) {<br />
$template_name = 'Correspondence';<br />
}<br />
else {<br />
#otherwise use the Autoreply template<br />
$template_name = 'Autoreply';<br />
}<br />
<br />
#Load the Queue Template<br />
$template->LoadQueueTemplate( <br />
Queue => $Ticket->Queue, <br />
Name => $template_name,<br />
);<br />
#if there's no Queue Template attempt to find a Global one.<br />
unless ( $template->id ) {<br />
$template->LoadGlobalTemplate( $template_name );<br />
unless ( $template->id ) { <br />
$RT::Logger->error("Could not load template : $template_name")<br />
}<br />
}<br />
#Process embedded fields & expressions of true templates;<br />
#note that we can only meaningfully use the body<br />
my($ret, $msg) = $template->Parse(<br />
TicketObj => $Ticket, <br />
TransactionObj => $Transaction,<br />
);<br />
$ret ? $template->MIMEObj->stringify : $msg;<br />
}<br />
</pre></div>Tharnhttps://rt-wiki.bestpractical.com/index.php?title=CustomConditionSnippets&diff=26362CustomConditionSnippets2016-08-14T19:56:13Z<p>Tharn: /* On Status Change to "resolved" */</p>
<hr />
<div>= Introduction =<br />
<br />
This page introduce you with some code that can be used in [[Condition]]s and is part of [[CodeSnippets]] series of articles.<br />
<br />
= Custom conditions specifics =<br />
<br />
Read [[WriteCustomCondition]] to understand basics of writing conditions.<br />
<br />
= Code Snippets =<br />
<br />
Here is list of various custom conditions you can use.<br />
<br />
== On Create and variants ==<br />
<br />
return 0 unless $self->TransactionObj->Type eq "Create";<br />
return 1;<br />
<br />
=== On Create, but queue is not 'xxx' ===<br />
<br />
<nowiki><br />
return 0 unless $self->TransactionObj->Type eq "Create";<br />
return 0 if $self-&gt;TicketObj-&gt;QueueObj-&gt;Name eq "xxx";<br />
return 1;<br />
</nowiki><br />
<br />
=== On Create with status resolved ===<br />
<br />
If you create [[Ticket]] with status resolved then standard RT condition OnResolve wouldn't trigger.<br />
<br />
return 0 unless $self-&gt;TransactionObj-&gt;Type eq "Create";<br />
return 0 unless $self-&gt;TicketObj-&gt;Status eq 'resolved';<br />
return 1;<br />
<br />
== On Correspond/Comment and variants ==<br />
<br />
return 0 unless $self-&gt;TransactionObj-&gt;Type eq "Correspond";<br />
return 1;<br />
<br />
=== On Correspond to Unowned Ticket ===<br />
<br />
return 0 unless $self-&gt;TransactionObj-&gt;Type eq "Correspond";<br />
return 0 unless $self-&gt;TicketObj-&gt;Owner == $RT::Nobody-&gt;id;<br />
return 1;<br />
<br />
Can be used in "On Correspond to Unowned Notify Admin Ccs With Admin Correspondence".<br />
<br />
== On Status Change and variants ==<br />
<br />
For historical reasons it's more correct to write this condition like this:<br />
<br />
my $txn = $self-&gt;TransactionObj;<br />
my $type = $txn-&gt;Type;<br />
return 0 unless $type eq "Status"<br />
|| ( $type eq 'Set' && $txn->Field eq 'Status');<br />
return 1;<br />
<br />
=== On Status Change to "resolved" ===<br />
<pre><br />
my $txn = $self-&gt;TransactionObj;<br />
my $type = $txn-&gt;Type;<br />
return 0 unless $type eq "Status"<br />
|| ( $type eq 'Set' && $txn->Field eq 'Status');<br />
<br />
return 0 unless $txn-&gt;NewValue eq "resolved";<br />
return 1;<br />
</pre><br />
<br />
=== on Status Change from "new" to "open" ===<br />
<br />
my $txn = $self-&gt;TransactionObj;<br />
my $type = $txn-&gt;Type;<br />
return 0 unless $type eq "Status"<br />
|| ( $type eq 'Set' && $txn->Field eq 'Status');<br />
<br />
return 0 unless $txn-&gt;OldValue eq "new";<br />
return 0 unless $txn-&gt;NewValue eq "open";<br />
return 1;<br />
<br />
== On Queue Change ==<br />
<br />
return 0 unless $self-&gt;TransactionObj-&gt;Type eq "Set";<br />
return 0 unless $self-&gt;TransactionObj-&gt;Field eq "Queue";<br />
return 1;<br />
<br />
== On Owner Change and variants ==<br />
<br />
return 0 unless $self-&gt;TransactionObj-&gt;Type eq "Set";<br />
return 0 unless $self-&gt;TransactionObj-&gt;Field eq "Owner";<br />
return 1;<br />
<br />
=== On Owner Change from Nobody to Any ===<br />
<br />
my $txn = $self-&gt;TransactionObj;<br />
return 0 unless $txn-&gt;Type eq "Set";<br />
return 0 unless $txn-&gt;Field eq "Owner";<br />
return 0 unless $txn-&gt;OldValue == $RT::Nobody-&gt;id;<br />
return 1;<br />
<br />
=== On Take ===<br />
<br />
my $txn = $self-&gt;TransactionObj;<br />
return 0 unless $txn-&gt;Type eq "Set";<br />
return 0 unless $txn-&gt;Field eq "Owner";<br />
return 0 unless $txn-&gt;OldValue == $RT::Nobody-&gt;id;<br />
return 0 unless $txn-&gt;NewValue == $txn-&gt;Creator;<br />
return 1;<br />
<br />
=== On Steal ===<br />
<br />
See also [[OnStealEnhanced]]<br />
<br />
my $txn = $self-&gt;TransactionObj;<br />
return 0 unless $txn-&gt;Type eq "Set";<br />
return 0 unless $txn-&gt;Field eq "Owner";<br />
return 0 if $txn-&gt;OldValue == $RT::Nobody-&gt;id;<br />
return 0 unless $txn-&gt;NewValue == $txn-&gt;Creator;<br />
return 1;<br />
<br />
=== On Give ===<br />
<br />
my $txn = $self-&gt;TransactionObj;<br />
return 0 unless $txn-&gt;Type eq "Set";<br />
return 0 unless $txn-&gt;Field eq "Owner";<br />
return 0 unless $txn-&gt;OldValue == $RT::Nobody-&gt;id;<br />
return 0 if $txn-&gt;NewValue == $txn-&gt;Creator;<br />
return 1;<br />
<br />
=== On Give Up ===<br />
<br />
my $txn = $self-&gt;TransactionObj;<br />
return 0 unless $txn-&gt;Type eq "Set";<br />
return 0 unless $txn-&gt;Field eq "Owner";<br />
return 0 unless $txn-&gt;OldValue == $txn-&gt;Creator;<br />
return 0 unless $txn-&gt;NewValue == $RT::Nobody-&gt;id;<br />
return 1;<br />
<br />
=== On Assign ===<br />
<br />
my $txn = $self-&gt;TransactionObj;<br />
return 0 unless $txn-&gt;Type eq "Set";<br />
return 0 unless $txn-&gt;Field eq "Owner";<br />
return 0 unless $txn-&gt;OldValue == $RT::Nobody-&gt;id;<br />
return 0 if $txn-&gt;NewValue == $txn-&gt;Creator;<br />
return 1;<br />
<br />
=== On Re-Assign ===<br />
<br />
my $txn = $self-&gt;TransactionObj;<br />
return 0 unless $txn-&gt;Type eq "Set";<br />
return 0 unless $txn-&gt;Field eq "Owner";<br />
return 0 if $txn-&gt;OldValue == $txn-&gt;Creator;<br />
return 0 if $txn-&gt;NewValue == $txn-&gt;Creator;<br />
return 1;<br />
<br />
=== Checks with acting user (actor) ===<br />
<br />
my $actor = $self-&gt;TransactionObj-&gt;CreatorObj;<br />
<br />
=== On action by privileged ===<br />
<br />
my $actor = $self-&gt;TransactionObj-&gt;CreatorObj;<br />
return 1 if $actor-&gt;Privileged;<br />
return 0;<br />
<br />
=== On owner's action ===<br />
<br />
my $actor = $self-&gt;TransactionObj-&gt;CreatorObj;<br />
my $owner = $self-&gt;TicketObj-&gt;OwnerObj;<br />
return 1 unless $actor-&gt;id == $owner->id;<br />
return 0;<br />
<br />
== Checks with watchers and groups ==<br />
<br />
=== When particular email address is in requestors ===<br />
<br />
return 1 if $self-&gt;TicketObj-&gt;IsWatcher(<br />
Type =&gt; 'Requestor', Email =&gt; 'foo@bar.com'<br />
);<br />
return 0;<br />
<br />
=== When actor is member of one or more particular groups ===<br />
<br />
my @enforced_groups = ("Group 1","Group 2","etc");<br />
<br />
my $actor = $self-&gt;TransactionObj-&gt;CreatorObj;<br />
<br />
foreach my $gname (@enforced_groups) {<br />
my $group = RT::Group-&gt;new( $self->CurrentUser );<br />
$group-&gt;LoadUserDefinedGroup( $gname );<br />
next unless $group->id;<br />
<br />
return 1 if $group->HasMemberRecursively( $actor->id );<br />
}<br />
return 0;<br />
<br />
=== Owner is not in the group ===<br />
<br />
my $in_group = 'SupportManagers';<br />
<br />
my $owner = $self-&gt;TicketObj-&gt;OwnerObj;<br />
my $group = RT::Group-&gt;new( $self->CurrentUser );<br />
$group->LoadUserDefinedGroup( $in_group );<br />
return 0 if $group-&gt;HasMemberRecursively( $owner->id );<br />
<br />
return 1;<br />
<br />
== Checks with Custom Fields ==<br />
<br />
=== When current custom field value is X ===<br />
<br />
<nowiki><br />
my $Ticket = $self-&gt;TicketObj;<br />
<br />
return 0 unless $self->TicketObj-&gt;FirstCustomFieldValue('CustomFieldName') eq 'X';<br />
return 1;<br />
</nowiki><br />
<br />
=== On CustomField change ===<br />
<br />
return 0 unless $self->TransactionObj->Type eq 'CustomField';<br />
return 1;<br />
<br />
=== On CustomField 'X' change ===<br />
<br />
return 0 unless $self->TransactionObj->Type eq 'CustomField';<br />
<br />
my $cf = RT::CustomField->new( $self->CurrentUser );<br />
$cf->Load( $self->TransactionObj->Field );<br />
return 0 unless $cf->Name eq 'X';<br />
<br />
return 1;<br />
<br />
=== On CustomField 'X' change from 'A' to 'B' ===<br />
<br />
my $txn = $self->TransactionObj;<br />
return 0 unless $txn->Type eq 'CustomField';<br />
<br />
my $cf = RT::CustomField->new( $self->CurrentUser );<br />
$cf->Load( $txn->Field );<br />
return 0 unless $cf->Name eq 'X';<br />
<br />
return 0 unless $txn->OldValue eq 'A';<br />
return 0 unless $txn->NewValue eq 'B';<br />
<br />
return 1;<br />
<br />
=== When Custom Field 'X' is set to 'A' ===<br />
<br />
my $txn = $self->TransactionObj;<br />
my $type = $txn->Type;<br />
if ( $type eq 'Create' ) {<br />
# CF can be set on create<br />
return 0 unless $self->TicketObj->FirstCustomFieldValue('X') eq 'A';<br />
} elsif ( $type eq 'CustomField' ) {<br />
# CF can be changed later<br />
my $cf = RT::CustomField->new( $self->CurrentUser );<br />
$cf->Load( $txn->Field );<br />
return 0 unless $cf->Name eq 'X';<br />
<br />
return 0 unless $txn->NewValue eq 'A';<br />
} else {<br />
return 0;<br />
}<br />
return 1;<br />
<br />
=== On Correspond, set Custom Field "X" to "0" ===<br />
<br />
return 1;<br />
<br />
my $ticket = $self->TicketObj;<br />
my $cf_obj = RT::CustomField->new($RT::SystemUser);<br />
my $cf_name = "X";<br />
my $cf_value = "0";<br />
<br />
$cf_obj->LoadByName( Name => $cf_name );<br />
$RT::Logger->debug( "Loaded \$cf_obj->Name = ". $cf_obj->Name() ."\n" );<br />
$ticket->AddCustomFieldValue( Field=>$cf_obj, Value=>$cf_value,<br />
RecordTransaction=>0 );<br />
<br />
return 1;<br />
<br />
== UserDefined2 (use with rt-crontool) ==<br />
create a file UserDefined2.pm in lib/RT/Condition<br />
package RT::Condition::UserDefined2;<br />
use base 'RT::Condition';<br />
use strict;<br />
=head2 IsApplicable<br />
<br />
=cut<br />
<br />
sub IsApplicable {<br />
my $self = shift;<br />
local $@;<br />
my $retval = eval { $self->Argument; }; warn $@ if $@;<br />
return ($retval);<br />
}<br />
<br />
RT::Base->_ImportOverlays();<br />
1;<br />
With this you have a powerful way to check a customized condition. You can use it like that:<br />
bin/rt-crontool --verbose --log debug --search RT::Search::ActiveTicketsInQueue --search-arg Support --condition RT::Condition::UserDefined2 --condition-arg "if($self->TicketObj->Priority == 4){return(1);}else {return(undef);}" --action RT::Action::SetPriority --action-arg 7<br />
This example searches all tickets in queue Support, checks if the priority is 4 and sets the priority to 7. You may use arbitrary perl-code for the condition.<br />
== add your own conditions ==<br />
<br />
...</div>Tharnhttps://rt-wiki.bestpractical.com/index.php?title=AddWatcherPerTicket&diff=26361AddWatcherPerTicket2016-08-14T19:47:13Z<p>Tharn: </p>
<hr />
<div>You can use this [[Scrip]] to add a [[Watcher]] to a particular ticket. One possible use would be to have tickets of a particular urgency (as in the custom condition below) forwarded to an 'emergency' email address, or to someone's phone via an email-SMS gateway.<br />
<br />
Note that you need to set up two scrips for this: the first to add the extra Watcher, and the second to send the email. This is because of the order in which RT runs through its automatic &amp; user-defined actions.<br />
<br />
'''First scrip'''<br />
<br />
Condition: User Defined<br />
<br />
Custom Condition:<br />
<br />
<pre><br />
return undef unless ( ($self-&gt;TransactionObj-&gt;Type eq "Create") ||<br />
( $self-&gt;TicketObj-&gt;FirstCustomFieldValue('Urgency') eq "High"));<br />
return 1;<br />
</pre><br />
<br />
Action: User Defined<br />
<br />
Custom action preparation code:<br />
<br />
<pre><br />
$self-&gt;TicketObj-&gt;AddWatcher( Type =&gt; "Cc",<br />
Email =&gt;"address\@email.com");<br />
push( @{$self-&gt;{'To'}},<br />
$self-&gt;TicketObj-&gt;Requestors-&gt;MemberEmailAddresses,<br />
$self-&gt;TicketObj-&gt;QueueObj-&gt;Cc-&gt;MemberEmailAddresses );<br />
return 1;<br />
</pre><br />
<br />
Custom action cleanup code:<br />
<br />
1;<br />
<br />
<br />
Template: Global Template: Transaction<br />
<br />
'''Second scrip'''<br />
<br />
Condition: On Create<br />
<br />
Action: Notify Ccs<br />
<br />
Template: Global Template: Transaction<br />
<br />
* I had problems with scrip order to get this to work. The Notify scrip has to be executed after the Add CC scrip. See [[ScripExecOrder]]<br />
<br />
See also: [[ManualScrips]], [[WriteCustomAction]], [[WriteCustomCondition]]</div>Tharnhttps://rt-wiki.bestpractical.com/index.php?title=AddLastCommentToMail&diff=26360AddLastCommentToMail2016-08-14T19:30:26Z<p>Tharn: </p>
<hr />
<div>This code adds last comment to mail. Add code into template.<br />
<pre><br />
{<br />
my $Transactions = $Ticket-&gt;Transactions;<br />
$Transactions-&gt;Limit( FIELD =&gt; 'Type', VALUE =&gt; 'Comment' );<br />
$Transactions-&gt;OrderByCols (<br />
{ FIELD =&gt; 'Created', ORDER =&gt; 'DESC' },<br />
{ FIELD =&gt; 'id', ORDER =&gt; 'DESC' },<br />
);<br />
<br />
my $resolution_comment;<br />
my $CommentObj = $Transactions-&gt;First;<br />
if( $CommentObj &amp;&amp; $CommentObj-&gt;id ) {<br />
$resolution_comment = $CommentObj-&gt;Content;<br />
}<br />
<br />
$resolution_comment;<br />
}<br />
<br />
</pre><br />
<br />
NOTE: This doesn't work for "Update all tickets at once." Reported to list 8/20/2004, no acknowledgement or action or solution posted.<br />
<br />
RT v3.4.5 - Showed the first comment for me maybe cos of $Transactions-&gt;First;<br />
<br />
* Method <code>First</code> returns the first object in a collection according to order of the collection. As you can see we order by the 'created' field with most recent comment on the top.<br />
* Ask question and post such comments to the [[MailingLists]], wiki is not a discussion forum! This comment would be deleted soon.<br />
<br />
----<br />
<br />
This will set the email address and subject<br />
<br />
----<br />
<br />
To: {$Ticket-&gt;Transactions-&gt;First-&gt;Message-&gt;First-&gt;[[GetHeader]]('To');}<br />
<br />
Subject: Resolved: {$Ticket-&gt;Subject}</div>Tharnhttps://rt-wiki.bestpractical.com/index.php?title=OpenTicketOnAllMemberResolve&diff=26359OpenTicketOnAllMemberResolve2016-08-14T01:19:19Z<p>Tharn: </p>
<hr />
<div>Another [[ScripAction]] from [[RuslanZakirov]]:<br />
<br />
Description: open tickets which has link on resolved ticket as member if all members resoved<br />
<br />
Condition: On Status Change<br />
<br />
Custom Condition:<br />
<br />
Template: Global template: Blank<br />
<br />
Action: User defined<br />
<br />
Custom action preparation code: return 1;<br />
<br />
Custom action cleanup code:<br />
<br />
<pre><br />
return 1 if ($self-&gt;TransactionObj-&gt;NewValue !~ /^(?:resolved|deleted|rejected)$/);<br />
# current ticket is memeber of(child of some parents)<br />
my $MemberOf = $self-&gt;TicketObj-&gt;MemberOf;<br />
while( my $l = $MemberOf-&gt;Next ) {<br />
# we can't check non local objects<br />
next unless( $l-&gt;TargetURI-&gt;IsLocal );<br />
# if parent ticket is not in active state then scrip can skip it<br />
next unless( $l-&gt;TargetObj-&gt;Status =~ /^(?:new|open|stalled)$/ );<br />
<br />
# the parent ticket has members(current ticket is one of them)<br />
my $ms = $l-&gt;TargetObj-&gt;Members();<br />
<br />
my $flag = 0;<br />
while( my $m = $ms-&gt;Next ) {<br />
next unless( $m-&gt;BaseURI-&gt;IsLocal );<br />
next unless( $m-&gt;BaseObj-&gt;Status =~ /^(?:new|open|stalled)$/ );<br />
$flag = 1;<br />
last;<br />
}<br />
# shouldn't open parent if some children is active<br />
next if( $flag );<br />
<br />
# Uncomment this string if you want open ticket<br />
# and comment next code down to END word<br />
# $l-&gt;TargetObj-&gt;SetStatus('open');<br />
<br />
# This code adds comment to the ticket<br />
my $id = $self-&gt;TicketObj-&gt;id;<br />
my $status = $self-&gt;TicketObj-&gt;Status;<br />
$l-&gt;TargetObj-&gt;Comment(Content =&gt; &lt;&lt;END);<br />
<br />
Last member(\#$id) of this ticket was $status.<br />
END<br />
# end of code that adds comment<br />
<br />
}<br />
return 1;<br />
</pre><br />
<br />
705617733494428679213672_test_num_</div>Tharnhttps://rt-wiki.bestpractical.com/index.php?title=OnQueueChangeFixReminders&diff=26358OnQueueChangeFixReminders2016-08-14T01:18:04Z<p>Tharn: </p>
<hr />
<div>This is a strange bug as it seems like reminders just lose their permission to be deleted. Actually what is happening is the reminder ticket objects are not being moved along with the tickets.<br />
<br />
(confirmed by this post http://lists.bestpractical.com/pipermail/rt-users/2008-January/049480.html )<br />
<br />
Edit: [[MarkR]] reports that this fix does not seem to be necessary in RT 3.8.x<br />
<br />
* <nowiki>Description: ''Fix reminders on queue change'' </nowiki><br />
* <nowiki>Condition: ''On Queue Change'' </nowiki><br />
* <nowiki>Action: ''User Defined'' </nowiki><br />
* <nowiki>Template: ''Global template: Blank'' </nowiki><br />
* <nowiki>Stage: ''</nowiki>[[TransactionCreate]]<nowiki>'' </nowiki><br />
<br />
* Custom condition: (blank)<br />
* Custom action preparation code: (blank)<br />
* Custom action cleanup code:<br />
<br />
<pre><br />
my $tickets = new RT::Tickets(RT-&gt;SystemUser)<br />
my $id = $self-&gt;TicketObj-&gt;id;<br />
my $queue = $self-&gt;TicketObj-&gt;QueueObj-&gt;Name;<br />
<br />
return 1 unless (defined($id)); # Can this be undefined?<br />
<br />
$tickets-&gt;FromSQL('Type = "reminder" AND RefersTo="'.$id.'"');<br />
while (my $ticket = $tickets-&gt;Next) {<br />
$RT::Logger-&gt;info("Moving associated reminder ticket '".$ticket-&gt;id."' to queue '$queue'");<br />
$ticket-&gt;SetQueue($queue);<br />
}<br />
<br />
return 1;<br />
<br />
</pre></div>Tharnhttps://rt-wiki.bestpractical.com/index.php?title=OnCreateSetDeptHeadCc&diff=26357OnCreateSetDeptHeadCc2016-08-14T01:17:15Z<p>Tharn: </p>
<hr />
<div>= Overview =<br />
<br />
Dept heads need to see and reply to tickets opened by their staff. This is accomplished by giving the global 'Cc' group [[ShowTicket]] perms and automatically making the dept head a Cc on the ticket.<br />
<br />
On create in the case that [[CustomField]].Department = 'Foo' then add group 'Head Foo' as a Cc. Be sure to first check that the Requestor is not in the 'Head Foo' group. That may be problematic for odd departments or units with more than one member in a 'Head x' group. (See also [[OnCreateAddGroupCc]].)<br />
<br />
= Scrip =<br />
<br />
All tickets have a Department custom field mapping the ticket to a dept. I globally grant the Cc role [[ShowTicket]] and [[ReplyToTicket]] permission and then add the department head as Cc on their staff's tickets. This way they can add comments and be aware of issues in their department. I do not yet wish to give them powers such as [[ModifyTicket]], so they can't monkey with priorities etc. Therefore, I felt Cc more appropriate than [[AdminCc]] even after granting extra rights to Cc.<br />
<br />
* <nowiki>Description: ''</nowiki>[[OnCreateSetDeptHeadCc]]<nowiki>'' </nowiki><br />
* <nowiki>Condition: ''User Defined'' </nowiki><br />
* <nowiki>Action: ''User Defined'' </nowiki><br />
* <nowiki>Template: ''Global template: Blank'' </nowiki><br />
* <nowiki>Stage: ''</nowiki>[[TransactionCreate]]<nowiki>'' </nowiki><br />
<br />
* Custom Condition:<br />
<br />
<pre><br />
# make sure we are called from a on create condition and<br />
# make sure department is filled out<br />
if (<br />
(<br />
($self-&gt;TransactionObj-&gt;Type eq "Create")<br />
|| ($self-&gt;TransactionObj-&gt;Type eq "CustomField")<br />
) &amp;&amp; (<br />
$self-&gt;TicketObj-&gt;FirstCustomFieldValue('Department')<br />
&amp;&amp; ($self-&gt;TicketObj-&gt;FirstCustomFieldValue('Department') ne 'None')<br />
)<br />
) {<br />
#$RT::Logger-&gt;info("Met User Defined OnCreateSetDeptHeadCc Condition #".<br />
# $self-&gt;TicketObj-&gt;id ." ".<br />
# $self-&gt;TicketObj-&gt;FirstCustomFieldValue('Department') );<br />
return 1;<br />
} else {<br />
#$RT::Logger-&gt;info("Did Not Meet User Defined OnCreateSetDeptHeadCc Condition #".<br />
# $self-&gt;TicketObj-&gt;id ." ". $self-&gt;TicketObj-&gt;FirstCustomFieldValue('Department') );<br />
return undef;<br />
}<br />
<br />
</pre><br />
<br />
* Custom action preparation code:<br />
<br />
<pre><br />
# example: for 'Systems' the group is 'Head Systems'<br />
my $derivedGroupName = 'Head ' . $self-&gt;TicketObj-&gt;FirstCustomFieldValue('Department');<br />
<br />
# instantiated a group object<br />
my $groupObj = RT::Group-&gt;new($RT::SystemUser);<br />
$groupObj-&gt;LoadUserDefinedGroup($derivedGroupName);<br />
return undef unless $groupObj;<br />
<br />
# one doesn't want to be a requestor and a Cc on the same ticket.<br />
# don't add group as CC if requestor is in that group<br />
# this could certainly be a problem if there are multiple members in a group<br />
my $requestorsGroupObj = $self-&gt;TicketObj-&gt;Requestors;<br />
# UMO grabs group members in subgroups too it is recursive<br />
my $requestorsMembersObj = $requestorsGroupObj-&gt;UserMembersObj;<br />
<br />
my $userObj;<br />
while ($userObj = $requestorsMembersObj-&gt;Next) {<br />
if ($groupObj-&gt;HasMember($userObj-&gt;PrincipalObj)) {<br />
$RT::Logger-&gt;info("Requestor '" . $userObj-&gt;Name .<br />
"' is in group '$derivedGroupName' not adding Cc on ticket #" .<br />
$self-&gt;TicketObj-&gt;id );<br />
<br />
return undef;<br />
}<br />
}<br />
<br />
# add group as Cc on ticket. see Ticket_Overlay.pm<br />
$RT::Logger-&gt;info("Add group '$derivedGroupName' as Cc on ticket #" .<br />
$self-&gt;TicketObj-&gt;id );<br />
my ($success, $msg)= $self-&gt;TicketObj-&gt;AddWatcher(<br />
Type =&gt; "Cc",<br />
PrincipalId =&gt; $groupObj-&gt;PrincipalId);<br />
<br />
if (! $success) {<br />
$RT::Logger-&gt;info($msg);<br />
return undef;<br />
} else {<br />
return 1;<br />
}<br />
</pre><br />
<br />
* Custom action cleanup code:<br />
<br />
<nowiki># blank<br />
<br />
</nowiki><br />
<br />
== Include CC Tickets in SelfService ==<br />
<br />
After the Department head has been made CC on a ticket they need an easy way to view all such tickets. So, let's modify [[SelfService]] to show them.<br />
<br />
Put the pieces in place:<br />
<br />
[root@lakshmi Elements]# mkdir -p /usr/local/rt/html/SelfService/Elements<br />
[root@lakshmi Elements]# cp MyRequests /usr/local/rt/html/SelfService/Elements<br />
<br />
<br />
Make one small change.<br />
<br />
[root@lakshmi Elements]# diff -u MyRequests /usr/local/rt/html/SelfService/Elements/MyRequests<br />
--- MyRequests 2005-02-01 06:20:40.000000000 -0800<br />
+++ /usr/local/rt/html/SelfService/Elements/MyRequests 2006-02-27 11:00:31.000000000 -0800<br />
@@ -70,7 +70,7 @@<br />
$title ||= loc("My [_1] tickets", $friendly_status);<br />
my $MyTickets;<br />
$MyTickets = new RT::Tickets ($session{'CurrentUser'});<br />
-$MyTickets-&gt;LimitWatcher(TYPE =&gt; 'Requestor', VALUE =&gt; $session{'CurrentUser'}-&gt;EmailAddress);<br />
+$MyTickets-&gt;LimitWatcher(VALUE =&gt; $session{'CurrentUser'}-&gt;EmailAddress);<br />
$MyTickets-&gt;OrderBy(FIELD =&gt; 'id', ORDER =&gt; 'ASC');<br />
<br />
foreach my $status (@status) {<br />
<br />
<br />
= Bugs in Solution =<br />
<br />
* If there is more than one user in a 'Head X' group and one user opens a ticket, the other user will not become a Cc.<br />
<br />
This would in an issue in departments with more than one "head". We can worry about that later. (Also see [[OnCreateAddGroupCc]].)<br />
<br />
* If the Department changes during the life of the ticket, the new Cc will be added, but the old Cc will not be removed.<br />
<br />
* How can send the [[AutoReply]] to the department head as well as the requestor?</div>Tharnhttps://rt-wiki.bestpractical.com/index.php?title=DivideTicketIntoSubtasks&diff=26356DivideTicketIntoSubtasks2016-08-14T01:14:59Z<p>Tharn: </p>
<hr />
<div>[[HomePage]] &gt; [[Contributions]] &gt; [[DivideTicketIntoSubtasks]]<br />
<br />
= DivideTicketIntoSubtasks =<br />
<br />
Often, we have a whole list of related tasks, and wish to create separate tickets for them all at once, perhaps for different people to work on. With these scrips You can do so through the use of bulleted list in the body of the initial ticket.<br />
<br />
== Original ==<br />
<br />
This scrip action creates tickets for each list item for each task the main ticket [[DependsOn]]. It handles lists with dash or asterisk bullets:<br />
<br />
<pre><br />
* thingy 1<br />
- thingy 2 <br />
</pre><br />
<br />
=== Scrip Fields ===<br />
<br />
Description: Split and link sub-tasks<br />
Condition: OnCreate<br />
Action: User Defined<br />
Template: Blank<br />
Stage: TransactionCreate #With modern RTs You may need to use TransactionBatch instead.<br />
<br />
=== Action preparation code ===<br />
<br />
<pre><br />
require MIME::Entity;<br />
<br />
my $trans = $self-&gt;TransactionObj;<br />
my $tkt = $self-&gt;TicketObj;<br />
<br />
my $cont = $trans-&gt;Content;<br />
my $requestors = [ $tkt-&gt;Requestors-&gt;MemberEmailAddresses ];<br />
<br />
#Updated to require whitespace between bullet &amp; entry<br />
if ($cont =~ m/^\s*[-*]\s/m) {<br />
$cont =~ s/^\n.*//sm;<br />
my @lines = split(/[\n\r]*^\s*[-*]\s+/m, $cont); #update<br />
shift(@lines);<br />
foreach my $line (@lines) {<br />
my $new_tkt = RT::Ticket-&gt;new($RT::SystemUser);<br />
my ($id, $msg) = $new_tkt-&gt;Create(<br />
Queue =&gt; "General",<br />
Subject =&gt; $line,<br />
Status =&gt; 'new',<br />
Requestor =&gt; $requestors,<br />
DependedOnBy =&gt; $tkt-&gt;Id,<br />
MIMEObj =&gt; MIME::Entity-&gt;build(<br />
Type =&gt; 'text/plain',<br />
Data =&gt; $line));<br />
}<br />
}<br />
<br />
return 1;<br />
</pre><br />
<br />
=== Action cleanup code ===<br />
<br />
return 1;<br />
<br />
== New Variant (2) ==<br />
<br />
The version below handles nested bulleted lists, and the bullet character indicates the link relationship, as defied in %links. Note that this scrip will not fire if the bulleted list is preceded by other content.<br />
<br />
<pre><br />
*starfruit<br />
* apple<br />
@@ gala<br />
++ fuji<br />
@ melons<br />
** watermelon<br />
*** stars &amp; stripes<br />
**cantaloupe<br />
</pre><br />
<br />
This update of [http://wiki.bestpractical.com/view/DivideTicketIntoSubtasks/4012 the initial revision] also handles signatures, long items entered via the UI, and fixes a conflict with [[HideTransactions]].<br />
<br />
=== Action preparation code ===<br />
<br />
require MIME::Entity; require Text::Wrap;<br />
<br />
<pre><br />
#Semi-configurable items, see also 5.10 @ Unwrap below<br />
local $Text::Wrap::columns = 64;<br />
#You probably don't want to use '#' without modifying the truncated entry comment char<br />
my %links = ('*' =&gt; 'DependedOnBy', '+' =&gt; 'Parents', '@' =&gt; 'RefersTo');<br />
<br />
<br />
#Preparation<br />
my $tkt = $self-&gt;TicketObj;<br />
my $cont = $self-&gt;TransactionObj-&gt;Content;<br />
my $requestors = [ $tkt-&gt;Requestors-&gt;MemberEmailAddresses ];<br />
my $sym = qr/[\Q@{[join '', keys %links]}\E]/;<br />
<br />
<br />
#Strip leading comments, e.g; untruncated entry<br />
$cont =~ s/^\s*(#[^\n]+\n)+\n?//s;<br />
<br />
#Strip signature<br />
$cont =~ s/^-- \n.+$//m;<br />
<br />
#Unwrap long lines from RT's hard-wrap textareas, use the one matching your perl<br />
#1 while $cont =~ s/^\s*$sym\s*[^\n]+\K\n(?!^$sym)/ /m; #5.10<br />
1 while $cont =~ s/^(\s*$sym\s*[^\n]+)\n(?!^$sym)/$1 /m; #5.8<br />
<br />
#Parse bullets<br />
while( $cont =~ /\G\s*($sym)\s*([^\n]+)((?:\s*$sym{2,}\s*[^\n]+)+)?/mg ){<br />
my($link, $subj, $body) = ($1, $2, $3||'');<br />
$body =~ s/^$sym//mg if length($body);<br />
<br />
#Wrap our long item into a body comment before truncating<br />
if( length($subj) &gt; $Text::Wrap::columns ){<br />
$body = Text::Wrap::wrap('# ', '# ', $subj) . "\n" . $body;<br />
<br />
my($under, $over) = ($Text::Wrap::columns-5, $Text::Wrap::columns+5);<br />
$subj =~ /(.{$under,$over}(?&lt;!\s)\b)|(.{$Text::Wrap::columns})/;<br />
$subj = $1 ? "$1..." : ($2||$subj);<br />
}<br />
<br />
#$RT::SystemUser would not play nice with HideTransactions<br />
my $new_tkt = RT::Ticket-&gt;new($RT::Nobody);<br />
my ($id, $msg) = $new_tkt-&gt;Create(<br />
Queue =&gt; "TODO",<br />
Subject =&gt; $subj,<br />
Status =&gt; 'new',<br />
Owner =&gt; $tkt-&gt;Owner,<br />
Requestor =&gt; $requestors,<br />
$links{$link} =&gt; $tkt-&gt;Id,<br />
MIMEObj =&gt; MIME::Entity-&gt;build(Data =&gt; $body)<br />
);<br />
}<br />
<br />
return 1;<br />
</pre><br />
<br />
==Nested Subtickets Variant 3==<br />
<br />
I had problems with Variant 2 in RT 3.8 and I wanted something a little simpler that would created nested subtickets. It does not use the MIME::Entity module since I don't want to put a default comment in the subtickets.<br />
<br />
This creates DependsOn links with either * or - symbols. Create sublinks by adding *'s or -'s.<br />
<br />
Example:<br />
*Level 1<br />
**Level 1-1<br />
**Level 1-1<br />
***Level 1-1-1<br />
<br />
*Level 2<br />
** Level 2-1<br />
* Level 3<br />
**Level 3-1<br />
Action Preparation Code:<br />
<br />
<pre><br />
my $trans = $self->TransactionObj;<br />
my $tkt = $self->TicketObj;<br />
my $cont = $trans->Content;<br />
my $requestors = [ $tkt->Requestors->MemberEmailAddresses ];<br />
<br />
if ($cont =~ m/^\s*[\*-]+/m) {<br />
my %tickets;<br />
foreach (split(/[\n\r]/, $cont)) {<br />
<br />
#Split number of *'s and actual subject.<br />
next unless /^\s*([\*-]+)(.+)$/;<br />
next unless $2;<br />
my $lvl = $1; my $value=$2;<br />
<br />
#Count the number of *'s<br />
my $count = scalar split //, $lvl;<br />
<br />
#Keep track of the tickets' parents<br />
$tickets{$count}=[$value];<br />
<br />
#One line is one ticket<br />
my $new_tkt = RT::Ticket->new($RT::SystemUser);<br />
<br />
#If there is a parent ticket create dependencies on it<br />
if (exists $tickets{$count-1}) {<br />
my ($id, $msg) = $new_tkt->Create(<br />
Queue => $tkt->Queue, <br />
Subject => $value,<br />
Status => 'new',<br />
Requestor => $requestors, <br />
Owner => $tkt->Owner, <br />
DependedOnBy => $tickets{$count-1}[1],<br />
); <br />
<br />
#Store ticket id for children<br />
push @{$tickets{$count}}, $id;<br />
<br />
#If no parent ticket, create dependencies on the new ticket<br />
} else {<br />
my ($id, $msg) = $new_tkt->Create(<br />
Queue => $tkt->Queue, <br />
Subject => $value,<br />
Status => 'new',<br />
Requestor => $requestors, <br />
Owner => $tkt->Owner, <br />
DependedOnBy => $tkt->Id, <br />
); <br />
<br />
#Store ticket id for children<br />
push @{$tickets{$count}}, $id;<br />
<br />
#Die if we got here without recording the parent somehow<br />
return 0 if $count > 1; <br />
} <br />
} <br />
}<br />
<br />
<br />
return 1;<br />
</pre></div>Tharnhttps://rt-wiki.bestpractical.com/index.php?title=CreatePriorityBasedOnCustomFieldValues&diff=26355CreatePriorityBasedOnCustomFieldValues2016-08-14T01:11:25Z<p>Tharn: </p>
<hr />
<div>== Abstract ==<br />
<br />
We created this scrip for use with ITIL type custom fields. This takes ticket urgency and ticket impact, does some simple math and creates the number for the priority field.<br />
<br />
Note that in ITIL priority is determined by ticket urgency and impact, the values of these fields should be low, medium, high, critical. The definition of urgency is simply the amount of time before this task must be completed. The definition of Impact is who/how many people/machines are impacted. These are general definitions you may need to change them based on your working model.<br />
<br />
== How to install ==<br />
<br />
Create your custom fields for your impact and urgency.<br />
<br />
We are using custom field names Ticket_urgency, and Ticket_impact.<br />
<br />
* Name: Ticket_urgency<br />
* Description: {blank, or put your own description}<br />
* Type: Select one value<br />
* Applies to: Tickets<br />
* Validation: (?#Mandatory).<br />
* Link Values to: {Blank}<br />
* Include page: {Blank}<br />
* Values:<br />
** 10 - Critical - {Blank} - {Blank}<br />
** 20 - High - {Blank} - {Blank}<br />
** 30 - Medium - {Blank} - {Blank}<br />
** 40 - Low - {Blank} - {Blank}<br />
<br />
* Name: Ticket_impact<br />
* Description: {blank, or put your own description}<br />
* Type: Select one value<br />
<br />
* Applies to: Tickets<br />
* Validation: (?#Mandatory).<br />
* Link Values to: {Blank}<br />
* Include page: {Blank}<br />
* Values:<br />
** 10 - Critical - {Blank} - {Blank}<br />
** 20 - High - {Blank} - {Blank}<br />
** 30 - Medium - {Blank} - {Blank}<br />
** 40 - Low - {Blank} - {Blank}<br />
<br />
= Scrip =<br />
<br />
* Description: Create Priority<br />
* Condition: On Transaction<br />
* Action: User Defined<br />
* Template: Global template: Blank<br />
* Stage: Transaction Batch<br />
* Custom Condition: 1;<br />
* Custom action preparation code: 1;<br />
* Custom action cleanup code:<br />
<br />
<pre><br />
if($self-&gt;TicketObj-&gt;Priority &gt; 0){<br />
return 1;<br />
}<br />
else{<br />
my $urgent = $self-&gt;TicketObj-&gt;CustomFieldValues('Ticket_urgency');<br />
my $impact = $self-&gt;TicketObj-&gt;CustomFieldValues('Ticket_impact');<br />
my $urg = $urgent-&gt;Next-&gt;Content;<br />
my $imp = $impact-&gt;Next-&gt;Content;<br />
$RT::Logger-&gt;error( "Urg: ".$urg<br />
);<br />
$RT::Logger-&gt;error( "Imp: ".$imp<br />
);<br />
my $urgn = "60" if ($urg =~ /^C/); #Critical<br />
$urgn = "45" if ($urg =~ /^H/); #High<br />
$urgn = "30" if ($urg =~ /^M/); #Medium<br />
$urgn = "5" if ($urg =~ /^L/); #Low<br />
my $impn = "30" if ($imp =~ /^C/); #Critical<br />
$impn = "20" if ($imp =~ /^H/); #High<br />
$impn = "10" if ($imp =~ /^M/); #Medium<br />
$impn = "5" if ($imp =~ /^L/); #Low<br />
my $pri = $urgn + $impn;<br />
$self-&gt;TicketObj-&gt;SetPriority ($pri);<br />
1;<br />
}<br />
</pre><br />
<br />
----<br />
<br />
Here is a visual representation of this table: Across is Ticket Impact Down is urgency<br />
<br />
{| border="1"<br />
|- valign="top"<br />
|<br />
| Critical<br />
| High<br />
| Medium<br />
| Low<br />
|- valign="top"<br />
| Critical<br />
| 90<br />
| 80<br />
| 70<br />
| 65<br />
|- valign="top"<br />
| High<br />
| 75<br />
| 65<br />
| 55<br />
| 50<br />
|- valign="top"<br />
| Medium<br />
| 60<br />
| 50<br />
| 40<br />
| 35<br />
|- valign="top"<br />
| Low<br />
| 35<br />
| 25<br />
| 15<br />
| 10<br />
|}<br />
<br />
This allows room for moving the priority as needed, ITIL States that urgency and impact should not be changed through the life cycle of the ticket, that is the reason for the if statement at the beginning of the scrip, this allows you to change the priority with out changing the initial impact or urgency.<br />
<br />
----<br />
<br />
By: Tc3driver -- Any questions hit me up on the list</div>Tharnhttps://rt-wiki.bestpractical.com/index.php?title=AutoSetOwnerFromCC&diff=26354AutoSetOwnerFromCC2016-08-14T01:09:58Z<p>Tharn: </p>
<hr />
<div>This is a variation on [[AutoSetOwner]], it auto-sets the owner of a ticket by using the Cc watcher. It then removes the new owner from the Cc.<br />
<br />
# Condition: On Create # Action: User Defined # Template: blank # Stage: [[TransactionCreate]]<br />
<br />
Description: [[AutoAssign]] Owner From Cc Condition: On Create Action: User Defined Template: Global Template: Blank Stage: [[TransactionCreate]]<br />
<br />
=== Custom Condition ===<br />
<br />
return 1;<br />
<br />
<br />
=== Custom Action Preparation ===<br />
<br />
return 1;<br />
<br />
=== Custom Action Cleanup Code ===<br />
<br />
<pre><br />
# Get the first CC<br />
my $newowner = $self-&gt;TicketObj-&gt;Cc-&gt;UserMembersObj-&gt;Next;<br />
<br />
if (defined $newowner) {<br />
$RT::Logger-&gt;debug("Attempting to auto assign ticket #". $self-&gt;TicketObj-&gt;id ." to user #". $newowner-&gt;id);<br />
<br />
my ($status, $msg) = $self-&gt;TicketObj-&gt;SetOwner( $newowner );<br />
unless( $status ) {<br />
$RT::Logger-&gt;error( "Impossible to assign ticket #" . $self-&gt;TicketObj-&gt;id . " to user #" . $newowner-&gt;id . ": $msg" );<br />
return undef;<br />
}<br />
<br />
$RT::Logger-&gt;debug("Auto-assign successful for ticket #". $self-&gt;TicketObj-&gt;id);<br />
<br />
# Now we need to remove them as Cc<br />
$RT::Logger-&gt;debug("Attempting to remove user #" . $newowner-&gt;id . " as Cc on ticket #". $self-&gt;TicketObj-&gt;id);<br />
<br />
my ($status, $msg)= $self-&gt;TicketObj-&gt;DeleteWatcher(<br />
Type =&gt; "Cc",<br />
PrincipalId =&gt; $newowner-&gt;id);<br />
unless( $status ) {<br />
$RT::Logger-&gt;error( "Unable to remove user #" . $newowner-&gt;id . " as Cc for ticket #" . $self-&gt;TicketObj-&gt;id);<br />
return undef;<br />
}<br />
<br />
$RT::Logger-&gt;debug("Cc removal successful for ticket #". $self-&gt;TicketObj-&gt;id);<br />
}<br />
<br />
return 1;<br />
</pre></div>Tharnhttps://rt-wiki.bestpractical.com/index.php?title=AutoSetOwnerIfAdminCc&diff=26353AutoSetOwnerIfAdminCc2016-08-14T01:06:20Z<p>Tharn: </p>
<hr />
<div>This is a variation on [[AutoSetOwner]], it auto-sets the owner of a ticket only if the person doing the correspondence is in the [[AdminCc]] watchers:<br />
<br />
<syntaxhighlight lang="perl" line="1" ><br />
# Condition: On correspond<br />
# Action: User Defined<br />
# Template: blank<br />
<br />
## based on http://wiki.bestpractical.com/index.cgi?AutoSetOwner<br />
## And testcode ~ line 576 of Queue_Overlay.pm (rt3.4.2)<br />
my $Actor = $self-&gt;TransactionObj-&gt;Creator;<br />
my $Queue = $self-&gt;TicketObj-&gt;QueueObj;<br />
# if actor is RT_SystemUser then get out of here<br />
return 1 if $Actor == $RT::SystemUser-&gt;id;<br />
# get out unless ticket owner is nobody<br />
return 1 unless $self-&gt;TicketObj-&gt;Owner == $RT::Nobody-&gt;id;<br />
# get out unless $Actor is not part of AdminCc watchers<br />
return 1 unless $Queue-&gt;IsWatcher(Type =&gt; 'AdminCc', PrincipalId =&gt; $Actor);<br />
# do the actual 'status update'<br />
my ($status, $msg) = $self-&gt;TicketObj-&gt;SetOwner( $Actor );<br />
unless( $status ) {<br />
$RT::Logger-&gt;warning( "can't set ticket owner to $Actor: $msg" );<br />
return undef;<br />
}<br />
return 1;<br />
</syntaxhighlight></div>Tharnhttps://rt-wiki.bestpractical.com/index.php?title=AutoCcOwner&diff=26352AutoCcOwner2016-08-14T01:02:02Z<p>Tharn: </p>
<hr />
<div>=== AutoCcOwner ===<br />
<br />
This scrip adds the new owner of a ticket to the Cc list when the owner is changed. This is useful if you want all successive (including the current) owners of a ticket to see followups of tickets.<br />
<br />
Description: AutoCcOwner<br />
<br />
Condition: On Owner Change<br />
<br />
Action: User Defined<br />
<br />
Custom action preparation code<br />
<br />
<pre><br />
my $last_id = $self-&gt;TransactionObj-&gt;NewValue;<br />
my $temp_user = RT::User-&gt;new();<br />
$temp_user-&gt;Load($last_id);<br />
my $last_email = $temp_user-&gt;EmailAddress();<br />
$self-&gt;TicketObj-&gt;AddWatcher( Type =&gt; "Cc",<br />
Email =&gt; $last_email);<br />
return 1;<br />
<br />
</pre><br />
<br />
Custom action cleanup code:<br />
return 1;<br />
<br />
Template: Global template: Blank<br />
<br />
You can also change the:<br />
<br />
$self-&gt;TicketObj-&gt;AddWatcher( Type =&gt; "Cc",<br />
Email =&gt; $last_email);<br />
<br />
to:<br />
<br />
$self-&gt;TicketObj-&gt;AddWatcher( Type =&gt; "AdminCc",<br />
Email =&gt; $last_email);<br />
<br />
to make this add the owner as [[AdminCc]].<br />
<br />
Update: thinking back: this is silly. You just need to make a script that notifies the current owner and use the [[AutoCcLastOwner]] to add previous owners.<br />
<br />
-- Comment: I don't think it's silly. If you notify Owner and adminCC with separate scrips, you get multiple "Outgoing mail recorded" lines cluttering the ticket. This is nice and clean IMO.</div>Tharnhttps://rt-wiki.bestpractical.com/index.php?title=AutomaticCustomFieldValue&diff=26351AutomaticCustomFieldValue2016-08-14T00:58:52Z<p>Tharn: </p>
<hr />
<div>This scrip will set a global custom field value according to a requestor's email address. We only want to set this field for users in a specific domain. You need to give requestors [[ModifyCustomFields]] rights. Took me too long to get this to work. Hope it helps someone.<br />
<br />
Dan Dofton, Four County Library System, [mailto:ddofton@4cls.org ddofton@4cls.org]<br />
<br />
=== PREP CODE ===<br />
<pre><br />
my $requestor_address = $self-&gt;TicketObj-&gt;RequestorAddresses;<br />
my $domain = "example.org";<br />
if ( $requestor_address !~ /$domain$/i ) {<br />
return 0;<br />
}<br />
else {<br />
return 1;<br />
}<br />
</pre><br />
<br />
=== CLEANUP CODE ===<br />
<pre><br />
my $T_Obj = $self-&gt;TicketObj;<br />
my $requestor_address = $T_Obj-&gt;RequestorAddresses;<br />
my $cf_value;<br />
my $CF_Obj = RT::CustomField-&gt;new($self-&gt;CurrentUser);<br />
my $domain = "example.org";<br />
my $cf_name = "customfieldname";<br />
<br />
if ( $requestor_address =~ m#^(..)\.\w+\@$domain#i ) {<br />
# make cf_value = the first two characters of the email address if pattern is matched.<br />
$cf_value = uc $1;<br />
$RT::Logger-&gt;debug( $self . " cf_value = ". $cf_value ."\n");<br />
}<br />
else {<br />
$cf_value = "DEFAULT";<br />
$RT::Logger-&gt;debug( $self . " cf_value = ". $cf_value . "\n" );<br />
}<br />
<br />
$CF_Obj-&gt;LoadByName( Name =&gt; $cf_name, Queue =&gt; '0',);<br />
$RT::Logger-&gt;debug( "Loaded \$CF_Obj-&gt;Name = ". $CF_Obj-&gt;Name() ."\n" );<br />
$CF_Obj-&gt;AddValueForObject( Object =&gt; $T_Obj, Content =&gt; $cf_value, );<br />
return 1;<br />
<br />
</pre><br />
<br />
== CustomField values based on incoming To: address ==<br />
<br />
Because we have multiple email addresses feeding into one queue, I needed a way to set a custom field based on the To: address a ticket was sent to. This is what I came up with, based on Dan Dofton's code above and help from Robert Spier.<br />
<br />
Travis Campbell<br />
<br />
Condition: [[OnCreate]] Action: User Defined<br />
<br />
Custom Action prep code:<br />
<br />
return 1;<br />
<br />
=== Custom Action cleanup code ===<br />
<br />
<pre><br />
my $to = $self-&gt;TicketObj-&gt;Transactions-&gt;First-&gt;Attachments-&gt;First-&gt;GetHeader("To");<br />
<br />
my $cf_value = "";<br />
$cf_value = "Value1" if ($to =~ /^address1/i);<br />
$cf_value = "Value2" if ($to =~ /^address2/i);<br />
$cf_value = "Value3" if ($to =~ /^address3/i);<br />
$cf_value = "Value4" if ($to =~ /^address4/i);<br />
$cf_value = "Value5" if ($to =~ /^address5/i);<br />
<br />
my $CF_Obj = RT::CustomField-&gt;new($RT::SystemUser);<br />
my $cf_name = "Site";<br />
<br />
$RT::Logger-&gt;debug( $self . " cf_value = ". $cf_value . "\n" );<br />
<br />
$CF_Obj-&gt;LoadByName( Name =&gt; $cf_name, Queue =&gt; '0',);<br />
$RT::Logger-&gt;debug( "Loaded \$CF_Obj-&gt;Name = ". $CF_Obj-&gt;Name() ."\n" );<br />
$CF_Obj-&gt;AddValueForObject( Object =&gt; $self-&gt;TicketObj,<br />
Content =&gt; $cf_value, );<br />
return 1;<br />
<br />
</pre><br />
<br />
== CustomField values based on Requestor Address ==<br />
<br />
Expanding on the two examples above here's a scrip that extracts both the username and domain name portion from the Requestor's Email address, took an hour or so to knock up, hopefully it will save some time for non Perl heads like myself.<br />
<br />
Condition: [[OnCreate]] Action: User Defined<br />
<br />
Custom Action prep code:<br />
<br />
return 1;<br />
<br />
<br />
=== Custom Action cleanup code ===<br />
<br />
<pre><br />
my $T_Obj = $self-&gt;TicketObj;<br />
my $requestor_address = $T_Obj-&gt;RequestorAddresses;<br />
my $cf_value;<br />
my $CF_Obj = RT::CustomField-&gt;new($self-&gt;CurrentUser);<br />
my $cf_name = "Custom Field Name"; #change this to the CF name<br />
<br />
<br />
$requestor_address =~ /(^.+)@([^\.].*\.[a-z]{2,}$)/;<br />
# $1 returns the username, $2 returns the domain name<br />
$cf_name = $2<br />
<br />
$CF_Obj-&gt;LoadByName( Name =&gt; $cf_name,);<br />
$RT::Logger-&gt;debug( "Loaded \$CF_Obj-&gt;Name = ". $CF_Obj-&gt;Name() ."\n" );<br />
$CF_Obj-&gt;AddValueForObject( Object =&gt; $self-&gt;TicketObj,<br />
Content =&gt; $cf_value, );<br />
return 1;<br />
</pre><br />
<br />
== Troubleshooting ==<br />
<br />
I found that this error started happening, possibly after an upgrade or some queue changes:<br />
<br />
RT::Principal=HASH(0xa354040) HasRight called with no valid object (/usr/share/request-tracker3.4/lib/RT/Principal_Overlay.pm:293)<br />
<br />
This call failed due to Queue =&gt; '0'.<br />
<br />
$CF_Obj-&gt;LoadByName( Name =&gt; $cf_name, Queue =&gt; '0',);<br />
<br />
<br />
Replaced with<br />
<br />
$CF_Obj-&gt;LoadByName( Name =&gt; $cf_name );<br />
<br />
<br />
<br />
= RT 3.8 and up =<br />
<br />
Some working examples at [[SetCustomFieldViaMail]]</div>Tharnhttps://rt-wiki.bestpractical.com/index.php?title=LoopIn&diff=26350LoopIn2016-08-14T00:54:42Z<p>Tharn: </p>
<hr />
<div>==Update:==<br />
The code below didn't worked out of the box for me. I did some corrections, the code is available at: https://github.com/zito/rt-action-loopin. I'm have prepared it for RT 4.2.2.<br />
==Overview==<br />
<br />
We had a set of users who, when using our RT, would like to say they were "looping in" folks when they responded to tickets. This was a common practice in their regular mail, where the Cc list would be extended with new addresses and everyone would be sure to use Reply All for the latest message. After explaining repeatedly that RT doesn't work like that, I decided to find a way to make it work like that.<br />
<br />
I started with the existing [[AddWatchersOnCorrespond]]. That action itsef points out there are security problems with letting people send mail with additional addresses that then are automagically added as watchers, so this action takes some care to limit who is allowed to loop addresses into the ticket. The addresses authorized include:<br />
*Requestor<br />
*Cc and AdminCc addresses<br />
Additionally, there is a lookaside file with rules to permit addresses to loop in others within a specific domain. The permitted list can include specific authorized addresses or anyone at a domain.<br />
<br />
=== RT::Action::LoopIn Module ===<br />
<pre> <br />
package RT::Action::LoopIn;<br />
<br />
# SEE: http://wiki.bestpractical.com/view/AddWatchersOnCorrespond<br />
<br />
# This scrip will add new watchers based on message headers, but<br />
# only if the actor is authorized (is a requestor, cc, or admincc).<br />
<br />
use strict;<br />
<br />
use base qw(RT::Action);<br />
<br />
my $scrip = 'Scrip:LoopIn';<br />
<br />
# {{{ sub Describe<br />
sub Describe {<br />
my $self = shift;<br />
return (ref $self . " add new watchers from to/cc list if actor is authorized.");<br />
}<br />
# }}}<br />
<br />
# {{{ sub Prepare<br />
sub Prepare {<br />
# nothing to prepare<br />
return 1;<br />
}<br />
# }}}<br />
<br />
sub Commit {<br />
my $self = shift;<br />
my $Transaction = $self->TransactionObj;<br />
my $ActorAddr = $Transaction->CreatorObj->EmailAddress;<br />
my $Queue = $self->TicketObj->QueueObj;<br />
my $Ticket = $self->TicketObj;<br />
my $Id = $self->TicketObj->id;<br />
my @Authorized;<br />
my @Unauthorized;<br />
<br />
# assume it is NOT valid to loop in additional addresses<br />
my $loopin_authorized = 0;<br />
<br />
$RT::Logger->debug("$scrip: about to check if creator is authorized");<br />
<br />
# load email alias file, if present<br />
my %loopauth;<br />
my $loopauthfile = RT->Config->Get('WM_LoopAuth');<br />
if ($loopauthfile and my $fh = IO::File->new($loopauthfile)) {<br />
while (<$fh>) {<br />
chomp;<br />
s/^\s*//; # strip leading whitespace<br />
s/\s*$//; # strip trailing whitespace<br />
next if /^$/; # skip blank lines<br />
next if /^#/; # skip comment lines<br />
if (/^equiv\s+(\S+)\s+(\S+)$/i) {<br />
$loopauth{equiv}{lc($1)} = $2;<br />
}<br />
elsif (/^domain\s+(\S+)\s+(\S+)$/i) {<br />
$loopauth{domain}{lc($1)}->{lc($2)} = 1;<br />
}<br />
else {<br />
$RT::Logger->error("$scrip: unknown loopauth directive: $_");<br />
}<br />
}<br />
}<br />
<br />
# if actor is a requestor, cc or admincc, loopin is authorized<br />
if (my $Creator = $Transaction->CreatorObj) {<br />
my $Principal = $Creator->PrincipalId if $Creator->Id;<br />
$RT::Logger->debug("$scrip: creator principal ID: #$Principal");<br />
if (($Queue->IsCc($Principal) or<br />
$Queue->IsAdminCc($Principal) or<br />
$Ticket->IsCc($Principal) or<br />
$Ticket->IsAdminCc($Principal) or<br />
$Ticket->IsRequestor($Principal)<br />
)) {<br />
$loopin_authorized = 1;<br />
$RT::Logger->debug("$scrip: $ActorAddr is authorized to loop in additional watchers");<br />
}<br />
}<br />
<br />
$RT::Logger->debug("$scrip: about to extract candidate address list");<br />
<br />
# extract a list of to and cc addresses associated with this transaction<br />
for my $h (qw(To Cc)) {<br />
my $header = $Transaction->Attachments->First->GetHeader($h);<br />
for my $addrObj (Mail::Address->parse($header)) {<br />
# extract and normalize email address<br />
my $addr = lc $RT::Nobody->UserObj->CanonicalizeEmailAddress($addrObj->address);<br />
<br />
# ignore the specific addresses for this queue:<br />
next if lc $Queue->CorrespondAddress eq $addr or lc $Queue->CommentAddress eq $addr;<br />
<br />
# ignore any email address that looks like one for ANY of our queues:<br />
next if RT::EmailParser::IsRTAddress('', $addr);''<br />
<br />
# normalize address if equivalence is defined<br />
if (defined($loopauth{equiv}{$addr})) {<br />
$RT::Logger->debug("$scrip: normalizing $addr to $loopauth{equiv}{$addr}");<br />
$addr = $loopauth{equiv}{$addr};<br />
}<br />
<br />
# ignore any email address that is already a watcher<br />
my $User = RT::User->new($RT::SystemUser);<br />
$User->LoadByEmail($addr); # NOT LoadOrCreateByEmail<br />
my $Principal = $User->PrincipalId if $User->Id;<br />
next if ($Queue->IsCc($Principal) or<br />
$Queue->IsAdminCc($Principal) or<br />
$Ticket->IsCc($Principal) or<br />
$Ticket->IsAdminCc($Principal) or<br />
$Ticket->IsRequestor($Principal)<br />
);<br />
<br />
# extend additional watchers list if authorized<br />
if ($loopin_authorized or domainauth($loopauth{domain}, $ActorAddr, $addr)) {<br />
$RT::Logger->debug("$scrip: Ticket #$Id correspondence contains header - $h: $addr");<br />
push @Authorized, $addr;<br />
}<br />
else {<br />
push @Unauthorized, $addr;<br />
}<br />
}<br />
}<br />
<br />
my $comment = "";<br />
<br />
if (@Unauthorized) {<br />
$comment .= "$ActorAddr made an unauthorized attempt to loop in the following:\n " . join("\n ", @Unauthorized) . "\n";<br />
}<br />
<br />
$RT::Logger->debug("$scrip: about to add candidate addresses as watchers");<br />
<br />
# add authorized candidates not already listed as watchers<br />
if (@Authorized) {<br />
my @looped; # list of looped addresses<br />
my @failed; # list of failed addresses<br />
for my $addr (@Authorized) {<br />
my $User = RT::User->new($RT::SystemUser);<br />
$User->LoadOrCreateByEmail($addr);<br />
my $Principal = $User->PrincipalId if $User->Id;<br />
# add the new watcher and check for errors<br />
my ($ret, $msg) = $Ticket->AddWatcher(<br />
Type => 'Cc',<br />
Email => $addr,<br />
PrincipalId => $Principal,<br />
);<br />
if ($ret) {<br />
$RT::Logger->info("$scrip: New watcher added to ticket #$Id: $addr (#$Principal)");<br />
push(@looped, $addr);<br />
} else {<br />
$RT::Logger->error("$scrip: Failed to add new watcher to ticket #$Id: $addr (#$Principal) - $msg");<br />
push(@failed, $addr);<br />
}<br />
}<br />
if (@looped) {<br />
$comment .= "$ActorAddr successfully looped in the following:\n " . join("\n ", @looped) . "\n";<br />
}<br />
if (@failed) {<br />
$comment .= "$ActorAddr failed to loop in the following:\n " . join("\n ", @failed) . "\n";<br />
}<br />
}<br />
<br />
$Ticket->Comment(Content => $comment) if $comment;<br />
}<br />
<br />
sub domainauth {<br />
my $domainauth = shift;<br />
my $actor = shift;<br />
my $loopin = shift;<br />
my $loopin_domain;<br />
my $actor_domain;<br />
<br />
$RT::Logger->debug("$scrip: checking domainauth for $actor looping in $loopin");<br />
($loopin_domain = $loopin) =~ s/^[^@]+@//;<br />
($actor_domain = $actor) =~ s/^[^@]+@//;<br />
$RT::Logger->debug("$scrip: actor domain: $actor_domain, loopin domain: $loopin_domain");<br />
return 0 unless defined $domainauth;<br />
return 0 unless defined $domainauth->{$loopin_domain};<br />
return 1 if $domainauth->{lc($loopin_domain)}->{lc($actor)};<br />
return 1 if $domainauth->{lc($loopin_domain)}->{'*@'.lc($actor_domain)};<br />
return 0;<br />
}<br />
<br />
eval "require RT::Action::LoopIn_Vendor";<br />
die $@ if ($@ && $@ !~ qr{^Can't locate RT/Action/LoopIn_Vendor.pm});<br />
eval "require RT::Action::LoopIn_Local";<br />
die $@ if ($@ && $@ !~ qr{^Can't locate RT/Action/LoopIn_Local.pm});<br />
<br />
return 1;<br />
<br />
</pre><br />
<br />
=== LoopIn External Configuration ===<br />
Additional rules can be defined for controlling access to LoopIn. The filename is defined in RT_SiteConfig.pm as follows:<br />
Set($WM_LoopAuth, "/opt/rt3/etc/loopauth.cfg");<br />
Note that the variable name includes a WM_ prefix for "Willing Minds", since I don't want to stomp on anything that might be defined by Best Practical or others later.<br />
<br />
A sample copy of this file with directive documentation is included below.<br />
<br />
<pre><br />
# loopin address equivalences<br />
#<br />
# This defines address normalization rules to avoid adding duplicate <br />
# watchers.<br />
#<br />
# Format:<br />
#<br />
# equiv ORIGADDR NORMADDR<br />
#<br />
# If ORIGADDR is being looped in, it will be normalized to NORMADDR<br />
# before the existing watcher list is checked.<br />
<br />
# normalize foo.com users with example.com addresses<br />
equiv user1@example.com user1@foo.com<br />
equiv user2@example.com user2@foo.com<br />
<br />
<br />
# loopin domain permissions<br />
# <br />
# Format:<br />
#<br />
# domain DOMAIN FROMADDR<br />
#<br />
# If fromaddr includes To or Cc addresses that is in DOMAIN not on the<br />
# ticket, then those addresses will be looped into the ticket. The<br />
# special case '*@domain' format may be used for FROMADDR to indicate<br />
# that anyone in the domain is authorized.<br />
<br />
# anyone at foo.com can loop in foo.com addresses<br />
domain foo.com *@foo.com<br />
<br />
# joe@bar.com can loop in baz.com addresses<br />
domain baz.com joe@bar.com<br />
</pre></div>Tharnhttps://rt-wiki.bestpractical.com/index.php?title=AddRefersToOnEqualCustomField&diff=26349AddRefersToOnEqualCustomField2016-08-14T00:51:18Z<p>Tharn: </p>
<hr />
<div>This isn't really an action, but actually used as a condition which is also acting. Which probably means it could be written better, but here goes.<br />
<br />
We use it to check for serial numbers on an incoming repair queue. If the serial numbers match, the unit to be repaired has been seen before, so create [[RefersTo]] links between them.<br />
<br />
It also triggers if the custom field is changed, but makes no effort to delete any links.<br />
<br />
<pre><br />
my $cfname = 'SerialNumber';<br />
my $cf = RT::CustomField-&gt;new(RT-&gt;SystemUser);<br />
$cf-&gt;LoadByName(Name =&gt; $cfname);<br />
my $cfid = $cf-&gt;Id();<br />
<br />
# If create or change to custom field<br />
unless (<br />
( $self-&gt;TransactionObj-&gt;Type eq "CustomField"<br />
&amp;&amp; $self-&gt;TransactionObj-&gt;Field == $cfid )<br />
|| $self-&gt;TransactionObj-&gt;Type eq "Create"<br />
) {<br />
return 0;<br />
}<br />
<br />
my $sn = $self-&gt;TicketObj-&gt;FirstCustomFieldValue($cfname);<br />
<br />
my $tickets = new RT::Tickets(RT-&gt;SystemUser);<br />
#$tickets-&gt;LimitQueue( VALUE =&gt; $queuename);<br />
$tickets-&gt;LimitCustomField(<br />
CUSTOMFIELD =&gt; $cfid,<br />
OPERATOR =&gt; '=',<br />
VALUE =&gt; $sn<br />
);<br />
<br />
my $i=0;<br />
while (my $ticket = $tickets-&gt;Next) {<br />
if ($ticket-&gt;id != $self-&gt;TicketObj-&gt;id) {<br />
$i++;<br />
$self-&gt;TicketObj-&gt;AddLink(Type=&gt;'RefersTo',Target=&gt;$ticket-&gt;id);<br />
}<br />
}<br />
<br />
# This means you can use it as a condition and have another action if it did something<br />
unless ($i &gt; 0) {<br />
return 0;<br />
}<br />
<br />
1;<br />
</pre></div>Tharnhttps://rt-wiki.bestpractical.com/index.php?title=AddAdminCcAndChangeQueue&diff=26348AddAdminCcAndChangeQueue2016-08-14T00:50:14Z<p>Tharn: </p>
<hr />
<div>On a queue with several admin[[CCs]] there was one slightly different task that needed to included one other person. Rather than create a second queue with its own list of Admin CC's and require everyone to watch two queues we made a second queue and added this script to move the tickets to the main queue. The web form for the special jobs routes them to the special queue, where the script adds the adminCC and changes the assigned queue to the main queue.<br />
<br />
<pre><br />
Description: AddAdminCC and change queue<br />
Condition: On Create<br />
Action: User Defined<br />
Template: Global Template: Admin Comment<br />
Stage: Transaction Create<br />
Custom Action Prep Code:<br />
<br />
return 1;<br />
<br />
Custom Action Cleanup Code:<br />
<br />
# add adminCC<br />
my $admincclist = $self-&gt;TicketObj-&gt;AdminCc;<br />
my $user = RT::User-&gt;new($RT::SystemUser);<br />
$user-&gt;LoadByEmail('somebody@somewhere.com');<br />
$admincclist-&gt;AddMember($user-&gt;Id);<br />
<br />
# Change queue<br />
my $newqueue = "some_queue_number";<br />
my $T_Obj = $self-&gt;TicketObj;<br />
<br />
$RT::Logger-&gt;info("Auto assign ticket #". $T_Obj-&gt;id ." to queue #". $newqueue );<br />
my ($status, $msg) = $T_Obj-&gt;SetQueue($newqueue);<br />
unless ($status) {<br />
$RT::Logger-&gt;warning("unable to set new queue: $msg");<br />
return undef;<br />
}<br />
return 1;<br />
</pre></div>Tharnhttps://rt-wiki.bestpractical.com/index.php?title=AddSquelchedCc&diff=26347AddSquelchedCc2016-08-14T00:49:13Z<p>Tharn: </p>
<hr />
<div>= Introduction =<br />
<br />
This script adds team members as watchers on a ticket, but squelch them (they'll not receive updates unless they are explicetely defined as requestors or cc or owners). When one of them creates a ticket, all the other will have access (you need to set at least [[ShowTicket]] right to "Cc". I'd recommend setting [[ShowTicket]] and [[ReplyToTicket]] to Cc and Requestors.<br />
<br />
There are a script and a scrip. The script is to apply this behavior on all the tickets already created, the scrip is for the new tickets.<br />
<br />
= Script for tickets already created =<br />
<br />
Usage: - Create a group starting with "TEAM_", add people to the group (if you need unprivileged member, select the user and click "memberships") - Run the following script<br />
<br />
<pre><br />
#!/usr/bin/perl<br />
<br />
# Name: AddSquelchedCc.pl<br />
#<br />
# Description:<br />
# This script adds team members as watchers on a ticket, but squelch them<br />
# (they'll not receive updates unless they are explicetely defined as<br />
# requestors or cc or owners).<br />
# When one of them creates a ticket, all the other will have access (you<br />
# need to set at least ShowTicket right to "Cc".<br />
# I'd recommend setting ShowTicket and ReplyToTicket to Cc and Requestors.<br />
#<br />
# Usage:<br />
# - Create a group starting with "TEAM_", add people to the group<br />
# (if you need unprivileged member, select the user and click "memberships")<br />
# - Run the script<br />
<br />
# Author: &lt;Christophe.Sahut {at} sgs {dot} com&gt;<br />
# License: This module is free software; you can redistribute it<br />
# and/or modify it under the GPLv2 licence.<br />
<br />
<br />
use warnings;<br />
use strict;<br />
use lib qw(/opt/rt/lib);<br />
use RT;<br />
use RT::Users;<br />
use RT::User;<br />
use RT::Tickets;<br />
use RT::Ticket;<br />
use RT::Groups;<br />
<br />
RT::LoadConfig();<br />
RT::Init();<br />
<br />
my $return;<br />
my $message = "";<br />
my $ticket = RT::Ticket-&gt;new($RT::SystemUser);<br />
<br />
my $tickets = RT::Tickets-&gt;new($RT::SystemUser);<br />
$tickets-&gt;UnLimit();<br />
#$tickets-&gt;LimitId(OPERATOR=&gt;'&gt;',VALUE=&gt;'15000');<br />
##$tickets-&gt;LimitStatus(VALUE=&gt;'open');<br />
<br />
while (my $ticket = $tickets-&gt;Next()) {<br />
<br />
print "== Working on ticket ".$ticket-&gt;Id." ==\n";<br />
<br />
my $groups = RT::Groups-&gt;new($RT::SystemUser);<br />
$groups-&gt;LimitToUserDefinedGroups();<br />
<br />
while (my $group = $groups-&gt;Next()) {<br />
next unless $group-&gt;Name =~ /^TEAM_/;<br />
if($group-&gt;HasMemberRecursively($ticket-&gt;Creator)){<br />
print "Creator found in ".$group-&gt;Name."\n";<br />
($return,$message)=$ticket-&gt;AddWatcher(Type=&gt;"Cc",PrincipalId=&gt;$group-&gt;Id);<br />
print "Adding ".$group-&gt;Name." as a Cc Watcher (".$message.")\n";<br />
<br />
foreach my $email($group-&gt;MemberEmailAddresses){<br />
if($email) {<br />
my $user = RT::User-&gt;new(RT-&gt;SystemUser);<br />
$user-&gt;LoadByEmail($email);<br />
<br />
# Don't squelch these users<br />
next if $ticket-&gt;IsRequestor($user-&gt;Id) or<br />
$ticket-&gt;IsCc($user-&gt;Id) or<br />
$ticket-&gt;IsOwner($user);<br />
<br />
$ticket-&gt;SquelchMailTo($email);<br />
print "Squelching ".$email."\n";<br />
}<br />
}<br />
}<br />
}<br />
}<br />
<br />
</pre><br />
<br />
= Scrip for new tickets =<br />
<br />
* Create a new scrip, on your incoming queue for example :<br />
<br />
Description: [[AddSquelchedCc]]<br />
<br />
Condition: [[OnCreate]]<br />
<br />
Action: User Defined<br />
<br />
Template: Global Template: blank<br />
<br />
Stage: [[TransactionCreate]]<br />
<br />
Custom condition:<br />
<br />
return 1;<br />
<br />
<br />
=== Custom action preparation code ===<br />
<br />
<pre><br />
my $ticket = $self-&gt;TicketObj;<br />
<br />
my $groups = RT::Groups-&gt;new($RT::SystemUser);<br />
$groups-&gt;LimitToUserDefinedGroups();<br />
<br />
while (my $group = $groups-&gt;Next()) {<br />
next unless $group-&gt;Name =~ /^TEAM_/;<br />
if($group-&gt;HasMemberRecursively($ticket-&gt;Creator)){<br />
$ticket-&gt;AddWatcher(Type=&gt;"Cc",PrincipalId=&gt;$group-&gt;Id);<br />
foreach my $email($group-&gt;MemberEmailAddresses){<br />
if($email) {<br />
my $user = RT::User-&gt;new(RT-&gt;SystemUser);<br />
$user-&gt;LoadByEmail($email);<br />
# Don't squelch these users<br />
next if $ticket-&gt;IsRequestor($user-&gt;Id) or<br />
$ticket-&gt;IsCc($user-&gt;Id) or<br />
$ticket-&gt;IsOwner($user);<br />
$ticket-&gt;SquelchMailTo($email);<br />
}<br />
}<br />
}<br />
}<br />
return 1;<br />
<br />
</pre><br />
<br />
Custom action cleanup code:<br />
<br />
return 1;</div>Tharnhttps://rt-wiki.bestpractical.com/index.php?title=AddWatchersOnCorrespond&diff=26346AddWatchersOnCorrespond2016-08-13T23:23:54Z<p>Tharn: </p>
<hr />
<div>= AddWatchersOnCorrespond =<br />
<br />
This RT [[Scrip]] will add the person making the correspondence as a [[Watcher]] to the ticket if they are not already a [[Watcher]], also, if the transaction originated from an email message, the script will scan the email headers and add all other recipients to the ticket as [[Watcher]]s (if they are not yet [[Watcher]]s). This can be used to complement the [[ParseNewMessageForTicketCcs]] [[SiteConfig]] option, which is part of the [[EmailInterface]] and does the same thing when tickets are created.<br />
<br />
In our RT setup, we have a group named '''general''' which contains all admins for our site. If the user this [[Scrip]] is going to add as a [[Watcher]] to the ticket is also a member of the '''general''' group, then the Scrip will add them as an [[AdminCC]] [[Watcher]] instead of a [[CC]] [[Watcher]]. It should be fairly easy for others who do not need this feature to remove or modify this [[Scrip]] accordingly.<br />
<br />
I wrote this [[Scrip]] to replace the patch we used to make to the RT Email Interface code, called [[ParseFollowupMessageForTicketCcs]]. Accordingly, it carries the same security warning about allowing basically '''anyone''' to add themselves to '''any''' ticket, simply by sending an appropriately formatted email. Some RT sites might not want this behavior, but it is necessary for us.<br />
<br />
Changelog<br />
<br />
2012-01-01: Fixed a bug in which the owner of the ticket would have been added as a Cc: for every reply he made. (HaimDimer)<br />
<br />
----<br />
<br />
'''Description:''' [[AddWatchersOnCorrespond]]<br />
<br />
'''Condition:''' On Correspond<br />
<br />
'''Action:''' User Defined<br />
<br />
'''Template:''' Global template: Blank<br />
<br />
'''Stage:''' [[TransactionBatch]]<br />
<br />
'''Custom condition:'''<br />
<br />
'''Custom action preparation code:''' <code>return 1;</code><br />
<br />
'''Custom action cleanup code:'''<br />
<br />
----<br />
<pre><br />
# Get some info:<br />
my $scrip = 'Scrip:AddWatchersOnCorrespond';<br />
my $Transaction = $self->TransactionObj;<br />
my $EmailAddr = $self->TransactionObj->CreatorObj->EmailAddress;<br />
my $Queue = $self->TicketObj->QueueObj;<br />
my $Ticket = $self->TicketObj;<br />
my $Id = $self->TicketObj->id;<br />
<br />
# Extract a list of people associated with this transaction:<br />
# - including the transaction creator, and if it is an email, the sender and recipients of that email<br />
my @People = ($EmailAddr);<br />
foreach my $h (qw(From To Cc)) {<br />
my $header = $Transaction->Attachments->First->GetHeader($h);<br />
my @addr = Mail::Address->parse($header);<br />
foreach my $addrobj (@addr) {<br />
my $addr = lc $RT::Nobody->UserObj->CanonicalizeEmailAddress($addrobj->address);<br />
# Ignore the specific addresses for this queue:<br />
next if lc $Queue->CorrespondAddress eq $addr;<br />
next if lc $Queue->CommentAddress eq $addr;<br />
# Ignore any email address that looks like one for ANY of our queues:<br />
next if RT::EmailParser->IsRTAddress($addr);<br />
$RT::Logger->debug("$scrip: Ticket #$Id correspondence contains header - $h: $addr");<br />
push @People, $addr;<br />
}<br />
}<br />
<br />
# Lookup the 'experts' (general) group to use below:<br />
my $Experts = RT::Group->new($self->CurrentUser);<br />
$Experts->LoadUserDefinedGroup('general');<br />
<br />
# Now check if each user is already watching the ticket or queue:<br />
foreach my $addr (@People) {<br />
my $User = RT::User->new($RT::SystemUser);<br />
$User->LoadOrCreateByEmail($addr);<br />
my $Name = $User->Name;<br />
my $Principal = $User->PrincipalId;<br />
if ( not ($Queue->IsWatcher(Type => 'Cc', PrincipalId => $Principal) or<br />
$Queue->IsWatcher(Type => 'AdminCc', PrincipalId => $Principal) or<br />
$Ticket->IsWatcher(Type => 'Cc', PrincipalId => $Principal) or<br />
$Ticket->IsWatcher(Type => 'AdminCc', PrincipalId => $Principal) or<br />
$Ticket->IsWatcher(Type => 'Requestor', PrincipalId => $Principal) or<br />
$Ticket->IsOwner($User) )) {<br />
# If the user is a member of the experts group, then add them as an AdminCc, otherwise as a nor<br />
my $type = 'Cc';<br />
$type = 'AdminCc' if $Experts->HasMember($User->PrincipalObj);<br />
# Add the new watcher now and check for errors:<br />
my ($ret, $msg) = $Ticket->AddWatcher(Type => $type, PrincipalId => $Principal);<br />
if ($ret) {<br />
$RT::Logger->info("$scrip: New $type watcher added to ticket #$Id: $addr (#$Principal)");<br />
} else {<br />
$RT::Logger->error("$scrip: Failed to add new $type watcher to ticket #$Id: $addr (#$Princi<br />
}<br />
}<br />
}<br />
<br />
return 1;<br />
</pre><br />
<br />
# vim:ft=perl:<br />
<br />
----</div>Tharnhttps://rt-wiki.bestpractical.com/index.php?title=AddWatchersOnCorrespond&diff=26345AddWatchersOnCorrespond2016-08-13T23:22:00Z<p>Tharn: </p>
<hr />
<div>= AddWatchersOnCorrespond =<br />
<br />
This RT [[Scrip]] will add the person making the correspondence as a [[Watcher]] to the ticket if they are not already a [[Watcher]], also, if the transaction originated from an email message, the script will scan the email headers and add all other recipients to the ticket as [[Watcher]]s (if they are not yet [[Watcher]]s). This can be used to complement the [[ParseNewMessageForTicketCcs]] [[SiteConfig]] option, which is part of the [[EmailInterface]] and does the same thing when tickets are created.<br />
<br />
In our RT setup, we have a group named '''general''' which contains all admins for our site. If the user this [[Scrip]] is going to add as a [[Watcher]] to the ticket is also a member of the '''general''' group, then the Scrip will add them as an [[AdminCC]] [[Watcher]] instead of a [[CC]] [[Watcher]]. It should be fairly easy for others who do not need this feature to remove or modify this [[Scrip]] accordingly.<br />
<br />
I wrote this [[Scrip]] to replace the patch we used to make to the RT Email Interface code, called [[ParseFollowupMessageForTicketCcs]]. Accordingly, it carries the same security warning about allowing basically '''anyone''' to add themselves to '''any''' ticket, simply by sending an appropriately formatted email. Some RT sites might not want this behavior, but it is necessary for us.<br />
<br />
Changelog<br />
<br />
2012-01-01: Fixed a bug in which the owner of the ticket would have been added as a Cc: for every reply he made. (HaimDimer)<br />
<br />
----<br />
<br />
'''Description:''' [[AddWatchersOnCorrespond]]<br />
<br />
'''Condition:''' On Correspond<br />
<br />
'''Action:''' User Defined<br />
<br />
'''Template:''' Global template: Blank<br />
<br />
'''Stage:''' [[TransactionBatch]]<br />
<br />
'''Custom condition:'''<br />
<br />
'''Custom action preparation code:''' <code>return 1;</code><br />
<br />
'''Custom action cleanup code:'''<br />
<br />
----<br />
<pre><br />
# Get some info:<br />
my $scrip = 'Scrip:AddWatchersOnCorrespond';<br />
my $Transaction = $self->TransactionObj;<br />
my $EmailAddr = $self->TransactionObj->CreatorObj->EmailAddress;<br />
my $Queue = $self->TicketObj->QueueObj;<br />
my $Ticket = $self->TicketObj;<br />
my $Id = $self->TicketObj->id;<br />
<br />
# Extract a list of people associated with this transaction:<br />
# - including the transaction creator, and if it is an email, the sender and recipients of that email message.<br />
my @People = ($EmailAddr);<br />
foreach my $h (qw(From To Cc)) {<br />
my $header = $Transaction->Attachments->First->GetHeader($h);<br />
my @addr = Mail::Address->parse($header);<br />
foreach my $addrobj (@addr) {<br />
my $addr = lc $RT::Nobody->UserObj->CanonicalizeEmailAddress($addrobj->address);<br />
# Ignore the specific addresses for this queue:<br />
next if lc $Queue->CorrespondAddress eq $addr;<br />
next if lc $Queue->CommentAddress eq $addr;<br />
# Ignore any email address that looks like one for ANY of our queues:<br />
next if RT::EmailParser->IsRTAddress($addr);<br />
$RT::Logger->debug("$scrip: Ticket #$Id correspondence contains header - $h: $addr");<br />
push @People, $addr;<br />
}<br />
}<br />
<br />
# Lookup the 'experts' (general) group to use below:<br />
my $Experts = RT::Group->new($self->CurrentUser);<br />
$Experts->LoadUserDefinedGroup('general');<br />
<br />
# Now check if each user is already watching the ticket or queue:<br />
foreach my $addr (@People) {<br />
my $User = RT::User->new($RT::SystemUser);<br />
$User->LoadOrCreateByEmail($addr);<br />
my $Name = $User->Name;<br />
my $Principal = $User->PrincipalId;<br />
if ( not ($Queue->IsWatcher(Type => 'Cc', PrincipalId => $Principal) or<br />
$Queue->IsWatcher(Type => 'AdminCc', PrincipalId => $Principal) or<br />
$Ticket->IsWatcher(Type => 'Cc', PrincipalId => $Principal) or<br />
$Ticket->IsWatcher(Type => 'AdminCc', PrincipalId => $Principal) or<br />
$Ticket->IsWatcher(Type => 'Requestor', PrincipalId => $Principal) or<br />
$Ticket->IsOwner($User) )) {<br />
# If the user is a member of the experts group, then add them as an AdminCc, otherwise as a normal Cc:<br />
my $type = 'Cc';<br />
$type = 'AdminCc' if $Experts->HasMember($User->PrincipalObj);<br />
# Add the new watcher now and check for errors:<br />
my ($ret, $msg) = $Ticket->AddWatcher(Type => $type, PrincipalId => $Principal);<br />
if ($ret) {<br />
$RT::Logger->info("$scrip: New $type watcher added to ticket #$Id: $addr (#$Principal)");<br />
} else {<br />
$RT::Logger->error("$scrip: Failed to add new $type watcher to ticket #$Id: $addr (#$Principal) - $msg");<br />
}<br />
}<br />
}<br />
<br />
return 1;<br />
</pre><br />
<br />
# vim:ft=perl:<br />
<br />
----</div>Tharnhttps://rt-wiki.bestpractical.com/index.php?title=ForkIntoNewTicket&diff=26344ForkIntoNewTicket2016-08-13T23:18:22Z<p>Tharn: </p>
<hr />
<div>[[HomePage]] &gt; [[Contributions]] &gt; [[ForkIntoNewTicket]]<br />
<br />
= ForkIntoNewTicket =<br />
<br />
== Summary ==<br />
<br />
I use this scrip associated with a condition scrip that only catch correspondence/comment to a closed ticket. This scrip create a new ticket based on the correspondence/comment (subject,to,from,data).<br />
<br />
It also copy attachments, and add links beetween the two tickets.<br />
<br />
== The scrip action ==<br />
<br />
In order to install it in RT I advise you to create a $RT/local/lib/RT/Action directory and put the scrip ih this directory.<br />
<br />
<pre><br />
$cat $RT/local/lib/RT/Action/ForkIntoNewTicket.pm<br />
package RT::Action::ForkIntoNewTicket;<br />
use strict;<br />
use base qw(RT::Action::Generic);<br />
#What does this type of Action doeszxczxc<br />
# {{{ sub Describe<br />
sub Describe {<br />
my $self = shift;<br />
return (ref $self . " Fork into a new ticket, the last correspondence / comment.");<br />
}<br />
# }}}<br />
# {{{ sub Prepare<br />
sub Prepare {<br />
# nothing to prepare<br />
return 1;<br />
}<br />
# }}}<br />
sub Commit {<br />
my $self = shift;<br />
my $ticket = $self-&gt;TicketObj;<br />
my $transaction = $self-&gt;TransactionObj;<br />
## retrieve original message<br />
my $MIMEObj = MIME::Entity-&gt;build(To =&gt; $transaction-&gt;Attachments-&gt;First-&gt;GetHeader('To'),<br />
From =&gt; $transaction-&gt;Attachments-&gt;First-&gt;GetHeader('From'),<br />
Subject =&gt; $transaction-&gt;Subject,<br />
Date =&gt; $transaction-&gt;Attachments-&gt;First-&gt;GetHeader('Date'),<br />
Encoding =&gt; '-SUGGEST',<br />
Data =&gt; 'Réponse reçue concernant le ticket résolu #'.$ticket-&gt;Id.":\n\n".$transaction-&gt;Content<br />
);<br />
<br />
RT::I18N::SetMIMEEntityToUTF8($MIMEObj);<br />
## don't forget the attachments<br />
my $transaction_content_obj = $transaction-&gt;ContentObj;<br />
my $attachments = $transaction-&gt;Attachments;<br />
while (my $attachment = $attachments-&gt;Next) {<br />
# don't attach blank file<br />
next unless ($attachment-&gt;ContentLength || $attachment-&gt;Filename);<br />
# don't attach the message itself...<br />
next if (<br />
$transaction_content_obj<br />
&amp;&amp; $transaction_content_obj-&gt;Id == $attachment-&gt;Id<br />
&amp;&amp; $transaction_content_obj-&gt;ContentType =~ qr{text/plain}i<br />
);<br />
$MIMEObj-&gt;attach(<br />
Type =&gt; $attachment-&gt;ContentType,<br />
Charset =&gt; $attachment-&gt;OriginalEncoding,<br />
Data =&gt; $attachment-&gt;OriginalContent,<br />
Filename =&gt; Encode::decode_utf8($attachment-&gt;Filename),<br />
Encoding =&gt; '-SUGGEST'<br />
);<br />
}<br />
# new ticket (cross link is automatic)<br />
my $child_ticket = RT::Ticket-&gt;new($RT::SystemUser);<br />
my $user = RT::User-&gt;new($RT::SystemUser);<br />
$user-&gt;Load($transaction-&gt;Creator);<br />
my $mail = $user-&gt;EmailAddress;<br />
my ($child_id, $child_TransObj, $errorMsg) =<br />
$child_ticket-&gt;Create(<br />
Queue =&gt; $ticket-&gt;Queue,<br />
Subject =&gt; '--&gt; ticket #'.$ticket-&gt;Id.':'.$ticket-&gt;Subject,<br />
RefersTo =&gt; $ticket-&gt;Id,<br />
MIMEObj =&gt; $MIMEObj,<br />
Requestor =&gt; $mail<br />
);<br />
## add a comment to the resolved ticket to inform that we have open a new ticket<br />
$ticket-&gt;Comment(<br />
Content =&gt; 'Suite à la correspondance précédente, un nouveau ticket a été ouvert : ticket #'.$child_ticket-&gt;Id);<br />
unless ($child_id) {<br />
$RT::Logger-&gt;debug("&gt;&gt;Error : ". $errorMsg);<br />
return undef<br />
}<br />
}<br />
eval "require RT::Action::ForkIntoNewTicket_Vendor";<br />
die $@ if ($@ &amp;&amp; $@ !~ qr{^Can't locate RT/Action/ForkIntoNewTicket_Vendor.pm});<br />
eval "require RT::Action::ForkIntoNewTicket_Local";<br />
die $@ if ($@ &amp;&amp; $@ !~ qr{^Can't locate RT/Action/ForkIntoNewTicket_Local.pm});<br />
1;<br />
</pre><br />
<br />
== installation ==<br />
<br />
Once we have created the scrip action we can inform RT of it. Execute the following script exactly one times. It will not make any output and can be removed afterwards.<br />
<br />
<pre><br />
$cat createForkIntoNewTicket.pl<br />
#!/home/rt/perl/bin/perl<br />
use strict;<br />
use Unicode::String qw(utf8 latin1);<br />
# Replace this with your RT_LIB_PATH<br />
use lib "/home/rt/rt/lib";<br />
# Replace this with your RT_ETC_PATH<br />
use lib "/home/rt/rt/etc";<br />
use RT;<br />
use RT::Interface::CLI qw( CleanEnv GetCurrentUser );<br />
use RT::ScripAction;<br />
##########################################################################<br />
### RT CLI initialization<br />
CleanEnv();<br />
RT::LoadConfig();<br />
RT::Init();<br />
##Drop setgid permissions<br />
RT::DropSetGIDPermissions();<br />
##Get the current user all loaded<br />
our $CurrentUser = GetCurrentUser();<br />
unless( $CurrentUser-&gt;Id )<br />
{<br />
print "No RT user found. Please consult your RT administrator.\n";<br />
exit 1;<br />
}<br />
my $sa = new RT::ScripAction($CurrentUser);<br />
$sa-&gt;Create(<br />
Name =&gt; "Fork la réponse dans un nouveau ticket",<br />
Description =&gt; "Fork la réponse dans un nouveau ticket",<br />
ExecModule =&gt; 'ForkIntoNewTicket'<br />
);<br />
<br />
</pre><br />
<br />
This script have to be run to use new action in web interface.<br />
<br />
$ perl createNotResolvedCondition.pl<br />
<br />
==== NOTE: ====<br />
<br />
In RT3.2 there is no sub named [[DropSetGIDPermissions]]() in lib/RT.pm, so I commented that line and it works.<br />
<br />
== problems ==<br />
<br />
If you meet problem it may be necessary to copy some files into local directory :<br />
<br />
$cp $RT/lib/RT/base.pm $RT/local/lib/RT<br />
$cp $RT/lib/RT/Action/Generic.pm $RT/local/lib/RT/Action<br />
<br />
== conclusion ==<br />
<br />
I use it with the scrip condition [[ReplyToResolved]] with global scrip On Reply Open disabled<br />
<br />
--</div>Tharnhttps://rt-wiki.bestpractical.com/index.php?title=OnQueueChangeResetPriorityAndDueDate&diff=26343OnQueueChangeResetPriorityAndDueDate2016-08-13T23:16:17Z<p>Tharn: /* Custom action cleanup code: */</p>
<hr />
<div>==On Queue Change Set Priority And DueDate==<br />
This Scrip will reset the priority and due dates to the default queue values on a ticket when the queue is change.<br />
*'''Description''': On Queue Change Set Priority and DueDate<br />
*'''Condition''': On Queue Change<br />
*'''Action''': User Defined<br />
*'''Template''': Global template: Blank<br />
*'''Stage''': TransactionBatch<br />
===Custom Condition:===<br />
# blank<br />
===Custom action preparation code:===<br />
return 1;<br />
===Custom action cleanup code:===<br />
<pre><br />
my $queue_id = $self->TransactionObj->NewValue;<br />
my $queue = RT::Queue->new( $RT::SystemUser );<br />
$queue->Load( $queue_id );<br />
<br />
# Reset Priority<br />
unless ( $self->TicketObj->Priority == $queue->InitialPriority ) {<br />
$RT::Logger->info("On Queue Change set ticket #". $self->TicketObj->id ." priority to ". $queue->InitialPriority );<br />
my ( $status, $msg ) = $self->TicketObj->SetPriority( $queue->InitialPriority );<br />
unless ( $status ) {<br />
$RT::Logger->error( "Unable to assign priority to ". $queue->InitialPriority );<br />
$RT::Logger->error( "Message: $msg" );<br />
}<br />
}<br />
# Reset Final Priority<br />
unless ( $self->TicketObj->FinalPriority == $queue->FinalPriority ) {<br />
$RT::Logger->info("On Queue Change set ticket #". $self->TicketObj->id ." final priority to ". $queue->FinalPriority );<br />
my ( $status, $msg ) = $self->TicketObj->SetFinalPriority( $queue->FinalPriority );<br />
unless ( $status ) {<br />
$RT::Logger->error( "Unable to assign final priority to ". $queue->FinalPriority );<br />
$RT::Logger->error( "Message: $msg" );<br />
}<br />
} <br />
<br />
# Reset Due Date<br />
my $due_date = RT::Date->new( $RT::SystemUser );<br />
$due_date->Set( Format => 'ISO', Value => $self->TicketObj->Due );<br />
<br />
# If queue doesn't specify due date, then clear this tickets due date.<br />
# Otherwise, add that many days.<br />
if ( $queue->DefaultDueIn == 0 ) {<br />
$due_date->Set( Format => 'ISO', Value => 0 );<br />
} else {<br />
$due_date->SetToNow;<br />
$due_date->AddDays( $queue->DefaultDueIn );<br />
}<br />
<br />
$RT::Logger->info("On Queue Change set ticket #". $self->TicketObj->id ." due date to ". $due_date->ISO );<br />
my ( $status, $msg ) = $self->TicketObj->SetDue( $due_date->ISO );<br />
unless ( $status ) {<br />
$RT::Logger->error( "Unable to assign due date to ". $due_date->ISO );<br />
}<br />
return 1;<br />
</pre></div>Tharnhttps://rt-wiki.bestpractical.com/index.php?title=OnCreateAddGroupCc&diff=26342OnCreateAddGroupCc2016-08-13T23:15:21Z<p>Tharn: </p>
<hr />
<div>= On Create Add Group Cc =<br />
<br />
I needed to add several [[CCs]] at an OEM partner whenever someone from the partner opened a ticket. It would be problematic if I skipped the entire group because one of them was already a CC or requestor, so I reworked the [[OnCreateSetDeptHeadCc]] scrip to eliminate the bug mentioned therein. The solution was to refactor so that the main loop iterates through the members of the group to add, and adds them if they are not already [[CCs]] or Requestors -- instead of iterating through the CC and Requestor lists to see if any of the principals are members of the group to add.<br />
<br />
* <nowiki>Description: ''01 On Create Add Group </nowiki>[[CCs]]<nowiki>'' </nowiki><br />
* <nowiki>Condition: ''On Create'' </nowiki><br />
* <nowiki>Action: ''User Defined'' </nowiki><br />
* <nowiki>Template: ''Global template: Blank'' </nowiki><br />
* <nowiki>Stage: ''</nowiki>[[TransactionCreate]]<nowiki>'' </nowiki><br />
<br />
* Custom condition: (blank)<br />
* Custom action preparation code:<br />
<br />
<pre><br />
$RT::Logger-&gt;debug("On Create Add Group CCs: entering custom action prep");<br />
my $ticket = $self-&gt;TicketObj;<br />
my $transaction = $self-&gt;TransactionObj;<br />
my $derivedGroupName = 'partner-OEM-CClist';<br />
<br />
# match any ticket created with a Requestor list that includes @*oem.com addresses<br />
my $OEMregex = '(?i).*@oem.com[,$]|.*@[\w\-]\.oem.com[,$]';<br />
if ($ticket-&gt;RequestorAddresses =~ m/$OEMregex/) {<br />
$RT::Logger-&gt;debug("On Create Add Group CC: matched an OEM address in Requestor list");<br />
} else {<br />
$RT::Logger-&gt;debug("On Create Add Group CC: no match: '" . $ticket-&gt;RequestorAddresses . "'");<br />
return undef;<br />
}<br />
<br />
# instantiate a group object<br />
my $addGroupObj = RT::Group-&gt;new($RT::SystemUser);<br />
$addGroupObj-&gt;LoadUserDefinedGroup($derivedGroupName);<br />
return undef unless $addGroupObj;<br />
my $addGroupMembersObj = $addGroupObj-&gt;UserMembersObj;<br />
<br />
my $userObj;<br />
# walk through members of group to add; if a given member is not already on the ticket, add to the CC list<br />
while ($userObj = $addGroupMembersObj-&gt;Next) {<br />
if (($ticket-&gt;IsRequestor($userObj-&gt;PrincipalId)) or ($ticket-&gt;IsCc($userObj-&gt;PrincipalId))) {<br />
$RT::Logger-&gt;debug("On Create Add Group CC: '" . $userObj-&gt;Name . "' is already a ticket watcher; not adding Cc on ticket \#" . $ticket-&gt;id );<br />
} else {<br />
$RT::Logger-&gt;debug("On Create Add Group CC: Adding '" . $userObj-&gt;Name ."' to ticket \#" .$ticket-&gt;id);<br />
my ($success, $msg)= $ticket-&gt;AddWatcher(<br />
Type =&gt; "Cc",<br />
PrincipalId =&gt; $userObj-&gt;PrincipalId);<br />
if (! $success) {<br />
$RT::Logger-&gt;info("On Create Add Group CC: couldn't add '" . $userObj-&gt;Name . "' to " . $ticket-&gt;id . "': got '" . $msg ."'");<br />
}<br />
}<br />
}<br />
return 1;<br />
<br />
<br />
</pre><br />
<br />
* Custom action cleanup code: (blank)<br />
<br />
Ole Craig Fri Sep 14 17:19:00 MDT 2007<br />
<br />
= RT 3.8.6 =<br />
<br />
Worked as is - great recipe. Thanks for sharing.</div>Tharnhttps://rt-wiki.bestpractical.com/index.php?title=CcManagers&diff=26341CcManagers2016-08-13T23:14:24Z<p>Tharn: /* Code */</p>
<hr />
<div>= Overview =<br />
If you have a similar group structure like the one defined in [[Rights|rights]] (see section departments of a company as groups), you might need that the users of each departments are not able to see the tickets of the queues and that managers of those departments can do. If creating a queue for each group is not an option, keep reading.<br />
<br />
Note that I habe been using RT for over a week now; this is my very first custom action, so there might be ways of improving it.<br />
<br />
--[[User:Gmirandaupc|Gmirandaupc]] 10:50, December 13, 2010 (UTC)<br />
<br />
= Custom action code =<br />
== Parameters ==<br />
The code assumes that there is a top-level group that must be passed as a parameter (in the installation script). I call that group 'Customers', although it could be just 'Departments'. There is also a hardcoded value, the suffix of the manager groups. Each department group with managers must have a subgroup whose name is the name of the parent group succeeded by 'Mgr'.<br />
<br />
Sample structure:<br />
* Customers<br />
** OrderDept<br />
*** OrderDeptMgr<br />
** TeacherDept<br />
*** TeacherDeptMgr<br />
<br />
== Install script ==<br />
# To install, install CcManagers.pm in local/lib/RT/Action, and<br />
# this script in local/etc/CcManagers.install<br />
# /opt/rt3/sbin/rt-setup-database --action insert \<br />
# --datafile /opt/rt3/local/etc/CcManagers.install<br />
#<br />
<br />
<br />
@ScripActions = (<br />
{<br />
Name => 'On Create Add Manager Group CC',<br />
Description => 'When a ticket is created, if the user belongs to a group under a predefined parent group (the argument), the manager subgroup will be added as Cc.',<br />
ExecModule => 'CcManagers',<br />
Argument => 'Customers'<br />
},<br />
);<br />
<br />
== Code ==<br />
Save the following code to the local action lib dir (i.e. /opt/rt3/local/lib/RT/Action) as CcManagers.pm<br />
<pre><br />
#<br />
# This custom action sets the users of the manager group of the ESSI or LSI<br />
# departments as CC of the ticket, depending on the group of the user that<br />
# created the ticket.<br />
#<br />
# @author Guillermo Miranda Álamo - LCLSI UPC <gmiranda@lsi.upc.edu><br />
# @date 09/12/2010 (dd/mm/yyyy).<br />
#<br />
<br />
package RT::Action::CcManagers;<br />
use base qw(RT::Action::Generic);<br />
<br />
use RT::Groups;<br />
<br />
my $actionDesc = "On Create Add Manager Group CC";<br />
<br />
<br />
=head2 Prepare<br />
<br />
When the ticket is created, if the user is in a group (or more) of 'Customers'<br />
(note that 'Customers' is the argument), the manager subgroup of that group<br />
will be added as Cc.<br />
<br />
It is mandatory that the managers subgroup are named with a suffix "Mgr".<br />
So if the group name is "TestingDept", the managers must be in "TestingDeptMgr",<br />
a subgroup of "TestingDept".<br />
<br />
=cut<br />
<br />
sub Prepare {<br />
my $self = shift;<br />
<br />
$RT::Logger->debug( "[" . $actionDesc . "] Custom action prep init. Argument is " . $self->Argument );<br />
<br />
my $ticket = $self->TicketObj;<br />
my $transaction = $self->TransactionObj;<br />
#my $owner = $ticket->OwnerObj; #nobody<br />
my $owner = RT::User->new( $RT::SystemUser );<br />
$owner->Load( $transaction->Creator );<br />
my $queue = $ticket->QueueObj;<br />
<br />
# Change this, it should be a user-defined value<br />
my $managerSuffix = "Mgr"; # The suffix of the name of every manager group<br />
my $parentGroupName = $self->Argument; # The name of the parent group of all the departments<br />
<br />
my $parentGroupObj = RT::Group->new( $RT::SystemUser );<br />
$parentGroupObj->LoadUserDefinedGroup( $parentGroupName );<br />
<br />
return undef unless $parentGroupObj;<br />
<br />
my $ownerGroups = RT::Groups->new( $RT::SystemUser );<br />
#$ownerGroups->LimitToRolesForQueue( $queue->Id ); # Doesn't seem to work<br />
$ownerGroups->LimitToUserDefinedGroups();<br />
$ownerGroups->UnLimit();<br />
<br />
# For each group<br />
while( my $ownerGroup = $ownerGroups->Next() ){<br />
# If it's a department and the user is in that department<br />
if( $parentGroupObj->HasMember( $ownerGroup->Id ) ){<br />
$RT::Logger->info( "[" . $actionDesc . "] Checking group '"<br />
. $ownerGroup->Name . "' (" . $ownerGroup->Id . ") for user "<br />
. $owner->Name . "-" . $owner->PrincipalId );<br />
<br />
if( $ownerGroup->HasMember( $owner->Id ) ){<br />
$RT::Logger->info( "[" . $actionDesc . "] User is member of '" <br />
. $ownerGroup->Name . "' (" . $ownerGroup->Id . ")" );<br />
<br />
# This action will be performed for each dept the user belongs to<br />
my $groupName = $ownerGroup->Name . $managerSuffix;<br />
<br />
# $RT::Logger->info( "[" . $actionDesc . "] Manager group name should be " . $groupName );<br />
# Retrieve the group<br />
my $groupObj = RT::Group->new( $RT::SystemUser );<br />
<br />
$groupObj->LoadUserDefinedGroup( $groupName );<br />
# If the group doesn't have a manager subgroup, skip<br />
if( !$groupObj ){<br />
next;<br />
}<br />
<br />
$RT::Logger->debug( "[" . $actionDesc . "] Adding '" . $groupName ."'(" . $groupObj->Id . ") to ticket \#" .$ticket->id );<br />
<br />
# Add the group (as a group, not every user of the group) as Cc<br />
$ticket->AddWatcher( Type => 'Cc',<br />
PrincipalId => $groupObj->PrincipalId );<br />
}<br />
}<br />
}<br />
<br />
return 1;<br />
}<br />
<br />
sub Commit{<br />
my $self = shift;<br />
<br />
return 1;<br />
}<br />
<br />
1;<br />
<br />
</pre><br />
<br />
You can use the following install script<br />
<br />
= Scrip creation =<br />
Condition: Create<br />
Action: On Create Add Manager Group Cc<br />
Template: Global template: Blank<br />
Stage: TransactionCreate<br />
<br />
= Credits =<br />
This custom action is based on the code of [[OnCreateAddGroupCc]] and [[OnCreateSetDeptHeadCc]].</div>Tharnhttps://rt-wiki.bestpractical.com/index.php?title=WorkFlow2&diff=26340WorkFlow22016-08-13T23:06:32Z<p>Tharn: </p>
<hr />
<div>= Another kind of workflow for RT =<br />
<br />
This method draws heavily on the other workflow plugin and from many contributions here in general, so I definitely don't claim ownership of all the code/methods (thx guys :)) In the original workflow plugin, customfields were defined for each task and initiated manually, with (potentially) all the tasks running concurrently (as subtickets). In this model, one master ticket initiates a child ticket for the first task, when the first child is resolved the second child ticket is initiated etc. In addition, this method allows a name, description, queue, and owner for each subticket (stage) of the process. Also, this reduces the number of scrips+templates+customfields to a constant number, regardless of how many workflows you have.<br />
<br />
== Custom Field Setup ==<br />
<br />
Add 5 (ticket) custom fields and activate them globally using the following names:<br />
<br />
[[WorkflowName]]<br />
<br />
[[WorkflowStage]]<br />
<br />
[[WorkflowStageDesc]]<br />
<br />
[[WorkflowStageOwner]]<br />
<br />
[[WorkflowstageQueue]]<br />
<br />
All of these should be "enter a value" fields with the exception of [[WorkflowName]], which should be a "select one value" field.<br />
<br />
== Template Setup ==<br />
<br />
Add a single global template called "WorkflowTemplate" that consists of the following:<br />
<br />
<pre><br />
===Create-Ticket: {$Tickets{'TOP'}-&gt;FirstCustomFieldValue('WorkflowName');}::{$Tickets{'TOP'}-&gt;FirstCustomFieldValue('WorkflowStage');}<br />
Subject: Workflow::{$Tickets{'TOP'}-&gt;Subject;}<br />
Parents: TOP<br />
Queue: {$Tickets{'TOP'}-&gt;FirstCustomFieldValue('WorkflowStageQueue');}<br />
Owner: {$Tickets{'TOP'}-&gt;FirstCustomFieldValue('WorkflowStageOwner');}<br />
Content: {$Tickets{'TOP'}-&gt;FirstCustomFieldValue('WorkflowStageDesc');}<br />
ENDOFCONTENT<br />
</pre><br />
<br />
== Main Scrip Setup ==<br />
<br />
Create a global scrip;<br />
<br />
Condition: User Defined<br />
<br />
Action: Create Tickets<br />
<br />
Template: WorkflowTemplate<br />
<br />
Stage: TransactionCreate<br />
<br />
Beware, this scrip is pretty ugly. Basically, it's a ticket creation scrip that performs a lot more than condition checking in the condition section (i didnt wanna mess around with making a new ticket from a template in the scrip, and this was easy).<br />
<br />
Paste the code below into the "Custom Condition" text box<br />
<br />
<pre><br />
#example workflow. Whatever you do, do NOT make '|' (a pipe symbol) the first character in a stagename<br />
my @newcompworkflow = (<br />
# stagename stagedesc owner queue<br />
["AskUserForSoftware", "please ask the user what specialized software they would like", "person1", "mainhelpqueue"],<br />
["OrderNewComputer", "please order the user's new computer", "person1", "purchases"],<br />
["ImageMachine","please image the user's new machine","person2", "mainhelpqueue"],<br />
["blah","please blah blah blah","person2","someotherqueue"],<br />
);<br />
my %workflows = ();<br />
#Set workflow name to 'New Computer'<br />
#Make as many workflows you want like the example above and define them here.<br />
#The name here ('New Computer') will correspond to the possible values for the customfield WorkflowName, so add them as values there.<br />
$workflows{'New Computer'} = \@newcompworkflow;<br />
my $stagename = 0;<br />
my $stagedesc = 1;<br />
my $stageowner = 2;<br />
my $stagequeue = 3;<br />
my $wfn = 'WorkflowName';<br />
my $wfs = 'WorkflowStage';<br />
my $wfsd = 'WorkflowStageDesc';<br />
my $wfso = 'WorkflowStageOwner';<br />
my $wfsq = 'WorkflowStageQueue';<br />
<br />
my $QueueObj = $self-&gt;TicketObj-&gt;QueueObj;<br />
my $wfnobj = RT::CustomField-&gt;new( $QueueObj-&gt;CurrentUser );<br />
$wfnobj-&gt;LoadByNameAndQueue( Name =&gt; $wfn, Queue =&gt; $QueueObj-&gt;id );<br />
unless( $wfnobj-&gt;id ) {<br />
$wfnobj-&gt;LoadByNameAndQueue( Name =&gt; $wfn, Queue =&gt; 0 );<br />
unless( $wfnobj-&gt;id ) {<br />
$RT::Logger-&gt;warning("custom field '$wfn' isn't global or defined for queue '". $QueueObj-&gt;Name ."'");<br />
return undef;<br />
}<br />
}<br />
<br />
my $wfsobj = RT::CustomField-&gt;new( $QueueObj-&gt;CurrentUser );<br />
$wfsobj-&gt;LoadByNameAndQueue( Name =&gt; $wfs, Queue =&gt; $QueueObj-&gt;id );<br />
unless( $wfsobj-&gt;id ) {<br />
$wfsobj-&gt;LoadByNameAndQueue( Name =&gt; $wfs, Queue =&gt; 0 );<br />
unless( $wfsobj-&gt;id ) {<br />
$RT::Logger-&gt;warning("custom field '$wfs' isn't global or defined for queue '". $QueueObj-&gt;Name ."'");<br />
return undef;<br />
}<br />
}<br />
my $wfsdobj = RT::CustomField-&gt;new( $QueueObj-&gt;CurrentUser );<br />
$wfsdobj-&gt;LoadByNameAndQueue( Name =&gt; $wfsd, Queue =&gt; $QueueObj-&gt;id );<br />
unless( $wfsdobj-&gt;id ) {<br />
$wfsdobj-&gt;LoadByNameAndQueue( Name =&gt; $wfsd, Queue =&gt; 0 );<br />
unless( $wfsdobj-&gt;id ) {<br />
$RT::Logger-&gt;warning("custom field '$wfsd' isn't global or defined for queue '". $QueueObj-&gt;Name ."'");<br />
return undef;<br />
}<br />
}<br />
<br />
my $wfsoobj = RT::CustomField-&gt;new( $QueueObj-&gt;CurrentUser );<br />
$wfsoobj-&gt;LoadByNameAndQueue( Name =&gt; $wfso, Queue =&gt; $QueueObj-&gt;id );<br />
unless( $wfsoobj-&gt;id ) {<br />
$wfsoobj-&gt;LoadByNameAndQueue( Name =&gt; $wfso, Queue =&gt; 0 );<br />
unless( $wfsoobj-&gt;id ) {<br />
$RT::Logger-&gt;warning("custom field '$wfso' isn't global or defined for queue '". $QueueObj-&gt;Name ."'");<br />
return undef;<br />
}<br />
}<br />
my $wfsqobj = RT::CustomField-&gt;new( $QueueObj-&gt;CurrentUser );<br />
$wfsqobj-&gt;LoadByNameAndQueue( Name =&gt; $wfsq, Queue =&gt; $QueueObj-&gt;id );<br />
unless( $wfsqobj-&gt;id ) {<br />
$wfsqobj-&gt;LoadByNameAndQueue( Name =&gt; $wfsq, Queue =&gt; 0 );<br />
unless( $wfsqobj-&gt;id ) {<br />
$RT::Logger-&gt;warning("custom field '$wfsq' isn't global or defined for queue '". $QueueObj-&gt;Name ."'");<br />
return undef;<br />
}<br />
}<br />
<br />
#See if this ticket is part of a workflow<br />
my $wfname = $self-&gt;TicketObj-&gt;FirstCustomFieldValue('WorkflowName');<br />
my @workflow;<br />
if ($wfname ne '') {<br />
@workflow = @{$workflows{$wfname}};<br />
}<br />
<br />
#If the customfield workflowname was just set, then set workflowstage to the first stage of that workflow<br />
if ( ( $self-&gt;TransactionObj-&gt;Type eq 'CustomField' ) &amp;&amp;<br />
($self-&gt;TransactionObj-&gt;Field == $wfnobj-&gt;id) ) {<br />
<br />
if ($workflows{$wfname}) {<br />
my( $st, $msg ) = $self-&gt;TicketObj-&gt;AddCustomFieldValue(<br />
Field =&gt; $wfsobj-&gt;id,<br />
Value =&gt; $workflow[0][$stagename],<br />
RecordTransaction =&gt; 1 );<br />
}<br />
return undef;<br />
#even though this did what we wanted it to, return undef because we have no email to send at this point<br />
}<br />
#if we dont have a workflow by now, bail<br />
if (!@workflow) {<br />
return undef;<br />
}<br />
#if the workflowstage is prefixed by a '|', advance to the next workflow stage in the sequence, or 'completed' if it's done<br />
#because completion generates no notice, i'd recommend having the last item in your workflow be a notification of the responsible party<br />
<br />
if ( ($self-&gt;TransactionObj-&gt;Type eq "CustomField") &amp;&amp;<br />
($self-&gt;TransactionObj-&gt;Field == $wfsobj-&gt;id) ) {<br />
if (substr($self-&gt;TicketObj-&gt;FirstCustomFieldValue('WorkflowStage'), 0, 1) eq '|') {<br />
my $x = 0;<br />
for ($x=0;$x&lt;@workflow;$x++) {<br />
if ($workflow[$x][$stagename] eq substr($self-&gt;TicketObj-&gt;FirstCustomFieldValue('WorkflowStage'), 1)) {<br />
my( $st, $msg ) = $self-&gt;TicketObj-&gt;AddCustomFieldValue(<br />
Field =&gt; $wfsobj-&gt;id,<br />
Value =&gt; ($x == $#workflow) ? 'Completed' : $workflow[$x+1][$stagename],<br />
RecordTransaction =&gt; 1 );<br />
}<br />
}<br />
return undef;<br />
}<br />
#If the workflowstage was just set, update the corresponding description, owner, and queue, and send off the new ticket<br />
my $x = 0;<br />
for ($x =0;$x&lt;@workflow;$x++) {<br />
if ($workflow[$x][$stagename] eq $self-&gt;TicketObj-&gt;FirstCustomFieldValue('WorkflowStage')) {<br />
my( $st, $msg ) = $self-&gt;TicketObj-&gt;AddCustomFieldValue(<br />
Field =&gt; $wfsdobj-&gt;id,<br />
Value =&gt; $workflow[$x][$stagedesc],<br />
RecordTransaction =&gt; 0 );<br />
<br />
my( $st, $msg ) = $self-&gt;TicketObj-&gt;AddCustomFieldValue(<br />
Field =&gt; $wfsoobj-&gt;id,<br />
Value =&gt; $workflow[$x][$stageowner],<br />
RecordTransaction =&gt; 0 );<br />
my( $st, $msg ) = $self-&gt;TicketObj-&gt;AddCustomFieldValue(<br />
Field =&gt; $wfsqobj-&gt;id,<br />
Value =&gt; $workflow[$x][$stagequeue],<br />
RecordTransaction =&gt; 0 );<br />
<br />
return 1;<br />
}<br />
}<br />
<br />
}<br />
return undef;<br />
<br />
</pre><br />
<br />
I apologize for the terrible formatting, if someone has a perl formatter handy feel free to have at it ;)<br />
<br />
== Stage Advancer scrip ==<br />
<br />
The second scrip (also global) is responsible for changing the workflowstage from "stagename" to "|stagename" on the resolution of a child ticket. This is all that is necessary for the main scrip to notice that it is time to advance to the next stage.<br />
<br />
Readers Note: <sigh> Condition? Action? Template? Stage?<br />
<br />
<pre><br />
my $MemberOf = $self-&gt;TicketObj-&gt;MemberOf;<br />
my $l = $MemberOf-&gt;Next;<br />
my $parentObj = $l-&gt;TargetObj;<br />
<br />
my $QueueObj = $parentObj-&gt;QueueObj;<br />
my $wfs = 'WorkflowStage';<br />
my $wfsobj = RT::CustomField-&gt;new( $QueueObj-&gt;CurrentUser );<br />
$wfsobj-&gt;LoadByNameAndQueue( Name =&gt; $wfs, Queue =&gt; $QueueObj-&gt;id );<br />
unless( $wfsobj-&gt;id ) {<br />
$wfsobj-&gt;LoadByNameAndQueue( Name =&gt; $wfs, Queue =&gt; 0 );<br />
unless( $wfsobj-&gt;id ) {<br />
$RT::Logger-&gt;warning("custom field '$wfs' isn't global or defined for queue '". $QueueObj-&gt;Name ."'");<br />
return undef;<br />
}<br />
}<br />
my $curval = $parentObj-&gt;FirstCustomFieldValue('WorkflowStage');<br />
<br />
if ($curval ne '') {<br />
my( $st, $msg ) = $parentObj-&gt;AddCustomFieldValue(<br />
Field =&gt; $wfsobj-&gt;id,<br />
Value =&gt; '|' . $curval,<br />
RecordTransaction =&gt; 1 );<br />
return 1;<br />
}<br />
return undef;<br />
</pre><br />
<br />
== Kicking it off ==<br />
<br />
I don't know how to make it work on create because the object doesnt exist yet, but if you create a ticket and then update the workflowname, the scrip will automatically initiate the first stage and create the first child ticket. As explained in the code comments, I'd recommend having the last phase of a workflow be creating a ticket to notify the responsible party that the sequence is complete, because currently nothing happens after the last step.<br />
<br />
Hope this helps someone</div>Tharnhttps://rt-wiki.bestpractical.com/index.php?title=ReplyToResolved&diff=26339ReplyToResolved2016-08-13T22:57:47Z<p>Tharn: </p>
<hr />
<div>[[HomePage]] &gt; [[Contributions]] &gt; [[ReplyToResolved]]<br />
<br />
= ReplyToResolved =<br />
<br />
== Introduction ==<br />
<br />
this is a simple scrip to catch correspondence/reply to ticket marked 'resolved'<br />
<br />
== the scrip condition ==<br />
<br />
Save this file as e.g. /usr/local/share/request-tracker3.6/lib/RT/Condition/[[ReplyToResolvedTicket]].pm<br />
<br />
package RT::Condition::ReplyToResolvedTicket;<br />
use strict;<br />
use base qw(RT::Condition::Generic); # thanks Ruslan for the suggestion<br />
sub IsApplicable {<br />
my $self = shift;<br />
my $ticket = $self-&gt;TicketObj;<br />
my $transaction = $self-&gt;TransactionObj;<br />
if ((($transaction-&gt;Type eq 'Correspond') || ($transaction-&gt;Type eq 'Comment')) &amp;&amp;<br />
$ticket-&gt;Status eq 'resolved' &amp;&amp;<br />
$transaction-&gt;Creator != 1) { # prevent loop<br />
return(1);<br />
}<br />
else {<br />
return(undef);<br />
}<br />
}<br />
eval "require RT::Condition::ReplyToResolvedTicket_Vendor";<br />
die $@ if ($@ &amp;&amp; $@ !~ qr{^Can't locate RT/Condition/ReplyToResolved_Vendor.pm});<br />
eval "require RT::Condition::ReplyToResolved_Local";<br />
die $@ if ($@ &amp;&amp; $@ !~ qr{^Can't locate RT/Condition/ReplyToResolved_Local.pm});<br />
1;<br />
<br />
== installation ==<br />
<br />
The following Perl script is used to register i.e. active the above code in RT and is no longer necessary afterwards:<br />
<br />
<pre><br />
#!/home/rt/perl/bin/perl<br />
use strict;<br />
use Unicode::String qw(utf8 latin1);<br />
# Replace this with your RT_LIB_PATH<br />
use lib "/home/rt/rt/lib";<br />
# Replace this with your RT_ETC_PATH<br />
use lib "/home/rt/rt/etc";<br />
use RT;<br />
use RT::Interface::CLI qw( CleanEnv GetCurrentUser );<br />
use RT::ScripCondition;<br />
CleanEnv();<br />
RT::LoadConfig();<br />
RT::Init();<br />
##Drop setgid permissions<br />
RT::DropSetGIDPermissions();<br />
##Get the current user all loaded<br />
our $CurrentUser = GetCurrentUser();<br />
unless( $CurrentUser-&gt;Id )<br />
{<br />
print "No RT user found. Please consult your RT administrator.\n";<br />
exit 1;<br />
}<br />
my $sc = new RT::ScripCondition($CurrentUser);<br />
<br />
$sc-&gt;Create(<br />
Name =&gt; 'On Reply to Resolved Ticket',<br />
Description =&gt; "Lors d'une r�ponse � un ticket r�solu",<br />
ExecModule =&gt; 'ReplyToResolvedTicket',<br />
ApplicableTransTypes =&gt; 'Any'<br />
);<br />
</pre><br />
<br />
== problems ==<br />
<br />
If you face problems with this scrip take a look at the [[NotResolved]] condition scrip as is it very similar, and I have detailled it more.<br />
<br />
== conclusion ==<br />
<br />
I personally use it with the [[ForkIntoNewTicket]] scrip action in order to open a new ticket on reply to resolved ticket. I also have disabled the global scrip On Reply Open by setting "Stage:" to "disabled". For the new script use the following values:<br />
<br />
Description: Replies to resolved tickets create a new ticket.<br />
Condition: On Reply To Resolved Ticket<br />
Action: Force Correspondence into new Ticket<br />
Template: Global Template: Correspondence<br />
Stage: TransactionCreate</div>Tharnhttps://rt-wiki.bestpractical.com/index.php?title=OnWatcherChange&diff=26338OnWatcherChange2016-08-13T22:56:18Z<p>Tharn: </p>
<hr />
<div>This simple scrip condition triggers when a Cc is added to a ticket.<br />
<br />
<pre><br />
my $transactionType = $self-&gt;TransactionObj-&gt;Type;<br />
my $watcherType = $self-&gt;TransactionObj-&gt;Field;<br />
if (($transactionType eq 'AddWatcher') and ($watcherType eq 'Cc')) {<br />
return 1;<br />
}<br />
return undef;<br />
</pre><br />
<br />
Just change "Cc" in line 3 to "[[AdminCc]]" to look for that change. Original version by Taan posted to rt-users at http://www.gossamer-threads.com/lists/rt/users/62335#62335<br />
<br />
To trigger on the removal of a watcher, change "[[AddWatcher]]" in line 3 to "[[DelWatcher]]".</div>Tharnhttps://rt-wiki.bestpractical.com/index.php?title=OnResolveOnce&diff=26337OnResolveOnce2016-08-13T22:52:33Z<p>Tharn: </p>
<hr />
<div>Contributed by Russell Mosemann.<br />
<br />
=== Overview ===<br />
<br />
A user might respond to a resolved ticket, perhaps just to say, "Thanks." That opens the ticket, and when it is marked resolved, again, the user receives another autonotification that the ticket has been resolved.<br />
<br />
The scrip condition below matches when a ticket is resolved but only if the ticket has not been resolved before. That permits a user to receive an autonotification when the ticket is first resolved, but if the ticket is opened and resolved, again, no autonotification is sent. Tickets merged into this ticket are ignored, in case they were resolved in the past.<br />
<br />
=== Installing ===<br />
<br />
1. Go to Configuration-&gt;Global-&gt;Scrips<br />
<br />
2. Click on (no name) above "On Resolve Notify Requestors with template Resolved"<br />
<br />
3. Give the scrip a name, such as "[[OnResolveOnce]]"<br />
<br />
4. Change Condition to "User Defined"<br />
<br />
5. Copy the code below into the Custom condition box<br />
<br />
6. Click Create<br />
<br />
=== Scrip Code ===<br />
<br />
<pre><br />
# OnResolveOnce<br />
# This scrip condition matches only if the current transaction is<br />
# "resolved" and the current ticket has no other resolved transactions.<br />
#<br />
# First, check if the current transaction is "resolved". If so, get<br />
# the list of transactions associated with the current ticket and go<br />
# through them one by one. If the ticket number in the transaction<br />
# matches the current ticket (i.e., this isn't a transaction from a<br />
# merged ticket) and the transaction is "resolved", count it. When<br />
# we are all done, return true if we only found 1 resolved transaction<br />
# (i.e., the current one) and false otherwise.<br />
<br />
my $result = undef;<br />
<br />
if ($self-&gt;TransactionObj-&gt;Type eq "Status" &amp;&amp;<br />
$self-&gt;TransactionObj-&gt;NewValue eq "resolved")<br />
{<br />
my $trans_list = $self-&gt;TicketObj-&gt;Transactions;<br />
my $trans;<br />
my $num_resolved = 0;<br />
<br />
while ($trans = $trans_list-&gt;Next)<br />
{<br />
$num_resolved++ if ($trans-&gt;Ticket == $self-&gt;TicketObj-&gt;Id) &amp;&amp;<br />
($trans-&gt;Type eq "Status") &amp;&amp;<br />
($trans-&gt;NewValue eq "resolved");<br />
}<br />
$result = ($num_resolved &lt;= 1);<br />
}<br />
<br />
return($result);<br />
</pre></div>Tharnhttps://rt-wiki.bestpractical.com/index.php?title=OnMerge&diff=26336OnMerge2016-08-13T22:51:18Z<p>Tharn: </p>
<hr />
<div>Return true if current transaction is merge action.<br />
<br />
Custom condition code:<br />
<br />
my $txn = $self-&gt;TransactionObj;<br />
return undef unless $txn-&gt;Type =~ /^AddLink$/i;<br />
return undef unless $txn-&gt;Field =~ /^MergedInto$/i;<br />
return 1;<br />
<br />
If you need to refer to the original ticket id in a template it is available as $Transaction-&gt;[[ObjectId]].<br />
<br />
Variation:<br />
<br />
Another Variation that says, if we are merging from a queue into a set of queues, and data from the merging tickets Custom fields need to be populated into the ticket your merging into..<br />
<br />
Description: OnMergeCF<br />
Condition: User Defined<br />
Action: User Defined<br />
Template: Global Template: Transaction<br />
State: TransactionCreate<br />
<br />
=== Custom Condition ===<br />
<br />
<pre><br />
#Transaction Association<br />
my $txn = $self-&gt;TransactionObj;<br />
<br />
#Condition on Type<br />
return undef unless $txn-&gt;Type =~ /^AddLink$/i;<br />
return undef unless $txn-&gt;Field =~ /^MergedInto$/i;<br />
<br />
$RT::Logger-&gt;info('Merge is Occurring');<br />
<br />
#Ticket Association<br />
#The New Ticket your Merging into<br />
my $ticket = $self-&gt;TicketObj;<br />
<br />
#The old Ticket your merging From<br />
my $oldTicket = RT::Ticket-&gt;new($RT::SystemUser);<br />
$oldTicket-&gt;LoadById($txn-&gt;ObjectId);<br />
<br />
$RT::Logger-&gt;info('Merging '.$txn-&gt;ObjectId.' into '.$ticket-&gt;Id);<br />
<br />
#Are we merging from the right queue?<br />
my $oldqueue = $oldTicket-&gt;Queue;<br />
return undef unless $oldqueue == 36;<br />
<br />
$RT::Logger-&gt;info('Merging From Lead Tracking Queue');<br />
<br />
#Are we merging into an allowed queue?<br />
my @queues = qw(Enrollment/Recruitment Installation Accounting HealthCheck/Follow-up);<br />
my $queue = $ticket-&gt;QueueObj-&gt;Name;<br />
my $qCount = grep(/\Q$queue\E/,@queues);<br />
return undef unless $qCount &gt;= 1;<br />
<br />
$RT::Logger-&gt;info('Merging into an allowed Queue');<br />
<br />
return 1;<br />
<br />
</pre><br />
<br />
=== Custom Action Preperation Code ===<br />
<br />
<pre><br />
#nothing to do here.. just return<br />
return 1;<br />
</pre><br />
<br />
=== Custom Action Cleanup Code ===<br />
<br />
<pre><br />
use List::MoreUtils qw/ uniq /;<br />
<br />
#Define the Custom Field Name Were Going to Play with.<br />
my $CFName = 'Lead Source';<br />
<br />
#Transaction Association<br />
my $txnObj = $self-&gt;TransactionObj;<br />
<br />
#Ticket Association<br />
#The New Ticket your Merging into<br />
my $ticketObj = $self-&gt;TicketObj;<br />
my $queueObj = $self-&gt;TicketObj-&gt;QueueObj;<br />
my $CFObj = RT::CustomField-&gt;new($RT::SystemUser);<br />
$CFObj-&gt;LoadByNameAndQueue(Name =&gt; $CFName, Queue =&gt; $queueObj-&gt;id);<br />
unless($CFObj-&gt;id) {<br />
$CFObj-&gt;LoadByNameAndQueue(Name =&gt; $CFName, Queue=&gt;0);<br />
unless($CFObj-&gt;id){<br />
$RT::Logger-&gt;warning("Custom Field: $CFName not for this Queue");<br />
return undef;<br />
};<br />
};<br />
<br />
#The old Ticket your merging From<br />
my $oldTicket = RT::Ticket-&gt;new($RT::SystemUser);<br />
$oldTicket-&gt;LoadById($txnObj-&gt;ObjectId);<br />
<br />
#skip merge into same ticket<br />
return undef if $oldTicket->id() == $ticketObj->id();<br />
<br />
#Extract the fields (including multifields) from both tickets<br />
my @cfv1 = sort(uniq(split(/\n/, $oldTicket->CustomFieldValuesAsString($CFName))));<br />
my @cfv2 = split(/\n/, $ticketObj->CustomFieldValuesAsString($CFName));<br />
<br />
#Merge in the fields from the old ticket into the new ticket<br />
my $cfv = "";<br />
foreach $cfv (@cfv1)<br />
{ <br />
if(! grep { $_ eq $cfv} @cfv2 ) <br />
{<br />
#$RT::Logger->warning("cfv: adding ". $cfv);<br />
my ($st, $msg) = $ticketObj->AddCustomFieldValue(<br />
Field => $CFObj->id,<br />
Value => $cfv,<br />
RecordTransaction => 0<br />
);<br />
<br />
unless ($st){<br />
$RT::Logger->warning("Odd we couldn't set $CFName to $cfv");<br />
};<br />
}<br />
}<br />
<br />
return 1;<br />
</pre><br />
<br />
[[Category:Alternate Custom Action Cleanup Code for Multiple Entries]]</div>Tharnhttps://rt-wiki.bestpractical.com/index.php?title=OnCreateSetUserDetails&diff=26335OnCreateSetUserDetails2016-08-13T22:49:09Z<p>Tharn: </p>
<hr />
<div>This scrip is a work in progress (it works for me, but is not well-polished). It pulls information from a vCard if present, and uses it to set user details. In particular note that it currently relies upon the presence of an address of type x-rt. For your use you'll probably want that to be work or home, or prefer one and fail over to the other e.g;<br />
<br />
-my $adr = $card-&gt;get({node_type=&gt;'addresses', types=&gt;'x-rt'})-&gt;[0];<br />
+my $adr = $card-&gt;get({node_type=&gt;'addresses', types=&gt;'home'})-&gt;[0] || $card-&gt;get({node_type=&gt;'addresses', types=&gt;'work'})-&gt;[0] ;<br />
<br />
<br />
Scrip Fields:<br />
<br />
Description: OnCreate Set user details<br />
Condition: On Create<br />
Action: User Defined<br />
Template: Global Template: Blank<br />
Stage: Transaction Create<br />
<br />
<br />
Custom Condition:<br />
<br />
<pre><br />
# return $Attachment-&gt;First-&gt;ContentType eq 'text/x-vcard' ? 1 : 0;<br />
my $Attachments = $self-&gt;TransactionObj-&gt;Attachments('text/x-vcard');<br />
return $Attachments-&gt;Count() ? 1 : 0;<br />
<br />
</pre><br />
<br />
Custom action preparation code:<br />
<br />
<pre><br />
use Text::vCard::Addressbook;<br />
my $Transaction = $self-&gt;TransactionObj;<br />
my $body = $Transaction-&gt;Attachments('text/x-vcard')-&gt;First-&gt;Content;<br />
<br />
#Parse<br />
my $card = Text::vCard::Addressbook-&gt;new({source_text=&gt;$body})-&gt;vcards()-&gt;[0];<br />
return 0 unless $card; #sometimes the condition -&gt;Count() fails :-(<br />
my $adr = $card-&gt;get({node_type=&gt;'addresses', types=&gt;'x-rt'})-&gt;[0];<br />
my %usr = (<br />
requestor=&gt;$card-&gt;email,<br />
RealName=&gt;$card-&gt;fullname,<br />
Organization=&gt;$card-&gt;get({node_type=&gt;'ORG'})-&gt;[0]-&gt;name,<br />
Address1=&gt;$adr-&gt;street,<br />
Address2=&gt;$adr-&gt;extended,<br />
City=&gt;$adr-&gt;city,<br />
State=&gt;$adr-&gt;region,<br />
Country=&gt;$adr-&gt;country,<br />
Zip=&gt;$adr-&gt;post_code,<br />
);<br />
foreach my $tel ( $card-&gt;get({node_type=&gt;'TEL'}) ){<br />
$usr{HomePhone} = $tel-&gt;value if $tel-&gt;is_type('home');<br />
$usr{WorkPhone} = $tel-&gt;value if $tel-&gt;is_type('work');<br />
$usr{PagerPhone} = $tel-&gt;value if $tel-&gt;is_type('pager');<br />
$usr{MobilePhone} = $tel-&gt;value if $tel-&gt;is_type('cell');<br />
}<br />
<br />
#Tweak requestor with %usr<br />
my $user = RT::User-&gt;new($RT::SystemUser);<br />
$user-&gt;LoadByEmail(delete($usr{requestor}));<br />
foreach my $attr ( keys %usr){<br />
my $method='Set' . $attr;<br />
$user-&gt;$method($usr{$attr});<br />
}<br />
<br />
</pre><br />
<br />
Custom action cleanup code:<br />
<br />
1;</div>Tharnhttps://rt-wiki.bestpractical.com/index.php?title=OnCreatePageOffHours&diff=26334OnCreatePageOffHours2016-08-13T18:24:16Z<p>Tharn: </p>
<hr />
<div>'''Purpose:''' Our company's helpdesk uses a different email system and need to assign tickets to us but we use RT. The other system will send an email to our RT system but we need to know if it is OFF Hours so we can get paged. This script tests to see if a ticket is created (sent in by) the external system and if it's OFF hours it will send an email to our pager.<br />
<br />
'''Author:''' macnlos AT gmail DOT com - I'm on the RT Users List<br />
<br />
'''Requirement:''' This scrip uses the [[SendEmailAction]] script which allows us to send to any external email address. Our template is based on the example in this scrip.<br />
<br />
'''Description:''' Helpdesk Off Hours Pager<br />
<br />
'''Condition:''' User Defined<br />
<br />
'''Action:''' Send Email &lt;--- this is the [[SendEmailAction]] scrip<br />
<br />
'''Template:''' Global template: Notifypager<br />
<br />
'''State:''' [[TransactionCreate]]<br />
<br />
'''Custom Condition:'''<br />
<br />
<pre><br />
# These first 4 variables correspond to the start and stop of<br />
# business days and hours. The 5th variable is the email<br />
# address of the other system that is forwarding messages.<br />
my $start_hr = 6;<br />
my $end_hr = 17;<br />
my $start_day = 1;<br />
my $end_day = 5;<br />
my $from_address = 'email@othersystem.com';<br />
<br />
# Bypass if this is not being created<br />
if ($self-&gt;TransactionObj-&gt;Type ne "Create") {<br />
return 0; # This is an update transaction<br />
}<br />
<br />
# Get who created this ticket and exit if it doesn't<br />
# match the system address from above<br />
my $requestor_address = lc($self-&gt;TicketObj-&gt;RequestorAddresses);<br />
if ($requestor_address ne $from_address) {<br />
return 0; ## Not from Unicenter<br />
}<br />
<br />
# Parse out the current time the check if it is on or off hours.<br />
my ($sec,$min,$hour,$day,$mon,$year,$wday,$yday,$isdst) = localtime time();<br />
if (($wday &lt; $start_day) || ($wday &gt; $end_day)) {<br />
return 1; ## It's not in the work week and is OFF Hours<br />
}<br />
<br />
if (($hour &gt;= $start_hr) &amp;&amp; ($hour &lt;= $end_hr)) {<br />
return 0; ## It's with business hours (ON Hours)<br />
}<br />
<br />
return 1; ## It's OFF Hours during the work week<br />
<br />
</pre><br />
<br />
'''Code Explanation:''' The first 5 variables allow you to define the time periods that is considered ON hours. It allows you to define what days are considered business days. Lastly it lets define from what email address the other system is sending requests.<br />
<br />
The parsing of current time is standard Perl. The important part is that $hour and $wday will have the current hour and day of the week. You need these to compare against your defined ON / OFF hours.<br />
<br />
The first IF block checks to see if it is from the external system. If it not then it exits out with a 0 and no page is sent.<br />
<br />
The second IF block checks to see if it during your business week. If it is not then it exits out with a 1 and a page is sent because it is OFF hours.<br />
<br />
The last IF block checks to see if it is during business hours. We already know that it's within business days. If it is within business hours then it exits with a 0 and no page is sent.<br />
<br />
If it makes it to the last return statement you know it's on a business day but off hours. So it exits out with a 1 and a page is sent.</div>Tharnhttps://rt-wiki.bestpractical.com/index.php?title=AnyReminderTransaction&diff=26333AnyReminderTransaction2016-08-13T18:21:12Z<p>Tharn: </p>
<hr />
<div>=== Overview ===<br />
<br />
This modification allows to associate scrips to the creation of a reminder.<br />
<br />
Tested only on 3.6.3<br />
<br />
=== How to set up AnyReminderTransaction in RT3: ===<br />
<br />
1. Save the following code to /path/to/rt3/local/lib/RT/Condition/[[AnyReminderTransaction]].pm<br />
<br />
<pre># BEGIN BPS TAGGED BLOCK {{{<br />
#<br />
# COPYRIGHT:<br />
#<br />
# Copyright (c) 2007 Revolution Linux, Inc<br />
# &lt;mjeanson@revolutionlinux.com&gt;<br />
#<br />
# (Except where explicitly superseded by other copyright notices)<br />
#<br />
#<br />
# LICENSE:<br />
#<br />
# This work is made available to you under the terms of Version 2 of<br />
# the GNU General Public License. A copy of that license should have<br />
# been provided with this software, but in any event can be snarfed<br />
# from www.gnu.org.<br />
#<br />
# This work is distributed in the hope that it will be useful, but<br />
# WITHOUT ANY WARRANTY; without even the implied warranty of<br />
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU<br />
# General Public License for more details.<br />
#<br />
# You should have received a copy of the GNU General Public License<br />
# along with this program; if not, write to the Free Software<br />
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.<br />
#<br />
# END BPS TAGGED BLOCK }}}<br />
<br />
package RT::Condition::AnyReminderTransaction;<br />
require RT::Condition::Generic;<br />
<br />
use strict;<br />
use vars qw/@ISA/;<br />
@ISA = qw(RT::Condition::Generic);<br />
<br />
<br />
=head2 IsApplicable<br />
<br />
If the ticket object type is "reminder" return true, otherwise return false<br />
<br />
=cut<br />
<br />
sub IsApplicable {<br />
my $self = shift;<br />
if ($self-&gt;TicketObj-&gt;Type eq 'reminder') {<br />
return(1);<br />
}<br />
else {<br />
return(undef);<br />
}<br />
}<br />
<br />
eval "require RT::Condition::AnyReminderTransaction_Vendor";<br />
die $@ if ($@ &amp;&amp; $@ !~ qr{^Can't locate RT/Condition/AnyReminderTransaction_Vendor.pm});<br />
eval "require RT::Condition::AnyReminderTransaction_Local";<br />
die $@ if ($@ &amp;&amp; $@ !~ qr{^Can't locate RT/Condition/AnyReminderTransaction_Local.pm});<br />
<br />
1;<br />
<br />
<br />
</pre><br />
<br />
2. Save the following code in a datafile named AnyReminderTransaction.insert on your server:<br />
<br />
<pre><br />
@ScripConditions = (<br />
{ Name =&gt; 'On Reminder Create', # loc<br />
Description =&gt; 'When a reminder is created', # loc<br />
ApplicableTransTypes =&gt; 'Create',<br />
ExecModule =&gt; 'AnyReminderTransaction', },<br />
);<br />
</pre><br />
<br />
3. Run the RT3 Setup Database tool specifying to insert the datafile just saved. This will create the ScripConditions in RT's database.<br />
<br />
/path/to/rt3/sbin/rt-setup-database --action insert --datafile /path/to/AnyReminderTransaction.insert</div>Tharnhttps://rt-wiki.bestpractical.com/index.php?title=OnStatusChange&diff=26332OnStatusChange2016-08-13T18:17:11Z<p>Tharn: </p>
<hr />
<div>RT is distributed with condition 'On Resolve' which is based on module RT::Condition::StatusChange. This module has functionality to handle other statuses as well, but only one thing you have to do. You have to insert description of the new scrip condition into database of the RT.<br />
<br />
To get condition 'On Stall':<br />
<pre><br />
INSERT INTO ScripConditions(<br />
Name,<br />
Description,<br />
ExecModule,<br />
Argument,<br />
ApplicableTransTypes,<br />
Creator,<br />
Created,<br />
LastUpdatedBy,<br />
LastUpdated<br />
) VALUES (<br />
'On Stall',<br />
'Whenever a ticket is stalled',<br />
'StatusChange',<br />
'stalled',<br />
'Status',<br />
&lt;user_id&gt;,<br />
NOW(),<br />
&lt;user_id&gt;,<br />
NOW()<br />
);<br />
</pre><br />
<br />
Get user id from other records in this table, this id is not used much, but should be correct.<br />
<br />
To get 'On Delete' and other conditions you have to insert similar data where Argument field matches the status value.</div>Tharnhttps://rt-wiki.bestpractical.com/index.php?title=AnyTransactionSource&diff=26331AnyTransactionSource2016-08-13T18:15:33Z<p>Tharn: </p>
<hr />
<div>== Overview ==<br />
<br />
In addition to using RT to receive requests from customers, we also use it to send notices or requests to customers and vendors. This gives us a complete log of both incoming and outgoing communications.<br />
<br />
The problem is that RT by default isn't designed to make outgoing e-mail easy. In the default state one must create a ticket then reply to it to get the correspondence sent to the requestor (who is the customer/vendor you want to send mail to). There is also the problem of autoreplies upon ticket creation.<br />
<br />
These problems are easily resolved by using the AnyTransactionSource ScripCondition provided by Florian Weimer and the RUS-CERT team at University of Stuttgart.<br />
<br />
You can find Florian Weimer's posting describing AnyTransactionSource in the mailing list archives here: http://lists.bestpractical.com/pipermail/rt-users/2003-January/011567.html<br />
<br />
I have spent some time implementing this in our RT3 installation and have documented the steps required as I wasn't able to find any previous documentation on the subject for RT3.<br />
<br />
== How to set up AnyTransactionSource in RT3: ==<br />
<br />
1. Copy the [https://git.netways.org/?p=rt3-modules.git;a=blob_plain;f=RTx-MiscScrips/lib/RT/Condition/AnyTransactionSource.pm;h=81d90795ca85b9e2c451dd4507c828e438b2b952;hb=763d62db092bfeaff9ea63e19cbcd17083560578 AnyTransactionSource.pm] ScripCondition (courtesy [https://www.netways.org/ NETWAYS] ) to your local RT3 customizations area:<br />
<br />
/path/to/rt3/local/lib/RT/Condition/!AnyTransactionSource.pm<br />
<br />
2. Save the following code in a datafile named AnyTransactionSource.insert on your server. In this context, "internal" means "staff" and "external" means "customers":<br />
<br />
<pre><br />
@ScripConditions = (<br />
{ Name =&gt; 'On Create External',<br />
Description =&gt; 'When a ticket is created externally',<br />
ExecModule =&gt; 'AnyTransactionSource',<br />
Argument =&gt; 'external',<br />
ApplicableTransTypes =&gt; 'Create'<br />
},<br />
{ Name =&gt; 'On Create Internal',<br />
Description =&gt; 'When a ticket is created internally',<br />
ExecModule =&gt; 'AnyTransactionSource',<br />
Argument =&gt; 'internal',<br />
ApplicableTransTypes =&gt; 'Create'<br />
},<br />
{ Name =&gt; 'On Correspond External',<br />
Description =&gt; 'Whenever external correspondence comes in',<br />
ExecModule =&gt; 'AnyTransactionSource',<br />
Argument =&gt; 'external',<br />
ApplicableTransTypes =&gt; 'Correspond'<br />
},<br />
{ Name =&gt; 'On Correspond Internal',<br />
Description =&gt; 'Whenever internal correspondence comes in',<br />
ExecModule =&gt; 'AnyTransactionSource',<br />
Argument =&gt; 'internal',<br />
ApplicableTransTypes =&gt; 'Correspond'<br />
},<br />
);<br />
</pre><br />
<br />
3. Run the RT3 Setup Database tool specifying to insert the datafile just saved. This will create the ScripConditions in RT's database.<br />
<br />
/path/to/rt3/sbin/rt-setup-database --action insert --datafile /path/to/AnyTransactionSource.insert<br />
<br />
4. Change your existing On Create autoreply scrip to use the "On Create External" condition instead of the "On Create" condition it currently uses.<br />
<br />
<br />
<br />
5. Add a new scrip:<br />
<br />
Condition: On Create Internal<br />
Action: Notify Requestors and Ccs<br />
Template: Correspondence<br />
<br />
When finished, tickets created externally by your users, will still receive the auto reply. Tickets created internally by your staff will automatically send the message entered as correspondence to the requestor specified. The "requestor" will not receive an auto reply.<br />
<br />
<br />
<br />
Contributed by [[BillGerrard]]<br />
<br />
== Note ==<br />
<br />
You cannot use this to distinguish if a ticket has been created via e-mail or via the [[SelfService]] interface. For that see [[OnWebCorrespond]].<br />
<br />
[http://cert.uni-stuttgart.de/projects/rt/snippets/Action/AnyTransactionSource.pm The original AnyTransactionSource.pm]<span style="font-style: normal; "> seems to have vanished; thanks to [https://www.netways.org/ NETWAYS] for having a copy in their git repository.</span><br />
<br />
See also [[OnCreateFromEmail]].</div>Tharnhttps://rt-wiki.bestpractical.com/index.php?title=OnCreateFromEmail&diff=26330OnCreateFromEmail2016-08-13T18:13:46Z<p>Tharn: </p>
<hr />
<div>We wanted to be able to send out autoreply emails only when a ticket is created by the end user sending an email. Emails are not sent when a ticket is created by an agent through the web interface.<br />
<br />
This scrip is based on [[OnWebCorrespond]] and has been tested with RT 3.6.0.<br />
<br />
<pre><br />
# Custom condition to autoreply only when a ticket is created via email<br />
# Written by Rob Lemley &lt;rob@slide.com&gt; 2006-08-09<br />
# Based on "OnWebCorrespond"<br />
# http://wiki.bestpractical.com/index.cgi?OnWebCorrespond<br />
<br />
my $trans = $self-&gt;TransactionObj;<br />
<br />
return 0 unless $trans-&gt;Type eq "Create";<br />
<br />
my $msgattr = $trans-&gt;Message-&gt;First;<br />
<br />
return 0 unless $msgattr;<br />
<br />
return 1 if $msgattr-&gt;GetHeader('Received');<br />
<br />
return 0;<br />
<br />
</pre><br />
<br />
See also [[AnyTransactionSource]]<br />
[[Category:RT Condition]]</div>Tharn