3 # $Header: /home/cvs/cvsroot/freeside/rt/bin/Attic/rt,v 1.1 2002-08-12 06:17:07 ivan Exp $
4 # RT is (c) 1996-2001 Jesse Vincent <jesse@bestpractical.com>
10 use lib "!!RT_LIB_PATH!!";
11 use lib "!!RT_ETC_PATH!!";
13 use RT::Interface::CLI qw(CleanEnv LoadConfig DBConnect
14 GetCurrentUser GetMessageContent);
16 #Clean out all the nasties from the environment
19 #Load etc/config.pm and drop privs
22 #Connect to the database and get RT::SystemUser and RT::Nobody loaded
25 #Drop setgid permissions
26 RT::DropSetGIDPermissions();
28 #Get the current user all loaded
29 my $CurrentUser = GetCurrentUser();
31 unless ($CurrentUser->Id) {
32 print "No RT user found. Please consult your RT administrator.\n";
37 # {{{ commandline flags
44 @limit_final_priority,
99 # Set defaults for cli args
101 $edit = 1; # Assume the user wants to edit replies and comments
102 # unless they specify --noedit
106 my @args =("id=s" => \@id,
107 "limit-queue=s" => \@limit_queue,
108 "limit-status=s" => \@limit_status,
109 "limit-owner=s" => \@limit_owner,
110 "limit-priority=s" => \@limit_priority,
111 "limit-final-priority=s" => \@limit_final_priority,
112 "limit-requestor=s" => \@limit_requestor,
113 "limit-subject=s" => \@limit_subject,
114 "limit-body=s", \@limit_body,
115 "limit-created=s" => \@limit_created,
116 "limit-due=s" => \@limit_due,
117 "limit-last-updated=s" => \@limit_lastupdated,
118 "limit-keyword=s" => \@limit_keyword,
120 "limit-member-of=s" => \@limit_memberof,
121 "limit-has-member=s" => \@limit_hasmember,
122 "limit-depended-on-by=s" => \@limit_dependedonby,
123 "limit-depends-on=s" => \@limit_dependson,
124 "limit-referred-to-by=s" => \@limit_referredtoby,
125 "limit-refers-to=s" => \@limit_refersto,
127 "limit-starts=s" => \@limit_starts,
128 "limit-started=s" => \@limit_started,
129 "limit-first=i" => \$limit_first,
130 "limit-rows=i" => \$limit_rows,
131 "history|show" => \$history,
132 "summary:s" => \$summary,
133 "create" => \$create,
134 "keywords=s" => \@keywords,
135 "requestor|requestors=s" => \@requestors,
137 "admincc=s" => \@admincc,
138 "status=s" => \$status,
139 "subject=s" => \$subject,
140 "owner=s" => \$owner,
142 "queue=s" => \$queue,
145 "priority=i" => \$priority,
146 "final-priority=i" => \$final_priority,
148 "starts=s" => \$starts,
149 "started=s" => \$started,
150 "contacted=s" => \$contacted,
151 "comment", \$comment,
152 "reply|respond", \$reply,
153 "source=s" => \$source,
155 "depends-on=s" => \@dependson,
156 "member-of=s" => \@memberof,
157 "merge-into=s" => \$mergeinto,
158 "refers-to=s" => \@refersto,
159 "time-left=i" => \$time_left,
160 "time-taken=i" => \$time_taken,
161 "verbose+" => \$verbose,
163 "version" => \$version,
164 "help|h|usage" => \$help
173 print join(':',@keywords);
174 # {{{ If they want it, print a usage message and get out
181 Limit the set of records returned:
183 --id=[first][-][last]
184 Specify a single ticket, a range, or to start with (n-) or end with (-n)
187 --limit-queue=<queue>
188 --limit-status=[!](new|open|stalled|resolved)
190 --limit-owner=[!]<userid>
191 --limit-priority=[starts][-][ends]
192 --limit-final-priority=[starts][-][ends]
193 starts is less than ends
194 --limit-requestor=[!]<userid>|<email>
195 --limit-subject=[!]<text>
196 --limit-body=[!]<text>
197 --limit-keyword=[!]<select>/<keyword>
200 --limit-member-of=<ticketid>
201 --limit-has-member=<ticketid>
202 --limit-refers-to=<ticketid>
203 --limit-referred-to-by=<ticketid>
204 --limit-depends-on=<ticketid>
205 --limit-depended-on-by=<ticketid>
209 --limit-created=[starts][-][ends]
210 --limit-due=[starts][-][ends]
211 --limit-starts=[starts][-][ends]
212 --limit-started=[starts][-][ends]
213 --limit-resolved=[starts][-][ends]
214 --limit-last-updated=[starts][-][ends]
215 starts and ends are dates. starts can not be less than ends
217 --limit-first=<first row returned>
218 --limit-rows=<row count>
221 show a history of the tickets found
224 --summary [format-string]
225 show a listing-style summary of the tickets found. If format string
226 is ommitted, uses \$RT_SUMMARY_FORMAT or an internal default
230 format: <atom>%<format>
233 name: (grep for # {{{ attribs for the array of ok values)
237 create a new ticket. Any attributes that you can modify on an existing ticket
238 can also be used for ticket creation.
244 --status=<new|open|stalled|resolved|dead>
251 Become the owner, even if someone else owns the ticket
257 --final-priority=<int>
260 --requestors=[+|-]<userid|email address>
261 add or remove this user as a ticket requestor
262 --cc=[+|-]<userid|email address>
263 add or remove this user as a ticket cc
264 --admincc=[+|-]<userid|email address>
265 add or remove this user as a ticket admincc
267 (When creating tickets, just leave off the + or - )
270 --keywords[+|-]<keyword_select>/<keyword>
271 Add or remove a keyword.
286 Link related manipulation:
288 --depends-on=[+|-]<ticketid>
289 --member-of=[+|-]<ticketid>
290 --refers-to=[+|-]<ticketid>
291 --merge-into=<ticketid>
298 Specify the path to the source file for this ticket update
301 Don't invoke \$EDITOR to edit the content of this update
319 # Print version, and leave
321 print "RT $RT::VERSION for $RT::rtname. Copyright 1996-2001 Jesse Vincent <jesse\@fsck.com>\n";
327 # {{{ Validate any options that were passed in. normalize them.
329 #if a queue was specified
331 # make sure that $queue is a valid queue and load it into $queue_obj
334 #For each date in: $due, $starts, $started
336 # load up an RT::Date object and parse it into a normalized form
337 # if it can't parse it, log an error and null out the variable
341 # {{{ Check if we're creating, if so, create the ticket and be done
344 $RT::Logger->debug("Creating a new ticket");
346 #Make sure the current user can create tickets in this queue
348 #Make sure that the owner specified can own tickets in this queue
352 my $linesref = GetMessageContent( Edit => $edit, Source => $source,
353 CurrentUser => $CurrentUser
356 require MIME::Entity;
360 $MIMEObj = MIME::Entity->build(Data => $linesref);
364 my $Ticket=new RT::Ticket($CurrentUser);
365 my ($ticket, $trans, $msg) =
366 $Ticket->Create(Queue => $queue,
368 Status => $status || 'new' ,
370 Requestor => \@requestors,
372 AdminCc => \@admincc,
376 TimeLeft => $time_left,
377 InitialPriority => $priority,
378 FinalPriority => $final_priority,
389 my $Tickets = new RT::Tickets($CurrentUser);
391 # {{{ Limit our search
392 my $value; #to use when iterating through restrictions
393 my $queue_id; #to use when limiting by keyword
397 foreach $value (@id) {
398 if ($value =~ /^(\d+)$/) {
399 $Tickets->LimitId ( VALUE => $1,
402 elsif ($value =~ /^(\d*)\D?(\d*)$/) {
407 OPERATOR => '>=') if ($start);
410 OPERATOR => '<=') if ($end);
417 # {{{ limit on status
419 foreach $value (@limit_status) {
420 if ($value =~ /^(=|!=|!|)(.*)$/) {
425 $op = ParseBooleanOp($op);
426 $Tickets->LimitStatus(VALUE => "$val",
436 foreach $value (@limit_queue) {
437 if ($value =~ /^(\W?)(.*?)$/i) {
441 $op = ParseBooleanOp($op);
443 my $queue_obj = new RT::Queue($RT::SystemUser);
445 unless ($queue_obj->Load($val)) {
446 $RT::Logger->debug("Queue '$val' not found");
447 print STDERR "Queue '$val' not found\n";
450 $RT::Logger->debug ("Limiting queue to $op ".$queue_obj->Name);
451 $Tickets->LimitQueue(VALUE => $queue_obj->Name,
453 $queue_id=$queue_obj->id;
457 # {{{ limit on keyword
458 foreach $value (@limit_keyword) {
459 if ($value =~ /^(\W?)(.*?)\/(.*)$/i) {
464 $op = ParseBooleanOp($op);
466 # load the keyword select
467 my $keyselect = RT::KeywordSelect->new($RT::SystemUser);
468 unless ($keyselect->LoadByName(Name=>$select, Queue=>$queue_id)) {
469 $RT::Logger->debug("KeywordSelect '$select' not found");
470 print STDERR "KeywordSelect '$select' not fount\n";
475 my $k = RT::Keyword->new($RT::SystemUser);
476 unless ($k->LoadByNameAndParentId($keyword, $keyselect->Keyword)) {
477 $RT::Logger->debug("Keyword '$keyword' not found");
478 print STDERR "Keyword '$keyword' not found\n";
481 $Tickets->LimitKeyword(OPERATOR => $op,
482 KEYWORDSELECT => $keyselect->id,
484 $RT::Logger->debug ("Limiting keyword to $op ".$k->Path);
489 foreach $value (@limit_owner) {
490 if ($value =~ /^(\W?)(.*?)$/i) {
494 $op = ParseBooleanOp($op);
496 my $user_obj = new RT::User($RT::SystemUser);
498 unless ($user_obj->Load($val)) {
499 $RT::Logger->debug("User '$val' not found");
500 print STDERR "User '$val' not found\n";
503 $val = $user_obj->id();
505 $RT::Logger->debug ("Limiting owner to $op $val");
506 $Tickets->LimitOwner(VALUE => "$val",
511 # {{{ limt on priority
513 foreach $value (@limit_priority) {
514 my ($start, $end) = ParseRange($value);
515 if ($start == $end) {
516 $Tickets->LimitPriority( VALUE => $start,
519 $Tickets->LimitPriority( VALUE => $start,
522 $Tickets->LimitPriority( VALUE => $end,
527 foreach $value (@limit_final_priority) {
528 my ($start, $end) = ParseRange($value);
529 if ($start == $end) {
530 $Tickets->LimitFinalPriority( VALUE => $start,
533 $Tickets->LimitFinalPriority( VALUE => $start,
536 $Tickets->LimitFinalPriority( VALUE => $end,
542 foreach $value (@limit_requestor) {
543 if ($value =~ /^(\W?)(.*?)$/i) {
547 $op = ParseBooleanOp($op);
548 $Tickets->LimitRequestor(VALUE => $val,
553 foreach $value (@limit_subject) {
555 if ($value =~ /^(\W?)(.*?)$/i) {
559 $op = ParseLikeOp($op);
561 $Tickets->LimitSubject(VALUE => $val,
566 foreach $value (@limit_body) {
567 if ($value =~ /^(\W?)(.*?)$/i) {
571 $op = ParseLikeOp($op);
573 $Tickets->LimitBody(VALUE => $val,
582 foreach my $date (@limit_created) {
583 my ($start, $end) = ParseDateRange($date);
584 $Tickets->LimitCreated ( VALUE => $start,
585 OPERATOR => '>=' ) if ($start);
586 $Tickets->LimitCreated ( VALUE => $end,
587 OPERATOR => '<=' ) if ($end);
590 foreach my $date (@limit_due) {
591 my ($start, $end) = ParseDateRange($date);
592 $Tickets->LimitDue ( VALUE => $start,
593 OPERATOR => '>=' ) if ($start);
594 $Tickets->LimitDue ( VALUE => $end,
595 OPERATOR => '<=' ) if ($end);
598 foreach my $date (@limit_starts) {
599 my ($start, $end) = ParseDateRange($date);
600 $Tickets->LimitStarts ( VALUE => $start,
601 OPERATOR => '>=' ) if ($start);
602 $Tickets->LimitStarts ( VALUE => $end,
603 OPERATOR => '<=' ) if ($end);
606 foreach my $date (@limit_started) {
607 my ($start, $end) = ParseDateRange($date);
608 $Tickets->LimitStarted ( VALUE => $start,
609 OPERATOR => '>=' ) if ($start);
610 $Tickets->LimitStarted ( VALUE => $end,
611 OPERATOR => '<=' ) if ($end);
614 foreach my $date (@limit_resolved) {
615 my ($start, $end) = ParseDateRange($date);
616 $Tickets->LimitResolved ( VALUE => $start,
617 OPERATOR => '>=' ) if ($start);
618 $Tickets->LimitResolved ( VALUE => $end,
619 OPERATOR => '<=' ) if ($end);
622 foreach my $date (@limit_lastupdated) {
623 my ($start, $end) = ParseDateRange($date);
624 $Tickets->LimitLastUpdated( VALUE => $start,
625 OPERATOR => '>=' ) if ($start);
626 $Tickets->LimitLastUpdated ( VALUE => $end,
627 OPERATOR => '<=' ) if ($end);
630 foreach my $link (@limit_memberof) {
631 $Tickets->LimitMemberOf($link);
634 foreach my $link (@limit_hasmember) {
635 $Tickets->LimitHasMember($link);
638 foreach my $link (@limit_dependson) {
639 $Tickets->LimitDependsOn($link);
642 foreach my $link (@limit_dependedonby) {
643 $Tickets->LimitDependedOnBy($link);
645 foreach my $link (@limit_refersto) {
646 $Tickets->LimitRefersTo($link);
649 foreach my $link (@limit_referredtoby) {
650 $Tickets->LimitReferredToBy($link);
661 # {{{ Iterate through all tickets we found
664 my ($format, $titles, $code);
666 #Set up the summary format if we need to
667 if (defined $summary) {
668 my $format_string = $summary || $ENV{'RT_SUMMARY_FORMAT'} || "%id4%status4%queue7%subject40%requestor16";
670 ($format, $titles, $code) = BuildListingFormat($format_string);
671 printf "$format\n", eval "$titles";
676 while (my $Ticket = $Tickets->Next()) {
677 $RT::Logger->debug ("Now working on ticket ". $Ticket->id);
679 #Run through all the ticket modifications we might want to do
680 #TODO: these are all insufficiently lazy and should be replaced with some
684 # {{{ deal with watchers
686 # add / delete requestors
687 foreach $value (@requestors) {
688 if ($value =~ /^(\W?)(.*)$/) {
692 $Ticket->AddRequestor(Email => $addr) if ($op eq '+');
693 $Ticket->DeleteRequestor( $addr) if ($op eq '-');
698 foreach $value (@cc) {
699 if ($value =~ /^(\W?)(.*)$/) {
702 $Ticket->AddCc(Email => $addr) if ($op eq '+');
703 $Ticket->DeleteCc($addr) if ($op eq '-');
707 # add / delete adminccs
708 $RT::Logger->debug("Looking at admin ccs");
709 foreach $value (@admincc) {
710 if ($value =~ /^(\W?)(.*)$/) {
713 $Ticket->AddAdminCc(Email => $addr) if ($op eq '+');
714 $Ticket->DeleteAdminCc($addr) if ($op eq '-');
720 # {{{ Deal with ticket keywords
722 my $KeywordSelects = $Ticket->QueueObj->KeywordSelects();
723 $RT::Logger->debug ("Looking at keywords");
724 foreach $value (@keywords) {
725 $RT::Logger->debug("Looking at --keyword=$value");
726 if ($value =~ /^(\W?)(.*?)\/(.*)$/) {
731 $RT::Logger->debug("Going to $op Keyword $select / $keyword");
732 while (my $ks = $KeywordSelects->Next) {
733 $RT::Logger->debug("$select is select ".$ks->Name." is found");
734 next unless ($ks->Name =~ /$select/i);
735 $RT::Logger->debug ("Found a match for $select\n");
736 my $kids = $ks->KeywordObj->Descendents;
739 foreach $kid (keys %{$kids}) {
740 $RT::Logger->debug("Now comparing $keyword with ".$kids->{$kid}. "\n");
741 next unless ($kids->{$kid} =~ /^$keyword$/i);
742 $RT::Logger->debug("Going to $op $select / $keyword (".$kids->{$kid} .")");
743 $Ticket->DeleteKeyword(KeywordSelect => $ks->id,
744 Keyword => $kid) if ($op eq '-');
746 $Ticket->AddKeyword(KeywordSelect => $ks->id,
747 Keyword => $kid) if ($op eq '+');
755 # {{{ deal with links
757 # Deal with merging {
759 my ($trans, $msg) =$Ticket->MergeInto($mergeinto);
762 # add /delete depends-ons
764 foreach my $value (@dependson) {
765 if ($value =~ /^(\W?)(.*)$/) {
768 if (!$op or ($op eq '+')) {
770 $Ticket->AddLink(Type => 'DependsOn', Target => $ticket);
775 $Ticket->DeleteLink(Type => 'DependsOn', Target => $ticket);
781 # add /delete member-of
782 foreach my $value (@memberof) {
783 if ($value =~ /^(\W?)(.*)$/) {
788 $Ticket->AddLink(Type => 'MemberOf', Target => $ticket);
793 $Ticket->DeleteLink(Type => 'MemberOf', Target => $ticket);
799 # add / delete refers-to
800 foreach my $value (@refersto) {
801 if ($value =~ /^(\W?)(.*)$/) {
806 $Ticket->AddLink(Type => 'RefersTo', Target => $ticket);
811 $Ticket->DeleteLink(Type => 'RefersTo', Target => $ticket);
820 # {{{ deal with dates
824 my $iso = ParseDateToISO($due);
826 $RT::Logger->debug("Setting due date to $iso ($due)");
828 $Ticket->SetDue($iso);
832 print "Due date '$due' could not be parsed";
838 my $iso = ParseDateToISO($due);
841 $Ticket->SetStarts($iso);
845 print "Starts date '$starts' could not be parsed";
850 my $iso = ParseDateToISO($started);
853 $Ticket->SetStarted($iso);
857 print "Started date '$started' could not be parsed";
862 my $iso = ParseDateToISO($contacted);
865 $Ticket->SetContacted($iso);
869 print "Contacted date '$contacted' could not be parsed";
875 # {{{ set other attributes
879 my ($trans, $msg) = $Ticket->SetSubject($subject);
886 $Ticket->SetPriority($priority);
891 if ($final_priority) {
893 $Ticket->SetFinalPriority($final_priority);
900 $Ticket->SetStatus($status);
907 $Ticket->SetTimeLeft($time_left);
914 $Ticket->SetTimeTaken($time_taken);
921 $Ticket->SetOwner($owner);
934 $Ticket->SetQueue($queue);
942 # {{{ Perform ticket comments/replies
944 $RT::Logger->debug("Replying to ticket ".$Ticket->Id);
946 my $linesref = GetMessageContent( Edit => $edit, Source => $source,
947 CurrentUser => $CurrentUser
950 #TODO build this entity
951 require MIME::Entity;
952 my $MIMEObj = MIME::Entity->build(Data => $linesref);
954 $Ticket->Correspond( MIMEObj => $MIMEObj ,
955 TimeTaken => $time_taken);
959 $RT::Logger->debug("Commenting on ticket ".$Ticket->Id);
961 my $linesref =GetMessageContent(Edit => $edit, Source => $source,
962 CurrentUser => $CurrentUser);
963 #TODO build this entity
964 require MIME::Entity;
965 my $MIMEObj = MIME::Entity->build(Data => $linesref);
967 $Ticket->Comment( MIMEObj => $MIMEObj,
968 TimeTaken => $time_taken);
973 # {{{ Display whatever we need to display
975 # {{{ Display a full ticket listing and history
978 $RT::Logger->debug("Show history for ".$Ticket->id);
980 if ($Ticket->CurrentUserHasRight("ShowTicket")) {
981 &ShowSummary($Ticket);
983 &ShowHistory($Ticket);
986 print "You don't have permission to view that ticket.\n";
992 # {{{ Display a summary if we need to
993 if (defined $summary) {
994 $RT::Logger->debug ("Show ticket summary with format $format");
996 printf $format."\n", eval $code;
1010 $RT::Handle->Disconnect();
1018 # {{{ sub ParseBooleanOp
1020 =head2 ParseBooleanOp
1022 Takes an option modifier. returns the apropriate SQL operator.
1023 If it's handed ! or -, returns !=. Otherwise returns =.
1027 sub ParseBooleanOp {
1031 #so that !new limits to not new, etc
1032 if ($op =~ /^(\!|-)/) {
1044 # {{{ sub ParseLikeOp
1047 Takes an option modifier. returns the apropriate SQL operator.
1048 If it's handed ! or -, returns NOT LIKE. Otherwise returns LIKE
1056 #so that !new limits to not new, etc
1057 if ($op =~ /^(\!|-)/) {
1068 # {{{ sub ParseDateToISO
1070 =head2 ParseDateToISO
1072 Takes a date in an arbitrary format.
1073 Returns an ISO date and time in GMT
1077 sub ParseDateToISO {
1080 my $date_obj = new RT::Date($CurrentUser);
1081 $date_obj->Set( Format => 'unknown',
1084 return ($date_obj->ISO);
1089 # {{{ sub ParseDateRange
1091 =head2 ParseDateRange [RANGE]
1093 Takes a range of dates of the form [<date>][-][<date>] and returns
1094 starting and ending dates (as ISOs) If a date is specified as neither a starting nor ending
1095 date, we parse it it as "midnight tonight to midnight tomorrow"
1099 sub ParseDateRange {
1105 my $start_obj = new RT::Date($CurrentUser);
1106 my $end_obj = new RT::Date($CurrentUser);
1108 if ($in =~ /^(.*?)-(.*?)$/) {
1113 $start_obj->Set(Format => 'unknown',
1117 $end_obj->Set(Format => 'unknown',
1125 $start_obj->Set(Format => 'unknown',
1128 $end_obj->Set(Format => 'unknown',
1131 $start_obj->SetToMidnight();
1132 $end_obj->SetToMidnight();
1137 $start = $start_obj->ISO;
1140 $end = $end_obj->ISO;
1143 return ($start, $end);
1149 =head2 ParseRange [RANGE]
1151 Takes a range of the form [<int>][-][<int>] and returns
1152 a first and a last value. If the - is omitted, both $start and $end are the same.
1159 if ($in =~ /(.*?)-(.*?)/) {
1168 return ($start, $end);
1176 # {{{ sub ShowSummary
1183 Serial Number: @{[$Ticket->Id]} Status:@{[$Ticket->Status]} Worked: @{[$Ticket->TimeWorked]} minutes Queue:@{[$Ticket->QueueObj->Name]}
1184 Subject: @{[$Ticket->Subject]}
1185 Requestors: @{[$Ticket->RequestorsAsString]}
1186 Cc: @{[$Ticket->CcAsString]}
1187 Admin Cc: @{[$Ticket->AdminCcAsString]}
1188 Owner: @{[$Ticket->OwnerObj->Name]}
1189 Priority: @{[$Ticket->Priority]} / @{[$Ticket->FinalPriority]}
1190 Due: @{[$Ticket->DueAsString]}
1191 Created: @{[$Ticket->CreatedAsString]} (@{[$Ticket->AgeAsString]})
1192 Last Contact: @{[$Ticket->ToldAsString]} (@{[$Ticket->LongSinceToldAsString]})
1193 Last Update: @{[$Ticket->LastUpdatedAsString]} by @{[$Ticket->LastUpdatedByObj->Name]}
1197 my $selects = $Ticket->QueueObj->KeywordSelects();
1198 #get the keyword selects
1199 print "Keywords:\n";
1200 while (my $select = $selects->Next) {
1201 print "\t" .$select->Name .": ";
1202 my $keys = $Ticket->KeywordsObj($select->id);
1203 while (my $key = $keys->Next) {
1204 print $key->KeywordObj->RelativePath($select->KeywordObj) . " ";
1210 #iterate through the keyword selects.
1211 #print the keyword select and all the related keywords
1215 #TODO: finish link descriptions
1216 print "Dependencies: \n";
1217 while (my $l=$Ticket->DependedOnBy->Next) {
1218 print $l->BaseObj->id," (",$l->BaseObj->Subject,") ",$l->Type," this ticket\n";
1220 while (my $l=$Ticket->DependsOn->Next) {
1221 print "This ticket ",$l->Type," ",$l->TargetObj->Id," (",$l->TargetObj->Subject,")\n";
1227 # {{{ sub ShowHistory
1231 my $Transactions = $Ticket->Transactions;
1233 while ($Transaction = $Transactions->Next) {
1234 &ShowTransaction($Transaction);
1239 # {{{ sub ShowTransaction
1240 sub ShowTransaction {
1241 my $transaction = shift;
1244 ==========================================================================
1245 Date: @{[$transaction->CreatedAsString]} (@{[$transaction->TimeTaken]} minutes)
1246 @{[$transaction->Description]}
1249 my $attachments=$transaction->Attachments();
1250 while (my $message=$attachments->Next) {
1252 --------------------------------------------------------------------------
1253 @{[$message->Headers]}
1256 if ($message->ContentType =~ m{^(text/plain|message|text$)}) {
1257 print $message->Content;
1259 print $message->ContentType, " not shown";
1268 # {{{ sub BuildListingFormat
1270 sub BuildListingFormat {
1271 my $format_string = shift;
1273 my ($id, @format, @code, @titles);
1274 my ($field,$titles,$length, $format);
1279 my $attribs = { id => { chars => '4',
1282 value => '$Ticket->id',
1285 queue => { chars => '8',
1288 value => '$Ticket->QueueObj->Name'
1290 subject => { chars => '30',
1293 value => '$Ticket->Subject',
1295 priority => { chars => '2',
1298 value => '$Ticket->Priority',
1300 final_priority => { chars => '2',
1303 value => '$Ticket->FinalPriority',
1305 time_worked => { chars => '6',
1308 value => '$Ticket->TimeWorked',
1310 time_left => { chars => '5',
1313 value => '$Ticket->TimeLeft',
1317 status => { chars => '6',
1320 value => '$Ticket->Status',
1322 owner => { chars => '10',
1325 value => '$Ticket->OwnerObj->Name'
1327 requestor => { chars => '10',
1329 title => 'Requestor',
1330 value => '$Ticket->RequestorsAsString'
1332 created => { chars => '12',
1335 value => '$Ticket->CreatedAsString'
1337 updated => { chars => '12',
1340 value => '$Ticket->LastUpdatedAsString'
1342 due => { chars => '12',
1345 value => '$Ticket->DueAsString'
1347 told => { chars => '12',
1350 value => '$Ticket->ToldAsString'
1360 foreach $field (split ('%',$format_string)) {
1362 if ($field =~ /^(\D*?)(\d*?)$/) {
1367 $RT::Logger->debug ("Error parsing $field\n");
1370 push (@format, "%".$length.".".$length."s ");
1372 push (@code, $attribs->{"$id"}->{'value'});
1374 push (@titles, "'". $attribs->{"$id"}->{title}. "'");
1379 $code = join (',', @code);
1380 $format = join (" ", @format);
1381 $titles = join (', ', @titles);
1384 return ($format, $titles, $code);