[Rt-commit] rt branch, 4.0/extensible-ticketsql-cf-limits, created. rt-4.0.13-124-g228451c

Thomas Sibley trs at bestpractical.com
Wed Jul 3 17:55:31 EDT 2013


The branch, 4.0/extensible-ticketsql-cf-limits has been created
        at  228451ce0c60bac20bf329d0896c7312db3af318 (commit)

- Log -----------------------------------------------------------------
commit e37801b72138e97fd23ad9e2a6e95d8ef96032d5
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Wed Feb 6 17:25:22 2013 -0800

    Allow CustomFieldLookupType to be called as a class method
    
    (cherry picked from commit 1ff3f2b0a97d1e336ced7542ce2b0201ff5309fc)

diff --git a/lib/RT/Record.pm b/lib/RT/Record.pm
index a58979e..9e44639 100644
--- a/lib/RT/Record.pm
+++ b/lib/RT/Record.pm
@@ -1616,7 +1616,7 @@ Returns the path RT uses to figure out which custom fields apply to this object.
 
 sub CustomFieldLookupType {
     my $self = shift;
-    return ref($self);
+    return ref($self) || $self;
 }
 
 

commit 7e5b1ccb0955d8c730d99a6a14b6cc05ec4b6960
Author: Kevin Falcone <falcone at bestpractical.com>
Date:   Mon Jan 30 17:27:49 2012 -0500

    Add an ObjectTypeFromlookupType method and document RecordClassFromLookupType
    
    RecordClassFromLookupType is useful when joining to ObjectCustomFields,
    but there wasn't an existing method for code that wants to join to
    ObjectCustomFieldValues, so we can borrow the code and tweak the regex a
    bit.

diff --git a/lib/RT/CustomField.pm b/lib/RT/CustomField.pm
index a69c6f4..11701b9 100644
--- a/lib/RT/CustomField.pm
+++ b/lib/RT/CustomField.pm
@@ -1205,12 +1205,37 @@ sub FriendlyLookupType {
     return ( $self->loc( $FriendlyObjectTypes[$#types], @types ) );
 }
 
+=head1 RecordClassFromLookupType
+
+Returns the type of Object referred to by ObjectCustomFields' ObjectId column
+
+=cut
+
 sub RecordClassFromLookupType {
     my $self = shift;
     my ($class) = ($self->LookupType =~ /^([^-]+)/);
     unless ( $class ) {
         $RT::Logger->error(
-            "Custom Field #". $self->id 
+            "Custom Field #". $self->id
+            ." has incorrect LookupType '". $self->LookupType ."'"
+        );
+        return undef;
+    }
+    return $class;
+}
+
+=head1 ObjectTypeFromLookupType
+
+Returns the ObjectType used in ObjectCustomFieldValues rows for this CF
+
+=cut
+
+sub ObjectTypeFromLookupType {
+    my $self = shift;
+    my ($class) = ($self->LookupType =~ /([^-]+)$/);
+    unless ( $class ) {
+        $RT::Logger->error(
+            "Custom Field #". $self->id
             ." has incorrect LookupType '". $self->LookupType ."'"
         );
         return undef;

commit 5088509209ccc5ecdfe9e905959c3f4b096cb5ae
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Wed Jun 26 16:06:31 2013 -0700

    ObjectType/RecordTypeFromLookupType as class methods
    
    Where LookupType must be passed as an argument.

diff --git a/lib/RT/CustomField.pm b/lib/RT/CustomField.pm
index 11701b9..0211815 100644
--- a/lib/RT/CustomField.pm
+++ b/lib/RT/CustomField.pm
@@ -51,7 +51,7 @@ package RT::CustomField;
 use strict;
 use warnings;
 
-
+use Scalar::Util 'blessed';
 
 use base 'RT::Record';
 
@@ -1209,16 +1209,25 @@ sub FriendlyLookupType {
 
 Returns the type of Object referred to by ObjectCustomFields' ObjectId column
 
+Optionally takes a LookupType to use instead of using the value on the loaded
+record.  In this case, the method may be called on the class instead of an
+object.
+
 =cut
 
 sub RecordClassFromLookupType {
     my $self = shift;
-    my ($class) = ($self->LookupType =~ /^([^-]+)/);
+    my $type = shift || $self->LookupType;
+    my ($class) = ($type =~ /^([^-]+)/);
     unless ( $class ) {
-        $RT::Logger->error(
-            "Custom Field #". $self->id
-            ." has incorrect LookupType '". $self->LookupType ."'"
-        );
+        if (blessed($self) and $self->LookupType eq $type) {
+            $RT::Logger->error(
+                "Custom Field #". $self->id
+                ." has incorrect LookupType '$type'"
+            );
+        } else {
+            RT->Logger->error("Invalid LookupType passed as argument: $type");
+        }
         return undef;
     }
     return $class;
@@ -1228,16 +1237,25 @@ sub RecordClassFromLookupType {
 
 Returns the ObjectType used in ObjectCustomFieldValues rows for this CF
 
+Optionally takes a LookupType to use instead of using the value on the loaded
+record.  In this case, the method may be called on the class instead of an
+object.
+
 =cut
 
 sub ObjectTypeFromLookupType {
     my $self = shift;
-    my ($class) = ($self->LookupType =~ /([^-]+)$/);
+    my $type = shift || $self->LookupType;
+    my ($class) = ($type =~ /([^-]+)$/);
     unless ( $class ) {
-        $RT::Logger->error(
-            "Custom Field #". $self->id
-            ." has incorrect LookupType '". $self->LookupType ."'"
-        );
+        if (blessed($self) and $self->LookupType eq $type) {
+            $RT::Logger->error(
+                "Custom Field #". $self->id
+                ." has incorrect LookupType '$type'"
+            );
+        } else {
+            RT->Logger->error("Invalid LookupType passed as argument: $type");
+        }
         return undef;
     }
     return $class;

commit f0ea5922e8c14114e3d1698f526634acff567dff
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Wed Jun 26 22:56:11 2013 -0700

    Refactor part of LimitToGlobalOrObjectId into LimitToObjectId
    
    Useful when you only want to find CFs applied to a certain object and
    not global ones too.

diff --git a/lib/RT/CustomFields.pm b/lib/RT/CustomFields.pm
index 017018e..1ea75da 100644
--- a/lib/RT/CustomFields.pm
+++ b/lib/RT/CustomFields.pm
@@ -141,6 +141,25 @@ sub LimitToParentType  {
     $self->Limit( FIELD => 'LookupType', STARTSWITH => "$lookup" );
 }
 
+=head2 LimitToObjectId
+
+Takes an ObjectId and limits the collection to CFs applied to said object.
+
+When called multiple times the ObjectId limits are joined with OR.
+
+=cut
+
+sub LimitToObjectId {
+    my $self = shift;
+    my $id = shift;
+    $self->Limit(
+        ALIAS           => $self->_OCFAlias,
+        FIELD           => 'ObjectId',
+        OPERATOR        => '=',
+        VALUE           => $id || 0,
+        ENTRYAGGREGATOR => 'OR'
+    );
+}
 
 =head2 LimitToGlobalOrObjectId
 
@@ -155,19 +174,11 @@ sub LimitToGlobalOrObjectId {
 
 
     foreach my $id (@_) {
-	$self->Limit( ALIAS           => $self->_OCFAlias,
-		    FIELD           => 'ObjectId',
-		    OPERATOR        => '=',
-		    VALUE           => $id || 0,
-		    ENTRYAGGREGATOR => 'OR' );
-	$global_only = 0 if $id;
+        $self->LimitToObjectId($id);
+        $global_only = 0 if $id;
     }
 
-    $self->Limit( ALIAS           => $self->_OCFAlias,
-                 FIELD           => 'ObjectId',
-                 OPERATOR        => '=',
-                 VALUE           => 0,
-                 ENTRYAGGREGATOR => 'OR' ) unless $global_only;
+    $self->LimitToObjectId(0) unless $global_only;
 }
 
 sub _LimitToOCFs {

commit a9ae79a1ee31dff81b9712493da1cf9bbd39f488
Author: Kevin Falcone <falcone at bestpractical.com>
Date:   Mon Jan 30 17:31:47 2012 -0500

    Rename some variables that will become confusing.
    
    As these join aliases start to refer to transaction cfs and not just
    ticket cfs, it is really weird to have everything named TicketCFs

diff --git a/lib/RT/Tickets.pm b/lib/RT/Tickets.pm
index 41aa313..679186b 100644
--- a/lib/RT/Tickets.pm
+++ b/lib/RT/Tickets.pm
@@ -1374,9 +1374,9 @@ sub _CustomFieldJoin {
                  $self->{_sql_cf_alias}{$cfkey} );
     }
 
-    my ($TicketCFs, $CFs);
+    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',
             FIELD1 => 'id',
@@ -1384,7 +1384,7 @@ sub _CustomFieldJoin {
             FIELD2 => 'ObjectId',
         );
         $self->SUPER::Limit(
-            LEFTJOIN        => $TicketCFs,
+            LEFTJOIN        => $ObjectCFs,
             FIELD           => 'CustomField',
             VALUE           => $cfid,
             ENTRYAGGREGATOR => 'AND'
@@ -1425,7 +1425,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',
@@ -1433,7 +1433,7 @@ sub _CustomFieldJoin {
             FIELD2 => 'CustomField',
         );
         $self->SUPER::Limit(
-            LEFTJOIN        => $TicketCFs,
+            LEFTJOIN        => $ObjectCFs,
             FIELD           => 'ObjectId',
             VALUE           => 'main.id',
             QUOTEVALUE      => 0,
@@ -1441,20 +1441,20 @@ sub _CustomFieldJoin {
         );
     }
     $self->SUPER::Limit(
-        LEFTJOIN        => $TicketCFs,
+        LEFTJOIN        => $ObjectCFs,
         FIELD           => 'ObjectType',
         VALUE           => 'RT::Ticket',
         ENTRYAGGREGATOR => 'AND'
     );
     $self->SUPER::Limit(
-        LEFTJOIN        => $TicketCFs,
+        LEFTJOIN        => $ObjectCFs,
         FIELD           => 'Disabled',
         OPERATOR        => '=',
         VALUE           => '0',
         ENTRYAGGREGATOR => 'AND'
     );
 
-    return ($TicketCFs, $CFs);
+    return ($ObjectCFs, $CFs);
 }
 
 =head2 _CustomFieldLimit
@@ -1580,10 +1580,10 @@ sub _CustomFieldLimit {
         # 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 );
         $self->_OpenParen;
         $self->_SQLLimit(
-            ALIAS    => $TicketCFs,
+            ALIAS    => $ObjectCFs,
             FIELD    => 'id',
             OPERATOR => $op,
             VALUE    => $value,
@@ -1643,7 +1643,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 );
 
         $self->_OpenParen;
 
@@ -1654,7 +1654,7 @@ 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,
@@ -1680,7 +1680,7 @@ sub _CustomFieldLimit {
                     $self->_OpenParen;
 
                     $self->_SQLLimit(
-                        ALIAS    => $TicketCFs,
+                        ALIAS    => $ObjectCFs,
                         FIELD    => 'Content',
                         OPERATOR => ">=",
                         VALUE    => $daystart,
@@ -1688,7 +1688,7 @@ sub _CustomFieldLimit {
                     );
 
                     $self->_SQLLimit(
-                        ALIAS    => $TicketCFs,
+                        ALIAS    => $ObjectCFs,
                         FIELD    => 'Content',
                         OPERATOR => "<",
                         VALUE    => $dayend,
@@ -1701,7 +1701,7 @@ sub _CustomFieldLimit {
             elsif ( $op eq '=' || $op eq '!=' || $op eq '<>' ) {
                 if ( length( Encode::encode_utf8($value) ) < 256 ) {
                     $self->_SQLLimit(
-                        ALIAS    => $TicketCFs,
+                        ALIAS    => $ObjectCFs,
                         FIELD    => 'Content',
                         OPERATOR => $op,
                         VALUE    => $value,
@@ -1712,14 +1712,14 @@ sub _CustomFieldLimit {
                 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',
@@ -1727,7 +1727,7 @@ sub _CustomFieldLimit {
                     );
                     $self->_CloseParen;
                     $self->_SQLLimit( $fix_op->(
-                        ALIAS           => $TicketCFs,
+                        ALIAS           => $ObjectCFs,
                         FIELD           => 'LargeContent',
                         OPERATOR        => $op,
                         VALUE           => $value,
@@ -1738,7 +1738,7 @@ sub _CustomFieldLimit {
             }
             else {
                 $self->_SQLLimit(
-                    ALIAS    => $TicketCFs,
+                    ALIAS    => $ObjectCFs,
                     FIELD    => 'Content',
                     OPERATOR => $op,
                     VALUE    => $value,
@@ -1749,14 +1749,14 @@ sub _CustomFieldLimit {
                 $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',
@@ -1764,7 +1764,7 @@ sub _CustomFieldLimit {
                 );
                 $self->_CloseParen;
                 $self->_SQLLimit( $fix_op->(
-                    ALIAS           => $TicketCFs,
+                    ALIAS           => $ObjectCFs,
                     FIELD           => 'LargeContent',
                     OPERATOR        => $op,
                     VALUE           => $value,
@@ -1798,7 +1798,7 @@ sub _CustomFieldLimit {
 
             if ($negative_op) {
                 $self->_SQLLimit(
-                    ALIAS           => $TicketCFs,
+                    ALIAS           => $ObjectCFs,
                     FIELD           => $column || 'Content',
                     OPERATOR        => 'IS',
                     VALUE           => 'NULL',
@@ -1812,7 +1812,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 );
 
         # reverse operation
         $op =~ s/!|NOT\s+//i;
@@ -1821,8 +1821,8 @@ 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,
@@ -1831,8 +1831,8 @@ sub _CustomFieldLimit {
         }
         else {
             $self->SUPER::Limit(
-                LEFTJOIN   => $TicketCFs,
-                ALIAS      => $TicketCFs,
+                LEFTJOIN   => $ObjectCFs,
+                ALIAS      => $ObjectCFs,
                 FIELD      => 'Content',
                 OPERATOR   => $op,
                 VALUE      => $value,
@@ -1841,7 +1841,7 @@ sub _CustomFieldLimit {
         }
         $self->_SQLLimit(
             %rest,
-            ALIAS      => $TicketCFs,
+            ALIAS      => $ObjectCFs,
             FIELD      => 'id',
             OPERATOR   => 'IS',
             VALUE      => 'NULL',
@@ -1955,7 +1955,7 @@ sub OrderByCols {
            my ($queue, $field, $cf_obj, $column) = $self->_CustomFieldDecipher( $subkey );
            my $cfkey = $cf_obj ? $cf_obj->id : "$queue.$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,
@@ -1977,7 +1977,7 @@ sub OrderByCols {
            }
            my $CFvs = $self->Join(
                TYPE   => 'LEFT',
-               ALIAS1 => $TicketCFs,
+               ALIAS1 => $ObjectCFs,
                FIELD1 => 'CustomField',
                TABLE2 => 'CustomFieldValues',
                FIELD2 => 'CustomField',
@@ -1986,12 +1986,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";

commit eb7dff164d3a37da0da73ca414c37fb91f42c4ba
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Thu Jun 27 15:25:46 2013 -0700

    Decipher TicketSQL CF identifiers using a passed in LookupType
    
    The LookupType is necessary to find the correct CF by name, as well as
    to support the arcane CF.ObjectId.{CFName} syntax, for arbitrary object
    types (not just ticket CFs on RT::Queue).
    
    Potential syntax made possible, for example:
    
        TxnCF.{CFName}
        TxnCF.Queue.{CFName}
        ArticleCF.Class.{CFName}
    
    The CF lookup behaviour of CF.Queue.{CFName} changes slightly due to
    changes in which custom fields API is used.  Most notably, such syntax
    previously exhibited the following behaviour but no longer does:
    
        • Disabled CFs could be found if no enabled CFs matched.
        • The existence of multiple matching CFs with the same name (& within
          the same queue) was ignored; the first was the only one used.
    
    As CF.Queue.{CFName} is fairly arcane syntax, these changes shouldn't
    affect most usage of TicketSQL.  It is unlikely that anyone using the
    extended syntax is intentionally relying on any of the discontinued
    behaviour noted above.  Disabled CFs, even if found, are eliminated from
    results by later code.  The two behaviour changes are effectively bug
    fixes.
    
    A case-sensitivity difference between CF.{Name} and CF.Queue.{Name} is
    preserved by checking which syntax was used.  This is bending over
    backwards solely for the sake of compatibility within the 4.0 series.
    It is conceivable that someone is relying on it, knowingly or not, and
    it is easy enough to replicate.  In 4.2, we should remove this
    distinction.

diff --git a/lib/RT/Tickets.pm b/lib/RT/Tickets.pm
index 679186b..3ef735c 100644
--- a/lib/RT/Tickets.pm
+++ b/lib/RT/Tickets.pm
@@ -1314,33 +1314,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 ( $object ) {
+        my $record_class = RT::CustomField->RecordClassFromLookupType($lookuptype);
+        $applied_to = $record_class->new( $self->CurrentUser );
+        $applied_to->Load( $object );
 
-        if ( $q->id ) {
-            # $queue = $q->Name; # should we normalize the queue?
-            $cf = $q->CustomField( $field );
+        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
@@ -1353,9 +1364,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
@@ -1477,8 +1490,8 @@ sub _CustomFieldLimit {
 
     # 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 );
     $cfid = $cf ? $cf->id  : 0 ;
 
 # If we're trying to find custom fields that don't match something, we
@@ -1574,7 +1587,7 @@ sub _CustomFieldLimit {
 
     my $single_value = !$cf || !$cfid || $cf->SingleValue;
 
-    my $cfkey = $cfid ? $cfid : "$queue.$field";
+    my $cfkey = $cfid ? $cfid : "$object.$field";
 
     if ( $null_op && !$column ) {
         # IS[ NOT] NULL without column is the same as has[ no] any CF value,
@@ -1952,8 +1965,8 @@ 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 ($ObjectCFs, $CFs) = $self->_CustomFieldJoin( $cfkey, ($cf_obj ?$cf_obj->id :0) , $field );
            # this is described in _CustomFieldLimit

commit d39521de41ec430ddcaab92f7efbad0d93de77ac
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Wed Jun 26 16:47:43 2013 -0700

    Refactor _CustomFieldJoin to take a LookupType
    
    … so that we can join to the correct table if not Tickets.  An
    extensible data structure is introduced to determine how to join CFs to
    applied objects.
    
    This lets _CustomFieldJoin handle joining for transaction CFs or other
    CFs which may want to be searched.

diff --git a/lib/RT/Tickets.pm b/lib/RT/Tickets.pm
index 3ef735c..0afe20b 100644
--- a/lib/RT/Tickets.pm
+++ b/lib/RT/Tickets.pm
@@ -1377,8 +1377,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} )
@@ -1387,11 +1393,15 @@ sub _CustomFieldJoin {
                  $self->{_sql_cf_alias}{$cfkey} );
     }
 
+    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 ) {
         $ObjectCFs = $self->{_sql_object_cfv_alias}{$cfkey} = $self->Join(
             TYPE   => 'LEFT',
-            ALIAS1 => 'main',
+            ALIAS1 => $ObjectAlias,
             FIELD1 => 'id',
             TABLE2 => 'ObjectCustomFieldValues',
             FIELD2 => 'ObjectId',
@@ -1429,7 +1439,7 @@ sub _CustomFieldJoin {
             LEFTJOIN        => $CFs,
             ENTRYAGGREGATOR => 'AND',
             FIELD           => 'LookupType',
-            VALUE           => 'RT::Queue-RT::Ticket',
+            VALUE           => $type,
         );
         $self->SUPER::Limit(
             LEFTJOIN        => $CFs,
@@ -1448,15 +1458,16 @@ sub _CustomFieldJoin {
         $self->SUPER::Limit(
             LEFTJOIN        => $ObjectCFs,
             FIELD           => 'ObjectId',
-            VALUE           => 'main.id',
+            VALUE           => "$ObjectAlias.id",
             QUOTEVALUE      => 0,
             ENTRYAGGREGATOR => 'AND',
         );
     }
+
     $self->SUPER::Limit(
         LEFTJOIN        => $ObjectCFs,
         FIELD           => 'ObjectType',
-        VALUE           => 'RT::Ticket',
+        VALUE           => RT::CustomField->ObjectTypeFromLookupType($type),
         ENTRYAGGREGATOR => 'AND'
     );
     $self->SUPER::Limit(

commit daf0a6c18f1ef822349e1d89d12be827b356d02e
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Thu Jun 27 15:50:34 2013 -0700

    Refactor _CustomFieldLimit to use a LookupType in %FIELD_METADATA
    
    Potential new search clauses, such as searching transaction CFs, want to
    match all the different ways that ticket CFs can.  It makes sense to
    just wrap the limits in a LookupType change (especially since
    _CustomFieldLimit happens to be recursive for IP and Date searches).

diff --git a/lib/RT/Tickets.pm b/lib/RT/Tickets.pm
index 0afe20b..b2480b0 100644
--- a/lib/RT/Tickets.pm
+++ b/lib/RT/Tickets.pm
@@ -142,9 +142,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
@@ -1497,12 +1497,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 ($object, $cfid, $cf, $column);
-    ($object, $field, $cf, $column) = $self->_CustomFieldDecipher( $field );
+    ($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
@@ -1598,13 +1602,13 @@ sub _CustomFieldLimit {
 
     my $single_value = !$cf || !$cfid || $cf->SingleValue;
 
-    my $cfkey = $cfid ? $cfid : "$object.$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 ($ObjectCFs, $CFs) = $self->_CustomFieldJoin( $cfkey, $cfid, $field );
+        my ($ObjectCFs, $CFs) = $self->_CustomFieldJoin( $cfkey, $cfid, $field, $type );
         $self->_OpenParen;
         $self->_SQLLimit(
             ALIAS    => $ObjectCFs,
@@ -1630,11 +1634,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',
             ); 
@@ -1642,20 +1646,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',
             );  
@@ -1667,7 +1671,7 @@ sub _CustomFieldLimit {
     } 
     elsif ( !$negative_op || $single_value ) {
         $cfkey .= '.'. $self->{'_sql_multiple_cfs_index'}++ if !$single_value && !$range_op;
-        my ($ObjectCFs, $CFs) = $self->_CustomFieldJoin( $cfkey, $cfid, $field );
+        my ($ObjectCFs, $CFs) = $self->_CustomFieldJoin( $cfkey, $cfid, $field, $type );
 
         $self->_OpenParen;
 
@@ -1836,7 +1840,7 @@ sub _CustomFieldLimit {
     }
     else {
         $cfkey .= '.'. $self->{'_sql_multiple_cfs_index'}++;
-        my ($ObjectCFs, $CFs) = $self->_CustomFieldJoin( $cfkey, $cfid, $field );
+        my ($ObjectCFs, $CFs) = $self->_CustomFieldJoin( $cfkey, $cfid, $field, $type );
 
         # reverse operation
         $op =~ s/!|NOT\s+//i;

commit be665348b78679d0a31900afb788517f9a10fda6
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Thu Jun 27 16:40:57 2013 -0700

    Fix horrendous load order of shredder tests
    
    They started failing because of an added
    RT::Ticket->CustomFieldLookupType call.
    
    The RT::Ticket load order is also awful and doesn't setup it's base
    class until very late, including after importing other packages.

diff --git a/t/shredder/utils.pl b/t/shredder/utils.pl
index 7be9513..d5a0589 100644
--- a/t/shredder/utils.pl
+++ b/t/shredder/utils.pl
@@ -8,8 +8,14 @@ require RT::Test;
 
 BEGIN {
 ### after:     push @INC, qw(@RT_LIB_PATH@);
+
+    use RT;
+    RT->LoadConfig;
+    RT->InitPluginPaths;
+    RT->InitClasses;
 }
-use RT::Shredder;
+
+require RT::Shredder;
 
 =head1 DESCRIPTION
 

commit 94aeca9f47f7923b8fe82c02e02d7f9e892716a7
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Fri Jun 28 10:10:49 2013 -0700

    Add callbacks for before and after the query builder criteria picker

diff --git a/share/html/Search/Elements/PickCriteria b/share/html/Search/Elements/PickCriteria
index 7d8a4e9..a15a21a 100644
--- a/share/html/Search/Elements/PickCriteria
+++ b/share/html/Search/Elements/PickCriteria
@@ -50,9 +50,10 @@
 <table width="100%" cellspacing="0" cellpadding="0" border="0">
 
 
-
+% $m->callback( %ARGS, CallbackName => "BeforeBasics" );
 <& PickBasics, queues => \%queues &>
 <& PickCFs, queues => \%queues &>
+% $m->callback( %ARGS, CallbackName => "AfterCFs" );
 
 <tr class="separator"><td colspan="3"><hr /></td></tr>
 <tr>

commit 565b40eaf184d846c20da315b5247fd7c86c2e17
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Fri Jun 28 10:11:38 2013 -0700

    PickTicketCFs refactored from the now-generic PickCFs
    
    PickCFs may now be used for other types of CFs.  Callback semantics
    remain the same, so no extension using the callbacks in PickCFs will be
    broken.

diff --git a/share/html/Search/Elements/PickCFs b/share/html/Search/Elements/PickCFs
index 5d16b9c..9765603 100644
--- a/share/html/Search/Elements/PickCFs
+++ b/share/html/Search/Elements/PickCFs
@@ -49,14 +49,6 @@
 <& ConditionRow, Condition => $_ &>
 % }
 <%INIT>
-my $CustomFields = RT::CustomFields->new( $session{'CurrentUser'});
-foreach my $id (keys %queues) {
-    # Gotta load up the $queue object, since queues get stored by name now.
-    my $queue = RT::Queue->new($session{'CurrentUser'});
-    $queue->Load($id);
-    $CustomFields->LimitToQueue($queue->Id) if $queue->Id;
-}
-$CustomFields->LimitToGlobal;
 $m->callback(
     CallbackName => 'MassageCustomFields',
     CustomFields => $CustomFields,
@@ -66,7 +58,7 @@ $m->callback(
 my @lines;
 while ( my $CustomField = $CustomFields->Next ) {
     my %line;
-    $line{'Name'} = "'CF.{" . $CustomField->Name . "}'";
+    $line{'Name'} = "'$TicketSQLField.{" . $CustomField->Name . "}'";
     $line{'Field'} = $CustomField->Name;
 
     # Op
@@ -120,4 +112,6 @@ $m->callback( Conditions => \@lines, Queues => \%queues );
 
 <%ARGS>
 %queues => ()
+$CustomFields
+$TicketSQLField => 'CF'
 </%ARGS>
diff --git a/share/html/Search/Elements/PickCriteria b/share/html/Search/Elements/PickCriteria
index a15a21a..485fceb 100644
--- a/share/html/Search/Elements/PickCriteria
+++ b/share/html/Search/Elements/PickCriteria
@@ -52,7 +52,7 @@
 
 % $m->callback( %ARGS, CallbackName => "BeforeBasics" );
 <& PickBasics, queues => \%queues &>
-<& PickCFs, queues => \%queues &>
+<& PickTicketCFs, queues => \%queues &>
 % $m->callback( %ARGS, CallbackName => "AfterCFs" );
 
 <tr class="separator"><td colspan="3"><hr /></td></tr>
diff --git a/share/html/Search/Elements/PickCriteria b/share/html/Search/Elements/PickTicketCFs
similarity index 79%
copy from share/html/Search/Elements/PickCriteria
copy to share/html/Search/Elements/PickTicketCFs
index a15a21a..6b01aa2 100644
--- a/share/html/Search/Elements/PickCriteria
+++ b/share/html/Search/Elements/PickTicketCFs
@@ -45,29 +45,17 @@
 %# those contributions and any derivatives thereof.
 %#
 %# END BPS TAGGED BLOCK }}}
-<&| /Widgets/TitleBox, title => loc('Add Criteria')&>
-
-<table width="100%" cellspacing="0" cellpadding="0" border="0">
-
-
-% $m->callback( %ARGS, CallbackName => "BeforeBasics" );
-<& PickBasics, queues => \%queues &>
-<& PickCFs, queues => \%queues &>
-% $m->callback( %ARGS, CallbackName => "AfterCFs" );
-
-<tr class="separator"><td colspan="3"><hr /></td></tr>
-<tr>
-<td class="label"><&|/l&>Aggregator</&></td>
-<td class="operator" colspan="2"><& SelectAndOr, Name => "AndOr" &></td>
-
-</tr>
-
-</table>
-
-</&>
-
 <%ARGS>
-$addquery => 0
-$query => undef
 %queues => ()
 </%ARGS>
+<%init>
+my $CustomFields = RT::CustomFields->new( $session{'CurrentUser'});
+foreach my $id (keys %queues) {
+    # Gotta load up the $queue object, since queues get stored by name now.
+    my $queue = RT::Queue->new($session{'CurrentUser'});
+    $queue->Load($id);
+    $CustomFields->LimitToQueue($queue->Id) if $queue->Id;
+}
+$CustomFields->LimitToGlobal;
+</%init>
+<& PickCFs, %ARGS, TicketSQLField => 'CF', CustomFields => $CustomFields &>

commit 228451ce0c60bac20bf329d0896c7312db3af318
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Fri Jun 28 12:14:27 2013 -0700

    Parse FooCF.{Bar} when adding a TicketSQL clause in the Query Builder
    
    Extends the parsing from a strict CF.{…} to SomeOptionalWordCF.{…}
    
    In an ideal world we programmatically build up what's recognized from a
    data structure like %RT::Tickets::FIELD_METADATA, but that requires more
    refactoring than we're up for in a stable series.

diff --git a/share/html/Search/Build.html b/share/html/Search/Build.html
index e377622..40b45d4 100644
--- a/share/html/Search/Build.html
+++ b/share/html/Search/Build.html
@@ -190,7 +190,7 @@ my @new_values = ();
 
 # Try to find if we're adding a clause
 foreach my $arg ( keys %ARGS ) {
-    next unless $arg =~ m/^ValueOf(\w+|'CF.{.*?}')$/
+    next unless $arg =~ m/^ValueOf(\w+|'\w*CF\.\{.*?\}')$/
                 && ( ref $ARGS{$arg} eq "ARRAY"
                      ? grep $_ ne '', @{ $ARGS{$arg} }
                      : $ARGS{$arg} ne '' );
@@ -234,10 +234,10 @@ foreach my $arg ( keys %ARGS ) {
             $value = "'$value'";
         }
 
-        if ($keyword =~ /^'CF\.{(.*)}'/) {
-            my $cf = $1;
+        if ($keyword =~ /^'(\w*CF)\.\{(.*)\}'/) {
+            my ($field, $cf) = ($1, $2);
             $cf =~ s/(['\\])/\\$1/g;
-            $keyword = "'CF.{$cf}'";
+            $keyword = "'$field.{$cf}'";
         }
 
         my $clause = {

-----------------------------------------------------------------------


More information about the Rt-commit mailing list