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