This commit was generated by cvs2svn to compensate for changes in r3921,
[freeside.git] / rt / html / REST / 1.0 / dhandler
1 %# {{{ BEGIN BPS TAGGED BLOCK
2 %# 
3 %# COPYRIGHT:
4 %#  
5 %# This software is Copyright (c) 1996-2004 Best Practical Solutions, LLC 
6 %#                                          <jesse@bestpractical.com>
7 %# 
8 %# (Except where explicitly superseded by other copyright notices)
9 %# 
10 %# 
11 %# LICENSE:
12 %# 
13 %# This work is made available to you under the terms of Version 2 of
14 %# the GNU General Public License. A copy of that license should have
15 %# been provided with this software, but in any event can be snarfed
16 %# from www.gnu.org.
17 %# 
18 %# This work is distributed in the hope that it will be useful, but
19 %# WITHOUT ANY WARRANTY; without even the implied warranty of
20 %# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
21 %# General Public License for more details.
22 %# 
23 %# You should have received a copy of the GNU General Public License
24 %# along with this program; if not, write to the Free Software
25 %# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
26 %# 
27 %# 
28 %# CONTRIBUTION SUBMISSION POLICY:
29 %# 
30 %# (The following paragraph is not intended to limit the rights granted
31 %# to you to modify and distribute this software under the terms of
32 %# the GNU General Public License and is only of importance to you if
33 %# you choose to contribute your changes and enhancements to the
34 %# community by submitting them to Best Practical Solutions, LLC.)
35 %# 
36 %# By intentionally submitting any modifications, corrections or
37 %# derivatives to this work, or any other work intended for use with
38 %# Request Tracker, to Best Practical Solutions, LLC, you confirm that
39 %# you are the copyright holder for those contributions and you grant
40 %# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
41 %# royalty-free, perpetual, license to use, copy, create derivative
42 %# works based on those contributions, and sublicense and distribute
43 %# those contributions and any derivatives thereof.
44 %# 
45 %# }}} END BPS TAGGED BLOCK
46 %# REST/1.0/dhandler
47 %#
48 <%ARGS>
49 @id => ()
50 $fields => undef
51 $format => undef
52 $content => undef
53 </%ARGS>
54 <%INIT>
55 use RT::Interface::REST;
56
57 my $output = "";
58 my $status = "200 Ok";
59 my $object = $m->dhandler_arg;
60
61 my $name   = qr{[\w.-]+};
62 my $list   = '(?:(?:\d+-)?\d+,)*(?:\d+-)?\d+';
63 my $label  = '[a-zA-Z0-9@_.+-]+';
64 my $field  = '[a-zA-Z][a-zA-Z0-9_-]*';
65 my $labels = "(?:$label,)*$label";
66
67 # We must handle requests such as the following:
68 #
69 # 1. http://.../REST/1.0/show (with a list of object specifications).
70 # 2. http://.../REST/1.0/edit (with a self-contained list of forms).
71 # 3. http://.../REST/1.0/ticket/show (implicit type specification).
72 #    http://.../REST/1.0/ticket/edit
73 # 4. http://.../REST/1.0/ticket/nn (all possibly with a single form).
74 #    http://.../REST/1.0/ticket/nn/history
75 #    http://.../REST/1.0/ticket/nn/attachment/1
76 #
77 # Objects are specified by their type, and either a unique numeric ID,
78 # or a unique name (e.g. ticket/1, queue/foo). Multiple objects of the
79 # same type may be specified by a comma-separated list of identifiers
80 # (e.g., user/ams,rai or ticket/1-3,5-7).
81 #
82 # Ultimately, we want a list of object specifications to operate upon.
83 # The URLs in (4) provide enough information to identify an object. We
84 # will assemble submitted information into that format in other cases.
85 #
86 my (@objects, $forms);
87 my $utype;
88
89 if ($object eq 'show' ||                                # $REST/show
90     (($utype) = ($object =~ m{^($name)/show$})))        # $REST/ticket/show
91 {
92     # We'll convert type/range specifications ("ticket/1-3,7-9/history")
93     # into a list of singular object specifications ("ticket/1/history").
94     # If the URL specifies a type, we'll accept only that one.
95     foreach my $id (@id) {
96         $id =~ s|^(?:$utype/)?|$utype/| if $utype;
97         if (my ($type, $oids, $extra) =
98             ($id =~ m#^($name)/($list|$labels)(?:(/.*))?$#o))
99         {
100             foreach my $oid (expand_list($oids)) {
101                 if ($extra =~ m{^(?:/($name)(?:/(.*))?)?$}o) {
102                     my ($attr, $args) = ($1, $2);
103                     # expand transaction and attachment range specifications
104                     # (if applicable)
105                     my $tids;
106                     if ($attr eq 'history' && $args =~ m#id/(\d.*)#o) {
107                         $tids = $1;
108                     }
109                     if ($tids) {
110                         push(@objects, "$type/$oid/$attr/id/$_") for expand_list($tids);
111                     } else {
112                         push(@objects, "$type/$oid$extra");
113                     }
114                 }
115             }
116         }
117         else {
118             $status = "400 Bad Request";
119             $output = "Invalid object ID specified: '$id'";
120             goto OUTPUT;
121         }
122     }
123 }
124 elsif ($object eq 'edit' ||                             # $REST/edit
125     (($utype) = ($object =~ m{^($name)/edit$})))        # $REST/ticket/edit
126 {
127     # We'll make sure each of the submitted forms is syntactically valid
128     # and sufficiently identifies an object to operate upon, then add to
129     # the object list as above.
130     my @output;
131
132     $forms = form_parse($content);
133     foreach my $form (@$forms) {
134         my ($c, $o, $k, $e) = @$form;
135
136         if ($e) {
137             push @output, [ "# Syntax error.", $o, $k, $e ];
138         }
139         else {
140             my ($type, $id);
141
142             # Look for matching types in the ID, form, and URL.
143             $type = exists $k->{type} ? $k->{type} : $utype;
144             $type =~ s|^(?:$utype)?|$utype/| if $utype;
145             $type =~ s|/$||;
146
147             if (exists $k->{id}) {
148                 $id = $k->{id};
149                 $id =~ s|^(?:$type/)?|$type/| if $type;
150
151                 if ($id =~ m#^$name/(?:$label|\d+)(?:/.*)?#o) {
152                     push @objects, $id;
153                 }
154                 else {
155                     push @output, [ "# Invalid object ID: '$id'", $o, $k, $e ];
156                 }
157             }
158             else {
159                 push @output, [ "# No object ID specified.", $o, $k, $e ];
160             }
161         }
162     }
163     # If we saw any errors at this stage, we won't process any part of
164     # the submitted data.
165     if (@output) {
166         unshift @output, [ "# Please resubmit with errors corrected." ];
167         $status = "409 Syntax Error";
168         $output = form_compose(\@output);
169         goto OUTPUT;
170     }
171 }
172 else {
173     # We'll assume that this is in the correct format already. Otherwise
174     # it will be caught by the loop below.
175     push @objects, $object;
176
177     if ($content) {
178         $forms = form_parse($content);
179
180         if (@$forms > 1) {
181             $status = "400 Bad Request";
182             $output = "You may submit only one form to this object.";
183             goto OUTPUT;
184         }
185
186         my ($c, $o, $k, $e) = @{ $forms->[0] };
187         if ($e) {
188             $status = "409 Syntax Error";
189             $output = form_compose([ ["# Syntax error.", $o, $k, $e] ]);
190             goto OUTPUT;
191         }
192     }
193 }
194
195 # Make sure we have something to do.
196 unless (@objects) {
197     $status = "400 Bad Request";
198     $output = "No objects specified.";
199     goto OUTPUT;
200 }
201
202 # Parse and validate any field specifications.
203 my (%fields, @fields);
204 if ($fields) {
205     unless ($fields =~ /^(?:$field,)*$field$/) {
206         $status = "400 Bad Request";
207         $output = "Invalid field specification: $fields";
208         goto OUTPUT;
209     }
210     @fields = map lc, split /,/, $fields;
211     @fields{@fields} = ();
212     unless (exists $fields{id}) {
213         unshift @fields, "id";
214         $fields{id} = ();
215     }
216 }
217
218 my (@comments, @output);
219
220 foreach $object (@objects) {
221     my ($handler, $type, $id, $attr, $args);
222     my ($c, $o, $k, $e) = ("", ["id"], {id => $object}, 0);
223
224     my $i = 0;
225     if ($object =~ m{^($name)/(\d+|$label)(?:/($name)(?:/(.*))?)?$}o ||
226         $object =~ m{^($name)/(new)$}o)
227     {
228         ($type, $id, $attr, $args) = ($1, $2, ($3 || 'default'), $4);
229         $handler = "Forms/$type/$attr";
230
231         unless ($m->comp_exists($handler)) {
232             $args = "$attr/$args";
233             $handler = "Forms/$type/default";
234
235             unless ($m->comp_exists($handler)) {
236                 $i = 2;
237                 $c = "# Unknown object type: $type";
238             }
239         }
240         elsif ($id ne 'new' && $id !~ /^\d+$/) {
241             my $ns = "Forms/$type/ns";
242
243             # Can we resolve named objects?
244             unless ($m->comp_exists($ns)) {
245                 $i = 3;
246                 $c = "# Objects of type $type must be specified by numeric id.";
247             }
248             else {
249                 my ($n, $s) = $m->comp("Forms/$type/ns", id => $id);
250                 if ($n <= 0) { $i = 4; $c = "# $s"; }
251                 else         { $i = 0; $id = $n;    }
252             }
253         }
254         else {
255             $i = 0;
256         }
257     }
258     else {
259         $i = 1;
260         $c = "# Invalid object specification: '$object'";
261     }
262
263     if ($i != 0) {
264         if ($content) {
265             (undef, $o, $k, $e) = @{ shift @$forms };
266         }
267         push @output, [ $c, $o, $k ];
268         next;
269     }
270
271     unless ($content) {
272         my $d = $m->comp($handler, id => $id, args => $args, format => $format, fields => \%fields);
273         my ($c, $o, $k, $e) = @$d;
274
275         if (!$e && @$o && keys %fields) {
276             my %lk = map { lc $_ => $_ } keys %$k;
277             @$o = map { $lk{$_} } @fields;
278             foreach my $key (keys %$k) {
279                 delete $k->{$key} unless exists $fields{lc $key};
280             }
281         }
282         push(@output, [ $c, $o, $k ]) if ($c || @$o || keys %$k);
283     }
284     else {
285         my ($c, $o, $k, $e) = @{ shift @$forms };
286         my $d = $m->comp($handler, id => $id, args => $args, format => $format,
287                          changes => $k);
288         ($c, $o, $k, $e) = @$d;
289
290         # We won't pass $e through to compose, trusting instead that the
291         # handler added suitable comments for the user.
292         if ($e) {
293             $status = "409 Syntax Error" if @$o;
294             push @output, [ $c, $o, $k ];
295         }
296         else {
297             push @comments, $c;
298         }
299     }
300 }
301
302 unshift(@output, [ join "\n", @comments ]) if @comments;
303 $output = form_compose(\@output);
304
305 OUTPUT:
306 </%INIT>
307 RT/<% $RT::VERSION %> <% $status %>
308
309 <% $output |n %>