%# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2019 Best Practical Solutions, LLC %# %# %# (Except where explicitly superseded by other copyright notices) %# %# %# LICENSE: %# %# 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. %# %# You should have received a copy of the GNU General Public License %# along with this program; if not, write to the Free Software %# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA %# 02110-1301 or visit their web page on the internet at %# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html. %# %# %# CONTRIBUTION SUBMISSION POLICY: %# %# (The following paragraph is not intended to limit the rights granted %# to you to modify and distribute this software under the terms of %# the GNU General Public License and is only of importance to you if %# you choose to contribute your changes and enhancements to the %# community by submitting them to Best Practical Solutions, LLC.) %# %# By intentionally submitting any modifications, corrections or %# derivatives to this work, or any other work intended for use with %# Request Tracker, to Best Practical Solutions, LLC, you confirm that %# you are the copyright holder for those contributions and you grant %# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable, %# royalty-free, perpetual, license to use, copy, create derivative %# works based on those contributions, and sublicense and distribute %# those contributions and any derivatives thereof. %# %# END BPS TAGGED BLOCK }}} %# REST/1.0/Forms/ticket/default %# <%ARGS> $id $changes => {} $fields => undef $args => undef <%INIT> use MIME::Entity; use RT::Interface::REST; my $cf_spec = RT::Interface::REST->custom_field_spec(1); my @comments; my ($c, $o, $k, $e) = ("", [], {}, 0); my %data = %$changes; my $ticket = RT::Ticket->new($session{CurrentUser}); my @dates = qw(Created Starts Started Due Resolved Told LastUpdated); 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 Content-Type); 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 ( %data ) { if ( $data{status} && lc $data{status} eq 'deleted' && ! grep { $_ ne 'id' && $_ ne 'status' } keys %data ) { if ( !$ticket->CurrentUserHasRight('DeleteTicket') ) { return [ "# You are not allowed to delete ticket $id.", [], {}, 1 ]; } } elsif ( !$ticket->CurrentUserHasRight('ModifyTicket') ) { return [ "# You are not allowed to modify ticket $id.", [], {}, 1 ]; } } elsif (!$ticket->CurrentUserHasRight('ShowTicket')) { return [ "# You are not allowed to display ticket $id.", [], {}, 1 ]; } } else { if (!keys(%data)) { # GET ticket/new: Return a suitable default form. # We get defaults from queue/1 (XXX: What if it isn't there?). my $queue = RT::Queue->new($session{CurrentUser}); $queue->Load(1); my $due; if ($queue->DefaultDueIn) { $due = RT::Date->new($session{CurrentUser}); $due->SetToNow; $due->AddDays($queue->DefaultDueIn); } my $starts = RT::Date->new($session{CurrentUser}); $starts->SetToNow; return [ "# Required: id, Queue", [ qw(id Queue Requestor Subject Cc AdminCc Owner Status Priority InitialPriority FinalPriority TimeEstimated Starts Due Attachment 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(Timezone => 'user'), Due => $due ? $due->ISO(Timezone => 'user') : undef, Attachment => '', 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, @atts); foreach my $k (keys %data) { # flexibly parse any dates if ($dates{lc $k}) { my $time = RT::Date->new($session{CurrentUser}); $time->Set(Format => 'unknown', Value => $data{$k}); $data{$k} = $time->ISO; } if (exists $create{lc $k}) { $v{$create{lc $k}} = delete $data{$k}; } # Set custom field elsif ($k =~ /^$cf_spec/) { my $key = $1 || $2; my $cf = RT::CustomField->new( $session{CurrentUser} ); $cf->LoadByName( Name => $key, LookupType => RT::Ticket->CustomFieldLookupType, ObjectId => $data{Queue} || $v{Queue}, IncludeGlobal => 1, ); if (not $cf->id) { push @comments, "# Invalid custom field name ($key)"; delete $data{$k}; next; } my $val = delete $data{$k}; next unless defined $val && length $val; $v{"CustomField-".$cf->Id()} = $cf->SingleValue ? $val : vsplit($val,1); } elsif (lc $k eq 'text') { $text = delete $data{$k}; } elsif (lc $k eq 'attachment') { push @atts, @{ vsplit(delete $data{$k}) }; } elsif ( $k !~ /^(?:id|requestors)$/i ) { $e = 1; push @$o, $k; push(@comments, "# $k: Unknown field"); } } if ( $e ) { unshift @comments, "# Could not create ticket."; $k = \%data; goto DONE; } # people fields allow multiple values $v{$_} = vsplit($v{$_}) foreach ( grep $create{lc $_}, @people ); if ($text || @atts) { $v{MIMEObj} = MIME::Entity->build( Type => "multipart/mixed", From => Encode::encode( "UTF-8", $session{CurrentUser}->EmailAddress ), Subject => Encode::encode( "UTF-8", $v{Subject}), 'X-RT-Interface' => 'REST', ); $v{MIMEObj}->attach( Type => $v{'Content-Type'} || 'text/plain', Charset => "UTF-8", Data => Encode::encode( "UTF-8", $text ), ) if $text; my ($status, $msg) = process_attachments($v{'MIMEObj'}, @atts); unless ($status) { push(@comments, "# $msg"); goto DONE; } $v{MIMEObj}->make_singlepart; } my($tid,$trid,$terr) = $ticket->Create(%v); unless ($tid) { push(@comments, "# Could not create ticket."); push(@comments, "# " . $terr); goto DONE; } delete $data{id}; $id = $ticket->Id; push(@comments, "# Ticket $id created."); # see if the hash is empty goto DONE if ! keys(%data); } } # Now we know we're dealing with an existing ticket. if (!keys(%data)) { 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 = RT::Date->new ($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 = RT::Date->new ($session{CurrentUser}); foreach $key (qw(TimeEstimated TimeWorked TimeLeft)) { next unless (!%$fields || (exists $fields->{lc $key})); $val = $ticket->$key || 0; $val = "$val minutes" if $val; push @data, [ $key => $val ]; } # Display custom fields my $CustomFields = $ticket->CustomFields; while (my $cf = $CustomFields->Next()) { next unless !%$fields || exists $fields->{"cf.{".lc($cf->Name)."}"} || exists $fields->{"cf-".lc $cf->Name}; my $vals = $ticket->CustomFieldValues($cf->Id()); my @out = (); if ( $cf->SingleValue ) { my $v = $vals->Next; 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; } else { my ($get, $set, $key, $val, $n, $s); my $updated; 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"; my $current = $ticket->$key; $current = '' unless defined $current; next if ($val eq $current) or ($current =~ /^\d+$/ && $val =~ /^\d+$/ && $val == $current); ($n, $s) = $ticket->$set("$val"); } elsif (exists $dates{$key}) { $key = $dates{$key}; # We try to detect whether it should update a field by checking # whether its current value equals the entered value. Since the # LastUpdated field is automatically updated as other columns are # changed, it is not properly skipped. Users cannot update this # field anyway. next if $key eq 'LastUpdated'; $set = "Set$key"; my $time = RT::Date->new($session{CurrentUser}); $time->Set(Format => 'sql', Value => $ticket->$key); next if ($val =~ /^not set$/i || $val eq $time->AsString); $time->Set(Format => 'unknown', Value => $val); ($n, $s) = $ticket->$set($time->ISO); } 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) { 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/^# //; } } # Set custom field elsif ($key =~ /^$cf_spec/) { $key = $1 || $2; my $cf = RT::CustomField->new( $session{CurrentUser} ); $cf->ContextObject( $ticket ); $cf->LoadByName( Name => $key, LookupType => RT::Ticket->CustomFieldLookupType, ObjectId => $ticket->Queue, IncludeGlobal => 1, ); if (not $cf->id) { $n = 0; $s = "Unknown custom field."; } else { my $vals = $ticket->CustomFieldValues($cf->id); if ( !defined $val || !length $val ) { while ( my $val = $vals->Next ) { ($n, $s) = $ticket->DeleteCustomFieldValue( Field => $cf, ValueId => $val->id, ); $s =~ s/^# // if defined $s; } } elsif ( $cf->SingleValue ) { ($n, $s) = $ticket->AddCustomFieldValue( Field => $cf, Value => $val ); $s =~ s/^# // if defined $s; } else { my @new = @{vsplit($val, 1)}; my %new; $new{$_}++ for @new; while (my $v = $vals->Next()) { my $c = $v->Content; if ( $new{$c} ) { $new{$c}--; } else { $ticket->DeleteCustomFieldValue( Field => $cf, ValueId => $v->id ); } } for ( @new ) { while ( $new{$_} && $new{$_}-- ) { ($n, $s) = $ticket->AddCustomFieldValue( Field => $cf, Value => $_ ); $s =~ s/^# // if defined $s; } } } } } elsif ($key ne 'id' && $key ne 'type' && $key ne 'creator' && $key ne 'content-type' ) { $n = 0; $s = "Unknown field."; } SET: if ($n == 0) { $e = 1; push @comments, "# $key: $s"; unless (@$o) { # move id forward @$o = ("id", grep { $_ ne 'id' } keys %$changes); $k = $changes; } } else { $updated ||= 1; } } push(@comments, "# Ticket ".$ticket->id." updated.") if $updated; } DONE: $c ||= join("\n", @comments) if @comments; return [$c, $o, $k, $e];