#
# 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)
use strict;
use warnings;
-
+use Scalar::Util 'blessed';
use base 'RT::Record';
'Select up to [_1] datetimes', # loc
]
},
- TimeValue => [
- 'Enter multiple time values (UNSUPPORTED)',
- 'Enter a time value',
- 'Enter [_1] time values (UNSUPPORTED)',
- ],
+ TimeValue => {
+ sort_order => 105,
+ selection_type => 0,
+ labels => [
+ 'Enter multiple time values (UNSUPPORTED)',
+ 'Enter a time value',
+ 'Enter [_1] time values (UNSUPPORTED)',
+ ]
+ },
IPAddress => {
sort_order => 110,
$self->SetUILocation( $args{'UILocation'} );
}
+ if ( exists $args{'NoClone'} ) {
+ $self->SetNoClone( $args{'NoClone'} );
+ }
+
return ($rv, $msg) unless exists $args{'Queue'};
# Compat code -- create a new ObjectCustomField mapping
}
# if we're looking for a queue by name, make it a number
- if ( defined $args{'Queue'} && $args{'Queue'} =~ /\D/ ) {
+ if ( defined $args{'Queue'} && ($args{'Queue'} =~ /\D/ || !$self->ContextObject) ) {
my $QueueObj = RT::Queue->new( $self->CurrentUser );
$QueueObj->Load( $args{'Queue'} );
$args{'Queue'} = $QueueObj->Id;
+ $self->SetContextObject( $QueueObj )
+ unless $self->ContextObject;
}
# XXX - really naive implementation. Slow. - not really. still just one query
# if the user has no rights, return an empty object
if ( $self->id && $self->CurrentUserHasRight( 'SeeCustomField') ) {
$cf_values->LimitToCustomField( $self->Id );
+ } else {
+ $cf_values->Limit( FIELD => 'id', VALUE => 0, SUBCLAUSE => 'acl' );
}
return ($cf_values);
}
my $self = shift;
my $class = shift;
- return 1 if !defined $class || $class eq 'RT::CustomFieldValues';
+ return 1 if !$class || $class eq 'RT::CustomFieldValues';
return 1 if grep $class eq $_, RT->Config->Get('CustomFieldValuesSources');
return undef;
}
my $self = shift;
return $self->{'context_object'};
}
-
+
+sub ValidContextType {
+ my $self = shift;
+ my $class = shift;
+
+ my %valid;
+ $valid{$_}++ for split '-', $self->LookupType;
+ delete $valid{'RT::Transaction'};
+
+ return $valid{$class};
+}
+
+=head2 LoadContextObject
+
+Takes an Id for a Context Object and loads the right kind of RT::Object
+for this particular Custom Field (based on the LookupType) and returns it.
+This is a good way to ensure you don't try to use a Queue as a Context
+Object on a User Custom Field.
+
+=cut
+
+sub LoadContextObject {
+ my $self = shift;
+ my $type = shift;
+ my $contextid = shift;
+
+ unless ( $self->ValidContextType($type) ) {
+ RT->Logger->debug("Invalid ContextType $type for Custom Field ".$self->Id);
+ return;
+ }
+
+ my $context_object = $type->new( $self->CurrentUser );
+ my ($id, $msg) = $context_object->LoadById( $contextid );
+ unless ( $id ) {
+ RT->Logger->debug("Invalid ContextObject id: $msg");
+ return;
+ }
+ return $context_object;
+}
+
+=head2 ValidateContextObject
+
+Ensure that a given ContextObject applies to this Custom Field.
+For custom fields that are assigned to Queues or to Classes, this checks that the Custom
+Field is actually applied to that objects. For Global Custom Fields, it returns true
+as long as the Object is of the right type, because you may be using
+your permissions on a given Queue of Class to see a Global CF.
+For CFs that are only applied Globally, you don't need a ContextObject.
+
+=cut
+
+sub ValidateContextObject {
+ my $self = shift;
+ my $object = shift;
+
+ return 1 if $self->IsApplied(0);
+
+ # global only custom fields don't have objects
+ # that should be used as context objects.
+ return if $self->ApplyGlobally;
+
+ # Otherwise, make sure we weren't passed a user object that we're
+ # supposed to treat as a queue.
+ return unless $self->ValidContextType(ref $object);
+
+ # Check that it is applied correctly
+ my ($applied_to) = grep {ref($_) eq $self->RecordClassFromLookupType} ($object, $object->ACLEquivalenceObjects);
+ return unless $applied_to;
+ return $self->IsApplied($applied_to->id);
+}
+
sub _Set {
my $self = shift;
$self->FriendlyType));
}
- # XXX: Remove this restriction once we support lists and cascaded selects
- if ( $self->BasedOnObj->id and $type =~ /List/ ) {
- return (0, $self->loc("We can't currently render as a List when basing categories on another custom field. Please use another render type."));
- }
-
return $self->_Set( Field => 'RenderType', Value => $type, @_ );
}
sub LookupTypes {
my $self = shift;
- return keys %FRIENDLY_OBJECT_TYPES;
+ return sort keys %FRIENDLY_OBJECT_TYPES;
}
my @FriendlyObjectTypes = (
return ( $self->loc( $FriendlyObjectTypes[$#types], @types ) );
}
+=head1 RecordClassFromLookupType
+
+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;
+}
+
+=head1 ObjectTypeFromLookupType
+
+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 $type = shift || $self->LookupType;
+ my ($class) = ($type =~ /([^-]+)$/);
+ unless ( $class ) {
+ 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;
}
}
- if (my $canonicalizer = $self->can('_CanonicalizeValue'.$self->Type)) {
- $canonicalizer->($self, \%args);
- }
-
-
-
my $newval = RT::ObjectCustomFieldValue->new( $self->CurrentUser );
my ($val, $msg) = $newval->Create(
ObjectType => ref($obj),
}
+sub _CanonicalizeValue {
+ my $self = shift;
+ my $args = shift;
+
+ my $type = $self->_Value('Type');
+ return 1 unless $type;
+
+ my $method = '_CanonicalizeValue'. $type;
+ return 1 unless $self->can($method);
+ $self->$method($args);
+}
sub _CanonicalizeValueDateTime {
my $self = shift;
$DateObj->Set( Format => 'unknown',
Value => $args->{'Content'} );
$args->{'Content'} = $DateObj->ISO;
+ return 1;
}
# For date, we need to store Content as ISO date
my $DateObj = RT::Date->new( $self->CurrentUser );
$DateObj->Set( Format => 'unknown',
Value => $args->{'Content'},
- Timezone => 'UTC',
);
- $args->{'Content'} = $DateObj->Date( Timezone => 'UTC' );
+ $args->{'Content'} = $DateObj->Date( Timezone => 'user' );
+ return 1;
+}
+
+sub _CanonicalizeValueIPAddress {
+ my $self = shift;
+ my $args = shift;
+
+ $args->{Content} = RT::ObjectCustomFieldValue->ParseIP( $args->{Content} );
+ return (0, $self->loc("Content is not a valid IP address"))
+ unless $args->{Content};
+ return 1;
+}
+
+sub _CanonicalizeValueIPAddressRange {
+ my $self = shift;
+ my $args = shift;
+
+ my $content = $args->{Content};
+ $content .= "-".$args->{LargeContent} if $args->{LargeContent};
+
+ ($args->{Content}, $args->{LargeContent})
+ = RT::ObjectCustomFieldValue->ParseIPRange( $content );
+
+ $args->{ContentType} = 'text/plain';
+ return (0, $self->loc("Content is not a valid IP address range"))
+ unless $args->{Content};
+ return 1;
}
=head2 MatchPattern STRING
my $object = shift;
my $values = RT::ObjectCustomFieldValues->new($self->CurrentUser);
- unless ($self->CurrentUserHasRight('SeeCustomField')) {
+ unless ($self->id and $self->CurrentUserHasRight('SeeCustomField')) {
# Return an empty object if they have no rights to see
+ $values->Limit( FIELD => "id", VALUE => 0, SUBCLAUSE => "ACL" );
return ($values);
}
-
-
+
$values->LimitToCustomField($self->Id);
- $values->LimitToEnabled();
$values->LimitToObject($object);
return ($values);
'RT::Queue-RT::Ticket-RT::Transaction' => "Ticket Transactions", # loc
'RT::User' => "Users", # loc
'RT::Group' => "Groups", # loc
+ 'RT::Queue' => "Queues", # loc
This is a class method.
unless defined $value and length $value;
my $cf = RT::CustomField->new( $self->CurrentUser );
+ $cf->SetContextObject( $self->ContextObject );
$cf->Load( ref $value ? $value->id : $value );
return (0, "Permission denied")
my $self = shift;
my $obj = RT::CustomField->new( $self->CurrentUser );
+ $obj->SetContextObject( $self->ContextObject );
if ( $self->BasedOn ) {
$obj->Load( $self->BasedOn );
}
}
}
+sub NoClone {
+ my $self = shift;
+ $self->FirstAttribute('NoClone') ? 1 : '';
+}
-
-
+sub SetNoClone {
+ my $self = shift;
+ my $value = shift;
+ if ( $value ) {
+ return $self->SetAttribute( Name => 'NoClone', Content => 1 );
+ } else {
+ return $self->DeleteAttribute('NoClone');
+ }
+}
=head2 id
{read => 1, write => 1, sql_type => -4, length => 0, is_blob => 1, is_numeric => 0, type => 'text', default => ''},
Repeated =>
{read => 1, write => 1, sql_type => 5, length => 6, is_blob => 0, is_numeric => 1, type => 'smallint(6)', default => '0'},
+ ValuesClass =>
+ {read => 1, write => 1, sql_type => 12, length => 64, is_blob => 0, is_numeric => 0, type => 'varchar(64)', default => ''},
BasedOn =>
{read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''},
Description =>