TimeWorked-like custom fields, RT#11168
[freeside.git] / rt / lib / RT / CustomFields_Overlay.pm
index b9f3787..e1dd977 100644 (file)
@@ -1,40 +1,40 @@
 # BEGIN BPS TAGGED BLOCK {{{
 # BEGIN BPS TAGGED BLOCK {{{
-# 
+#
 # COPYRIGHT:
 # COPYRIGHT:
-#  
-# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC 
-#                                          <jesse@bestpractical.com>
-# 
+#
+# This software is Copyright (c) 1996-2011 Best Practical Solutions, LLC
+#                                          <sales@bestpractical.com>
+#
 # (Except where explicitly superseded by other copyright notices)
 # (Except where explicitly superseded by other copyright notices)
-# 
-# 
+#
+#
 # LICENSE:
 # 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 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.
 # 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/licenses/old-licenses/gpl-2.0.html.
 # 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/licenses/old-licenses/gpl-2.0.html.
-# 
-# 
+#
+#
 # CONTRIBUTION SUBMISSION POLICY:
 # 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.)
 # (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
 # 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,8 +43,9 @@
 # royalty-free, perpetual, license to use, copy, create derivative
 # works based on those contributions, and sublicense and distribute
 # those contributions and any derivatives thereof.
 # 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 }}}
 # END BPS TAGGED BLOCK }}}
+
 =head1 NAME
 
   RT::CustomFields - a collection of RT CustomField objects
 =head1 NAME
 
   RT::CustomFields - a collection of RT CustomField objects
 =head1 METHODS
 
 
 =head1 METHODS
 
 
-=begin testing
-
-ok (require RT::CustomFields);
-
-=end testing
 
 =cut
 
 
 =cut
 
@@ -70,29 +66,134 @@ ok (require RT::CustomFields);
 package RT::CustomFields;
 
 use strict;
 package RT::CustomFields;
 
 use strict;
+use warnings;
 no warnings qw(redefine);
 use DBIx::SearchBuilder::Unique;
 
 no warnings qw(redefine);
 use DBIx::SearchBuilder::Unique;
 
+sub _Init {
+    my $self = shift;
+    $self->{'table'} = 'CustomFields';
+    $self->{'primary_key'} = 'id';
+    $self->{'with_disabled_column'} = 1;
 
 
-sub _OCFAlias {
+    return $self->SUPER::_Init(@_);
+}
+
+
+=head2 LimitToLookupType
+
+Takes LookupType and limits collection.
+
+=cut
+
+sub LimitToLookupType  {
+    my $self = shift;
+    my $lookup = shift;
+
+    $self->Limit( FIELD => 'LookupType', VALUE => "$lookup" );
+}
+
+=head2 LimitToChildType
+
+Takes partial LookupType and limits collection to records
+where LookupType is equal or ends with the value.
+
+=cut
+
+sub LimitToChildType  {
+    my $self = shift;
+    my $lookup = shift;
+
+    $self->Limit( FIELD => 'LookupType', VALUE => "$lookup" );
+    $self->Limit( FIELD => 'LookupType', ENDSWITH => "$lookup" );
+}
+
+
+=head2 LimitToParentType
+
+Takes partial LookupType and limits collection to records
+where LookupType is equal or starts with the value.
+
+=cut
+
+sub LimitToParentType  {
+    my $self = shift;
+    my $lookup = shift;
+
+    $self->Limit( FIELD => 'LookupType', VALUE => "$lookup" );
+    $self->Limit( FIELD => 'LookupType', STARTSWITH => "$lookup" );
+}
+
+
+=head2 LimitToGlobalOrObjectId
+
+Takes list of object IDs and limits collection to custom
+fields that are applied to these objects or globally.
+
+=cut
+
+sub LimitToGlobalOrObjectId {
     my $self = shift;
     my $self = shift;
-    unless ($self->{_sql_ocfalias}) {
+    my $global_only = 1;
+
 
 
-        $self->{'_sql_ocfalias'} = $self->NewAlias('ObjectCustomFields');
-    $self->Join( ALIAS1 => 'main',
-                FIELD1 => 'id',
-                ALIAS2 => $self->_OCFAlias,
-                FIELD2 => 'CustomField' );
+    foreach my $id (@_) {
+       $self->Limit( ALIAS           => $self->_OCFAlias,
+                   FIELD           => 'ObjectId',
+                   OPERATOR        => '=',
+                   VALUE           => $id || 0,
+                   ENTRYAGGREGATOR => 'OR' );
+       $global_only = 0 if $id;
     }
     }
-    return($self->{_sql_ocfalias});
+
+    $self->Limit( ALIAS           => $self->_OCFAlias,
+                 FIELD           => 'ObjectId',
+                 OPERATOR        => '=',
+                 VALUE           => 0,
+                 ENTRYAGGREGATOR => 'OR' ) unless $global_only;
 }
 
 }
 
+=head2 LimitToNotApplied
+
+Takes either list of object ids or nothing. Limits collection
+to custom fields to listed objects or any corespondingly. Use
+zero to mean global.
+
+=cut
 
 
-# {{{ sub LimitToGlobalOrQueue 
+sub LimitToNotApplied {
+    my $self = shift;
+    my @ids = @_;
+
+    my $ocfs_alias = $self->_OCFAlias( New => 1, Left => 1 );
+    if ( @ids ) {
+        # XXX: we need different EA in join clause, but DBIx::SB
+        # doesn't support them, use IN (X) instead
+        my $dbh = $self->_Handle->dbh;
+        $self->Limit(
+            LEFTJOIN   => $ocfs_alias,
+            ALIAS      => $ocfs_alias,
+            FIELD      => 'ObjectId',
+            OPERATOR   => 'IN',
+            QUOTEVALUE => 0,
+            VALUE      => "(". join( ',', map $dbh->quote($_), @ids ) .")",
+        );
+    }
+
+    $self->Limit(
+        ENTRYAGGREGATOR => 'AND',
+        ALIAS    => $ocfs_alias,
+        FIELD    => 'id',
+        OPERATOR => 'IS',
+        VALUE    => 'NULL',
+    );
+}
 
 =head2 LimitToGlobalOrQueue QUEUEID
 
 
 =head2 LimitToGlobalOrQueue QUEUEID
 
-Limits the set of custom fields found to global custom fields or those tied to the queue with ID QUEUEID 
+DEPRECATED since CFs are applicable not only to tickets these days.
+
+Limits the set of custom fields found to global custom fields or those tied to the queue with ID QUEUEID
 
 =cut
 
 
 =cut
 
@@ -103,13 +204,12 @@ sub LimitToGlobalOrQueue {
     $self->LimitToLookupType( 'RT::Queue-RT::Ticket' );
 }
 
     $self->LimitToLookupType( 'RT::Queue-RT::Ticket' );
 }
 
-# }}}
-
-# {{{ sub LimitToQueue 
 
 =head2 LimitToQueue QUEUEID
 
 
 =head2 LimitToQueue QUEUEID
 
-Takes a queue id (numerical) as its only argument. Makes sure that 
+DEPRECATED since CFs are applicable not only to tickets these days.
+
+Takes a queue id (numerical) as its only argument. Makes sure that
 Scopes it pulls out apply to this queue (or another that you've selected with
 another call to this method
 
 Scopes it pulls out apply to this queue (or another that you've selected with
 another call to this method
 
@@ -118,7 +218,7 @@ another call to this method
 sub LimitToQueue  {
    my $self = shift;
   my $queue = shift;
 sub LimitToQueue  {
    my $self = shift;
   my $queue = shift;
+
   $self->Limit (ALIAS => $self->_OCFAlias,
                 ENTRYAGGREGATOR => 'OR',
                FIELD => 'ObjectId',
   $self->Limit (ALIAS => $self->_OCFAlias,
                 ENTRYAGGREGATOR => 'OR',
                FIELD => 'ObjectId',
@@ -126,139 +226,122 @@ sub LimitToQueue  {
       if defined $queue;
   $self->LimitToLookupType( 'RT::Queue-RT::Ticket' );
 }
       if defined $queue;
   $self->LimitToLookupType( 'RT::Queue-RT::Ticket' );
 }
-# }}}
 
 
-# {{{ sub LimitToGlobal
 
 =head2 LimitToGlobal
 
 
 =head2 LimitToGlobal
 
-Makes sure that 
-Scopes it pulls out apply to all queues (or another that you've selected with
-another call to this method or LimitToQueue
+DEPRECATED since CFs are applicable not only to tickets these days.
 
 
-=cut
+Makes sure that Scopes it pulls out apply to all queues
+(or another that you've selected with
+another call to this method or LimitToQueue)
 
 
+=cut
 
 sub LimitToGlobal  {
    my $self = shift;
 
 sub LimitToGlobal  {
    my $self = shift;
+
   $self->Limit (ALIAS => $self->_OCFAlias,
                 ENTRYAGGREGATOR => 'OR',
                FIELD => 'ObjectId',
                VALUE => 0);
   $self->LimitToLookupType( 'RT::Queue-RT::Ticket' );
 }
   $self->Limit (ALIAS => $self->_OCFAlias,
                 ENTRYAGGREGATOR => 'OR',
                FIELD => 'ObjectId',
                VALUE => 0);
   $self->LimitToLookupType( 'RT::Queue-RT::Ticket' );
 }
-# }}}
-
 
 
-# {{{ sub _DoSearch 
 
 
-=head2 _DoSearch
+=head2 ApplySortOrder
 
 
-A subclass of DBIx::SearchBuilder::_DoSearch that makes sure that 
- _Disabled rows never get seen unless we're explicitly trying to see 
-them.
+Sort custom fields according to thier order application to objects. It's
+expected that collection contains only records of one
+L<RT::CustomField/LookupType> and applied to one object or globally
+(L</LimitToGlobalOrObjectId>), otherwise sorting makes no sense.
 
 =cut
 
 
 =cut
 
-sub _DoSearch {
+sub ApplySortOrder {
     my $self = shift;
     my $self = shift;
-    
-    #unless we really want to find disabled rows, make sure we\'re only finding enabled ones.
-    unless($self->{'find_disabled_rows'}) {
-        $self->LimitToEnabled();
-    }
-    
-    return($self->SUPER::_DoSearch(@_));
-    
+    my $order = shift || 'ASC';
+    $self->OrderByCols( {
+        ALIAS => $self->_OCFAlias,
+        FIELD => 'SortOrder',
+        ORDER => $order,
+    } );
 }
 
 }
 
-# }}}
 
 
-# {{{ sub Next 
+=head2 ContextObject
 
 
-=head2 Next
-
-Returns the next custom field that this user can see.
+Returns context object for this collection of custom fields,
+but only if it's defined.
 
 =cut
 
 =cut
-  
-sub Next {
-    my $self = shift;
-    
-    
-    my $CF = $self->SUPER::Next();
-    if ((defined($CF)) and (ref($CF))) {
-
-       if ($CF->CurrentUserHasRight('SeeCustomField')) {
-           return($CF);
-       }
-       
-       #If the user doesn't have the right to show this queue
-       else {  
-           return($self->Next());
-       }
-    }
-    #if there never was any queue
-    else {
-       return(undef);
-    }  
-    
-}
-# }}}
 
 
-sub LimitToLookupType  {
+sub ContextObject {
     my $self = shift;
     my $self = shift;
-    my $lookup = shift;
-    $self->Limit( FIELD => 'LookupType', VALUE => "$lookup" );
+    return $self->{'context_object'};
 }
 
 }
 
-sub LimitToChildType  {
+
+=head2 SetContextObject
+
+Sets context object for this collection of custom fields.
+
+=cut
+
+sub SetContextObject {
     my $self = shift;
     my $self = shift;
-    my $lookup = shift;
-    $self->Limit( FIELD => 'LookupType', VALUE => "$lookup" );
-    $self->Limit( FIELD => 'LookupType', ENDSWITH => "$lookup" );
+    return $self->{'context_object'} = shift;
 }
 
 }
 
-sub LimitToParentType  {
+
+sub _OCFAlias {
     my $self = shift;
     my $self = shift;
-    my $lookup = shift;
-    $self->Limit( FIELD => 'LookupType', VALUE => "$lookup" );
-    $self->Limit( FIELD => 'LookupType', STARTSWITH => "$lookup" );
+    my %args = ( New => 0, Left => 0, @_ );
+
+    return $self->{'_sql_ocfalias'} if $self->{'_sql_ocfalias'} && !$args{'New'};
+
+    my $alias = $self->Join(
+        $args{'Left'} ? (TYPE => 'LEFT') : (),
+        ALIAS1 => 'main',
+        FIELD1 => 'id',
+        TABLE2 => 'ObjectCustomFields',
+        FIELD2 => 'CustomField'
+    );
+    return $alias if $args{'New'};
+    return $self->{'_sql_ocfalias'} = $alias;
 }
 
 }
 
-sub LimitToGlobalOrObjectId {
+
+=head2 Next
+
+Returns the next custom field that this user can see.
+
+=cut
+
+sub Next {
     my $self = shift;
     my $self = shift;
-    my $global_only = 1;
 
 
+    my $CF = $self->SUPER::Next();
+    return $CF unless $CF;
 
 
-    foreach my $id (@_) {
-       $self->Limit( ALIAS           => $self->_OCFAlias,
-                   FIELD           => 'ObjectId',
-                   OPERATOR        => '=',
-                   VALUE           => $id || 0,
-                   ENTRYAGGREGATOR => 'OR' );
-       $global_only = 0 if $id;
-    }
+    $CF->SetContextOject( $self->ContextObject );
 
 
-    $self->Limit( ALIAS           => $self->_OCFAlias,
-                 FIELD           => 'ObjectId',
-                 OPERATOR        => '=',
-                 VALUE           => 0,
-                 ENTRYAGGREGATOR => 'OR' ) unless $global_only;
+    return $self->Next unless $CF->CurrentUserHasRight('SeeCustomField');
+    return $CF;
+}
 
 
-    $self->OrderByCols(
-       { ALIAS => $self->_OCFAlias, FIELD => 'ObjectId' },
-       { ALIAS => $self->_OCFAlias, FIELD => 'SortOrder' },
-    );
-    
-    # This doesn't work on postgres. 
-    #$self->OrderBy( ALIAS => $class_cfs , FIELD => "SortOrder", ORDER => 'ASC');
+=head2 Next
 
 
+Overrides <RT::SearchBuilder/Next> to make sure </ContextObject>
+is inherited.
+
+=cut
+
+sub NewItem {
+    my $self = shift;
+    my $res = RT::CustomField->new($self->CurrentUser);
+    $res->SetContextObject($self->ContextObject);
+    return $res;
 }
 }
-  
-1;
 
 
+1;