This commit was generated by cvs2svn to compensate for changes in r4407,
[freeside.git] / rt / lib / RT / SearchBuilder.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::SearchBuilder - a baseclass for RT collection objects
50
51 =head1 SYNOPSIS
52
53 =head1 DESCRIPTION
54
55
56 =head1 METHODS
57
58
59 =begin testing
60
61 ok (require RT::SearchBuilder);
62
63 =end testing
64
65
66 =cut
67
68 package RT::SearchBuilder;
69
70 use RT::Base;
71 use DBIx::SearchBuilder;
72
73 use strict;
74 use vars qw(@ISA);
75 @ISA = qw(DBIx::SearchBuilder RT::Base);
76
77 # {{{ sub _Init 
78 sub _Init  {
79     my $self = shift;
80     
81     $self->{'user'} = shift;
82     unless(defined($self->CurrentUser)) {
83         use Carp;
84         Carp::confess("$self was created without a CurrentUser");
85         $RT::Logger->err("$self was created without a CurrentUser");
86         return(0);
87     }
88     $self->SUPER::_Init( 'Handle' => $RT::Handle);
89 }
90 # }}}
91
92 # {{{ sub LimitToEnabled
93
94 =head2 LimitToEnabled
95
96 Only find items that haven\'t been disabled
97
98 =cut
99
100 sub LimitToEnabled {
101     my $self = shift;
102     
103     $self->Limit( FIELD => 'Disabled',
104                   VALUE => '0',
105                   OPERATOR => '=' );
106 }
107 # }}}
108
109 # {{{ sub LimitToDisabled
110
111 =head2 LimitToDeleted
112
113 Only find items that have been deleted.
114
115 =cut
116
117 sub LimitToDeleted {
118     my $self = shift;
119     
120     $self->{'find_disabled_rows'} = 1;
121     $self->Limit( FIELD => 'Disabled',
122                   OPERATOR => '=',
123                   VALUE => '1'
124                 );
125 }
126 # }}}
127
128 # {{{ sub LimitAttribute
129
130 =head2 LimitAttribute PARAMHASH
131
132 Takes NAME, OPERATOR and VALUE to find records that has the
133 matching Attribute.
134
135 If EMPTY is set, also select rows with an empty string as
136 Attribute's Content.
137
138 If NULL is set, also select rows without the named Attribute.
139
140 =cut
141
142 my %Negate = qw(
143     =           !=
144     !=          =
145     >           <=
146     <           >=
147     >=          <
148     <=          >
149     LIKE        NOT LIKE
150     NOT LIKE    LIKE
151     IS          IS NOT
152     IS NOT      IS
153 );
154
155 sub LimitAttribute {
156     my ($self, %args) = @_;
157     my $clause = 'ALIAS';
158     my $operator = ($args{OPERATOR} || '=');
159     
160     if ($args{NULL} and exists $args{VALUE}) {
161         $clause = 'LEFTJOIN';
162         $operator = $Negate{$operator};
163     }
164     elsif ($args{NEGATE}) {
165         $operator = $Negate{$operator};
166     }
167     
168     my $alias = $self->Join(
169         TYPE   => 'left',
170         ALIAS1 => $args{ALIAS} || 'main',
171         FIELD1 => 'id',
172         TABLE2 => 'Attributes',
173         FIELD2 => 'ObjectId'
174     );
175
176     my $type = ref($self);
177     $type =~ s/(?:s|Collection)$//; # XXX - Hack!
178
179     $self->Limit(
180         $clause    => $alias,
181         FIELD      => 'ObjectType',
182         OPERATOR   => '=',
183         VALUE      => $type,
184     );
185     $self->Limit(
186         $clause    => $alias,
187         FIELD      => 'Name',
188         OPERATOR   => '=',
189         VALUE      => $args{NAME},
190     ) if exists $args{NAME};
191
192     return unless exists $args{VALUE};
193
194     $self->Limit(
195         $clause    => $alias,
196         FIELD      => 'Content',
197         OPERATOR   => $operator,
198         VALUE      => $args{VALUE},
199     );
200
201     # Capture rows with the attribute defined as an empty string.
202     $self->Limit(
203         $clause    => $alias,
204         FIELD      => 'Content',
205         OPERATOR   => '=',
206         VALUE      => '',
207         ENTRYAGGREGATOR => $args{NULL} ? 'AND' : 'OR',
208     ) if $args{EMPTY};
209
210     # Capture rows without the attribute defined
211     $self->Limit(
212         %args,
213         ALIAS      => $alias,
214         FIELD      => 'id',
215         OPERATOR   => ($args{NEGATE} ? 'IS NOT' : 'IS'),
216         VALUE      => 'NULL',
217     ) if $args{NULL};
218 }
219 # }}}
220
221 # {{{ sub LimitCustomField
222
223 =head2 LimitCustomField
224
225 Takes a paramhash of key/value pairs with the following keys:
226
227 =over 4
228
229 =item CUSTOMFIELD - CustomField id. Optional
230
231 =item OPERATOR - The usual Limit operators
232
233 =item VALUE - The value to compare against
234
235 =back
236
237 =cut
238
239 sub _SingularClass {
240     my $self = shift;
241     my $class = ref($self);
242     $class =~ s/s$// or die "Cannot deduce SingularClass for $class";
243     return $class;
244 }
245
246 sub LimitCustomField {
247     my $self = shift;
248     my %args = ( VALUE        => undef,
249                  CUSTOMFIELD  => undef,
250                  OPERATOR     => '=',
251                  @_ );
252
253     my $alias = $self->Join(
254         TYPE       => 'left',
255         ALIAS1     => 'main',
256         FIELD1     => 'id',
257         TABLE2     => 'ObjectCustomFieldValues',
258         FIELD2     => 'ObjectId'
259     );
260     $self->Limit(
261         ALIAS      => $alias,
262         FIELD      => 'CustomField',
263         OPERATOR   => '=',
264         VALUE      => $args{'CUSTOMFIELD'},
265     ) if ($args{'CUSTOMFIELD'});
266     $self->Limit(
267         ALIAS      => $alias,
268         FIELD      => 'ObjectType',
269         OPERATOR   => '=',
270         VALUE      => $self->_SingularClass,
271     );
272     $self->Limit(
273         ALIAS      => $alias,
274         FIELD      => 'Content',
275         OPERATOR   => $args{'OPERATOR'},
276         VALUE      => $args{'VALUE'},
277     );
278 }
279
280 # {{{ sub FindAllRows
281
282 =head2 FindAllRows
283
284 Find all matching rows, regardless of whether they are disabled or not
285
286 =cut
287
288 sub FindAllRows {
289     shift->{'find_disabled_rows'} = 1;
290 }
291
292 # {{{ sub Limit 
293
294 =head2 Limit PARAMHASH
295
296 This Limit sub calls SUPER::Limit, but defaults "CASESENSITIVE" to 1, thus
297 making sure that by default lots of things don't do extra work trying to 
298 match lower(colname) agaist lc($val);
299
300 =cut
301
302 sub Limit {
303     my $self = shift;
304     my %args = ( CASESENSITIVE => 1,
305                  @_ );
306
307     return $self->SUPER::Limit(%args);
308 }
309
310 # }}}
311
312 # {{{ sub ItemsOrderBy
313
314 =head2 ItemsOrderBy
315
316 If it has a SortOrder attribute, sort the array by SortOrder.
317 Otherwise, if it has a "Name" attribute, sort alphabetically by Name
318 Otherwise, just give up and return it in the order it came from the
319 db.
320
321 =cut
322
323 sub ItemsOrderBy {
324     my $self = shift;
325     my $items = shift;
326   
327     if ($self->NewItem()->_Accessible('SortOrder','read')) {
328         $items = [ sort { $a->SortOrder <=> $b->SortOrder } @{$items} ];
329     }
330     elsif ($self->NewItem()->_Accessible('Name','read')) {
331         $items = [ sort { lc($a->Name) cmp lc($b->Name) } @{$items} ];
332     }
333
334     return $items;
335 }
336
337 # }}}
338
339 # {{{ sub ItemsArrayRef
340
341 =head2 ItemsArrayRef
342
343 Return this object's ItemsArray, in the order that ItemsOrderBy sorts
344 it.
345
346 =begin testing
347
348 use_ok(RT::Queues);
349 ok(my $queues = RT::Queues->new($RT::SystemUser), 'Created a queues object');
350 ok( $queues->UnLimit(),'Unlimited the result set of the queues object');
351 my $items = $queues->ItemsArrayRef();
352 my @items = @{$items};
353
354 ok($queues->NewItem->_Accessible('Name','read'));
355 my @sorted = sort {lc($a->Name) cmp lc($b->Name)} @items;
356 ok (@sorted, "We have an array of queues, sorted". join(',',map {$_->Name} @sorted));
357
358 ok (@items, "We have an array of queues, raw". join(',',map {$_->Name} @items));
359 my @sorted_ids = map {$_->id } @sorted;
360 my @items_ids = map {$_->id } @items;
361
362 is ($#sorted, $#items);
363 is ($sorted[0]->Name, $items[0]->Name);
364 is ($sorted[-1]->Name, $items[-1]->Name);
365 is_deeply(\@items_ids, \@sorted_ids, "ItemsArrayRef sorts alphabetically by name");;
366
367
368 =end testing
369
370 =cut
371
372 sub ItemsArrayRef {
373     my $self = shift;
374     my @items;
375     
376     return $self->ItemsOrderBy($self->SUPER::ItemsArrayRef());
377 }
378
379 # }}}
380
381 eval "require RT::SearchBuilder_Vendor";
382 die $@ if ($@ && $@ !~ qr{^Can't locate RT/SearchBuilder_Vendor.pm});
383 eval "require RT::SearchBuilder_Local";
384 die $@ if ($@ && $@ !~ qr{^Can't locate RT/SearchBuilder_Local.pm});
385
386 1;
387
388