%# 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 }}}
<%ARGS>
$Class        => 'RT::Tickets'

@Format       => undef
$FormatString => undef
@OrderBy      => ()
@Order        => ()
$Query        => undef
$Rows         => undef
$Page         => undef
$GenericQueryArgs => undef
$maxitems     => undef

$AllowSorting  => undef
$AllowFiltering => undef
$BaseURL       => undef
@PassArguments => qw(Query Format Rows Page Order OrderBy)
</%ARGS>
<tr class="collection-as-table">
<%PERL>

my $generic_query_args = $GenericQueryArgs || {map { $_ => $ARGS{$_} } @PassArguments};
# backward compatibility workaround
$generic_query_args->{'Format'} = $FormatString if grep $_ eq 'Format', @PassArguments;

my $column_map_class;
if ( $Class =~ /::/ ) {
    $column_map_class = $Class->ColumnMapClassName;
}
else {
    # For back compatibility
    $column_map_class = $Class;
}

my $item = 0;


# "Requestor" has alias "Requestors", this is to handle the case where
# OrderBy is "Requestor.Name" and result Format is "Requestors"
my %field_alias;
if ( $Class->can('RecordClass') && $Class->RecordClass->DOES("RT::Record::Role::Roles") ) {
    for my $role ( $Class->RecordClass->Roles( UserDefined => 0 ) ) {
        my $attrs = $Class->RecordClass->Role($role);
        $field_alias{$role} = $role . 's' unless $attrs->{Single};
    }
}

my %order_by;
for my $i ( 0 .. $#OrderBy ) {
    my $field = $OrderBy[$i];
    $order_by{$field}{index} = $i;
    $order_by{$field}{order} = $Order[$i] || 'ASC';
    $order_by{$field}{class} = $order_by{ $OrderBy[$i] }{order} =~ /ASC/i ? 'fa-sort-up' : 'fa-sort-down';

    if ( $field =~ m!^(\w+)\.! && $field_alias{$1} ) {
        my $alias = $field;
        $alias =~ s!^(\w+)\.!$field_alias{$1}.!;
        $order_by{$alias} = $order_by{$field};
    }
}

foreach my $col ( @Format ) {
    my $attr = $col->{'attribute'} || $col->{'last_attribute'};

    my $title = $col->{'title'} || '';
    if ( $title eq 'NEWLINE' ) {
        while ( $item < $maxitems ) {
            $m->out(qq{<th class="collection-as-table">&nbsp;</th>\n});
            $item++;
        }

        $item = 0;
        $m->out(qq{</tr>\n<tr class="collection-as-table">});
        next;
    } elsif ( $title eq 'NBSP' ) {
        $item++;
        $m->out(qq{<th class="collection-as-table">&nbsp;</th>\n});
        next;
    }

    my $span = $col->{'span'};
    $item += ($span || 1);

    my $class = 'collection-as-table';
    $class .= ' allow-sorting' if $AllowSorting;
    $class .= ' allow-filtering' if $AllowFiltering;

    $m->out(qq{<th class="$class"});
    $m->out(' colspan="' . $m->interp->apply_escapes($span  => 'h') . '"')
        if $span;

    my $align = $col->{'align'} || do {
        my $tmp_columnmap = $m->comp( '/Elements/ColumnMap',
            Class => $column_map_class,
            Name => $attr,
            Attr => 'align',
        );
        ProcessColumnMapValue( $tmp_columnmap, Arguments => [ $attr ] );
    };
    $m->out(qq{ style="text-align: $align"}) if $align;
    $m->out('>');

    my $loc_title;
    # if title is not defined then use defined attribute or last
    # one we saw in the format
    unless ( defined $col->{'title'} ) {
        my $tmp = $m->comp( '/Elements/ColumnMap',
            Class => $column_map_class,
            Name  => $attr,
            Attr  => 'title',
        );
        $title = ProcessColumnMapValue( $tmp, Arguments => [ $attr ] );

        # in case title is not defined in ColumnMap 
        # the following regex changes $attr like from "ReferredToBy" to "Referred To By"
        $title = join ' ', split /(?<=[a-z])(?=[A-Z])/, $attr unless defined $title;
        $loc_title = $attr =~ /^(?:CustomField|CF)\./ ? $title : loc($title);
    } else {
        $loc_title = loc($m->comp('/Elements/ScrubHTML', Content => $title));
    }

    my $attribute;
    if ( $col->{'attribute'} ) {
        $attribute = $m->comp(
            "/Elements/ColumnMap",
            Class => $column_map_class,
            Name  => $col->{'attribute'},
            Attr  => 'attribute'
        );
    }

    if ( $AllowSorting and $attribute ) {
        my $attr = ProcessColumnMapValue( $attribute, Arguments => [ $col->{'attribute'} ], Escape => 0 );


        my @new_order_by = @OrderBy;
        my @new_order = @Order;

        # ASC -> DESC -> UNSET -> ASC, etc.
        if ( $order_by{$attr} ) {
             splice @new_order_by, $order_by{$attr}{index}, 1;
             splice @new_order,   $order_by{$attr}{index}, 1;

             if ( $order_by{$attr}{order} eq 'ASC' ) {
                 unshift @new_order_by, $attr;
                 unshift @new_order,   'DESC';
             }
        }
        else {
            unshift @new_order_by, $attr;
            unshift @new_order, 'ASC';
        }

        $m->out(
            '<span class="title"><a href="' . $m->interp->apply_escapes($BaseURL
            . $m->comp( '/Elements/QueryString',
                ShortenSearchQuery(%$generic_query_args),
                OrderBy => join( '|', @new_order_by ), Order => join( '|', @new_order ),
            ), 'h')
            . '">'. $loc_title .'</a></span>'
        );

        if ( $order_by{$attr} ) {
            $m->out( qq!&nbsp;<span class="fas $order_by{$attr}{class}"></span>! );
        }
        else {
            $m->out( qq!&nbsp;<span class="fas fa-sort"></span>! );
        }
    }
    else {
        $m->out( qq{<span class="title">$loc_title</span>} );
    }

    if ( $AllowFiltering ) {
        my $attr = ProcessColumnMapValue( $attribute, Arguments => [ $col->{'attribute'} ], Escape => 1 );
        my %supported;
        my $filter_comp;
        if ( $Class eq 'RT::Tickets' ) {
            %supported = map { $_ => 1 }
                qw/id Subject Status Queue Owner Type Creator LastUpdatedBy SLA InitialPriority FinalPriority Priority TimeLeft TimeWorked TimeEstimated Created LastUpdated Told Starts Started Due Resolved Requestors Requestor Cc AdminCc/;
            $filter_comp = '/Search/Elements/FilterTickets';
        }
        elsif ( $Class eq 'RT::Assets' ) {
            %supported = map { $_ => 1 }
                qw/id Name Description Status Catalog Created LastUpdated Creator LastUpdatedBy Owner HeldBy Contact Contacts/;
            $filter_comp = '/Search/Elements/FilterAssets';
        }

        my $field;
        if ( ( $attr || '' ) =~ /^(\w+)/ && $supported{$1} ) {
            $field = $1;
        }
        elsif ( ( $attr || '' ) =~ /^(CustomRole\.\{.+?\})/ ) {
            $field = $1;
        }
        elsif ( ( $attr || '' ) =~ /^(?:CF|CustomField(?:View)?)\.(?:(\w+)|\{(.+?)\})/ ) {
            my $name = $1 || $2;
            $field = "CustomField.{$name}";
        }

        if ( $field && $filter_comp ) {
            my $tooltip = loc( 'Filter on [_1]', loc($field) );
            my $icon = exists $filter_data{filter}{$field} ? 'funnel-fill' : 'funnel';
            $icon = RT::Interface::Web::MenuBuilder::GetSVGImage($icon);
            $m->out(
                qq{&nbsp;<a href="javascript:void(0)" class="btn btn-primary button search-filter" data-toggle="tooltip" data-placement="bottom" data-original-title="$tooltip">$icon</a>}
            );
            $m->out( $m->scomp( $filter_comp, Attribute => $field, FilterData => \%filter_data, %ARGS ) );
        }
    }
    $m->out('</th>');
}
</%PERL>
</tr>

<%INIT>

my %filter_data;
if ( $AllowFiltering && $ARGS{Query} && $ARGS{BaseQuery} ) {

    if ( $Class eq 'RT::Tickets' ) {
        my $tickets = RT::Tickets->new( $session{CurrentUser} );
        my ($ok) = $tickets->FromSQL( $ARGS{Query} );
        return unless $ok && ( $ARGS{BaseQuery} || $tickets->Count );

        my @queues;

        my $tree = RT::Interface::Web::QueryBuilder::Tree->new;
        $tree->ParseSQL( Query => $ARGS{BaseQuery} || $ARGS{Query}, CurrentUser => $session{'CurrentUser'} );
        my $referenced_queues = $tree->GetReferencedQueues;
        for my $name_or_id ( keys %$referenced_queues ) {
            my $queue = RT::Queue->new( $session{CurrentUser} );
            $queue->Load($name_or_id);
            if ( $queue->id ) {
                push @queues, $queue;
            }
        }

        my %status;
        my @lifecycles;

        if (@queues) {
            my %lifecycle;
            for my $queue (@queues) {
                next if $lifecycle{ $queue->Lifecycle }++;
                push @lifecycles, $queue->LifecycleObj;
            }
        }
        else {
            @lifecycles = map { RT::Lifecycle->Load( Type => 'ticket', Name => $_ ) } RT::Lifecycle->List('ticket');
        }

        for my $lifecycle (@lifecycles) {
            $status{$_} = 1 for $lifecycle->Valid;
        }
        delete $status{deleted};

        if ( !@queues ) {
            my $queues = RT::Queues->new( $session{CurrentUser} );
            $queues->UnLimit;

            while ( my $queue = $queues->Next ) {
                push @queues, $queue;
                last if @queues == 100;    # TODO make a config for it
            }
        }

        my %filter;

        if ( $ARGS{BaseQuery} && $ARGS{BaseQuery} ne $ARGS{Query} ) {
            my $query = $ARGS{Query};
            $query =~ s!^\s*\(?\s*\Q$ARGS{BaseQuery}\E\s*\)? AND !!;
            my $tree = RT::Interface::Web::QueryBuilder::Tree->new;
            $tree->ParseSQL( Query => $query, CurrentUser => $session{'CurrentUser'} );
            $tree->traverse(
                sub {
                    my $node = shift;

                    return if $node->isRoot;
                    return unless $node->isLeaf;

                    my $clause = $node->getNodeValue();
                    if ( $clause->{Key} =~ /^Queue/ ) {
                        my $queue = RT::Queue->new( $session{CurrentUser} );
                        $queue->Load( $clause->{Value} );
                        if ( $queue->id ) {
                            $filter{ $clause->{Key} }{ $queue->id } = 1;
                        }
                    }
                    elsif ( $clause->{Key} =~ /^(?:Status|SLA|Type)/ ) {
                        $filter{ $clause->{Key} }{ $clause->{Value} } = 1;
                    }
                    elsif ( $clause->{Key}
                        =~ /^(?:(?:Initial|Final)?Priority|Time(?:Worked|Estimated|Left)|id|Told|Starts|Started|Due|Resolved|Created|LastUpdated\b)/
                        )
                    {
                        $filter{ $clause->{Key} }{ $clause->{Op} } = $clause->{Value};
                    }
                    else {
                        my $value = $clause->{Value};
                        $value =~ s!\\([\\"])!$1!g;
                        my $key = $clause->{Key};
                        my $cf;
                        if ( $key eq 'CustomField' ) {
                            $key .= ".$clause->{Subkey}";
                            my ($cf_name) = $clause->{Subkey} =~ /{(.+)}/;
                            $cf = RT::CustomField->new( RT->SystemUser );
                            $cf->Load($cf_name);
                        }
                        elsif ( $key eq 'CustomRole' ) {
                            $key .= ".$1" if $clause->{Subkey} =~ /(\{.+?\})/;
                        }
                        if ( $cf && $cf->id && $cf->Type eq 'Select' ) {
                            $filter{$key}{$value} = 1;
                        }
                        else {
                            $filter{$key} = $value;
                        }
                    }
                }
            );
            $filter{Requestors} = $filter{Requestor} if $filter{Requestor};
        }
        %filter_data = ( status => \%status, queues => \@queues, filter => \%filter );
    }
    elsif ( $Class eq 'RT::Assets' ) {
        my $assets = RT::Assets->new( $session{CurrentUser} );
        my ($ok) = $assets->FromSQL( $ARGS{Query} );
        return unless $ok && ( $ARGS{BaseQuery} || $assets->Count );

        my @catalogs;

        my $tree = RT::Interface::Web::QueryBuilder::Tree->new;
        $tree->ParseSQL(
            Query       => $ARGS{BaseQuery} || $ARGS{Query},
            CurrentUser => $session{'CurrentUser'},
            Class       => 'RT::Assets',
        );
        my $referenced_catalogs = $tree->GetReferencedCatalogs;
        for my $name_or_id ( keys %$referenced_catalogs ) {
            my $catalog = RT::Catalog->new( $session{CurrentUser} );
            $catalog->Load($name_or_id);
            if ( $catalog->id ) {
                push @catalogs, $catalog;
            }
        }

        my %status;
        my @lifecycles;

        if (@catalogs) {
            my %lifecycle;
            for my $catalog (@catalogs) {
                next if $lifecycle{ $catalog->Lifecycle }++;
                push @lifecycles, $catalog->LifecycleObj;
            }
        }
        else {
            @lifecycles = map { RT::Lifecycle->Load( Type => 'asset', Name => $_ ) } RT::Lifecycle->List('asset');
        }

        for my $lifecycle (@lifecycles) {
            $status{$_} = 1 for $lifecycle->Valid;
        }
        delete $status{deleted};

        if ( !@catalogs ) {
            my $catalogs = RT::Catalogs->new( $session{CurrentUser} );
            $catalogs->UnLimit;

            while ( my $catalog = $catalogs->Next ) {
                push @catalogs, $catalog;
                last if @catalogs == 100;    # TODO make a config for it
            }
        }

        my %filter;

        if ( $ARGS{BaseQuery} && $ARGS{BaseQuery} ne $ARGS{Query} ) {
            my $query = $ARGS{Query};
            $query =~ s!^\s*\(?\s*\Q$ARGS{BaseQuery}\E\s*\)? AND !!;
            my $tree = RT::Interface::Web::QueryBuilder::Tree->new;
            $tree->ParseSQL( Query => $query, CurrentUser => $session{'CurrentUser'}, Class => 'RT::Assets' );
            $tree->traverse(
                sub {
                    my $node = shift;

                    return if $node->isRoot;
                    return unless $node->isLeaf;

                    my $clause = $node->getNodeValue();
                    if ( $clause->{Key} =~ /^Catalog/ ) {
                        my $catalog = RT::Catalog->new( $session{CurrentUser} );
                        $catalog->Load( $clause->{Value} );
                        if ( $catalog->id ) {
                            $filter{ $clause->{Key} }{ $catalog->id } = 1;
                        }
                    }
                    elsif ( $clause->{Key} eq 'Status' ) {
                        $filter{ $clause->{Key} }{ $clause->{Value} } = 1;
                    }
                    elsif ( $clause->{Key} =~ /^(?:id|Created|LastUpdated\b)/ ) {
                        $filter{ $clause->{Key} }{ $clause->{Op} } = $clause->{Value};
                    }
                    else {
                        my $value = $clause->{Value};
                        $value =~ s!\\([\\"])!$1!g;
                        my $key = $clause->{Key};
                        my $cf;
                        if ( $key eq 'CustomField' ) {
                            $key .= ".$clause->{Subkey}";
                            my ($cf_name) = $clause->{Subkey} =~ /{(.+)}/;
                            $cf = RT::CustomField->new( RT->SystemUser );
                            $cf->Load($cf_name);
                        }
                        elsif ( $key eq 'CustomRole' ) {
                            $key .= ".$1" if $clause->{Subkey} =~ /(\{.+?\})/;
                        }
                        if ( $cf && $cf->id && $cf->Type eq 'Select' ) {
                            $filter{$key}{$value} = 1;
                        }
                        else {
                            $filter{$key} = $value;
                        }
                    }
                }
            );
            $filter{Contacts} = $filter{Contact} if $filter{Contact};
        }
        %filter_data = ( status => \%status, catalogs => \@catalogs, filter => \%filter );
    }
}
</%INIT>
