import rt 3.6.4
[freeside.git] / rt / lib / RT / CustomField_Overlay.pm
index 9e0ce24..8f7c8bd 100644 (file)
@@ -2,7 +2,7 @@
 # 
 # COPYRIGHT:
 #  
-# This software is Copyright (c) 1996-2005 Best Practical Solutions, LLC 
+# This software is Copyright (c) 1996-2007 Best Practical Solutions, LLC 
 #                                          <jesse@bestpractical.com>
 # 
 # (Except where explicitly superseded by other copyright notices)
@@ -22,7 +22,9 @@
 # 
 # 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/copyleft/gpl.html.
 # 
 # 
 # CONTRIBUTION SUBMISSION POLICY:
@@ -85,6 +87,11 @@ use RT::ObjectCustomFieldValues;
         'Upload one file',             # loc
         'Upload up to [_1] files',     # loc
     ],
+    Combobox => [
+        'Combobox: Select or enter multiple values',   # loc
+        'Combobox: Select or enter one value',         # loc
+        'Combobox: Select or enter up to [_1] values', # loc
+    ],
 );
 
 
@@ -194,8 +201,15 @@ sub Create {
         unless ( $queue->CurrentUserHasRight('AssignCustomFields') ) {
             return ( 0, $self->loc('Permission Denied') );
         }
-       $args{'LookupType'} = 'RT::Queue-RT::Ticket';
+        $args{'LookupType'} = 'RT::Queue-RT::Ticket';
+        $args{'Queue'} = $queue->Id;
+    }
+
+    my ($ok, $msg) = $self->_IsValidRegex($args{'Pattern'});
+    if (!$ok) {
+        return (0, $self->loc("Invalid pattern: [_1]", $msg));
     }
+
     my $rv = $self->SUPER::Create(
                          Name => $args{'Name'},
                          Type => $args{'Type'},
@@ -356,26 +370,20 @@ ok ($delval,"Deleting a cf value: $delmsg");
 =cut
 
 sub AddValue {
-       my $self = shift;
-       my %args = ( Name => undef,
-                    Description => undef,
-                    SortOrder => undef,
-                    @_ );
+    my $self = shift;
+    my %args = @_;
 
     unless ($self->CurrentUserHasRight('AdminCustomField')) {
         return (0, $self->loc('Permission Denied'));
     }
 
-    unless ($args{'Name'}) {
+    # allow zero value
+    if ( !defined $args{'Name'} || $args{'Name'} eq '' ) {
         return(0, $self->loc("Can't add a custom field value without a name"));
     }
-       my $newval = RT::CustomFieldValue->new($self->CurrentUser);
-       return($newval->Create(
-                    CustomField => $self->Id,
-             Name =>$args{'Name'},
-             Description => ($args{'Description'} || ''),
-             SortOrder => ($args{'SortOrder'} || '0')
-        ));    
+
+    my $newval = RT::CustomFieldValue->new($self->CurrentUser);
+    return($newval->Create(%args, CustomField => $self->Id));
 }
 
 
@@ -461,7 +469,7 @@ sub ValuesForTicket {
        my $self = shift;
     my $ticket_id = shift;
     
-    $RT::Logger->debug( ref($self) . " -> ValuesForTicket deprecated in favor of ValuesForObject"); 
+    $RT::Logger->debug( ref($self) . " -> ValuesForTicket deprecated in favor of ValuesForObject at (". join(":",caller).")"); 
     my $ticket = RT::Ticket->new($self->CurrentUser);
     $ticket->Load($ticket_id);
 
@@ -485,7 +493,7 @@ sub AddValueForTicket {
        my %args = ( Ticket => undef,
                  Content => undef,
                     @_ );
-    $RT::Logger->debug( ref($self) . " -> AddValueForTicket deprecated in favor of AddValueForObject"); 
+    $RT::Logger->debug( ref($self) . " -> AddValueForTicket deprecated in favor of AddValueForObject at (". join(":",caller).")");
 
 
     my $ticket = RT::Ticket->new($self->CurrentUser);
@@ -513,7 +521,7 @@ sub DeleteValueForTicket {
                  Content => undef,
                     @_ );
 
-    $RT::Logger->debug( ref($self) . " -> DeleteValueForTicket deprecated in favor of DeleteValueForObject"); 
+    $RT::Logger->debug( ref($self) . " -> DeleteValueForTicket deprecated in favor of DeleteValueForObject at (". join(":",caller).")"); 
 
 
     my $ticket = RT::Ticket->new($self->CurrentUser);
@@ -565,6 +573,22 @@ sub Types {
 
 # }}}
 
+# {{{ IsSelectionType
+=head2 IsSelectionType 
+
+Retuns a boolean value indicating whether the C<Values> method makes sense
+to this Custom Field.
+
+=cut
+
+sub IsSelectionType {
+    my $self = shift;
+    $self->Type =~ /(?:Select|Combobox)/;
+}
+
+# }}}
+
 
 =head2 FriendlyType [TYPE, MAX_VALUES]
 
@@ -615,7 +639,7 @@ sub ValidateType {
     my $type = shift;
 
     if ($type =~ s/(?:Single|Multiple)$//) {
-       $RT::Logger->warning( "Prefix 'Single' and 'Multiple' to Type deprecated, use MaxValues instead");
+       $RT::Logger->warning( "Prefix 'Single' and 'Multiple' to Type deprecated, use MaxValues instead at (". join(":",caller).")");
     }
 
     if( $FieldTypes{$type}) {
@@ -631,12 +655,57 @@ sub SetType {
     my $self = shift;
     my $type = shift;
     if ($type =~ s/(?:(Single)|Multiple)$//) {
-       warn "'Single' and 'Multiple' on SetType deprecated, use SetMaxValues instead";
+       $RT::Logger->warning("'Single' and 'Multiple' on SetType deprecated, use SetMaxValues instead at (". join(":",caller).")");
        $self->SetMaxValues($1 ? 1 : 0);
     }
     $self->SUPER::SetType($type);
 }
 
+=head2 SetPattern STRING
+
+Takes a single string representing a regular expression.  Performs basic
+validation on that regex, and sets the C<Pattern> field for the CF if it
+is valid.
+
+=cut
+
+sub SetPattern {
+    my $self = shift;
+    my $regex = shift;
+
+    my ($ok, $msg) = $self->_IsValidRegex($regex);
+    if ($ok) {
+        return $self->SUPER::SetPattern($regex);
+    }
+    else {
+        return (0, $self->loc("Invalid pattern: [_1]", $msg));
+    }
+}
+
+=head2 _IsValidRegex(Str $regex) returns (Bool $success, Str $msg)
+
+Tests if the string contains an invalid regex.
+
+=cut
+
+sub _IsValidRegex {
+    my $self  = shift;
+    my $regex = shift or return (1, 'valid');
+
+    local $^W; local $@;
+    $SIG{__DIE__} = sub { 1 };
+    $SIG{__WARN__} = sub { 1 };
+
+    if (eval { qr/$regex/; 1 }) {
+        return (1, 'valid');
+    }
+
+    my $err = $@;
+    $err =~ s{[,;].*}{};    # strip debug info from error
+    chomp $err;
+    return (0, $err);
+}
+
 # {{{ SingleValue
 
 =head2 SingleValue
@@ -738,19 +807,19 @@ Takes a boolean.
 # }}}
 
 sub Queue {
-    $RT::Logger->debug( ref($_[0]) . " -> Queue deprecated");
+    $RT::Logger->debug( ref($_[0]) . " -> Queue deprecated at (". join(":",caller).")");
     
     return 0;
 }
 
 sub SetQueue {
-    $RT::Logger->debug( ref($_[0]) . " -> SetQueue deprecated");
+    $RT::Logger->debug( ref($_[0]) . " -> SetQueue deprecated at (". join(":",caller).")");
 
     return 0;
 }
 
 sub QueueObj {
-    $RT::Logger->debug( ref($_[0]) . " -> QueueObj deprecated");
+    $RT::Logger->debug( ref($_[0]) . " -> QueueObj deprecated at (". join(":",caller).")");
 
     return undef;
 }
@@ -808,7 +877,7 @@ Returns an array of all possible composite values for custom fields.
 
 sub TypeComposites {
     my $self = shift;
-    return grep !/Text-0/, map { ("$_-1", "$_-0") } $self->Types;
+    return grep !/(?:[Tt]ext|Combobox)-0/, map { ("$_-1", "$_-0") } $self->Types;
 }
 
 =head2 LookupTypes
@@ -876,10 +945,10 @@ sub AddToObject {
     if ( $ObjectCF->Id ) {
         return ( 0, $self->loc("That is already the current value") );
     }
-    my ( $id, $msg ) =
+    my ( $oid, $msg ) =
       $ObjectCF->Create( ObjectId => $id, CustomField => $self->Id );
 
-    return ( $id, $msg );
+    return ( $oid, $msg );
 }
 
 
@@ -911,9 +980,10 @@ sub RemoveFromObject {
     unless ( $ObjectCF->Id ) {
         return ( 0, $self->loc("This custom field does not apply to that object") );
     }
-    my ( $id, $msg ) = $ObjectCF->Delete;
+    # XXX: Delete doesn't return anything
+    my ( $oid, $msg ) = $ObjectCF->Delete;
 
-    return ( $id, $msg );
+    return ( $oid, $msg );
 }
 
 # {{{ AddValueForObject
@@ -950,6 +1020,10 @@ sub AddValueForObject {
         return ( 0, $self->loc('Permission Denied') );
     }
 
+    unless ( $self->MatchPattern($args{Content}) ) {
+        return ( 0, $self->loc('Input must match [_1]', $self->FriendlyPattern) );
+    }
+
     $RT::Handle->BeginTransaction;
 
     my $current_values = $self->ValuesForObject($obj);
@@ -1000,6 +1074,51 @@ sub AddValueForObject {
 
 # }}}
 
+# {{{ MatchPattern
+
+=head2 MatchPattern STRING
+
+Tests the incoming string against the Pattern of this custom field object
+and returns a boolean; returns true if the Pattern is empty.
+
+=cut
+
+sub MatchPattern {
+    my $self = shift;
+    my $regex = $self->Pattern;
+
+    return 1 if !length($regex);
+    return ($_[0] =~ $regex);
+}
+
+
+# }}}
+
+# {{{ FriendlyPattern
+
+=head2 FriendlyPattern
+
+Prettify the pattern of this custom field, by taking the text in C<(?#text)>
+and localizing it.
+
+=cut
+
+sub FriendlyPattern {
+    my $self = shift;
+    my $regex = $self->Pattern;
+
+    return '' if !length($regex);
+    if ($regex =~ /\(\?#([^)]*)\)/) {
+        return '[' . $self->loc($1) . ']';
+    }
+    else {
+        return $regex;
+    }
+}
+
+
+# }}}
+
 # {{{ DeleteValueForObject
 
 =head2 DeleteValueForObject HASH
@@ -1036,10 +1155,16 @@ sub DeleteValueForObject {
     }
 
 
-    # check ot make sure we found it
+    # check to make sure we found it
     unless ($oldval->Id) {
         return(0, $self->loc("Custom field value [_1] could not be found for custom field [_2]", $args{'Content'}, $self->Name));
     }
+
+    # for single-value fields, we need to validate that empty string is a valid value for it
+    if ( $self->SingleValue and not $self->MatchPattern( '' ) ) {
+        return ( 0, $self->loc('Input must match [_1]', $self->FriendlyPattern) );
+    }
+
     # delete it
 
     my $ret = $oldval->Delete();
@@ -1099,6 +1224,77 @@ sub _ForObjectType {
 
 }
 
-# }}}
 
+=head2 IncludeContentForValue [VALUE] (and SetIncludeContentForValue)
+
+Gets or sets the  C<IncludeContentForValue> for this custom field. RT
+uses this field to automatically include content into the user's browser
+as they display records with custom fields in RT.
+
+=cut
+
+sub SetIncludeContentForValue {
+    shift->IncludeContentForValue(@_);
+}
+sub IncludeContentForValue{
+    my $self = shift;
+    $self->_URLTemplate('IncludeContentForValue', @_);
+}
+
+
+
+=head2 LinkValueTo [VALUE] (and SetLinkValueTo)
+
+Gets or sets the  C<LinkValueTo> for this custom field. RT
+uses this field to make custom field values into hyperlinks in the user's
+browser as they display records with custom fields in RT.
+
+=cut
+
+
+sub SetLinkValueTo {
+    shift->LinkValueTo(@_);
+}
+
+sub LinkValueTo {
+    my $self = shift;
+    $self->_URLTemplate('LinkValueTo', @_);
+
+}
+
+
+=head2 _URLTemplate  NAME [VALUE]
+
+With one argument, returns the _URLTemplate named C<NAME>, but only if
+the current user has the right to see this custom field.
+
+With two arguments, attemptes to set the relevant template value.
+
+=cut
+
+
+
+sub _URLTemplate {
+    my $self          = shift;
+    my $template_name = shift;
+    if (@_) {
+
+        my $value = shift;
+        unless ( $self->CurrentUserHasRight('AdminCustomField') ) {
+            return ( 0, $self->loc('Permission Denied') );
+        }
+        $self->SetAttribute( Name => $template_name, Content => $value );
+        return ( 1, $self->loc('Updated') );
+    } else {
+        unless ( $self->id && $self->CurrentUserHasRight('SeeCustomField') ) {
+            return (undef);
+        }
+
+        my @attr = $self->Attributes->Named($template_name);
+        my $attr = shift @attr;
+
+        if ($attr) { return $attr->Content }
+
+    }
+}
 1;