1 %# BEGIN BPS TAGGED BLOCK {{{
5 %# This software is Copyright (c) 1996-2015 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 <table width="100%" border="0">
54 <td valign="top" width="50%">
55 % if (not $classes_configured) {
56 <& /Articles/Elements/NeedsSetup &>
57 % } elsif (not @classes) {
58 <i><&|/l&>You don't have permission to view Articles in any Class</&></i>
61 % for my $class (@classes) {
62 <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>
67 <td valign="top" width="50%">
68 <form action="Search.html" method="get">
69 <& /Widgets/TitleBoxStart, title => loc('Saved searches') &>
70 <&|/l&>Load saved search:</&><br />
71 <& Elements/SelectSavedSearches, Name => 'LoadSavedSearch', Default => $CurrentSearch &>
72 <input value="<%loc('Load')%>" name="Load" type="submit" />
73 <& /Widgets/TitleBoxEnd &>
81 <& /Elements/ListActions, actions => \@results &>
82 <div style="float:right;margin-top:15px"><a href="#criteria"><&|/l&>Modify this search...</&></a></div>
84 % if ($articles->BuildSelectCountQuery =~ /WHERE/i) {
85 <h2><&|/l&>Search results</&></h2>
86 <& /Elements/CollectionList,
87 Collection => $articles,
92 GenericQueryArgs => { %filtered, Format => $format, },
99 <a name="criteria"></a>
100 <form action="Search.html" method="get">
101 <& Elements/ShowSearchCriteria, dates => \%dates, RefersTo => $RefersTo, customfields => $customfields, ReferredToBy => $ReferredToBy, %ARGS &>
104 <& Elements/ShowSavedSearches, CurrentSearch => $CurrentSearch,
105 Name => ($search ? $search->Name : undef),
106 Privacy => ($search ? $search->Privacy : undef) &>
111 <a href="<%RT->Config->Get('WebPath')%>/Articles/Article/Search.html<%$QueryString%>"><&|/l&>Bookmarkable link for this search</&></a><br />
114 my $Classes = RT::Classes->new($session{'CurrentUser'});
115 $Classes->LimitToEnabled();
117 # This is a COUNT(), which doesn't apply ACLs; as such, we don't display
118 # the warning if there are classes, but the user can't see them.
119 my $classes_configured = $Classes->Count;
120 my @classes = @{ $Classes->ItemsArrayRef };
121 $ARGS{Class} = $classes[0]->id if @classes == 1;
125 my $articles = RT::Articles->new( $session{'CurrentUser'} );
127 '<a href="__WebPath__/Articles/Article/Display.html?id=__id__">__id__</a>/TITLE:#',
128 '<a href="__WebPath__/Articles/Article/Display.html?id=__id__">__Name__</a>/TITLE:Name',
130 '__CreatedRelative__',
131 '__LastUpdatedRelative__',
135 # {{{ Quicksearch logic
137 # If it is a number, load the article with that ID. Otherwise, search
138 # on name and summary.
139 if ($ARGS{'q'} && $ARGS{'q'} =~ /^(\d+)$/) {
140 return $m->comp("/Articles/Article/Display.html", id => $1);
144 # {{{ Saved search logic
148 # The keys in %ARGS that are not saved and loaded with named searches.
149 # These need to be treated specially.
150 my @metakeys = qw/NewSearchName CurrentSearch SearchPrivacy Save Load
153 if ($CurrentSearch =~ /^(.*-\d+)-SavedSearch-(\d+)$/) {
154 $search = RT::SavedSearch->new($session{'CurrentUser'});
155 $search->Load($1, $2);
158 # Have we been asked to load a search?
161 if ($ARGS{'LoadSavedSearch'} =~ /^(.*-\d+)-SavedSearch-(\d+)$/ ) {
165 $search = RT::SavedSearch->new($session{'CurrentUser'});
166 my ($ret, $msg) = $search->Load($privacy, $search_id);
168 my $searchargs = $search->GetParameter('args');
169 # Clean out ARGS and fill it in with the saved args from the
171 foreach my $key (@metakeys) {
172 $searchargs->{$key} = $ARGS{$key};
174 %ARGS = %{$searchargs};
175 $CurrentSearch = "$privacy-SavedSearch-$search_id";
177 push(@results, loc("Error: could not load saved search [_1]: [_2]",
178 $ARGS{'LoadSavedSearch'}, $msg));
181 push(@results, loc("Invalid [_1] argument", 'LoadSavedSearch'));
185 # ...or have we been asked to save, update, or delete a search?
188 my %searchargs = %ARGS;
189 foreach my $key (@metakeys) {
190 delete $searchargs{$key};
193 $search = RT::SavedSearch->new($session{'CurrentUser'});
194 unless ($ARGS{'SearchPrivacy'} =~ /^(.*)-(\d+)$/) {
195 # This shouldn't really happen, but hey.
196 push(@results, loc("WARNING: Saving search to user-level privacy"));
197 $ARGS{'SearchPrivacy'} = 'RT::User-'.$session{'CurrentUser'}->Id;
199 my ($ret, $msg) = $search->Save(Privacy => $ARGS{'SearchPrivacy'},
201 Name => $ARGS{'NewSearchName'},
202 SearchParams => {'args' => \%searchargs});
204 $CurrentSearch = $ARGS{'SearchPrivacy'} . "-SavedSearch-" .
206 push(@results, loc("Created search [_1]", $search->Name));
208 undef $search; # if we bomb out creating a search
209 # we don't want to have the empty object hang around
210 push(@results, loc("Could not create search: [_1]", $msg));
212 } elsif ($ARGS{'Update'}) {
213 if ($ARGS{'SearchPrivacy'} != $search->Privacy) {
215 loc("Error: cannot change privacy value of existing search"));
217 my %searchargs = %ARGS;
218 foreach my $key (@metakeys) {
219 delete $searchargs{$key};
222 # We already have a search loaded, because CurrentSearch is set,
223 # or else we would not have gotten here.
224 my ($ret, $msg) = $search->Update(Name => $ARGS{'NewSearchName'},
225 SearchParams => \%searchargs);
227 push(@results, loc("Search [_1] updated", $search->Name));
229 push(@results, loc("Error: search [_1] not updated: [_2]",
230 $search->Name, $msg));
233 } elsif ($ARGS{'Delete'}) {
234 # Keep track of this, as we are about to delete the search.
235 my $searchname = $search->Name;
236 my ($ret, $msg) = $search->Delete;
238 $ARGS{'CurrentSearch'} = undef;
239 push(@results, loc("Deleted search [_1]", $searchname));
240 # Get rid of all the state.
241 foreach my $key (keys %ARGS) {
244 $CurrentSearch = 'new';
247 $ReferredToBy = undef;
249 push(@results, loc("Could not delete search [_1]: [_2]",
256 # Don't want to search for a null class when there is no class specced
257 my $customfields = RT::CustomFields->new( $session{'CurrentUser'} );
260 $articles->Search( %ARGS, CustomFields => $customfields, Dates => \%dates,
261 OrderBy => \@OrderBy, Order => \@Order, );
263 $m->callback( %ARGS, _Search => $articles );
265 my %filtered = %ARGS;
266 delete $filtered{$_} for (@metakeys, "EditTopics", "ExpandTopics");
267 delete $filtered{$_} for grep {$filtered{$_} !~ /\S/} keys %filtered;
268 @filtered{qw(OrderBy Order)} = (\@OrderBy, \@Order);
269 my $QueryString = "?".$m->comp('/Elements/QueryString', %filtered);
275 $LastUpdatedBefore => ''
276 $LastUpdatedAfter => ''
278 $ReferredToBy => undef
279 $CurrentSearch => 'new'