-# BEGIN LICENSE BLOCK
-#
-# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
-#
-# (Except where explictly superceded by other copyright notices)
-#
+# BEGIN BPS TAGGED BLOCK {{{
+#
+# COPYRIGHT:
+#
+# This software is Copyright (c) 1996-2005 Best Practical Solutions, LLC
+# <jesse@bestpractical.com>
+#
+# (Except where explicitly superseded by other copyright notices)
+#
+#
+# LICENSE:
+#
# This work is made available to you under the terms of Version 2 of
# the GNU General Public License. A copy of that license should have
# been provided with this software, but in any event can be snarfed
# from www.gnu.org.
-#
+#
# This work is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
-#
-# Unless otherwise specified, all modifications, corrections or
-# extensions to this work which alter its source code become the
-# property of Best Practical Solutions, LLC when submitted for
-# inclusion in the work.
-#
-#
-# END LICENSE BLOCK
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+#
+#
+# CONTRIBUTION SUBMISSION POLICY:
+#
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+#
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+#
+# END BPS TAGGED BLOCK }}}
# Major Changes:
# - Decimated ProcessRestrictions and broke it into multiple
=begin testing
ok (require RT::Tickets);
+ok( my $testtickets = RT::Tickets->new( $RT::SystemUser ) );
+ok( $testtickets->LimitStatus( VALUE => 'deleted' ) );
+# Should be zero until 'allow_deleted_search'
+ok( $testtickets->Count == 0 );
=end testing
=cut
+
+package RT::Tickets;
+
use strict;
+
+package RT::Tickets;
+
no warnings qw(redefine);
use vars qw(@SORTFIELDS);
-
+use RT::CustomFields;
# Configuration Tables:
# FIELDS is a mapping of searchable Field name, to Type, and other
# metadata.
-my %FIELDS =
- ( Status => ['ENUM'],
- Queue => ['ENUM' => 'Queue',],
- Type => ['ENUM',],
- Creator => ['ENUM' => 'User',],
- LastUpdatedBy => ['ENUM' => 'User',],
- Owner => ['ENUM' => 'User',],
- EffectiveId => ['INT',],
- id => ['INT',],
- InitialPriority => ['INT',],
- FinalPriority => ['INT',],
- Priority => ['INT',],
- TimeLeft => ['INT',],
- TimeWorked => ['INT',],
- MemberOf => ['LINK' => To => 'MemberOf', ],
- DependsOn => ['LINK' => To => 'DependsOn',],
- RefersTo => ['LINK' => To => 'RefersTo',],
- HasMember => ['LINK' => From => 'MemberOf',],
- DependentOn => ['LINK' => From => 'DependsOn',],
- ReferredToBy => ['LINK' => From => 'RefersTo',],
-# HasDepender => ['LINK',],
-# RelatedTo => ['LINK',],
- Told => ['DATE' => 'Told',],
- Starts => ['DATE' => 'Starts',],
- Started => ['DATE' => 'Started',],
- Due => ['DATE' => 'Due',],
- Resolved => ['DATE' => 'Resolved',],
- LastUpdated => ['DATE' => 'LastUpdated',],
- Created => ['DATE' => 'Created',],
- Subject => ['STRING',],
- Type => ['STRING',],
- Content => ['TRANSFIELD',],
- ContentType => ['TRANSFIELD',],
- Filename => ['TRANSFIELD',],
- TransactionDate => ['TRANSDATE',],
- Requestor => ['WATCHERFIELD' => 'Requestor',],
- CC => ['WATCHERFIELD' => 'Cc',],
- AdminCC => ['WATCHERFIELD' => 'AdminCC',],
- Watcher => ['WATCHERFIELD'],
- LinkedTo => ['LINKFIELD',],
- CustomFieldValue =>['CUSTOMFIELD',],
- CF => ['CUSTOMFIELD',],
- );
+my %FIELDS = (
+ Status => ['ENUM'],
+ Queue => [ 'ENUM' => 'Queue', ],
+ Type => [ 'ENUM', ],
+ Creator => [ 'ENUM' => 'User', ],
+ LastUpdatedBy => [ 'ENUM' => 'User', ],
+ Owner => [ 'WATCHERFIELD' => 'Owner', ],
+ EffectiveId => [ 'INT', ],
+ id => [ 'INT', ],
+ InitialPriority => [ 'INT', ],
+ FinalPriority => [ 'INT', ],
+ Priority => [ 'INT', ],
+ TimeLeft => [ 'INT', ],
+ TimeWorked => [ 'INT', ],
+ MemberOf => [ 'LINK' => To => 'MemberOf', ],
+ DependsOn => [ 'LINK' => To => 'DependsOn', ],
+ RefersTo => [ 'LINK' => To => 'RefersTo', ],
+ HasMember => [ 'LINK' => From => 'MemberOf', ],
+ DependentOn => [ 'LINK' => From => 'DependsOn', ],
+ DependedOnBy => [ 'LINK' => From => 'DependsOn', ],
+ ReferredToBy => [ 'LINK' => From => 'RefersTo', ],
+ Told => [ 'DATE' => 'Told', ],
+ Starts => [ 'DATE' => 'Starts', ],
+ Started => [ 'DATE' => 'Started', ],
+ Due => [ 'DATE' => 'Due', ],
+ Resolved => [ 'DATE' => 'Resolved', ],
+ LastUpdated => [ 'DATE' => 'LastUpdated', ],
+ Created => [ 'DATE' => 'Created', ],
+ Subject => [ 'STRING', ],
+ Content => [ 'TRANSFIELD', ],
+ ContentType => [ 'TRANSFIELD', ],
+ Filename => [ 'TRANSFIELD', ],
+ TransactionDate => [ 'TRANSDATE', ],
+ Requestor => [ 'WATCHERFIELD' => 'Requestor', ],
+ Requestors => [ 'WATCHERFIELD' => 'Requestor', ],
+ Cc => [ 'WATCHERFIELD' => 'Cc', ],
+ AdminCc => [ 'WATCHERFIELD' => 'AdminCc', ],
+ Watcher => ['WATCHERFIELD'],
+ LinkedTo => [ 'LINKFIELD', ],
+ CustomFieldValue => [ 'CUSTOMFIELD', ],
+ CF => [ 'CUSTOMFIELD', ],
+ Updated => [ 'TRANSDATE', ],
+ RequestorGroup => [ 'MEMBERSHIPFIELD' => 'Requestor', ],
+ CCGroup => [ 'MEMBERSHIPFIELD' => 'Cc', ],
+ AdminCCGroup => [ 'MEMBERSHIPFIELD' => 'AdminCc', ],
+ WatcherGroup => [ 'MEMBERSHIPFIELD', ],
+);
# Mapping of Field Type to Function
-my %dispatch =
- ( ENUM => \&_EnumLimit,
- INT => \&_IntLimit,
- LINK => \&_LinkLimit,
- DATE => \&_DateLimit,
- STRING => \&_StringLimit,
- TRANSFIELD => \&_TransLimit,
- TRANSDATE => \&_TransDateLimit,
+my %dispatch = (
+ ENUM => \&_EnumLimit,
+ INT => \&_IntLimit,
+ LINK => \&_LinkLimit,
+ DATE => \&_DateLimit,
+ STRING => \&_StringLimit,
+ TRANSFIELD => \&_TransLimit,
+ TRANSDATE => \&_TransDateLimit,
WATCHERFIELD => \&_WatcherLimit,
- LINKFIELD => \&_LinkFieldLimit,
- CUSTOMFIELD => \&_CustomFieldLimit,
- );
-my %can_bundle =
- ( WATCHERFIELD => "yeps",
- );
+ MEMBERSHIPFIELD => \&_WatcherMembershipLimit,
+ LINKFIELD => \&_LinkFieldLimit,
+ CUSTOMFIELD => \&_CustomFieldLimit,
+);
+my %can_bundle = ( WATCHERFIELD => "yes", );
# Default EntryAggregator per type
# if you specify OP, you must specify all valid OPs
my %DefaultEA = (
- INT => 'AND',
- ENUM => { '=' => 'OR',
- '!='=> 'AND'
- },
- DATE => { '=' => 'OR',
- '>='=> 'AND',
- '<='=> 'AND',
- '>' => 'AND',
- '<' => 'AND'
- },
- STRING => { '=' => 'OR',
- '!='=> 'AND',
- 'LIKE'=> 'AND',
- 'NOT LIKE' => 'AND'
- },
- TRANSFIELD => 'AND',
- TRANSDATE => 'AND',
- LINK => 'OR',
- LINKFIELD => 'AND',
- TARGET => 'AND',
- BASE => 'AND',
- WATCHERFIELD => { '=' => 'OR',
- '!='=> 'AND',
- 'LIKE'=> 'OR',
- 'NOT LIKE' => 'AND'
- },
-
- CUSTOMFIELD => 'OR',
- );
-
+ INT => 'AND',
+ ENUM => {
+ '=' => 'OR',
+ '!=' => 'AND'
+ },
+ DATE => {
+ '=' => 'OR',
+ '>=' => 'AND',
+ '<=' => 'AND',
+ '>' => 'AND',
+ '<' => 'AND'
+ },
+ STRING => {
+ '=' => 'OR',
+ '!=' => 'AND',
+ 'LIKE' => 'AND',
+ 'NOT LIKE' => 'AND'
+ },
+ TRANSFIELD => 'AND',
+ TRANSDATE => 'AND',
+ LINK => 'OR',
+ LINKFIELD => 'AND',
+ TARGET => 'AND',
+ BASE => 'AND',
+ WATCHERFIELD => {
+ '=' => 'OR',
+ '!=' => 'AND',
+ 'LIKE' => 'OR',
+ 'NOT LIKE' => 'AND'
+ },
+
+ CUSTOMFIELD => 'OR',
+);
# Helper functions for passing the above lexically scoped tables above
# into Tickets_Overlay_SQL.
-sub FIELDS { return \%FIELDS }
-sub dispatch { return \%dispatch }
+sub FIELDS { return \%FIELDS }
+sub dispatch { return \%dispatch }
sub can_bundle { return \%can_bundle }
# Bring in the clowns.
# {{{ sub SortFields
@SORTFIELDS = qw(id Status
- Queue Subject
- Owner Created Due Starts Started
- Told
- Resolved LastUpdated Priority TimeWorked TimeLeft);
+ Queue Subject
+ Owner Created Due Starts Started
+ Told
+ Resolved LastUpdated Priority TimeWorked TimeLeft);
=head2 SortFields
=cut
sub SortFields {
- my $self = shift;
- return(@SORTFIELDS);
+ my $self = shift;
+ return (@SORTFIELDS);
}
-
# }}}
-
# BEGIN SQL STUFF *********************************
=head1 Limit Helper Routines
=cut
sub _EnumLimit {
- my ($sb,$field,$op,$value,@rest) = @_;
+ my ( $sb, $field, $op, $value, @rest ) = @_;
- # SQL::Statement changes != to <>. (Can we remove this now?)
- $op = "!=" if $op eq "<>";
+ # SQL::Statement changes != to <>. (Can we remove this now?)
+ $op = "!=" if $op eq "<>";
- die "Invalid Operation: $op for $field"
- unless $op eq "=" or $op eq "!=";
+ die "Invalid Operation: $op for $field"
+ unless $op eq "="
+ or $op eq "!=";
- my $meta = $FIELDS{$field};
- if (defined $meta->[1]) {
- my $class = "RT::" . $meta->[1];
- my $o = $class->new($sb->CurrentUser);
- $o->Load( $value );
- $value = $o->Id;
- }
- $sb->_SQLLimit( FIELD => $field,,
- VALUE => $value,
- OPERATOR => $op,
- @rest,
- );
+ my $meta = $FIELDS{$field};
+ if ( defined $meta->[1] ) {
+ my $class = "RT::" . $meta->[1];
+ my $o = $class->new( $sb->CurrentUser );
+ $o->Load($value);
+ $value = $o->Id;
+ }
+ $sb->_SQLLimit(
+ FIELD => $field,
+ VALUE => $value,
+ OPERATOR => $op,
+ @rest,
+ );
}
=head2 _IntLimit
=cut
sub _IntLimit {
- my ($sb,$field,$op,$value,@rest) = @_;
+ my ( $sb, $field, $op, $value, @rest ) = @_;
- die "Invalid Operator $op for $field"
- unless $op =~ /^(=|!=|>|<|>=|<=)$/;
+ die "Invalid Operator $op for $field"
+ unless $op =~ /^(=|!=|>|<|>=|<=)$/;
- $sb->_SQLLimit(
- FIELD => $field,
- VALUE => $value,
- OPERATOR => $op,
- @rest,
- );
+ $sb->_SQLLimit(
+ FIELD => $field,
+ VALUE => $value,
+ OPERATOR => $op,
+ @rest,
+ );
}
-
=head2 _LinkLimit
Handle fields which deal with links between tickets. (MemberOf, DependsOn)
Meta Data:
1: Direction (From,To)
- 2: Relationship Type (MemberOf, DependsOn,RefersTo)
+ 2: Link Type (MemberOf, DependsOn,RefersTo)
=cut
sub _LinkLimit {
- my ($sb,$field,$op,$value,@rest) = @_;
-
- die "Op must be ="
- unless $op eq "=";
+ my ( $sb, $field, $op, $value, @rest ) = @_;
- my $meta = $FIELDS{$field};
- die "Incorrect Meta Data for $field"
- unless (defined $meta->[1] and defined $meta->[2]);
+ my $meta = $FIELDS{$field};
+ die "Invalid Operator $op for $field" unless $op =~ /^(=|!=|IS)/io;
- my $LinkAlias = $sb->NewAlias ('Links');
+ die "Incorrect Metadata for $field"
+ unless ( defined $meta->[1] and defined $meta->[2] );
- $sb->_OpenParen();
-
- $sb->_SQLLimit(
- ALIAS => $LinkAlias,
- FIELD => 'Type',
- OPERATOR => '=',
- VALUE => $meta->[2],
- @rest,
- );
-
- if ($meta->[1] eq "To") {
- my $matchfield = ( $value =~ /^(\d+)$/ ? "LocalTarget" : "Target" );
-
- $sb->_SQLLimit(
- ALIAS => $LinkAlias,
- ENTRYAGGREGATOR => 'AND',
- FIELD => $matchfield,
- OPERATOR => '=',
- VALUE => $value ,
- );
+ my $direction = $meta->[1];
- #If we're searching on target, join the base to ticket.id
- $sb->Join( ALIAS1 => 'main', FIELD1 => $sb->{'primary_key'},
- ALIAS2 => $LinkAlias, FIELD2 => 'LocalBase');
+ my $matchfield;
+ my $linkfield;
+ my $is_local = 1;
+ my $is_null = 0;
+ if ( $direction eq 'To' ) {
+ $matchfield = "Target";
+ $linkfield = "Base";
- } elsif ( $meta->[1] eq "From" ) {
- my $matchfield = ( $value =~ /^(\d+)$/ ? "LocalBase" : "Base" );
+ }
+ elsif ( $direction eq 'From' ) {
+ $linkfield = "Target";
+ $matchfield = "Base";
- $sb->_SQLLimit(
- ALIAS => $LinkAlias,
- ENTRYAGGREGATOR => 'AND',
- FIELD => $matchfield,
- OPERATOR => '=',
- VALUE => $value ,
- );
+ }
+ else {
+ die "Invalid link direction '$meta->[1]' for $field\n";
+ }
- #If we're searching on base, join the target to ticket.id
- $sb->Join( ALIAS1 => 'main', FIELD1 => $sb->{'primary_key'},
- ALIAS2 => $LinkAlias, FIELD2 => 'LocalTarget');
+ if ( $op eq '=' || $op =~ /^is/oi ) {
+ if ( $value eq '' || $value =~ /^null$/io ) {
+ $is_null = 1;
+ }
+ elsif ( $value =~ /\D/o ) {
+ $is_local = 0;
+ }
+ else {
+ $is_local = 1;
+ }
+ }
- } else {
- die "Invalid link direction '$meta->[1]' for $field\n";
- }
+#For doing a left join to find "unlinked tickets" we want to generate a query that looks like this
+# SELECT main.* FROM Tickets main
+# LEFT JOIN Links Links_1 ON ( (Links_1.Type = 'MemberOf')
+# AND(main.id = Links_1.LocalTarget))
+# WHERE ((main.EffectiveId = main.id))
+# AND ((main.Status != 'deleted'))
+# AND (Links_1.LocalBase IS NULL);
+
+ if ($is_null) {
+ my $linkalias = $sb->Join(
+ TYPE => 'left',
+ ALIAS1 => 'main',
+ FIELD1 => 'id',
+ TABLE2 => 'Links',
+ FIELD2 => 'Local' . $linkfield
+ );
+
+ $sb->SUPER::Limit(
+ LEFTJOIN => $linkalias,
+ FIELD => 'Type',
+ OPERATOR => '=',
+ VALUE => $meta->[2],
+ @rest,
+ );
+
+ $sb->_SQLLimit(
+ ALIAS => $linkalias,
+ ENTRYAGGREGATOR => 'AND',
+ FIELD => ( $is_local ? "Local$matchfield" : $matchfield ),
+ OPERATOR => 'IS',
+ VALUE => 'NULL',
+ QUOTEVALUE => '0',
+ );
- $sb->_CloseParen();
+ }
+ else {
+ $sb->{_sql_linkalias} = $sb->NewAlias('Links')
+ unless defined $sb->{_sql_linkalias};
+
+ $sb->_OpenParen();
+
+ $sb->_SQLLimit(
+ ALIAS => $sb->{_sql_linkalias},
+ FIELD => 'Type',
+ OPERATOR => '=',
+ VALUE => $meta->[2],
+ @rest,
+ );
+
+ $sb->_SQLLimit(
+ ALIAS => $sb->{_sql_linkalias},
+ ENTRYAGGREGATOR => 'AND',
+ FIELD => ( $is_local ? "Local$matchfield" : $matchfield ),
+ OPERATOR => '=',
+ VALUE => $value,
+ );
+
+ #If we're searching on target, join the base to ticket.id
+ $sb->_SQLJoin(
+ ALIAS1 => 'main',
+ FIELD1 => $sb->{'primary_key'},
+ ALIAS2 => $sb->{_sql_linkalias},
+ FIELD2 => 'Local' . $linkfield
+ );
+
+ $sb->_CloseParen();
+ }
}
=head2 _DateLimit
Handle date fields. (Created, LastTold..)
Meta Data:
- 1: type of relationship. (Probably not necessary.)
+ 1: type of link. (Probably not necessary.)
=cut
sub _DateLimit {
- my ($sb,$field,$op,$value,@rest) = @_;
+ my ( $sb, $field, $op, $value, @rest ) = @_;
- die "Invalid Date Op: $op"
- unless $op =~ /^(=|>|<|>=|<=)$/;
+ die "Invalid Date Op: $op"
+ unless $op =~ /^(=|>|<|>=|<=)$/;
- my $meta = $FIELDS{$field};
- die "Incorrect Meta Data for $field"
- unless (defined $meta->[1]);
+ my $meta = $FIELDS{$field};
+ die "Incorrect Meta Data for $field"
+ unless ( defined $meta->[1] );
- require Time::ParseDate;
- use POSIX 'strftime';
+ use POSIX 'strftime';
- # FIXME: Replace me with RT::Date( Type => 'unknown' ...)
- my $time = Time::ParseDate::parsedate( $value,
- UK => $RT::DateDayBeforeMonth,
- PREFER_PAST => $RT::AmbiguousDayInPast,
- PREFER_FUTURE => !($RT::AmbiguousDayInPast),
- FUZZY => 1
- );
+ my $date = RT::Date->new( $sb->CurrentUser );
+ $date->Set( Format => 'unknown', Value => $value );
+ my $time = $date->Unix;
- if ($op eq "=") {
- # if we're specifying =, that means we want everything on a
- # particular single day. in the database, we need to check for >
- # and < the edges of that day.
+ if ( $op eq "=" ) {
- my $daystart = strftime("%Y-%m-%d %H:%M",
- gmtime($time - ( $time % 86400 )));
- my $dayend = strftime("%Y-%m-%d %H:%M",
- gmtime($time + ( 86399 - $time % 86400 )));
+ # if we're specifying =, that means we want everything on a
+ # particular single day. in the database, we need to check for >
+ # and < the edges of that day.
- $sb-> _OpenParen;
+ my $daystart = strftime( "%Y-%m-%d %H:%M",
+ gmtime( $time - ( $time % 86400 ) ) );
+ my $dayend = strftime( "%Y-%m-%d %H:%M",
+ gmtime( $time + ( 86399 - $time % 86400 ) ) );
- $sb->_SQLLimit(
- FIELD => $meta->[1],
- OPERATOR => ">=",
- VALUE => $daystart,
- @rest,
- );
+ $sb->_OpenParen;
- $sb->_SQLLimit(
- FIELD => $meta->[1],
- OPERATOR => "<=",
- VALUE => $dayend,
- @rest,
- ENTRYAGGREGATOR => 'AND',
- );
+ $sb->_SQLLimit(
+ FIELD => $meta->[1],
+ OPERATOR => ">=",
+ VALUE => $daystart,
+ @rest,
+ );
- $sb-> _CloseParen;
+ $sb->_SQLLimit(
+ FIELD => $meta->[1],
+ OPERATOR => "<=",
+ VALUE => $dayend,
+ @rest,
+ ENTRYAGGREGATOR => 'AND',
+ );
- } else {
- $value = strftime("%Y-%m-%d %H:%M", gmtime($time));
- $sb->_SQLLimit(
- FIELD => $meta->[1],
- OPERATOR => $op,
- VALUE => $value,
- @rest,
- );
- }
+ $sb->_CloseParen;
+
+ }
+ else {
+ $value = strftime( "%Y-%m-%d %H:%M", gmtime($time) );
+ $sb->_SQLLimit(
+ FIELD => $meta->[1],
+ OPERATOR => $op,
+ VALUE => $value,
+ @rest,
+ );
+ }
}
=head2 _StringLimit
=cut
sub _StringLimit {
- my ($sb,$field,$op,$value,@rest) = @_;
+ my ( $sb, $field, $op, $value, @rest ) = @_;
- # FIXME:
- # Valid Operators:
- # =, !=, LIKE, NOT LIKE
+ # FIXME:
+ # Valid Operators:
+ # =, !=, LIKE, NOT LIKE
- $sb->_SQLLimit(
- FIELD => $field,
- OPERATOR => $op,
- VALUE => $value,
- CASESENSITIVE => 0,
- @rest,
- );
+ $sb->_SQLLimit(
+ FIELD => $field,
+ OPERATOR => $op,
+ VALUE => $value,
+ CASESENSITIVE => 0,
+ @rest,
+ );
}
=head2 _TransDateLimit
=cut
+# This routine should really be factored into translimit.
sub _TransDateLimit {
- my ($sb,$field,$op,$value,@rest) = @_;
-
- # See the comments for TransLimit, they apply here too
-
- $sb->{_sql_transalias} = $sb->NewAlias ('Transactions')
- unless defined $sb->{_sql_transalias};
- $sb->{_sql_trattachalias} = $sb->NewAlias ('Attachments')
- unless defined $sb->{_sql_trattachalias};
+ my ( $sb, $field, $op, $value, @rest ) = @_;
+
+ # See the comments for TransLimit, they apply here too
+
+ $sb->{_sql_transalias} = $sb->NewAlias('Transactions')
+ unless defined $sb->{_sql_transalias};
+
+ my $date = RT::Date->new( $sb->CurrentUser );
+ $date->Set( Format => 'unknown', Value => $value );
+ my $time = $date->Unix;
+
+ $sb->_OpenParen;
+ if ( $op eq "=" ) {
+
+ # if we're specifying =, that means we want everything on a
+ # particular single day. in the database, we need to check for >
+ # and < the edges of that day.
+
+ my $daystart = strftime( "%Y-%m-%d %H:%M",
+ gmtime( $time - ( $time % 86400 ) ) );
+ my $dayend = strftime( "%Y-%m-%d %H:%M",
+ gmtime( $time + ( 86399 - $time % 86400 ) ) );
+
+ $sb->_SQLLimit(
+ ALIAS => $sb->{_sql_transalias},
+ FIELD => 'Created',
+ OPERATOR => ">=",
+ VALUE => $daystart,
+ CASESENSITIVE => 0,
+ @rest
+ );
+ $sb->_SQLLimit(
+ ALIAS => $sb->{_sql_transalias},
+ FIELD => 'Created',
+ OPERATOR => "<=",
+ VALUE => $dayend,
+ CASESENSITIVE => 0,
+ @rest,
+ ENTRYAGGREGATOR => 'AND',
+ );
- $sb->_OpenParen;
+ }
- # Join Transactions To Attachments
- $sb->Join( ALIAS1 => $sb->{_sql_trattachalias}, FIELD1 => 'TransactionId',
- ALIAS2 => $sb->{_sql_transalias}, FIELD2 => 'id');
+ # not searching for a single day
+ else {
- # Join Transactions to Tickets
- $sb->Join( ALIAS1 => 'main', FIELD1 => $sb->{'primary_key'}, # UGH!
- ALIAS2 => $sb->{_sql_transalias}, FIELD2 => 'Ticket');
+ #Search for the right field
+ $sb->_SQLLimit(
+ ALIAS => $sb->{_sql_transalias},
+ FIELD => 'Created',
+ OPERATOR => $op,
+ VALUE => $value,
+ CASESENSITIVE => 0,
+ @rest
+ );
+ }
- my $d = new RT::Date( $sb->CurrentUser );
- $d->Set( Format => 'ISO', Value => $value);
- $value = $d->ISO;
+ # Join Transactions to Tickets
+ $sb->_SQLJoin(
+ ALIAS1 => 'main',
+ FIELD1 => $sb->{'primary_key'}, # UGH!
+ ALIAS2 => $sb->{_sql_transalias},
+ FIELD2 => 'ObjectId'
+ );
- #Search for the right field
- $sb->_SQLLimit(ALIAS => $sb->{_sql_trattachalias},
- FIELD => 'Created',
- OPERATOR => $op,
- VALUE => $value,
- CASESENSITIVE => 0,
- @rest
- );
+ $sb->SUPER::Limit(
+ ALIAS => $sb->{_sql_transalias},
+ FIELD => 'ObjectType',
+ VALUE => 'RT::Ticket'
+ );
- $sb->_CloseParen;
+ $sb->_CloseParen;
}
=head2 _TransLimit
=cut
sub _TransLimit {
- # Content, ContentType, Filename
-
- # If only this was this simple. We've got to do something
- # complicated here:
- #Basically, we want to make sure that the limits apply to
- #the same attachment, rather than just another attachment
- #for the same ticket, no matter how many clauses we lump
- #on. We put them in TicketAliases so that they get nuked
- #when we redo the join.
+ # Content, ContentType, Filename
- # In the SQL, we might have
- # (( Content = foo ) or ( Content = bar AND Content = baz ))
- # The AND group should share the same Alias.
+ # If only this was this simple. We've got to do something
+ # complicated here:
- # Actually, maybe it doesn't matter. We use the same alias and it
- # works itself out? (er.. different.)
+ #Basically, we want to make sure that the limits apply to
+ #the same attachment, rather than just another attachment
+ #for the same ticket, no matter how many clauses we lump
+ #on. We put them in TicketAliases so that they get nuked
+ #when we redo the join.
- # Steal more from _ProcessRestrictions
+ # In the SQL, we might have
+ # (( Content = foo ) or ( Content = bar AND Content = baz ))
+ # The AND group should share the same Alias.
- # FIXME: Maybe look at the previous FooLimit call, and if it was a
- # TransLimit and EntryAggregator == AND, reuse the Aliases?
+ # Actually, maybe it doesn't matter. We use the same alias and it
+ # works itself out? (er.. different.)
- # Or better - store the aliases on a per subclause basis - since
- # those are going to be the things we want to relate to each other,
- # anyway.
+ # Steal more from _ProcessRestrictions
- # maybe we should not allow certain kinds of aggregation of these
- # clauses and do a psuedo regex instead? - the problem is getting
- # them all into the same subclause when you have (A op B op C) - the
- # way they get parsed in the tree they're in different subclauses.
+ # FIXME: Maybe look at the previous FooLimit call, and if it was a
+ # TransLimit and EntryAggregator == AND, reuse the Aliases?
- my ($sb,$field,$op,$value,@rest) = @_;
+ # Or better - store the aliases on a per subclause basis - since
+ # those are going to be the things we want to relate to each other,
+ # anyway.
- $sb->{_sql_transalias} = $sb->NewAlias ('Transactions')
- unless defined $sb->{_sql_transalias};
- $sb->{_sql_trattachalias} = $sb->NewAlias ('Attachments')
- unless defined $sb->{_sql_trattachalias};
+ # maybe we should not allow certain kinds of aggregation of these
+ # clauses and do a psuedo regex instead? - the problem is getting
+ # them all into the same subclause when you have (A op B op C) - the
+ # way they get parsed in the tree they're in different subclauses.
- $sb->_OpenParen;
+ my ( $self, $field, $op, $value, @rest ) = @_;
- # Join Transactions To Attachments
- $sb->Join( ALIAS1 => $sb->{_sql_trattachalias}, FIELD1 => 'TransactionId',
- ALIAS2 => $sb->{_sql_transalias}, FIELD2 => 'id');
+ $self->{_sql_transalias} = $self->NewAlias('Transactions')
+ unless defined $self->{_sql_transalias};
+ $self->{_sql_trattachalias} = $self->NewAlias('Attachments')
+ unless defined $self->{_sql_trattachalias};
- # Join Transactions to Tickets
- $sb->Join( ALIAS1 => 'main', FIELD1 => $sb->{'primary_key'}, # UGH!
- ALIAS2 => $sb->{_sql_transalias}, FIELD2 => 'Ticket');
+ $self->_OpenParen;
- #Search for the right field
- $sb->_SQLLimit(ALIAS => $sb->{_sql_trattachalias},
- FIELD => $field,
- OPERATOR => $op,
- VALUE => $value,
- CASESENSITIVE => 0,
- @rest
- );
+ #Search for the right field
+ $self->_SQLLimit(
+ ALIAS => $self->{_sql_trattachalias},
+ FIELD => $field,
+ OPERATOR => $op,
+ VALUE => $value,
+ CASESENSITIVE => 0,
+ @rest
+ );
+
+ $self->_SQLJoin(
+ ALIAS1 => $self->{_sql_trattachalias},
+ FIELD1 => 'TransactionId',
+ ALIAS2 => $self->{_sql_transalias},
+ FIELD2 => 'id'
+ );
+
+ # Join Transactions to Tickets
+ $self->_SQLJoin(
+ ALIAS1 => 'main',
+ FIELD1 => $self->{'primary_key'}, # Why not use "id" here?
+ ALIAS2 => $self->{_sql_transalias},
+ FIELD2 => 'ObjectId'
+ );
+
+ $self->SUPER::Limit(
+ ALIAS => $self->{_sql_transalias},
+ FIELD => 'ObjectType',
+ VALUE => 'RT::Ticket',
+ ENTRYAGGREGATOR => 'AND'
+ );
- $sb->_CloseParen;
+ $self->_CloseParen;
}
Meta Data:
1: Field to query on
-=cut
-sub _WatcherLimit {
- my ($self,$field,$op,$value,@rest) = @_;
- my %rest = @rest;
+=begin testing
+
+# Test to make sure that you can search for tickets by requestor address and
+# by requestor name.
+
+my ($id,$msg);
+my $u1 = RT::User->new($RT::SystemUser);
+($id, $msg) = $u1->Create( Name => 'RequestorTestOne', EmailAddress => 'rqtest1@example.com');
+ok ($id,$msg);
+my $u2 = RT::User->new($RT::SystemUser);
+($id, $msg) = $u2->Create( Name => 'RequestorTestTwo', EmailAddress => 'rqtest2@example.com');
+ok ($id,$msg);
+
+my $t1 = RT::Ticket->new($RT::SystemUser);
+my ($trans);
+($id,$trans,$msg) =$t1->Create (Queue => 'general', Subject => 'Requestor test one', Requestor => [$u1->EmailAddress]);
+ok ($id, $msg);
+
+my $t2 = RT::Ticket->new($RT::SystemUser);
+($id,$trans,$msg) =$t2->Create (Queue => 'general', Subject => 'Requestor test one', Requestor => [$u2->EmailAddress]);
+ok ($id, $msg);
+
- $self->_OpenParen;
+my $t3 = RT::Ticket->new($RT::SystemUser);
+($id,$trans,$msg) =$t3->Create (Queue => 'general', Subject => 'Requestor test one', Requestor => [$u2->EmailAddress, $u1->EmailAddress]);
+ok ($id, $msg);
- my $groups = $self->NewAlias('Groups');
- my $groupmembers = $self->NewAlias('CachedGroupMembers');
- my $users = $self->NewAlias('Users');
+my $tix1 = RT::Tickets->new($RT::SystemUser);
+$tix1->FromSQL('Requestor.EmailAddress LIKE "rqtest1" OR Requestor.EmailAddress LIKE "rqtest2"');
- #Find user watchers
-# my $subclause = undef;
-# my $aggregator = 'OR';
-# if ($restriction->{'OPERATOR'} =~ /!|NOT/i ){
-# $subclause = 'AndEmailIsNot';
-# $aggregator = 'AND';
-# }
+is ($tix1->Count, 3);
- if (ref $field) { # gross hack
- my @bundle = @$field;
+my $tix2 = RT::Tickets->new($RT::SystemUser);
+$tix2->FromSQL('Requestor.Name LIKE "TestOne" OR Requestor.Name LIKE "TestTwo"');
+
+is ($tix2->Count, 3);
+
+
+my $tix3 = RT::Tickets->new($RT::SystemUser);
+$tix3->FromSQL('Requestor.EmailAddress LIKE "rqtest1"');
+
+is ($tix3->Count, 2);
+
+my $tix4 = RT::Tickets->new($RT::SystemUser);
+$tix4->FromSQL('Requestor.Name LIKE "TestOne" ');
+
+is ($tix4->Count, 2);
+
+# Searching for tickets that have two requestors isn't supported
+# There's no way to differentiate "one requestor name that matches foo and bar"
+# and "two requestors, one matching foo and one matching bar"
+
+# my $tix5 = RT::Tickets->new($RT::SystemUser);
+# $tix5->FromSQL('Requestor.Name LIKE "TestOne" AND Requestor.Name LIKE "TestTwo"');
+#
+# is ($tix5->Count, 1);
+#
+# my $tix6 = RT::Tickets->new($RT::SystemUser);
+# $tix6->FromSQL('Requestor.EmailAddress LIKE "rqtest1" AND Requestor.EmailAddress LIKE "rqtest2"');
+#
+# is ($tix6->Count, 1);
+
+
+=end testing
+
+=cut
+
+sub _WatcherLimit {
+ my $self = shift;
+ my $field = shift;
+ my $op = shift;
+ my $value = shift;
+ my %rest = (@_);
+
+ # Find out what sort of watcher we're looking for
+ my $fieldname;
+ if ( ref $field ) {
+ $fieldname = $field->[0]->[0];
+ }
+ else {
+ $fieldname = $field;
+ $field = [ [ $field, $op, $value, %rest ] ]; # gross hack
+ }
+ my $meta = $FIELDS{$fieldname};
+ my $type = ( defined $meta->[1] ? $meta->[1] : undef );
+
+ # Owner was ENUM field, so "Owner = 'xxx'" allowed user to
+ # search by id and Name at the same time, this is workaround
+ # to preserve backward compatibility
+ if ( $fieldname eq 'Owner' ) {
+ my $flag = 0;
+ for my $chunk ( splice @$field ) {
+ my ( $f, $op, $value, %rest ) = @$chunk;
+ if ( !$rest{SUBKEY} && $op =~ /^!?=$/ ) {
+ $self->_OpenParen unless $flag++;
+ my $o = RT::User->new( $self->CurrentUser );
+ $o->Load($value);
+ $value = $o->Id;
+ $self->_SQLLimit(
+ FIELD => 'Owner',
+ OPERATOR => $op,
+ VALUE => $value,
+ %rest,
+ );
+ }
+ else {
+ push @$field, $chunk;
+ }
+ }
+ $self->_CloseParen if $flag;
+ return unless @$field;
+ }
+
+ my $users = $self->_WatcherJoin($type);
+
+ # If we're looking for multiple watchers of a given type,
+ # TicketSQL will be handing it to us as an array of clauses in
+ # $field
$self->_OpenParen;
- for my $chunk (@bundle) {
- ($field,$op,$value,@rest) = @$chunk;
- $self->_SQLLimit(ALIAS => $users,
- FIELD => $rest{SUBKEY} || 'EmailAddress',
- VALUE => $value,
- OPERATOR => $op,
- CASESENSITIVE => 0,
- @rest,
- );
+ for my $chunk (@$field) {
+ ( $field, $op, $value, %rest ) = @$chunk;
+ $rest{SUBKEY} ||= 'EmailAddress';
+
+ my $re_negative_op = qr[!=|NOT LIKE];
+ $self->_OpenParen if $op =~ /$re_negative_op/;
+
+ $self->_SQLLimit(
+ ALIAS => $users,
+ FIELD => $rest{SUBKEY},
+ VALUE => $value,
+ OPERATOR => $op,
+ CASESENSITIVE => 0,
+ %rest
+ );
+
+ if ( $op =~ /$re_negative_op/ ) {
+ $self->_SQLLimit(
+ ALIAS => $users,
+ FIELD => $rest{SUBKEY},
+ OPERATOR => 'IS',
+ VALUE => 'NULL',
+ ENTRYAGGREGATOR => 'OR',
+ );
+ $self->_CloseParen;
+ }
}
$self->_CloseParen;
- } else {
- $self->_SQLLimit(ALIAS => $users,
- FIELD => $rest{SUBKEY} || 'EmailAddress',
- VALUE => $value,
- OPERATOR => $op,
- CASESENSITIVE => 0,
- @rest,
- );
- }
+}
+
+=head2 _WatcherJoin
+
+Helper function which provides joins to a watchers table both for limits
+and for ordering.
+
+=cut
+
+sub _WatcherJoin {
+ my $self = shift;
+ my $type = shift;
+
+ # we cache joins chain per watcher type
+ # if we limit by requestor then we shouldn't join requestors again
+ # for sort or limit on other requestors
+ if ( $self->{'_watcher_join_users_alias'}{ $type || 'any' } ) {
+ return $self->{'_watcher_join_users_alias'}{ $type || 'any' };
+ }
+
+# we always have watcher groups for ticket
+# this join should be NORMAL
+# XXX: if we change this from Join to NewAlias+Limit
+# then Pg will complain because SB build wrong query.
+# Query looks like "FROM (Tickets LEFT JOIN CGM ON(Groups.id = CGM.GroupId)), Groups"
+# Pg doesn't like that fact that it doesn't know about Groups table yet when
+# join CGM table into Tickets. Problem is in Join method which doesn't use
+# ALIAS1 argument when build braces.
+ my $groups = $self->Join(
+ ALIAS1 => 'main',
+ FIELD1 => 'id',
+ TABLE2 => 'Groups',
+ FIELD2 => 'Instance',
+ ENTRYAGGREGATOR => 'AND'
+ );
+ $self->SUPER::Limit(
+ ALIAS => $groups,
+ FIELD => 'Domain',
+ VALUE => 'RT::Ticket-Role',
+ ENTRYAGGREGATOR => 'AND'
+ );
+ $self->SUPER::Limit(
+ ALIAS => $groups,
+ FIELD => 'Type',
+ VALUE => $type,
+ ENTRYAGGREGATOR => 'AND'
+ )
+ if ($type);
+
+ my $groupmembers = $self->Join(
+ TYPE => 'LEFT',
+ ALIAS1 => $groups,
+ FIELD1 => 'id',
+ TABLE2 => 'CachedGroupMembers',
+ FIELD2 => 'GroupId'
+ );
+
+ # XXX: work around, we must hide groups that
+ # are members of the role group we search in,
+ # otherwise them result in wrong NULLs in Users
+ # table and break ordering. Now, we know that
+ # RT doesn't allow to add groups as members of the
+ # ticket roles, so we just hide entries in CGM table
+ # with MemberId == GroupId from results
+ my $groupmembers = $self->SUPER::Limit(
+ LEFTJOIN => $groupmembers,
+ FIELD => 'GroupId',
+ OPERATOR => '!=',
+ VALUE => "$groupmembers.MemberId",
+ QUOTEVALUE => 0,
+ );
+ my $users = $self->Join(
+ TYPE => 'LEFT',
+ ALIAS1 => $groupmembers,
+ FIELD1 => 'MemberId',
+ TABLE2 => 'Users',
+ FIELD2 => 'id'
+ );
+ return $self->{'_watcher_join_users_alias'}{ $type || 'any' } = $users;
+}
+
+=head2 _WatcherMembershipLimit
+
+Handle watcher membership limits, i.e. whether the watcher belongs to a
+specific group or not.
- # {{{ Tie to groups for tickets we care about
- $self->_SQLLimit(ALIAS => $groups,
- FIELD => 'Domain',
- VALUE => 'RT::Ticket-Role',
- ENTRYAGGREGATOR => 'AND');
+Meta Data:
+ 1: Field to query on
- $self->Join(ALIAS1 => $groups, FIELD1 => 'Instance',
- ALIAS2 => 'main', FIELD2 => 'id');
- # }}}
+SELECT DISTINCT main.*
+FROM
+ Tickets main,
+ Groups Groups_1,
+ CachedGroupMembers CachedGroupMembers_2,
+ Users Users_3
+WHERE (
+ (main.EffectiveId = main.id)
+) AND (
+ (main.Status != 'deleted')
+) AND (
+ (main.Type = 'ticket')
+) AND (
+ (
+ (Users_3.EmailAddress = '22')
+ AND
+ (Groups_1.Domain = 'RT::Ticket-Role')
+ AND
+ (Groups_1.Type = 'RequestorGroup')
+ )
+) AND
+ Groups_1.Instance = main.id
+AND
+ Groups_1.id = CachedGroupMembers_2.GroupId
+AND
+ CachedGroupMembers_2.MemberId = Users_3.id
+ORDER BY main.id ASC
+LIMIT 25
- # If we care about which sort of watcher
- my $meta = $FIELDS{$field};
- my $type = ( defined $meta->[1] ? $meta->[1] : undef );
+=cut
- if ( $type ) {
- $self->_SQLLimit(ALIAS => $groups,
- FIELD => 'Type',
- VALUE => $type,
- ENTRYAGGREGATOR => 'AND');
- }
+sub _WatcherMembershipLimit {
+ my ( $self, $field, $op, $value, @rest ) = @_;
+ my %rest = @rest;
+
+ $self->_OpenParen;
- $self->Join (ALIAS1 => $groups, FIELD1 => 'id',
- ALIAS2 => $groupmembers, FIELD2 => 'GroupId');
+ my $groups = $self->NewAlias('Groups');
+ my $groupmembers = $self->NewAlias('CachedGroupMembers');
+ my $users = $self->NewAlias('Users');
+ my $memberships = $self->NewAlias('CachedGroupMembers');
+
+ if ( ref $field ) { # gross hack
+ my @bundle = @$field;
+ $self->_OpenParen;
+ for my $chunk (@bundle) {
+ ( $field, $op, $value, @rest ) = @$chunk;
+ $self->_SQLLimit(
+ ALIAS => $memberships,
+ FIELD => 'GroupId',
+ VALUE => $value,
+ OPERATOR => $op,
+ @rest,
+ );
+ }
+ $self->_CloseParen;
+ }
+ else {
+ $self->_SQLLimit(
+ ALIAS => $memberships,
+ FIELD => 'GroupId',
+ VALUE => $value,
+ OPERATOR => $op,
+ @rest,
+ );
+ }
- $self->Join( ALIAS1 => $groupmembers, FIELD1 => 'MemberId',
- ALIAS2 => $users, FIELD2 => 'id');
+ # {{{ Tie to groups for tickets we care about
+ $self->_SQLLimit(
+ ALIAS => $groups,
+ FIELD => 'Domain',
+ VALUE => 'RT::Ticket-Role',
+ ENTRYAGGREGATOR => 'AND'
+ );
+
+ $self->Join(
+ ALIAS1 => $groups,
+ FIELD1 => 'Instance',
+ ALIAS2 => 'main',
+ FIELD2 => 'id'
+ );
+
+ # }}}
+
+ # If we care about which sort of watcher
+ my $meta = $FIELDS{$field};
+ my $type = ( defined $meta->[1] ? $meta->[1] : undef );
+
+ if ($type) {
+ $self->_SQLLimit(
+ ALIAS => $groups,
+ FIELD => 'Type',
+ VALUE => $type,
+ ENTRYAGGREGATOR => 'AND'
+ );
+ }
- $self->_CloseParen;
+ $self->Join(
+ ALIAS1 => $groups,
+ FIELD1 => 'id',
+ ALIAS2 => $groupmembers,
+ FIELD2 => 'GroupId'
+ );
+
+ $self->Join(
+ ALIAS1 => $groupmembers,
+ FIELD1 => 'MemberId',
+ ALIAS2 => $users,
+ FIELD2 => 'id'
+ );
+
+ $self->Join(
+ ALIAS1 => $memberships,
+ FIELD1 => 'MemberId',
+ ALIAS2 => $users,
+ FIELD2 => 'id'
+ );
+
+ $self->_CloseParen;
}
sub _LinkFieldLimit {
- my $restriction;
- my $self;
- my $LinkAlias;
- my %args;
- if ($restriction->{'TYPE'}) {
- $self->SUPER::Limit(ALIAS => $LinkAlias,
- ENTRYAGGREGATOR => 'AND',
- FIELD => 'Type',
- OPERATOR => '=',
- VALUE => $restriction->{'TYPE'} );
- }
-
- #If we're trying to limit it to things that are target of
- if ($restriction->{'TARGET'}) {
- # If the TARGET is an integer that means that we want to look at
- # the LocalTarget field. otherwise, we want to look at the
- # "Target" field
- my ($matchfield);
- if ($restriction->{'TARGET'} =~/^(\d+)$/) {
- $matchfield = "LocalTarget";
- } else {
- $matchfield = "Target";
+ my $restriction;
+ my $self;
+ my $LinkAlias;
+ my %args;
+ if ( $restriction->{'TYPE'} ) {
+ $self->SUPER::Limit(
+ ALIAS => $LinkAlias,
+ ENTRYAGGREGATOR => 'AND',
+ FIELD => 'Type',
+ OPERATOR => '=',
+ VALUE => $restriction->{'TYPE'}
+ );
}
- $self->SUPER::Limit(ALIAS => $LinkAlias,
- ENTRYAGGREGATOR => 'AND',
- FIELD => $matchfield,
- OPERATOR => '=',
- VALUE => $restriction->{'TARGET'} );
- #If we're searching on target, join the base to ticket.id
- $self->Join( ALIAS1 => 'main', FIELD1 => $self->{'primary_key'},
- ALIAS2 => $LinkAlias,
- FIELD2 => 'LocalBase');
- }
- #If we're trying to limit it to things that are base of
- elsif ($restriction->{'BASE'}) {
- # If we're trying to match a numeric link, we want to look at
- # LocalBase, otherwise we want to look at "Base"
- my ($matchfield);
- if ($restriction->{'BASE'} =~/^(\d+)$/) {
- $matchfield = "LocalBase";
- } else {
- $matchfield = "Base";
+
+ #If we're trying to limit it to things that are target of
+ if ( $restriction->{'TARGET'} ) {
+
+ # If the TARGET is an integer that means that we want to look at
+ # the LocalTarget field. otherwise, we want to look at the
+ # "Target" field
+ my ($matchfield);
+ if ( $restriction->{'TARGET'} =~ /^(\d+)$/ ) {
+ $matchfield = "LocalTarget";
+ }
+ else {
+ $matchfield = "Target";
+ }
+ $self->SUPER::Limit(
+ ALIAS => $LinkAlias,
+ ENTRYAGGREGATOR => 'AND',
+ FIELD => $matchfield,
+ OPERATOR => '=',
+ VALUE => $restriction->{'TARGET'}
+ );
+
+ #If we're searching on target, join the base to ticket.id
+ $self->_SQLJoin(
+ ALIAS1 => 'main',
+ FIELD1 => $self->{'primary_key'},
+ ALIAS2 => $LinkAlias,
+ FIELD2 => 'LocalBase'
+ );
}
- $self->SUPER::Limit(ALIAS => $LinkAlias,
- ENTRYAGGREGATOR => 'AND',
- FIELD => $matchfield,
- OPERATOR => '=',
- VALUE => $restriction->{'BASE'} );
- #If we're searching on base, join the target to ticket.id
- $self->Join( ALIAS1 => 'main', FIELD1 => $self->{'primary_key'},
- ALIAS2 => $LinkAlias,
- FIELD2 => 'LocalTarget')
- }
-}
+ #If we're trying to limit it to things that are base of
+ elsif ( $restriction->{'BASE'} ) {
+
+ # If we're trying to match a numeric link, we want to look at
+ # LocalBase, otherwise we want to look at "Base"
+ my ($matchfield);
+ if ( $restriction->{'BASE'} =~ /^(\d+)$/ ) {
+ $matchfield = "LocalBase";
+ }
+ else {
+ $matchfield = "Base";
+ }
+ $self->SUPER::Limit(
+ ALIAS => $LinkAlias,
+ ENTRYAGGREGATOR => 'AND',
+ FIELD => $matchfield,
+ OPERATOR => '=',
+ VALUE => $restriction->{'BASE'}
+ );
+
+ #If we're searching on base, join the target to ticket.id
+ $self->_SQLJoin(
+ ALIAS1 => 'main',
+ FIELD1 => $self->{'primary_key'},
+ ALIAS2 => $LinkAlias,
+ FIELD2 => 'LocalTarget'
+ );
+ }
+}
=head2 KeywordLimit
=cut
sub _CustomFieldLimit {
- my ($self,$_field,$op,$value,@rest) = @_;
-
- my %rest = @rest;
- my $field = $rest{SUBKEY} || die "No field specified";
-
- # For our sanity, we can only limit on one queue at a time
- my $queue = undef;
- # Ugh. This will not do well for things with underscores in them
-
- use RT::CustomFields;
- my $CF = RT::CustomFields->new( $self->CurrentUser );
- #$CF->Load( $cfid} );
-
- my $q;
- if ($field =~ /^(.+?)\.{(.+)}$/) {
- my $q = RT::Queue->new($self->CurrentUser);
- $q->Load($1);
- $field = $2;
- $CF->LimitToQueue( $q->Id );
- $queue = $q->Id;
- } else {
- $field = $1 if $field =~ /^{(.+)}$/; # trim { }
- $CF->LimitToGlobal;
- }
- $CF->FindAllRows;
-
- my $cfid = 0;
-
- while ( my $CustomField = $CF->Next ) {
- if ($CustomField->Name eq $field) {
- $cfid = $CustomField->Id;
- last;
- }
- }
- die "No custom field named $field found\n"
- unless $cfid;
+ my ( $self, $_field, $op, $value, @rest ) = @_;
-# use RT::CustomFields;
-# my $CF = RT::CustomField->new( $self->CurrentUser );
-# $CF->Load( $cfid );
+ my %rest = @rest;
+ my $field = $rest{SUBKEY} || die "No field specified";
+ # For our sanity, we can only limit on one queue at a time
+ my $queue = 0;
- my $null_columns_ok;
+ if ( $field =~ /^(.+?)\.{(.+)}$/ ) {
+ $queue = $1;
+ $field = $2;
+ }
+ $field = $1 if $field =~ /^{(.+)}$/; # trim { }
- my $TicketCFs;
- # Perform one Join per CustomField
- if ($self->{_sql_keywordalias}{$cfid}) {
- $TicketCFs = $self->{_sql_keywordalias}{$cfid};
- } else {
- $TicketCFs = $self->{_sql_keywordalias}{$cfid} =
- $self->Join( TYPE => 'left',
- ALIAS1 => 'main',
- FIELD1 => 'id',
- TABLE2 => 'TicketCustomFieldValues',
- FIELD2 => 'Ticket' );
- }
+ # If we're trying to find custom fields that don't match something, we
+ # want tickets where the custom field has no value at all. Note that
+ # we explicitly don't include the "IS NULL" case, since we would
+ # otherwise end up with a redundant clause.
- $self->_OpenParen;
+ my $null_columns_ok;
+ if ( ( $op =~ /^NOT LIKE$/i ) or ( $op eq '!=' ) ) {
+ $null_columns_ok = 1;
+ }
- $self->_SQLLimit( ALIAS => $TicketCFs,
- FIELD => 'Content',
- OPERATOR => $op,
- VALUE => $value,
- QUOTEVALUE => 1,
- @rest );
+ my $cfid = 0;
+ if ($queue) {
- if ( $op =~ /^IS$/i
- or ( $op eq '!=' ) ) {
- $null_columns_ok = 1;
- }
+ my $q = RT::Queue->new( $self->CurrentUser );
+ $q->Load($queue) if ($queue);
- #If we're trying to find tickets where the keyword isn't somethng,
- #also check ones where it _IS_ null
+ my $cf;
+ if ( $q->id ) {
+ $cf = $q->CustomField($field);
+ }
+ else {
+ $cf = RT::CustomField->new( $self->CurrentUser );
+ $cf->LoadByNameAndQueue( Queue => '0', Name => $field );
+ }
- if ( $op eq '!=' ) {
- $self->_SQLLimit( ALIAS => $TicketCFs,
- FIELD => 'Content',
- OPERATOR => 'IS',
- VALUE => 'NULL',
- QUOTEVALUE => 0,
- ENTRYAGGREGATOR => 'OR', );
- }
+ $cfid = $cf->id;
- $self->_SQLLimit( LEFTJOIN => $TicketCFs,
- FIELD => 'CustomField',
- VALUE => $cfid,
- ENTRYAGGREGATOR => 'OR' );
+ }
+ my $TicketCFs;
+ my $cfkey = $cfid ? $cfid : "$queue.$field";
+ # Perform one Join per CustomField
+ if ( $self->{_sql_object_cf_alias}{$cfkey} ) {
+ $TicketCFs = $self->{_sql_object_cf_alias}{$cfkey};
+ }
+ else {
+ if ($cfid) {
+ $TicketCFs = $self->{_sql_object_cf_alias}{$cfkey} = $self->Join(
+ TYPE => 'left',
+ ALIAS1 => 'main',
+ FIELD1 => 'id',
+ TABLE2 => 'ObjectCustomFieldValues',
+ FIELD2 => 'ObjectId',
+ );
+ $self->SUPER::Limit(
+ LEFTJOIN => $TicketCFs,
+ FIELD => 'CustomField',
+ VALUE => $cfid,
+ ENTRYAGGREGATOR => 'AND'
+ );
+ }
+ else {
+ my $cfalias = $self->Join(
+ TYPE => 'left',
+ EXPRESSION => "'$field'",
+ TABLE2 => 'CustomFields',
+ FIELD2 => 'Name',
+ );
+
+ $TicketCFs = $self->{_sql_object_cf_alias}{$cfkey} = $self->Join(
+ TYPE => 'left',
+ ALIAS1 => $cfalias,
+ FIELD1 => 'id',
+ TABLE2 => 'ObjectCustomFieldValues',
+ FIELD2 => 'CustomField',
+ );
+ $self->SUPER::Limit(
+ LEFTJOIN => $TicketCFs,
+ FIELD => 'ObjectId',
+ VALUE => 'main.id',
+ QUOTEVALUE => 0,
+ ENTRYAGGREGATOR => 'AND',
+ );
+ }
+ $self->SUPER::Limit(
+ LEFTJOIN => $TicketCFs,
+ FIELD => 'ObjectType',
+ VALUE => ref( $self->NewItem )
+ , # we want a single item, not a collection
+ ENTRYAGGREGATOR => 'AND'
+ );
+ $self->SUPER::Limit(
+ LEFTJOIN => $TicketCFs,
+ FIELD => 'Disabled',
+ OPERATOR => '=',
+ VALUE => '0',
+ ENTRYAGGREGATOR => 'AND'
+ );
+ }
- $self->_CloseParen;
+ $self->_OpenParen if ($null_columns_ok);
+
+ $self->_SQLLimit(
+ ALIAS => $TicketCFs,
+ FIELD => 'Content',
+ OPERATOR => $op,
+ VALUE => $value,
+ QUOTEVALUE => 1,
+ @rest
+ );
+
+ if ($null_columns_ok) {
+ $self->_SQLLimit(
+ ALIAS => $TicketCFs,
+ FIELD => 'Content',
+ OPERATOR => 'IS',
+ VALUE => 'NULL',
+ QUOTEVALUE => 0,
+ ENTRYAGGREGATOR => 'OR',
+ );
+ }
+ $self->_CloseParen if ($null_columns_ok);
}
-
# End Helper Functions
# End of SQL Stuff -------------------------------------------------
Generally best called from LimitFoo methods
=cut
+
sub Limit {
my $self = shift;
- my %args = ( FIELD => undef,
- OPERATOR => '=',
- VALUE => undef,
- DESCRIPTION => undef,
- @_
- );
+ my %args = (
+ FIELD => undef,
+ OPERATOR => '=',
+ VALUE => undef,
+ DESCRIPTION => undef,
+ @_
+ );
$args{'DESCRIPTION'} = $self->loc(
- "[_1] [_2] [_3]", $args{'FIELD'}, $args{'OPERATOR'}, $args{'VALUE'}
- ) if (!defined $args{'DESCRIPTION'}) ;
+ "[_1] [_2] [_3]", $args{'FIELD'},
+ $args{'OPERATOR'}, $args{'VALUE'}
+ )
+ if ( !defined $args{'DESCRIPTION'} );
my $index = $self->_NextIndex;
- #make the TicketRestrictions hash the equivalent of whatever we just passed in;
+#make the TicketRestrictions hash the equivalent of whatever we just passed in;
- %{$self->{'TicketRestrictions'}{$index}} = %args;
+ %{ $self->{'TicketRestrictions'}{$index} } = %args;
$self->{'RecalcTicketLimits'} = 1;
- # If we're looking at the effective id, we don't want to append the other clause
- # which limits us to tickets where id = effective id
- if ($args{'FIELD'} eq 'EffectiveId') {
+# If we're looking at the effective id, we don't want to append the other clause
+# which limits us to tickets where id = effective id
+ if ( $args{'FIELD'} eq 'EffectiveId'
+ && ( !$args{'ALIAS'} || $args{'ALIAS'} eq 'main' ) )
+ {
$self->{'looking_at_effective_id'} = 1;
}
- if ($args{'FIELD'} eq 'Type') {
+ if ( $args{'FIELD'} eq 'Type'
+ && ( !$args{'ALIAS'} || $args{'ALIAS'} eq 'main' ) )
+ {
$self->{'looking_at_type'} = 1;
}
# }}}
-
-
-
=head2 FreezeLimits
Returns a frozen string suitable for handing back to ThawLimits.
=cut
+
+sub _FreezeThawKeys {
+ 'TicketRestrictions', 'restriction_index', 'looking_at_effective_id',
+ 'looking_at_type';
+}
+
# {{{ sub FreezeLimits
sub FreezeLimits {
- my $self = shift;
- require FreezeThaw;
- return (FreezeThaw::freeze($self->{'TicketRestrictions'},
- $self->{'restriction_index'}
- ));
+ my $self = shift;
+ require Storable;
+ require MIME::Base64;
+ MIME::Base64::base64_encode(
+ Storable::freeze( \@{$self}{ $self->_FreezeThawKeys } ) );
}
# }}}
object have that set of limits.
=cut
+
# {{{ sub ThawLimits
sub ThawLimits {
- my $self = shift;
- my $in = shift;
-
- #if we don't have $in, get outta here.
- return undef unless ($in);
+ my $self = shift;
+ my $in = shift;
- $self->{'RecalcTicketLimits'} = 1;
+ #if we don't have $in, get outta here.
+ return undef unless ($in);
+
+ $self->{'RecalcTicketLimits'} = 1;
- require FreezeThaw;
-
- #We don't need to die if the thaw fails.
-
- eval {
- ($self->{'TicketRestrictions'},
- $self->{'restriction_index'}
- ) = FreezeThaw::thaw($in);
- }
+ require Storable;
+ require MIME::Base64;
+
+ #We don't need to die if the thaw fails.
+ @{$self}{ $self->_FreezeThawKeys }
+ = eval { @{ Storable::thaw( MIME::Base64::base64_decode($in) ) }; };
+
+ $RT::Logger->error($@) if $@;
}
sub LimitQueue {
my $self = shift;
- my %args = (VALUE => undef,
- OPERATOR => '=',
- @_);
+ my %args = (
+ VALUE => undef,
+ OPERATOR => '=',
+ @_
+ );
#TODO VALUE should also take queue names and queue objects
#TODO FIXME why are we canonicalizing to name, not id, robrt?
- if ($args{VALUE} =~ /^\d+$/) {
- my $queue = new RT::Queue($self->CurrentUser);
- $queue->Load($args{'VALUE'});
- $args{VALUE} = $queue->Name;
+ if ( $args{VALUE} =~ /^\d+$/ ) {
+ my $queue = new RT::Queue( $self->CurrentUser );
+ $queue->Load( $args{'VALUE'} );
+ $args{VALUE} = $queue->Name;
}
# What if they pass in an Id? Check for isNum() and convert to
#TODO check for a valid queue here
- $self->Limit (FIELD => 'Queue',
- VALUE => $args{VALUE},
- OPERATOR => $args{'OPERATOR'},
- DESCRIPTION => join(
- ' ', $self->loc('Queue'), $args{'OPERATOR'}, $args{VALUE},
- ),
- );
+ $self->Limit(
+ FIELD => 'Queue',
+ VALUE => $args{VALUE},
+ OPERATOR => $args{'OPERATOR'},
+ DESCRIPTION => join(
+ ' ', $self->loc('Queue'), $args{'OPERATOR'}, $args{VALUE},
+ ),
+ );
}
+
# }}}
# {{{ sub LimitStatus
OPERATOR is one of = or !=.
VALUE is a status.
+RT adds Status != 'deleted' until object has
+allow_deleted_search internal property set.
+$tickets->{'allow_deleted_search'} = 1;
+$tickets->LimitStatus( VALUE => 'deleted' );
+
=cut
sub LimitStatus {
my $self = shift;
- my %args = ( OPERATOR => '=',
- @_);
- $self->Limit (FIELD => 'Status',
- VALUE => $args{'VALUE'},
- OPERATOR => $args{'OPERATOR'},
- DESCRIPTION => join(
- ' ', $self->loc('Status'), $args{'OPERATOR'}, $self->loc($args{'VALUE'})
- ),
- );
+ my %args = (
+ OPERATOR => '=',
+ @_
+ );
+ $self->Limit(
+ FIELD => 'Status',
+ VALUE => $args{'VALUE'},
+ OPERATOR => $args{'OPERATOR'},
+ DESCRIPTION => join( ' ',
+ $self->loc('Status'), $args{'OPERATOR'},
+ $self->loc( $args{'VALUE'} ) ),
+ );
}
# }}}
sub LimitType {
my $self = shift;
- my %args = (OPERATOR => '=',
- VALUE => undef,
- @_);
- $self->Limit (FIELD => 'Type',
- VALUE => $args{'VALUE'},
- OPERATOR => $args{'OPERATOR'},
- DESCRIPTION => join(
- ' ', $self->loc('Type'), $args{'OPERATOR'}, $args{'Limit'},
- ),
- );
+ my %args = (
+ OPERATOR => '=',
+ VALUE => undef,
+ @_
+ );
+ $self->Limit(
+ FIELD => 'Type',
+ VALUE => $args{'VALUE'},
+ OPERATOR => $args{'OPERATOR'},
+ DESCRIPTION => join( ' ',
+ $self->loc('Type'), $args{'OPERATOR'}, $args{'Limit'}, ),
+ );
}
# }}}
sub LimitSubject {
my $self = shift;
my %args = (@_);
- $self->Limit (FIELD => 'Subject',
- VALUE => $args{'VALUE'},
- OPERATOR => $args{'OPERATOR'},
- DESCRIPTION => join(
- ' ', $self->loc('Subject'), $args{'OPERATOR'}, $args{'VALUE'},
- ),
- );
+ $self->Limit(
+ FIELD => 'Subject',
+ VALUE => $args{'VALUE'},
+ OPERATOR => $args{'OPERATOR'},
+ DESCRIPTION => join( ' ',
+ $self->loc('Subject'), $args{'OPERATOR'}, $args{'VALUE'}, ),
+ );
}
# }}}
sub LimitId {
my $self = shift;
- my %args = (OPERATOR => '=',
- @_);
+ my %args = (
+ OPERATOR => '=',
+ @_
+ );
- $self->Limit (FIELD => 'id',
- VALUE => $args{'VALUE'},
- OPERATOR => $args{'OPERATOR'},
- DESCRIPTION => join(
- ' ', $self->loc('Id'), $args{'OPERATOR'}, $args{'VALUE'},
- ),
- );
+ $self->Limit(
+ FIELD => 'id',
+ VALUE => $args{'VALUE'},
+ OPERATOR => $args{'OPERATOR'},
+ DESCRIPTION =>
+ join( ' ', $self->loc('Id'), $args{'OPERATOR'}, $args{'VALUE'}, ),
+ );
}
# }}}
sub LimitPriority {
my $self = shift;
my %args = (@_);
- $self->Limit (FIELD => 'Priority',
- VALUE => $args{'VALUE'},
- OPERATOR => $args{'OPERATOR'},
- DESCRIPTION => join(
- ' ', $self->loc('Priority'), $args{'OPERATOR'}, $args{'VALUE'},
- ),
- );
+ $self->Limit(
+ FIELD => 'Priority',
+ VALUE => $args{'VALUE'},
+ OPERATOR => $args{'OPERATOR'},
+ DESCRIPTION => join( ' ',
+ $self->loc('Priority'),
+ $args{'OPERATOR'}, $args{'VALUE'}, ),
+ );
}
# }}}
sub LimitInitialPriority {
my $self = shift;
my %args = (@_);
- $self->Limit (FIELD => 'InitialPriority',
- VALUE => $args{'VALUE'},
- OPERATOR => $args{'OPERATOR'},
- DESCRIPTION => join(
- ' ', $self->loc('Initial Priority'), $args{'OPERATOR'}, $args{'VALUE'},
- ),
- );
+ $self->Limit(
+ FIELD => 'InitialPriority',
+ VALUE => $args{'VALUE'},
+ OPERATOR => $args{'OPERATOR'},
+ DESCRIPTION => join( ' ',
+ $self->loc('Initial Priority'), $args{'OPERATOR'},
+ $args{'VALUE'}, ),
+ );
}
# }}}
sub LimitFinalPriority {
my $self = shift;
my %args = (@_);
- $self->Limit (FIELD => 'FinalPriority',
- VALUE => $args{'VALUE'},
- OPERATOR => $args{'OPERATOR'},
- DESCRIPTION => join(
- ' ', $self->loc('Final Priority'), $args{'OPERATOR'}, $args{'VALUE'},
- ),
- );
+ $self->Limit(
+ FIELD => 'FinalPriority',
+ VALUE => $args{'VALUE'},
+ OPERATOR => $args{'OPERATOR'},
+ DESCRIPTION => join( ' ',
+ $self->loc('Final Priority'), $args{'OPERATOR'},
+ $args{'VALUE'}, ),
+ );
}
# }}}
sub LimitTimeWorked {
my $self = shift;
my %args = (@_);
- $self->Limit (FIELD => 'TimeWorked',
- VALUE => $args{'VALUE'},
- OPERATOR => $args{'OPERATOR'},
- DESCRIPTION => join(
- ' ', $self->loc('Time worked'), $args{'OPERATOR'}, $args{'VALUE'},
- ),
- );
+ $self->Limit(
+ FIELD => 'TimeWorked',
+ VALUE => $args{'VALUE'},
+ OPERATOR => $args{'OPERATOR'},
+ DESCRIPTION => join( ' ',
+ $self->loc('Time worked'),
+ $args{'OPERATOR'}, $args{'VALUE'}, ),
+ );
}
# }}}
sub LimitTimeLeft {
my $self = shift;
my %args = (@_);
- $self->Limit (FIELD => 'TimeLeft',
- VALUE => $args{'VALUE'},
- OPERATOR => $args{'OPERATOR'},
- DESCRIPTION => join(
- ' ', $self->loc('Time left'), $args{'OPERATOR'}, $args{'VALUE'},
- ),
- );
+ $self->Limit(
+ FIELD => 'TimeLeft',
+ VALUE => $args{'VALUE'},
+ OPERATOR => $args{'OPERATOR'},
+ DESCRIPTION => join( ' ',
+ $self->loc('Time left'),
+ $args{'OPERATOR'}, $args{'VALUE'}, ),
+ );
}
# }}}
VALUE is a string to search for in the body of the ticket
=cut
+
sub LimitContent {
my $self = shift;
my %args = (@_);
- $self->Limit (FIELD => 'Content',
- VALUE => $args{'VALUE'},
- OPERATOR => $args{'OPERATOR'},
- DESCRIPTION => join(
- ' ', $self->loc('Ticket content'), $args{'OPERATOR'}, $args{'VALUE'},
- ),
- );
+ $self->Limit(
+ FIELD => 'Content',
+ VALUE => $args{'VALUE'},
+ OPERATOR => $args{'OPERATOR'},
+ DESCRIPTION => join( ' ',
+ $self->loc('Ticket content'), $args{'OPERATOR'},
+ $args{'VALUE'}, ),
+ );
}
# }}}
VALUE is a string to search for in the body of the ticket
=cut
+
sub LimitFilename {
my $self = shift;
my %args = (@_);
- $self->Limit (FIELD => 'Filename',
- VALUE => $args{'VALUE'},
- OPERATOR => $args{'OPERATOR'},
- DESCRIPTION => join(
- ' ', $self->loc('Attachment filename'), $args{'OPERATOR'}, $args{'VALUE'},
- ),
- );
+ $self->Limit(
+ FIELD => 'Filename',
+ VALUE => $args{'VALUE'},
+ OPERATOR => $args{'OPERATOR'},
+ DESCRIPTION => join( ' ',
+ $self->loc('Attachment filename'), $args{'OPERATOR'},
+ $args{'VALUE'}, ),
+ );
}
# }}}
sub LimitContentType {
my $self = shift;
my %args = (@_);
- $self->Limit (FIELD => 'ContentType',
- VALUE => $args{'VALUE'},
- OPERATOR => $args{'OPERATOR'},
- DESCRIPTION => join(
- ' ', $self->loc('Ticket content type'), $args{'OPERATOR'}, $args{'VALUE'},
- ),
- );
+ $self->Limit(
+ FIELD => 'ContentType',
+ VALUE => $args{'VALUE'},
+ OPERATOR => $args{'OPERATOR'},
+ DESCRIPTION => join( ' ',
+ $self->loc('Ticket content type'), $args{'OPERATOR'},
+ $args{'VALUE'}, ),
+ );
}
+
# }}}
# }}}
sub LimitOwner {
my $self = shift;
- my %args = ( OPERATOR => '=',
- @_);
+ my %args = (
+ OPERATOR => '=',
+ @_
+ );
+
+ my $owner = new RT::User( $self->CurrentUser );
+ $owner->Load( $args{'VALUE'} );
- my $owner = new RT::User($self->CurrentUser);
- $owner->Load($args{'VALUE'});
# FIXME: check for a valid $owner
- $self->Limit (FIELD => 'Owner',
- VALUE => $args{'VALUE'},
- OPERATOR => $args{'OPERATOR'},
- DESCRIPTION => join(
- ' ', $self->loc('Owner'), $args{'OPERATOR'}, $owner->Name(),
- ),
- );
+ $self->Limit(
+ FIELD => 'Owner',
+ VALUE => $args{'VALUE'},
+ OPERATOR => $args{'OPERATOR'},
+ DESCRIPTION => join( ' ',
+ $self->loc('Owner'), $args{'OPERATOR'}, $owner->Name(), ),
+ );
}
# {{{ sub LimitWatcher
-
=head2 LimitWatcher
Takes a paramhash with the fields OPERATOR, TYPE and VALUE.
sub LimitWatcher {
my $self = shift;
- my %args = ( OPERATOR => '=',
- VALUE => undef,
- TYPE => undef,
- @_);
-
+ my %args = (
+ OPERATOR => '=',
+ VALUE => undef,
+ TYPE => undef,
+ @_
+ );
#build us up a description
- my ($watcher_type, $desc);
- if ($args{'TYPE'}) {
- $watcher_type = $args{'TYPE'};
+ my ( $watcher_type, $desc );
+ if ( $args{'TYPE'} ) {
+ $watcher_type = $args{'TYPE'};
}
else {
- $watcher_type = "Watcher";
+ $watcher_type = "Watcher";
}
- $self->Limit (FIELD => $watcher_type,
- VALUE => $args{'VALUE'},
- OPERATOR => $args{'OPERATOR'},
- TYPE => $args{'TYPE'},
- DESCRIPTION => join(
- ' ', $self->loc($watcher_type), $args{'OPERATOR'}, $args{'VALUE'},
- ),
- );
+ $self->Limit(
+ FIELD => $watcher_type,
+ VALUE => $args{'VALUE'},
+ OPERATOR => $args{'OPERATOR'},
+ TYPE => $args{'TYPE'},
+ DESCRIPTION => join( ' ',
+ $self->loc($watcher_type),
+ $args{'OPERATOR'}, $args{'VALUE'}, ),
+ );
}
-
sub LimitRequestor {
my $self = shift;
my %args = (@_);
- my ($package, $filename, $line) = caller;
- $RT::Logger->error("Tickets->LimitRequestor is deprecated. please rewrite call at $package - $filename: $line");
- $self->LimitWatcher(TYPE => 'Requestor', @_);
+ $RT::Logger->error( "Tickets->LimitRequestor is deprecated at ("
+ . join( ":", caller )
+ . ")" );
+ $self->LimitWatcher( TYPE => 'Requestor', @_ );
}
# }}}
-
# }}}
# }}}
=head2 LimitLinkedTo
LimitLinkedTo takes a paramhash with two fields: TYPE and TARGET
-TYPE limits the sort of relationship we want to search on
+TYPE limits the sort of link we want to search on
TYPE = { RefersTo, MemberOf, DependsOn }
sub LimitLinkedTo {
my $self = shift;
my %args = (
- TICKET => undef,
- TARGET => undef,
- TYPE => undef,
- @_);
+ TICKET => undef,
+ TARGET => undef,
+ TYPE => undef,
+ @_
+ );
$self->Limit(
- FIELD => 'LinkedTo',
- BASE => undef,
- TARGET => ($args{'TARGET'} || $args{'TICKET'}),
- TYPE => $args{'TYPE'},
- DESCRIPTION => $self->loc(
- "Tickets [_1] by [_2]", $self->loc($args{'TYPE'}), ($args{'TARGET'} || $args{'TICKET'})
- ),
- );
+ FIELD => 'LinkedTo',
+ BASE => undef,
+ TARGET => ( $args{'TARGET'} || $args{'TICKET'} ),
+ TYPE => $args{'TYPE'},
+ DESCRIPTION => $self->loc(
+ "Tickets [_1] by [_2]",
+ $self->loc( $args{'TYPE'} ),
+ ( $args{'TARGET'} || $args{'TICKET'} )
+ ),
+ );
}
-
# }}}
# {{{ LimitLinkedFrom
=head2 LimitLinkedFrom
LimitLinkedFrom takes a paramhash with two fields: TYPE and BASE
-TYPE limits the sort of relationship we want to search on
+TYPE limits the sort of link we want to search on
BASE is the id or URI of the BASE of the link
sub LimitLinkedFrom {
my $self = shift;
- my %args = ( BASE => undef,
- TICKET => undef,
- TYPE => undef,
- @_);
+ my %args = (
+ BASE => undef,
+ TICKET => undef,
+ TYPE => undef,
+ @_
+ );
# translate RT2 From/To naming to RT3 TicketSQL naming
my %fromToMap = qw(DependsOn DependentOn
- MemberOf HasMember
- RefersTo ReferredToBy);
+ MemberOf HasMember
+ RefersTo ReferredToBy);
my $type = $args{'TYPE'};
- $type = $fromToMap{$type} if exists($fromToMap{$type});
+ $type = $fromToMap{$type} if exists( $fromToMap{$type} );
- $self->Limit( FIELD => 'LinkedTo',
- TARGET => undef,
- BASE => ($args{'BASE'} || $args{'TICKET'}),
- TYPE => $type,
- DESCRIPTION => $self->loc(
- "Tickets [_1] [_2]", $self->loc($args{'TYPE'}), ($args{'BASE'} || $args{'TICKET'})
- ),
- );
+ $self->Limit(
+ FIELD => 'LinkedTo',
+ TARGET => undef,
+ BASE => ( $args{'BASE'} || $args{'TICKET'} ),
+ TYPE => $type,
+ DESCRIPTION => $self->loc(
+ "Tickets [_1] [_2]",
+ $self->loc( $args{'TYPE'} ),
+ ( $args{'BASE'} || $args{'TICKET'} )
+ ),
+ );
}
-
# }}}
# {{{ LimitMemberOf
sub LimitMemberOf {
- my $self = shift;
+ my $self = shift;
my $ticket_id = shift;
- $self->LimitLinkedTo ( TARGET=> "$ticket_id",
- TYPE => 'MemberOf',
- );
+ $self->LimitLinkedTo(
+ TARGET => "$ticket_id",
+ TYPE => 'MemberOf',
+ );
}
+
# }}}
# {{{ LimitHasMember
sub LimitHasMember {
- my $self = shift;
- my $ticket_id =shift;
- $self->LimitLinkedFrom ( BASE => "$ticket_id",
- TYPE => 'HasMember',
- );
+ my $self = shift;
+ my $ticket_id = shift;
+ $self->LimitLinkedFrom(
+ BASE => "$ticket_id",
+ TYPE => 'HasMember',
+ );
}
+
# }}}
# {{{ LimitDependsOn
sub LimitDependsOn {
- my $self = shift;
+ my $self = shift;
my $ticket_id = shift;
- $self->LimitLinkedTo ( TARGET => "$ticket_id",
- TYPE => 'DependsOn',
- );
+ $self->LimitLinkedTo(
+ TARGET => "$ticket_id",
+ TYPE => 'DependsOn',
+ );
}
# {{{ LimitDependedOnBy
sub LimitDependedOnBy {
- my $self = shift;
+ my $self = shift;
my $ticket_id = shift;
- $self->LimitLinkedFrom ( BASE => "$ticket_id",
- TYPE => 'DependentOn',
- );
+ $self->LimitLinkedFrom(
+ BASE => "$ticket_id",
+ TYPE => 'DependentOn',
+ );
}
# }}}
-
# {{{ LimitRefersTo
sub LimitRefersTo {
- my $self = shift;
+ my $self = shift;
my $ticket_id = shift;
- $self->LimitLinkedTo ( TARGET => "$ticket_id",
- TYPE => 'RefersTo',
- );
+ $self->LimitLinkedTo(
+ TARGET => "$ticket_id",
+ TYPE => 'RefersTo',
+ );
}
# {{{ LimitReferredToBy
sub LimitReferredToBy {
- my $self = shift;
+ my $self = shift;
my $ticket_id = shift;
- $self->LimitLinkedFrom ( BASE=> "$ticket_id",
- TYPE => 'ReferredTo',
- );
+ $self->LimitLinkedFrom(
+ BASE => "$ticket_id",
+ TYPE => 'ReferredToBy',
+ );
}
sub LimitDate {
my $self = shift;
my %args = (
- FIELD => undef,
- VALUE => undef,
- OPERATOR => undef,
+ FIELD => undef,
+ VALUE => undef,
+ OPERATOR => undef,
- @_);
+ @_
+ );
#Set the description if we didn't get handed it above
- unless ($args{'DESCRIPTION'} ) {
- $args{'DESCRIPTION'} = $args{'FIELD'} . " " .$args{'OPERATOR'}. " ". $args{'VALUE'} . " GMT"
+ unless ( $args{'DESCRIPTION'} ) {
+ $args{'DESCRIPTION'} = $args{'FIELD'} . " "
+ . $args{'OPERATOR'} . " "
+ . $args{'VALUE'} . " GMT";
}
- $self->Limit (%args);
+ $self->Limit(%args);
}
# }}}
-
-
-
sub LimitCreated {
my $self = shift;
- $self->LimitDate( FIELD => 'Created', @_);
+ $self->LimitDate( FIELD => 'Created', @_ );
}
+
sub LimitDue {
my $self = shift;
- $self->LimitDate( FIELD => 'Due', @_);
+ $self->LimitDate( FIELD => 'Due', @_ );
}
+
sub LimitStarts {
my $self = shift;
- $self->LimitDate( FIELD => 'Starts', @_);
+ $self->LimitDate( FIELD => 'Starts', @_ );
}
+
sub LimitStarted {
my $self = shift;
- $self->LimitDate( FIELD => 'Started', @_);
+ $self->LimitDate( FIELD => 'Started', @_ );
}
+
sub LimitResolved {
my $self = shift;
- $self->LimitDate( FIELD => 'Resolved', @_);
+ $self->LimitDate( FIELD => 'Resolved', @_ );
}
+
sub LimitTold {
my $self = shift;
- $self->LimitDate( FIELD => 'Told', @_);
+ $self->LimitDate( FIELD => 'Told', @_ );
}
+
sub LimitLastUpdated {
my $self = shift;
- $self->LimitDate( FIELD => 'LastUpdated', @_);
+ $self->LimitDate( FIELD => 'LastUpdated', @_ );
}
+
#
# {{{ sub LimitTransactionDate
sub LimitTransactionDate {
my $self = shift;
my %args = (
- FIELD => 'TransactionDate',
- VALUE => undef,
- OPERATOR => undef,
+ FIELD => 'TransactionDate',
+ VALUE => undef,
+ OPERATOR => undef,
- @_);
+ @_
+ );
# <20021217042756.GK28744@pallas.fsck.com>
# "Kill It" - Jesse.
#Set the description if we didn't get handed it above
- unless ($args{'DESCRIPTION'} ) {
- $args{'DESCRIPTION'} = $args{'FIELD'} . " " .$args{'OPERATOR'}. " ". $args{'VALUE'} . " GMT"
+ unless ( $args{'DESCRIPTION'} ) {
+ $args{'DESCRIPTION'} = $args{'FIELD'} . " "
+ . $args{'OPERATOR'} . " "
+ . $args{'VALUE'} . " GMT";
}
- $self->Limit (%args);
+ $self->Limit(%args);
}
=over 4
-=item CUSTOMFIELD - CustomField name or id. If a name is passed, an additional
-parameter QUEUE may also be passed to distinguish the custom field.
+=item CUSTOMFIELD - CustomField name or id. If a name is passed, an additional parameter QUEUE may also be passed to distinguish the custom field.
=item OPERATOR - The usual Limit operators
sub LimitCustomField {
my $self = shift;
- my %args = ( VALUE => undef,
- CUSTOMFIELD => undef,
- OPERATOR => '=',
- DESCRIPTION => undef,
- FIELD => 'CustomFieldValue',
- QUOTEVALUE => 1,
- @_ );
-
- use RT::CustomFields;
+ my %args = (
+ VALUE => undef,
+ CUSTOMFIELD => undef,
+ OPERATOR => '=',
+ DESCRIPTION => undef,
+ FIELD => 'CustomFieldValue',
+ QUOTEVALUE => 1,
+ @_
+ );
+
my $CF = RT::CustomField->new( $self->CurrentUser );
- if ( $args{CUSTOMFIELD} =~ /^\d+$/) {
- $CF->Load( $args{CUSTOMFIELD} );
+ if ( $args{CUSTOMFIELD} =~ /^\d+$/ ) {
+ $CF->Load( $args{CUSTOMFIELD} );
}
else {
- $CF->LoadByNameAndQueue( Name => $args{CUSTOMFIELD}, Queue => $args{QUEUE} );
- $args{CUSTOMFIELD} = $CF->Id;
+ $CF->LoadByNameAndQueue(
+ Name => $args{CUSTOMFIELD},
+ Queue => $args{QUEUE}
+ );
+ $args{CUSTOMFIELD} = $CF->Id;
}
#If we are looking to compare with a null value.
if ( $args{'OPERATOR'} =~ /^is$/i ) {
- $args{'DESCRIPTION'} ||= $self->loc("Custom field [_1] has no value.", $CF->Name);
+ $args{'DESCRIPTION'}
+ ||= $self->loc( "Custom field [_1] has no value.", $CF->Name );
}
elsif ( $args{'OPERATOR'} =~ /^is not$/i ) {
- $args{'DESCRIPTION'} ||= $self->loc("Custom field [_1] has a value.", $CF->Name);
+ $args{'DESCRIPTION'}
+ ||= $self->loc( "Custom field [_1] has a value.", $CF->Name );
}
# if we're not looking to compare with a null value
else {
- $args{'DESCRIPTION'} ||= $self->loc("Custom field [_1] [_2] [_3]", $CF->Name , $args{OPERATOR} , $args{VALUE});
+ $args{'DESCRIPTION'} ||= $self->loc( "Custom field [_1] [_2] [_3]",
+ $CF->Name, $args{OPERATOR}, $args{VALUE} );
}
my $q = "";
- if ($CF->Queue) {
- my $qo = new RT::Queue( $self->CurrentUser );
- $qo->load( $CF->Queue );
- $q = $qo->Name;
+ if ( $CF->Queue ) {
+ my $qo = new RT::Queue( $self->CurrentUser );
+ $qo->Load( $CF->Queue );
+ $q = $qo->Name;
}
my @rest;
@rest = ( ENTRYAGGREGATOR => 'AND' )
- if ($CF->Type eq 'SelectMultiple');
-
- $self->Limit( VALUE => $args{VALUE},
- FIELD => "CF.".( $q
- ? $q . ".{" . $CF->Name . "}"
- : $CF->Name
- ),
- OPERATOR => $args{OPERATOR},
- CUSTOMFIELD => 1,
- @rest,
- );
+ if ( $CF->Type eq 'SelectMultiple' );
+ $self->Limit(
+ VALUE => $args{VALUE},
+ FIELD => "CF."
+ . (
+ $q
+ ? $q . ".{" . $CF->Name . "}"
+ : $CF->Name
+ ),
+ OPERATOR => $args{OPERATOR},
+ CUSTOMFIELD => 1,
+ @rest,
+ );
$self->{'RecalcTicketLimits'} = 1;
}
# }}}
# }}}
-
# {{{ sub _NextIndex
=head2 _NextIndex
sub _NextIndex {
my $self = shift;
- return ($self->{'restriction_index'}++);
+ return ( $self->{'restriction_index'}++ );
}
+
# }}}
# }}}
# {{{ Core bits to make this a DBIx::SearchBuilder object
# {{{ sub _Init
-sub _Init {
+sub _Init {
my $self = shift;
- $self->{'table'} = "Tickets";
- $self->{'RecalcTicketLimits'} = 1;
+ $self->{'table'} = "Tickets";
+ $self->{'RecalcTicketLimits'} = 1;
$self->{'looking_at_effective_id'} = 0;
- $self->{'looking_at_type'} = 0;
- $self->{'restriction_index'} =1;
- $self->{'primary_key'} = "id";
+ $self->{'looking_at_type'} = 0;
+ $self->{'restriction_index'} = 1;
+ $self->{'primary_key'} = "id";
delete $self->{'items_array'};
delete $self->{'item_map'};
+ delete $self->{'columns_to_display'};
$self->SUPER::_Init(@_);
$self->_InitSQL;
}
+
# }}}
# {{{ sub Count
sub Count {
- my $self = shift;
- $self->_ProcessRestrictions() if ($self->{'RecalcTicketLimits'} == 1 );
- return($self->SUPER::Count());
+ my $self = shift;
+ $self->_ProcessRestrictions() if ( $self->{'RecalcTicketLimits'} == 1 );
+ return ( $self->SUPER::Count() );
}
+
# }}}
# {{{ sub CountAll
sub CountAll {
- my $self = shift;
- $self->_ProcessRestrictions() if ($self->{'RecalcTicketLimits'} == 1 );
- return($self->SUPER::CountAll());
+ my $self = shift;
+ $self->_ProcessRestrictions() if ( $self->{'RecalcTicketLimits'} == 1 );
+ return ( $self->SUPER::CountAll() );
}
-# }}}
+# }}}
# {{{ sub ItemsArrayRef
my $placeholder = $self->_ItemsCounter;
$self->GotoFirstItem();
while ( my $item = $self->Next ) {
- push ( @{ $self->{'items_array'} }, $item );
+ push( @{ $self->{'items_array'} }, $item );
}
$self->GotoItem($placeholder);
+ $self->{'items_array'}
+ = $self->ItemsOrderBy( $self->{'items_array'} );
}
return ( $self->{'items_array'} );
}
+
# }}}
# {{{ sub Next
sub Next {
- my $self = shift;
-
- $self->_ProcessRestrictions() if ($self->{'RecalcTicketLimits'} == 1 );
-
- my $Ticket = $self->SUPER::Next();
- if ((defined($Ticket)) and (ref($Ticket))) {
-
- #Make sure we _never_ show deleted tickets
- #TODO we should be doing this in the where clause.
- #but you can't do multiple clauses on the same field just yet :/
-
- if ($Ticket->__Value('Status') eq 'deleted') {
- return($self->Next());
- }
- # Since Ticket could be granted with more rights instead
- # of being revoked, it's ok if queue rights allow
- # ShowTicket. It seems need another query, but we have
- # rights cache in Principal::HasRight.
- elsif ($Ticket->QueueObj->CurrentUserHasRight('ShowTicket') ||
- $Ticket->CurrentUserHasRight('ShowTicket')) {
- return($Ticket);
- }
-
- #If the user doesn't have the right to show this ticket
- else {
- return($self->Next());
- }
- }
- #if there never was any ticket
- else {
- return(undef);
- }
+ my $self = shift;
+
+ $self->_ProcessRestrictions() if ( $self->{'RecalcTicketLimits'} == 1 );
+
+ my $Ticket = $self->SUPER::Next();
+ if ( ( defined($Ticket) ) and ( ref($Ticket) ) ) {
+
+ if ( $Ticket->__Value('Status') eq 'deleted'
+ && !$self->{'allow_deleted_search'} )
+ {
+ return ( $self->Next() );
+ }
+
+ # Since Ticket could be granted with more rights instead
+ # of being revoked, it's ok if queue rights allow
+ # ShowTicket. It seems need another query, but we have
+ # rights cache in Principal::HasRight.
+ elsif ($Ticket->QueueObj->CurrentUserHasRight('ShowTicket')
+ || $Ticket->CurrentUserHasRight('ShowTicket') )
+ {
+ return ($Ticket);
+ }
+
+ if ( $Ticket->__Value('Status') eq 'deleted' ) {
+ return ( $self->Next() );
+ }
+
+ # Since Ticket could be granted with more rights instead
+ # of being revoked, it's ok if queue rights allow
+ # ShowTicket. It seems need another query, but we have
+ # rights cache in Principal::HasRight.
+ elsif ($Ticket->QueueObj->CurrentUserHasRight('ShowTicket')
+ || $Ticket->CurrentUserHasRight('ShowTicket') )
+ {
+ return ($Ticket);
+ }
+
+ #If the user doesn't have the right to show this ticket
+ else {
+ return ( $self->Next() );
+ }
+ }
+
+ #if there never was any ticket
+ else {
+ return (undef);
+ }
}
+
# }}}
# }}}
=cut
-sub DescribeRestrictions {
+sub DescribeRestrictions {
my $self = shift;
- my ($row, %listing);
+ my ( $row, %listing );
- foreach $row (keys %{$self->{'TicketRestrictions'}}) {
- $listing{$row} = $self->{'TicketRestrictions'}{$row}{'DESCRIPTION'};
+ foreach $row ( keys %{ $self->{'TicketRestrictions'} } ) {
+ $listing{$row} = $self->{'TicketRestrictions'}{$row}{'DESCRIPTION'};
}
return (%listing);
}
+
# }}}
# {{{ sub RestrictionValues
=cut
sub RestrictionValues {
- my $self = shift;
+ my $self = shift;
my $field = shift;
- map $self->{'TicketRestrictions'}{$_}{'VALUE'},
- grep {
- $self->{'TicketRestrictions'}{$_}{'FIELD'} eq $field
- && $self->{'TicketRestrictions'}{$_}{'OPERATOR'} eq "="
- }
- keys %{$self->{'TicketRestrictions'}};
+ map $self->{'TicketRestrictions'}{$_}{'VALUE'}, grep {
+ $self->{'TicketRestrictions'}{$_}{'FIELD'} eq $field
+ && $self->{'TicketRestrictions'}{$_}{'OPERATOR'} eq "="
+ }
+ keys %{ $self->{'TicketRestrictions'} };
}
# }}}
my $self = shift;
delete $self->{'TicketRestrictions'};
$self->{'looking_at_effective_id'} = 0;
- $self->{'looking_at_type'} = 0;
- $self->{'RecalcTicketLimits'} =1;
+ $self->{'looking_at_type'} = 0;
+ $self->{'RecalcTicketLimits'} = 1;
}
# }}}
=cut
-
sub DeleteRestriction {
my $self = shift;
- my $row = shift;
+ my $row = shift;
delete $self->{'TicketRestrictions'}{$row};
$self->{'RecalcTicketLimits'} = 1;
+
#make the underlying easysearch object forget all its preconceptions
}
# Convert a set of oldstyle SB Restrictions to Clauses for RQL
sub _RestrictionsToClauses {
- my $self = shift;
-
- my $row;
- my %clause;
- foreach $row (keys %{$self->{'TicketRestrictions'}}) {
- my $restriction = $self->{'TicketRestrictions'}{$row};
- #use Data::Dumper;
- #print Dumper($restriction),"\n";
-
- # We need to reimplement the subclause aggregation that SearchBuilder does.
- # Default Subclause is ALIAS.FIELD, and default ALIAS is 'main',
- # Then SB AND's the different Subclauses together.
-
- # So, we want to group things into Subclauses, convert them to
- # SQL, and then join them with the appropriate DefaultEA.
- # Then join each subclause group with AND.
-
- my $field = $restriction->{'FIELD'};
- my $realfield = $field; # CustomFields fake up a fieldname, so
- # we need to figure that out
-
- # One special case
- # Rewrite LinkedTo meta field to the real field
- if ($field =~ /LinkedTo/) {
- $realfield = $field = $restriction->{'TYPE'};
- }
+ my $self = shift;
- # Two special case
- # CustomFields have a different real field
- if ($field =~ /^CF\./) {
- $realfield = "CF"
- }
+ my $row;
+ my %clause;
+ foreach $row ( keys %{ $self->{'TicketRestrictions'} } ) {
+ my $restriction = $self->{'TicketRestrictions'}{$row};
- die "I don't know about $field yet"
- unless (exists $FIELDS{$realfield} or $restriction->{CUSTOMFIELD});
-
- my $type = $FIELDS{$realfield}->[0];
- my $op = $restriction->{'OPERATOR'};
-
- my $value = ( grep { defined }
- map { $restriction->{$_} } qw(VALUE TICKET BASE TARGET))[0];
-
- # this performs the moral equivalent of defined or/dor/C<//>,
- # without the short circuiting.You need to use a 'defined or'
- # type thing instead of just checking for truth values, because
- # VALUE could be 0.(i.e. "false")
-
- # You could also use this, but I find it less aesthetic:
- # (although it does short circuit)
- #( defined $restriction->{'VALUE'}? $restriction->{VALUE} :
- # defined $restriction->{'TICKET'} ?
- # $restriction->{TICKET} :
- # defined $restriction->{'BASE'} ?
- # $restriction->{BASE} :
- # defined $restriction->{'TARGET'} ?
- # $restriction->{TARGET} )
-
- my $ea = $restriction->{ENTRYAGGREGATOR} || $DefaultEA{$type} || "AND";
- if ( ref $ea ) {
- die "Invalid operator $op for $field ($type)"
- unless exists $ea->{$op};
- $ea = $ea->{$op};
- }
+ #use Data::Dumper;
+ #print Dumper($restriction),"\n";
- # Each CustomField should be put into a different Clause so they
- # are ANDed together.
- if ($restriction->{CUSTOMFIELD}) {
- $realfield = $field;
- }
+ # We need to reimplement the subclause aggregation that SearchBuilder does.
+ # Default Subclause is ALIAS.FIELD, and default ALIAS is 'main',
+ # Then SB AND's the different Subclauses together.
+
+ # So, we want to group things into Subclauses, convert them to
+ # SQL, and then join them with the appropriate DefaultEA.
+ # Then join each subclause group with AND.
+
+ my $field = $restriction->{'FIELD'};
+ my $realfield = $field; # CustomFields fake up a fieldname, so
+ # we need to figure that out
+
+ # One special case
+ # Rewrite LinkedTo meta field to the real field
+ if ( $field =~ /LinkedTo/ ) {
+ $realfield = $field = $restriction->{'TYPE'};
+ }
+
+ # Two special case
+ # Handle subkey fields with a different real field
+ if ( $field =~ /^(\w+)\./ ) {
+ $realfield = $1;
+ }
+
+ die "I don't know about $field yet"
+ unless ( exists $FIELDS{$realfield}
+ or $restriction->{CUSTOMFIELD} );
+
+ my $type = $FIELDS{$realfield}->[0];
+ my $op = $restriction->{'OPERATOR'};
+
+ my $value = (
+ grep {defined}
+ map { $restriction->{$_} } qw(VALUE TICKET BASE TARGET)
+ )[0];
+
+ # this performs the moral equivalent of defined or/dor/C<//>,
+ # without the short circuiting.You need to use a 'defined or'
+ # type thing instead of just checking for truth values, because
+ # VALUE could be 0.(i.e. "false")
+
+ # You could also use this, but I find it less aesthetic:
+ # (although it does short circuit)
+ #( defined $restriction->{'VALUE'}? $restriction->{VALUE} :
+ # defined $restriction->{'TICKET'} ?
+ # $restriction->{TICKET} :
+ # defined $restriction->{'BASE'} ?
+ # $restriction->{BASE} :
+ # defined $restriction->{'TARGET'} ?
+ # $restriction->{TARGET} )
+
+ my $ea = $restriction->{ENTRYAGGREGATOR}
+ || $DefaultEA{$type}
+ || "AND";
+ if ( ref $ea ) {
+ die "Invalid operator $op for $field ($type)"
+ unless exists $ea->{$op};
+ $ea = $ea->{$op};
+ }
+
+ # Each CustomField should be put into a different Clause so they
+ # are ANDed together.
+ if ( $restriction->{CUSTOMFIELD} ) {
+ $realfield = $field;
+ }
- exists $clause{$realfield} or $clause{$realfield} = [];
- # Escape Quotes
- $field =~ s!(['"])!\\$1!g;
- $value =~ s!(['"])!\\$1!g;
- my $data = [ $ea, $type, $field, $op, $value ];
+ exists $clause{$realfield} or $clause{$realfield} = [];
- # here is where we store extra data, say if it's a keyword or
- # something. (I.e. "TYPE SPECIFIC STUFF")
+ # Escape Quotes
+ $field =~ s!(['"])!\\$1!g;
+ $value =~ s!(['"])!\\$1!g;
+ my $data = [ $ea, $type, $field, $op, $value ];
- #print Dumper($data);
- push @{$clause{$realfield}}, $data;
- }
- return \%clause;
+ # here is where we store extra data, say if it's a keyword or
+ # something. (I.e. "TYPE SPECIFIC STUFF")
+
+ #print Dumper($data);
+ push @{ $clause{$realfield} }, $data;
+ }
+ return \%clause;
}
# }}}
sub _ProcessRestrictions {
my $self = shift;
-
+
#Blow away ticket aliases since we'll need to regenerate them for
#a new search
delete $self->{'TicketAliases'};
- delete $self->{'items_array'};
+ delete $self->{'items_array'};
delete $self->{'item_map'};
delete $self->{'raw_rows'};
delete $self->{'rows'};
delete $self->{'count_all'};
-
- my $sql = $self->{_sql_query}; # Violating the _SQL namespace
- if (!$sql||$self->{'RecalcTicketLimits'}) {
- # "Restrictions to Clauses Branch\n";
- my $clauseRef = eval { $self->_RestrictionsToClauses; };
- if ($@) {
- $RT::Logger->error( "RestrictionsToClauses: " . $@ );
- $self->FromSQL("");
- } else {
- $sql = $self->ClausesToSQL($clauseRef);
- $self->FromSQL($sql);
- }
- }
+ my $sql = $self->Query; # Violating the _SQL namespace
+ if ( !$sql || $self->{'RecalcTicketLimits'} ) {
+
+ # "Restrictions to Clauses Branch\n";
+ my $clauseRef = eval { $self->_RestrictionsToClauses; };
+ if ($@) {
+ $RT::Logger->error( "RestrictionsToClauses: " . $@ );
+ $self->FromSQL("");
+ }
+ else {
+ $sql = $self->ClausesToSQL($clauseRef);
+ $self->FromSQL($sql) if $sql;
+ }
+ }
$self->{'RecalcTicketLimits'} = 0;
my $self = shift;
my $items = $self->ItemsArrayRef;
- my $prev = 0 ;
+ my $prev = 0;
delete $self->{'item_map'};
- if ($items->[0]) {
- $self->{'item_map'}->{'first'} = $items->[0]->EffectiveId;
- while (my $item = shift @$items ) {
- my $id = $item->EffectiveId;
- $self->{'item_map'}->{$id}->{'defined'} = 1;
- $self->{'item_map'}->{$id}->{prev} = $prev;
- $self->{'item_map'}->{$id}->{next} = $items->[0]->EffectiveId if ($items->[0]);
- $prev = $id;
- }
- $self->{'item_map'}->{'last'} = $prev;
+ if ( $items->[0] ) {
+ $self->{'item_map'}->{'first'} = $items->[0]->EffectiveId;
+ while ( my $item = shift @$items ) {
+ my $id = $item->EffectiveId;
+ $self->{'item_map'}->{$id}->{'defined'} = 1;
+ $self->{'item_map'}->{$id}->{prev} = $prev;
+ $self->{'item_map'}->{$id}->{next} = $items->[0]->EffectiveId
+ if ( $items->[0] );
+ $prev = $id;
+ }
+ $self->{'item_map'}->{'last'} = $prev;
}
-}
-
+}
=head2 ItemMap
$ItemMap->{'first'} = first ticketid found
$ItemMap->{'last'} = last ticketid found
-$ItemMap->{$id}->{prev} = the tikcet id found before $id
-$ItemMap->{$id}->{next} = the tikcet id found after $id
+$ItemMap->{$id}->{prev} = the ticket id found before $id
+$ItemMap->{$id}->{next} = the ticket id found after $id
=cut
sub ItemMap {
my $self = shift;
- $self->_BuildItemMap() unless ($self->{'item_map'});
- return ($self->{'item_map'});
+ $self->_BuildItemMap()
+ unless ( $self->{'items_array'} and $self->{'item_map'} );
+ return ( $self->{'item_map'} );
}
+=cut
-=cut
-
}
=cut
-
sub PrepForSerialization {
my $self = shift;
delete $self->{'items'};
$self->RedoSearch();
}
+=head1 FLAGS
+
+RT::Tickets supports several flags which alter search behavior:
+
+
+allow_deleted_search (Otherwise never show deleted tickets in search results)
+looking_at_type (otherwise limit to type=ticket)
+
+These flags are set by calling
+
+$tickets->{'flagname'} = 1;
+
+BUG: There should be an API for this
+
+=cut
+
+=begin testing
+
+# We assume that we've got some tickets hanging around from before.
+ok( my $unlimittickets = RT::Tickets->new( $RT::SystemUser ) );
+ok( $unlimittickets->UnLimit );
+ok( $unlimittickets->Count > 0, "UnLimited tickets object should return tickets" );
+
+=end testing
+
1;
+
+