rt 4.0.23
[freeside.git] / rt / lib / RT / Tickets.pm
index bfc8e56..c826b6f 100755 (executable)
@@ -2,7 +2,7 @@
 #
 # COPYRIGHT:
 #
-# This software is Copyright (c) 1996-2012 Best Practical Solutions, LLC
+# This software is Copyright (c) 1996-2015 Best Practical Solutions, LLC
 #                                          <sales@bestpractical.com>
 #
 # (Except where explicitly superseded by other copyright notices)
@@ -89,7 +89,6 @@ use base 'RT::SearchBuilder';
 sub Table { 'Tickets'}
 
 use RT::CustomFields;
-use DBIx::SearchBuilder::Unique;
 
 # Configuration Tables:
 
@@ -142,9 +141,9 @@ our %FIELD_METADATA = (
     QueueCc          => [ 'WATCHERFIELD'    => 'Cc'      => 'Queue', ], #loc_left_pair
     QueueAdminCc     => [ 'WATCHERFIELD'    => 'AdminCc' => 'Queue', ], #loc_left_pair
     QueueWatcher     => [ 'WATCHERFIELD'    => undef     => 'Queue', ], #loc_left_pair
-    CustomFieldValue => [ 'CUSTOMFIELD', ], #loc_left_pair
-    CustomField      => [ 'CUSTOMFIELD', ], #loc_left_pair
-    CF               => [ 'CUSTOMFIELD', ], #loc_left_pair
+    CustomFieldValue => [ 'CUSTOMFIELD' => 'Ticket' ], #loc_left_pair
+    CustomField      => [ 'CUSTOMFIELD' => 'Ticket' ], #loc_left_pair
+    CF               => [ 'CUSTOMFIELD' => 'Ticket' ], #loc_left_pair
     Updated          => [ 'TRANSDATE', ], #loc_left_pair
     RequestorGroup   => [ 'MEMBERSHIPFIELD' => 'Requestor', ], #loc_left_pair
     CCGroup          => [ 'MEMBERSHIPFIELD' => 'Cc', ], #loc_left_pair
@@ -158,6 +157,9 @@ our %FIELD_METADATA = (
     WillResolve      => [ 'DATE'            => 'WillResolve', ], #loc_left_pair
 );
 
+# Lower Case version of FIELDS, for case insensitivity
+our %LOWER_CASE_FIELDS = map { ( lc($_) => $_ ) } (keys %FIELD_METADATA);
+
 our %SEARCHABLE_SUBFIELDS = (
     User => [qw(
         EmailAddress Name RealName Nickname Organization Address1 Address2
@@ -379,7 +381,11 @@ sub _EnumLimit {
         my $class = "RT::" . $meta->[1];
         my $o     = $class->new( $sb->CurrentUser );
         $o->Load($value);
-        $value = $o->Id;
+        $value = $o->Id || 0;
+    } elsif ( $field eq "Type" ) {
+        $value = lc $value if $value =~ /^(ticket|approval|reminder)$/i;
+    } elsif ($field eq "Status") {
+        $value = lc $value;
     }
     $sb->_SQLLimit(
         FIELD    => $field,
@@ -459,9 +465,10 @@ sub _LinkLimit {
 
     my $is_local = 1;
     if ( $is_null ) {
-        $op = ($op =~ /^(=|IS)$/)? 'IS': 'IS NOT';
+        $op = ($op =~ /^(=|IS)$/i)? 'IS': 'IS NOT';
     }
     elsif ( $value =~ /\D/ ) {
+        $value = RT::URI->new( $sb->CurrentUser )->CanonicalizeURI( $value );
         $is_local = 0;
     }
     $matchfield = "Local$matchfield" if $is_local;
@@ -937,22 +944,23 @@ sub _WatcherLimit {
         die "Invalid watcher subfield: '$rest{SUBKEY}'";
     }
 
+    # if it's equality op and search by Email or Name then we can preload user
+    # we do it to help some DBs better estimate number of rows and get better plans
+    if ( $op =~ /^!?=$/ && (!$rest{'SUBKEY'} || $rest{'SUBKEY'} eq 'Name' || $rest{'SUBKEY'} eq 'EmailAddress') ) {
+        my $o = RT::User->new( $self->CurrentUser );
+        my $method =
+            !$rest{'SUBKEY'}
+            ? $field eq 'Owner'? 'Load' : 'LoadByEmail'
+            : $rest{'SUBKEY'} eq 'EmailAddress' ? 'LoadByEmail': 'Load';
+        $o->$method( $value );
+        $rest{'SUBKEY'} = 'id';
+        $value = $o->id || 0;
+    }
+
     # 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' ) {
-        if ( $op =~ /^!?=$/ && (!$rest{'SUBKEY'} || $rest{'SUBKEY'} eq 'Name' || $rest{'SUBKEY'} eq 'EmailAddress') ) {
-            my $o = RT::User->new( $self->CurrentUser );
-            my $method = ($rest{'SUBKEY'}||'') eq 'EmailAddress' ? 'LoadByEmail': 'Load';
-            $o->$method( $value );
-            $self->_SQLLimit(
-                FIELD    => 'Owner',
-                OPERATOR => $op,
-                VALUE    => $o->id,
-                %rest,
-            );
-            return;
-        }
         if ( ($rest{'SUBKEY'}||'') eq 'id' ) {
             $self->_SQLLimit(
                 FIELD    => 'Owner',
@@ -965,13 +973,18 @@ sub _WatcherLimit {
     }
     $rest{SUBKEY} ||= 'EmailAddress';
 
-    my $groups = $self->_RoleGroupsJoin( Type => $type, Class => $class, New => !$type );
+    my ($groups, $group_members, $users);
+    if ( $rest{'BUNDLE'} ) {
+        ($groups, $group_members, $users) = @{ $rest{'BUNDLE'} };
+    } else {
+        $groups = $self->_RoleGroupsJoin( Type => $type, Class => $class, New => !$type );
+    }
 
     $self->_OpenParen;
-    if ( $op =~ /^IS(?: NOT)?$/ ) {
+    if ( $op =~ /^IS(?: NOT)?$/i ) {
         # is [not] empty case
 
-        my $group_members = $self->_GroupMembersJoin( GroupsAlias => $groups );
+        $group_members ||= $self->_GroupMembersJoin( GroupsAlias => $groups );
         # to avoid joining the table Users into the query, we just join GM
         # and make sure we don't match records where group is member of itself
         $self->SUPER::Limit(
@@ -1009,7 +1022,7 @@ sub _WatcherLimit {
         $users_obj->RowsPerPage(2);
         my @users = @{ $users_obj->ItemsArrayRef };
 
-        my $group_members = $self->_GroupMembersJoin( GroupsAlias => $groups );
+        $group_members ||= $self->_GroupMembersJoin( GroupsAlias => $groups );
         if ( @users <= 1 ) {
             my $uid = 0;
             $uid = $users[0]->id if @users;
@@ -1034,7 +1047,7 @@ sub _WatcherLimit {
                 VALUE      => "$group_members.MemberId",
                 QUOTEVALUE => 0,
             );
-            my $users = $self->Join(
+            $users ||= $self->Join(
                 TYPE            => 'LEFT',
                 ALIAS1          => $group_members,
                 FIELD1          => 'MemberId',
@@ -1060,10 +1073,10 @@ sub _WatcherLimit {
     } else {
         # positive condition case
 
-        my $group_members = $self->_GroupMembersJoin(
+        $group_members ||= $self->_GroupMembersJoin(
             GroupsAlias => $groups, New => 1, Left => 0
         );
-        my $users = $self->Join(
+        $users ||= $self->Join(
             TYPE            => 'LEFT',
             ALIAS1          => $group_members,
             FIELD1          => 'MemberId',
@@ -1080,6 +1093,7 @@ sub _WatcherLimit {
         );
     }
     $self->_CloseParen;
+    return ($groups, $group_members, $users);
 }
 
 sub _RoleGroupsJoin {
@@ -1330,33 +1344,44 @@ sub _WatcherMembershipLimit {
 
 Try and turn a CF descriptor into (cfid, cfname) object pair.
 
+Takes an optional second parameter of the CF LookupType, defaults to Ticket CFs.
+
 =cut
 
 sub _CustomFieldDecipher {
-    my ($self, $string) = @_;
+    my ($self, $string, $lookuptype) = @_;
+    $lookuptype ||= $self->_SingularClass->CustomFieldLookupType;
 
-    my ($queue, $field, $column) = ($string =~ /^(?:(.+?)\.)?{(.+)}(?:\.(Content|LargeContent))?$/);
+    my ($object, $field, $column) = ($string =~ /^(?:(.+?)\.)?\{(.+)\}(?:\.(Content|LargeContent))?$/);
     $field ||= ($string =~ /^{(.*?)}$/)[0] || $string;
 
-    my $cf;
-    if ( $queue ) {
-        my $q = RT::Queue->new( $self->CurrentUser );
-        $q->Load( $queue );
+    my ($cf, $applied_to);
 
-        if ( $q->id ) {
-            # $queue = $q->Name; # should we normalize the queue?
-            $cf = $q->CustomField( $field );
+    if ( $object ) {
+        my $record_class = RT::CustomField->RecordClassFromLookupType($lookuptype);
+        $applied_to = $record_class->new( $self->CurrentUser );
+        $applied_to->Load( $object );
+
+        if ( $applied_to->id ) {
+            RT->Logger->debug("Limiting to CFs identified by '$field' applied to $record_class #@{[$applied_to->id]} (loaded via '$object')");
         }
         else {
-            $RT::Logger->warning("Queue '$queue' doesn't exist, parsed from '$string'");
-            $queue = 0;
+            RT->Logger->warning("$record_class '$object' doesn't exist, parsed from '$string'");
+            $object = 0;
+            undef $applied_to;
         }
     }
-    elsif ( $field =~ /\D/ ) {
-        $queue = '';
+
+    if ( $field =~ /\D/ ) {
+        $object ||= '';
         my $cfs = RT::CustomFields->new( $self->CurrentUser );
-        $cfs->Limit( FIELD => 'Name', VALUE => $field );
-        $cfs->LimitToLookupType('RT::Queue-RT::Ticket');
+        $cfs->Limit( FIELD => 'Name', VALUE => $field, ($applied_to ? (CASESENSITIVE => 0) : ()) );
+        $cfs->LimitToLookupType($lookuptype);
+
+        if ($applied_to) {
+            $cfs->SetContextObject($applied_to);
+            $cfs->LimitToObjectId($applied_to->id);
+        }
 
         # if there is more then one field the current user can
         # see with the same name then we shouldn't return cf object
@@ -1369,9 +1394,11 @@ sub _CustomFieldDecipher {
     else {
         $cf = RT::CustomField->new( $self->CurrentUser );
         $cf->Load( $field );
+        $cf->SetContextObject($applied_to)
+            if $cf->id and $applied_to;
     }
 
-    return ($queue, $field, $cf, $column);
+    return ($object, $field, $cf, $column);
 }
 
 =head2 _CustomFieldJoin
@@ -1380,8 +1407,14 @@ Factor out the Join of custom fields so we can use it for sorting too
 
 =cut
 
+our %JOIN_ALIAS_FOR_LOOKUP_TYPE = (
+    RT::Ticket->CustomFieldLookupType      => sub { "main" },
+);
+
 sub _CustomFieldJoin {
-    my ($self, $cfkey, $cfid, $field) = @_;
+    my ($self, $cfkey, $cfid, $field, $type) = @_;
+    $type ||= RT::Ticket->CustomFieldLookupType;
+
     # Perform one Join per CustomField
     if ( $self->{_sql_object_cfv_alias}{$cfkey} ||
          $self->{_sql_cf_alias}{$cfkey} )
@@ -1390,17 +1423,21 @@ sub _CustomFieldJoin {
                  $self->{_sql_cf_alias}{$cfkey} );
     }
 
-    my ($TicketCFs, $CFs);
+    my $ObjectAlias = $JOIN_ALIAS_FOR_LOOKUP_TYPE{$type}
+        ? $JOIN_ALIAS_FOR_LOOKUP_TYPE{$type}->($self)
+        : die "We don't know how to join on $type";
+
+    my ($ObjectCFs, $CFs);
     if ( $cfid ) {
-        $TicketCFs = $self->{_sql_object_cfv_alias}{$cfkey} = $self->Join(
+        $ObjectCFs = $self->{_sql_object_cfv_alias}{$cfkey} = $self->Join(
             TYPE   => 'LEFT',
-            ALIAS1 => 'main',
+            ALIAS1 => $ObjectAlias,
             FIELD1 => 'id',
             TABLE2 => 'ObjectCustomFieldValues',
             FIELD2 => 'ObjectId',
         );
         $self->SUPER::Limit(
-            LEFTJOIN        => $TicketCFs,
+            LEFTJOIN        => $ObjectCFs,
             FIELD           => 'CustomField',
             VALUE           => $cfid,
             ENTRYAGGREGATOR => 'AND'
@@ -1432,7 +1469,7 @@ sub _CustomFieldJoin {
             LEFTJOIN        => $CFs,
             ENTRYAGGREGATOR => 'AND',
             FIELD           => 'LookupType',
-            VALUE           => 'RT::Queue-RT::Ticket',
+            VALUE           => $type,
         );
         $self->SUPER::Limit(
             LEFTJOIN        => $CFs,
@@ -1441,7 +1478,7 @@ sub _CustomFieldJoin {
             VALUE           => $field,
         );
 
-        $TicketCFs = $self->{_sql_object_cfv_alias}{$cfkey} = $self->Join(
+        $ObjectCFs = $self->{_sql_object_cfv_alias}{$cfkey} = $self->Join(
             TYPE   => 'LEFT',
             ALIAS1 => $CFs,
             FIELD1 => 'id',
@@ -1449,28 +1486,29 @@ sub _CustomFieldJoin {
             FIELD2 => 'CustomField',
         );
         $self->SUPER::Limit(
-            LEFTJOIN        => $TicketCFs,
+            LEFTJOIN        => $ObjectCFs,
             FIELD           => 'ObjectId',
-            VALUE           => 'main.id',
+            VALUE           => "$ObjectAlias.id",
             QUOTEVALUE      => 0,
             ENTRYAGGREGATOR => 'AND',
         );
     }
+
     $self->SUPER::Limit(
-        LEFTJOIN        => $TicketCFs,
+        LEFTJOIN        => $ObjectCFs,
         FIELD           => 'ObjectType',
-        VALUE           => 'RT::Ticket',
+        VALUE           => RT::CustomField->ObjectTypeFromLookupType($type),
         ENTRYAGGREGATOR => 'AND'
     );
     $self->SUPER::Limit(
-        LEFTJOIN        => $TicketCFs,
+        LEFTJOIN        => $ObjectCFs,
         FIELD           => 'Disabled',
         OPERATOR        => '=',
         VALUE           => '0',
         ENTRYAGGREGATOR => 'AND'
     );
 
-    return ($TicketCFs, $CFs);
+    return ($ObjectCFs, $CFs);
 }
 
 =head2 _CustomFieldLimit
@@ -1489,12 +1527,16 @@ use Regexp::Common::net::CIDR;
 sub _CustomFieldLimit {
     my ( $self, $_field, $op, $value, %rest ) = @_;
 
+    my $meta  = $FIELD_METADATA{ $_field };
+    my $class = $meta->[1] || 'Ticket';
+    my $type  = "RT::$class"->CustomFieldLookupType;
+
     my $field = $rest{'SUBKEY'} || die "No field specified";
 
     # For our sanity, we can only limit on one queue at a time
 
-    my ($queue, $cfid, $cf, $column);
-    ($queue, $field, $cf, $column) = $self->_CustomFieldDecipher( $field );
+    my ($object, $cfid, $cf, $column);
+    ($object, $field, $cf, $column) = $self->_CustomFieldDecipher( $field, $type );
     $cfid = $cf ? $cf->id  : 0 ;
 
 # If we're trying to find custom fields that don't match something, we
@@ -1535,15 +1577,6 @@ sub _CustomFieldLimit {
     }
 
     if ( $cf && $cf->Type eq 'IPAddressRange' ) {
-
-        if ( $value =~ /^\s*$RE{net}{CIDR}{IPv4}{-keep}\s*$/o ) {
-
-            # convert incomplete 192.168/24 to 192.168.0.0/24 format
-            $value =
-              join( '.', map $_ || 0, ( split /\./, $1 )[ 0 .. 3 ] ) . "/$2"
-              || $value;
-        }
-
         my ( $start_ip, $end_ip ) =
           RT::ObjectCustomFieldValue->ParseIPRange($value);
         if ( $start_ip && $end_ip ) {
@@ -1565,18 +1598,41 @@ sub _CustomFieldLimit {
         }
     }
 
+    if ( $cf && $cf->Type =~ /^Date(?:Time)?$/ ) {
+        my $date = RT::Date->new( $self->CurrentUser );
+        $date->Set( Format => 'unknown', Value => $value );
+        if ( $date->Unix ) {
+
+            if (
+                   $cf->Type eq 'Date'
+                || $value =~ /^\s*(?:today|tomorrow|yesterday)\s*$/i
+                || (   $value !~ /midnight|\d+:\d+:\d+/i
+                    && $date->Time( Timezone => 'user' ) eq '00:00:00' )
+              )
+            {
+                $value = $date->Date( Timezone => 'user' );
+            }
+            else {
+                $value = $date->DateTime;
+            }
+        }
+        else {
+            $RT::Logger->warn("$value is not a valid date string");
+        }
+    }
+
     my $single_value = !$cf || !$cfid || $cf->SingleValue;
 
-    my $cfkey = $cfid ? $cfid : "$queue.$field";
+    my $cfkey = $cfid ? $cfid : "$type-$object.$field";
 
     if ( $null_op && !$column ) {
         # IS[ NOT] NULL without column is the same as has[ no] any CF value,
         # we can reuse our default joins for this operation
         # with column specified we have different situation
-        my ($TicketCFs, $CFs) = $self->_CustomFieldJoin( $cfkey, $cfid, $field );
+        my ($ObjectCFs, $CFs) = $self->_CustomFieldJoin( $cfkey, $cfid, $field, $type );
         $self->_OpenParen;
         $self->_SQLLimit(
-            ALIAS    => $TicketCFs,
+            ALIAS    => $ObjectCFs,
             FIELD    => 'id',
             OPERATOR => $op,
             VALUE    => $value,
@@ -1599,11 +1655,11 @@ sub _CustomFieldLimit {
         $self->_OpenParen;
         if ( $op !~ /NOT|!=|<>/i ) { # positive equation
             $self->_CustomFieldLimit(
-                'CF', '<=', $end_ip, %rest,
+                $_field, '<=', $end_ip, %rest,
                 SUBKEY => $rest{'SUBKEY'}. '.Content',
             );
             $self->_CustomFieldLimit(
-                'CF', '>=', $start_ip, %rest,
+                $_field, '>=', $start_ip, %rest,
                 SUBKEY          => $rest{'SUBKEY'}. '.LargeContent',
                 ENTRYAGGREGATOR => 'AND',
             ); 
@@ -1611,20 +1667,20 @@ sub _CustomFieldLimit {
             # estimations and scan less rows
 # have to disable this tweak because of ipv6
 #            $self->_CustomFieldLimit(
-#                $field, '>=', '000.000.000.000', %rest,
+#                $_field, '>=', '000.000.000.000', %rest,
 #                SUBKEY          => $rest{'SUBKEY'}. '.Content',
 #                ENTRYAGGREGATOR => 'AND',
 #            );
 #            $self->_CustomFieldLimit(
-#                $field, '<=', '255.255.255.255', %rest,
+#                $_field, '<=', '255.255.255.255', %rest,
 #                SUBKEY          => $rest{'SUBKEY'}. '.LargeContent',
 #                ENTRYAGGREGATOR => 'AND',
 #            );  
         }       
         else { # negative equation
-            $self->_CustomFieldLimit($field, '>', $end_ip, %rest);
+            $self->_CustomFieldLimit($_field, '>', $end_ip, %rest);
             $self->_CustomFieldLimit(
-                $field, '<', $start_ip, %rest,
+                $_field, '<', $start_ip, %rest,
                 SUBKEY          => $rest{'SUBKEY'}. '.LargeContent',
                 ENTRYAGGREGATOR => 'OR',
             );  
@@ -1636,7 +1692,7 @@ sub _CustomFieldLimit {
     } 
     elsif ( !$negative_op || $single_value ) {
         $cfkey .= '.'. $self->{'_sql_multiple_cfs_index'}++ if !$single_value && !$range_op;
-        my ($TicketCFs, $CFs) = $self->_CustomFieldJoin( $cfkey, $cfid, $field );
+        my ($ObjectCFs, $CFs) = $self->_CustomFieldJoin( $cfkey, $cfid, $field, $type );
 
         $self->_OpenParen;
 
@@ -1647,10 +1703,11 @@ sub _CustomFieldLimit {
         # otherwise search in Content and in LargeContent
         if ( $column ) {
             $self->_SQLLimit( $fix_op->(
-                ALIAS      => $TicketCFs,
+                ALIAS      => $ObjectCFs,
                 FIELD      => $column,
                 OPERATOR   => $op,
                 VALUE      => $value,
+                CASESENSITIVE => 0,
                 %rest
             ) );
             $self->_CloseParen;
@@ -1659,27 +1716,12 @@ sub _CustomFieldLimit {
         }
         else {
             # need special treatment for Date
-            if ( $cf and $cf->Type eq 'DateTime' and $op eq '=' ) {
-
-                if ( $value =~ /:/ ) {
-                    # there is time speccified.
-                    my $date = RT::Date->new( $self->CurrentUser );
-                    $date->Set( Format => 'unknown', Value => $value );
-                    $self->_SQLLimit(
-                        ALIAS    => $TicketCFs,
-                        FIELD    => 'Content',
-                        OPERATOR => "=",
-                        VALUE    => $date->ISO,
-                        %rest,
-                    );
-                }
-                else {
+            if ( $cf and $cf->Type eq 'DateTime' and $op eq '=' && $value !~ /:/ ) {
                 # no time specified, that means we want everything on a
                 # particular day.  in the database, we need to check for >
                 # and < the edges of that day.
                     my $date = RT::Date->new( $self->CurrentUser );
                     $date->Set( Format => 'unknown', Value => $value );
-                    $date->SetToMidnight( Timezone => 'server' );
                     my $daystart = $date->ISO;
                     $date->AddDay;
                     my $dayend = $date->ISO;
@@ -1687,7 +1729,7 @@ sub _CustomFieldLimit {
                     $self->_OpenParen;
 
                     $self->_SQLLimit(
-                        ALIAS    => $TicketCFs,
+                        ALIAS    => $ObjectCFs,
                         FIELD    => 'Content',
                         OPERATOR => ">=",
                         VALUE    => $daystart,
@@ -1695,38 +1737,38 @@ sub _CustomFieldLimit {
                     );
 
                     $self->_SQLLimit(
-                        ALIAS    => $TicketCFs,
+                        ALIAS    => $ObjectCFs,
                         FIELD    => 'Content',
-                        OPERATOR => "<=",
+                        OPERATOR => "<",
                         VALUE    => $dayend,
                         %rest,
                         ENTRYAGGREGATOR => 'AND',
                     );
 
                     $self->_CloseParen;
-                }
             }
             elsif ( $op eq '=' || $op eq '!=' || $op eq '<>' ) {
-                if ( length( Encode::encode_utf8($value) ) < 256 ) {
+                if ( length( Encode::encode( "UTF-8", $value) ) < 256 ) {
                     $self->_SQLLimit(
-                        ALIAS    => $TicketCFs,
+                        ALIAS    => $ObjectCFs,
                         FIELD    => 'Content',
                         OPERATOR => $op,
                         VALUE    => $value,
+                        CASESENSITIVE => 0,
                         %rest
                     );
                 }
                 else {
                     $self->_OpenParen;
                     $self->_SQLLimit(
-                        ALIAS           => $TicketCFs,
+                        ALIAS           => $ObjectCFs,
                         FIELD           => 'Content',
                         OPERATOR        => '=',
                         VALUE           => '',
                         ENTRYAGGREGATOR => 'OR'
                     );
                     $self->_SQLLimit(
-                        ALIAS           => $TicketCFs,
+                        ALIAS           => $ObjectCFs,
                         FIELD           => 'Content',
                         OPERATOR        => 'IS',
                         VALUE           => 'NULL',
@@ -1734,34 +1776,36 @@ sub _CustomFieldLimit {
                     );
                     $self->_CloseParen;
                     $self->_SQLLimit( $fix_op->(
-                        ALIAS           => $TicketCFs,
+                        ALIAS           => $ObjectCFs,
                         FIELD           => 'LargeContent',
                         OPERATOR        => $op,
                         VALUE           => $value,
                         ENTRYAGGREGATOR => 'AND',
+                        CASESENSITIVE => 0,
                     ) );
                 }
             }
             else {
                 $self->_SQLLimit(
-                    ALIAS    => $TicketCFs,
+                    ALIAS    => $ObjectCFs,
                     FIELD    => 'Content',
                     OPERATOR => $op,
                     VALUE    => $value,
+                    CASESENSITIVE => 0,
                     %rest
                 );
 
                 $self->_OpenParen;
                 $self->_OpenParen;
                 $self->_SQLLimit(
-                    ALIAS           => $TicketCFs,
+                    ALIAS           => $ObjectCFs,
                     FIELD           => 'Content',
                     OPERATOR        => '=',
                     VALUE           => '',
                     ENTRYAGGREGATOR => 'OR'
                 );
                 $self->_SQLLimit(
-                    ALIAS           => $TicketCFs,
+                    ALIAS           => $ObjectCFs,
                     FIELD           => 'Content',
                     OPERATOR        => 'IS',
                     VALUE           => 'NULL',
@@ -1769,11 +1813,12 @@ sub _CustomFieldLimit {
                 );
                 $self->_CloseParen;
                 $self->_SQLLimit( $fix_op->(
-                    ALIAS           => $TicketCFs,
+                    ALIAS           => $ObjectCFs,
                     FIELD           => 'LargeContent',
                     OPERATOR        => $op,
                     VALUE           => $value,
                     ENTRYAGGREGATOR => 'AND',
+                    CASESENSITIVE => 0,
                 ) );
                 $self->_CloseParen;
             }
@@ -1802,7 +1847,7 @@ sub _CustomFieldLimit {
 
             if ($negative_op) {
                 $self->_SQLLimit(
-                    ALIAS           => $TicketCFs,
+                    ALIAS           => $ObjectCFs,
                     FIELD           => $column || 'Content',
                     OPERATOR        => 'IS',
                     VALUE           => 'NULL',
@@ -1816,7 +1861,7 @@ sub _CustomFieldLimit {
     }
     else {
         $cfkey .= '.'. $self->{'_sql_multiple_cfs_index'}++;
-        my ($TicketCFs, $CFs) = $self->_CustomFieldJoin( $cfkey, $cfid, $field );
+        my ($ObjectCFs, $CFs) = $self->_CustomFieldJoin( $cfkey, $cfid, $field, $type );
 
         # reverse operation
         $op =~ s/!|NOT\s+//i;
@@ -1825,25 +1870,27 @@ sub _CustomFieldLimit {
         # otherwise search in Content and in LargeContent
         if ( $column ) {
             $self->SUPER::Limit( $fix_op->(
-                LEFTJOIN   => $TicketCFs,
-                ALIAS      => $TicketCFs,
+                LEFTJOIN   => $ObjectCFs,
+                ALIAS      => $ObjectCFs,
                 FIELD      => $column,
                 OPERATOR   => $op,
                 VALUE      => $value,
+                CASESENSITIVE => 0,
             ) );
         }
         else {
             $self->SUPER::Limit(
-                LEFTJOIN   => $TicketCFs,
-                ALIAS      => $TicketCFs,
+                LEFTJOIN   => $ObjectCFs,
+                ALIAS      => $ObjectCFs,
                 FIELD      => 'Content',
                 OPERATOR   => $op,
                 VALUE      => $value,
+                CASESENSITIVE => 0,
             );
         }
         $self->_SQLLimit(
             %rest,
-            ALIAS      => $TicketCFs,
+            ALIAS      => $ObjectCFs,
             FIELD      => 'id',
             OPERATOR   => 'IS',
             VALUE      => 'NULL',
@@ -1953,10 +2000,10 @@ sub OrderByCols {
             }
             push @res, { %$row, ALIAS => $users, FIELD => $subkey };
        } elsif ( defined $meta->[0] && $meta->[0] eq 'CUSTOMFIELD' ) {
-           my ($queue, $field, $cf_obj, $column) = $self->_CustomFieldDecipher( $subkey );
-           my $cfkey = $cf_obj ? $cf_obj->id : "$queue.$field";
+           my ($object, $field, $cf_obj, $column) = $self->_CustomFieldDecipher( $subkey );
+           my $cfkey = $cf_obj ? $cf_obj->id : "$object.$field";
            $cfkey .= ".ordering" if !$cf_obj || ($cf_obj->MaxValues||0) != 1;
-           my ($TicketCFs, $CFs) = $self->_CustomFieldJoin( $cfkey, ($cf_obj ?$cf_obj->id :0) , $field );
+           my ($ObjectCFs, $CFs) = $self->_CustomFieldJoin( $cfkey, ($cf_obj ?$cf_obj->id :0) , $field );
            # this is described in _CustomFieldLimit
            $self->_SQLLimit(
                ALIAS      => $CFs,
@@ -1966,19 +2013,9 @@ sub OrderByCols {
                QUOTEVALUE => 1,
                ENTRYAGGREGATOR => 'AND',
            ) if $CFs;
-           unless ($cf_obj) {
-               # 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,
+               ALIAS1 => $ObjectCFs,
                FIELD1 => 'CustomField',
                TABLE2 => 'CustomFieldValues',
                FIELD2 => 'CustomField',
@@ -1987,12 +2024,12 @@ sub OrderByCols {
                LEFTJOIN        => $CFvs,
                FIELD           => 'Name',
                QUOTEVALUE      => 0,
-               VALUE           => $TicketCFs . ".Content",
+               VALUE           => $ObjectCFs . ".Content",
                ENTRYAGGREGATOR => 'AND'
            );
 
            push @res, { %$row, ALIAS => $CFvs, FIELD => 'SortOrder' };
-           push @res, { %$row, ALIAS => $TicketCFs, FIELD => 'Content' };
+           push @res, { %$row, ALIAS => $ObjectCFs, FIELD => 'Content' };
        } elsif ( $field eq "Custom" && $subkey eq "Ownership") {
            # PAW logic is "reversed"
            my $order = "ASC";
@@ -2084,7 +2121,12 @@ sub JoinToCustLinks {
             TABLE2 => 'Links',
             FIELD2 => 'LocalBase',
         );
-
+       $self->SUPER::Limit(
+         LEFTJOIN => $linkalias,
+         FIELD    => 'Base',
+         OPERATOR => 'LIKE',
+         VALUE    => 'fsck.com-rt://%/ticket/%',
+       );
         $self->SUPER::Limit(
             LEFTJOIN => $linkalias,
             FIELD    => 'Type',
@@ -2134,6 +2176,12 @@ sub JoinToSvcLinks {
             TABLE2 => 'Links',
             FIELD2 => 'LocalBase',
         );
+       $self->SUPER::Limit(
+         LEFTJOIN => $linkalias,
+         FIELD    => 'Base',
+         OPERATOR => 'LIKE',
+         VALUE    => 'fsck.com-rt://%/ticket/%',
+       );
 
         $self->SUPER::Limit(
             LEFTJOIN => $linkalias,
@@ -2448,7 +2496,7 @@ sub LimitType {
         VALUE       => $args{'VALUE'},
         OPERATOR    => $args{'OPERATOR'},
         DESCRIPTION => join( ' ',
-            $self->loc('Type'), $args{'OPERATOR'}, $args{'Limit'}, ),
+            $self->loc('Type'), $args{'OPERATOR'}, $args{'VALUE'}, ),
     );
 }
 
@@ -2511,7 +2559,7 @@ sub LimitId {
 
 Takes a paramhash with the fields OPERATOR and VALUE.
 OPERATOR is one of =, >, < or !=.
-VALUE is a value to match the ticket\'s priority against
+VALUE is a value to match the ticket's priority against
 
 =cut
 
@@ -2534,7 +2582,7 @@ sub LimitPriority {
 
 Takes a paramhash with the fields OPERATOR and VALUE.
 OPERATOR is one of =, >, < or !=.
-VALUE is a value to match the ticket\'s initial priority against
+VALUE is a value to match the ticket's initial priority against
 
 
 =cut
@@ -2558,7 +2606,7 @@ sub LimitInitialPriority {
 
 Takes a paramhash with the fields OPERATOR and VALUE.
 OPERATOR is one of =, >, < or !=.
-VALUE is a value to match the ticket\'s final priority against
+VALUE is a value to match the ticket's final priority against
 
 =cut
 
@@ -2731,7 +2779,7 @@ sub LimitOwner {
 
   Takes a paramhash with the fields OPERATOR, TYPE and VALUE.
   OPERATOR is one of =, LIKE, NOT LIKE or !=.
-  VALUE is a value to match the ticket\'s watcher email addresses against
+  VALUE is a value to match the ticket's watcher email addresses against
   TYPE is the sort of watchers you want to match against. Leave it undef if you want to search all of them
 
 
@@ -3107,7 +3155,7 @@ sub LimitCustomField {
     $self->Limit(
         VALUE => $args{VALUE},
         FIELD => "CF"
-            .(defined $args{'QUEUE'}? ".{$args{'QUEUE'}}" : '' )
+            .(defined $args{'QUEUE'}? ".$args{'QUEUE'}" : '' )
             .".{" . $CF->Name . "}",
         OPERATOR    => $args{OPERATOR},
         CUSTOMFIELD => 1,