import of rt 3.0.4
[freeside.git] / rt / lib / RT / Record.pm
index 5340f7d..6962221 100755 (executable)
@@ -1,5 +1,26 @@
-#$Header: /home/cvs/cvsroot/freeside/rt/lib/RT/Record.pm,v 1.1 2002-08-12 06:17:07 ivan Exp $
-
+# BEGIN LICENSE BLOCK
+# 
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+# 
+# (Except where explictly superceded by other copyright notices)
+# 
+# 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.
+# 
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+# 
+# 
+# END LICENSE BLOCK
 =head1 NAME
 
   RT::Record - Base class for RT record objects
@@ -20,20 +41,31 @@ ok (require RT::Record);
 
 =cut
 
-
 package RT::Record;
-use DBIx::SearchBuilder::Record::Cachable;
 use RT::Date;
 use RT::User;
 
-@ISA= qw(DBIx::SearchBuilder::Record::Cachable);
+use RT::Base;
+use DBIx::SearchBuilder::Record::Cachable;
+
+use strict;
+use vars qw/@ISA/;
+
+@ISA = qw(RT::Base);
+
+if ($RT::DontCacheSearchBuilderRecords ) {
+    push (@ISA, 'DBIx::SearchBuilder::Record');
+} else {
+    push (@ISA, 'DBIx::SearchBuilder::Record::Cachable');
+
+}
 
 # {{{ sub _Init 
 
-sub _Init  {
-  my $self = shift;
-  $self->_MyCurrentUser(@_);
-  
+sub _Init {
+    my $self = shift;
+    $self->CurrentUser(@_);
+
 }
 
 # }}}
@@ -48,68 +80,108 @@ The primary keys for RT classes is 'id'
 
 sub _PrimaryKeys {
     my $self = shift;
-    return(['id']);
+    return ( ['id'] );
 }
 
 # }}}
 
-# {{{ sub _MyCurrentUser 
-
-sub _MyCurrentUser  {
+# {{{ sub _Handle 
+sub _Handle {
     my $self = shift;
-  
-    $self->CurrentUser(@_);
-    if(!defined($self->CurrentUser)) {
-       use Carp;
-       Carp::cluck();
-       $RT::Logger->err("$self was created without a CurrentUser\n"); 
-      return(0);
-    }
+    return ($RT::Handle);
 }
 
 # }}}
 
-# {{{ sub _Handle 
-sub _Handle  {
-  my $self = shift;
-  return($RT::Handle);
-}
-# }}}
-
 # {{{ sub Create 
 
-sub Create  {
-    my $self = shift;
-    my $now = new RT::Date($self->CurrentUser);
-    $now->Set(Format=> 'unix', Value => time);
-    push @_, 'Created', $now->ISO()
-      if ($self->_Accessible('Created', 'auto'));
-    
-
-    push @_, 'Creator', $self->{'user'}->id
-      if $self->_Accessible('Creator', 'auto');
-    
-    push @_, 'LastUpdated', $now->ISO()
-      if ($self->_Accessible('LastUpdated', 'auto'));
-
-    push @_, 'LastUpdatedBy', $self->{'user'}->id
-      if $self->_Accessible('LastUpdatedBy', 'auto');
-    
-    
-
-   my $id = $self->SUPER::Create(@_);
-    
-    if ($id) {
-       $self->Load($id);
+=item  Create PARAMHASH
+
+Takes a PARAMHASH of Column -> Value pairs.
+If any Column has a Validate$PARAMNAME subroutine defined and the 
+value provided doesn't pass validation, this routine returns
+an error.
+
+If this object's table has any of the following atetributes defined as
+'Auto', this routine will automatically fill in their values.
+
+=cut
+
+sub Create {
+    my $self    = shift;
+    my %attribs = (@_);
+    foreach my $key ( keys %attribs ) {
+        my $method = "Validate$key";
+        unless ( $self->$method( $attribs{$key} ) ) {
+            if (wantarray) {
+                return ( 0, $self->loc('Invalid value for [_1]', $key) );
+            }
+            else {
+                return (0);
+            }
+        }
+    }
+    my $now = RT::Date->new( $self->CurrentUser );
+    $now->Set( Format => 'unix', Value => time );
+    $attribs{'Created'} = $now->ISO() if ( $self->_Accessible( 'Created', 'auto' ) && !$attribs{'Created'});
+
+    if ($self->_Accessible( 'Creator', 'auto' ) && !$attribs{'Creator'}) {
+         $attribs{'Creator'} = $self->CurrentUser->id || '0'; 
+    }
+    $attribs{'LastUpdated'} = $now->ISO()
+      if ( $self->_Accessible( 'LastUpdated', 'auto' ) && !$attribs{'LastUpdated'});
+
+    $attribs{'LastUpdatedBy'} = $self->CurrentUser->id || '0'
+      if ( $self->_Accessible( 'LastUpdatedBy', 'auto' ) && !$attribs{'LastUpdatedBy'});
+
+    my $id = $self->SUPER::Create(%attribs);
+    if ( UNIVERSAL::isa( $id, 'Class::ReturnValue' ) ) {
+        if ( $id->errno ) {
+            if (wantarray) {
+                return ( 0,
+                    $self->loc( "Internal Error: [_1]", $id->{error_message} ) );
+            }
+            else {
+                return (0);
+            }
+        }
+    }
+    # If the object was created in the database, 
+    # load it up now, so we're sure we get what the database 
+    # has.  Arguably, this should not be necessary, but there
+    # isn't much we can do about it.
+
+   unless ($id) { 
+    if (wantarray) {
+        return ( $id, $self->loc('Object could not be created') );
+    }
+    else {
+        return ($id);
+    }
+
+   }
+
+    if  (UNIVERSAL::isa('errno',$id)) {
+        exit(0);
+       warn "It's here!";
+        return(undef);
+    }
+
+    $self->Load($id) if ($id);
+
+
+
+    if (wantarray) {
+        return ( $id, $self->loc('Object created') );
+    }
+    else {
+        return ($id);
     }
-    
-    return($id);
-    
+
 }
 
 # }}}
 
-
 # {{{ sub LoadByCols
 
 =head2 LoadByCols
@@ -125,28 +197,33 @@ sub LoadByCols {
 
     # If this database is case sensitive we need to uncase objects for
     # explicit loading
-    if ($self->_Handle->CaseSensitive) {
-        my %newhash;
-        foreach my $key (keys %hash) {
-        # If we've been passed an empty value, we can't do the lookup. 
-               # We don't need to explicitly downcase integers or an id.
-               if ($key =~ '^id$' || $hash{$key} =~/^\d+$/ || !defined ($hash{$key}) ) {
-                       $newhash{$key} = $hash{$key};
-               }
-               else {
-                       $newhash{"lower(".$key.")"} = lc($hash{$key});  
-               }
-        }
-       $self->SUPER::LoadByCols(%newhash);
-    }
-    else {
-       $self->SUPER::LoadByCols(%hash);
+    if ( $self->_Handle->CaseSensitive ) {
+        my %newhash;
+        foreach my $key ( keys %hash ) {
+
+            # If we've been passed an empty value, we can't do the lookup. 
+            # We don't need to explicitly downcase integers or an id.
+            if ( $key =~ '^id$'
+                || !defined( $hash{$key} )
+                || $hash{$key} =~ /^\d+$/
+                 )
+            {
+                $newhash{$key} = $hash{$key};
+            }
+            else {
+                $newhash{ "lower(" . $key . ")" } = lc( $hash{$key} );
+            }
+        }
+
+        # We've clobbered everything we care about. bash the old hash
+        # and replace it with the new hash
+        %hash = %newhash;
     }
+    $self->SUPER::LoadByCols(%hash);
 }
 
 # }}}
 
-
 # {{{ Datehandling
 
 # There is room for optimizations in most of those subs:
@@ -154,10 +231,10 @@ sub LoadByCols {
 # {{{ LastUpdatedObj
 
 sub LastUpdatedObj {
-    my $self=shift;
-    my $obj = new RT::Date($self->CurrentUser);
-    
-    $obj->Set(Format => 'sql', Value => $self->LastUpdated);
+    my $self = shift;
+    my $obj  = new RT::Date( $self->CurrentUser );
+
+    $obj->Set( Format => 'sql', Value => $self->LastUpdated );
     return $obj;
 }
 
@@ -166,12 +243,11 @@ sub LastUpdatedObj {
 # {{{ CreatedObj
 
 sub CreatedObj {
-    my $self=shift;
-    my $obj = new RT::Date($self->CurrentUser);
-    
-    $obj->Set(Format => 'sql', Value => $self->Created);
+    my $self = shift;
+    my $obj  = new RT::Date( $self->CurrentUser );
+
+    $obj->Set( Format => 'sql', Value => $self->Created );
 
-    
     return $obj;
 }
 
@@ -182,9 +258,10 @@ sub CreatedObj {
 # TODO: This should be deprecated
 #
 sub AgeAsString {
-    my $self=shift;
-    return($self->CreatedObj->AgeAsString());
+    my $self = shift;
+    return ( $self->CreatedObj->AgeAsString() );
 }
+
 # }}}
 
 # {{{ LastUpdatedAsString
@@ -192,12 +269,13 @@ sub AgeAsString {
 # TODO this should be deprecated
 
 sub LastUpdatedAsString {
-    my $self=shift;
-    if ($self->LastUpdated) {
-       return ($self->LastUpdatedObj->AsString());
-         
-    } else {
-       return "never";
+    my $self = shift;
+    if ( $self->LastUpdated ) {
+        return ( $self->LastUpdatedObj->AsString() );
+
+    }
+    else {
+        return "never";
     }
 }
 
@@ -209,8 +287,9 @@ sub LastUpdatedAsString {
 #
 sub CreatedAsString {
     my $self = shift;
-    return ($self->CreatedObj->AsString());
+    return ( $self->CreatedObj->AsString() );
 }
+
 # }}}
 
 # {{{ LongSinceUpdateAsString
@@ -218,42 +297,47 @@ sub CreatedAsString {
 # TODO This should be deprecated
 #
 sub LongSinceUpdateAsString {
-    my $self=shift;
-    if ($self->LastUpdated) {
-      
-        return ($self->LastUpdatedObj->AgeAsString());
-       
-    } else {
-       return "never";
+    my $self = shift;
+    if ( $self->LastUpdated ) {
+
+        return ( $self->LastUpdatedObj->AgeAsString() );
+
+    }
+    else {
+        return "never";
     }
 }
+
 # }}}
 
 # }}} Datehandling
 
-
 # {{{ sub _Set 
-sub _Set  {
-  my $self = shift;
+sub _Set {
+    my $self = shift;
 
-  my %args = ( Field => undef,
-              Value => undef,
-              IsSQL => undef,
-              @_ );
+    my %args = (
+        Field => undef,
+        Value => undef,
+        IsSQL => undef,
+        @_
+    );
 
+    #if the user is trying to modify the record
+    # TODO: document _why_ this code is here
 
-  #if the user is trying to modify the record
-  if ((!defined ($args{'Field'})) || (!defined ($args{'Value'}))) {
-    $args{'Value'} = 0; 
-   }
+    if ( ( !defined( $args{'Field'} ) ) || ( !defined( $args{'Value'} ) ) ) {
+        $args{'Value'} = 0;
+    }
 
-  $self->_SetLastUpdated();
-  $self->SUPER::_Set(Field => $args{'Field'},
-                    Value => $args{'Value'},
-                    IsSQL => $args{'IsSQL'});
-  
-  
+    $self->_SetLastUpdated();
+    my ( $val, $msg ) = $self->SUPER::_Set(
+        Field => $args{'Field'},
+        Value => $args{'Value'},
+        IsSQL => $args{'IsSQL'}
+    );
 }
+
 # }}}
 
 # {{{ sub _SetLastUpdated
@@ -268,16 +352,20 @@ It takes no options. Arguably, this is a bug
 sub _SetLastUpdated {
     my $self = shift;
     use RT::Date;
-    my $now = new RT::Date($self->CurrentUser);
+    my $now = new RT::Date( $self->CurrentUser );
     $now->SetToNow();
 
-    if ($self->_Accessible('LastUpdated','auto')) {
-       my ($msg, $val) = $self->__Set( Field => 'LastUpdated',
-                                        Value => $now->ISO);
+    if ( $self->_Accessible( 'LastUpdated', 'auto' ) ) {
+        my ( $msg, $val ) = $self->__Set(
+            Field => 'LastUpdated',
+            Value => $now->ISO
+        );
     }
-    if ($self->_Accessible('LastUpdatedBy','auto')) {
-        my ($msg, $val) = $self->__Set( Field => 'LastUpdatedBy', 
-                                       Value => $self->CurrentUser->id);
+    if ( $self->_Accessible( 'LastUpdatedBy', 'auto' ) ) {
+        my ( $msg, $val ) = $self->__Set(
+            Field => 'LastUpdatedBy',
+            Value => $self->CurrentUser->id
+        );
     }
 }
 
@@ -291,15 +379,16 @@ Returns an RT::User object with the RT account of the creator of this row
 
 =cut
 
-sub CreatorObj  {
-  my $self = shift;
-  unless (exists $self->{'CreatorObj'}) {
-    
-    $self->{'CreatorObj'} = RT::User->new($self->CurrentUser);
-    $self->{'CreatorObj'}->Load($self->Creator);
-  }
-  return($self->{'CreatorObj'});
+sub CreatorObj {
+    my $self = shift;
+    unless ( exists $self->{'CreatorObj'} ) {
+
+        $self->{'CreatorObj'} = RT::User->new( $self->CurrentUser );
+        $self->{'CreatorObj'}->Load( $self->Creator );
+    }
+    return ( $self->{'CreatorObj'} );
 }
+
 # }}}
 
 # {{{ sub LastUpdatedByObj
@@ -311,35 +400,56 @@ sub CreatorObj  {
 =cut
 
 sub LastUpdatedByObj {
-    my $self=shift;
-    unless (exists $self->{LastUpdatedByObj}) {
-       $self->{'LastUpdatedByObj'}=RT::User->new($self->CurrentUser);
-       $self->{'LastUpdatedByObj'}->Load($self->LastUpdatedBy);
+    my $self = shift;
+    unless ( exists $self->{LastUpdatedByObj} ) {
+        $self->{'LastUpdatedByObj'} = RT::User->new( $self->CurrentUser );
+        $self->{'LastUpdatedByObj'}->Load( $self->LastUpdatedBy );
     }
     return $self->{'LastUpdatedByObj'};
 }
 
 # }}}
 
-# {{{ sub CurrentUser 
 
-=head2 CurrentUser
+require Encode::compat if $] < 5.007001;
+require Encode;
 
-If called with an argument, sets the current user to that user object.
-This will affect ACL decisions, etc.  
-Returns the current user
+sub __Value {
+    my $self  = shift;
+    my $field = shift;
+    my %args = ( decode_utf8 => 1,
+                 @_ );
 
-=cut
+    unless (defined $field && $field) {
+        $RT::Logger->error("$self __Value called with undef field");
+    }
+    my $value = $self->SUPER::__Value($field);
+
+    return('') if ( !defined($value) || $value eq '');
+
+    return Encode::decode_utf8($value) || $value if $args{'decode_utf8'};
+    return $value;
+}
 
-sub CurrentUser  {
-  my $self = shift;
+# Set up defaults for DBIx::SearchBuilder::Record::Cachable
 
-  if (@_) {
-    $self->{'user'} = shift;
+sub _CacheConfig {
+  {
+     'cache_p'        => 1,
+     'fast_update_p'  => 1,
+     'cache_for_sec'  => 30,
   }
-  return ($self->{'user'});
 }
-# }}}
 
+=head2 _DecodeUTF8
+
+ When passed a string will "decode" it int a proper UTF-8 string
+
+=cut
+
+eval "require RT::Record_Vendor";
+die $@ if ($@ && $@ !~ qr{^Can't locate RT/Record_Vendor.pm});
+eval "require RT::Record_Local";
+die $@ if ($@ && $@ !~ qr{^Can't locate RT/Record_Local.pm});
 
 1;