%# 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 }}} <& /Elements/Header, Title => $title, Refresh => $refresh, LinkRel => \%link_rel &> <& /Elements/Tabs &> <& /Elements/ListActions &> % my $DisplayFormat; % $m->callback( ARGSRef => \%ARGS, Format => \$Format, DisplayFormat => \$DisplayFormat, Count => $count, CallbackName => 'BeforeResults' ); % unless ($ok) { % $msg =~ s{ at .*? line .*}{}s; <&| /Widgets/TitleBox, title => loc("Error"), class => "error-titlebox" &> <&|/l_unsafe, "".$m->interp->apply_escapes($msg, "h")."" &>There was an error parsing your search query: [_1]. Your RT admin can find more information in the error logs. % } else { <& /Elements/CollectionList, Query => $Query, Collection => $session{$session_name}, TotalFound => $count, AllowSorting => 1, AllowFiltering => 1, OrderBy => $OrderBy, Order => $Order, Rows => $Rows, Page => $Page, Format => $Format, DisplayFormat => $DisplayFormat, # in case we set it in callbacks Class => $Class, BaseURL => $BaseURL, SavedSearchId => $ARGS{'SavedSearchId'}, SavedChartSearchId => $ARGS{'SavedChartSearchId'}, ObjectType => $ObjectType, BaseQuery => $BaseQuery || $Query, @ExtraQueryParams ? ( map { $_ => $ARGS{$_} } grep { defined $ARGS{$_} } 'ExtraQueryParams', @ExtraQueryParams ) : (), PassArguments => [qw(sc BaseQuery Query Format Rows Page Order OrderBy SavedSearchId SavedChartSearchId Class ObjectType ExtraQueryParams), @ExtraQueryParams], ShowJumpToPage => 1, sc => $ARGS{sc}, &> % } % $m->callback( ARGSRef => \%ARGS, Count => $count, CallbackName => 'AfterResults' ); % my %hiddens = ( sc => $ARGS{sc}, BaseQuery => $BaseQuery || $Query, Query => $Query, Format => $Format, Class => $Class, ObjectType => $ObjectType, Rows => $Rows, OrderBy => $OrderBy, Order => $Order, HideResults => $HideResults, Page => $Page, SavedChartSearchId => $SavedChartSearchId );
% foreach my $key (keys(%hiddens)) { % } % for my $input ( @ExtraQueryParams ) { % if ( defined $ARGS{$input} ) { % } % }
<& /Elements/Refresh, Name => 'SearchResultsRefreshInterval', Default => $session{$interval_name}||RT->Config->Get('SearchResultsRefreshInterval', $session{'CurrentUser'}) &>
%# Keyboard shortcuts info
<%INIT> # Some valid query criteria can be simplified down to an empty # string by the time this component is called, and in many # contexts (e.g. SQL) an empty criteria set is shorthand for # "give me everything", so default in a non-empty query that # will give the expected results in both of those cases. # This is done before the callback so that it sees the same # query that we're going to operate on. $Query ||= 'id > 0'; $m->callback( ARGSRef => \%ARGS, Query => \$Query, Format => \$Format, Rows => \$Rows, OrderBy => \$OrderBy, Order => \$Order, Page => \$Page, HideResults => \$HideResults, ExtraQueryParams => \@ExtraQueryParams, CallbackName => 'Initial', ); # Read from user preferences my $prefs = $session{'CurrentUser'}->UserObj->Preferences("SearchDisplay") || {}; # These variables are what define a search_hash; this is also # where we give sane defaults. if ( $Class eq 'RT::Transactions' ) { $Format ||= RT->Config->Get('TransactionDefaultSearchResultFormat')->{$ObjectType}; $Order ||= RT->Config->Get('TransactionDefaultSearchResultOrder')->{$ObjectType}; $OrderBy ||= RT->Config->Get('TransactionDefaultSearchResultOrderBy')->{$ObjectType}; } elsif ( $Class eq 'RT::Assets' ) { $Format ||= RT->Config->Get('AssetDefaultSearchResultFormat'); $Order ||= RT->Config->Get('AssetDefaultSearchResultOrder'); $OrderBy ||= RT->Config->Get('AssetDefaultSearchResultOrderBy'); } else { $Format ||= $prefs->{'Format'} || RT->Config->Get('DefaultSearchResultFormat'); $Order ||= $prefs->{'Order'} || RT->Config->Get('DefaultSearchResultOrder'); $OrderBy ||= $prefs->{'OrderBy'} || RT->Config->Get('DefaultSearchResultOrderBy'); } # Some forms pass in "RowsPerPage" rather than "Rows" # We call it RowsPerPage everywhere else. $Rows //= $ARGS{'RowsPerPage'} // $prefs->{'RowsPerPage'} // RT->Config->Get('DefaultSearchResultRowsPerPage') // 50; $Page = 1 unless $Page && $Page =~ /\A\d+\z/ && $Page > 1; my $hash_name = join '-', 'CurrentSearchHash', $Class, $ObjectType || (); my $session_name = join '-', 'collection', $Class, $ObjectType || (); $session{'i'}++; $session{$session_name} = $Class->new($session{'CurrentUser'}) ; my ( $ok, $msg ); if ( $Query ) { if ( $Class eq 'RT::Transactions' ) { $Query = PreprocessTransactionSearchQuery( Query => $Query, ObjectType => $ObjectType ); } ( $ok, $msg ) = $session{$session_name}->FromSQL($Query); } # Provide an empty search if parsing failed $session{$session_name}->FromSQL("id < 0") unless ($ok); if ($OrderBy =~ /\|/) { # Multiple Sorts my @OrderBy = split /\|/,$OrderBy; my @Order = split /\|/,$Order; $session{$session_name}->OrderByCols( map { { FIELD => $OrderBy[$_], ORDER => $Order[$_] } } grep { $OrderBy[$_] } ( 0 .. $#OrderBy ) ); } else { $session{$session_name}->OrderBy(FIELD => $OrderBy, ORDER => $Order); } $session{$session_name}->RowsPerPage( $Rows ) if $Rows; $session{$session_name}->GotoPage( $Page - 1 ); $session{$session_name}->CombineSearchAndCount(1); $session{$hash_name} = { Format => $Format, Query => $Query, Page => $Page, Order => $Order, OrderBy => $OrderBy, RowsPerPage => $Rows, ObjectType => $ObjectType, }; my $count = $session{$session_name}->Query() ? $session{$session_name}->CountAll() : 0; my $title; if ( $Class eq 'RT::Transactions' ) { if ( $session{$session_name}->Query() ) { $title = loc( 'Found [quant,_1,transaction,transactions]', $count ); } else { $title = loc("Find transactions"); } } elsif ( $Class eq 'RT::Assets' ) { if ( $session{$session_name}->Query() ) { $title = loc( 'Found [quant,_1,asset,assets]', $count ); } else { $title = loc("Find assets"); } } else { if ( $session{$session_name}->Query() ) { $title = loc( 'Found [quant,_1,ticket,tickets]', $count ); } else { $title = loc("Find tickets"); } } my $QueryString = "?".$m->comp('/Elements/QueryString', Query => $Query, Format => $Format, Rows => $Rows, OrderBy => $OrderBy, Order => $Order, Page => $Page); my $ShortQueryString = "?".$m->comp('/Elements/QueryString', Query => $Query); my $interval_name = join '_', $Class, $ObjectType || (), 'refresh_interval'; if ($ARGS{'SearchResultsRefreshInterval'}) { $session{$interval_name} = $ARGS{'SearchResultsRefreshInterval'}; } my $refresh = $session{$interval_name} || RT->Config->Get('SearchResultsRefreshInterval', $session{'CurrentUser'} ); # Check $m->request_args, not $DECODED_ARGS, to avoid creating a new CSRF token on each refresh if (RT->Config->Get('RestrictReferrer') and $refresh and not $m->request_args->{CSRF_Token}) { my $token = RT::Interface::Web::StoreRequestToken( $session{$hash_name} ); $m->notes->{RefreshURL} = RT->Config->Get('WebURL') . "Search/Results.html?CSRF_Token=" . $token; } my %link_rel; my $genpage = sub { return QueryString( ShortenSearchQuery( Query => $Query, Format => $Format, Rows => $Rows, OrderBy => $OrderBy, Order => $Order, Page => shift(@_), ) ); }; if ( RT->Config->Get('SearchResultsAutoRedirect') && $count == 1 && $session{$session_name}->First ) { # $count is not always precise unless $UseSQLForACLChecks is set to true, # check $session{$session_name}->First here is to make sure the ticket is there. RT::Interface::Web::Redirect( RT->Config->Get('WebURL') . $Class->RecordClass->RecordType . "/Display.html?id=" . $session{$session_name}->First->id ); } my $BaseURL = RT->Config->Get('WebPath')."/Search/Results.html?"; $link_rel{first} = $BaseURL . $genpage->(1) if $Page > 1; $link_rel{prev} = $BaseURL . $genpage->($Page - 1) if $Page > 1; $link_rel{next} = $BaseURL . $genpage->($Page + 1) if ($Page * $Rows) < $count; $link_rel{last} = $BaseURL . $genpage->(POSIX::ceil($count/$Rows)) if $Rows and ($Page * $Rows) < $count; $m->notes( RefreshURL => '?' . QueryString( ShortenSearchQuery(%ARGS) ) ); <%CLEANUP> $session{$session_name}->PrepForSerialization(); <%ARGS> $Query => undef $BaseQuery => undef $Format => undef $HideResults => 0 $Rows => undef $Page => 1 $OrderBy => undef $Order => undef $SavedSearchId => undef $SavedChartSearchId => undef $Class => 'RT::Tickets' $ObjectType => $Class eq 'RT::Transactions' ? 'RT::Ticket' : '' @ExtraQueryParams => ()