1 %# BEGIN BPS TAGGED BLOCK {{{
5 %# This software is Copyright (c) 1996-2014 Best Practical Solutions, LLC
6 %# <sales@bestpractical.com>
8 %# (Except where explicitly superseded by other copyright notices)
13 %# This work is made available to you under the terms of Version 2 of
14 %# the GNU General Public License. A copy of that license should have
15 %# been provided with this software, but in any event can be snarfed
18 %# This work is distributed in the hope that it will be useful, but
19 %# WITHOUT ANY WARRANTY; without even the implied warranty of
20 %# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
21 %# General Public License for more details.
23 %# You should have received a copy of the GNU General Public License
24 %# along with this program; if not, write to the Free Software
25 %# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
26 %# 02110-1301 or visit their web page on the internet at
27 %# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
30 %# CONTRIBUTION SUBMISSION POLICY:
32 %# (The following paragraph is not intended to limit the rights granted
33 %# to you to modify and distribute this software under the terms of
34 %# the GNU General Public License and is only of importance to you if
35 %# you choose to contribute your changes and enhancements to the
36 %# community by submitting them to Best Practical Solutions, LLC.)
38 %# By intentionally submitting any modifications, corrections or
39 %# derivatives to this work, or any other work intended for use with
40 %# Request Tracker, to Best Practical Solutions, LLC, you confirm that
41 %# you are the copyright holder for those contributions and you grant
42 %# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
43 %# royalty-free, perpetual, license to use, copy, create derivative
44 %# works based on those contributions, and sublicense and distribute
45 %# those contributions and any derivatives thereof.
47 %# END BPS TAGGED BLOCK }}}
48 <& /Elements/Header, Title => loc("Search for articles") &>
51 % unless ( keys %ARGS ) {
52 % my $Classes = RT::Classes->new($session{'CurrentUser'});
53 % $Classes->LimitToEnabled();
54 <table width="100%" border="0">
56 <td valign="top" width="50%">
58 % while (my $class = $Classes->Next) {
59 <li><a href="<%RT->Config->Get('WebPath')%>/Articles/Article/Search.html?<% $m->comp('/Elements/QueryString', %filtered, Class => $class->id) %>"><&|/l, $class->Name&>in class [_1]</&></a></li>
63 <td valign="top" width="50%">
64 <form action="Search.html" method="get">
65 <& /Elements/TitleBoxStart, title => loc('Saved searches') &>
66 <&|/l&>Load saved search:</&><br />
67 <& Elements/SelectSavedSearches, Name => 'LoadSavedSearch', Default => $CurrentSearch &>
68 <input value="<%loc('Load')%>" name="Load" type="submit" />
69 <& /Elements/TitleBoxEnd &>
77 <& /Elements/ListActions, actions => \@results &>
78 <div style="float:right;margin-top:15px"><a href="#criteria"><&|/l&>Modify this search...</&></a></div>
80 % if ($articles->BuildSelectCountQuery =~ /WHERE/i) {
81 <h2><&|/l&>Search results</&></h2>
82 <& /Elements/CollectionList,
83 Collection => $articles,
88 GenericQueryArgs => { %filtered, Format => $format, },
95 <a name="criteria"></a>
96 <form action="Search.html" method="get">
97 <& Elements/ShowSearchCriteria, dates => \%dates, RefersTo => $RefersTo, customfields => $customfields, ReferredToBy => $ReferredToBy, %ARGS &>
100 <& Elements/ShowSavedSearches, CurrentSearch => $CurrentSearch,
101 Name => ($search ? $search->Name : undef),
102 Privacy => ($search ? $search->Privacy : undef) &>
107 <a href="<%RT->Config->Get('WebPath')%>/Articles/Article/Search.html<%$QueryString%>"><&|/l&>Bookmarkable link for this search</&></a><br />
112 my $articles = RT::Articles->new( $session{'CurrentUser'} );
114 '<a href="__WebPath__/Articles/Article/Display.html?id=__id__">__id__</a>/TITLE:#',
115 '<a href="__WebPath__/Articles/Article/Display.html?id=__id__">__Name__</a>/TITLE:Name',
117 '__CreatedRelative__',
118 '__LastUpdatedRelative__',
122 # {{{ Quicksearch logic
124 # If it is a number, load the article with that ID. Otherwise, search
125 # on name and summary.
126 if ($ARGS{'q'} && $ARGS{'q'} =~ /^(\d+)$/) {
127 return $m->comp("/Articles/Article/Display.html", id => $1);
131 # {{{ Saved search logic
135 # The keys in %ARGS that are not saved and loaded with named searches.
136 # These need to be treated specially.
137 my @metakeys = qw/NewSearchName CurrentSearch SearchPrivacy Save Load
140 if ($CurrentSearch =~ /^(.*-\d+)-SavedSearch-(\d+)$/) {
141 $search = RT::SavedSearch->new($session{'CurrentUser'});
142 $search->Load($1, $2);
145 # Have we been asked to load a search?
148 if ($ARGS{'LoadSavedSearch'} =~ /^(.*-\d+)-SavedSearch-(\d+)$/ ) {
152 $search = RT::SavedSearch->new($session{'CurrentUser'});
153 my ($ret, $msg) = $search->Load($privacy, $search_id);
155 my $searchargs = $search->GetParameter('args');
156 # Clean out ARGS and fill it in with the saved args from the
158 foreach my $key (@metakeys) {
159 $searchargs->{$key} = $ARGS{$key};
161 %ARGS = %{$searchargs};
162 $CurrentSearch = "$privacy-SavedSearch-$search_id";
164 push(@results, loc("Error: could not load saved search [_1]: [_2]",
165 $ARGS{'LoadSavedSearch'}, $msg));
168 push(@results, loc("Invalid [_1] argument", 'LoadSavedSearch'));
172 # ...or have we been asked to save, update, or delete a search?
175 my %searchargs = %ARGS;
176 foreach my $key (@metakeys) {
177 delete $searchargs{$key};
180 $search = RT::SavedSearch->new($session{'CurrentUser'});
181 unless ($ARGS{'SearchPrivacy'} =~ /^(.*)-(\d+)$/) {
182 # This shouldn't really happen, but hey.
183 push(@results, loc("WARNING: Saving search to user-level privacy"));
184 $ARGS{'SearchPrivacy'} = 'RT::User-'.$session{'CurrentUser'}->Id;
186 my ($ret, $msg) = $search->Save(Privacy => $ARGS{'SearchPrivacy'},
188 Name => $ARGS{'NewSearchName'},
189 SearchParams => {'args' => \%searchargs});
191 $CurrentSearch = $ARGS{'SearchPrivacy'} . "-SavedSearch-" .
193 push(@results, loc("Created search [_1]", $search->Name));
195 undef $search; # if we bomb out creating a search
196 # we don't want to have the empty object hang around
197 push(@results, loc("Could not create search: [_1]", $msg));
199 } elsif ($ARGS{'Update'}) {
200 if ($ARGS{'SearchPrivacy'} != $search->Privacy) {
202 loc("Error: cannot change privacy value of existing search"));
204 my %searchargs = %ARGS;
205 foreach my $key (@metakeys) {
206 delete $searchargs{$key};
209 # We already have a search loaded, because CurrentSearch is set,
210 # or else we would not have gotten here.
211 my ($ret, $msg) = $search->Update(Name => $ARGS{'NewSearchName'},
212 SearchParams => \%searchargs);
214 push(@results, loc("Search [_1] updated", $search->Name));
216 push(@results, loc("Error: search [_1] not updated: [_2]",
217 $search->Name, $msg));
220 } elsif ($ARGS{'Delete'}) {
221 # Keep track of this, as we are about to delete the search.
222 my $searchname = $search->Name;
223 my ($ret, $msg) = $search->Delete;
225 $ARGS{'CurrentSearch'} = undef;
226 push(@results, loc("Deleted search [_1]", $searchname));
227 # Get rid of all the state.
228 foreach my $key (keys %ARGS) {
231 $CurrentSearch = 'new';
234 $ReferredToBy = undef;
236 push(@results, loc("Could not delete search [_1]: [_2]",
243 # Don't want to search for a null class when there is no class specced
244 my $customfields = RT::CustomFields->new( $session{'CurrentUser'} );
247 $articles->Search( %ARGS, CustomFields => $customfields, Dates => \%dates,
248 OrderBy => \@OrderBy, Order => \@Order, );
250 $m->callback( %ARGS, _Search => $articles );
252 my %filtered = %ARGS;
253 delete $filtered{$_} for (@metakeys, "EditTopics", "ExpandTopics");
254 delete $filtered{$_} for grep {$filtered{$_} !~ /\S/} keys %filtered;
255 @filtered{qw(OrderBy Order)} = (\@OrderBy, \@Order);
256 my $QueryString = "?".$m->comp('/Elements/QueryString', %filtered);
262 $LastUpdatedBefore => ''
263 $LastUpdatedAfter => ''
265 $ReferredToBy => undef
266 $CurrentSearch => 'new'