]>
Commit | Line | Data |
---|---|---|
84fb5b46 MKG |
1 | %# BEGIN BPS TAGGED BLOCK {{{ |
2 | %# | |
3 | %# COPYRIGHT: | |
4 | %# | |
3ffc5f4f | 5 | %# This software is Copyright (c) 1996-2014 Best Practical Solutions, LLC |
84fb5b46 MKG |
6 | %# <sales@bestpractical.com> |
7 | %# | |
8 | %# (Except where explicitly superseded by other copyright notices) | |
9 | %# | |
10 | %# | |
11 | %# LICENSE: | |
12 | %# | |
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 | |
16 | %# from www.gnu.org. | |
17 | %# | |
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. | |
22 | %# | |
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. | |
28 | %# | |
29 | %# | |
30 | %# CONTRIBUTION SUBMISSION POLICY: | |
31 | %# | |
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.) | |
37 | %# | |
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. | |
46 | %# | |
47 | %# END BPS TAGGED BLOCK }}} | |
48 | <& /Elements/Header, Title => loc("Search for articles") &> | |
49 | <& /Elements/Tabs &> | |
50 | ||
51 | % unless ( keys %ARGS ) { | |
52 | % my $Classes = RT::Classes->new($session{'CurrentUser'}); | |
53 | % $Classes->LimitToEnabled(); | |
54 | <table width="100%" border="0"> | |
55 | <tr> | |
56 | <td valign="top" width="50%"> | |
3ffc5f4f MKG |
57 | <%perl> |
58 | if (not $Classes->First) { | |
59 | $m->comp("/Articles/Elements/NeedsSetup"); | |
60 | } else { | |
61 | $Classes->GotoFirstItem; # Reset for loop below | |
62 | } | |
63 | </%perl> | |
84fb5b46 MKG |
64 | <ul> |
65 | % while (my $class = $Classes->Next) { | |
66 | <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 | % } | |
68 | </ul> | |
69 | </td> | |
70 | <td valign="top" width="50%"> | |
71 | <form action="Search.html" method="get"> | |
3ffc5f4f | 72 | <& /Widgets/TitleBoxStart, title => loc('Saved searches') &> |
84fb5b46 MKG |
73 | <&|/l&>Load saved search:</&><br /> |
74 | <& Elements/SelectSavedSearches, Name => 'LoadSavedSearch', Default => $CurrentSearch &> | |
75 | <input value="<%loc('Load')%>" name="Load" type="submit" /> | |
3ffc5f4f | 76 | <& /Widgets/TitleBoxEnd &> |
84fb5b46 MKG |
77 | </form> |
78 | </td> | |
79 | </tr> | |
80 | </table> | |
81 | % return; | |
82 | % } | |
83 | ||
84 | <& /Elements/ListActions, actions => \@results &> | |
403d7b0b | 85 | <div style="float:right;margin-top:15px"><a href="#criteria"><&|/l&>Modify this search...</&></a></div> |
84fb5b46 MKG |
86 | |
87 | % if ($articles->BuildSelectCountQuery =~ /WHERE/i) { | |
88 | <h2><&|/l&>Search results</&></h2> | |
89 | <& /Elements/CollectionList, | |
90 | Collection => $articles, | |
91 | AllowSorting => 1, | |
92 | OrderBy => \@OrderBy, | |
93 | Order => \@Order, | |
94 | Format => $format, | |
95 | GenericQueryArgs => { %filtered, Format => $format, }, | |
96 | &> | |
97 | % } | |
98 | ||
99 | <br /> | |
100 | <br /> | |
101 | <br /> | |
102 | <a name="criteria"></a> | |
103 | <form action="Search.html" method="get"> | |
104 | <& Elements/ShowSearchCriteria, dates => \%dates, RefersTo => $RefersTo, customfields => $customfields, ReferredToBy => $ReferredToBy, %ARGS &> | |
105 | <br /> | |
106 | <br /> | |
107 | <& Elements/ShowSavedSearches, CurrentSearch => $CurrentSearch, | |
108 | Name => ($search ? $search->Name : undef), | |
109 | Privacy => ($search ? $search->Privacy : undef) &> | |
110 | ||
111 | </form> | |
112 | ||
113 | <div align=right> | |
114 | <a href="<%RT->Config->Get('WebPath')%>/Articles/Article/Search.html<%$QueryString%>"><&|/l&>Bookmarkable link for this search</&></a><br /> | |
115 | </div> | |
116 | <%init> | |
117 | use RT::SavedSearch; | |
118 | my @results; | |
119 | my $articles = RT::Articles->new( $session{'CurrentUser'} ); | |
120 | my $format = q{ | |
121 | '<a href="__WebPath__/Articles/Article/Display.html?id=__id__">__id__</a>/TITLE:#', | |
122 | '<a href="__WebPath__/Articles/Article/Display.html?id=__id__">__Name__</a>/TITLE:Name', | |
123 | '__ClassName__', | |
124 | '__CreatedRelative__', | |
125 | '__LastUpdatedRelative__', | |
126 | '__Summary__', | |
127 | '__Topics__', }; | |
128 | ||
129 | # {{{ Quicksearch logic | |
130 | ||
131 | # If it is a number, load the article with that ID. Otherwise, search | |
132 | # on name and summary. | |
133 | if ($ARGS{'q'} && $ARGS{'q'} =~ /^(\d+)$/) { | |
134 | return $m->comp("/Articles/Article/Display.html", id => $1); | |
135 | } | |
136 | # }}} | |
137 | ||
138 | # {{{ Saved search logic | |
139 | ||
140 | my $search; | |
141 | ||
142 | # The keys in %ARGS that are not saved and loaded with named searches. | |
143 | # These need to be treated specially. | |
144 | my @metakeys = qw/NewSearchName CurrentSearch SearchPrivacy Save Load | |
145 | Update Delete/; | |
146 | ||
147 | if ($CurrentSearch =~ /^(.*-\d+)-SavedSearch-(\d+)$/) { | |
148 | $search = RT::SavedSearch->new($session{'CurrentUser'}); | |
149 | $search->Load($1, $2); | |
150 | } | |
151 | ||
152 | # Have we been asked to load a search? | |
153 | ||
154 | if ($ARGS{'Load'}) { | |
155 | if ($ARGS{'LoadSavedSearch'} =~ /^(.*-\d+)-SavedSearch-(\d+)$/ ) { | |
3ffc5f4f MKG |
156 | my $privacy = $1; |
157 | my $search_id = $2; | |
158 | ||
159 | $search = RT::SavedSearch->new($session{'CurrentUser'}); | |
160 | my ($ret, $msg) = $search->Load($privacy, $search_id); | |
161 | if ($ret) { | |
162 | my $searchargs = $search->GetParameter('args'); | |
163 | # Clean out ARGS and fill it in with the saved args from the | |
164 | # loaded search. | |
165 | foreach my $key (@metakeys) { | |
166 | $searchargs->{$key} = $ARGS{$key}; | |
167 | } | |
168 | %ARGS = %{$searchargs}; | |
169 | $CurrentSearch = "$privacy-SavedSearch-$search_id"; | |
170 | } else { | |
171 | push(@results, loc("Error: could not load saved search [_1]: [_2]", | |
172 | $ARGS{'LoadSavedSearch'}, $msg)); | |
173 | } | |
84fb5b46 | 174 | } else { |
3ffc5f4f | 175 | push(@results, loc("Invalid [_1] argument", 'LoadSavedSearch')); |
84fb5b46 MKG |
176 | } |
177 | } | |
178 | ||
179 | # ...or have we been asked to save, update, or delete a search? | |
180 | ||
181 | if ($ARGS{'Save'}) { | |
182 | my %searchargs = %ARGS; | |
183 | foreach my $key (@metakeys) { | |
3ffc5f4f | 184 | delete $searchargs{$key}; |
84fb5b46 MKG |
185 | } |
186 | ||
187 | $search = RT::SavedSearch->new($session{'CurrentUser'}); | |
188 | unless ($ARGS{'SearchPrivacy'} =~ /^(.*)-(\d+)$/) { | |
3ffc5f4f MKG |
189 | # This shouldn't really happen, but hey. |
190 | push(@results, loc("WARNING: Saving search to user-level privacy")); | |
191 | $ARGS{'SearchPrivacy'} = 'RT::User-'.$session{'CurrentUser'}->Id; | |
84fb5b46 | 192 | } |
3ffc5f4f MKG |
193 | my ($ret, $msg) = $search->Save(Privacy => $ARGS{'SearchPrivacy'}, |
194 | Type => 'Article', | |
195 | Name => $ARGS{'NewSearchName'}, | |
196 | SearchParams => {'args' => \%searchargs}); | |
84fb5b46 | 197 | if ($ret) { |
3ffc5f4f MKG |
198 | $CurrentSearch = $ARGS{'SearchPrivacy'} . "-SavedSearch-" . |
199 | $search->Id; | |
200 | push(@results, loc("Created search [_1]", $search->Name)); | |
84fb5b46 MKG |
201 | } else { |
202 | undef $search; # if we bomb out creating a search | |
203 | # we don't want to have the empty object hang around | |
3ffc5f4f | 204 | push(@results, loc("Could not create search: [_1]", $msg)); |
84fb5b46 MKG |
205 | } |
206 | } elsif ($ARGS{'Update'}) { | |
207 | if ($ARGS{'SearchPrivacy'} != $search->Privacy) { | |
3ffc5f4f MKG |
208 | push(@results, |
209 | loc("Error: cannot change privacy value of existing search")); | |
84fb5b46 | 210 | } else { |
3ffc5f4f MKG |
211 | my %searchargs = %ARGS; |
212 | foreach my $key (@metakeys) { | |
213 | delete $searchargs{$key}; | |
214 | } | |
215 | ||
216 | # We already have a search loaded, because CurrentSearch is set, | |
217 | # or else we would not have gotten here. | |
218 | my ($ret, $msg) = $search->Update(Name => $ARGS{'NewSearchName'}, | |
219 | SearchParams => \%searchargs); | |
220 | if ($ret) { | |
221 | push(@results, loc("Search [_1] updated", $search->Name)); | |
222 | } else { | |
223 | push(@results, loc("Error: search [_1] not updated: [_2]", | |
224 | $search->Name, $msg)); | |
225 | } | |
84fb5b46 MKG |
226 | } |
227 | } elsif ($ARGS{'Delete'}) { | |
228 | # Keep track of this, as we are about to delete the search. | |
229 | my $searchname = $search->Name; | |
230 | my ($ret, $msg) = $search->Delete; | |
231 | if ($ret) { | |
3ffc5f4f MKG |
232 | $ARGS{'CurrentSearch'} = undef; |
233 | push(@results, loc("Deleted search [_1]", $searchname)); | |
234 | # Get rid of all the state. | |
235 | foreach my $key (keys %ARGS) { | |
236 | delete $ARGS{$key}; | |
237 | } | |
238 | $CurrentSearch = 'new'; | |
239 | $search = undef; | |
240 | $RefersTo = undef; | |
241 | $ReferredToBy = undef; | |
84fb5b46 | 242 | } else { |
3ffc5f4f MKG |
243 | push(@results, loc("Could not delete search [_1]: [_2]", |
244 | $searchname, $msg)); | |
84fb5b46 MKG |
245 | } |
246 | } | |
247 | ||
248 | # }}} | |
249 | ||
250 | # Don't want to search for a null class when there is no class specced | |
251 | my $customfields = RT::CustomFields->new( $session{'CurrentUser'} ); | |
252 | my %dates; | |
253 | ||
254 | $articles->Search( %ARGS, CustomFields => $customfields, Dates => \%dates, | |
255 | OrderBy => \@OrderBy, Order => \@Order, ); | |
256 | ||
257 | $m->callback( %ARGS, _Search => $articles ); | |
258 | ||
259 | my %filtered = %ARGS; | |
260 | delete $filtered{$_} for (@metakeys, "EditTopics", "ExpandTopics"); | |
261 | delete $filtered{$_} for grep {$filtered{$_} !~ /\S/} keys %filtered; | |
262 | @filtered{qw(OrderBy Order)} = (\@OrderBy, \@Order); | |
263 | my $QueryString = "?".$m->comp('/Elements/QueryString', %filtered); | |
264 | </%init> | |
265 | ||
266 | <%ARGS> | |
267 | $CreatedBefore => '' | |
268 | $CreatedAfter => '' | |
269 | $LastUpdatedBefore => '' | |
270 | $LastUpdatedAfter => '' | |
271 | $RefersTo => undef | |
272 | $ReferredToBy => undef | |
273 | $CurrentSearch => 'new' | |
274 | @OrderBy => () | |
275 | @Order => () | |
276 | </%ARGS> |