#
# COPYRIGHT:
#
-# This software is Copyright (c) 1996-2012 Best Practical Solutions, LLC
+# This software is Copyright (c) 1996-2014 Best Practical Solutions, LLC
# <sales@bestpractical.com>
#
# (Except where explicitly superseded by other copyright notices)
# Abhijit Menon-Sen <ams@wiw.org>
use strict;
+use warnings;
if ( $ARGV[0] && $ARGV[0] =~ /^(?:--help|-h)$/ ) {
require Pod::Usage;
use HTTP::Headers;
use Term::ReadLine;
use Time::Local; # used in prettyshow
+use File::Temp;
# strong (GSSAPI based) authentication is supported if the server does provide
# it and the perl modules GSSAPI and LWP::Authen::Negotiate are installed
# (XXX: Ask Autrijus how i18n changes these definitions.)
my $name = '[\w.-]+';
-my $CF_name = '[\sa-z0-9_ :()/-]+';
+my $CF_name = '[^,]+?';
my $field = '(?i:[a-z][a-z0-9_-]*|C(?:ustom)?F(?:ield)?-'.$CF_name.'|CF\.\{'.$CF_name.'\})';
-my $label = '[a-zA-Z0-9@_.+-]+';
+my $label = '[^,\\/]+';
my $labels = "(?:$label,)*$label";
my $idlist = '(?:(?:\d+-)?\d+,)*(?:\d+-)?\d+';
sub edit {
my ($action) = @_;
my (%data, $type, @objects);
- my ($cl, $text, $edit, $input, $output);
+ my ($cl, $text, $edit, $input, $output, $content_type);
use vars qw(%set %add %del);
%set = %add = %del = ();
if (/^-e$/) { $edit = 1 }
elsif (/^-i$/) { $input = 1 }
elsif (/^-o$/) { $output = 1 }
+ elsif (/^-ct$/) { $content_type = shift @ARGV }
elsif (/^-t$/) {
$bad = 1, last unless defined($type = get_type_argument());
}
return 0;
}
+ my @files;
+ @files = @{ vsplit($set{'attachment'}) } if exists $set{'attachment'};
+
my $synerr = 0;
EDIT:
# We'll let the user edit the form before sending it to the server,
# unless we have enough information to submit it non-interactively.
+ if ( $type && $type eq 'ticket' && $text !~ /^Content-Type:/m ) {
+ $text .= "Content-Type: $content_type\n"
+ if $content_type and $content_type ne "text/plain";
+ }
+
if ($edit || (!$input && !$cl)) {
- my $newtext = vi($text);
+ my ($newtext) = vi_form_while(
+ $text,
+ sub {
+ my ($text, $form) = @_;
+ return 1 unless exists $form->[2]{'Attachment'};
+
+ foreach my $f ( @{ vsplit($form->[2]{'Attachment'}) } ) {
+ return (0, "File '$f' doesn't exist") unless -f $f;
+ }
+ @files = @{ vsplit($form->[2]{'Attachment'}) };
+ return 1;
+ },
+ );
+ return $newtext unless $newtext;
# We won't resubmit a bad form unless it was changed.
$text = ($synerr && $newtext eq $text) ? undef : $newtext;
}
+ delete @data{ grep /^attachment_\d+$/, keys %data };
+ my $i = 1;
+ foreach my $file (@files) {
+ $data{"attachment_$i"} = bless([ $file ], "Attachment");
+ $i++;
+ }
+
if ($text) {
my $r = submit("$REST/edit", {content => $text, %data});
if ($r->code == 409) {
# If we submitted a bad form, we'll give the user a chance
# to correct it and resubmit.
if ($edit || (!$input && !$cl)) {
- $text = $r->content;
+ my $content = $r->content . "\n";
+ $content =~ s/^(?!#)/# /mg;
+ $text = $content . $text;
$synerr = 1;
goto EDIT;
}
sub comment {
my ($action) = @_;
- my (%data, $id, @files, @bcc, @cc, $msg, $wtime, $edit);
+ my (%data, $id, @files, @bcc, @cc, $msg, $content_type, $wtime, $edit);
my $bad = 0;
while (@ARGV) {
if (/^-e$/) {
$edit = 1;
}
- elsif (/^-[abcmw]$/) {
+ elsif (/^-(?:[abcmw]|ct)$/) {
unless (@ARGV) {
whine "No argument specified with $_.";
$bad = 1; last;
}
push @files, shift @ARGV;
}
+ elsif (/-ct/) {
+ $content_type = shift @ARGV;
+ }
elsif (/-([bc])/) {
my $a = $_ eq "-b" ? \@bcc : \@cc;
@$a = split /\s*,\s*/, shift @ARGV;
while (<STDIN>) { $msg .= $_ }
}
}
-
elsif (/-w/) { $wtime = shift @ARGV }
}
elsif (!$id && m|^(?:ticket/)?($idlist)$|) {
my $form = [
"",
- [ "Ticket", "Action", "Cc", "Bcc", "Attachment", "TimeWorked", "Text" ],
+ [ "Ticket", "Action", "Cc", "Bcc", "Attachment", "TimeWorked", "Content-Type", "Text" ],
{
Ticket => $id,
Action => $action,
Bcc => [ @bcc ],
Attachment => [ @files ],
TimeWorked => $wtime || '',
+ 'Content-Type' => $content_type || 'text/plain',
Text => $msg || '',
Status => ''
}
my $text = Form::compose([ $form ]);
if ($edit || !$msg) {
- my $error = 0;
- my ($c, $o, $k, $e);
-
- do {
- my $ntext = vi($text);
- return if ($error && $ntext eq $text);
- $text = $ntext;
- $form = Form::parse($text);
- $error = 0;
-
- ($c, $o, $k, $e) = @{ $form->[0] };
- if ($e) {
- $error = 1;
- $c = "# Syntax error.";
- goto NEXT;
- }
- elsif (!@$o) {
- return 0;
- }
- @files = @{ vsplit($k->{Attachment}) };
-
- NEXT:
- $text = Form::compose([[$c, $o, $k, $e]]);
- } while ($error);
+ my ($tmp) = vi_form_while(
+ $text,
+ sub {
+ my ($text, $form) = @_;
+ foreach my $f ( @{ vsplit($form->[2]{'Attachment'}) } ) {
+ return (0, "File '$f' doesn't exist") unless -f $f;
+ }
+ @files = @{ vsplit($form->[2]{'Attachment'}) };
+ return 1;
+ },
+ );
+ return $tmp unless $tmp;
+ $text = $tmp;
}
my $i = 1;
sub grant {
my ($cmd) = @_;
- my $revoke = 0;
- while (@ARGV) {
- }
-
- $revoke = 1 if $cmd->{action} eq 'revoke';
- return 0;
+ whine "$cmd is unimplemented.";
+ return 1;
}
# Client <-> Server communication.
return $passwd;
}
+sub vi_form_while {
+ my $text = shift;
+ my $cb = shift;
+
+ my $error = 0;
+ my ($c, $o, $k, $e);
+ do {
+ my $ntext = vi($text);
+ return undef if ($error && $ntext eq $text);
+
+ $text = $ntext;
+
+ my $form = Form::parse($text);
+ $error = 0;
+ ($c, $o, $k, $e) = @{ $form->[0] };
+ if ( $e ) {
+ $error = 1;
+ $c = "# Syntax error.";
+ goto NEXT;
+ }
+ elsif (!@$o) {
+ return 0;
+ }
+
+ my ($status, $msg) = $cb->( $text, [$c, $o, $k, $e] );
+ unless ( $status ) {
+ $error = 1;
+ $c = "# $msg";
+ }
+
+ NEXT:
+ $text = Form::compose([[$c, $o, $k, $e]]);
+ } while ($error);
+
+ return $text;
+}
+
sub vi {
my ($text) = @_;
- my $file = "/tmp/rt.form.$$";
my $editor = $ENV{EDITOR} || $ENV{VISUAL} || "vi";
local $/ = undef;
- open( my $handle, '>', $file ) or die "$file: $!\n";
+ my $handle = File::Temp->new;
print $handle $text;
close($handle);
- system($editor, $file) && die "Couldn't run $editor.\n";
+ system($editor, $handle->filename) && die "Couldn't run $editor.\n";
- open( $handle, '<', $file ) or die "$file: $!\n";
+ open( $handle, '<', $handle->filename ) or die "$handle: $!\n";
$text = <$handle>;
close($handle);
- unlink($file);
-
return $text;
}
}
push @words, $s;
}
- elsif ( $a =~ /^q{/ ) {
+ elsif ( $a =~ /^q\{/ ) {
my $s = $a;
- while ( $a !~ /}$/ ) {
+ while ( $a !~ /\}$/ ) {
( $a, $b ) =
split /\s*,\s*/, $b, 2;
$s .= ',' . $a;
}
- $s =~ s/^q{/'/;
- $s =~ s/}/'/;
+ $s =~ s/^q\{/'/;
+ $s =~ s/\}/'/;
push @words, $s;
}
else {
ticket/1-3,5-7/history
user/ams
- user/ams/rights
- user/ams,rai,1/rights
For more information:
- edit
- create
- In addition, the following type-specific actions exist:
-
- - grant
- - revoke
-
- Attributes:
-
- The following attributes can be used with "rt show" or "rt edit"
- to retrieve or edit other information associated with users and
- groups:
-
- rights Global rights granted to this user.
- rights/<queue> Queue rights for this user.
-
--
Title: queue
-S var=val
Submits the specified variable with the request.
-t type Specifies object type.
+ -ct content-type Specifies content type of message(tickets only).
Examples:
# Interactive (starts $EDITOR with a form).
rt edit ticket/3
rt create -t ticket
+ rt create -t ticket -ct text/html
# Non-interactive.
rt edit ticket/1-3 add cc=foo@example.com set priority=3 due=tomorrow
Options:
-m <text> Specify comment text.
+ -ct <content-type> Specify content-type of comment text.
-a <file> Attach a file to the comment. (May be used more
than once to attach multiple files.)
-c <addrs> A comma-separated list of Cc addresses.
--
-Title: grant
-Title: revoke
-Text:
-
---
-
Title: query
Text: