%# BEGIN BPS TAGGED BLOCK {{{
%#
%# COPYRIGHT:
%#
%# This software is Copyright (c) 1996-2025 Best Practical Solutions, LLC
%#                                          <sales@bestpractical.com>
%#
%# (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
</%ARGS>
<%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 SLA);
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 $due = RT::Date->new($session{CurrentUser});
        my $queue = RT::Queue->new($session{CurrentUser});
        my $starts = RT::Date->new($session{CurrentUser});
        $queue->Load(1);
        if ( $queue->DefaultValue('Due') ) {
            $due->Set( Format => 'unknown', Value => $queue->DefaultValue('Due') );
        }

        if ( $queue->DefaultValue('Starts') ) {
            $starts->Set( Format => 'unknown', Value => $queue->DefaultValue('Starts') );
        }
        else {
            $starts->SetToNow;
        }

        return [
            "# Required: id, Queue",
            [ qw(id Queue Requestor Subject Cc AdminCc Owner Status Priority
                 InitialPriority FinalPriority TimeEstimated Starts Due),
                 ( $queue->SLADisabled ? () : 'SLA' ), qw(Attachment Text) ],
            {
                id               => "ticket/new",
                Queue            => $queue->Name,
                Requestor        => $session{CurrentUser}->Name,
                Subject          => "",
                Cc               => [],
                AdminCc          => [],
                Owner            => "",
                Status           => "new",
                Priority         => $queue->DefaultValue('InitialPriority'),
                InitialPriority  => $queue->DefaultValue('InitialPriority'),
                FinalPriority    => $queue->DefaultValue('FinalPriority'),
                TimeEstimated    => 0,
                Starts           => $starts->ISO(Timezone => 'user'),
                Due              => $due->IsSet ? $due->ISO(Timezone => 'user') : undef,
                SLA              => '',
                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;
            RT::I18N::SetMIMEEntityToUTF8( $v{MIMEObj} );
        }

        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 ];
    }

    if ( !$ticket->QueueObj->SLADisabled && (!%$fields || (exists $fields->{sla})) ) {
        push @data, [ SLA => $ticket->SLA ];
    }

    # 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;

            my $members = $ticket->$key->MembersObj;
            while (my $member = $members->Next) {
                my $principal = $member->MemberObj;
                if ($principal->IsGroup) {
                    $old{ $principal->Id } = 1;
                }
                else {
                    $old{ $principal->Object->EmailAddress } = 1;
                }
            }

            my $type = $key eq 'Requestors' ? 'Requestor' : $key;

            foreach $p (keys %old) {
                unless (exists $new{$p}) {
                    my $key = "Email";
                    $key = "PrincipalId" if $p =~ /^\d+$/;

                    ($s, $n) = $ticket->DeleteWatcher(Type => $type,
                                                      $key => $p);
                    push @msgs, [ $s, $n ];
                }
            }
            foreach $p (keys %new) {
                my $key = "Email";
                $key = "PrincipalId" if $p =~ /^\d+$/;
                unless ($ticket->IsWatcher(Type => $type, $key => $p)) {
                    ($s, $n) = $ticket->AddWatcher(Type => $type,
                                                   $key => $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 ( $ticket->CustomFieldValueIsEmpty( Field => $cf, Value => $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{$_}-- ) {
                            next if $ticket->CustomFieldValueIsEmpty(
                                Field => $cf,
                                Value => $_,
                            );
                            ($n, $s) = $ticket->AddCustomFieldValue(
                                Field => $cf, Value => $_ );
                            $s =~ s/^# // if defined $s;
                        }
                    }
                }
            }
        }
        elsif ( $key eq 'sla' ) {
            my $current = $ticket->SLA // '';
            next if $val eq $current;
            ($n, $s) = $ticket->SetSLA($val);
        }
        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];

</%INIT>
