%# 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 }}}
<& /Elements/Header, Title  => $title &>
<& /Elements/Tabs &>

% $m->callback(CallbackName => 'BeforeActionList', ARGSRef => \%ARGS, Ticket => $TicketObj, results => \@results);
<& /Elements/ListActions, actions => \@results &>

<div>
<form action="Update.html" name="TicketUpdate"
    method="post" enctype="multipart/form-data">
% $m->callback( CallbackName => 'FormStart', ARGSRef => \%ARGS, Ticket => $TicketObj, CanRespond => $CanRespond, CanComment => $CanComment, ResponseDefault => $ResponseDefault, CommentDefault => $CommentDefault );
<input type="hidden" class="hidden" name="QuoteTransaction" value="<% $ARGS{QuoteTransaction}||'' %>" />
<input type="hidden" class="hidden" name="DefaultStatus" value="<% $DefaultStatus ||''%>" />
<input type="hidden" class="hidden" name="Action" value="<% $Action %>" />
<input type="hidden" class="hidden" name="Token" value="<% $ARGS{'Token'} %>" />

<& /Elements/Crypt/SignEncryptWidget:ShowIssues, self => $gnupg_widget, Queue => $TicketObj->QueueObj &>

<div id="ticket-update-metadata">
  <&|/Widgets/TitleBox, title => loc('Ticket and Transaction'), class => 'ticket-info-basics' &>
<div>
% $m->callback(CallbackName => 'AfterTableOpens', ARGSRef => \%ARGS, Ticket => $TicketObj);

% my $skip;
% $m->callback( %ARGS, CallbackName => 'BeforeUpdateType', skip => \$skip );
% if (!$skip) {
<input type="hidden" class="hidden" name="id" value="<%$TicketObj->Id%>" />
% }

% if ( $TicketObj->CurrentUserHasRight('SeeQueue') ) {
<&| /Elements/LabeledValue, Label => loc('Queue'), ValueSpanClass => 'current-value' &>
<% $TicketObj->QueueObj->Name %>
</&>
% }

<div class="form-row"><div class="label col-3"><&|/l&>Update Type</&>:</div>
<div class="value col-9"><select class="form-control selectpicker" name="UpdateType" id="UpdateType">
% if ($CanComment) {
<option value="private" <% ($ARGS{'UpdateType'} &&  $ARGS{'UpdateType'} eq "private") ? qq[ selected="selected"] : !$ARGS{'UpdateType'}&&$CommentDefault |n %>><&|/l&>Comments (Not sent to requestors)</&></option>
% }
% if ($CanRespond) {
<option value="response" <% ($ARGS{'UpdateType'} && $ARGS{'UpdateType'} eq "response") ? qq[ selected="selected"] : !$ARGS{'UpdateType'}&&$ResponseDefault |n %>><&|/l&>Reply to requestors</&></option>
% }
</select> 

% $m->callback( %ARGS, CallbackName => 'AfterUpdateType' );
</div></div>

<& /Ticket/Elements/EditBasics,
    TicketObj => $TicketObj,
    InTable   => 1,
    fields    => [
        {   name => 'Status',
            comp => '/Ticket/Elements/SelectStatus',
            args => {
                Name => 'Status',
                Default => $DefaultStatus,
                TicketObj => $TicketObj,
            },
        },
        {   name => 'Owner',
            comp => '/Elements/SelectOwner',
            args => {
                Name         => "Owner",
                TicketObj    => $TicketObj,
                QueueObj     => $TicketObj->QueueObj,
                DefaultLabel => loc("[_1] (Unchanged)", $TicketObj->OwnerObj->Format),
                Default      => $ARGS{'Owner'}
            }
        },
        { special => 'roles' },
        {   name => 'Worked',
            comp => '/Elements/EditTimeValue',
            args => {
                Name => 'UpdateTimeWorked',
                Default => $ARGS{UpdateTimeWorked}||'',
            }
        },
    ]
&>

% $m->callback( %ARGS, CallbackName => 'AfterWorked', Ticket => $TicketObj );

<& /Ticket/Elements/EditTransactionCustomFields, %ARGS, TicketObj => $TicketObj &>

  </div>
  </&>

% $m->callback( %ARGS, CallbackName => 'RightColumnBottom', Ticket => $TicketObj );
</div>

<div id="ticket-update-message">
% if ( RT->Config->Get('SimplifiedRecipients', $session{'CurrentUser'}) ) {
    <&|/Widgets/TitleBox, title => loc('Recipients'), id => 'recipients' &>
%   unless ($TicketObj->CurrentUserHasRight('ShowOutgoingEmail')) {
%     for my $address (grep {defined} ref $ARGS{TxnSendMailTo} ? @{$ARGS{TxnSendMailTo}} : $ARGS{TxnSendMailTo}) {
        <input type="hidden" name="TxnSendMailTo" value="<% $address %>" />
%     }
      <input type="hidden" name="TxnRecipients" value="<% $ARGS{TxnRecipients} || '' %>" />
%   }
    </&>
%}

  <&|/Widgets/TitleBox, title => loc('Message'), class => 'messagedetails' &>

<& /Ticket/Elements/UpdateCc, %ARGS, TicketObj => $TicketObj &>

% if ( $gnupg_widget ) {
<div class="form-row">
  <div class="label col-2 empty"></div>
  <div class="value col-9">
    <& /Elements/Crypt/SignEncryptWidget,
        self => $gnupg_widget,
        TicketObj => $TicketObj,
    &>
  </div>
</div>
% }
% $m->callback( %ARGS, CallbackName => 'AfterGnuPG' );

<div class="form-row">
  <div class="label col-2">
    <&|/l&>Subject</&>:
  </div>
  <div class="value col-9">
    <input class="form-control" type="text" name="UpdateSubject" value="<% $ARGS{UpdateSubject} || $TicketObj->Subject || '' %>" />
% $m->callback( %ARGS, CallbackName => 'AfterSubject' );
  </div>
</div>
<& /Articles/Elements/BeforeMessageBox, %ARGS &>

  <div class="messagebox-container action-<% $type %>">
% $m->callback( %ARGS, CallbackName => 'BeforeMessageBox' );
% if (exists $ARGS{UpdateContent}) {
% # preserve QuoteTransaction so we can use it to set up sane references/in/reply to
% my $temp = $ARGS{'QuoteTransaction'};
% delete $ARGS{'QuoteTransaction'};
    <& /Elements/MessageBox, Name=>"UpdateContent", Default=>$ARGS{UpdateContent}, IncludeSignature => 0, %ARGS, FullWidth => 1 &>
% $ARGS{'QuoteTransaction'} = $temp;
% } else {
% my $IncludeSignature = 1;
% $IncludeSignature = 0 if $Action ne 'Respond' && !RT->Config->Get('MessageBoxIncludeSignatureOnComment');
    <& /Elements/MessageBox, Name=>"UpdateContent", IncludeSignature => $IncludeSignature, %ARGS, FullWidth => 1 &>
% }
% $m->callback( %ARGS, CallbackName => 'AfterMessageBox' );
  </div>

  <& /Ticket/Elements/AddAttachments, %ARGS, TicketObj => $TicketObj, FullWidth => 1 &>
</&>

% $m->callback( %ARGS, CallbackName => 'BeforeSubmit', Ticket => $TicketObj );

<div class="form-row">
  <div class="col-12">
    <& /Elements/Submit, Label => loc('Update Ticket'), Name => 'SubmitTicket', id => 'SubmitTicket' &>
  </div>
</div>

% $m->callback( %ARGS, CallbackName => 'BeforeScrips', Ticket => $TicketObj );

% if ($TicketObj->CurrentUserHasRight('ShowOutgoingEmail')) {
  <&|/Widgets/TitleBox, title => loc('Scrips and Recipients'), id => 'previewscrips', rolledup => RT->Config->Get('SimplifiedRecipients', $session{'CurrentUser'}), class => 'ticket-info-people' &>
%   for my $address (grep {defined} ref $ARGS{TxnSendMailTo} ? @{$ARGS{TxnSendMailTo}} : $ARGS{TxnSendMailTo}) {
      <input type="hidden" name="TxnSendMailTo" value="<% $address %>" />
%   }
    <input type="hidden" name="TxnRecipients" value="<% $ARGS{TxnRecipients} || '' %>" />
  </&>
% }

%# Close #ticket-update-message
</div>
% $m->callback( %ARGS, CallbackName => 'AfterScrips', Ticket => $TicketObj );

</form>
</div>
<hr class="clear" />

% $m->callback( %ARGS, CallbackName => 'AfterForm', Ticket => $TicketObj );

% if ($TicketObj->CurrentUserHasRight('ShowOutgoingEmail') or RT->Config->Get('SimplifiedRecipients', $session{'CurrentUser'})) {
<script type="text/javascript">
jQuery( function() {
   var updateScrips = function() {
       if (CKEDITOR.instances && CKEDITOR.instances['UpdateContent']) {
           CKEDITOR.instances['UpdateContent'].updateElement();
       }
       var syncCheckboxes = function(ev) {
           var target = ev.target;
           jQuery("input[name=TxnSendMailTo]").filter( function() { return this.value == target.value; } ).prop("checked",jQuery(target).prop('checked'));
       };

       jQuery('#recipients div.titlebox-content').addClass('refreshing');
       jQuery('#previewscrips div.titlebox-content').addClass('refreshing');

       // Wait a little bit in case user leaves related inputs(which
       // could fire updateScrips) by checking/unchecking recipient
       // checkboxes, this is to get checkboxes' latest status
       setTimeout( function() {
           var payload = jQuery('form[name=TicketUpdate]').serializeArray();

           jQuery('#recipients div.titlebox-content div.card-body').load( '<% RT->Config->Get('WebPath')%>/Helpers/ShowSimplifiedRecipients',
               payload,
               function() {
                   jQuery('#recipients div.titlebox-content').removeClass('refreshing');
                   var txn_send_field = jQuery("#recipients input[name=TxnSendMailTo]");
                   txn_send_field.change( function(ev) { syncCheckboxes(ev); setCheckbox(this); } );
                   jQuery("#recipients input[name=TxnSendMailToAll]").click( function() { setCheckbox(this, 'TxnSendMailTo'); } );
                   if (txn_send_field.length > 0) {
                       setCheckbox(txn_send_field[0]);
                   }
               }
           );

           jQuery('#previewscrips div.titlebox-content div.card-body').load( '<% RT->Config->Get('WebPath')%>/Helpers/PreviewScrips',
               payload,
               function() {
                   jQuery('#previewscrips div.titlebox-content').removeClass('refreshing');
                   var txn_send_field = jQuery("#previewscrips input[name=TxnSendMailTo]");
                   txn_send_field.change( function(ev) { syncCheckboxes(ev); setCheckbox(this); } );
                   jQuery("#previewscrips input[name=TxnSendMailToAll]").click( function() { setCheckbox(this, 'TxnSendMailTo'); } );
                   if (txn_send_field.length > 0) {
                       setCheckbox(txn_send_field[0]);
                   }
               }
           );
       }, 100);
   };
   updateScrips();

   if (CKEDITOR.instances && CKEDITOR.instances['UpdateContent']) {
       CKEDITOR.instances['UpdateContent'].on('blur', updateScrips );
   }
   else {
       jQuery('#UpdateContent').on('blur', updateScrips );
   }

   jQuery("#ticket-update-metadata :input, #UpdateCc, #UpdateBcc").change( updateScrips );

   var syncOneTimeCheckboxes = function() {
        var emails = jQuery(this).val().split(/,\s*/);
        var prefix = jQuery(this).attr('id');
        var type = prefix.replace('Update', '');
        var checked = 0;
        var unchecked = 0;
        jQuery('input:checkbox[name^=' + prefix + ']').each( function() {
            var name = jQuery(this).attr('name');
            name = escapeRegExp(name.replace(prefix + '-', ''));

            var filter_function = function(n,i) {
                return n.match(new RegExp('^\\s*' + name + '\\s*$', 'i')) || n.match(new RegExp('<\\s*' + name + '\\s*>', 'i'));
            };
            if ( jQuery.grep(emails, filter_function).length == 0 ) {
                unchecked++;
                if ( jQuery(this).prop('checked') ) {
                    jQuery(this).prop('checked', false);
                }
            }
            else {
                checked++;
                if ( !jQuery(this).prop('checked') ) {
                    jQuery(this).prop('checked', true);
                    if ( jQuery('#UpdateIgnoreAddressCheckboxes').val() == 0 ) {
                        jQuery('#UpdateIgnoreAddressCheckboxes').val(1);
                    }
                }
            }
        });

        if ( unchecked > 0 ) {
            if ( jQuery('#AllSuggested' + type).is(':checked') ) {
                jQuery('#AllSuggested' + type).prop('checked', false);
            }
        }
        else if ( checked > 0 && unchecked == 0 ) {
            if ( !jQuery('#AllSuggested' + type).is(':checked') ) {
                jQuery('#AllSuggested' + type).prop('checked', true);
            }
        }
   };

   jQuery("#UpdateCc, #UpdateBcc").change(syncOneTimeCheckboxes);

});
</script>
% }
<%INIT>
my $CanRespond = 0;
my $CanComment = 0;
my $checks_failure = 0;

my $TicketObj = LoadTicket($id);

my @results;

# no support for muliple articles included at the same time
( $ARGS{IncludeArticleId} ) = grep defined && length, @{ $ARGS{IncludeArticleId} }
    if ref( $ARGS{IncludeArticleId} ) eq 'ARRAY';

if ($ARGS{IncludeArticleId}) {
    my $article = RT::Article->new($session{'CurrentUser'});
    my ($ret, $msg) = $article->Load( $ARGS{IncludeArticleId} );

    if (!$ret) {
        # Make sure we don't finalize the update if we won't be able to include the article
        $checks_failure = 1;
        push @results, loc('Unable to load article "[_1]"', $ARGS{IncludeArticleId});
    }
}

$m->callback( Ticket => $TicketObj, ARGSRef => \%ARGS, checks_failure => \$checks_failure, results => \@results, CallbackName => 'Initial' );
$m->scomp( '/Articles/Elements/SubjectOverride', Ticket => $TicketObj, ARGSRef => \%ARGS, results => \@results );

unless($DefaultStatus){
    $DefaultStatus=($ARGS{'Status'} ||$TicketObj->Status());
}

my $title = loc("Update ticket #[_1]: [_2]", $TicketObj->id, $TicketObj->Subject);

# Things needed in the template - we'll do the processing here, just
# for the convenience:

my ($CommentDefault, $ResponseDefault);
if ($Action ne 'Respond') {
    $CommentDefault = qq[ selected="selected"]; 
    $ResponseDefault = "";
} else {
    $CommentDefault = ""; 
    $ResponseDefault = qq[ selected="selected"];
}

my $type =             $ARGS{'UpdateType'} ? $ARGS{'UpdateType'} :
           lc $Action eq 'respond' ? 'response'          :
           lc $Action eq 'comment' ? 'private'           :
                                             'none'              ;


$CanRespond = 1 if ( $TicketObj->CurrentUserHasRight('ReplyToTicket') or
                     $TicketObj->CurrentUserHasRight('ModifyTicket') ); 

$CanComment = 1 if ( $TicketObj->CurrentUserHasRight('CommentOnTicket') or
                     $TicketObj->CurrentUserHasRight('ModifyTicket') ); 

ProcessAttachments(ARGSRef => \%ARGS);

my %squelched = ProcessTransactionSquelching( \%ARGS );
$ARGS{'SquelchMailTo'} = [keys %squelched] if keys %squelched;

my $gnupg_widget = $m->comp('/Elements/Crypt/SignEncryptWidget:new', Arguments => \%ARGS );
$m->comp( '/Elements/Crypt/SignEncryptWidget:Process',
    self => $gnupg_widget,
    TicketObj => $TicketObj,
);

if ( $ARGS{'SubmitTicket'} ) {

    my ($status, @msg) = $m->comp(
        '/Elements/ValidateCustomFields',
        CustomFields => $TicketObj->TransactionCustomFields,
        Object => RT::Transaction->new( $session{'CurrentUser'} ),
        ARGSRef => \%ARGS
    );
    unless ( $status ) {
        push @results, @msg;
        $checks_failure = 1;
    }
    $status = $m->comp('/Elements/Crypt/SignEncryptWidget:Check',
        self      => $gnupg_widget,
        TicketObj => $TicketObj,
    );
    $checks_failure = 1 unless $status;

    ( $status, @msg ) = PreprocessTimeUpdates( \%ARGS );
    unless ( $status ) {
        push @results, @msg;
        $checks_failure = 1;
    }
}

# check email addresses for RT's
{
    foreach my $field ( qw(UpdateCc UpdateBcc) ) {
        my $value = $ARGS{ $field };
        next unless defined $value && length $value;

        my ($label) = $field =~ /Update(\w+)/;
        my ( $filtered_emails, $error_msgs ) = ProcessEmailAddresses(
            Field   => $field,
            Value   => $value,
            Label   => loc( 'One-time [_1]', loc($label) ),
            ARGSRef => \%ARGS,
        );

        if ( @$error_msgs ) {
            push @results, @$error_msgs;
            $checks_failure ||= 1;
        }
        $ARGS{ $field } = join ', ', grep defined, @$filtered_emails;
    }
}

# $skip_update is provided below by reference to allow a final check to stop
# the update and print a message for the user to fix something.
my $skip_update = 0;
$m->callback( CallbackName => 'BeforeUpdate', ARGSRef => \%ARGS, skip_update => \$skip_update,
              checks_failure => $checks_failure, results => \@results, TicketObj => $TicketObj );

if ( !$checks_failure && !$skip_update && exists $ARGS{SubmitTicket} ) {
    $m->callback( Ticket => $TicketObj, ARGSRef => \%ARGS, CallbackName => 'BeforeDisplay' );
    return $m->comp('Display.html', TicketObj => $TicketObj, %ARGS);
}

$TicketObj->CurrentUser->AddRecentlyViewedTicket($TicketObj);

</%INIT>

<%ARGS>
$id => undef
$Action => ''
$DefaultStatus => undef
</%ARGS>
