%# BEGIN BPS TAGGED BLOCK {{{
%#
%# COPYRIGHT:
%#
%# This software is Copyright (c) 1996-2026 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 }}}
<&| /Widgets/TitleBox, title => loc('Attachments'), 
        title_class=> 'inverse',  
        class => 'ticket-info-attachments',
        color => "#336699",
        hide_chrome => $HideTitleBox,
        titleright_raw => $titleright,
        htmx_load => $ARGS{htmx_load},
&>

% if ( !$ReadOnly && !$SelectOnly ) {
<div class="row mt-2 gx-2 bulk-only attachment-bulk-actions">
  <div class="col-auto">
%   if ( $can_edit ) {
    <a href="javascript:void(0)" class="btn btn-primary btn-sm disabled attachment-bulk-delete" aria-disabled="true"><% loc('Delete Selected') %></a>
%   }
  </div>
  <div class="col text-end">
    <a href="<% RT->Config->Get("WebPath") %>/<% $DisplayPath %>/Attachment/Bulk" class="btn btn-sm btn-primary disabled attachment-bulk-download" aria-disabled="true"><% loc('Download Selected') %></a>
    <a href="<% RT->Config->Get("WebPath") %>/<% $DisplayPath %>/Create.html" class="btn btn-sm btn-primary disabled attachment-bulk-create" aria-disabled="true"><% loc('Copy Selected to New Ticket') %></a>

  </div>
</div>
% }

% $m->callback( %ARGS, CallbackName => 'BeforeList', TicketObj => $Ticket, Attachments => $Attachments, Documents => \%documents, IsChecked => \%is_checked );

% if ( $HideTitleBox && length $controls ) {
<div class="mt-2 d-flex justify-content-end align-items-center gap-1 attachment-picker-toolbar">
  <% $controls |n %>
</div>
% }

<div class="attachment-list mt-2" data-ticket-id="<% $Ticket ? $Ticket->Id : '' %>" data-update-url="<% RT->Config->Get('WebPath') %>/Helpers/TicketUpdate">
<table class="align-middle table <% RT->Config->Get('TableAccent') %> <% $ReadOnly || $SelectOnly ? '' : 'inline-edit' %>">
  <tbody>
% for my $filename (sort { lc($a) cmp lc($b) } keys %documents) {
%     my $first = $documents{$filename}[0];
%     my $pinned = $Ticket && grep ( { $_ eq $filename } $Ticket->PinnedAttachments ) ? 1 : 0;
    <tr data-name="<% $filename %>" data-type="<% $type{$filename} %>" data-size="<% $first->ContentLength %>" data-date="<% $first->Created %>" data-id="<% $first->Id %>" class="<% $pinned ? 'attachment-pinned' : '' %>">
      <td class="<% !$ReadOnly && $SelectOnly ? '' : 'bulk-only' %>">
        <div class="form-check">
          <input type="checkbox" class="attachment-select checkbox form-check-input" name="<% $SelectInputName %>" <% $is_checked{$first->Id} ? 'checked' : '' %> id="SelectAttachments-<% $first->Id %>" value="<% $first->Id %>" aria-label="<% loc('Select [_1]', $filename) %>" />
          <label class="form-check-label" for="SelectAttachments-<% $first->Id %>"></label>
        </div>
      </td>
      <td class="attachment-icon">
%       my $svg = RT->Config->Get('SVG')->{"filetype-$type{$filename}"} ? "filetype-$type{$filename}" : 'file-earmark';
        <% GetSVGImage( Name => $svg, Size => 32 ) |n %>
      </td>
      <td data-search="1">
%     if ( $can_edit ) {
        <div class="editable">
          <form hx-swap="none" hx-post="<% RT->Config->Get('WebPath') %>/Helpers/TicketUpdate?id=<% $Ticket->Id %>" class="editor" autocomplete="off">
            <input type="text" required class="form-control" name="RenameAttachment-<% $first->Id %>" value="<% $filename %>">
            <% GetSVGImage( Name => 'close-circle', ExtraClasses => 'cancel text-danger', Title => loc('Cancel') ) |n %>
            <% GetSVGImage( Name => 'check-circle', ExtraClasses => 'submit text-success', Title => loc('Save') ) |n %>
            <div class="spinner-border spinner-border-sm loading" role="status"></div>
          </form>
%     }
        <div class="value">
%       if ($first->ContentLength) {
%         if (my $url = RT->System->ExternalStorageURLFor($first)) {
          <a hx-boost="false" href="<% $url %>">
%         } else {
          <a hx-boost="false" href="<% RT->Config->Get('WebPath') %>/<% $DisplayPath %>/Attachment/<%
$first->TransactionId%>/<% $first->Id %>/<% $first->Filename | un %>">
%         }
            <% $filename %>
          </a>
%       } else {
          <% $filename %>
%       }
          <span class="attachment-size text-body-secondary">(<% $first->FriendlyContentLength %>)</span>

%       if ( $can_edit ) {
          <% GetSVGImage( Name => 'pencil', ExtraClasses => 'edit-icon float-none ms-1', Title => loc('Rename') ) |n %>
          </div>
%       }
        </div>
        <small>
        <% loc("[_1] by [_2]", $first->CreatedAsString, $m->scomp('/Elements/ShowUser', User => $first->CreatorObj)) |n %>
%       if ( $first->TransactionObj->Type ne 'AddAttachment' ) {
        <br>
        <% loc('Linked to') %>: <a href="<% RT->Config->Get('WebPath') %>/<% $session{CurrentUser}->Privileged ? '' : 'SelfService/' %>Transaction/Display.html?id=<% $first->TransactionId
%>"><% loc($first->TransactionObj->Type) %> #<% $first->TransactionId %></a>
%       }
        </small>
      </td>
      <td class="text-center attachment-pin-indicator">
%     if ( $pinned ) {
        <% GetSVGImage( Name => 'pin', Title => loc('Pinned') ) |n %>
%     }
      </td>
      <td class="text-center">
%     if ($first->ContentLength) {
%       if (my $url = RT->System->ExternalStorageURLFor($first)) {
        <a hx-boost="false" href="<% $url %>">
%       } else {
        <a hx-boost="false" href="<% RT->Config->Get('WebPath') %>/<% $DisplayPath %>/Attachment/<%
$first->TransactionId%>/<% $first->Id %>/<% $first->Filename | un %>?AlwaysDownloadAttachments=1">
%       }
        <% GetSVGImage( Name => 'download', Title => loc('Download') ) |n %>
        </a>
%     }
      </td>
%     if ( !$ReadOnly && !$SelectOnly ) {
      <td class="text-center">
        <a class="menu-item" href="javascript:void(0)" aria-label="<% loc('More') %>" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
          <% GetSVGImage( Name => 'three-dots-vertical', Title => loc('More') ) |n %>
        </a>
        <ul class="dropdown-menu dropdown-menu-end">
%       if ( @{$documents{$filename}} > 1 ) {
          <li>
            <a class="dropdown-item" href="javascript:void(0)" data-bs-toggle="modal" data-bs-target="#attachment-<% $first->id %>-modal"><% loc('Show Older') %></a>
          </li>
%       }

%       if ( $Ticket ) {
%         if ( $Ticket->CurrentUserHasRight('ModifyTicket') ) {
          <li>
            <a href="javascript:void(0)" class="dropdown-item attachment-delete"><% loc('Delete') %></a>
          </li>
          <li>
            <a href="javascript:void(0)" class="dropdown-item attachment-rename"><% loc('Rename') %></a>
          </li>
          <li>
%           if ( $pinned ) {
            <a href="javascript:void(0)" class="dropdown-item attachment-unpin"><% loc('Unpin') %></a>
%           } else {
            <a href="javascript:void(0)" class="dropdown-item attachment-pin"><% loc('Pin to top') %></a>
%           }
          </li>
%         }

%         if ( $session{CurrentUser}->Privileged ) {
          <li>
            <a class="dropdown-item" href="<% RT->Config->Get("WebPath") %>/<% $DisplayPath %>/Create.html?AttachExisting=<% $first->Id %>"><% loc('Copy to New Ticket') %></a>
          </li>
%         }

%         if ( $Ticket->CurrentUserHasRight('ReplyToTicket') ) {
          <li><a class="dropdown-item" href="<% RT->Config->Get("WebPath") %>/<% $DisplayPath %>/Update.html?Action=Correspond&id=<%
$Ticket->Id %>&AttachExisting=<% $first->Id %>"><% loc('Reply') %></a></li>
%         }
%         if ( $Ticket->CurrentUserHasRight('CommentOnTicket') ) {
          <li><a class="dropdown-item" href="<% RT->Config->Get("WebPath") %>/<% $DisplayPath %>/Update.html?Action=Comment&id=<%
$Ticket->Id %>&AttachExisting=<% $first->Id %>"><% loc('Comment') %></a></li>
%         }
%       }
        </ul>
%       if ( @{$documents{$filename}} > 1 ) {
        <div hx-boost="false" class="modal fade" id="attachment-<% $first->id %>-modal" tabindex="-1" role="dialog">
          <div class="modal-dialog modal-dialog-scrollable" role="document">
            <div class="modal-content">
              <div class="modal-header">
                <h5 class="modal-title"><% loc('All versions of [_1]', $first->Filename) %></h5>
                <a href="javascript:void(0)" class="close" data-bs-dismiss="modal" aria-label="Close">
                  <span aria-hidden="true">&times;</span>
                </a>
              </div>
              <div class="modal-body">
                <table class="table <% RT->Config->Get('TableAccent') %>">
                  <thead>
                    <tr>
                      <th><% loc('Date') %></th>
                      <th><% loc('Size') %></th>
                      <th><% loc('Creator') %></th>
                      <th></th>
                    </tr>
                  </thead>
                  <tbody>
%                     for my $version ( @{$documents{$filename}} ) {
                    <tr>
                      <td><% $version->CreatedAsString %></td>
                      <td><% $version->FriendlyContentLength %></td>
                      <td><& /Elements/ShowUser, User => $version->CreatorObj &></td>
                      <td>
%                         if ($version->ContentLength) {
%                           if (my $url = RT->System->ExternalStorageURLFor($version)) {
                        <a hx-boost="false" href="<% $url %>">
%                           } else {
                        <a hx-boost="false" href="<% RT->Config->Get('WebPath') %>/<% $DisplayPath %>/Attachment/<%
$version->TransactionId%>/<% $version->Id %>/<% $version->Filename | un %>?AlwaysDownloadAttachments=1">
%                           }
                        <% GetSVGImage( Name => 'download', Title => loc('Download') ) |n %>
                        </a>
%                         }
                      </td>
                    </tr>
%                     }
                  </tbody>
                </table>
              </div>
            </div>
          </div>
        </div>
%       }
      </td>
%     }
    </tr>
% }
  </tbody>
</table>

% $m->callback( %ARGS, CallbackName => 'AfterList', TicketObj => $Ticket, Attachments => $Attachments, Documents => \%documents, IsChecked => \%is_checked );

</div>

% if ( $can_edit ) {
<form class="attachment-add-form" hx-post="<% RT->Config->Get("WebPath") %>/Helpers/TicketUpdate" hx-swap="none" enctype="multipart/form-data">
  <input name="Token" value="<% Digest::MD5::md5_hex( rand(1024) ) %>" type="hidden">
  <input name="id" value="<% $Ticket->Id %>" type="hidden">
  <input name="AddAttachments" value="1" type="hidden">
  <& /Ticket/Elements/AddAttachments, TicketObj => $Ticket, SelectAttachments => 0, FullWidth => 1, AutoSubmit => 1 &>
</form>
% }

% if ( !$ReadOnly && !$SelectOnly && $Ticket && $Ticket->CurrentUserHasRight('ModifyTicket') ) {
<div class="modal attachment-delete-confirm-modal" tabindex="-1">
  <div class="modal-dialog modal-dialog-centered" role="document">
    <div class="modal-content">
      <div class="modal-header">
        <h5 class="modal-title"><&|/l&>Really delete?</&></h5>
        <a href="javascript:void(0)" class="close" data-bs-dismiss="modal" aria-label="Close">
          <span aria-hidden="true">&times;</span>
        </a>
      </div>
      <div class="modal-body">
        <p><&|/l&>The following will be deleted:</&></p>
        <ul class="attachment-delete-list"></ul>
      </div>
      <div class="modal-footer">
        <div class="row mt-2 justify-content-end">
          <div class="col-auto">
            <input type="button" class="btn btn-primary attachment-delete-confirm" data-bs-dismiss="modal" value="<% loc('Delete') %>" />
          </div>
        </div>
      </div>
    </div>
  </div>
</div>
% }

% if ( $can_edit ) {
  <& /Widgets/Spinner &>
% }
</&>

<%INIT>

# If we haven't been passed in an Attachments object (through the precaching mechanism)
# then we need to find one
$Attachments ||= $Ticket->Attachments;

# Avoid applying limits to this collection that may be used elsewhere
# (e.g. transaction display)
$Attachments = $Attachments->Clone;

# Remember, each message in a transaction is an attachment; we only
# want named attachments (real files)
$Attachments->LimitHasFilename;

my %documents;

# show newest first
$Attachments->OrderByCols(
    { FIELD => 'Created', ORDER => 'DESC' },
    { FIELD => 'id',      ORDER => 'DESC' },
);

while ( my $attach = $Attachments->Next() ) {
   push @{ $documents{ $attach->Filename } }, $attach;
}

return if keys %documents == 0 && $ReadOnly;

my $can_edit
    = !$ReadOnly
    && !$SelectOnly
    && $session{CurrentUser}->Privileged
    && $Ticket
    && $Ticket->CurrentUserHasRight('ModifyTicket') ? 1 : 0;

my %type;
for my $filename ( keys %documents ) {
    $type{$filename} = $filename =~ /\.([^.]+)\z/ ? lc $1 : 'other';
}

my $controls    = '';
my $bulk_toggle = '';
if ( keys %documents ) {
    # Search and type filtering run client-side, so render both glasses and both
    # funnels (and their labels); init.js swaps them as the filters change.
    my $search_icon        = GetSVGImage( Name => 'search' ) . GetSVGImage( Name => 'search-fill' );
    my $search_label        = loc('Search');
    my $search_label_active = loc('Search (active)');

    # The collapse-to-a-glass behavior only applies in the titlebox widget; the
    # picker (HideTitleBox) has no titlebox to expand into, so keep search open
    # and skip the toggle/close affordances.
    my ( $toggle_class, $toggle_attrs, $close_span ) = ( '', '', '' );
    if ( !$HideTitleBox ) {
        # No tooltip on the close icon: it hides itself on click, which would orphan
        # a stuck tooltip at 0,0. The span's aria-label keeps it accessible.
        my $close_icon  = GetSVGImage( Name => 'close' );
        my $close_label = loc('Close');
        $toggle_class = ' attachment-search-toggle';
        $toggle_attrs
            = qq{ role="button" tabindex="0" aria-label="$search_label" data-label="$search_label" data-label-active="$search_label_active"};
        $close_span
            = qq{<span class="input-group-text attachment-search-close" role="button" tabindex="0" aria-label="$close_label">$close_icon</span>};
    }
    $controls
        .= qq{<span class="input-group d-inline-flex align-top attachment-search-input">}
        . qq{<span class="input-group-text$toggle_class"$toggle_attrs>$search_icon</span>}
        . qq{<input class="form-control attachment-search" name="attachment-search" type="text" placeholder="$search_label">}
        . qq{$close_span</span> };

    my $funnel           = GetSVGImage( Name => 'funnel' ) . GetSVGImage( Name => 'funnel-fill' );
    my $filter_alt        = loc('Type Filter');
    my $filter_alt_active = loc('Type Filter (active)');
    my $all_label  = loc('All');
    my $none_label = loc('None');
    $controls
        .= qq{<span class="rt-inline-icon dropdown border rounded attachment-filter-toggle" data-bs-toggle="tooltip" data-bs-placement="top" data-bs-title="$filter_alt" data-label="$filter_alt" data-label-active="$filter_alt_active">}
        . qq{<a class="attachment-filter menu-item" href="#" aria-label="$filter_alt" data-bs-toggle="dropdown" data-bs-auto-close="outside" data-bs-boundary="viewport" aria-haspopup="true" aria-expanded="false">$funnel</a>}
        . qq{<ul class="dropdown-menu dropdown-menu-end attachment-filter-dropdown" data-bs-boundary="viewport"><li>}
        . qq{<div class="dropdown-submenu"><div class="d-flex justify-content-between align-items-center mb-2 px-2 filter-actions">}
        . qq{<a href="#" onclick="return attachmentFilterSelectAll(this, event);" class="attachment-filter-select-link text-body-secondary text-decoration-none">$all_label</a>}
        . qq{<a href="#" onclick="return attachmentFilterSelectNone(this, event);" class="attachment-filter-select-link text-body-secondary text-decoration-none">$none_label</a>}
        . qq{</div>};
    for my $t ( sort { $a cmp $b } List::MoreUtils::uniq values %type ) {
        my $et    = $m->interp->apply_escapes( $t, 'h' );
        my $label = $t eq 'other' ? loc('other') : $et;
        $controls
            .= qq{<div class="form-check mx-3"><input class="form-check-input" type="checkbox" name="FilterAttachmentTypes" value="$et" id="attachment-type-$et" checked>}
            . qq{<label class="form-check-label" for="attachment-type-$et">$label</label></div>};
    }
    $controls .= qq{</div></li></ul></span> };

    my $sort_label = loc('Sort');

    # The titlebox widget renders both sort controls; ticket.css shows the tom-select
    # on wide screens and the compact menu on mobile. The picker never collapses, so
    # it gets only the tom-select.
    $controls .= qq{<select class="form-select form-select-sm selectpicker attachment-sort" aria-label="$sort_label">};
    for my $field (qw/Name Type Size Date/) {
        for my $order (qw/asc desc/) {
            my $arrow = $order eq 'asc' ? '&#8593;' : '&#8595;';
            $controls .= qq{<option value="$field-$order">} . loc($field) . qq{ $arrow</option>};
        }
    }
    $controls .= qq{</select> };

    if ( !$HideTitleBox ) {
        my $sort_icon = GetSVGImage( Name => 'sort-down', Title => $sort_label );
        $controls
            .= qq{<span class="rt-inline-icon dropdown border rounded attachment-sort-menu">}
            . qq{<a class="attachment-sort-toggle menu-item" href="javascript:void(0)" aria-label="$sort_label" data-bs-toggle="dropdown" data-bs-boundary="viewport" aria-haspopup="true" aria-expanded="false">$sort_icon</a>}
            . qq{<ul class="dropdown-menu dropdown-menu-end">};
        for my $field (qw/Name Type Size Date/) {
            for my $order (qw/asc desc/) {
                my $arrow = $order eq 'asc' ? '&#8593;' : '&#8595;';
                $controls
                    .= qq{<li><a class="dropdown-item attachment-sort-option" href="javascript:void(0)" data-sort="$field-$order">}
                    . loc($field)
                    . qq{ $arrow</a></li>};
            }
        }
        $controls .= qq{</ul></span> };
    }

    if ( !$ReadOnly && !$SelectOnly ) {
        my $bulk_svg   = GetSVGImage( Name => 'collection', Title => loc('Bulk') );
        my $bulk_alt   = loc('Bulk');
        my $cancel_svg = GetSVGImage( Name => 'close', Title => loc('Cancel') );
        my $cancel_alt = loc('Cancel');
        $bulk_toggle
            .= qq{<a class="attachment-bulk-toggle bulk" href="javascript:void(0)" aria-label="$bulk_alt"><span class="rt-inline-icon border rounded">$bulk_svg</span></a>}
            . qq{<a class="attachment-bulk-toggle cancel hidden" href="javascript:void(0)" aria-label="$cancel_alt"><span class="rt-inline-icon border rounded">$cancel_svg</span></a>};
    }
}
my $titleright
    = $HideTitleBox
    ? ''
    : qq{<span class="d-inline-flex align-items-center gap-1">$controls$bulk_toggle</span>};
my %is_checked = map { $_ => 1 } @Checked;
</%INIT>
<%ARGS>
$Ticket => undef
$Attachments => undef
$DisplayPath => $session{'CurrentUser'}->Privileged ? 'Ticket' : 'SelfService'
$HideTitleBox => 0
$SelectOnly => 0
$SelectInputName => 'SelectAttachments'
$ReadOnly => 0
@Checked => ()
</%ARGS>

<%attr>
directly_accessible => 1
</%attr>
