]>
Commit | Line | Data |
---|---|---|
84fb5b46 MKG |
1 | %# BEGIN BPS TAGGED BLOCK {{{ |
2 | %# | |
3 | %# COPYRIGHT: | |
4 | %# | |
320f0092 | 5 | %# This software is Copyright (c) 1996-2014 Best Practical Solutions, LLC |
84fb5b46 MKG |
6 | %# <sales@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., 51 Franklin Street, Fifth Floor, Boston, MA | |
26 | %# 02110-1301 or visit their web page on the internet at | |
27 | %# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html. | |
28 | %# | |
29 | %# | |
30 | %# CONTRIBUTION SUBMISSION POLICY: | |
31 | %# | |
32 | %# (The following paragraph is not intended to limit the rights granted | |
33 | %# to you to modify and distribute this software under the terms of | |
34 | %# the GNU General Public License and is only of importance to you if | |
35 | %# you choose to contribute your changes and enhancements to the | |
36 | %# community by submitting them to Best Practical Solutions, LLC.) | |
37 | %# | |
38 | %# By intentionally submitting any modifications, corrections or | |
39 | %# derivatives to this work, or any other work intended for use with | |
40 | %# Request Tracker, to Best Practical Solutions, LLC, you confirm that | |
41 | %# you are the copyright holder for those contributions and you grant | |
42 | %# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable, | |
43 | %# royalty-free, perpetual, license to use, copy, create derivative | |
44 | %# works based on those contributions, and sublicense and distribute | |
45 | %# those contributions and any derivatives thereof. | |
46 | %# | |
47 | %# END BPS TAGGED BLOCK }}} | |
48 | %# REST/1.0/Forms/ticket/default | |
49 | %# | |
50 | <%ARGS> | |
51 | $id | |
52 | $changes => {} | |
53 | $fields => undef | |
54 | $args => undef | |
55 | </%ARGS> | |
56 | <%INIT> | |
57 | use MIME::Entity; | |
58 | use RT::Interface::REST; | |
59 | ||
60 | my $cf_spec = RT::Interface::REST->custom_field_spec(1); | |
61 | ||
62 | my @comments; | |
63 | my ($c, $o, $k, $e) = ("", [], {}, 0); | |
64 | my %data = %$changes; | |
65 | my $ticket = RT::Ticket->new($session{CurrentUser}); | |
66 | my @dates = qw(Created Starts Started Due Resolved Told LastUpdated); | |
67 | my @people = qw(Requestors Cc AdminCc); | |
68 | my @create = qw(Queue Requestor Subject Cc AdminCc Owner Status Priority | |
69 | InitialPriority FinalPriority TimeEstimated TimeWorked | |
01e3b242 | 70 | TimeLeft Starts Started Due Resolved Content-Type); |
84fb5b46 MKG |
71 | my @simple = qw(Subject Status Priority Disabled TimeEstimated TimeWorked |
72 | TimeLeft InitialPriority FinalPriority); | |
73 | my %dates = map {lc $_ => $_} @dates; | |
74 | my %people = map {lc $_ => $_} @people; | |
75 | my %create = map {lc $_ => $_} @create; | |
76 | my %simple = map {lc $_ => $_} @simple; | |
77 | ||
78 | # Are we dealing with an existing ticket? | |
79 | if ($id ne 'new') { | |
80 | $ticket->Load($id); | |
81 | if (!$ticket->Id) { | |
82 | return [ "# Ticket $id does not exist.", [], {}, 1 ]; | |
83 | } | |
84 | elsif ( %data ) { | |
01e3b242 | 85 | if ( $data{status} && lc $data{status} eq 'deleted' && ! grep { $_ ne 'id' && $_ ne 'status' } keys %data ) { |
84fb5b46 MKG |
86 | if ( !$ticket->CurrentUserHasRight('DeleteTicket') ) { |
87 | return [ "# You are not allowed to delete ticket $id.", [], {}, 1 ]; | |
88 | } | |
89 | } | |
90 | elsif ( !$ticket->CurrentUserHasRight('ModifyTicket') ) { | |
91 | return [ "# You are not allowed to modify ticket $id.", [], {}, 1 ]; | |
92 | } | |
93 | } | |
94 | elsif (!$ticket->CurrentUserHasRight('ShowTicket')) { | |
95 | return [ "# You are not allowed to display ticket $id.", [], {}, 1 ]; | |
96 | } | |
97 | } | |
98 | else { | |
99 | if (!keys(%data)) { | |
100 | # GET ticket/new: Return a suitable default form. | |
101 | # We get defaults from queue/1 (XXX: What if it isn't there?). | |
102 | my $due = RT::Date->new($session{CurrentUser}); | |
103 | my $queue = RT::Queue->new($session{CurrentUser}); | |
104 | my $starts = RT::Date->new($session{CurrentUser}); | |
105 | $queue->Load(1); | |
106 | $due->SetToNow; | |
107 | $due->AddDays($queue->DefaultDueIn) if $queue->DefaultDueIn; | |
108 | $starts->SetToNow; | |
109 | ||
110 | return [ | |
111 | "# Required: id, Queue", | |
112 | [ qw(id Queue Requestor Subject Cc AdminCc Owner Status Priority | |
01e3b242 | 113 | InitialPriority FinalPriority TimeEstimated Starts Due Attachment Text) ], |
84fb5b46 MKG |
114 | { |
115 | id => "ticket/new", | |
116 | Queue => $queue->Name, | |
117 | Requestor => $session{CurrentUser}->Name, | |
118 | Subject => "", | |
119 | Cc => [], | |
120 | AdminCc => [], | |
121 | Owner => "", | |
122 | Status => "new", | |
123 | Priority => $queue->InitialPriority, | |
124 | InitialPriority => $queue->InitialPriority, | |
125 | FinalPriority => $queue->FinalPriority, | |
126 | TimeEstimated => 0, | |
127 | Starts => $starts->ISO, | |
128 | Due => $due->ISO, | |
01e3b242 | 129 | Attachment => '', |
84fb5b46 MKG |
130 | Text => "", |
131 | }, | |
132 | 0 | |
133 | ]; | |
134 | } | |
135 | else { | |
136 | # We'll create a new ticket, and fall through to set fields that | |
137 | # can't be set in the call to Create(). | |
01e3b242 | 138 | my (%v, $text, @atts); |
84fb5b46 MKG |
139 | |
140 | foreach my $k (keys %data) { | |
141 | # flexibly parse any dates | |
142 | if ($dates{lc $k}) { | |
143 | my $time = RT::Date->new($session{CurrentUser}); | |
144 | $time->Set(Format => 'unknown', Value => $data{$k}); | |
145 | $data{$k} = $time->ISO; | |
146 | } | |
147 | ||
148 | if (exists $create{lc $k}) { | |
149 | $v{$create{lc $k}} = delete $data{$k}; | |
150 | } | |
151 | # Set custom field | |
152 | elsif ($k =~ /^$cf_spec/) { | |
b5747ff2 MKG |
153 | my $key = $1 || $2; |
154 | ||
155 | my $cf = RT::CustomField->new( $session{CurrentUser} ); | |
156 | $cf->LoadByName( Name => $key, Queue => $data{Queue} || $v{Queue} ); | |
157 | unless ( $cf->id ) { | |
158 | $cf->LoadByName( Name => $key, Queue => 0 ); | |
159 | } | |
160 | ||
161 | if (not $cf->id) { | |
162 | push @comments, "# Invalid custom field name ($key)"; | |
84fb5b46 MKG |
163 | delete $data{$k}; |
164 | next; | |
165 | } | |
166 | $v{"CustomField-".$cf->Id()} = delete $data{$k}; | |
167 | } | |
168 | elsif (lc $k eq 'text') { | |
169 | $text = delete $data{$k}; | |
170 | } | |
01e3b242 MKG |
171 | elsif (lc $k eq 'attachment') { |
172 | push @atts, @{ vsplit(delete $data{$k}) }; | |
173 | } | |
403d7b0b | 174 | elsif ( $k !~ /^(?:id|requestors)$/i ) { |
dab09ea8 MKG |
175 | $e = 1; |
176 | push @$o, $k; | |
177 | push(@comments, "# $k: Unknown field"); | |
178 | } | |
179 | } | |
180 | ||
181 | if ( $e ) { | |
182 | unshift @comments, "# Could not create ticket."; | |
183 | $k = \%data; | |
184 | goto DONE; | |
84fb5b46 MKG |
185 | } |
186 | ||
187 | # people fields allow multiple values | |
188 | $v{$_} = vsplit($v{$_}) foreach ( grep $create{lc $_}, @people ); | |
189 | ||
01e3b242 | 190 | if ($text || @atts) { |
84fb5b46 MKG |
191 | $v{MIMEObj} = |
192 | MIME::Entity->build( | |
01e3b242 | 193 | Type => "multipart/mixed", |
84fb5b46 MKG |
194 | From => $session{CurrentUser}->EmailAddress, |
195 | Subject => $v{Subject}, | |
403d7b0b | 196 | 'X-RT-Interface' => 'REST', |
84fb5b46 | 197 | ); |
01e3b242 MKG |
198 | $v{MIMEObj}->attach( |
199 | Data => $text, | |
200 | 'Content-Type' => $v{'Content-Type'} || 'text/plain', | |
201 | ) if $text; | |
202 | my ($status, $msg) = process_attachments($v{'MIMEObj'}, @atts); | |
203 | unless ($status) { | |
204 | push(@comments, "# $msg"); | |
205 | goto DONE; | |
206 | } | |
207 | $v{MIMEObj}->make_singlepart; | |
84fb5b46 MKG |
208 | } |
209 | ||
210 | my($tid,$trid,$terr) = $ticket->Create(%v); | |
211 | unless ($tid) { | |
212 | push(@comments, "# Could not create ticket."); | |
213 | push(@comments, "# " . $terr); | |
214 | goto DONE; | |
215 | } | |
216 | ||
217 | delete $data{id}; | |
218 | $id = $ticket->Id; | |
219 | push(@comments, "# Ticket $id created."); | |
220 | # see if the hash is empty | |
221 | goto DONE if ! keys(%data); | |
222 | } | |
223 | } | |
224 | ||
225 | # Now we know we're dealing with an existing ticket. | |
226 | if (!keys(%data)) { | |
227 | my ($time, $key, $val, @data); | |
228 | ||
229 | push @data, [ id => "ticket/".$ticket->Id ]; | |
af59614d MKG |
230 | push @data, [ Queue => $ticket->QueueObj->Name ] |
231 | if (!%$fields || exists $fields->{lc 'Queue'}); | |
84fb5b46 | 232 | push @data, [ Owner => $ticket->OwnerObj->Name ] |
af59614d | 233 | if (!%$fields || exists $fields->{lc 'Owner'}); |
84fb5b46 | 234 | push @data, [ Creator => $ticket->CreatorObj->Name ] |
af59614d | 235 | if (!%$fields || exists $fields->{lc 'Creator'}); |
84fb5b46 MKG |
236 | |
237 | foreach (qw(Subject Status Priority InitialPriority FinalPriority)) { | |
af59614d | 238 | next unless (!%$fields || (exists $fields->{lc $_})); |
84fb5b46 MKG |
239 | push @data, [$_ => $ticket->$_ ]; |
240 | } | |
241 | ||
242 | foreach $key (@people) { | |
243 | next unless (!%$fields || (exists $fields->{lc $key})); | |
244 | push @data, [ $key => [ $ticket->$key->MemberEmailAddresses ] ]; | |
245 | } | |
246 | ||
247 | $time = RT::Date->new ($session{CurrentUser}); | |
248 | foreach $key (@dates) { | |
af59614d | 249 | next unless (!%$fields || (exists $fields->{lc $key})); |
84fb5b46 MKG |
250 | $time->Set(Format => 'sql', Value => $ticket->$key); |
251 | push @data, [ $key => $time->AsString ]; | |
252 | } | |
253 | ||
254 | $time = RT::Date->new ($session{CurrentUser}); | |
255 | foreach $key (qw(TimeEstimated TimeWorked TimeLeft)) { | |
af59614d | 256 | next unless (!%$fields || (exists $fields->{lc $key})); |
84fb5b46 MKG |
257 | $val = $ticket->$key || 0; |
258 | $val = "$val minutes" if $val; | |
259 | push @data, [ $key => $val ]; | |
260 | } | |
261 | ||
262 | # Display custom fields | |
263 | my $CustomFields = $ticket->CustomFields; | |
264 | while (my $cf = $CustomFields->Next()) { | |
265 | next unless !%$fields | |
266 | || exists $fields->{"cf.{".lc($cf->Name)."}"} | |
267 | || exists $fields->{"cf-".lc $cf->Name}; | |
268 | ||
269 | my $vals = $ticket->CustomFieldValues($cf->Id()); | |
270 | my @out = (); | |
271 | if ( $cf->SingleValue ) { | |
272 | my $v = $vals->Next; | |
273 | push @out, $v->Content if $v; | |
274 | } | |
275 | else { | |
276 | while (my $v = $vals->Next()) { | |
277 | my $content = $v->Content; | |
278 | $content =~ s/'/\\'/g; | |
279 | if ( $v->Content =~ /,/ ) { | |
280 | push @out, q{'} . $content . q{'}; | |
281 | } | |
282 | else { | |
283 | push @out, $content; | |
284 | } | |
285 | } | |
286 | } | |
287 | push @data, [ ('CF.{' . $cf->Name . '}') => join ',', @out ]; | |
288 | } | |
289 | ||
290 | my %k = map {@$_} @data; | |
291 | $o = [ map {$_->[0]} @data ]; | |
292 | $k = \%k; | |
293 | } | |
294 | else { | |
295 | my ($get, $set, $key, $val, $n, $s); | |
01e3b242 | 296 | my $updated; |
84fb5b46 MKG |
297 | |
298 | foreach $key (keys %data) { | |
299 | $val = $data{$key}; | |
300 | $key = lc $key; | |
301 | $n = 1; | |
302 | ||
303 | if (ref $val eq 'ARRAY') { | |
304 | unless ($key =~ /^(?:Requestors|Cc|AdminCc)$/i) { | |
305 | $n = 0; | |
306 | $s = "$key may have only one value."; | |
307 | goto SET; | |
308 | } | |
309 | } | |
310 | ||
311 | if ($key =~ /^queue$/i) { | |
312 | next if $val eq $ticket->QueueObj->Name; | |
313 | ($n, $s) = $ticket->SetQueue($val); | |
314 | } | |
315 | elsif ($key =~ /^owner$/i) { | |
316 | next if $val eq $ticket->OwnerObj->Name; | |
317 | ($n, $s) = $ticket->SetOwner($val); | |
318 | } | |
319 | elsif (exists $simple{$key}) { | |
320 | $key = $simple{$key}; | |
321 | $set = "Set$key"; | |
dab09ea8 MKG |
322 | my $current = $ticket->$key; |
323 | $current = '' unless defined $current; | |
84fb5b46 | 324 | |
dab09ea8 | 325 | next if ($val eq $current) or ($current =~ /^\d+$/ && $val =~ /^\d+$/ && $val == $current); |
84fb5b46 MKG |
326 | ($n, $s) = $ticket->$set("$val"); |
327 | } | |
328 | elsif (exists $dates{$key}) { | |
329 | $key = $dates{$key}; | |
330 | ||
331 | # We try to detect whether it should update a field by checking | |
332 | # whether its current value equals the entered value. Since the | |
333 | # LastUpdated field is automatically updated as other columns are | |
334 | # changed, it is not properly skipped. Users cannot update this | |
335 | # field anyway. | |
336 | next if $key eq 'LastUpdated'; | |
337 | ||
338 | $set = "Set$key"; | |
339 | ||
340 | my $time = RT::Date->new($session{CurrentUser}); | |
341 | $time->Set(Format => 'sql', Value => $ticket->$key); | |
342 | next if ($val =~ /^not set$/i || $val eq $time->AsString); | |
343 | ||
344 | $time->Set(Format => 'unknown', Value => $val); | |
345 | ($n, $s) = $ticket->$set($time->ISO); | |
346 | } | |
347 | elsif (exists $people{$key}) { | |
348 | $key = $people{$key}; | |
349 | my ($p, @msgs); | |
350 | ||
351 | my %new = map {$_=>1} @{ vsplit($val) }; | |
352 | my %old = map {$_=>1} $ticket->$key->MemberEmailAddresses; | |
353 | my $type = $key eq 'Requestors' ? 'Requestor' : $key; | |
354 | ||
355 | foreach $p (keys %old) { | |
356 | unless (exists $new{$p}) { | |
357 | ($s, $n) = $ticket->DeleteWatcher(Type => $type, | |
358 | Email => $p); | |
359 | push @msgs, [ $s, $n ]; | |
360 | } | |
361 | } | |
362 | foreach $p (keys %new) { | |
84fb5b46 MKG |
363 | unless ($ticket->IsWatcher(Type => $type, Email => $p)) { |
364 | ($s, $n) = $ticket->AddWatcher(Type => $type, | |
365 | Email => $p); | |
366 | push @msgs, [ $s, $n ]; | |
367 | } | |
368 | } | |
369 | ||
370 | $n = 1; | |
371 | if (@msgs = grep {$_->[0] == 0} @msgs) { | |
372 | $n = 0; | |
373 | $s = join "\n", map {"# ".$_->[1]} @msgs; | |
374 | $s =~ s/^# //; | |
375 | } | |
376 | } | |
377 | # Set custom field | |
378 | elsif ($key =~ /^$cf_spec/) { | |
84fb5b46 | 379 | $key = $1 || $2; |
b5747ff2 MKG |
380 | |
381 | my $cf = RT::CustomField->new( $session{CurrentUser} ); | |
382 | $cf->LoadByName( Name => $key, Queue => $ticket->Queue ); | |
383 | unless ( $cf->id ) { | |
384 | $cf->LoadByName( Name => $key, Queue => 0 ); | |
385 | } | |
386 | ||
387 | if (not $cf->id) { | |
84fb5b46 MKG |
388 | $n = 0; |
389 | $s = "Unknown custom field."; | |
390 | } | |
391 | else { | |
392 | my $vals = $ticket->CustomFieldValues($cf->id); | |
393 | ||
403d7b0b MKG |
394 | if ( !defined $val || !length $val ) { |
395 | while ( my $val = $vals->Next ) { | |
396 | ($n, $s) = $ticket->DeleteCustomFieldValue( | |
397 | Field => $cf, ValueId => $val->id, | |
398 | ); | |
399 | $s =~ s/^# // if defined $s; | |
400 | } | |
401 | } | |
402 | elsif ( $cf->SingleValue ) { | |
af59614d MKG |
403 | ($n, $s) = $ticket->AddCustomFieldValue( |
404 | Field => $cf, Value => $val ); | |
405 | $s =~ s/^# // if defined $s; | |
84fb5b46 MKG |
406 | } |
407 | else { | |
408 | my @new; | |
409 | my ( $a, $b ) = split /\s*,\s*/, $val, 2; | |
410 | while ($a) { | |
411 | no warnings 'uninitialized'; | |
412 | if ( $a =~ /^'/ ) { | |
413 | my $s = $a; | |
414 | while ( $a !~ /'$/ || ( $a !~ /(\\\\)+'$/ | |
415 | && $a =~ /(\\)+'$/ ) ) { | |
416 | ( $a, $b ) = split /\s*,\s*/, $b, 2; | |
417 | $s .= ',' . $a; | |
418 | } | |
419 | $s =~ s/^'//; | |
420 | $s =~ s/'$//; | |
421 | $s =~ s/\\'/'/g; | |
422 | push @new, $s; | |
423 | } | |
01e3b242 | 424 | elsif ( $a =~ /^q\{/ ) { |
84fb5b46 | 425 | my $s = $a; |
01e3b242 | 426 | while ( $a !~ /\}$/ ) { |
84fb5b46 MKG |
427 | ( $a, $b ) = split /\s*,\s*/, $b, 2; |
428 | $s .= ',' . $a; | |
429 | } | |
01e3b242 MKG |
430 | $s =~ s/^q\{//; |
431 | $s =~ s/\}//; | |
84fb5b46 MKG |
432 | push @new, $s; |
433 | } | |
434 | else { | |
435 | push @new, $a; | |
436 | } | |
437 | ( $a, $b ) = split /\s*,\s*/, $b, 2; | |
438 | } | |
439 | ||
440 | my %new; | |
441 | $new{$_}++ for @new; | |
442 | ||
443 | while (my $v = $vals->Next()) { | |
444 | my $c = $v->Content; | |
445 | if ( $new{$c} ) { | |
446 | $new{$c}--; | |
447 | } | |
448 | else { | |
403d7b0b | 449 | $ticket->DeleteCustomFieldValue( Field => $cf, ValueId => $v->id ); |
84fb5b46 MKG |
450 | } |
451 | } | |
452 | for ( @new ) { | |
453 | while ( $new{$_} && $new{$_}-- ) { | |
454 | ($n, $s) = $ticket->AddCustomFieldValue( | |
455 | Field => $cf, Value => $_ ); | |
456 | $s =~ s/^# // if defined $s; | |
457 | } | |
458 | } | |
459 | } | |
460 | } | |
461 | } | |
01e3b242 | 462 | elsif ($key ne 'id' && $key ne 'type' && $key ne 'creator' && $key ne 'content-type' ) { |
84fb5b46 MKG |
463 | $n = 0; |
464 | $s = "Unknown field."; | |
465 | } | |
466 | ||
467 | SET: | |
468 | if ($n == 0) { | |
469 | $e = 1; | |
470 | push @comments, "# $key: $s"; | |
471 | unless (@$o) { | |
472 | # move id forward | |
473 | @$o = ("id", grep { $_ ne 'id' } keys %$changes); | |
474 | $k = $changes; | |
475 | } | |
476 | } | |
01e3b242 MKG |
477 | else { |
478 | $updated ||= 1; | |
479 | } | |
84fb5b46 | 480 | } |
01e3b242 | 481 | push(@comments, "# Ticket ".$ticket->id." updated.") if $updated; |
84fb5b46 MKG |
482 | } |
483 | ||
484 | DONE: | |
485 | $c ||= join("\n", @comments) if @comments; | |
486 | return [$c, $o, $k, $e]; | |
487 | ||
488 | </%INIT> |