diff options
Diffstat (limited to 'rt/lib/RT/Tickets_Overlay.pm')
-rw-r--r-- | rt/lib/RT/Tickets_Overlay.pm | 977 |
1 files changed, 338 insertions, 639 deletions
diff --git a/rt/lib/RT/Tickets_Overlay.pm b/rt/lib/RT/Tickets_Overlay.pm index 2d89ca2db..98a6ac61a 100644 --- a/rt/lib/RT/Tickets_Overlay.pm +++ b/rt/lib/RT/Tickets_Overlay.pm @@ -1,40 +1,38 @@ # BEGIN BPS TAGGED BLOCK {{{ -# +# # COPYRIGHT: -# -# This software is Copyright (c) 1996-2007 Best Practical Solutions, LLC +# +# 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. -# +# # 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., 51 Franklin Street, Fifth Floor, Boston, MA -# 02110-1301 or visit their web page on the internet at -# http://www.gnu.org/copyleft/gpl.html. -# -# +# 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 @@ -43,7 +41,7 @@ # 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: @@ -87,18 +85,20 @@ ok( $testtickets->Count == 0 ); package RT::Tickets; use strict; -no warnings qw(redefine); +package RT::Tickets; + +no warnings qw(redefine); +use vars qw(@SORTFIELDS); use RT::CustomFields; -use DBIx::SearchBuilder::Unique; # Configuration Tables: -# FIELD_METADATA is a mapping of searchable Field name, to Type, and other +# FIELDS is a mapping of searchable Field name, to Type, and other # metadata. -my %FIELD_METADATA = ( - Status => [ 'ENUM', ], +my %FIELDS = ( + Status => ['ENUM'], Queue => [ 'ENUM' => 'Queue', ], Type => [ 'ENUM', ], Creator => [ 'ENUM' => 'User', ], @@ -111,7 +111,6 @@ my %FIELD_METADATA = ( Priority => [ 'INT', ], TimeLeft => [ 'INT', ], TimeWorked => [ 'INT', ], - TimeEstimated => [ 'INT', ], MemberOf => [ 'LINK' => To => 'MemberOf', ], DependsOn => [ 'LINK' => To => 'DependsOn', ], RefersTo => [ 'LINK' => To => 'RefersTo', ], @@ -135,10 +134,9 @@ my %FIELD_METADATA = ( Requestors => [ 'WATCHERFIELD' => 'Requestor', ], Cc => [ 'WATCHERFIELD' => 'Cc', ], AdminCc => [ 'WATCHERFIELD' => 'AdminCc', ], - Watcher => [ 'WATCHERFIELD', ], + Watcher => ['WATCHERFIELD'], LinkedTo => [ 'LINKFIELD', ], CustomFieldValue => [ 'CUSTOMFIELD', ], - CustomField => [ 'CUSTOMFIELD', ], CF => [ 'CUSTOMFIELD', ], Updated => [ 'TRANSDATE', ], RequestorGroup => [ 'MEMBERSHIPFIELD' => 'Requestor', ], @@ -161,7 +159,7 @@ my %dispatch = ( LINKFIELD => \&_LinkFieldLimit, CUSTOMFIELD => \&_CustomFieldLimit, ); -my %can_bundle = (); # WATCHERFIELD => "yes", ); +my %can_bundle = ( WATCHERFIELD => "yes", ); # Default EntryAggregator per type # if you specify OP, you must specify all valid OPs @@ -202,7 +200,7 @@ my %DefaultEA = ( # Helper functions for passing the above lexically scoped tables above # into Tickets_Overlay_SQL. -sub FIELDS { return \%FIELD_METADATA } +sub FIELDS { return \%FIELDS } sub dispatch { return \%dispatch } sub can_bundle { return \%can_bundle } @@ -211,7 +209,7 @@ require RT::Tickets_Overlay_SQL; # {{{ sub SortFields -our @SORTFIELDS = qw(id Status +@SORTFIELDS = qw(id Status Queue Subject Owner Created Due Starts Started Told @@ -232,22 +230,6 @@ sub SortFields { # BEGIN SQL STUFF ********************************* - -sub CleanSlate { - my $self = shift; - $self->SUPER::CleanSlate( @_ ); - delete $self->{$_} foreach qw( - _sql_cf_alias - _sql_group_members_aliases - _sql_object_cfv_alias - _sql_role_group_aliases - _sql_transalias - _sql_trattachalias - _sql_u_watchers_alias_for_sort - _sql_u_watchers_aliases - ); -} - =head1 Limit Helper Routines These routines are the targets of a dispatch table depending on the @@ -289,7 +271,7 @@ sub _EnumLimit { unless $op eq "=" or $op eq "!="; - my $meta = $FIELD_METADATA{$field}; + my $meta = $FIELDS{$field}; if ( defined $meta->[1] && defined $value && $value !~ /^\d+$/ ) { my $class = "RT::" . $meta->[1]; my $o = $class->new( $sb->CurrentUser ); @@ -333,24 +315,26 @@ sub _IntLimit { Handle fields which deal with links between tickets. (MemberOf, DependsOn) Meta Data: - 1: Direction (From, To) - 2: Link Type (MemberOf, DependsOn, RefersTo) + 1: Direction (From,To) + 2: Link Type (MemberOf, DependsOn,RefersTo) =cut sub _LinkLimit { my ( $sb, $field, $op, $value, @rest ) = @_; - my $meta = $FIELD_METADATA{$field}; - die "Incorrect Metadata for $field" - unless defined $meta->[1] && defined $meta->[2]; + my $meta = $FIELDS{$field}; + die "Invalid Operator $op for $field" unless $op =~ /^(=|!=|IS)/io; - die "Invalid Operator $op for $field" unless $op =~ /^(=|!=|IS|IS NOT)$/io; + die "Incorrect Metadata for $field" + unless ( defined $meta->[1] and defined $meta->[2] ); my $direction = $meta->[1]; my $matchfield; my $linkfield; + my $is_local = 1; + my $is_null = 0; if ( $direction eq 'To' ) { $matchfield = "Target"; $linkfield = "Base"; @@ -365,105 +349,84 @@ sub _LinkLimit { die "Invalid link direction '$meta->[1]' for $field\n"; } - my ($is_local, $is_null) = (1, 0); - if ( !$value || $value =~ /^null$/io ) { - $is_null = 1; - $op = ($op =~ /^(=|IS)$/)? 'IS': 'IS NOT'; - } - elsif ( $value =~ /\D/o ) { - $is_local = 0; - } - $matchfield = "Local$matchfield" if $is_local; - - my $is_negative = 0; - if ( $op eq '!=' ) { - $is_negative = 1; - $op = '='; + 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; + } } #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 Links_1.LocalBase IS NULL; +# WHERE ((main.EffectiveId = main.id)) +# AND ((main.Status != 'deleted')) +# AND (Links_1.LocalBase IS NULL); if ($is_null) { my $linkalias = $sb->Join( - TYPE => 'LEFT', + TYPE => 'left', ALIAS1 => 'main', FIELD1 => 'id', TABLE2 => 'Links', FIELD2 => 'Local' . $linkfield ); + $sb->SUPER::Limit( LEFTJOIN => $linkalias, FIELD => 'Type', OPERATOR => '=', VALUE => $meta->[2], - ); - $sb->_SQLLimit( @rest, - ALIAS => $linkalias, - FIELD => $matchfield, - OPERATOR => $op, - VALUE => 'NULL', - QUOTEVALUE => 0, - ); - } - elsif ( $is_negative ) { - 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], - ); - $sb->SUPER::Limit( - LEFTJOIN => $linkalias, - FIELD => $matchfield, - OPERATOR => $op, - VALUE => $value, ); + $sb->_SQLLimit( - @rest, - ALIAS => $linkalias, - FIELD => $matchfield, + ALIAS => $linkalias, + ENTRYAGGREGATOR => 'AND', + FIELD => ( $is_local ? "Local$matchfield" : $matchfield ), OPERATOR => 'IS', VALUE => 'NULL', - QUOTEVALUE => 0, + QUOTEVALUE => '0', ); + } else { - my $linkalias = $sb->NewAlias('Links'); + + $sb->{_sql_linkalias} = $sb->NewAlias('Links') + unless defined $sb->{_sql_linkalias}; + $sb->_OpenParen(); + $sb->_SQLLimit( - @rest, - ALIAS => $linkalias, + ALIAS => $sb->{_sql_linkalias}, FIELD => 'Type', OPERATOR => '=', VALUE => $meta->[2], + @rest, ); + $sb->_SQLLimit( - ALIAS => $linkalias, - FIELD => 'Local' . $linkfield, - OPERATOR => '=', - VALUE => 'main.id', - QUOTEVALUE => 0, + ALIAS => $sb->{_sql_linkalias}, ENTRYAGGREGATOR => 'AND', + FIELD => ( $is_local ? "Local$matchfield" : $matchfield ), + OPERATOR => '=', + VALUE => $value, ); - $sb->_SQLLimit( - ALIAS => $linkalias, - FIELD => $matchfield, - OPERATOR => $op, - VALUE => $value, - ENTRYAGGREGATOR => 'AND', + + #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(); } } @@ -483,7 +446,7 @@ sub _DateLimit { die "Invalid Date Op: $op" unless $op =~ /^(=|>|<|>=|<=)$/; - my $meta = $FIELD_METADATA{$field}; + my $meta = $FIELDS{$field}; die "Incorrect Meta Data for $field" unless ( defined $meta->[1] ); @@ -573,20 +536,8 @@ sub _TransDateLimit { # See the comments for TransLimit, they apply here too - unless ( $sb->{_sql_transalias} ) { - $sb->{_sql_transalias} = $sb->Join( - ALIAS1 => 'main', - FIELD1 => 'id', - TABLE2 => 'Transactions', - FIELD2 => 'ObjectId', - ); - $sb->SUPER::Limit( - ALIAS => $sb->{_sql_transalias}, - FIELD => 'ObjectType', - VALUE => 'RT::Ticket', - ENTRYAGGREGATOR => 'AND', - ); - } + $sb->{_sql_transalias} = $sb->NewAlias('Transactions') + unless defined $sb->{_sql_transalias}; my $date = RT::Date->new( $sb->CurrentUser ); $date->Set( Format => 'unknown', Value => $value ); @@ -637,6 +588,20 @@ sub _TransDateLimit { ); } + # Join Transactions to Tickets + $sb->_SQLJoin( + ALIAS1 => 'main', + FIELD1 => $sb->{'primary_key'}, # UGH! + ALIAS2 => $sb->{_sql_transalias}, + FIELD2 => 'ObjectId' + ); + + $sb->SUPER::Limit( + ALIAS => $sb->{_sql_transalias}, + FIELD => 'ObjectType', + VALUE => 'RT::Ticket' + ); + $sb->_CloseParen; } @@ -685,63 +650,44 @@ sub _TransLimit { my ( $self, $field, $op, $value, @rest ) = @_; - unless ( $self->{_sql_transalias} ) { - $self->{_sql_transalias} = $self->Join( - ALIAS1 => 'main', - FIELD1 => 'id', - TABLE2 => 'Transactions', - FIELD2 => 'ObjectId', - ); - $self->SUPER::Limit( - ALIAS => $self->{_sql_transalias}, - FIELD => 'ObjectType', - VALUE => 'RT::Ticket', - ENTRYAGGREGATOR => 'AND', - ); - } - unless ( defined $self->{_sql_trattachalias} ) { - $self->{_sql_trattachalias} = $self->_SQLJoin( - TYPE => 'LEFT', # not all txns have an attachment - ALIAS1 => $self->{_sql_transalias}, - FIELD1 => 'id', - TABLE2 => 'Attachments', - FIELD2 => 'TransactionId', - ); - } + $self->{_sql_transalias} = $self->NewAlias('Transactions') + unless defined $self->{_sql_transalias}; + $self->{_sql_trattachalias} = $self->NewAlias('Attachments') + unless defined $self->{_sql_trattachalias}; $self->_OpenParen; #Search for the right field - if ($field eq 'Content' and $RT::DontSearchFileAttachments) { - $self->_SQLLimit( - ALIAS => $self->{_sql_trattachalias}, - FIELD => 'Filename', - OPERATOR => 'IS', - VALUE => 'NULL', - SUBCLAUSE => 'contentquery', - ENTRYAGGREGATOR => 'AND', - ); - $self->_SQLLimit( - ALIAS => $self->{_sql_trattachalias}, - FIELD => $field, - OPERATOR => $op, - VALUE => $value, - CASESENSITIVE => 0, - @rest, - ENTRYAGGREGATOR => 'AND', - SUBCLAUSE => 'contentquery', - ); - } else { - $self->_SQLLimit( - ALIAS => $self->{_sql_trattachalias}, - FIELD => $field, - OPERATOR => $op, - VALUE => $value, - CASESENSITIVE => 0, - ENTRYAGGREGATOR => 'AND', - @rest - ); - } + $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' + ); $self->_CloseParen; @@ -830,129 +776,57 @@ sub _WatcherLimit { my $value = shift; my %rest = (@_); - my $meta = $FIELD_METADATA{ $field }; - my $type = $meta->[1] || ''; + # 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 ( $field eq 'Owner' && !$rest{SUBKEY} && $op =~ /^!?=$/ ) { - my $o = RT::User->new( $self->CurrentUser ); - $o->Load( $value ); - $self->_SQLLimit( - FIELD => 'Owner', - OPERATOR => $op, - VALUE => $o->Id, - %rest, - ); - return; + 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; } - $rest{SUBKEY} ||= 'EmailAddress'; - my $groups = $self->_RoleGroupsJoin( Type => $type ); + 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; - if ( $op =~ /^IS(?: NOT)?$/ ) { - my $group_members = $self->_GroupMembersJoin( GroupsAlias => $groups ); - $self->SUPER::Limit( - LEFTJOIN => $group_members, - FIELD => 'GroupId', - OPERATOR => '!=', - VALUE => "$group_members.MemberId", - QUOTEVALUE => 0, - ); - $self->_SQLLimit( - ALIAS => $group_members, - FIELD => 'GroupId', - OPERATOR => $op, - VALUE => $value, - %rest, - ); - } - elsif ( $op =~ /^!=$|^NOT\s+/i ) { - # reverse op - $op =~ s/!|NOT\s+//i; - - # XXX: we have no way to build correct "Watcher.X != 'Y'" when condition - # "X = 'Y'" matches more then one user so we try to fetch two records and - # do the right thing when there is only one exist and semi-working solution - # otherwise. - my $users_obj = RT::Users->new( $self->CurrentUser ); - $users_obj->Limit( - FIELD => $rest{SUBKEY}, - OPERATOR => $op, - VALUE => $value, - ); - $users_obj->OrderBy; - $users_obj->RowsPerPage(2); - my @users = @{ $users_obj->ItemsArrayRef }; - - my $group_members = $self->_GroupMembersJoin( GroupsAlias => $groups ); - if ( @users <= 1 ) { - my $uid = 0; - $uid = $users[0]->id if @users; - $self->SUPER::Limit( - LEFTJOIN => $group_members, - ALIAS => $group_members, - FIELD => 'MemberId', - VALUE => $uid, - ); - $self->_SQLLimit( - %rest, - ALIAS => $group_members, - FIELD => 'id', - OPERATOR => 'IS', - VALUE => 'NULL', - ); - } else { - $self->SUPER::Limit( - LEFTJOIN => $group_members, - FIELD => 'GroupId', - OPERATOR => '!=', - VALUE => "$group_members.MemberId", - QUOTEVALUE => 0, - ); - my $users = $self->Join( - TYPE => 'LEFT', - ALIAS1 => $group_members, - FIELD1 => 'MemberId', - TABLE2 => 'Users', - FIELD2 => 'id', - ); - $self->SUPER::Limit( - LEFTJOIN => $users, - ALIAS => $users, - FIELD => $rest{SUBKEY}, - OPERATOR => $op, - VALUE => $value, - CASESENSITIVE => 0, - ); - $self->_SQLLimit( - %rest, - ALIAS => $users, - FIELD => 'id', - OPERATOR => 'IS', - VALUE => 'NULL', - ); - } - } else { - my $group_members = $self->_GroupMembersJoin( - GroupsAlias => $groups, - New => 0, - ); + for my $chunk (@$field) { + ( $field, $op, $value, %rest ) = @$chunk; + $rest{SUBKEY} ||= 'EmailAddress'; - my $users = $self->{'_sql_u_watchers_aliases'}{$group_members}; - unless ( $users ) { - $users = $self->{'_sql_u_watchers_aliases'}{$group_members} = - $self->NewAlias('Users'); - $self->SUPER::Limit( - LEFTJOIN => $group_members, - ALIAS => $group_members, - FIELD => 'MemberId', - VALUE => "$users.id", - QUOTEVALUE => 0, - ); - } + my $re_negative_op = qr[!=|NOT LIKE]; + $self->_OpenParen if $op =~ /$re_negative_op/; $self->_SQLLimit( ALIAS => $users, @@ -960,97 +834,78 @@ sub _WatcherLimit { VALUE => $value, OPERATOR => $op, CASESENSITIVE => 0, - %rest, - ); - $self->_SQLLimit( - ENTRYAGGREGATOR => 'AND', - ALIAS => $group_members, - FIELD => 'id', - OPERATOR => 'IS NOT', - VALUE => 'NULL', + %rest ); + + if ( $op =~ /$re_negative_op/ ) { + $self->_SQLLimit( + ALIAS => $users, + FIELD => $rest{SUBKEY}, + OPERATOR => 'IS', + VALUE => 'NULL', + ENTRYAGGREGATOR => 'OR', + ); + $self->_CloseParen; + } } $self->_CloseParen; } -sub _RoleGroupsJoin { +=head2 _WatcherJoin + +Helper function which provides joins to a watchers table both for limits +and for ordering. + +=cut + +sub _WatcherJoin { my $self = shift; - my %args = (New => 0, Type => '', @_); - return $self->{'_sql_role_group_aliases'}{ $args{'Type'} } - if $self->{'_sql_role_group_aliases'}{ $args{'Type'} } && !$args{'New'}; - - # XXX: this has been fixed in DBIx::SB-1.48 - # XXX: if we change this from Join to NewAlias+Limit - # then Pg and mysql 5.x 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. - - # we always have watcher groups for ticket, so we use INNER join + 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', + ENTRYAGGREGATOR => 'AND' ); $self->SUPER::Limit( - LEFTJOIN => $groups, ALIAS => $groups, FIELD => 'Domain', VALUE => 'RT::Ticket-Role', + ENTRYAGGREGATOR => 'AND' ); $self->SUPER::Limit( - LEFTJOIN => $groups, ALIAS => $groups, FIELD => 'Type', - VALUE => $args{'Type'}, - ) if $args{'Type'}; - - $self->{'_sql_role_group_aliases'}{ $args{'Type'} } = $groups - unless $args{'New'}; - - return $groups; -} - -sub _GroupMembersJoin { - my $self = shift; - my %args = (New => 1, GroupsAlias => undef, @_); - - return $self->{'_sql_group_members_aliases'}{ $args{'GroupsAlias'} } - if $self->{'_sql_group_members_aliases'}{ $args{'GroupsAlias'} } - && !$args{'New'}; + VALUE => $type, + ENTRYAGGREGATOR => 'AND' + ) + if ($type); - my $alias = $self->Join( - TYPE => 'LEFT', - ALIAS1 => $args{'GroupsAlias'}, - FIELD1 => 'id', - TABLE2 => 'CachedGroupMembers', - FIELD2 => 'GroupId', - ENTRYAGGREGATOR => 'AND', + my $groupmembers = $self->Join( + TYPE => 'LEFT', + ALIAS1 => $groups, + FIELD1 => 'id', + TABLE2 => 'CachedGroupMembers', + FIELD2 => 'GroupId' ); - $self->{'_sql_group_members_aliases'}{ $args{'GroupsAlias'} } = $alias - unless $args{'New'}; - - return $alias; -} - -=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 || ''; - - - my $groups = $self->_RoleGroupsJoin( Type => $type ); - my $group_members = $self->_GroupMembersJoin( GroupsAlias => $groups ); # 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 @@ -1059,20 +914,20 @@ sub _WatcherJoin { # ticket roles, so we just hide entries in CGM table # with MemberId == GroupId from results $self->SUPER::Limit( - LEFTJOIN => $group_members, + LEFTJOIN => $groupmembers, FIELD => 'GroupId', OPERATOR => '!=', - VALUE => "$group_members.MemberId", + VALUE => "$groupmembers.MemberId", QUOTEVALUE => 0, ); my $users = $self->Join( - TYPE => 'LEFT', - ALIAS1 => $group_members, - FIELD1 => 'MemberId', - TABLE2 => 'Users', - FIELD2 => 'id', + TYPE => 'LEFT', + ALIAS1 => $groupmembers, + FIELD1 => 'MemberId', + TABLE2 => 'Users', + FIELD2 => 'id' ); - return ($groups, $group_members, $users); + return $self->{'_watcher_join_users_alias'}{ $type || 'any' } = $users; } =head2 _WatcherMembershipLimit @@ -1168,7 +1023,7 @@ sub _WatcherMembershipLimit { # }}} # If we care about which sort of watcher - my $meta = $FIELD_METADATA{$field}; + my $meta = $FIELDS{$field}; my $type = ( defined $meta->[1] ? $meta->[1] : undef ); if ($type) { @@ -1281,175 +1136,122 @@ sub _LinkFieldLimit { } } +=head2 KeywordLimit -=head2 _CustomFieldDecipher +Limit based on Keywords -Try and turn a CF descriptor into (cfid, cfname) object pair. +Meta Data: + none =cut -sub _CustomFieldDecipher { - my ($self, $field) = @_; - +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 = 0; + if ( $field =~ /^(.+?)\.{(.+)}$/ ) { - ($queue, $field) = ($1, $2); + $queue = $1; + $field = $2; } $field = $1 if $field =~ /^{(.+)}$/; # trim { } - my $cfid; - if ( $queue ) { + # 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. + + my $null_columns_ok; + if ( ( $op =~ /^NOT LIKE$/i ) or ( $op eq '!=' ) ) { + $null_columns_ok = 1; + } + + my $cfid = 0; + if ($queue) { + my $q = RT::Queue->new( $self->CurrentUser ); - $q->Load( $queue ) if $queue; + $q->Load($queue) if ($queue); my $cf; if ( $q->id ) { - # $queue = $q->Name; # should we normalize the queue? - $cf = $q->CustomField( $field ); + $cf = $q->CustomField($field); } else { $cf = RT::CustomField->new( $self->CurrentUser ); - $cf->LoadByNameAndQueue( Queue => 0, Name => $field ); + $cf->LoadByNameAndQueue( Queue => '0', Name => $field ); } - $cfid = $cf->id if $cf; - } - - return ($queue, $field, $cfid); - -} - -=head2 _CustomFieldJoin - -Factor out the Join of custom fields so we can use it for sorting too -=cut + $cfid = $cf->id; -sub _CustomFieldJoin { - my ($self, $cfkey, $cfid, $field) = @_; - # Perform one Join per CustomField - if ( $self->{_sql_object_cfv_alias}{$cfkey} || - $self->{_sql_cf_alias}{$cfkey} ) - { - return ( $self->{_sql_object_cfv_alias}{$cfkey}, - $self->{_sql_cf_alias}{$cfkey} ); } - my ($TicketCFs, $CFs); - if ( $cfid ) { - $TicketCFs = $self->{_sql_object_cfv_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' - ); + 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 { - my $ocfalias = $self->Join( - TYPE => 'LEFT', - FIELD1 => 'Queue', - TABLE2 => 'ObjectCustomFields', - FIELD2 => 'ObjectId', - ); + 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 => $ocfalias, - ENTRYAGGREGATOR => 'OR', - FIELD => 'ObjectId', - VALUE => '0', - ); - - $CFs = $self->{_sql_cf_alias}{$cfkey} = $self->Join( - TYPE => 'LEFT', - ALIAS1 => $ocfalias, - FIELD1 => 'CustomField', - TABLE2 => 'CustomFields', - FIELD2 => 'id', - ); - - $TicketCFs = $self->{_sql_object_cfv_alias}{$cfkey} = $self->Join( - TYPE => 'left', - ALIAS1 => $CFs, - FIELD1 => 'id', - TABLE2 => 'ObjectCustomFieldValues', - FIELD2 => 'CustomField', + LEFTJOIN => $TicketCFs, + FIELD => 'ObjectType', + VALUE => ref( $self->NewItem ) + , # we want a single item, not a collection + ENTRYAGGREGATOR => 'AND' ); $self->SUPER::Limit( LEFTJOIN => $TicketCFs, - FIELD => 'ObjectId', - VALUE => 'main.id', - QUOTEVALUE => 0, - ENTRYAGGREGATOR => 'AND', - ); - } - $self->SUPER::Limit( - LEFTJOIN => $TicketCFs, - FIELD => 'ObjectType', - VALUE => 'RT::Ticket', - ENTRYAGGREGATOR => 'AND' - ); - $self->SUPER::Limit( - LEFTJOIN => $TicketCFs, - FIELD => 'Disabled', - OPERATOR => '=', - VALUE => '0', - ENTRYAGGREGATOR => 'AND' - ); - - return ($TicketCFs, $CFs); -} - -=head2 _CustomFieldLimit - -Limit based on CustomFields - -Meta Data: - none - -=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, $cfid); - ($queue, $field, $cfid ) = $self->_CustomFieldDecipher( $field ); - -# 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. - - my $null_columns_ok; - if ( ( $op =~ /^NOT LIKE$/i ) or ( $op eq '!=' ) ) { - $null_columns_ok = 1; - } - - my $cfkey = $cfid ? $cfid : "$queue.$field"; - my ($TicketCFs, $CFs) = $self->_CustomFieldJoin( $cfkey, $cfid, $field ); - - $self->_OpenParen; - - if ( $CFs ) { - $self->SUPER::Limit( - ALIAS => $CFs, - FIELD => 'Name', - VALUE => $field, - ENTRYAGGREGATOR => 'AND', + FIELD => 'Disabled', + OPERATOR => '=', + VALUE => '0', + ENTRYAGGREGATOR => 'AND' ); } - $self->_OpenParen if $null_columns_ok; + $self->_OpenParen if ($null_columns_ok); $self->_SQLLimit( ALIAS => $TicketCFs, @@ -1469,10 +1271,8 @@ sub _CustomFieldLimit { QUOTEVALUE => 0, ENTRYAGGREGATOR => 'OR', ); - $self->_CloseParen; } - - $self->_CloseParen; + $self->_CloseParen if ($null_columns_ok); } @@ -1480,99 +1280,6 @@ sub _CustomFieldLimit { # End of SQL Stuff ------------------------------------------------- -# {{{ Allow sorting on watchers - -=head2 OrderByCols ARRAY - -A modified version of the OrderBy method which automatically joins where -C<ALIAS> is set to the name of a watcher type. - -=cut - -sub OrderByCols { - my $self = shift; - my @args = @_; - my $clause; - my @res = (); - my $order = 0; - - foreach my $row (@args) { - if ( $row->{ALIAS} || $row->{FIELD} !~ /\./ ) { - push @res, $row; - next; - } - my ( $field, $subkey ) = split /\./, $row->{FIELD}, 2; - my $meta = $self->FIELDS->{$field}; - if ( $meta->[0] eq 'WATCHERFIELD' ) { - # cache alias as we want to use one alias per watcher type for sorting - my $users = $self->{_sql_u_watchers_alias_for_sort}{ $meta->[1] }; - unless ( $users ) { - $self->{_sql_u_watchers_alias_for_sort}{ $meta->[1] } - = $users = ( $self->_WatcherJoin( $meta->[1] ) )[2]; - } - push @res, { %$row, ALIAS => $users, FIELD => $subkey }; - } elsif ( $meta->[0] eq 'CUSTOMFIELD' ) { - my ($queue, $field, $cfid ) = $self->_CustomFieldDecipher( $subkey ); - my $cfkey = $cfid ? $cfid : "$queue.$field"; - my ($TicketCFs, $CFs) = $self->_CustomFieldJoin( $cfkey, $cfid, $field ); - unless ($cfid) { - # For those cases where we are doing a join against the - # CF name, and don't have a CFid, use Unique to make sure - # we don't show duplicate tickets. NOTE: I'm pretty sure - # this will stay mixed in for the life of the - # class/package, and not just for the life of the object. - # Potential performance issue. - require DBIx::SearchBuilder::Unique; - DBIx::SearchBuilder::Unique->import; - } - my $CFvs = $self->Join( - TYPE => 'left', - ALIAS1 => $TicketCFs, - FIELD1 => 'CustomField', - TABLE2 => 'CustomFieldValues', - FIELD2 => 'CustomField', - ); - $self->SUPER::Limit( - LEFTJOIN => $CFvs, - FIELD => 'Name', - QUOTEVALUE => 0, - VALUE => $TicketCFs . ".Content", - ENTRYAGGREGATOR => 'AND' - ); - - push @res, { %$row, ALIAS => $CFvs, FIELD => 'SortOrder' }; - push @res, { %$row, ALIAS => $TicketCFs, FIELD => 'Content' }; - } elsif ( $field eq "Custom" && $subkey eq "Ownership") { - # PAW logic is "reversed" - my $order = "ASC"; - if (exists $row->{ORDER} ) { - my $o = $row->{ORDER}; - delete $row->{ORDER}; - $order = "DESC" if $o =~ /asc/i; - } - - # Unowned - # Else - - # Ticket.Owner 1 0 0 - my $ownerId = $self->CurrentUser->Id; - push @res, { %$row, FIELD => "Owner=$ownerId", ORDER => $order } ; - - # Unowned Tickets 0 1 0 - my $nobodyId = $RT::Nobody->Id; - push @res, { %$row, FIELD => "Owner=$nobodyId", ORDER => $order } ; - - push @res, { %$row, FIELD => "Priority", ORDER => $order } ; - } - else { - push @res, $row; - } - } - return $self->SUPER::OrderByCols(@res); -} - -# }}} - # {{{ Limit the result set based on content # {{{ sub Limit @@ -1601,7 +1308,7 @@ sub Limit { 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; @@ -2198,10 +1905,9 @@ TARGET is the id or URI of the TARGET of the link sub LimitLinkedTo { my $self = shift; my %args = ( - TICKET => undef, - TARGET => undef, - TYPE => undef, - OPERATOR => '=', + TICKET => undef, + TARGET => undef, + TYPE => undef, @_ ); @@ -2215,7 +1921,6 @@ sub LimitLinkedTo { $self->loc( $args{'TYPE'} ), ( $args{'TARGET'} || $args{'TICKET'} ) ), - OPERATOR => $args{'OPERATOR'}, ); } @@ -2238,10 +1943,9 @@ 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, - OPERATOR => '=', + BASE => undef, + TICKET => undef, + TYPE => undef, @_ ); @@ -2263,7 +1967,6 @@ sub LimitLinkedFrom { $self->loc( $args{'TYPE'} ), ( $args{'BASE'} || $args{'TICKET'} ) ), - OPERATOR => $args{'OPERATOR'}, ); } @@ -2273,11 +1976,11 @@ sub LimitLinkedFrom { sub LimitMemberOf { my $self = shift; my $ticket_id = shift; - return $self->LimitLinkedTo( - @_, - TARGET => $ticket_id, + $self->LimitLinkedTo( + TARGET => "$ticket_id", TYPE => 'MemberOf', ); + } # }}} @@ -2286,8 +1989,7 @@ sub LimitMemberOf { sub LimitHasMember { my $self = shift; my $ticket_id = shift; - return $self->LimitLinkedFrom( - @_, + $self->LimitLinkedFrom( BASE => "$ticket_id", TYPE => 'HasMember', ); @@ -2301,9 +2003,8 @@ sub LimitHasMember { sub LimitDependsOn { my $self = shift; my $ticket_id = shift; - return $self->LimitLinkedTo( - @_, - TARGET => $ticket_id, + $self->LimitLinkedTo( + TARGET => "$ticket_id", TYPE => 'DependsOn', ); @@ -2316,9 +2017,8 @@ sub LimitDependsOn { sub LimitDependedOnBy { my $self = shift; my $ticket_id = shift; - return $self->LimitLinkedFrom( - @_, - BASE => $ticket_id, + $self->LimitLinkedFrom( + BASE => "$ticket_id", TYPE => 'DependentOn', ); @@ -2331,9 +2031,8 @@ sub LimitDependedOnBy { sub LimitRefersTo { my $self = shift; my $ticket_id = shift; - return $self->LimitLinkedTo( - @_, - TARGET => $ticket_id, + $self->LimitLinkedTo( + TARGET => "$ticket_id", TYPE => 'RefersTo', ); @@ -2346,11 +2045,11 @@ sub LimitRefersTo { sub LimitReferredToBy { my $self = shift; my $ticket_id = shift; - return $self->LimitLinkedFrom( - @_, - BASE => $ticket_id, + $self->LimitLinkedFrom( + BASE => "$ticket_id", TYPE => 'ReferredToBy', ); + } # }}} @@ -2817,9 +2516,9 @@ sub _RestrictionsToClauses { #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. + # 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. @@ -2842,10 +2541,10 @@ sub _RestrictionsToClauses { } die "I don't know about $field yet" - unless ( exists $FIELD_METADATA{$realfield} - or $restriction->{CUSTOMFIELD} ); + unless ( exists $FIELDS{$realfield} + or $restriction->{CUSTOMFIELD} ); - my $type = $FIELD_METADATA{$realfield}->[0]; + my $type = $FIELDS{$realfield}->[0]; my $op = $restriction->{'OPERATOR'}; my $value = ( |