Upgrade 4.0.17 clean.
[usit-rt.git] / lib / RT / SearchBuilder.pm
CommitLineData
84fb5b46
MKG
1# BEGIN BPS TAGGED BLOCK {{{
2#
3# COPYRIGHT:
4#
403d7b0b 5# This software is Copyright (c) 1996-2013 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
49=head1 NAME
50
51 RT::SearchBuilder - a baseclass for RT collection objects
52
53=head1 SYNOPSIS
54
55=head1 DESCRIPTION
56
57
58=head1 METHODS
59
60
61
62
63=cut
64
65package RT::SearchBuilder;
66
67use RT::Base;
68use DBIx::SearchBuilder "1.40";
69
70use strict;
71use warnings;
72
73
74use base qw(DBIx::SearchBuilder RT::Base);
75
76sub _Init {
77 my $self = shift;
78
79 $self->{'user'} = shift;
80 unless(defined($self->CurrentUser)) {
81 use Carp;
82 Carp::confess("$self was created without a CurrentUser");
83 $RT::Logger->err("$self was created without a CurrentUser");
84 return(0);
85 }
86 $self->SUPER::_Init( 'Handle' => $RT::Handle);
87}
88
01e3b242
MKG
89sub _Handle { return $RT::Handle }
90
84fb5b46
MKG
91sub CleanSlate {
92 my $self = shift;
93 $self->{'_sql_aliases'} = {};
01e3b242
MKG
94 delete $self->{'handled_disabled_column'};
95 delete $self->{'find_disabled_rows'};
84fb5b46
MKG
96 return $self->SUPER::CleanSlate(@_);
97}
98
99sub JoinTransactions {
100 my $self = shift;
101 my %args = ( New => 0, @_ );
102
103 return $self->{'_sql_aliases'}{'transactions'}
104 if !$args{'New'} && $self->{'_sql_aliases'}{'transactions'};
105
106 my $alias = $self->Join(
107 ALIAS1 => 'main',
108 FIELD1 => 'id',
109 TABLE2 => 'Transactions',
110 FIELD2 => 'ObjectId',
111 );
b5747ff2
MKG
112
113 my $item = $self->NewItem;
114 my $object_type = $item->can('ObjectType') ? $item->ObjectType : ref $item;
115
84fb5b46
MKG
116 $self->RT::SearchBuilder::Limit(
117 LEFTJOIN => $alias,
118 FIELD => 'ObjectType',
b5747ff2 119 VALUE => $object_type,
84fb5b46
MKG
120 );
121 $self->{'_sql_aliases'}{'transactions'} = $alias
122 unless $args{'New'};
123
124 return $alias;
125}
126
127sub OrderByCols {
128 my $self = shift;
129 my @sort;
130 for my $s (@_) {
131 next if defined $s->{FIELD} and $s->{FIELD} =~ /\W/;
132 $s->{FIELD} = $s->{FUNCTION} if $s->{FUNCTION};
133 push @sort, $s;
134 }
135 return $self->SUPER::OrderByCols( @sort );
136}
137
138# If we're setting RowsPerPage or FirstRow, ensure we get a natural number or undef.
139sub RowsPerPage {
140 my $self = shift;
141 return if @_ and defined $_[0] and $_[0] =~ /\D/;
142 return $self->SUPER::RowsPerPage(@_);
143}
144
145sub FirstRow {
146 my $self = shift;
147 return if @_ and defined $_[0] and $_[0] =~ /\D/;
148 return $self->SUPER::FirstRow(@_);
149}
150
151=head2 LimitToEnabled
152
153Only find items that haven't been disabled
154
155=cut
156
157sub LimitToEnabled {
158 my $self = shift;
159
160 $self->{'handled_disabled_column'} = 1;
161 $self->Limit( FIELD => 'Disabled', VALUE => '0' );
162}
163
164=head2 LimitToDeleted
165
166Only find items that have been deleted.
167
168=cut
169
170sub LimitToDeleted {
171 my $self = shift;
172
173 $self->{'handled_disabled_column'} = $self->{'find_disabled_rows'} = 1;
174 $self->Limit( FIELD => 'Disabled', VALUE => '1' );
175}
176
177=head2 FindAllRows
178
179Find all matching rows, regardless of whether they are disabled or not
180
181=cut
182
183sub FindAllRows {
184 shift->{'find_disabled_rows'} = 1;
185}
186
187=head2 LimitCustomField
188
189Takes a paramhash of key/value pairs with the following keys:
190
191=over 4
192
193=item CUSTOMFIELD - CustomField id. Optional
194
195=item OPERATOR - The usual Limit operators
196
197=item VALUE - The value to compare against
198
199=back
200
201=cut
202
203sub _SingularClass {
204 my $self = shift;
205 my $class = ref($self);
206 $class =~ s/s$// or die "Cannot deduce SingularClass for $class";
207 return $class;
208}
209
210sub LimitCustomField {
211 my $self = shift;
212 my %args = ( VALUE => undef,
213 CUSTOMFIELD => undef,
214 OPERATOR => '=',
215 @_ );
216
217 my $alias = $self->Join(
dab09ea8
MKG
218 TYPE => 'left',
219 ALIAS1 => 'main',
220 FIELD1 => 'id',
221 TABLE2 => 'ObjectCustomFieldValues',
222 FIELD2 => 'ObjectId'
84fb5b46
MKG
223 );
224 $self->Limit(
dab09ea8
MKG
225 ALIAS => $alias,
226 FIELD => 'CustomField',
227 OPERATOR => '=',
228 VALUE => $args{'CUSTOMFIELD'},
84fb5b46
MKG
229 ) if ($args{'CUSTOMFIELD'});
230 $self->Limit(
dab09ea8
MKG
231 ALIAS => $alias,
232 FIELD => 'ObjectType',
233 OPERATOR => '=',
234 VALUE => $self->_SingularClass,
84fb5b46
MKG
235 );
236 $self->Limit(
dab09ea8
MKG
237 ALIAS => $alias,
238 FIELD => 'Content',
239 OPERATOR => $args{'OPERATOR'},
240 VALUE => $args{'VALUE'},
241 );
242 $self->Limit(
243 ALIAS => $alias,
244 FIELD => 'Disabled',
245 OPERATOR => '=',
246 VALUE => 0,
84fb5b46
MKG
247 );
248}
249
250=head2 Limit PARAMHASH
251
252This Limit sub calls SUPER::Limit, but defaults "CASESENSITIVE" to 1, thus
253making sure that by default lots of things don't do extra work trying to
254match lower(colname) agaist lc($val);
255
256We also force VALUE to C<NULL> when the OPERATOR is C<IS> or C<IS NOT>.
257This ensures that we don't pass invalid SQL to the database or allow SQL
258injection attacks when we pass through user specified values.
259
260=cut
261
262sub Limit {
263 my $self = shift;
264 my %ARGS = (
265 CASESENSITIVE => 1,
266 OPERATOR => '=',
267 @_,
268 );
269
270 # We use the same regex here that DBIx::SearchBuilder uses to exclude
271 # values from quoting
272 if ( $ARGS{'OPERATOR'} =~ /IS/i ) {
273 # Don't pass anything but NULL for IS and IS NOT
274 $ARGS{'VALUE'} = 'NULL';
275 }
276
277 if ($ARGS{FUNCTION}) {
278 ($ARGS{ALIAS}, $ARGS{FIELD}) = split /\./, delete $ARGS{FUNCTION}, 2;
279 $self->SUPER::Limit(%ARGS);
280 } elsif ($ARGS{FIELD} =~ /\W/
281 or $ARGS{OPERATOR} !~ /^(=|<|>|!=|<>|<=|>=
282 |(NOT\s*)?LIKE
283 |(NOT\s*)?(STARTS|ENDS)WITH
284 |(NOT\s*)?MATCHES
285 |IS(\s*NOT)?
5b0d0914 286 |(NOT\s*)?IN
84fb5b46
MKG
287 |\@\@)$/ix) {
288 $RT::Logger->crit("Possible SQL injection attack: $ARGS{FIELD} $ARGS{OPERATOR}");
289 $self->SUPER::Limit(
290 %ARGS,
291 FIELD => 'id',
292 OPERATOR => '<',
293 VALUE => '0',
294 );
295 } else {
296 $self->SUPER::Limit(%ARGS);
297 }
298}
299
300=head2 ItemsOrderBy
301
302If it has a SortOrder attribute, sort the array by SortOrder.
303Otherwise, if it has a "Name" attribute, sort alphabetically by Name
304Otherwise, just give up and return it in the order it came from the
305db.
306
307=cut
308
309sub ItemsOrderBy {
310 my $self = shift;
311 my $items = shift;
312
313 if ($self->NewItem()->_Accessible('SortOrder','read')) {
314 $items = [ sort { $a->SortOrder <=> $b->SortOrder } @{$items} ];
315 }
316 elsif ($self->NewItem()->_Accessible('Name','read')) {
317 $items = [ sort { lc($a->Name) cmp lc($b->Name) } @{$items} ];
318 }
319
320 return $items;
321}
322
323=head2 ItemsArrayRef
324
325Return this object's ItemsArray, in the order that ItemsOrderBy sorts
326it.
327
328=cut
329
330sub ItemsArrayRef {
331 my $self = shift;
332 return $self->ItemsOrderBy($self->SUPER::ItemsArrayRef());
333}
334
335# make sure that Disabled rows never get seen unless
336# we're explicitly trying to see them.
337
338sub _DoSearch {
339 my $self = shift;
340
341 if ( $self->{'with_disabled_column'}
342 && !$self->{'handled_disabled_column'}
343 && !$self->{'find_disabled_rows'}
344 ) {
345 $self->LimitToEnabled;
346 }
347 return $self->SUPER::_DoSearch(@_);
348}
349sub _DoCount {
350 my $self = shift;
351
352 if ( $self->{'with_disabled_column'}
353 && !$self->{'handled_disabled_column'}
354 && !$self->{'find_disabled_rows'}
355 ) {
356 $self->LimitToEnabled;
357 }
358 return $self->SUPER::_DoCount(@_);
359}
360
361=head2 ColumnMapClassName
362
363ColumnMap needs a Collection name to load the correct list display.
364Depluralization is hard, so provide an easy way to correct the naive
365algorithm that this code uses.
366
367=cut
368
369sub ColumnMapClassName {
370 my $self = shift;
371 my $Class = ref $self;
372 $Class =~ s/s$//;
373 $Class =~ s/:/_/g;
374 return $Class;
375}
376
377RT::Base->_ImportOverlays();
378
3791;