This commit was generated by cvs2svn to compensate for changes in r4407,
[freeside.git] / rt / lib / RT / SavedSearch.pm
1 # BEGIN BPS TAGGED BLOCK {{{
2
3 # COPYRIGHT:
4 #  
5 # This software is Copyright (c) 1996-2005 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., 675 Mass Ave, Cambridge, MA 02139, USA.
26
27
28 # CONTRIBUTION SUBMISSION POLICY:
29
30 # (The following paragraph is not intended to limit the rights granted
31 # to you to modify and distribute this software under the terms of
32 # the GNU General Public License and is only of importance to you if
33 # you choose to contribute your changes and enhancements to the
34 # community by submitting them to Best Practical Solutions, LLC.)
35
36 # By intentionally submitting any modifications, corrections or
37 # derivatives to this work, or any other work intended for use with
38 # Request Tracker, to Best Practical Solutions, LLC, you confirm that
39 # you are the copyright holder for those contributions and you grant
40 # Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
41 # royalty-free, perpetual, license to use, copy, create derivative
42 # works based on those contributions, and sublicense and distribute
43 # those contributions and any derivatives thereof.
44
45 # END BPS TAGGED BLOCK }}}
46
47 =head1 NAME
48
49   RT::SavedSearch - an API for saving and retrieving search form values.
50
51 =head1 SYNOPSIS
52
53   use RT::SavedSearch
54
55 =head1 DESCRIPTION
56
57   SavedSearch is an object that can belong to either an RT::User or an
58   RT::Group.  It consists of an ID, a description, and a number of
59   search parameters.
60
61 =head1 METHODS
62
63 =begin testing
64
65 use_ok(RT::SavedSearch);
66
67 # Real tests are in lib/t/20savedsearch.t
68
69 =end testing
70
71 =cut
72
73 package RT::SavedSearch;
74
75 use RT::Base;
76 use RT::Attribute;
77
78 use strict;
79 use vars qw/@ISA/;
80 @ISA = qw/RT::Base/;
81
82 sub new  {
83     my $proto = shift;
84     my $class = ref($proto) || $proto;
85     my $self  = {};
86     $self->{'Id'} = 0;
87     bless ($self, $class);
88     $self->CurrentUser(@_);
89     return $self;
90 }
91
92 =head2 Load
93
94 Takes a privacy specification, an object ID, and a search ID.  Loads
95 the given search ID if it belongs to the stated user or group.
96 Returns a tuple of status and message, where status is true on
97 success.
98
99 =cut
100
101 sub Load {
102     my $self = shift;
103     my ($privacy, $id) = @_;
104     my $object = $self->_GetObject($privacy);
105
106     if ($object) {
107         $self->{'Attribute'} = $object->Attributes->WithId($id);
108         if ($self->{'Attribute'}->Id) {
109             $self->{'Id'} = $self->{'Attribute'}->Id;
110             $self->{'Privacy'} = $privacy;
111             $self->{'Type'} = $self->{'Attribute'}->SubValue('SearchType');
112             return (1, $self->loc("Loaded search [_1]", $self->Name));
113         } else {
114             $RT::Logger->error("Could not load attribute " . $id
115                                . " for object " . $privacy);
116             return (0, $self->loc("Search attribute load failure"));
117         }
118     } else {
119         $RT::Logger->error("Could not load object $privacy when loading search");
120         return (0, $self->loc("Could not load object for [_1]", $privacy));
121     }
122
123 }
124
125 =head2 Save
126
127 Takes a privacy, an optional type, a name, and a hashref containing the
128 search parameters.  Saves the given parameters to the appropriate user/
129 group object, and loads the resulting search.  Returns a tuple of status
130 and message, where status is true on success.  Defaults are:
131   Privacy:      undef
132   Type:         Ticket
133   Name:         "new search"
134   SearchParams: (empty hash)
135
136 =cut
137
138 sub Save {
139     my $self = shift;
140     my %args = ('Privacy' => 'RT::User-' . $self->CurrentUser->Id,
141                 'Type' => 'Ticket',
142                 'Name' => 'new search',
143                 'SearchParams' => {},
144                 @_);
145     my $privacy = $args{'Privacy'};
146     my $type = $args{'Type'};
147     my $name = $args{'Name'};
148     my %params = %{$args{'SearchParams'}};
149
150     $params{'SearchType'} = $type;
151     my $object = $self->_GetObject($privacy);
152     if ($object) {
153         my ($att_id, $att_msg) = $object->AddAttribute(
154                                                        'Name' => 'SavedSearch',
155                                                        'Description' => $name,
156                                                        'Content' => \%params);
157         if ($att_id) {
158             $self->{'Attribute'} = $object->Attributes->WithId($att_id);
159             $self->{'Id'} = $att_id;
160             $self->{'Privacy'} = $privacy;
161             $self->{'Type'} = $type;
162             return (1, $self->loc("Saved search [_1]", $name));
163         } else {
164             $RT::Logger->error("SavedSearch save failure: $att_msg");
165             return (0, $self->loc("Failed to create search attribute"));
166         }
167     } else {
168         return (0, $self->loc("Failed to load object for [_1]", $privacy));
169     }
170 }
171
172 =head2 Update
173
174 Updates the parameters of an existing search.  Takes the arguments
175 "Name" and "SearchParams"; SearchParams should be a hashref containing
176 the new parameters of the search.  If Name is not specified, the name
177 will not be changed.
178
179 =cut
180
181 sub Update {
182     my $self = shift;
183     my %args = ('Name' => '',
184                 'SearchParams' => {},
185                 @_);
186     
187     return(0, $self->loc("No search loaded")) unless $self->Id;
188     return(0, $self->loc("Could not load search attribute"))
189         unless $self->{'Attribute'}->Id;
190     my ($status, $msg) = $self->{'Attribute'}->SetSubValues(%{$args{'SearchParams'}});
191     if ($status && $args{'Name'}) {
192         ($status, $msg) = $self->{'Attribute'}->SetDescription($args{'Name'});
193     }
194     return ($status, $self->loc("Search update: [_1]", $msg));
195 }
196
197 =head2 Delete
198     
199 Deletes the existing search.  Returns a tuple of status and message,
200 where status is true upon success.
201
202 =cut
203
204 sub Delete {
205     my $self = shift;
206
207     my ($status, $msg) = $self->{'Attribute'}->Delete;
208     if ($status) {
209         return (1, $self->loc("Deleted search"));
210     } else {
211         return (0, $self->loc("Delete failed: [_1]", $msg));
212     }
213 }
214         
215
216 ### Accessor methods
217
218 =head2 Name
219
220 Returns the name of the search.
221
222 =cut
223
224 sub Name {
225     my $self = shift;
226     return unless ref($self->{'Attribute'}) eq 'RT::Attribute';
227     return $self->{'Attribute'}->Description();
228 }
229
230 =head2 GetParameter
231
232 Returns the given named parameter of the search, e.g. 'Query', 'Format'.
233
234 =cut
235
236 sub GetParameter {
237     my $self = shift;
238     my $param = shift;
239     return unless ref($self->{'Attribute'}) eq 'RT::Attribute';
240     return $self->{'Attribute'}->SubValue($param);
241 }
242
243 =head2 Id
244
245 Returns the numerical id of this search.
246
247 =cut
248
249 sub Id {
250      my $self = shift;
251      return $self->{'Id'};
252 }
253
254 =head2 Privacy
255
256 Returns the principal object to whom this search belongs, in a string
257 "<class>-<id>", e.g. "RT::Group-16".
258
259 =cut
260
261 sub Privacy {
262     my $self = shift;
263     return $self->{'Privacy'};
264 }
265
266 =head2 Type
267
268 Returns the type of this search, e.g. 'Ticket'.  Useful for denoting the
269 saved searches that are relevant to a particular search page.
270
271 =cut
272
273 sub Type {
274     my $self = shift;
275     return $self->{'Type'};
276 }
277
278 ### Internal methods
279
280 # _GetObject: helper routine to load the correct object whose parameters
281 #  have been passed.
282
283 sub _GetObject {
284     my $self = shift;
285     my $privacy = shift;
286
287     my ($obj_type, $obj_id) = split(/\-/, $privacy);
288     unless ($obj_type eq 'RT::User' || $obj_type eq 'RT::Group') {
289         $RT::Logger->error("Tried to load a search belonging to an $obj_type, which is neither a user nor a group");
290         return undef;
291     }
292
293     my $object;
294     eval "
295          require $obj_type;
296          \$object = $obj_type->new(\$self->CurrentUser);
297          \$object->Load(\$obj_id);
298     ";
299     unless (ref($object) eq $obj_type) {
300         $RT::Logger->error("Could not load object of type $obj_type with ID $obj_id");
301         return undef;
302     }
303     
304     # Do not allow the loading of a user object other than the current
305     # user, or of a group object of which the current user is not a member.
306
307     if ($obj_type eq 'RT::User' 
308         && $object->Id != $self->CurrentUser->UserObj->Id()) {
309         $RT::Logger->debug("Permission denied for user other than self");
310         return undef;
311     }
312     if ($obj_type eq 'RT::Group' &&
313         !$object->HasMemberRecursively($self->CurrentUser->PrincipalObj)) {
314         $RT::Logger->debug("Permission denied, ".$self->CurrentUser->Name.
315                            " is not a member of group");
316         return undef;
317     }
318
319     return $object;
320 }
321
322 eval "require RT::SavedSearch_Vendor";
323 die $@ if ($@ && $@ !~ qr{^Can't locate RT/SavedSearch_Vendor.pm});
324 eval "require RT::SavedSearch_Local";
325 die $@ if ($@ && $@ !~ qr{^Can't locate RT/SavedSearch_Local.pm});
326
327 1;