QueueAdminCc => [ 'WATCHERFIELD' => 'AdminCc' => 'Queue', ], #loc_left_pair
QueueWatcher => [ 'WATCHERFIELD' => undef => 'Queue', ], #loc_left_pair
CustomFieldValue => [ 'CUSTOMFIELD', ], #loc_left_pair
+ DateCustomFieldValue => [ 'DATECUSTOMFIELD', ],
CustomField => [ 'CUSTOMFIELD', ], #loc_left_pair
CF => [ 'CUSTOMFIELD', ], #loc_left_pair
Updated => [ 'TRANSDATE', ], #loc_left_pair
CCGroup => [ 'MEMBERSHIPFIELD' => 'Cc', ], #loc_left_pair
AdminCCGroup => [ 'MEMBERSHIPFIELD' => 'AdminCc', ], #loc_left_pair
WatcherGroup => [ 'MEMBERSHIPFIELD', ], #loc_left_pair
+ HasAttribute => [ 'HASATTRIBUTE', 1 ],
+ HasNoAttribute => [ 'HASATTRIBUTE', 0 ],
);
# Mapping of Field Type to Function
WATCHERFIELD => \&_WatcherLimit,
MEMBERSHIPFIELD => \&_WatcherMembershipLimit,
CUSTOMFIELD => \&_CustomFieldLimit,
+ DATECUSTOMFIELD => \&_DateCustomFieldLimit,
+ HASATTRIBUTE => \&_HasAttributeLimit,
);
our %can_bundle = ();# WATCHERFIELD => "yes", );
'NOT LIKE' => 'AND'
},
+ HASATTRIBUTE => {
+ '=' => 'AND',
+ '!=' => 'AND',
+ },
+
CUSTOMFIELD => 'OR',
);
# 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.
- my ( $self, $field, $op, $value, @rest ) = @_;
+ my ( $self, $field, $op, $value, %rest ) = @_;
unless ( $self->{_sql_transalias} ) {
$self->{_sql_transalias} = $self->Join(
);
}
- $self->_OpenParen;
-
#Search for the right field
if ( $field eq 'Content' and RT->Config->Get('DontSearchFileAttachments') ) {
- $self->_SQLLimit(
- ALIAS => $self->{_sql_trattachalias},
- FIELD => 'Filename',
- OPERATOR => 'IS',
- VALUE => 'NULL',
- SUBCLAUSE => 'contentquery',
- ENTRYAGGREGATOR => 'AND',
- );
- $self->_SQLLimit(
+ $self->_OpenParen;
+ $self->_SQLLimit(
+ %rest,
ALIAS => $self->{_sql_trattachalias},
FIELD => $field,
OPERATOR => $op,
VALUE => $value,
CASESENSITIVE => 0,
- @rest,
+ );
+ $self->_SQLLimit(
ENTRYAGGREGATOR => 'AND',
- SUBCLAUSE => 'contentquery',
+ ALIAS => $self->{_sql_trattachalias},
+ FIELD => 'Filename',
+ OPERATOR => 'IS',
+ VALUE => 'NULL',
);
+ $self->_CloseParen;
} else {
$self->_SQLLimit(
+ %rest,
ALIAS => $self->{_sql_trattachalias},
FIELD => $field,
OPERATOR => $op,
VALUE => $value,
CASESENSITIVE => 0,
- ENTRYAGGREGATOR => 'AND',
- @rest
);
}
- $self->_CloseParen;
}
return ($TicketCFs, $CFs);
}
+=head2 _DateCustomFieldLimit
+
+Limit based on CustomFields of type Date
+
+Meta Data:
+ none
+
+=cut
+
+sub _DateCustomFieldLimit {
+ my ( $self, $_field, $op, $value, %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, $column);
+ ($queue, $field, $cfid, $column) = $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 && !$cfid ) {
+ $self->SUPER::Limit(
+ ALIAS => $CFs,
+ FIELD => 'Name',
+ VALUE => $field,
+ ENTRYAGGREGATOR => 'AND',
+ );
+ }
+
+ $self->_OpenParen if $null_columns_ok;
+
+ my $date = RT::Date->new( $self->CurrentUser );
+ $date->Set( Format => 'unknown', Value => $value );
+
+ 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.
+
+ $date->SetToMidnight( Timezone => 'server' );
+ my $daystart = $date->ISO;
+ $date->AddDay;
+ my $dayend = $date->ISO;
+
+ $self->_OpenParen;
+
+ $self->_SQLLimit(
+ ALIAS => $TicketCFs,
+ FIELD => 'Content',
+ OPERATOR => ">=",
+ VALUE => $daystart,
+ %rest,
+ );
+
+ $self->_SQLLimit(
+ ALIAS => $TicketCFs,
+ FIELD => 'Content',
+ OPERATOR => "<=",
+ VALUE => $dayend,
+ %rest,
+ ENTRYAGGREGATOR => 'AND',
+ );
+
+ $self->_CloseParen;
+
+ }
+ else {
+ $self->_SQLLimit(
+ ALIAS => $TicketCFs,
+ FIELD => 'Content',
+ OPERATOR => $op,
+ VALUE => $date->ISO,
+ %rest,
+ );
+ }
+
+ $self->_CloseParen;
+
+}
+
=head2 _CustomFieldLimit
Limit based on CustomFields
# we explicitly don't include the "IS NULL" case, since we would
# otherwise end up with a redundant clause.
- my ($negative_op, $null_op, $inv_op, $range_op) = $self->ClassifySQLOperation( $op );
+ my ($negative_op, $null_op, $inv_op, $range_op)
+ = $self->ClassifySQLOperation( $op );
my $fix_op = sub {
my $op = shift;
%rest
);
}
+ elsif ( $op eq '=' || $op eq '!=' || $op eq '<>' ) {
+ unless ( length( Encode::encode_utf8($value) ) > 255 ) {
+ $self->_SQLLimit(
+ ALIAS => $TicketCFs,
+ FIELD => 'Content',
+ OPERATOR => $op,
+ VALUE => $value,
+ %rest
+ );
+ } else {
+ $self->_OpenParen;
+ $self->_SQLLimit(
+ ALIAS => $TicketCFs,
+ FIELD => 'Content',
+ OPERATOR => '=',
+ VALUE => '',
+ ENTRYAGGREGATOR => 'OR'
+ );
+ $self->_SQLLimit(
+ ALIAS => $TicketCFs,
+ FIELD => 'Content',
+ OPERATOR => 'IS',
+ VALUE => 'NULL',
+ ENTRYAGGREGATOR => 'OR'
+ );
+ $self->_CloseParen;
+ $self->_SQLLimit(
+ ALIAS => $TicketCFs,
+ FIELD => 'LargeContent',
+ OPERATOR => $fix_op->($op),
+ VALUE => $value,
+ ENTRYAGGREGATOR => 'AND',
+ );
+ }
+ }
else {
$self->_SQLLimit(
ALIAS => $TicketCFs,
}
}
+sub _HasAttributeLimit {
+ my ( $self, $field, $op, $value, %rest ) = @_;
+
+ my $alias = $self->Join(
+ TYPE => 'LEFT',
+ ALIAS1 => 'main',
+ FIELD1 => 'id',
+ TABLE2 => 'Attributes',
+ FIELD2 => 'ObjectId',
+ );
+ $self->SUPER::Limit(
+ LEFTJOIN => $alias,
+ FIELD => 'ObjectType',
+ VALUE => 'RT::Ticket',
+ ENTRYAGGREGATOR => 'AND'
+ );
+ $self->SUPER::Limit(
+ LEFTJOIN => $alias,
+ FIELD => 'Name',
+ OPERATOR => $op,
+ VALUE => $value,
+ ENTRYAGGREGATOR => 'AND'
+ );
+ $self->_SQLLimit(
+ %rest,
+ ALIAS => $alias,
+ FIELD => 'id',
+ OPERATOR => $FIELD_METADATA{$field}->[1]? 'IS NOT': 'IS',
+ VALUE => 'NULL',
+ QUOTEVALUE => 0,
+ );
+}
+
+
# End Helper Functions
# End of SQL Stuff -------------------------------------------------
}
push @res, { %$row, FIELD => "Priority", ORDER => $order } ;
- }
+
+ } elsif ( $field eq 'Customer' ) { #Freeside
+
+ my $linkalias = $self->Join(
+ TYPE => 'LEFT',
+ ALIAS1 => 'main',
+ FIELD1 => 'id',
+ TABLE2 => 'Links',
+ FIELD2 => 'LocalBase'
+ );
+
+ $self->SUPER::Limit(
+ LEFTJOIN => $linkalias,
+ FIELD => 'Type',
+ OPERATOR => '=',
+ VALUE => 'MemberOf',
+ );
+ $self->SUPER::Limit(
+ LEFTJOIN => $linkalias,
+ FIELD => 'Target',
+ OPERATOR => 'STARTSWITH',
+ VALUE => 'freeside://freeside/cust_main/',
+ );
+
+ #if there was a Links.RemoteTarget int, this bs wouldn't be necessary
+ my $custnum_sql = "CAST(SUBSTR($linkalias.Target,31) AS INTEGER)";
+
+ if ( $subkey eq 'Number' ) {
+
+ push @res, { %$row,
+ ALIAS => '',
+ FIELD => $custnum_sql,
+ };
+
+ } elsif ( $subkey eq 'Name' ) {
+
+ my $custalias = $self->Join(
+ TYPE => 'LEFT',
+ EXPRESSION => $custnum_sql,
+ TABLE2 => 'cust_main',
+ FIELD2 => 'custnum',
+
+ );
+
+ my $field = "COALESCE( $custalias.company,
+ $custalias.last || ', ' || $custalias.first
+ )";
+
+ push @res, { %$row, ALIAS => '', FIELD => $field };
+
+ }
+
+ } #Freeside
+
else {
push @res, $row;
}
$args{CUSTOMFIELD} = $CF->Id;
}
+ # Handle special customfields types
+ if ($CF->Type eq 'Date') {
+ $args{FIELD} = 'DateCustomFieldValue';
+ }
+
#If we are looking to compare with a null value.
if ( $args{'OPERATOR'} =~ /^is$/i ) {
$args{'DESCRIPTION'}
sub ItemsArrayRef {
my $self = shift;
- unless ( $self->{'items_array'} ) {
+ return $self->{'items_array'} if $self->{'items_array'};
- my $placeholder = $self->_ItemsCounter;
- $self->GotoFirstItem();
- while ( my $item = $self->Next ) {
- push( @{ $self->{'items_array'} }, $item );
- }
- $self->GotoItem($placeholder);
- $self->{'items_array'}
- = $self->ItemsOrderBy( $self->{'items_array'} );
+ my $placeholder = $self->_ItemsCounter;
+ $self->GotoFirstItem();
+ while ( my $item = $self->Next ) {
+ push( @{ $self->{'items_array'} }, $item );
}
- return ( $self->{'items_array'} );
+ $self->GotoItem($placeholder);
+ $self->{'items_array'}
+ = $self->ItemsOrderBy( $self->{'items_array'} );
+
+ return $self->{'items_array'};
+}
+
+sub ItemsArrayRefWindow {
+ my $self = shift;
+ my $window = shift;
+
+ my @old = ($self->_ItemsCounter, $self->RowsPerPage, $self->FirstRow+1);
+
+ $self->RowsPerPage( $window );
+ $self->FirstRow(1);
+ $self->GotoFirstItem;
+
+ my @res;
+ while ( my $item = $self->Next ) {
+ push @res, $item;
+ }
+
+ $self->RowsPerPage( $old[1] );
+ $self->FirstRow( $old[2] );
+ $self->GotoItem( $old[0] );
+
+ return \@res;
}
# }}}
}
}
+ unless ( @direct_queues || keys %roles ) {
+ $self->SUPER::Limit(
+ SUBCLAUSE => 'ACL',
+ ALIAS => 'main',
+ FIELD => 'id',
+ VALUE => 0,
+ ENTRYAGGREGATOR => 'AND',
+ );
+ return $self->{'_sql_current_user_can_see_applied'} = 1;
+ }
+
{
my $join_roles = keys %roles;
$join_roles = 0 if $join_roles == 1 && $roles{'Owner'};
return unless @queues;
if ( @queues == 1 ) {
- $self->_SQLLimit(
+ $self->SUPER::Limit(
+ SUBCLAUSE => 'ACL',
ALIAS => 'main',
FIELD => 'Queue',
VALUE => $_[0],
ENTRYAGGREGATOR => $ea,
);
} else {
- $self->_OpenParen;
+ $self->SUPER::_OpenParen('ACL');
foreach my $q ( @queues ) {
- $self->_SQLLimit(
+ $self->SUPER::Limit(
+ SUBCLAUSE => 'ACL',
ALIAS => 'main',
FIELD => 'Queue',
VALUE => $q,
);
$ea = 'OR';
}
- $self->_CloseParen;
+ $self->SUPER::_CloseParen('ACL');
}
return 1;
};
- $self->_OpenParen;
+ $self->SUPER::_OpenParen('ACL');
my $ea = 'AND';
$ea = 'OR' if $limit_queues->( $ea, @direct_queues );
while ( my ($role, $queues) = each %roles ) {
- $self->_OpenParen;
+ $self->SUPER::_OpenParen('ACL');
if ( $role eq 'Owner' ) {
- $self->_SQLLimit(
+ $self->SUPER::Limit(
+ SUBCLAUSE => 'ACL',
FIELD => 'Owner',
VALUE => $id,
ENTRYAGGREGATOR => $ea,
);
}
else {
- $self->_SQLLimit(
+ $self->SUPER::Limit(
+ SUBCLAUSE => 'ACL',
ALIAS => $cgm_alias,
FIELD => 'MemberId',
OPERATOR => 'IS NOT',
QUOTEVALUE => 0,
ENTRYAGGREGATOR => $ea,
);
- $self->_SQLLimit(
+ $self->SUPER::Limit(
+ SUBCLAUSE => 'ACL',
ALIAS => $role_group_alias,
FIELD => 'Type',
VALUE => $role,
}
$limit_queues->( 'AND', @$queues ) if ref $queues;
$ea = 'OR' if $ea eq 'AND';
- $self->_CloseParen;
+ $self->SUPER::_CloseParen('ACL');
}
- $self->_CloseParen;
+ $self->SUPER::_CloseParen('ACL');
}
return $self->{'_sql_current_user_can_see_applied'} = 1;
}
=head2 _BuildItemMap
- # Build up a map of first/last/next/prev items, so that we can display search nav quickly
+Build up a L</ItemMap> of first/last/next/prev items, so that we can
+display search nav quickly.
=cut
sub _BuildItemMap {
my $self = shift;
- my $items = $self->ItemsArrayRef;
- my $prev = 0;
+ my $window = RT->Config->Get('TicketsItemMapSize');
- 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;
+ $self->{'item_map'} = {};
+
+ my $items = $self->ItemsArrayRefWindow( $window );
+ return unless $items && @$items;
+
+ my $prev = 0;
+ $self->{'item_map'}{'first'} = $items->[0]->EffectiveId;
+ for ( my $i = 0; $i < @$items; $i++ ) {
+ my $item = $items->[$i];
+ my $id = $item->EffectiveId;
+ $self->{'item_map'}{$id}{'defined'} = 1;
+ $self->{'item_map'}{$id}{'prev'} = $prev;
+ $self->{'item_map'}{$id}{'next'} = $items->[$i+1]->EffectiveId
+ if $items->[$i+1];
+ $prev = $id;
}
+ $self->{'item_map'}{'last'} = $prev
+ if !$window || @$items < $window;
}
=head2 ItemMap
-Returns an a map of all items found by this search. The map is of the form
+Returns an a map of all items found by this search. The map is a hash
+of the form:
-$ItemMap->{'first'} = first ticketid found
-$ItemMap->{'last'} = last ticketid found
-$ItemMap->{$id}->{prev} = the ticket id found before $id
-$ItemMap->{$id}->{next} = the ticket id found after $id
+ {
+ first => <first ticket id found>,
+ last => <last ticket id found or undef>,
+
+ <ticket id> => {
+ prev => <the ticket id found before>,
+ next => <the ticket id found after>,
+ },
+ <ticket id> => {
+ prev => ...,
+ next => ...,
+ },
+ }
=cut
sub ItemMap {
my $self = shift;
- $self->_BuildItemMap()
- unless ( $self->{'items_array'} and $self->{'item_map'} );
- return ( $self->{'item_map'} );
+ $self->_BuildItemMap unless $self->{'item_map'};
+ return $self->{'item_map'};
}
=head2 PrepForSerialization
-You don't want to serialize a big tickets object, as the {items} hash will be instantly invalid _and_ eat lots of space
+You don't want to serialize a big tickets object, as
+the {items} hash will be instantly invalid _and_ eat
+lots of space
=cut
sub PrepForSerialization {
my $self = shift;
delete $self->{'items'};
+ delete $self->{'items_array'};
$self->RedoSearch();
}