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