summaryrefslogtreecommitdiff
path: root/rt/html/REST/1.0
diff options
context:
space:
mode:
Diffstat (limited to 'rt/html/REST/1.0')
-rw-r--r--rt/html/REST/1.0/Forms/queue/default123
-rw-r--r--rt/html/REST/1.0/Forms/queue/ns38
-rw-r--r--rt/html/REST/1.0/Forms/ticket/attachments107
-rw-r--r--rt/html/REST/1.0/Forms/ticket/default253
-rw-r--r--rt/html/REST/1.0/Forms/ticket/history144
-rw-r--r--rt/html/REST/1.0/Forms/ticket/links148
-rw-r--r--rt/html/REST/1.0/Forms/user/default141
-rw-r--r--rt/html/REST/1.0/Forms/user/ns41
-rw-r--r--rt/html/REST/1.0/NoAuth/mail-gateway7
-rw-r--r--rt/html/REST/1.0/autohandler32
-rw-r--r--rt/html/REST/1.0/dhandler287
-rw-r--r--rt/html/REST/1.0/logout27
-rw-r--r--rt/html/REST/1.0/search/dhandler32
-rw-r--r--rt/html/REST/1.0/search/ticket119
-rw-r--r--rt/html/REST/1.0/ticket/comment149
-rw-r--r--rt/html/REST/1.0/ticket/link96
-rw-r--r--rt/html/REST/1.0/ticket/merge78
17 files changed, 1820 insertions, 2 deletions
diff --git a/rt/html/REST/1.0/Forms/queue/default b/rt/html/REST/1.0/Forms/queue/default
new file mode 100644
index 0000000..ce54088
--- /dev/null
+++ b/rt/html/REST/1.0/Forms/queue/default
@@ -0,0 +1,123 @@
+%# REST/1.0/Forms/queue/default
+%#
+<%ARGS>
+$id
+$format => 's'
+$changes => {}
+</%ARGS>
+<%perl>
+my @comments;
+my ($c, $o, $k, $e) = ("", [], {}, 0);
+my %data = %$changes;
+my $queue = new RT::Queue $session{CurrentUser};
+my @fields = qw(Name Description CorrespondAddress CommentAddress
+ InitialPriority FinalPriority DefaultDueIn);
+my %fields = map { lc $_ => $_ } @fields;
+
+if ($id ne 'new') {
+ $queue->Load($id);
+ if (!$queue->Id) {
+ return [ "# Queue $id does not exist.", [], {}, 1 ];
+ }
+}
+else {
+ if (%data == 0) {
+ return [
+ "# Required: Name",
+ [ "id", @fields ],
+ {
+ id => 'queue/new',
+ Name => '<queue name>',
+ Description => "",
+ CommentAddress => "",
+ CorrespondAddress => "",
+ InitialPriority => "",
+ FinalPriority => "",
+ DefaultDueIn => "",
+ },
+ 0
+ ];
+ }
+ else {
+ my %v;
+ my %create = %fields;
+
+ foreach my $k (keys %data) {
+ if (exists $create{lc $k}) {
+ $v{$create{lc $k}} = delete $data{$k};
+ }
+ }
+
+ if ($v{Name} eq '<queue name>') {
+ my %o = keys %$changes;
+ delete @o{"id", @fields};
+ return [
+ "# Please set the queue name.",
+ [ "id", @fields, keys %o ], $changes, 1
+ ];
+ }
+
+ $queue->Create(%v);
+ unless ($queue->Id) {
+ return [ "# Could not create queue.", [], {}, 1 ];
+ }
+
+ delete $data{id};
+ $id = $queue->Id;
+ push(@comments, "# Queue $id created.");
+ goto DONE if %data == 0;
+ }
+}
+
+if (%data == 0) {
+ my @data;
+
+ push @data, [ id => "queue/".$queue->Id ];
+ foreach my $key (@fields) {
+ push @data, [ $key => $queue->$key ];
+ }
+
+ my %k = map {@$_} @data;
+ $o = [ map {$_->[0]} @data ];
+ $k = \%k;
+}
+else {
+ my ($get, $set, $key, $val, $n, $s);
+
+ foreach $key (keys %data) {
+ $val = $data{$key};
+ $key = lc $key;
+ $n = 1;
+
+ if (exists $fields{$key}) {
+ $key = $fields{$key};
+ $set = "Set$key";
+
+ next if $val eq $queue->$key;
+ ($n, $s) = $queue->$set($val);
+ }
+ elsif ($key ne 'id') {
+ $n = 0;
+ $s = "Unknown field.";
+ }
+
+ SET:
+ if ($n == 0) {
+ $e = 1;
+ push @comments, "# $key: $s";
+ unless (@$o) {
+ my %o = keys %$changes;
+ delete @o{"id", @fields};
+ @$o = ("id", @fields, keys %o);
+ $k = $changes;
+ }
+ }
+ }
+
+ push(@comments, "# Queue $id updated.") unless $n == 0;
+}
+
+DONE:
+$c ||= join("\n", @comments) if @comments;
+return [ $c, $o, $k, $e ];
+</%perl>
diff --git a/rt/html/REST/1.0/Forms/queue/ns b/rt/html/REST/1.0/Forms/queue/ns
new file mode 100644
index 0000000..360eea8
--- /dev/null
+++ b/rt/html/REST/1.0/Forms/queue/ns
@@ -0,0 +1,38 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# This work is made available to you under the terms of Version 2 of
+%# the GNU General Public License. A copy of that license should have
+%# been provided with this software, but in any event can be snarfed
+%# from www.gnu.org.
+%#
+%# This work is distributed in the hope that it will be useful, but
+%# WITHOUT ANY WARRANTY; without even the implied warranty of
+%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+%# General Public License for more details.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+%# REST/1.0/Forms/queue/ns
+%#
+<%ARGS>
+$id
+</%ARGS>
+<%perl>
+use RT::Queues;
+
+my $queues = new RT::Queues $session{CurrentUser};
+$queues->Limit(FIELD => 'Name', OPERATOR => '=', VALUE => $id);
+if ($queues->Count == 0) {
+ return (0, "No queue named $id exists.");
+}
+return $queues->Next->Id;
+</%perl>
diff --git a/rt/html/REST/1.0/Forms/ticket/attachments b/rt/html/REST/1.0/Forms/ticket/attachments
new file mode 100644
index 0000000..bcb571a
--- /dev/null
+++ b/rt/html/REST/1.0/Forms/ticket/attachments
@@ -0,0 +1,107 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# This work is made available to you under the terms of Version 2 of
+%# the GNU General Public License. A copy of that license should have
+%# been provided with this software, but in any event can be snarfed
+%# from www.gnu.org.
+%#
+%# This work is distributed in the hope that it will be useful, but
+%# WITHOUT ANY WARRANTY; without even the implied warranty of
+%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+%# General Public License for more details.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+%# REST/1.0/Forms/ticket/attachments
+%#
+<%ARGS>
+$id
+$args => undef
+</%ARGS>
+<%perl>
+my @data;
+my ($c, $o, $k, $e) = ("", [], {}, "");
+my $ticket = new RT::Ticket $session{CurrentUser};
+
+$ticket->Load($id);
+unless ($ticket->Id) {
+ return [ "# Ticket $id does not exist.", [], {}, 1 ];
+}
+
+my @arglist = split('/', $args);
+my ($aid, $content);
+
+if ($arglist[1] eq 'content') {
+ $aid = $arglist[0];
+ $content = 1;
+} else {
+ $aid = $args;
+ $content = 0;
+}
+
+if ($aid) {
+ unless ($aid =~ /^\d+$/) {
+ return [ "# Invalid attachment id: $aid", [], {}, 1 ];
+ }
+ my $attachment = new RT::Attachment $session{CurrentUser};
+ $attachment->Load($aid);
+ unless ($attachment->Id eq $aid) {
+ return [ "# Invalid attachment id: $aid", [], {}, 1 ];
+ }
+ if ($content) {
+ $c = $attachment->Content;
+ } else {
+ my @data;
+ push @data, [ id => $attachment->Id ];
+ push @data, [ Subject => $attachment->Subject ];
+ push @data, [ Creator => $attachment->Creator ];
+ push @data, [ Created => $attachment->Created ];
+ push @data, [ Transaction => $attachment->TransactionId ];
+ push @data, [ Parent => $attachment->Parent ];
+ push @data, [ MessageId => $attachment->MessageId ];
+ push @data, [ Filename => $attachment->Filename ];
+ push @data, [ ContentType => $attachment->ContentType ];
+ push @data, [ ContentEncoding => $attachment->ContentEncoding ];
+ push @data, [ Headers => $attachment->Headers ];
+ push @data, [ Content => $attachment->Content ];
+
+ my %k = map {@$_} @data;
+ $o = [ map {$_->[0]} @data ];
+ $k = \%k;
+ }
+
+}
+else {
+ my @attachments;
+ my $transactions = $ticket->Transactions;
+ while (my $t = $transactions->Next) {
+ my $attachments = $t->Attachments;
+ while (my $a = $attachments->Next) {
+ next unless $a->Filename;
+ my $size = length($a->Content);
+ if ($size > 1024) { $size = int($size/102.4)/10 . "k" }
+ else { $size .= "b" }
+ push @attachments, $a->Id.": ".$a->Filename." (".$size.")";
+ }
+ }
+
+ if (@attachments) {
+ $o = [ "id", "Attachments" ];
+ $k = {
+ id => "ticket/".$ticket->Id."/attachments",
+ Attachments => \@attachments
+ };
+ }
+}
+
+return [ $c, $o, $k, $e ];
+</%perl>
diff --git a/rt/html/REST/1.0/Forms/ticket/default b/rt/html/REST/1.0/Forms/ticket/default
new file mode 100644
index 0000000..fec499b
--- /dev/null
+++ b/rt/html/REST/1.0/Forms/ticket/default
@@ -0,0 +1,253 @@
+%# REST/1.0/Forms/ticket/default
+%#
+<%ARGS>
+$id
+$changes => {}
+$fields => undef
+</%ARGS>
+<%perl>
+use MIME::Entity;
+
+my @comments;
+my ($c, $o, $k, $e) = ("", [], {}, 0);
+my %data = %$changes;
+my $ticket = new RT::Ticket $session{CurrentUser};
+my @dates = qw(Created Starts Started Due Resolved Told);
+my @people = qw(Requestors Cc AdminCc);
+my @create = qw(Queue Requestor Subject Cc AdminCc Owner Status Priority
+ InitialPriority FinalPriority TimeEstimated TimeWorked
+ TimeLeft Starts Started Due Resolved);
+my @simple = qw(Subject Status Priority Disabled TimeEstimated TimeWorked
+ TimeLeft InitialPriority FinalPriority);
+my %dates = map {lc $_ => $_} @dates;
+my %people = map {lc $_ => $_} @people;
+my %create = map {lc $_ => $_} @create;
+my %simple = map {lc $_ => $_} @simple;
+
+# Are we dealing with an existing ticket?
+if ($id ne 'new') {
+ $ticket->Load($id);
+ if (!$ticket->Id) {
+ return [ "# Ticket $id does not exist.", [], {}, 1 ];
+ }
+ elsif (!$ticket->CurrentUserHasRight('ShowTicket') ||
+ (%data && !$ticket->CurrentUserHasRight('ModifyTicket')))
+ {
+ my $act = %data ? "modify" : "display";
+ return [ "# You are not allowed to $act ticket $id.", [], {}, 1 ];
+ }
+}
+else {
+ if (%data == 0) {
+ # GET ticket/new: Return a suitable default form.
+ # We get defaults from queue/1 (XXX: What if it isn't there?).
+ my $due = new RT::Date $session{CurrentUser};
+ my $queue = new RT::Queue $session{CurrentUser};
+ my $starts = new RT::Date $session{CurrentUser};
+ $queue->Load(1);
+ $due->SetToNow;
+ $due->AddDays($queue->DefaultDueIn) if $queue->DefaultDueIn;
+ $starts->SetToNow;
+
+ return [
+ "# Required: Queue, Requestor, Subject",
+ [ qw(id Queue Requestor Subject Cc AdminCc Owner Status Priority
+ InitialPriority FinalPriority TimeEstimated Starts Due Text) ],
+ {
+ id => "ticket/new",
+ Queue => $queue->Name,
+ Requestor => $session{CurrentUser}->Name,
+ Subject => "",
+ Cc => [],
+ AdminCc => [],
+ Owner => "",
+ Status => "new",
+ Priority => $queue->InitialPriority,
+ InitialPriority => $queue->InitialPriority,
+ FinalPriority => $queue->FinalPriority,
+ TimeEstimated => 0,
+ Starts => $starts->ISO,
+ Due => $due->ISO,
+ Text => "",
+ },
+ 0
+ ];
+ }
+ else {
+ # We'll create a new ticket, and fall through to set fields that
+ # can't be set in the call to Create().
+ my (%v, $text);
+
+ foreach my $k (keys %data) {
+ if (exists $create{lc $k}) {
+ $v{$create{lc $k}} = delete $data{$k};
+ }
+ elsif (lc $k eq 'text') {
+ $text = delete $data{$k};
+ }
+ }
+
+ if ($text) {
+ $v{MIMEObj} =
+ MIME::Entity->build(
+ From => $session{CurrentUser}->EmailAddress,
+ Subject => $v{Subject},
+ Data => $text
+ );
+ }
+
+ $ticket->Create(%v);
+ unless ($ticket->Id) {
+ return [ "# Could not create ticket.", [], {}, 1 ];
+ }
+
+ delete $data{id};
+ $id = $ticket->Id;
+ push(@comments, "# Ticket $id created.");
+ goto DONE if %data == 0;
+ }
+}
+
+# Now we know we're dealing with an existing ticket.
+if (%data == 0) {
+ my ($time, $key, $val, @data);
+
+ push @data, [ id => "ticket/".$ticket->Id ];
+ push @data, [ Queue => $ticket->QueueObj->Name ]
+ if (!%$fields || exists $fields->{lc 'Queue'});
+ push @data, [ Owner => $ticket->OwnerObj->Name ]
+ if (!%$fields || exists $fields->{lc 'Owner'});
+ push @data, [ Creator => $ticket->CreatorObj->Name ]
+ if (!%$fields || exists $fields->{lc 'Creator'});
+
+ foreach (qw(Subject Status Priority InitialPriority FinalPriority)) {
+ next unless (!%$fields || (exists $fields->{lc $_}));
+ push @data, [$_ => $ticket->$_ ];
+ }
+
+ foreach $key (@people) {
+ next unless (!%$fields || (exists $fields->{lc $key}));
+ push @data, [ $key => [ $ticket->$key->MemberEmailAddresses ] ];
+ }
+
+ $time = new RT::Date ($session{CurrentUser});
+ foreach $key (@dates) {
+ next unless (!%$fields || (exists $fields->{lc $key}));
+ $time->Set(Format => 'sql', Value => $ticket->$key);
+ push @data, [ $key => $time->AsString ];
+ }
+
+ $time = new RT::Date ($session{CurrentUser});
+ foreach $key (qw(TimeEstimated TimeWorked TimeLeft)) {
+ next unless (!%$fields || (exists $fields->{lc $key}));
+ $val = $ticket->$key || 0;
+ $val = $time->DurationAsString($val*60) if $val;
+ push @data, [ $key => $val ];
+ }
+
+ my %k = map {@$_} @data;
+ $o = [ map {$_->[0]} @data ];
+ $k = \%k;
+}
+else {
+ my ($get, $set, $key, $val, $n, $s);
+
+ foreach $key (keys %data) {
+ $val = $data{$key};
+ $key = lc $key;
+ $n = 1;
+
+ if (ref $val eq 'ARRAY') {
+ unless ($key =~ /^(?:Requestors|Cc|AdminCc)$/i) {
+ $n = 0;
+ $s = "$key may have only one value.";
+ goto SET;
+ }
+ }
+
+ if ($key =~ /^queue$/i) {
+ next if $val eq $ticket->QueueObj->Name;
+ ($n, $s) = $ticket->SetQueue($val);
+ }
+ elsif ($key =~ /^owner$/i) {
+ next if $val eq $ticket->OwnerObj->Name;
+ ($n, $s) = $ticket->SetOwner($val);
+ }
+ elsif (exists $simple{$key}) {
+ $key = $simple{$key};
+ $set = "Set$key";
+
+ next if $val eq $ticket->$key;
+ ($n, $s) = $ticket->$set($val);
+ }
+ elsif (exists $dates{$key}) {
+ $key = $dates{$key};
+ $set = "Set$key";
+
+ my $time = new RT::Date $session{CurrentUser};
+ $time->Set(Format => 'sql', Value => $ticket->$key);
+ next if ($val =~ /^not set$/i || $val eq $time->AsString);
+ ($n, $s) = $ticket->$set($val);
+ }
+ elsif (exists $people{$key}) {
+ $key = $people{$key};
+ my ($p, @msgs);
+
+ my %new = map {$_=>1} @{ vsplit($val) };
+ my %old = map {$_=>1} $ticket->$key->MemberEmailAddresses;
+ my $type = $key eq 'Requestors' ? 'Requestor' : $key;
+
+ foreach $p (keys %old) {
+ unless (exists $new{$p}) {
+ ($s, $n) = $ticket->DeleteWatcher(Type => $type,
+ Email => $p);
+ push @msgs, [ $s, $n ];
+ }
+ }
+ foreach $p (keys %new) {
+ # XXX: This is a stupid test.
+ unless ($p =~ /^[\w.+-]+\@([\w.-]+\.)*\w+.?$/) {
+ $s = 0;
+ $n = "$p is not a valid email address.";
+ push @msgs, [ $s, $n ];
+ next;
+ }
+ unless ($ticket->IsWatcher(Type => $type, Email => $p)) {
+ ($s, $n) = $ticket->AddWatcher(Type => $type,
+ Email => $p);
+ push @msgs, [ $s, $n ];
+ }
+ }
+
+ $n = 1;
+ if (@msgs = grep {$_->[0] == 0} @msgs) {
+ $n = 0;
+ $s = join "\n", map {"# ".$_->[1]} @msgs;
+ $s =~ s/^# //;
+ }
+ }
+ elsif ($key ne 'id' && $key ne 'type') {
+ $n = 0;
+ $s = "Unknown field.";
+ }
+
+ SET:
+ if ($n == 0) {
+ $e = 1;
+ push @comments, "# $key: $s";
+ unless (@$o) {
+ my %o = keys %$changes;
+ delete $o{id};
+ @$o = ("id", keys %o);
+ $k = $changes;
+ }
+ }
+ }
+ push(@comments, "# Ticket ".$ticket->id." updated.") unless $n == 0;
+}
+
+DONE:
+$c ||= join("\n", @comments) if @comments;
+return [$c, $o, $k, $e];
+
+</%perl>
diff --git a/rt/html/REST/1.0/Forms/ticket/history b/rt/html/REST/1.0/Forms/ticket/history
new file mode 100644
index 0000000..f5c1dc9
--- /dev/null
+++ b/rt/html/REST/1.0/Forms/ticket/history
@@ -0,0 +1,144 @@
+%# REST/1.0/Forms/ticket/history
+%#
+<%ARGS>
+$id
+$args => undef
+$format => undef
+$fields => undef
+</%ARGS>
+<%perl>
+my $ticket = new RT::Ticket $session{CurrentUser};
+my ($c, $o, $k, $e) = ("", [], {}, "");
+
+$ticket->Load($id);
+unless ($ticket->Id) {
+ return [ "# Ticket $id does not exist.", [], {}, 1 ];
+}
+
+my $trans = $ticket->Transactions();
+my $total = $trans->Count();
+
+chomp $args;
+my @arglist = split('/', $args);
+my ($type, $tid);
+
+if ($arglist[0] eq 'type') {
+ $type = $arglist[1];
+} elsif ($arglist[0] eq 'id') {
+ $tid = $arglist[1];
+} else {
+ $type = $args;
+}
+
+if ($type) {
+ # Create, Set, Status, Correspond, Comment, Give, Steal, Take, Told
+ # CustomField, AddLink, DeleteLink, AddWatcher, DelWatcher
+ if ($args =~ /^links?$/) {
+ $trans->Limit(FIELD => 'Type', OPERATOR => 'LIKE', VALUE => '%Link');
+ }
+ elsif ($args =~ /^watchers?$/) {
+ $trans->Limit(FIELD => 'Type', OPERATOR => 'LIKE', VALUE => '%Watcher');
+ }
+ else {
+ $trans->Limit(FIELD => 'Type', OPERATOR => '=', VALUE => $type);
+ }
+} elsif ($tid) {
+ $trans->Limit(FIELD => 'Id', OPERATOR => '=', VALUE => $tid);
+}
+
+if ($tid) {
+ my @data;
+ my $t = new RT::Transaction $session{CurrentUser};
+ $t->Load($tid);
+
+ push @data, [ id => $t->Id ];
+ push @data, [ EffectiveTicket => $t->EffectiveTicket ]
+ if (!%$fields || exists $fields->{lc 'EffectiveTicket'});
+ push @data, [ Ticket => $t->Ticket ]
+ if (!%$fields || exists $fields->{lc 'Ticket'});
+ push @data, [ TimeTaken => $t->TimeTaken ]
+ if (!%$fields || exists $fields->{lc 'TimeTaken'});
+ push @data, [ Type => $t->Type ]
+ if (!%$fields || exists $fields->{lc 'Type'});
+ push @data, [ Field => $t->Field ]
+ if (!%$fields || exists $fields->{lc 'Field'});
+ push @data, [ OldValue => $t->OldValue ]
+ if (!%$fields || exists $fields->{lc 'OldValue'});
+ push @data, [ NewValue => $t->NewValue ]
+ if (!%$fields || exists $fields->{lc 'NewValue'});
+ push @data, [ Data => $t->Data ]
+ if (!%$fields || exists $fields->{lc 'Data'});
+ push @data, [ Description => $t->Description ]
+ if (!%$fields || exists $fields->{lc 'Description'});
+ push @data, [ Content => $t->Content ]
+ if (!%$fields || exists $fields->{lc 'Content'});
+
+
+ if (!%$fields || exists $fields->{lc 'Content'}) {
+ my $creator = new RT::User $session{CurrentUser};
+ $creator->Load($t->Creator);
+ push @data, [ Creator => $creator->Name ];
+ }
+ push @data, [ Created => $t->Created ]
+ if (!%$fields || exists $fields->{lc 'Created'});
+
+ if (!%$fields || exists $fields->{lc 'Attachments'}) {
+ my $attachlist;
+ my $attachments = $t->Attachments;
+ while (my $a = $attachments->Next) {
+ my $size = length($a->Content);
+ if ($size > 1024) { $size = int($size/102.4)/10 . "k" }
+ else { $size .= "b" }
+ $attachlist .= "\n" . $a->Id.": ".($a->Filename || "untitled")." (".$size.")";
+ }
+
+ push @data, [Attachments => $attachlist];
+ }
+
+ my %k = map {@$_} @data;
+ $o = [ map {$_->[0]} @data ];
+ $k = \%k;
+
+} else {
+ my (@data, $tids);
+ $format ||= "s";
+ $format = "l" if (%$fields);
+
+ while (my $t = $trans->Next) {
+ my $tid = $t->Id;
+
+ if ($format eq "l") {
+ $tids .= "," if $tids;
+ $tids .= $tid;
+ } else {
+ push @$o, $tid;
+ $k->{$tid} = $t->Description;
+ }
+ }
+
+ if ($format eq "l") {
+ my @tid;
+ push @tid, "ticket/$id/history/id/$tids";
+ my $fieldstring;
+ foreach my $key (keys %$fields) {
+ $fieldstring .= "," if $fieldstring;
+ $fieldstring .= $key;
+ }
+ my ($content, $forms);
+
+ $m->subexec("$RT::WebPath/REST/1.0/show",
+ id => \@tid,
+ format => $format,
+ fields => $fieldstring);
+ return [ $c, $o, $k, $e ];
+ }
+}
+
+if (!$c) {
+ my $sub = $trans->Count();
+ $c = "# $sub/$total ($args/total)";
+}
+
+return [ $c, $o, $k, $e ];
+
+</%perl>
diff --git a/rt/html/REST/1.0/Forms/ticket/links b/rt/html/REST/1.0/Forms/ticket/links
new file mode 100644
index 0000000..8ac9dc2
--- /dev/null
+++ b/rt/html/REST/1.0/Forms/ticket/links
@@ -0,0 +1,148 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# This work is made available to you under the terms of Version 2 of
+%# the GNU General Public License. A copy of that license should have
+%# been provided with this software, but in any event can be snarfed
+%# from www.gnu.org.
+%#
+%# This work is distributed in the hope that it will be useful, but
+%# WITHOUT ANY WARRANTY; without even the implied warranty of
+%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+%# General Public License for more details.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+%# REST/1.0/Forms/ticket/links
+%#
+<%ARGS>
+$id
+$format => 's'
+$changes => undef
+</%ARGS>
+<%perl>
+my @data;
+my $ticket = new RT::Ticket $session{CurrentUser};
+
+$ticket->Load($id);
+if (!$ticket->Id) {
+ return [ "# Ticket $id does not exist.", [], {}, 1 ];
+}
+
+my ($c, $o, $k, $e) = ("", [], {}, 0);
+my @fields = qw(DependsOn DependedOnBy RefersTo ReferredToBy Members MemberOf);
+my %fields = map { lc $_ => $_ } @fields;
+
+my %lfields = (
+ Members => { Type => 'MemberOf', Mode => 'Base' },
+ ReferredToBy => { Type => 'RefersTo', Mode => 'Base' },
+ DependedOnBy => { Type => 'DependsOn', Mode => 'Base' },
+ MemberOf => { Type => 'MemberOf', Mode => 'Target' },
+ RefersTo => { Type => 'RefersTo', Mode => 'Target' },
+ DependsOn => { Type => 'DependsOn', Mode => 'Target' },
+);
+
+if ($changes) {
+ my ($get, $set, $key, $val, $n, $s);
+ my %data = %$changes;
+ my @comments;
+
+ foreach $key (keys %data) {
+ $val = $data{$key};
+ $key = lc $key;
+ $n = 1;
+
+ if (exists $fields{$key}) {
+ $key = $fields{$key};
+
+ my %old;
+ my $field = $lfields{$key}->{Mode};
+ while (my $link = $ticket->$key->Next) {
+ $old{$link->$field} = 1;
+ }
+
+ my %new;
+ foreach my $nkey (@{vsplit($val)}) {
+ if ($nkey =~ /^\d+$/) {
+ my $uri = new RT::URI $session{CurrentUser};
+ my $tick = new RT::Ticket $session{CurrentUser};
+ $tick->Load($nkey);
+ if ($tick->Id) {
+ $nkey = $uri->FromObject($tick);
+ }
+ else {
+ $n = 0;
+ $s = "Ticket $nkey does not exist.";
+ goto SET;
+ }
+ }
+ $new{$nkey} = 1;
+ }
+
+ foreach my $u (keys %old) {
+ if (exists $new{$u}) {
+ delete $new{$u};
+ }
+ else {
+ my $type = $lfields{$key}->{Type};
+ my $mode = $lfields{$key}->{Mode};
+ ($n, $s) = $ticket->DeleteLink(Type => $type, $mode => $u);
+ goto SET;
+ }
+ }
+ foreach my $u (keys %new) {
+ my $type = $lfields{$key}->{Type};
+ my $mode = $lfields{$key}->{Mode};
+ ($n, $s) = $ticket->AddLink(Type => $type, $mode => $u);
+ goto SET;
+ }
+ }
+ elsif ($key ne 'id' && $key ne 'type') {
+ $n = 0;
+ $s = "Unknown field: $key";
+ }
+
+ SET:
+ if ($n == 0) {
+ $e = 1;
+ push @comments, "# $key: $s";
+ unless (@$o) {
+ @$o = ("id", @fields);
+ %$k = %data;
+ }
+ }
+ }
+
+ push(@comments, "# Links for ticket $id updated.") unless @comments;
+ $c = join("\n", @comments) if @comments;
+}
+else {
+ my @data;
+
+ push @data, [ id => "ticket/".$ticket->Id."/links" ];
+ foreach my $key (@fields) {
+ my @val;
+
+ my $field = $lfields{$key}->{Mode};
+ while (my $link = $ticket->$key->Next) {
+ push @val, $link->$field;
+ }
+ push(@val, "") if (@val == 0 && $format eq 'l');
+ push @data, [ $key => [ @val ] ] if @val;
+ }
+
+ my %k = map {@$_} @data;
+ $o = [ map {$_->[0]} @data ];
+ $k = \%k;
+}
+
+return [ $c, $o, $k, $e ];
+</%perl>
diff --git a/rt/html/REST/1.0/Forms/user/default b/rt/html/REST/1.0/Forms/user/default
new file mode 100644
index 0000000..6b216e0
--- /dev/null
+++ b/rt/html/REST/1.0/Forms/user/default
@@ -0,0 +1,141 @@
+%# REST/1.0/Forms/user/default
+%#
+<%ARGS>
+$id
+$format => 's'
+$changes => {}
+</%ARGS>
+<%perl>
+my @comments;
+my ($c, $o, $k, $e) = ("", [], {}, 0);
+my %data = %$changes;
+my $user = new RT::User $session{CurrentUser};
+my @fields = qw(RealName NickName Gecos Organization Address1 Address2 City
+ State Zip Country HomePhone WorkPhone MobilePhone PagerPhone
+ FreeformContactInfo Comments Signature Lang EmailEncoding
+ WebEncoding ExternalContactInfoId ContactInfoSystem
+ ExternalAuthId AuthSystem);
+my %fields = map { lc $_ => $_ } @fields;
+
+if ($id ne 'new') {
+ $user->Load($id);
+ if (!$user->Id) {
+ return [ "# User $id does not exist.", [], {}, 1 ];
+ }
+}
+else {
+ if (%data == 0) {
+ return [
+ "# Required: Name, EmailAddress",
+ [ qw(id Name EmailAddress Organization Password Comments) ],
+ {
+ id => "user/new",
+ Name => "",
+ EmailAddress => "",
+ Organization => "",
+ Password => "",
+ Comments => ""
+ },
+ 0
+ ];
+ }
+ else {
+ my %v;
+ my %create = %fields;
+ $create{name} = "Name";
+ $create{password} = "Password";
+ $create{emailaddress} = "EmailAddress";
+ $create{contactinfo} = "FreeformContactInfo";
+ # Do any fields need to be excluded here?
+
+ foreach my $k (keys %data) {
+ if (exists $create{lc $k}) {
+ $v{$create{lc $k}} = delete $data{$k};
+ }
+ }
+
+ $user->Create(%v);
+ unless ($user->Id) {
+ return [ "# Could not create user.", [], {}, 1 ];
+ }
+
+ $id = $user->Id;
+ delete $data{id};
+ push(@comments, "# User $id created.");
+ goto DONE if %data == 0;
+ }
+}
+
+if (%data == 0) {
+ my @data;
+
+ push @data, [ id => "user/".$user->Id ];
+ push @data, [ Name => $user->Name ];
+ push @data, [ Password => '********' ];
+ push @data, [ EmailAddress => $user->EmailAddress ];
+
+ foreach my $key (@fields) {
+ my $val = $user->$key;
+
+ if ($format eq 'l' || (defined $val && $val ne '')) {
+ $key = "ContactInfo" if $key eq 'FreeformContactInfo';
+ push @data, [ $key => $val ];
+ }
+ }
+
+ my %k = map {@$_} @data;
+ $o = [ map {$_->[0]} @data ];
+ $k = \%k;
+}
+else {
+ my ($get, $set, $key, $val, $n, $s);
+
+ foreach $key (keys %data) {
+ $val = $data{$key};
+ $key = lc $key;
+ $n = 1;
+
+ if ($key eq 'name' || $key eq 'emailaddress' ||
+ $key eq 'contactinfo' || exists $fields{$key})
+ {
+ if (exists $fields{$key}) {
+ $key = $fields{$key};
+ }
+ else {
+ $key = "FreeformContactInfo" if $key eq 'contactinfo';
+ $key = "EmailAddress" if $key eq 'emailaddress';
+ $key = "Name" if $key eq 'name';
+ }
+ $set = "Set$key";
+
+ next if $val eq $user->$key;
+ ($n, $s) = $user->$set($val);
+ }
+ elsif ($key eq 'password') {
+ ($n, $s) = $user->SetPassword($val) unless $val =~ /^\**$/;
+ }
+ elsif ($key ne 'id') {
+ $n = 0;
+ $s = "Unknown field.";
+ }
+
+ SET:
+ if ($n == 0) {
+ $e = 1;
+ push @comments, "# $key: $s";
+ unless (@$o) {
+ my %o = keys %$changes;
+ delete @o{"id", @fields};
+ @$o = ("id", @fields, keys %o);
+ $k = $changes;
+ }
+ }
+ }
+
+ push(@comments, "# User $id updated.") unless $n == 0;
+}
+
+DONE:
+$c ||= join("\n", @comments) if @comments;
+return [ $c, $o, $k, $e ];
+</%perl>
diff --git a/rt/html/REST/1.0/Forms/user/ns b/rt/html/REST/1.0/Forms/user/ns
new file mode 100644
index 0000000..36b3237
--- /dev/null
+++ b/rt/html/REST/1.0/Forms/user/ns
@@ -0,0 +1,41 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# This work is made available to you under the terms of Version 2 of
+%# the GNU General Public License. A copy of that license should have
+%# been provided with this software, but in any event can be snarfed
+%# from www.gnu.org.
+%#
+%# This work is distributed in the hope that it will be useful, but
+%# WITHOUT ANY WARRANTY; without even the implied warranty of
+%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+%# General Public License for more details.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+%# REST/1.0/Forms/user/ns
+%#
+<%ARGS>
+$id
+</%ARGS>
+<%perl>
+use RT::Users;
+
+my $field = "Name";
+$field = "EmailAddress" if $id =~ /\@/;
+
+my $users = new RT::Users $session{CurrentUser};
+$users->Limit(FIELD => $field, OPERATOR => '=', VALUE => $id);
+if ($users->Count == 0) {
+ return (0, "No user named $id exists.");
+}
+return $users->Next->Id;
+</%perl>
diff --git a/rt/html/REST/1.0/NoAuth/mail-gateway b/rt/html/REST/1.0/NoAuth/mail-gateway
index 8db80d5..359331f 100644
--- a/rt/html/REST/1.0/NoAuth/mail-gateway
+++ b/rt/html/REST/1.0/NoAuth/mail-gateway
@@ -29,12 +29,15 @@ $ticket => undef
</%ARGS>
<%init>
use RT::Interface::Email;
-my ( $status, $error, $Ticket ) = RT::Interface::Email::Gateway( %ARGS);
+my ( $status, $error, $Ticket ) = RT::Interface::Email::Gateway(\%ARGS);
</%init>
<%flags>
inherit => undef # inhibit UTF8 conversion done in /autohandler
</%flags>
-% if ($status) {
+% if ($status == -75 ) {
+temporary failure
+% }
+% elsif ($status == 1) {
ok
% if ( $Ticket->Id ) {
Ticket: <% $Ticket->Id %>
diff --git a/rt/html/REST/1.0/autohandler b/rt/html/REST/1.0/autohandler
new file mode 100644
index 0000000..9084a1b
--- /dev/null
+++ b/rt/html/REST/1.0/autohandler
@@ -0,0 +1,32 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# This work is made available to you under the terms of Version 2 of
+%# the GNU General Public License. A copy of that license should have
+%# been provided with this software, but in any event can be snarfed
+%# from www.gnu.org.
+%#
+%# This work is distributed in the hope that it will be useful, but
+%# WITHOUT ANY WARRANTY; without even the implied warranty of
+%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+%# General Public License for more details.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+%# REST/1.0/autohandler
+%#
+<%INIT>
+use RT::Interface::REST;
+$r->content_type('text/plain');
+$m->error_format('text');
+$m->call_next();
+$m->abort();
+</%INIT>
diff --git a/rt/html/REST/1.0/dhandler b/rt/html/REST/1.0/dhandler
new file mode 100644
index 0000000..ef5217f
--- /dev/null
+++ b/rt/html/REST/1.0/dhandler
@@ -0,0 +1,287 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# This work is made available to you under the terms of Version 2 of
+%# the GNU General Public License. A copy of that license should have
+%# been provided with this software, but in any event can be snarfed
+%# from www.gnu.org.
+%#
+%# This work is distributed in the hope that it will be useful, but
+%# WITHOUT ANY WARRANTY; without even the implied warranty of
+%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+%# General Public License for more details.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+%# REST/1.0/dhandler
+%#
+<%ARGS>
+@id => ()
+$fields => undef
+$format => undef
+$content => undef
+</%ARGS>
+<%INIT>
+use RT::Interface::REST;
+
+my $output = "";
+my $status = "200 Ok";
+my $object = $m->dhandler_arg;
+
+my $name = qr{[\w.-]+};
+my $list = '(?:(?:\d+-)?\d+,)*(?:\d+-)?\d+';
+my $label = '[a-zA-Z0-9@_.+-]+';
+my $field = '[a-zA-Z][a-zA-Z0-9_-]*';
+my $labels = "(?:$label,)*$label";
+
+# We must handle requests such as the following:
+#
+# 1. http://.../REST/1.0/show (with a list of object specifications).
+# 2. http://.../REST/1.0/edit (with a self-contained list of forms).
+# 3. http://.../REST/1.0/ticket/show (implicit type specification).
+# http://.../REST/1.0/ticket/edit
+# 4. http://.../REST/1.0/ticket/nn (all possibly with a single form).
+# http://.../REST/1.0/ticket/nn/history
+# http://.../REST/1.0/ticket/nn/attachment/1
+#
+# Objects are specified by their type, and either a unique numeric ID,
+# or a unique name (e.g. ticket/1, queue/foo). Multiple objects of the
+# same type may be specified by a comma-separated list of identifiers
+# (e.g., user/ams,rai or ticket/1-3,5-7).
+#
+# Ultimately, we want a list of object specifications to operate upon.
+# The URLs in (4) provide enough information to identify an object. We
+# will assemble submitted information into that format in other cases.
+#
+my (@objects, $forms);
+my $utype;
+
+if ($object eq 'show' || # $REST/show
+ (($utype) = ($object =~ m{^($name)/show$}))) # $REST/ticket/show
+{
+ # We'll convert type/range specifications ("ticket/1-3,7-9/history")
+ # into a list of singular object specifications ("ticket/1/history").
+ # If the URL specifies a type, we'll accept only that one.
+ foreach my $id (@id) {
+ $id =~ s|^(?:$utype/)?|$utype/| if $utype;
+ if (my ($type, $oids, $extra) =
+ ($id =~ m#^($name)/($list|$labels)(?:(/.*))?$#o))
+ {
+ foreach my $oid (expand_list($oids)) {
+ if ($extra =~ m{^(?:/($name)(?:/(.*))?)?$}o) {
+ my ($attr, $args) = ($1, $2);
+ # expand transaction and attachment range specifications
+ # (if applicable)
+ my $tids;
+ if ($attr eq 'history' && $args =~ m#id/(\d.*)#o) {
+ $tids = $1;
+ }
+ if ($tids) {
+ push(@objects, "$type/$oid/$attr/id/$_") for expand_list($tids);
+ } else {
+ push(@objects, "$type/$oid$extra");
+ }
+ }
+ }
+ }
+ else {
+ $status = "400 Bad Request";
+ $output = "Invalid object ID specified: '$id'";
+ goto OUTPUT;
+ }
+ }
+}
+elsif ($object eq 'edit' || # $REST/edit
+ (($utype) = ($object =~ m{^($name)/edit$}))) # $REST/ticket/edit
+{
+ # We'll make sure each of the submitted forms is syntactically valid
+ # and sufficiently identifies an object to operate upon, then add to
+ # the object list as above.
+ my @output;
+
+ $forms = form_parse($content);
+ foreach my $form (@$forms) {
+ my ($c, $o, $k, $e) = @$form;
+
+ if ($e) {
+ push @output, [ "# Syntax error.", $o, $k, $e ];
+ }
+ else {
+ my ($type, $id);
+
+ # Look for matching types in the ID, form, and URL.
+ $type = exists $k->{type} ? $k->{type} : $utype;
+ $type =~ s|^(?:$utype)?|$utype/| if $utype;
+ $type =~ s|/$||;
+
+ if (exists $k->{id}) {
+ $id = $k->{id};
+ $id =~ s|^(?:$type/)?|$type/| if $type;
+
+ if ($id =~ m#^$name/(?:$label|\d+)(?:/.*)?#o) {
+ push @objects, $id;
+ }
+ else {
+ push @output, [ "# Invalid object ID: '$id'", $o, $k, $e ];
+ }
+ }
+ else {
+ push @output, [ "# No object ID specified.", $o, $k, $e ];
+ }
+ }
+ }
+ # If we saw any errors at this stage, we won't process any part of
+ # the submitted data.
+ if (@output) {
+ unshift @output, [ "# Please resubmit with errors corrected." ];
+ $status = "409 Syntax Error";
+ $output = form_compose(\@output);
+ goto OUTPUT;
+ }
+}
+else {
+ # We'll assume that this is in the correct format already. Otherwise
+ # it will be caught by the loop below.
+ push @objects, $object;
+
+ if ($content) {
+ $forms = form_parse($content);
+
+ if (@$forms > 1) {
+ $status = "400 Bad Request";
+ $output = "You may submit only one form to this object.";
+ goto OUTPUT;
+ }
+
+ my ($c, $o, $k, $e) = @{ $forms->[0] };
+ if ($e) {
+ $status = "409 Syntax Error";
+ $output = form_compose([ ["# Syntax error.", $o, $k, $e] ]);
+ goto OUTPUT;
+ }
+ }
+}
+
+# Make sure we have something to do.
+unless (@objects) {
+ $status = "400 Bad Request";
+ $output = "No objects specified.";
+ goto OUTPUT;
+}
+
+# Parse and validate any field specifications.
+my (%fields, @fields);
+if ($fields) {
+ unless ($fields =~ /^(?:$field,)*$field$/) {
+ $status = "400 Bad Request";
+ $output = "Invalid field specification: $fields";
+ goto OUTPUT;
+ }
+ @fields = map lc, split /,/, $fields;
+ @fields{@fields} = ();
+ unless (exists $fields{id}) {
+ unshift @fields, "id";
+ $fields{id} = ();
+ }
+}
+
+my (@comments, @output);
+
+foreach $object (@objects) {
+ my ($handler, $type, $id, $attr, $args);
+ my ($c, $o, $k, $e) = ("", ["id"], {id => $object}, 0);
+
+ my $i = 0;
+ if ($object =~ m{^($name)/(\d+|$label)(?:/($name)(?:/(.*))?)?$}o ||
+ $object =~ m{^($name)/(new)$}o)
+ {
+ ($type, $id, $attr, $args) = ($1, $2, ($3 || 'default'), $4);
+ $handler = "Forms/$type/$attr";
+
+ unless ($m->comp_exists($handler)) {
+ $args = "$attr/$args";
+ $handler = "Forms/$type/default";
+
+ unless ($m->comp_exists($handler)) {
+ $i = 2;
+ $c = "# Unknown object type: $type";
+ }
+ }
+ elsif ($id ne 'new' && $id !~ /^\d+$/) {
+ my $ns = "Forms/$type/ns";
+
+ # Can we resolve named objects?
+ unless ($m->comp_exists($ns)) {
+ $i = 3;
+ $c = "# Objects of type $type must be specified by numeric id.";
+ }
+ else {
+ my ($n, $s) = $m->comp("Forms/$type/ns", id => $id);
+ if ($n <= 0) { $i = 4; $c = "# $s"; }
+ else { $i = 0; $id = $n; }
+ }
+ }
+ else {
+ $i = 0;
+ }
+ }
+ else {
+ $i = 1;
+ $c = "# Invalid object specification: '$object'";
+ }
+
+ if ($i != 0) {
+ if ($content) {
+ (undef, $o, $k, $e) = @{ shift @$forms };
+ }
+ push @output, [ $c, $o, $k ];
+ next;
+ }
+
+ unless ($content) {
+ my $d = $m->comp($handler, id => $id, args => $args, format => $format, fields => \%fields);
+ my ($c, $o, $k, $e) = @$d;
+
+ if (!$e && @$o && keys %fields) {
+ my %lk = map { lc $_ => $_ } keys %$k;
+ @$o = map { $lk{$_} } @fields;
+ foreach my $key (keys %$k) {
+ delete $k->{$key} unless exists $fields{lc $key};
+ }
+ }
+ push(@output, [ $c, $o, $k ]) if ($c || @$o || keys %$k);
+ }
+ else {
+ my ($c, $o, $k, $e) = @{ shift @$forms };
+ my $d = $m->comp($handler, id => $id, args => $args, format => $format,
+ changes => $k);
+ ($c, $o, $k, $e) = @$d;
+
+ # We won't pass $e through to compose, trusting instead that the
+ # handler added suitable comments for the user.
+ if ($e) {
+ $status = "409 Syntax Error" if @$o;
+ push @output, [ $c, $o, $k ];
+ }
+ else {
+ push @comments, $c;
+ }
+ }
+}
+
+unshift(@output, [ join "\n", @comments ]) if @comments;
+$output = form_compose(\@output);
+
+OUTPUT:
+</%INIT>
+RT/<% $RT::VERSION %> <% $status %>
+
+<% $output |n %>
diff --git a/rt/html/REST/1.0/logout b/rt/html/REST/1.0/logout
new file mode 100644
index 0000000..b64938b
--- /dev/null
+++ b/rt/html/REST/1.0/logout
@@ -0,0 +1,27 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# This work is made available to you under the terms of Version 2 of
+%# the GNU General Public License. A copy of that license should have
+%# been provided with this software, but in any event can be snarfed
+%# from www.gnu.org.
+%#
+%# This work is distributed in the hope that it will be useful, but
+%# WITHOUT ANY WARRANTY; without even the implied warranty of
+%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+%# General Public License for more details.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<%PERL>
+tied(%session)->delete if (defined %session);
+</%PERL>
+RT/<% $RT::VERSION %> 200 Ok
diff --git a/rt/html/REST/1.0/search/dhandler b/rt/html/REST/1.0/search/dhandler
new file mode 100644
index 0000000..90b4653
--- /dev/null
+++ b/rt/html/REST/1.0/search/dhandler
@@ -0,0 +1,32 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# This work is made available to you under the terms of Version 2 of
+%# the GNU General Public License. A copy of that license should have
+%# been provided with this software, but in any event can be snarfed
+%# from www.gnu.org.
+%#
+%# This work is distributed in the hope that it will be useful, but
+%# WITHOUT ANY WARRANTY; without even the implied warranty of
+%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+%# General Public License for more details.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+%# REST/1.0/search/dhandler
+%#
+<%INIT>
+my $status = "500 Server Error";
+my $output = "Unsupported object type.";
+</%INIT>
+RT/<% $RT::VERSION %> <% $status %>
+
+<% $output |n %>
diff --git a/rt/html/REST/1.0/search/ticket b/rt/html/REST/1.0/search/ticket
new file mode 100644
index 0000000..2443529
--- /dev/null
+++ b/rt/html/REST/1.0/search/ticket
@@ -0,0 +1,119 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# This work is made available to you under the terms of Version 2 of
+%# the GNU General Public License. A copy of that license should have
+%# been provided with this software, but in any event can be snarfed
+%# from www.gnu.org.
+%#
+%# This work is distributed in the hope that it will be useful, but
+%# WITHOUT ANY WARRANTY; without even the implied warranty of
+%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+%# General Public License for more details.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+%# REST/1.0/search/ticket
+%#
+<%ARGS>
+$query
+$format => undef
+$orderby => undef
+$fields => undef
+</%ARGS>
+<%INIT>
+my $output = "";
+my $status = "200 Ok";
+my $tickets = new RT::Tickets $session{CurrentUser};
+
+# Parse and validate any field specifications.
+my $field = '[a-zA-Z][a-zA-Z0-9_-]*';
+my (%fields, @fields);
+if ($fields) {
+ $format = "l";
+ unless ($fields =~ /^(?:$field,)*$field$/) {
+ $status = "400 Bad Request";
+ $output = "Invalid field specification: $fields";
+ goto OUTPUT;
+ }
+ @fields = map lc, split /,/, $fields;
+ @fields{@fields} = ();
+ unless (exists $fields{id}) {
+ unshift @fields, "id";
+ $fields{id} = ();
+ }
+}
+
+$format ||= "s";
+if ($format !~ /^[isl]$/) {
+ $status = "400 Bad request";
+ $output = "Unknown listing format: $format. (Use i, s, or l.)\n";
+ goto OUTPUT;
+}
+
+my ($n, $s);
+eval {
+ ($n, $s) = $tickets->FromSQL($query);
+};
+my $sortstring = "";
+if ($orderby) {
+ $sortstring = 'FIELD => ';
+ my $order = substr($orderby, 0, 1);
+ if ($order eq '+' || $order eq '-') {
+ $sortstring .= 'substr($orderby, 1)';
+ if ($order eq '+') {
+ $sortstring .= ", ORDER => 'ASC'";
+ } elsif ($order eq '-') {
+ $sortstring .= ", ORDER => 'DESC'";
+ }
+ } else {
+ $sortstring .= '$orderby';
+ }
+ my $foo = 'FIELD => ';
+ $foo .= '$orderby';
+ $tickets->OrderBy(eval $sortstring);
+}
+if ($@ || $n == 0) {
+ $s ||= $@;
+ $status = "400 Bad request";
+ $output = "Invalid query: '$s'.\n";
+ goto OUTPUT;
+}
+
+$n = 0;
+my @output;
+while (my $ticket = $tickets->Next) {
+ $n++;
+
+ if ($format eq "i") {
+ $output .= "ticket/" . $ticket->Id . "\n";
+ }
+ elsif ($format eq "s") {
+ $output .= $ticket->Id . ": ". $ticket->Subject . "\n";
+ }
+ else {
+ my $id = $ticket->Id;
+ my $d = $m->comp("$RT::WebPath/REST/1.0/Forms/ticket/default", id => $id, format => $format, fields => \%fields);
+ my ($c, $o, $k, $e) = @$d;
+ push @output, [ $c, $o, $k ];
+ }
+}
+if ($n == 0 && $format ne "i") {
+ $output = "No matching results.\n";
+}
+
+$output = form_compose(\@output) if @output;
+
+OUTPUT:
+</%INIT>
+RT/<% $RT::VERSION %> <% $status %>
+
+<% $output |n %>
diff --git a/rt/html/REST/1.0/ticket/comment b/rt/html/REST/1.0/ticket/comment
new file mode 100644
index 0000000..9d1b062
--- /dev/null
+++ b/rt/html/REST/1.0/ticket/comment
@@ -0,0 +1,149 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# This work is made available to you under the terms of Version 2 of
+%# the GNU General Public License. A copy of that license should have
+%# been provided with this software, but in any event can be snarfed
+%# from www.gnu.org.
+%#
+%# This work is distributed in the hope that it will be useful, but
+%# WITHOUT ANY WARRANTY; without even the implied warranty of
+%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+%# General Public License for more details.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+%# REST/1.0/ticket/comment
+%#
+<%ARGS>
+$content
+</%ARGS>
+<%INIT>
+use MIME::Entity;
+use LWP::MediaTypes;
+use RT::Interface::REST;
+use File::Temp qw(tempfile);
+
+my $ticket = new RT::Ticket $session{CurrentUser};
+my $object = $r->path_info;
+my $status = "200 Ok";
+my $output;
+my $action;
+
+# http://.../REST/1.0/ticket/comment/1
+my ($c, $o, $k, $e) = @{ form_parse($content)->[0] };
+if ($e || !$o) {
+ if (!$o) {
+ $output = "Empty form submitted.\n";
+ }
+ else {
+ $c = "# Syntax error.";
+ $output = form_compose([[$c, $o, $k, $e]]);
+ }
+ $status = "400 Bad Request";
+ goto OUTPUT;
+}
+
+$object =~ s#^/##;
+$object ||= $k->{Ticket};
+unless ($object =~ /^\d+/) {
+ $output = "Invalid ticket id: `$object'.\n";
+ $status = "400 Bad Request";
+ goto OUTPUT;
+}
+if ($k->{Ticket} && $object ne $k->{Ticket}) {
+ $output = "The submitted form and URL specify different tickets.\n";
+ $status = "400 Bad Request";
+ goto OUTPUT;
+}
+
+($action = $k->{Action}) =~ s/^(.)(.*)$/\U$1\L$2\E/;
+unless ($action =~ /^(?:Comment|Correspond)$/) {
+ $output = "Invalid action: `$action'.\n";
+ $status = "400 Bad Request";
+ goto OUTPUT;
+}
+
+my $text = $k->{Text};
+my @atts = @{ vsplit($k->{Attachment}) };
+
+if (!$k->{Text} && @atts == 0) {
+ $status = "400 Bad Request";
+ $output = "Empty comment with no attachments submitted.\n";
+ goto OUTPUT;
+}
+
+my $cgi = $m->cgi_object;
+my $ent = MIME::Entity->build(Type => "multipart/mixed");
+$ent->attach(Data => $k->{Text}) if $k->{Text};
+
+my $i = 1;
+foreach my $att (@atts) {
+ local $/=undef;
+ my $file = $att;
+ $file =~ s#^.*[\\/]##;
+
+ my $fh = $cgi->upload("attachment_$i");
+ if ($fh) {
+ my $buf;
+ my ($w, $tmp) = tempfile();
+ my $info = $cgi->uploadInfo();
+
+ while (sysread($fh, $buf, 8192)) {
+ syswrite($w, $buf);
+ }
+
+ $ent->attach(
+ Path => $tmp,
+ Type => $info->{'Content-Type'} || guess_media_type($tmp),
+ Filename => $file,
+ Disposition => "attachment"
+ );
+ }
+ else {
+ $status = "400 Bad Request";
+ $output = "No attachment for $att.\n";
+ goto OUTPUT;
+ }
+
+ $i++;
+}
+
+$ticket->Load($object);
+unless ($ticket->Id) {
+ $output = "Couldn't load ticket id: `$object'.\n";
+ $status = "404 Ticket not found";
+ goto OUTPUT;
+}
+unless ($ticket->CurrentUserHasRight('ModifyTicket') ||
+ ($action eq "Comment" &&
+ $ticket->CurrentUserHasRight("CommentOnTicket")) ||
+ ($action eq "Correspond" &&
+ $ticket->CurrentUserHasRight("ReplyToTicket")))
+{
+ $output = "You are not allowed to $action on ticket $object.\n";
+ $status = "403 Permission denied";
+ goto OUTPUT;
+}
+
+my $cc = join ", ", @{ vsplit($k->{Cc}) };
+my $bcc = join ", ", @{ vsplit($k->{Bcc}) };
+my ($n, $s) = $ticket->$action(MIMEObj => $ent,
+ CcMessageTo => $cc,
+ BccMessageTo => $bcc,
+ TimeTaken => $k->{TimeWorked} || 0);
+$output = $s;
+
+OUTPUT:
+</%INIT>
+RT/<% $RT::VERSION %> <% $status %>
+
+<% $output |n %>
diff --git a/rt/html/REST/1.0/ticket/link b/rt/html/REST/1.0/ticket/link
new file mode 100644
index 0000000..5747625
--- /dev/null
+++ b/rt/html/REST/1.0/ticket/link
@@ -0,0 +1,96 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# This work is made available to you under the terms of Version 2 of
+%# the GNU General Public License. A copy of that license should have
+%# been provided with this software, but in any event can be snarfed
+%# from www.gnu.org.
+%#
+%# This work is distributed in the hope that it will be useful, but
+%# WITHOUT ANY WARRANTY; without even the implied warranty of
+%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+%# General Public License for more details.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+%# REST/1.0/ticket/link
+%#
+<%ARGS>
+$id => undef
+$del => 0
+$rel
+$to
+</%ARGS>
+<%INIT>
+use RT::Interface::REST;
+
+my $output;
+my $status = "200 Ok";
+my $ticket = new RT::Ticket $session{CurrentUser};
+my $object = $r->path_info;
+
+my @fields = qw(DependsOn DependedOnBy RefersTo ReferredToBy HasMember MemberOf);
+my %fields = map { lc $_ => $_ } @fields;
+my %lfields = (
+ HasMember => { Type => 'MemberOf', Mode => 'Base' },
+ ReferredToBy => { Type => 'RefersTo', Mode => 'Base' },
+ DependedOnBy => { Type => 'DependsOn', Mode => 'Base' },
+ MemberOf => { Type => 'MemberOf', Mode => 'Target' },
+ RefersTo => { Type => 'RefersTo', Mode => 'Target' },
+ DependsOn => { Type => 'DependsOn', Mode => 'Target' },
+);
+
+# http://.../REST/1.0/ticket/link/1
+
+$object =~ s#^/##;
+if ($id && $object && $id != $object) {
+ $output = "Different ids in URL (`$object') and submitted form (`$id').\n";
+ $status = "400 Bad Request";
+ goto OUTPUT;
+}
+$id ||= $object;
+unless ($id =~ /^\d+$/ && $to =~ /^\d+$/) {
+ my $bad = ($id !~ /^\d+$/) ? $id : $to;
+ $output = $r->path_info. "\n";
+ $output .= "Invalid ticket id: '$bad'.\n";
+ $status = "400 Bad Request";
+ goto OUTPUT;
+}
+unless (exists $fields{lc $rel}) {
+ $output = "Invalid relationship: '$rel'.\n";
+ $status = "400 Bad Request";
+ goto OUTPUT;
+}
+$rel = $fields{lc $rel};
+
+$ticket->Load($id);
+unless ($ticket->Id) {
+ $output = "Couldn't load ticket id: '$id'.\n";
+ $status = "404 Ticket not found";
+ goto OUTPUT;
+}
+
+my $type = $lfields{$rel}->{Type};
+my $mode = $lfields{$rel}->{Mode};
+
+my $n = 1;
+my $op = $del ? "DeleteLink" : "AddLink";
+
+($n, $output) = $ticket->$op(Type => $type, $mode => $to);
+if ($n == 0) {
+ $status = "500 Error";
+}
+
+OUTPUT:
+</%INIT>
+RT/<% $RT::VERSION %> <% $status %>
+
+<% $output |n %>
diff --git a/rt/html/REST/1.0/ticket/merge b/rt/html/REST/1.0/ticket/merge
new file mode 100644
index 0000000..9cd2a7c
--- /dev/null
+++ b/rt/html/REST/1.0/ticket/merge
@@ -0,0 +1,78 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# This work is made available to you under the terms of Version 2 of
+%# the GNU General Public License. A copy of that license should have
+%# been provided with this software, but in any event can be snarfed
+%# from www.gnu.org.
+%#
+%# This work is distributed in the hope that it will be useful, but
+%# WITHOUT ANY WARRANTY; without even the implied warranty of
+%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+%# General Public License for more details.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+%# REST/1.0/ticket/merge
+%#
+<%ARGS>
+$id => undef
+$into
+</%ARGS>
+<%INIT>
+use RT::Interface::REST;
+
+my $output;
+my $status = "200 Ok";
+my $ticket = new RT::Ticket $session{CurrentUser};
+my $object = $r->path_info;
+
+# http://.../REST/1.0/ticket/merge/1
+
+$object =~ s#^/##;
+if ($id && $object && $id != $object) {
+ $output = "Different ids in URL (`$object') and submitted form (`$id').\n";
+ $status = "400 Bad Request";
+ goto OUTPUT;
+}
+$id ||= $object;
+unless ($id =~ /^\d+$/ && $into =~ /^\d+$/) {
+ my $bad = ($id !~ /^\d+$/) ? $id : $into;
+ $output = $r->path_info. "\n";
+ $output .= "Invalid ticket id: `$bad'.\n";
+ $status = "400 Bad Request";
+ goto OUTPUT;
+}
+
+$ticket->Load($id);
+unless ($ticket->Id) {
+ $output = "Couldn't load ticket id: `$id'.\n";
+ $status = "404 Ticket not found";
+ goto OUTPUT;
+}
+unless ($ticket->CurrentUserHasRight('ModifyTicket')) {
+ $output = "You are not allowed to modify ticket $id.\n";
+ $status = "403 Permission denied";
+ goto OUTPUT;
+}
+
+my ($n, $s) = $ticket->MergeInto($into);
+
+if ($n == 0) {
+ $status = "500 Error";
+}
+$output = $s;
+
+OUTPUT:
+</%INIT>
+RT/<% $RT::VERSION %> <% $status %>
+
+<% $output |n %>