%# 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 => loc("Search for articles") &> <& /Elements/Tabs &> % unless ( keys %ARGS ) {
% if (not $classes_configured) { <& /Articles/Elements/NeedsSetup &> % } elsif (not @classes) { <&|/l&>You don't have permission to view Articles in any Class % } else { <& /Widgets/TitleBoxStart, title => loc('Select Class') &>

% for my $class (@classes) {
- <&|/l, $class->Name&>[_1]
% }

<& /Widgets/TitleBoxEnd &> % }
<& /Widgets/TitleBoxStart, title => loc('Saved searches') &> <&|/l&>Load saved search:
<& Elements/SelectSavedSearches, Name => 'LoadSavedSearch', Default => $CurrentSearch &>
<& /Widgets/TitleBoxEnd &>
% return; % } <& /Elements/ListActions, actions => \@results &> % if ($articles->BuildSelectCountQuery =~ /WHERE/i) { <& /Elements/CollectionList, Collection => $articles, AllowSorting => 1, OrderBy => \@OrderBy, Order => \@Order, Format => $format, GenericQueryArgs => { %filtered, Format => $format, }, &> % }
<& Elements/ShowSearchCriteria, dates => \%dates, RefersTo => $RefersTo, customfields => $customfields, ReferredToBy => $ReferredToBy, %ARGS &>
<& Elements/ShowSavedSearches, CurrentSearch => $CurrentSearch, Name => ($search ? $search->Name : undef), Description => ($search ? $search->Description : undef), Privacy => ($search ? $search->PrincipalId : undef) &>
<&|/l&>Bookmarkable link for this search
<%init> my $Classes = RT::Classes->new($session{'CurrentUser'}); $Classes->LimitToEnabled(); # This is a COUNT(), which doesn't apply ACLs; as such, we don't display # the warning if there are classes, but the user can't see them. my $classes_configured = $Classes->Count; my @classes = @{ $Classes->ItemsArrayRef }; $ARGS{Class} = $classes[0]->id if @classes == 1; use RT::SavedSearch; my @results; my $articles = RT::Articles->new( $session{'CurrentUser'} ); my $format = q{ '__id__/TITLE:#', '__Name__/TITLE:Name', '__ClassName__', '__CreatedRelative__', '__LastUpdatedRelative__', '__Summary__', '__Topics__', }; # {{{ Quicksearch logic # If it is a number, load the article with that ID. Otherwise, search # on name and summary. if ($ARGS{'q'} && $ARGS{'q'} =~ /^(\d+)$/) { RT::Interface::Web::Redirect( RT->Config->Get('WebURL') . "Articles/Article/Display.html?id=" . $1 ); } # }}} # {{{ Saved search logic my $search; # The keys in %ARGS that are not saved and loaded with named searches. # These need to be treated specially. my @metakeys = qw/NewSearchName CurrentSearch SearchPrivacy Save Load Update Delete/; if ($CurrentSearch =~ /^SavedSearch-(\d+)$/) { $search = RT::SavedSearch->new($session{'CurrentUser'}); $search->Load($1); } # Have we been asked to load a search? if ($ARGS{'Load'}) { if ($ARGS{'LoadSavedSearch'} =~ /SavedSearch-(\d+)$/ ) { my $search_id = $1; $search = RT::SavedSearch->new($session{'CurrentUser'}); my ($ret, $msg) = $search->Load($search_id); if ($ret) { my $searchargs = ( $search->Content || {} )->{'args'}; # Clean out ARGS and fill it in with the saved args from the # loaded search. foreach my $key (@metakeys) { $searchargs->{$key} = $ARGS{$key}; } %ARGS = %{$searchargs}; $CurrentSearch = "SavedSearch-$search_id"; } else { push(@results, loc("Error: could not load saved search [_1]: [_2]", $ARGS{'LoadSavedSearch'}, $msg)); } } else { push(@results, loc("Invalid [_1] argument", 'LoadSavedSearch')); } } # ...or have we been asked to save, update, or delete a search? if ($ARGS{'Save'}) { my %searchargs = %ARGS; foreach my $key (@metakeys) { delete $searchargs{$key}; } $search = RT::SavedSearch->new($session{'CurrentUser'}); my ( $ret, $msg ) = $search->Create( PrincipalId => $ARGS{'SearchPrivacy'}, Type => 'Article', Name => $ARGS{'NewSearchName'}, Description => $ARGS{'NewSearchDescription'}, Content => { 'args' => \%searchargs }, ); if ($ret) { $CurrentSearch = "SavedSearch-" . $search->Id; push(@results, loc("Created search [_1]", $search->Name)); } else { undef $search; # if we bomb out creating a search # we don't want to have the empty object hang around push(@results, loc("Could not create search: [_1]", $msg)); } } elsif ($ARGS{'Update'}) { my %searchargs = %ARGS; foreach my $key (@metakeys) { delete $searchargs{$key}; } if ( $search->Name ne $ARGS{'NewSearchName'} ) { my ($ret, $msg) = $search->SetName( $ARGS{'NewSearchName'} );; push @results, $msg unless $ret; } if ( $search->Description ne $ARGS{'NewSearchDescription'} ) { my ($ret, $msg) = $search->SetDescription( $ARGS{'NewSearchDescription'} ); push @results, $msg unless $ret; } my ( $ret, $msg ) = $search->SetContent( { 'args' => \%searchargs } ); # We already have a search loaded, because CurrentSearch is set, # or else we would not have gotten here. if ($ret) { push(@results, loc("Search [_1] updated", $search->Name)); } else { push(@results, loc("Error: search [_1] not updated: [_2]", $search->Name, $msg)); } } elsif ($ARGS{'Delete'}) { # Keep track of this, as we are about to delete the search. my $searchname = $search->Name; my ($ret, $msg) = $search->Delete; if ($ret) { $ARGS{'CurrentSearch'} = undef; push(@results, loc("Deleted search [_1]", $searchname)); # Get rid of all the state. foreach my $key (keys %ARGS) { delete $ARGS{$key}; } $CurrentSearch = 'new'; $search = undef; $RefersTo = undef; $ReferredToBy = undef; } else { push(@results, loc("Could not delete search [_1]: [_2]", $searchname, $msg)); } } # }}} # Don't want to search for a null class when there is no class specced my $customfields = RT::CustomFields->new( $session{'CurrentUser'} ); my %dates; $articles->FindAllRows if $ARGS{FindDisabledArticles}; $articles->Search( %ARGS, CustomFields => $customfields, Dates => \%dates, OrderBy => \@OrderBy, Order => \@Order, ); $m->callback( %ARGS, _Search => $articles ); my %filtered = %ARGS; delete $filtered{$_} for (@metakeys, "EditTopics", "ExpandTopics"); delete $filtered{$_} for grep {$filtered{$_} !~ /\S/} keys %filtered; @filtered{qw(OrderBy Order)} = (\@OrderBy, \@Order); my $QueryString = "?".$m->comp('/Elements/QueryString', %filtered); <%ARGS> $CreatedBefore => '' $CreatedAfter => '' $LastUpdatedBefore => '' $LastUpdatedAfter => '' $RefersTo => undef $ReferredToBy => undef $CurrentSearch => 'new' @OrderBy => () @Order => ()