Upgrade to 4.0.8 with mod of ExternalAuth + absolute paths to ticket-menu.
[usit-rt.git] / lib / RT / Articles.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
49use strict;
50use warnings;
51
52package RT::Articles;
53
54use base 'RT::SearchBuilder';
55
56sub Table {'Articles'}
57
58sub _Init {
59 my $self = shift;
60 $self->OrderByCols(
61 { FIELD => 'SortOrder', ORDER => 'ASC' },
62 { FIELD => 'Name', ORDER => 'ASC' },
63 );
64 return $self->SUPER::_Init( @_ );
65}
66
67=head2 Next
68
69Returns the next article that this user can see.
70
71=cut
72
73sub Next {
74 my $self = shift;
75
76 my $Object = $self->SUPER::Next();
77 if ( ( defined($Object) ) and ( ref($Object) ) ) {
78
79 if ( $Object->CurrentUserHasRight('ShowArticle') ) {
80 return ($Object);
81 }
82
83 #If the user doesn't have the right to show this Object
84 else {
85 return ( $self->Next() );
86 }
87 }
88
89 #if there never was any queue
90 else {
91 return (undef);
92 }
93
94}
95
96=head2 Limit { FIELD => undef, OPERATOR => '=', VALUE => 'undef'}
97
98Limit the result set. See DBIx::SearchBuilder docs
99In addition to the "normal" stuff, value can be an array.
100
101=cut
102
103sub Limit {
104 my $self = shift;
105 my %ARGS = (
106 OPERATOR => '=',
107 @_
108 );
109
110 if ( ref( $ARGS{'VALUE'} ) ) {
111 my @values = $ARGS{'VALUE'};
112 delete $ARGS{'VALUE'};
113 foreach my $v (@values) {
114 $self->SUPER::Limit( %ARGS, VALUE => $v );
115 }
116 }
117 else {
118 $self->SUPER::Limit(%ARGS);
119 }
120}
121
122=head2 LimitName { OPERATOR => 'LIKE', VALUE => undef }
123
124Find all articles with Name fields which satisfy OPERATOR for VALUE
125
126=cut
127
128sub LimitName {
129 my $self = shift;
130
131 my %args = (
132 FIELD => 'Name',
133 OPERATOR => 'LIKE',
134 CASESENSITIVE => 0,
135 VALUE => undef,
136 @_
137 );
138
139 $self->Limit(%args);
140
141}
142
143=head2 LimitSummary { OPERATOR => 'LIKE', VALUE => undef }
144
145Find all articles with summary fields which satisfy OPERATOR for VALUE
146
147=cut
148
149sub LimitSummary {
150 my $self = shift;
151
152 my %args = (
153 FIELD => 'Summary',
154 OPERATOR => 'LIKE',
155 CASESENSITIVE => 0,
156 VALUE => undef,
157 @_
158 );
159
160 $self->Limit(%args);
161
162}
163
164sub LimitCreated {
165 my $self = shift;
166 my %args = (
167 FIELD => 'Created',
168 OPERATOR => undef,
169 VALUE => undef,
170 @_
171 );
172
173 $self->Limit(%args);
174
175}
176
177sub LimitCreatedBy {
178 my $self = shift;
179 my %args = (
180 FIELD => 'CreatedBy',
181 OPERATOR => '=',
182 VALUE => undef,
183 @_
184 );
185
186 $self->Limit(%args);
187
188}
189
190sub LimitUpdated {
191
192 my $self = shift;
193 my %args = (
194 FIELD => 'Updated',
195 OPERATOR => undef,
196 VALUE => undef,
197 @_
198 );
199
200 $self->Limit(%args);
201
202}
203
204sub LimitUpdatedBy {
205 my $self = shift;
206 my %args = (
207 FIELD => 'UpdatedBy',
208 OPERATOR => '=',
209 VALUE => undef,
210 @_
211 );
212
213 $self->Limit(%args);
214
215}
216
217# {{{ LimitToParent ID
218
219=head2 LimitToParent ID
220
221Limit the returned set of articles to articles which are children
222of article ID.
223This does not recurse.
224
225=cut
226
227sub LimitToParent {
228 my $self = shift;
229 my $parent = shift;
230 $self->Limit(
231 FIELD => 'Parent',
232 OPERATOR => '=',
233 VALUE => $parent
234 );
235
236}
237
238# }}}
239# {{{ LimitCustomField
240
241=head2 LimitCustomField HASH
242
243Limit the result set to articles which have or do not have the custom field
244value listed, using a left join to catch things where no rows match.
245
246HASH needs the following fields:
247 FIELD (A custom field id) or undef for any custom field
248 ENTRYAGGREGATOR => (AND, OR)
249 OPERATOR ('=', 'LIKE', '!=', 'NOT LIKE')
250 VALUE ( a single scalar value or a list of possible values to be concatenated with ENTRYAGGREGATOR)
251
252The subclause that the LIMIT statement(s) should be done in can also
253be passed in with a SUBCLAUSE parameter.
254
255=cut
256
257sub LimitCustomField {
258 my $self = shift;
259 my %args = (
260 FIELD => undef,
261 ENTRYAGGREGATOR => 'OR',
262 OPERATOR => '=',
263 QUOTEVALUE => 1,
264 VALUE => undef,
265 SUBCLAUSE => undef,
266 @_
267 );
268
269 my $value = $args{'VALUE'};
270 # XXX: this work in a different way than RT
271 return unless $value; #strip out total blank wildcards
272
273 my $ObjectValuesAlias = $self->Join(
274 TYPE => 'left',
275 ALIAS1 => 'main',
276 FIELD1 => 'id',
277 TABLE2 => 'ObjectCustomFieldValues',
278 FIELD2 => 'ObjectId',
279 EXPRESSION => 'main.id'
280 );
281
282 $self->Limit(
283 LEFTJOIN => $ObjectValuesAlias,
284 FIELD => 'Disabled',
285 VALUE => '0'
286 );
287
288 if ( $args{'FIELD'} ) {
289
290 my $field_id;
291 if (UNIVERSAL::isa($args{'FIELD'} ,'RT::CustomField')) {
292 $field_id = $args{'FIELD'}->id;
293 } elsif($args{'FIELD'} =~ /^\d+$/) {
294 $field_id = $args{'FIELD'};
295 }
296 if ($field_id) {
297 $self->Limit( LEFTJOIN => $ObjectValuesAlias,
298 FIELD => 'CustomField',
299 VALUE => $field_id,
300 ENTRYAGGREGATOR => 'AND');
301 # Could convert the above to a non-left join and also enable the thing below
302 # $self->SUPER::Limit( ALIAS => $ObjectValuesAlias,
303 # FIELD => 'CustomField',
304 # OPERATOR => 'IS',
305 # VALUE => 'NULL',
306 # QUOTEVALUE => 0,
307 # ENTRYAGGREGATOR => 'OR',);
308 } else {
309 # Search for things by name if the cf was specced by name.
310 my $fields = $self->NewAlias('CustomFields');
311 $self->Join( TYPE => 'left',
312 ALIAS1 => $ObjectValuesAlias , FIELD1 => 'CustomField',
313 ALIAS2 => $fields, FIELD2=> 'id');
314 $self->Limit( ALIAS => $fields,
315 FIELD => 'Name',
316 VALUE => $args{'FIELD'},
317 ENTRYAGGREGATOR => 'OR');
318 $self->Limit(
319 ALIAS => $fields,
320 FIELD => 'LookupType',
321 VALUE =>
322 RT::Article->new($RT::SystemUser)->CustomFieldLookupType()
323 );
324
325 }
326 }
327 # If we're trying to find articles where a custom field value
328 # doesn't match something, be sure to find things where it's null
329
330 # basically, we do a left join on the value being applicable to
331 # the article and then we turn around and make sure that it's
332 # actually null in practise
333
334 # TODO this should deal with starts with and ends with
335
336 my $fix_op = sub {
337 my $op = shift;
338 return $op unless RT->Config->Get('DatabaseType') eq 'Oracle';
339 return 'MATCHES' if $op eq '=';
340 return 'NOT MATCHES' if $op eq '!=';
341 return $op;
342 };
343
344 my $clause = $args{'SUBCLAUSE'} || $ObjectValuesAlias;
345
346 if ( $args{'OPERATOR'} eq '!=' || $args{'OPERATOR'} =~ /^not like$/i ) {
347 my $op;
348 if ( $args{'OPERATOR'} eq '!=' ) {
349 $op = "=";
350 }
351 elsif ( $args{'OPERATOR'} =~ /^not like$/i ) {
352 $op = 'LIKE';
353 }
354
355 $self->SUPER::Limit(
356 LEFTJOIN => $ObjectValuesAlias,
357 FIELD => 'Content',
358 OPERATOR => $op,
359 VALUE => $value,
360 QUOTEVALUE => $args{'QUOTEVALUE'},
361 ENTRYAGGREGATOR => 'AND', #$args{'ENTRYAGGREGATOR'},
362 SUBCLAUSE => $clause,
dab09ea8 363 CASESENSITIVE => 0,
84fb5b46
MKG
364 );
365 $self->SUPER::Limit(
366 ALIAS => $ObjectValuesAlias,
367 FIELD => 'Content',
368 OPERATOR => 'IS',
369 VALUE => 'NULL',
370 QUOTEVALUE => 0,
371 ENTRYAGGREGATOR => 'AND',
372 SUBCLAUSE => $clause,
373 );
374 }
375 else {
376 $self->SUPER::Limit(
377 ALIAS => $ObjectValuesAlias,
378 FIELD => 'LargeContent',
379 OPERATOR => $fix_op->($args{'OPERATOR'}),
380 VALUE => $value,
381 QUOTEVALUE => $args{'QUOTEVALUE'},
382 ENTRYAGGREGATOR => $args{'ENTRYAGGREGATOR'},
383 SUBCLAUSE => $clause,
dab09ea8 384 CASESENSITIVE => 0,
84fb5b46
MKG
385 );
386 $self->SUPER::Limit(
387 ALIAS => $ObjectValuesAlias,
388 FIELD => 'Content',
389 OPERATOR => $args{'OPERATOR'},
390 VALUE => $value,
391 QUOTEVALUE => $args{'QUOTEVALUE'},
392 ENTRYAGGREGATOR => $args{'ENTRYAGGREGATOR'},
393 SUBCLAUSE => $clause,
dab09ea8 394 CASESENSITIVE => 0,
84fb5b46
MKG
395 );
396 }
397}
398
399# }}}
400
401# {{{ LimitTopics
402sub LimitTopics {
403 my $self = shift;
404 my @topics = @_;
405
406 my $topics = $self->NewAlias('ObjectTopics');
407 $self->Limit(
408 ALIAS => $topics,
409 FIELD => 'Topic',
410 VALUE => $_,
411 ENTRYAGGREGATOR => 'OR'
412 )
413 for @topics;
414
415 $self->Limit(
416 ALIAS => $topics,
417 FIELD => 'ObjectType',
418 VALUE => 'RT::Article',
419 );
420 $self->Join(
421 ALIAS1 => 'main',
422 FIELD1 => 'id',
423 ALIAS2 => $topics,
424 FIELD2 => 'ObjectId',
425 );
426}
427
428# }}}
429
430# {{{ LimitRefersTo URI
431
432=head2 LimitRefersTo URI
433
434Limit the result set to only articles which are referred to by the URI passed in.
435
436=cut
437
438sub LimitRefersTo {
439 my $self = shift;
440 my $uri = shift;
441
442 my $uri_obj = RT::URI->new($self->CurrentUser);
443 $uri_obj->FromURI($uri);
444 my $links = $self->NewAlias('Links');
445 $self->Limit(
446 ALIAS => $links,
447 FIELD => 'Target',
448 VALUE => $uri_obj->URI
449 );
450
451 $self->Join(
452 ALIAS1 => 'main',
453 FIELD1 => 'URI',
454 ALIAS2 => $links,
455 FIELD2 => 'Base'
456 );
457
458}
459
460# }}}
461
462# {{{ LimitReferredToBy URI
463
464=head2 LimitReferredToBy URI
465
466Limit the result set to only articles which are referred to by the URI passed in.
467
468=cut
469
470sub LimitReferredToBy {
471 my $self = shift;
472 my $uri = shift;
473
474 my $uri_obj = RT::URI->new($self->CurrentUser);
475 $uri_obj->FromURI($uri);
476 my $links = $self->NewAlias('Links');
477 $self->Limit(
478 ALIAS => $links,
479 FIELD => 'Base',
480 VALUE => $uri_obj->URI
481 );
482
483 $self->Join(
484 ALIAS1 => 'main',
485 FIELD1 => 'URI',
486 ALIAS2 => $links,
487 FIELD2 => 'Target'
488 );
489
490}
491
492# }}}
493
494=head2 LimitHostlistClasses
495
496Only fetch Articles from classes where Hotlist is true.
497
498=cut
499
500sub LimitHotlistClasses {
501 my $self = shift;
502
503 my $classes = $self->Join(
504 ALIAS1 => 'main',
505 FIELD1 => 'Class',
506 TABLE2 => 'Classes',
507 FIELD2 => 'id',
508 );
509 $self->Limit( ALIAS => $classes, FIELD => 'HotList', VALUE => 1 );
510}
511
512=head2 LimitAppliedClasses Queue => QueueObj
513
514Takes a Queue and limits articles returned to classes which are applied to that Queue
515
516Accepts either a Queue obj or a Queue id
517
518=cut
519
520sub LimitAppliedClasses {
521 my $self = shift;
522 my %args = @_;
523
524 unless (ref $args{Queue} || $args{Queue} =~/^[0-9]+$/) {
525 $RT::Logger->error("Not a valid Queue: $args{Queue}");
526 return;
527 }
528
529 my $queue = ( ref $args{Queue} ? $args{Queue}->Id : $args{Queue} );
530
531 my $oc_alias = $self->Join(
532 ALIAS1 => 'main',
533 FIELD1 => 'Class',
534 TABLE2 => 'ObjectClasses',
535 FIELD2 => 'Class'
536 );
537
538 my $subclause = "possibleobjectclasses";
539 $self->_OpenParen($subclause);
540 $self->Limit( ALIAS => $oc_alias,
541 FIELD => 'ObjectId',
542 VALUE => $queue,
543 SUBCLAUSE => $subclause,
544 ENTRYAGGREGATOR => 'OR' );
545 $self->Limit( ALIAS => $oc_alias,
546 FIELD => 'ObjectType',
547 VALUE => 'RT::Queue',
548 SUBCLAUSE => $subclause,
549 ENTRYAGGREGATOR => 'AND' );
550 $self->_CloseParen($subclause);
551
552 $self->_OpenParen($subclause);
553 $self->Limit( ALIAS => $oc_alias,
554 FIELD => 'ObjectId',
555 VALUE => 0,
556 SUBCLAUSE => $subclause,
557 ENTRYAGGREGATOR => 'OR' );
558 $self->Limit( ALIAS => $oc_alias,
559 FIELD => 'ObjectType',
560 VALUE => 'RT::System',
561 SUBCLAUSE => $subclause,
562 ENTRYAGGREGATOR => 'AND' );
563 $self->_CloseParen($subclause);
564
565 return $self;
566
567}
568
569sub Search {
570 my $self = shift;
571 my %args = @_;
572 my $customfields = $args{CustomFields}
573 || RT::CustomFields->new( $self->CurrentUser );
574 my $dates = $args{Dates} || {};
575 my $order_by = $args{OrderBy};
576 my $order = $args{Order};
577 if ( $args{'q'} ) {
578 $self->Limit(
579 FIELD => 'Name',
580 SUBCLAUSE => 'NameOrSummary',
581 OPERATOR => 'LIKE',
582 ENTRYAGGREGATOR => 'OR',
583 CASESENSITIVE => 0,
584 VALUE => $args{'q'}
585 );
586 $self->Limit(
587 FIELD => 'Summary',
588 SUBCLAUSE => 'NameOrSummary',
589 OPERATOR => 'LIKE',
590 ENTRYAGGREGATOR => 'OR',
591 CASESENSITIVE => 0,
592 VALUE => $args{'q'}
593 );
594 }
595
596
597 require Time::ParseDate;
598 foreach my $date (qw(Created< Created> LastUpdated< LastUpdated>)) {
599 next unless ( $args{$date} );
600 my $seconds = Time::ParseDate::parsedate( $args{$date}, FUZZY => 1, PREFER_PAST => 1 );
601 my $date_obj = RT::Date->new( $self->CurrentUser );
602 $date_obj->Set( Format => 'unix', Value => $seconds );
603 $dates->{$date} = $date_obj;
604
605 if ( $date =~ /^(.*?)<$/i ) {
606 $self->Limit(
607 FIELD => $1,
608 OPERATOR => "<=",
609 ENTRYAGGREGATOR => "AND",
610 VALUE => $date_obj->ISO
611 );
612 }
613
614 if ( $date =~ /^(.*?)>$/i ) {
615 $self->Limit(
616 FIELD => $1,
617 OPERATOR => ">=",
618 ENTRYAGGREGATOR => "AND",
619 VALUE => $date_obj->ISO
620 );
621 }
622
623 }
624
625 if ($args{'RefersTo'}) {
626 foreach my $link ( split( /\s+/, $args{'RefersTo'} ) ) {
627 next unless ($link);
628 $self->LimitRefersTo($link);
629 }
630 }
631
632 if ($args{'ReferredToBy'}) {
633 foreach my $link ( split( /\s+/, $args{'ReferredToBy'} ) ) {
634 next unless ($link);
635 $self->LimitReferredToBy($link);
636 }
637 }
638
639 if ( $args{'Topics'} ) {
640 my @Topics =
641 ( ref $args{'Topics'} eq 'ARRAY' )
642 ? @{ $args{'Topics'} }
643 : ( $args{'Topics'} );
644 @Topics = map { split } @Topics;
645 if ( $args{'ExpandTopics'} ) {
646 my %topics;
647 while (@Topics) {
648 my $id = shift @Topics;
649 next if $topics{$id};
650 my $Topics =
651 RT::Topics->new( $self->CurrentUser );
652 $Topics->Limit( FIELD => 'Parent', VALUE => $id );
653 push @Topics, $_->Id while $_ = $Topics->Next;
654 $topics{$id}++;
655 }
656 @Topics = keys %topics;
657 $args{'Topics'} = \@Topics;
658 }
659 $self->LimitTopics(@Topics);
660 }
661
662 my %cfs;
663 $customfields->LimitToLookupType(
664 RT::Article->new( $self->CurrentUser )
665 ->CustomFieldLookupType );
666 if ( $args{'Class'} ) {
667 my @Classes =
668 ( ref $args{'Class'} eq 'ARRAY' )
669 ? @{ $args{'Class'} }
670 : ( $args{'Class'} );
671 foreach my $class (@Classes) {
672 $customfields->LimitToGlobalOrObjectId($class);
673 }
674 }
675 else {
676 $customfields->LimitToGlobalOrObjectId();
677 }
678 while ( my $cf = $customfields->Next ) {
679 $cfs{ $cf->Name } = $cf->Id;
680 }
681
682 # reset the iterator because we use this to build the UI
683 $customfields->GotoFirstItem;
684
685 foreach my $field ( keys %cfs ) {
686
687 my @MatchLike =
688 ( ref $args{ $field . "~" } eq 'ARRAY' )
689 ? @{ $args{ $field . "~" } }
690 : ( $args{ $field . "~" } );
691 my @NoMatchLike =
692 ( ref $args{ $field . "!~" } eq 'ARRAY' )
693 ? @{ $args{ $field . "!~" } }
694 : ( $args{ $field . "!~" } );
695
696 my @Match =
697 ( ref $args{$field} eq 'ARRAY' )
698 ? @{ $args{$field} }
699 : ( $args{$field} );
700 my @NoMatch =
701 ( ref $args{ $field . "!" } eq 'ARRAY' )
702 ? @{ $args{ $field . "!" } }
703 : ( $args{ $field . "!" } );
704
705 foreach my $val (@MatchLike) {
706 next unless $val;
707 push @Match, "~" . $val;
708 }
709
710 foreach my $val (@NoMatchLike) {
711 next unless $val;
712 push @NoMatch, "~" . $val;
713 }
714
715 foreach my $value (@Match) {
716 next unless $value;
717 my $op;
718 if ( $value =~ /^~(.*)$/ ) {
719 $value = "%$1%";
720 $op = 'LIKE';
721 }
722 else {
723 $op = '=';
724 }
725 $self->LimitCustomField(
726 FIELD => $cfs{$field},
727 VALUE => $value,
728 CASESENSITIVE => 0,
729 ENTRYAGGREGATOR => 'OR',
730 OPERATOR => $op
731 );
732 }
733 foreach my $value (@NoMatch) {
734 next unless $value;
735 my $op;
736 if ( $value =~ /^~(.*)$/ ) {
737 $value = "%$1%";
738 $op = 'NOT LIKE';
739 }
740 else {
741 $op = '!=';
742 }
743 $self->LimitCustomField(
744 FIELD => $cfs{$field},
745 VALUE => $value,
746 CASESENSITIVE => 0,
747 ENTRYAGGREGATOR => 'OR',
748 OPERATOR => $op
749 );
750 }
751 }
752
753### Searches for any field
754
755 if ( $args{'Article~'} ) {
756 $self->LimitCustomField(
757 VALUE => $args{'Article~'},
758 ENTRYAGGREGATOR => 'OR',
759 OPERATOR => 'LIKE',
760 CASESENSITIVE => 0,
761 SUBCLAUSE => 'SearchAll'
762 );
763 $self->Limit(
764 SUBCLAUSE => 'SearchAll',
765 FIELD => "Name",
766 VALUE => $args{'Article~'},
767 ENTRYAGGREGATOR => 'OR',
768 CASESENSITIVE => 0,
769 OPERATOR => 'LIKE'
770 );
771 $self->Limit(
772 SUBCLAUSE => 'SearchAll',
773 FIELD => "Summary",
774 VALUE => $args{'Article~'},
775 ENTRYAGGREGATOR => 'OR',
776 CASESENSITIVE => 0,
777 OPERATOR => 'LIKE'
778 );
779 }
780
781 if ( $args{'Article!~'} ) {
782 $self->LimitCustomField(
783 VALUE => $args{'Article!~'},
784 OPERATOR => 'NOT LIKE',
785 CASESENSITIVE => 0,
786 SUBCLAUSE => 'SearchAll'
787 );
788 $self->Limit(
789 SUBCLAUSE => 'SearchAll',
790 FIELD => "Name",
791 VALUE => $args{'Article!~'},
792 ENTRYAGGREGATOR => 'AND',
793 CASESENSITIVE => 0,
794 OPERATOR => 'NOT LIKE'
795 );
796 $self->Limit(
797 SUBCLAUSE => 'SearchAll',
798 FIELD => "Summary",
799 VALUE => $args{'Article!~'},
800 ENTRYAGGREGATOR => 'AND',
801 CASESENSITIVE => 0,
802 OPERATOR => 'NOT LIKE'
803 );
804 }
805
806 foreach my $field (qw(Name Summary Class)) {
807
808 my @MatchLike =
809 ( ref $args{ $field . "~" } eq 'ARRAY' )
810 ? @{ $args{ $field . "~" } }
811 : ( $args{ $field . "~" } );
812 my @NoMatchLike =
813 ( ref $args{ $field . "!~" } eq 'ARRAY' )
814 ? @{ $args{ $field . "!~" } }
815 : ( $args{ $field . "!~" } );
816
817 my @Match =
818 ( ref $args{$field} eq 'ARRAY' )
819 ? @{ $args{$field} }
820 : ( $args{$field} );
821 my @NoMatch =
822 ( ref $args{ $field . "!" } eq 'ARRAY' )
823 ? @{ $args{ $field . "!" } }
824 : ( $args{ $field . "!" } );
825
826 foreach my $val (@MatchLike) {
827 next unless $val;
828 push @Match, "~" . $val;
829 }
830
831 foreach my $val (@NoMatchLike) {
832 next unless $val;
833 push @NoMatch, "~" . $val;
834 }
835
836 my $op;
837 foreach my $value (@Match) {
838 if ( $value && $value =~ /^~(.*)$/ ) {
839 $value = "%$1%";
840 $op = 'LIKE';
841 }
842 else {
843 $op = '=';
844 }
845
846 # preprocess Classes, so we can search on class
847 if ( $field eq 'Class' && $value ) {
848 my $class = RT::Class->new($RT::SystemUser);
849 $class->Load($value);
850 $value = $class->Id;
851 }
852
853 # now that we've pruned the value, get out if it's different.
854 next unless $value;
855
856 $self->Limit(
857 SUBCLAUSE => $field . 'Match',
858 FIELD => $field,
859 OPERATOR => $op,
860 CASESENSITIVE => 0,
861 VALUE => $value,
862 ENTRYAGGREGATOR => 'OR'
863 );
864
865 }
866 foreach my $value (@NoMatch) {
867
868 # preprocess Classes, so we can search on class
869 if ( $value && $value =~ /^~(.*)/ ) {
870 $value = "%$1%";
871 $op = 'NOT LIKE';
872 }
873 else {
874 $op = '!=';
875 }
876 if ( $field eq 'Class' ) {
877 my $class = RT::Class->new($RT::SystemUser);
878 $class->Load($value);
879 $value = $class->Id;
880 }
881
882 # now that we've pruned the value, get out if it's different.
883 next unless $value;
884
885 $self->Limit(
886 SUBCLAUSE => $field . 'NoMatch',
887 OPERATOR => $op,
888 VALUE => $value,
889 CASESENSITIVE => 0,
890 FIELD => $field,
891 ENTRYAGGREGATOR => 'AND'
892 );
893
894 }
895 }
896
897 if ($order_by && @$order_by) {
898 if ( $order_by->[0] && $order_by->[0] =~ /\|/ ) {
899 @$order_by = split '|', $order_by->[0];
900 @$order = split '|', $order->[0];
901 }
902 my @tmp =
903 map { { FIELD => $order_by->[$_], ORDER => $order->[$_] } } 0 .. $#{$order_by};
904 $self->OrderByCols(@tmp);
905 }
906
907 return 1;
908}
909
910
911=head2 NewItem
912
913Returns an empty new RT::Article item
914
915=cut
916
917sub NewItem {
918 my $self = shift;
919 return(RT::Article->new($self->CurrentUser));
920}
921
922
923
924RT::Base->_ImportOverlays();
925
9261;
927
9281;