diff --git a/lib/RT/Test.pm b/lib/RT/Test.pm index 003f3bc675d..cb6b597c3bd 100644 --- a/lib/RT/Test.pm +++ b/lib/RT/Test.pm @@ -1000,6 +1000,33 @@ sub load_or_create_custom_field { return $obj; } +=head2 load_or_create_txn_custom_field + +=cut + +sub load_or_create_txn_custom_field { + my $self = shift; + my %args = ( Disabled => 0, @_ ); + my $obj = RT::CustomField->new( RT->SystemUser ); + if ( $args{'Name'} ) { + $obj->LoadByName( + Name => $args{'Name'}, + LookupType => RT::Transaction->CustomFieldLookupType, + ObjectId => $args{'Queue'}->id, + ); + } else { + die "Name is required"; + } + unless ( $obj->id ) { + $args{'LookupType'} = 'RT::Queue-RT::Ticket-RT::Transaction'; + my $queue = delete $args{'Queue'}; + my ($val, $msg) = $obj->Create( %args ); + $obj->AddToObject($queue); + } + + return $obj; +} + sub last_ticket { my $self = shift; my $current = shift; diff --git a/share/html/REST/1.0/Forms/ticket/comment b/share/html/REST/1.0/Forms/ticket/comment index fb5c0d1deda..f95c82ac601 100644 --- a/share/html/REST/1.0/Forms/ticket/comment +++ b/share/html/REST/1.0/Forms/ticket/comment @@ -55,6 +55,8 @@ $id use MIME::Entity; use RT::Interface::REST; +my $cf_spec = RT::Interface::REST->custom_field_spec(1); + $RT::Logger->debug("Got ticket id=$id for comment"); $RT::Logger->debug("Got args @{[keys(%changes)]}."); @@ -78,7 +80,8 @@ unless ($action =~ /^(?:Comment|Correspond)$/) { } my $text = $changes{Text}; -my @atts = @{ vsplit($changes{Attachment}) }; +my @atts = @{ vsplit($changes{Attachment}) } + if defined $changes{Attachment}; if (!$changes{Text} && @atts == 0) { $e = 1; @@ -106,6 +109,20 @@ $ent->attach( } } +{ + my ($status, @msg) = $m->comp( + '/Elements/ValidateCustomFields', + CustomFields => $ticket->TransactionCustomFields, + Object => RT::Transaction->new( $session{'CurrentUser'} ), + ARGSRef => \%ARGS + ); + unless ( $status ) { + $e = 1; + $c = "# " . join("\n# ", @msg); + goto OUTPUT; + } +} + unless ($ticket->CurrentUserHasRight('ModifyTicket') || ($action eq "Comment" && $ticket->CurrentUserHasRight("CommentOnTicket")) || @@ -117,18 +134,39 @@ unless ($ticket->CurrentUserHasRight('ModifyTicket') || goto OUTPUT; } -my $cc = join ", ", @{ vsplit($changes{Cc}) }; -my $bcc = join ", ", @{ vsplit($changes{Bcc}) }; -my ($n, $s) = $ticket->$action(MIMEObj => $ent, - CcMessageTo => $cc, - BccMessageTo => $bcc, - TimeTaken => $changes{TimeWorked} || 0); +my $cc = join ", ", @{ vsplit($changes{Cc} || '') }; +my $bcc = join ", ", @{ vsplit($changes{Bcc} || '') }; +my ($n, $s, $txn) = $ticket->$action(MIMEObj => $ent, + CcMessageTo => $cc, + BccMessageTo => $bcc, + TimeTaken => $changes{TimeWorked} || 0); $c = "# ".$s; if ($changes{Status}) { my ($status_n, $status_s) = $ticket->SetStatus($changes{'Status'} ); $c .= "\n# ".$status_s; } +my %v; +foreach my $k (keys %changes) { + if ($k =~ /^$cf_spec/) { + my $key = $1 || $2; + + my $cf = $txn->LoadCustomFieldByIdentifier($key); + + if (not $cf->id) { + $c .= "\n# Invalid custom field name ($key)"; + delete $changes{$k}; + next; + } + if ($cf->SingleValue) { + $v{"CustomField-".$cf->Id()} = delete $changes{$k}; + } else { + $v{"CustomField-".$cf->Id()} = vsplit(delete $changes{$k}); + } + } +} +$txn->UpdateCustomFields(%v); + OUTPUT: return [ $c, $o, $k, $e ]; diff --git a/share/html/REST/1.0/Forms/ticket/history b/share/html/REST/1.0/Forms/ticket/history index cbb0ec38f24..67942bc1ce9 100644 --- a/share/html/REST/1.0/Forms/ticket/history +++ b/share/html/REST/1.0/Forms/ticket/history @@ -149,6 +149,37 @@ if ($tid) { push @data, [Attachments => $attachlist]; } + # Display custom fields + my $CustomFields = $t->CustomFields; + while (my $cf = $CustomFields->Next()) { + next unless !%$fields + || exists $fields->{"cf.{".lc($cf->Name)."}"} + || exists $fields->{"cf-".lc $cf->Name}; + + my $vals = $t->CustomFieldValues($cf->Id()); + my @out = (); + my $count = $vals->Count; + next unless $count; + + if ( $count == 1) { + my $v = $vals->First; + push @out, $v->Content if $v; + } + else { + while (my $v = $vals->Next()) { + my $content = $v->Content; + if ( $v->Content =~ /,/ ) { + $content =~ s/([\\'])/\\$1/g; + push @out, q{'} . $content . q{'}; + } + else { + push @out, $content; + } + } + } + push @data, [ ('CF.{' . $cf->Name . '}') => join ',', @out ]; + } + my %k = map {@$_} @data; $o = [ map {$_->[0]} @data ]; $k = \%k; diff --git a/t/web/rest-txn-cf.t b/t/web/rest-txn-cf.t new file mode 100644 index 00000000000..85ff4751a5b --- /dev/null +++ b/t/web/rest-txn-cf.t @@ -0,0 +1,196 @@ +use strict; +use warnings; +use RT::Interface::REST; + +use RT::Test tests => 37; +use Test::Warn; + +my ($baseurl, $m) = RT::Test->started_ok; + +my $queue = RT::Test->load_or_create_queue(Name => 'General'); +ok($queue->Id, "loaded the General queue"); + +{ + my $cf = RT::Test->load_or_create_txn_custom_field( + Name => 'txn_cf', + Type => 'FreeformSingle', + Queue => $queue, + ); + ok($cf->Id, "created a CustomField: txn_cf"); +} +{ + my $cf = RT::Test->load_or_create_txn_custom_field( + Name => 'txn_cf_multi', + Type => 'FreeformMultiple', + Queue => $queue, + ); + ok($cf->Id, "created a CustomField: txn_cf"); +} + +my $other_queue = RT::Test->load_or_create_queue(Name => 'Other Queue'); +ok($other_queue->Id, "loaded the Other Queue queue"); + +{ + my $cf = RT::Test->load_or_create_txn_custom_field( + Name => 'txn_other_queue_cf', + Type => 'FreeformSingle', + Queue => $other_queue, + ); + ok($cf->Id, "created a CustomField"); +} + +$m->post("$baseurl/REST/1.0/ticket/new", [ + user => 'root', + pass => 'password', + format => 'l', +]); + +my $text = $m->content; +my @lines = $text =~ m{.*}g; +shift @lines; # header + +ok($text =~ s/Subject:\s*$/Subject: REST interface/m, "successfully replaced subject"); + +$m->post("$baseurl/REST/1.0/ticket/edit", [ + user => 'root', + pass => 'password', + + content => $text, +], Content_Type => 'form-data'); + +my ($id) = $m->content =~ /Ticket (\d+) created/; +ok($id, "got ticket #$id"); + +$text = join("\n", ( "Ticket: $id", "Action: correspond", "Content-Type: text/plain" )); +$m->post( + "$baseurl/REST/1.0/ticket/$id/comment", + [ + user => 'root', + pass => 'password', + content => "$text\nText: Test with no CF", + ], + Content_Type => 'form-data' +); +like($m->content, qr{Correspondence added}, "correspondance added - no CF"); + +my $with_valid_cf = $text . "\nText: Test with valid CF\nCF.{txn_cf}: valid cf"; + +$m->post( + "$baseurl/REST/1.0/ticket/$id/comment", + [ + user => 'root', + pass => 'password', + content => $with_valid_cf, + ], + Content_Type => 'form-data' +); +like($m->content, qr{Correspondence added}, "correspondance added - valid CF"); +unlike($m->content, qr{Invalid custom field name}, "no invalid custom field - valid CF"); + +my $ticket = RT::Ticket->new(RT->SystemUser); +$ticket->Load($id); +is($ticket->Id, $id, "loaded the REST-created ticket"); +is($ticket->Subject, "REST interface", "subject successfully set"); + +my $txn = $ticket->Transactions->Last; +my ($msg, @attachments) = @{$txn->Attachments->ItemsArrayRef}; +like($msg->Content, qr/Test with valid CF/, "Transaction contains expected content - valid CF"); + +is($txn->FirstCustomFieldValue('txn_cf'), "valid cf", "CF successfully set - valid CF"); + +$m->post( + "$baseurl/REST/1.0/ticket/$id/history", + [ + user => 'root', + pass => 'password', + format => 'l', + ], + Content_Type => 'form-data' +); + +like($m->content, qr/CF.\{txn_cf\}: valid cf/, "Ticket history contains expected content - valid CF"); + +my $with_nonexistant_cf = $text . "\nText: Test with invalid CF\nCF.{other_cf}: invalid cf"; + +$m->post( + "$baseurl/REST/1.0/ticket/$id/comment", + [ + user => 'root', + pass => 'password', + content => $with_nonexistant_cf, + ], + Content_Type => 'form-data' +); +like($m->content, qr{Correspondence added}, "correspondance added - nonexistant CF"); +like($m->content, qr{Invalid custom field name}, "invalid custom field - nonexistant CF"); + +$ticket->Load($id); +is($ticket->Id, $id, "loaded the REST-created ticket"); +is($ticket->Subject, "REST interface", "subject successfully set"); + +$txn = $ticket->Transactions->Last; +($msg, @attachments) = @{$txn->Attachments->ItemsArrayRef}; +like($msg->Content, qr/Test with invalid CF/, "Transaction contains expected content - invalid CF"); + +warning_like {$txn->FirstCustomFieldValue('other_cf')} qr"Couldn't load custom field by 'other_cf' identifier", "CF isn't set - invalid CF"; + +my $with_other_queue_cf = $text . "\nText: Test with other queue CF\nCF.{txn_other_queue_cf}: invalid cf"; + +$m->post( + "$baseurl/REST/1.0/ticket/$id/comment", + [ + user => 'root', + pass => 'password', + content => $with_other_queue_cf, + ], + Content_Type => 'form-data' +); +like($m->content, qr{Correspondence added}, "correspondance added - other queue CF"); +like($m->content, qr{Invalid custom field name}, "invalid custom field - other queue CF"); + +$ticket->Load($id); +is($ticket->Id, $id, "loaded the REST-created ticket"); +is($ticket->Subject, "REST interface", "subject successfully set"); + +$txn = $ticket->Transactions->Last; +($msg, @attachments) = @{$txn->Attachments->ItemsArrayRef}; +like($msg->Content, qr/Test with other queue CF/, "Transaction contains expected content - other queue CF"); + +warning_like {$txn->FirstCustomFieldValue('txn_other_queue_cf')} qr"Couldn't load custom field by 'txn_other_queue_cf' identifier", "CF isn't set - other queue CF"; + +my $with_valid_cf_multi = $text . "\nText: Test with multi CF\nCF.{txn_cf_multi}: Value 1, Value 2"; + +$m->post( + "$baseurl/REST/1.0/ticket/$id/comment", + [ + user => 'root', + pass => 'password', + content => $with_valid_cf_multi, + ], + Content_Type => 'form-data' +); +like($m->content, qr{Correspondence added}, "correspondance added - valid CF multi"); +unlike($m->content, qr{Invalid custom field name}, "no invalid custom field - valid CF multi"); + +$ticket = RT::Ticket->new(RT->SystemUser); +$ticket->Load($id); +is($ticket->Id, $id, "loaded the REST-created ticket - valid CF multi"); +is($ticket->Subject, "REST interface", "subject successfully set - valid CF multi"); + +$txn = $ticket->Transactions->Last; +($msg, @attachments) = @{$txn->Attachments->ItemsArrayRef}; +like($msg->Content, qr/Test with multi CF/, "Transaction contains expected content - valid CF multi"); + +is($txn->FirstCustomFieldValue('txn_cf_multi'), "Value 1", "CF successfully set - valid CF multi"); + +$m->post( + "$baseurl/REST/1.0/ticket/$id/history", + [ + user => 'root', + pass => 'password', + format => 'l', + ], + Content_Type => 'form-data' +); + +like($m->content, qr/CF.\{txn_cf_multi\}: Value 1,Value 2/, "Ticket history contains expected content - valid CF multi");