import rt 3.6.6
[freeside.git] / rt / lib / RT / SavedSearch.pm
1 # BEGIN BPS TAGGED BLOCK {{{
2
3 # COPYRIGHT:
4 #  
5 # This software is Copyright (c) 1996-2007 Best Practical Solutions, LLC 
6 #                                          <jesse@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/copyleft/gpl.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 =head1 NAME
49
50   RT::SavedSearch - an API for saving and retrieving search form values.
51
52 =head1 SYNOPSIS
53
54   use RT::SavedSearch
55
56 =head1 DESCRIPTION
57
58   SavedSearch is an object that can belong to either an RT::User or an
59   RT::Group.  It consists of an ID, a description, and a number of
60   search parameters.
61
62 =head1 METHODS
63
64 =begin testing
65
66 use_ok(RT::SavedSearch);
67
68 # Real tests are in lib/t/20savedsearch.t
69
70 =end testing
71
72 =cut
73
74 package RT::SavedSearch;
75
76 use RT::Base;
77 use RT::Attribute;
78
79 use strict;
80 use warnings;
81 use base qw/RT::Base/;
82
83 sub new  {
84     my $proto = shift;
85     my $class = ref($proto) || $proto;
86     my $self  = {};
87     $self->{'Id'} = 0;
88     bless ($self, $class);
89     $self->CurrentUser(@_);
90     return $self;
91 }
92
93 =head2 Load
94
95 Takes a privacy specification, an object ID, and a search ID.  Loads
96 the given search ID if it belongs to the stated user or group.
97 Returns a tuple of status and message, where status is true on
98 success.
99
100 =cut
101
102 sub Load {
103     my $self = shift;
104     my ($privacy, $id) = @_;
105     my $object = $self->_GetObject($privacy);
106
107     if ($object) {
108         $self->{'Attribute'} = $object->Attributes->WithId($id);
109         if ($self->{'Attribute'}->Id) {
110             $self->{'Id'} = $self->{'Attribute'}->Id;
111             $self->{'Privacy'} = $privacy;
112             $self->{'Type'} = $self->{'Attribute'}->SubValue('SearchType');
113             return (1, $self->loc("Loaded search [_1]", $self->Name));
114         } else {
115             $RT::Logger->error("Could not load attribute " . $id
116                                . " for object " . $privacy);
117             return (0, $self->loc("Search attribute load failure"));
118         }
119     } else {
120         $RT::Logger->warning("Could not load object $privacy when loading search");
121         return (0, $self->loc("Could not load object for [_1]", $privacy));
122     }
123
124 }
125
126 =head2 Save
127
128 Takes a privacy, an optional type, a name, and a hashref containing the
129 search parameters.  Saves the given parameters to the appropriate user/
130 group object, and loads the resulting search.  Returns a tuple of status
131 and message, where status is true on success.  Defaults are:
132   Privacy:      undef
133   Type:         Ticket
134   Name:         "new search"
135   SearchParams: (empty hash)
136
137 =cut
138
139 sub Save {
140     my $self = shift;
141     my %args = ('Privacy' => 'RT::User-' . $self->CurrentUser->Id,
142                 'Type' => 'Ticket',
143                 'Name' => 'new search',
144                 'SearchParams' => {},
145                 @_);
146     my $privacy = $args{'Privacy'};
147     my $type = $args{'Type'};
148     my $name = $args{'Name'};
149     my %params = %{$args{'SearchParams'}};
150
151     $params{'SearchType'} = $type;
152     my $object = $self->_GetObject($privacy);
153
154     return (0, $self->loc("Failed to load object for [_1]", $privacy))
155         unless $object;
156
157     if ( $object->isa('RT::System') ) {
158         return ( 0, $self->loc("No permission to save system-wide searches") )
159             unless $self->CurrentUser->HasRight(
160             Object => $RT::System,
161             Right  => 'SuperUser'
162         );
163     }
164
165     my ( $att_id, $att_msg ) = $object->AddAttribute(
166         'Name'        => 'SavedSearch',
167         'Description' => $name,
168         'Content'     => \%params
169     );
170     if ($att_id) {
171         $self->{'Attribute'} = $object->Attributes->WithId($att_id);
172         $self->{'Id'}        = $att_id;
173         $self->{'Privacy'}   = $privacy;
174         $self->{'Type'}      = $type;
175         return ( 1, $self->loc( "Saved search [_1]", $name ) );
176     }
177     else {
178         $RT::Logger->error("SavedSearch save failure: $att_msg");
179         return ( 0, $self->loc("Failed to create search attribute") );
180     }
181 }
182
183 =head2 Update
184
185 Updates the parameters of an existing search.  Takes the arguments
186 "Name" and "SearchParams"; SearchParams should be a hashref containing
187 the new parameters of the search.  If Name is not specified, the name
188 will not be changed.
189
190 =cut
191
192 sub Update {
193     my $self = shift;
194     my %args = ('Name' => '',
195                 'SearchParams' => {},
196                 @_);
197     
198     return(0, $self->loc("No search loaded")) unless $self->Id;
199     return(0, $self->loc("Could not load search attribute"))
200         unless $self->{'Attribute'}->Id;
201     my ($status, $msg) = $self->{'Attribute'}->SetSubValues(%{$args{'SearchParams'}});
202     if ($status && $args{'Name'}) {
203         ($status, $msg) = $self->{'Attribute'}->SetDescription($args{'Name'});
204     }
205     return ($status, $self->loc("Search update: [_1]", $msg));
206 }
207
208 =head2 Delete
209     
210 Deletes the existing search.  Returns a tuple of status and message,
211 where status is true upon success.
212
213 =cut
214
215 sub Delete {
216     my $self = shift;
217
218     my ($status, $msg) = $self->{'Attribute'}->Delete;
219     if ($status) {
220         return (1, $self->loc("Deleted search"));
221     } else {
222         return (0, $self->loc("Delete failed: [_1]", $msg));
223     }
224 }
225         
226
227 ### Accessor methods
228
229 =head2 Name
230
231 Returns the name of the search.
232
233 =cut
234
235 sub Name {
236     my $self = shift;
237     return unless ref($self->{'Attribute'}) eq 'RT::Attribute';
238     return $self->{'Attribute'}->Description();
239 }
240
241 =head2 GetParameter
242
243 Returns the given named parameter of the search, e.g. 'Query', 'Format'.
244
245 =cut
246
247 sub GetParameter {
248     my $self = shift;
249     my $param = shift;
250     return unless ref($self->{'Attribute'}) eq 'RT::Attribute';
251     return $self->{'Attribute'}->SubValue($param);
252 }
253
254 =head2 Id
255
256 Returns the numerical id of this search.
257
258 =cut
259
260 sub Id {
261      my $self = shift;
262      return $self->{'Id'};
263 }
264
265 =head2 Privacy
266
267 Returns the principal object to whom this search belongs, in a string
268 "<class>-<id>", e.g. "RT::Group-16".
269
270 =cut
271
272 sub Privacy {
273     my $self = shift;
274     return $self->{'Privacy'};
275 }
276
277 =head2 Type
278
279 Returns the type of this search, e.g. 'Ticket'.  Useful for denoting the
280 saved searches that are relevant to a particular search page.
281
282 =cut
283
284 sub Type {
285     my $self = shift;
286     return $self->{'Type'};
287 }
288
289 ### Internal methods
290
291 sub _load_privacy_object {
292     my ($self, $obj_type, $obj_id) = @_;
293     if ( $obj_type eq 'RT::User' && $obj_id == $self->CurrentUser->Id)  {
294         return $self->CurrentUser->UserObj;
295     }
296     elsif ($obj_type eq 'RT::Group') {
297         my $group = RT::Group->new($self->CurrentUser);
298         $group->Load($obj_id);
299         return $group;
300     }
301     elsif ($obj_type eq 'RT::System') {
302         return RT::System->new($self->CurrentUser);
303     }
304
305     $RT::Logger->error("Tried to load a search belonging to an $obj_type, which is neither a user nor a group");
306     return undef;
307 }
308
309 # _GetObject: helper routine to load the correct object whose parameters
310 #  have been passed.
311
312 sub _GetObject {
313     my $self = shift;
314     my $privacy = shift;
315
316     my ($obj_type, $obj_id) = split(/\-/, $privacy);
317
318     my $object = $self->_load_privacy_object($obj_type, $obj_id);
319
320     unless (ref($object) eq $obj_type) {
321         $RT::Logger->error("Could not load object of type $obj_type with ID $obj_id");
322         return undef;
323     }
324
325     # Do not allow the loading of a user object other than the current
326     # user, or of a group object of which the current user is not a member.
327
328     if ($obj_type eq 'RT::User' 
329         && $object->Id != $self->CurrentUser->UserObj->Id()) {
330         $RT::Logger->debug("Permission denied for user other than self");
331         return undef;
332     }
333     if ($obj_type eq 'RT::Group' &&
334         !$object->HasMemberRecursively($self->CurrentUser->PrincipalObj)) {
335         $RT::Logger->debug("Permission denied, ".$self->CurrentUser->Name.
336                            " is not a member of group");
337         return undef;
338     }
339
340     return $object;
341 }
342
343 eval "require RT::SavedSearch_Vendor";
344 die $@ if ($@ && $@ !~ qr{^Can't locate RT/SavedSearch_Vendor.pm});
345 eval "require RT::SavedSearch_Local";
346 die $@ if ($@ && $@ !~ qr{^Can't locate RT/SavedSearch_Local.pm});
347
348 1;