1 # BEGIN BPS TAGGED BLOCK {{{
5 # This software is Copyright (c) 1996-2013 Best Practical Solutions, LLC
6 # <sales@bestpractical.com>
8 # (Except where explicitly superseded by other copyright notices)
13 # This work is made available to you under the terms of Version 2 of
14 # the GNU General Public License. A copy of that license should have
15 # been provided with this software, but in any event can be snarfed
18 # This work is distributed in the hope that it will be useful, but
19 # WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
21 # General Public License for more details.
23 # You should have received a copy of the GNU General Public License
24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
26 # 02110-1301 or visit their web page on the internet at
27 # http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
30 # CONTRIBUTION SUBMISSION POLICY:
32 # (The following paragraph is not intended to limit the rights granted
33 # to you to modify and distribute this software under the terms of
34 # the GNU General Public License and is only of importance to you if
35 # you choose to contribute your changes and enhancements to the
36 # community by submitting them to Best Practical Solutions, LLC.)
38 # By intentionally submitting any modifications, corrections or
39 # derivatives to this work, or any other work intended for use with
40 # Request Tracker, to Best Practical Solutions, LLC, you confirm that
41 # you are the copyright holder for those contributions and you grant
42 # Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
43 # royalty-free, perpetual, license to use, copy, create derivative
44 # works based on those contributions, and sublicense and distribute
45 # those contributions and any derivatives thereof.
47 # END BPS TAGGED BLOCK }}}
49 package RT::CustomField;
55 use Scalar::Util 'blessed';
57 use base 'RT::Record';
59 use Role::Basic 'with';
60 with "RT::Record::Role::Rights";
62 sub Table {'CustomFields'}
64 use Scalar::Util qw(blessed);
65 use RT::CustomFieldValues;
66 use RT::ObjectCustomFields;
67 use RT::ObjectCustomFieldValues;
74 labels => [ 'Select multiple values', # loc
75 'Select one value', # loc
76 'Select up to [_1] values', # loc
82 # Default is the first one
86 single => [ 'Select box', # loc
97 labels => [ 'Enter multiple values', # loc
98 'Enter one value', # loc
99 'Enter up to [_1] values', # loc
106 'Fill in multiple text areas', # loc
107 'Fill in one text area', # loc
108 'Fill in up to [_1] text areas', # loc
115 'Fill in multiple wikitext areas', # loc
116 'Fill in one wikitext area', # loc
117 'Fill in up to [_1] wikitext areas', # loc
125 'Upload multiple images', # loc
126 'Upload one image', # loc
127 'Upload up to [_1] images', # loc
134 'Upload multiple files', # loc
135 'Upload one file', # loc
136 'Upload up to [_1] files', # loc
144 'Combobox: Select or enter multiple values', # loc
145 'Combobox: Select or enter one value', # loc
146 'Combobox: Select or enter up to [_1] values', # loc
153 'Enter multiple values with autocompletion', # loc
154 'Enter one value with autocompletion', # loc
155 'Enter up to [_1] values with autocompletion', # loc
163 'Select multiple dates', # loc
165 'Select up to [_1] dates', # loc
172 'Select multiple datetimes', # loc
173 'Select datetime', # loc
174 'Select up to [_1] datetimes', # loc
182 labels => [ 'Enter multiple IP addresses', # loc
183 'Enter one IP address', # loc
184 'Enter up to [_1] IP addresses', # loc
191 labels => [ 'Enter multiple IP address ranges', # loc
192 'Enter one IP address range', # loc
193 'Enter up to [_1] IP address ranges', # loc
199 my %BUILTIN_GROUPINGS;
200 my %FRIENDLY_LOOKUP_TYPES = ();
202 __PACKAGE__->RegisterLookupType( 'RT::Queue-RT::Ticket' => "Tickets", ); #loc
203 __PACKAGE__->RegisterLookupType( 'RT::Queue-RT::Ticket-RT::Transaction' => "Ticket Transactions", ); #loc
204 __PACKAGE__->RegisterLookupType( 'RT::User' => "Users", ); #loc
205 __PACKAGE__->RegisterLookupType( 'RT::Queue' => "Queues", ); #loc
206 __PACKAGE__->RegisterLookupType( 'RT::Group' => "Groups", ); #loc
208 __PACKAGE__->RegisterBuiltInGroupings(
209 'RT::Ticket' => [ qw(Basics Dates Links People) ],
210 'RT::User' => [ 'Identity', 'Access control', 'Location', 'Phones' ],
213 __PACKAGE__->AddRight( General => SeeCustomField => 'View custom fields'); # loc_pair
214 __PACKAGE__->AddRight( Admin => AdminCustomField => 'Create, modify and delete custom fields'); # loc_pair
215 __PACKAGE__->AddRight( Admin => AdminCustomFieldValues => 'Create, modify and delete custom fields values'); # loc_pair
216 __PACKAGE__->AddRight( Staff => ModifyCustomField => 'Add, modify and delete custom field values for objects'); # loc_pair
220 RT::CustomField_Overlay - overlay for RT::CustomField
224 =head1 'CORE' METHODS
226 =head2 Create PARAMHASH
228 Create takes a hash of values and creates a row in the database:
233 varchar(255) 'Pattern'.
234 varchar(255) 'Description'.
236 varchar(255) 'LookupType'.
237 smallint(6) 'Disabled'.
239 C<LookupType> is generally the result of either
240 C<RT::Ticket->CustomFieldLookupType> or C<RT::Transaction->CustomFieldLookupType>.
255 IncludeContentForValue => '',
259 unless ( $self->CurrentUser->HasRight(Object => $RT::System, Right => 'AdminCustomField') ) {
260 return (0, $self->loc('Permission Denied'));
263 if ( $args{TypeComposite} ) {
264 @args{'Type', 'MaxValues'} = split(/-/, $args{TypeComposite}, 2);
266 elsif ( $args{Type} =~ s/(?:(Single)|Multiple)$// ) {
267 # old style Type string
268 $args{'MaxValues'} = $1 ? 1 : 0;
270 $args{'MaxValues'} = int $args{'MaxValues'};
272 if ( !exists $args{'Queue'}) {
273 # do nothing -- things below are strictly backward compat
275 elsif ( ! $args{'Queue'} ) {
276 unless ( $self->CurrentUser->HasRight( Object => $RT::System, Right => 'AssignCustomFields') ) {
277 return ( 0, $self->loc('Permission Denied') );
279 $args{'LookupType'} = 'RT::Queue-RT::Ticket';
282 my $queue = RT::Queue->new($self->CurrentUser);
283 $queue->Load($args{'Queue'});
284 unless ($queue->Id) {
285 return (0, $self->loc("Queue not found"));
287 unless ( $queue->CurrentUserHasRight('AssignCustomFields') ) {
288 return ( 0, $self->loc('Permission Denied') );
290 $args{'LookupType'} = 'RT::Queue-RT::Ticket';
291 $args{'Queue'} = $queue->Id;
294 my ($ok, $msg) = $self->_IsValidRegex( $args{'Pattern'} );
295 return (0, $self->loc("Invalid pattern: [_1]", $msg)) unless $ok;
297 if ( $args{'MaxValues'} != 1 && $args{'Type'} =~ /(text|combobox)$/i ) {
298 $RT::Logger->debug("Support for 'multiple' Texts or Comboboxes is not implemented");
299 $args{'MaxValues'} = 1;
302 if ( $args{'RenderType'} ||= undef ) {
303 my $composite = join '-', @args{'Type', 'MaxValues'};
304 return (0, $self->loc("This custom field has no Render Types"))
305 unless $self->HasRenderTypes( $composite );
307 if ( $args{'RenderType'} eq $self->DefaultRenderType( $composite ) ) {
308 $args{'RenderType'} = undef;
310 return (0, $self->loc("Invalid Render Type") )
311 unless grep $_ eq $args{'RenderType'}, $self->RenderTypes( $composite );
315 $args{'ValuesClass'} = undef if ($args{'ValuesClass'} || '') eq 'RT::CustomFieldValues';
316 if ( $args{'ValuesClass'} ||= undef ) {
317 return (0, $self->loc("This Custom Field can not have list of values"))
318 unless $self->IsSelectionType( $args{'Type'} );
320 unless ( $self->ValidateValuesClass( $args{'ValuesClass'} ) ) {
321 return (0, $self->loc("Invalid Custom Field values source"));
325 $args{'Disabled'} ||= 0;
327 (my $rv, $msg) = $self->SUPER::Create(
328 Name => $args{'Name'},
329 Type => $args{'Type'},
330 RenderType => $args{'RenderType'},
331 MaxValues => $args{'MaxValues'},
332 Pattern => $args{'Pattern'},
333 BasedOn => $args{'BasedOn'},
334 ValuesClass => $args{'ValuesClass'},
335 Description => $args{'Description'},
336 Disabled => $args{'Disabled'},
337 LookupType => $args{'LookupType'},
341 if ( exists $args{'LinkValueTo'}) {
342 $self->SetLinkValueTo($args{'LinkValueTo'});
345 if ( exists $args{'IncludeContentForValue'}) {
346 $self->SetIncludeContentForValue($args{'IncludeContentForValue'});
349 return ($rv, $msg) unless exists $args{'Queue'};
351 # Compat code -- create a new ObjectCustomField mapping
352 my $OCF = RT::ObjectCustomField->new( $self->CurrentUser );
354 CustomField => $self->Id,
355 ObjectId => $args{'Queue'},
364 Load a custom field. If the value handed in is an integer, load by custom field ID. Otherwise, Load by name.
370 my $id = shift || '';
372 if ( $id =~ /^\d+$/ ) {
373 return $self->SUPER::Load( $id );
375 return $self->LoadByName( Name => $id );
381 =head2 LoadByName (Queue => QUEUEID, Name => NAME)
383 Loads the Custom field named NAME.
385 Will load a Disabled Custom Field even if there is a non-disabled Custom Field
388 If a Queue parameter is specified, only look for ticket custom fields tied to that Queue.
390 If the Queue parameter is '0', look for global ticket custom fields.
392 If no queue parameter is specified, look for any and all custom fields with this name.
394 BUG/TODO, this won't let you specify that you only want user or group CFs.
398 # Compatibility for API change after 3.0 beta 1
399 *LoadNameAndQueue = \&LoadByName;
400 # Change after 3.4 beta.
401 *LoadByNameAndQueue = \&LoadByName;
412 unless ( defined $args{'Name'} && length $args{'Name'} ) {
413 $RT::Logger->error("Couldn't load Custom Field without Name");
414 return wantarray ? (0, $self->loc("No name provided")) : 0;
417 # if we're looking for a queue by name, make it a number
418 if ( defined $args{'Queue'} && ($args{'Queue'} =~ /\D/ || !$self->ContextObject) ) {
419 my $QueueObj = RT::Queue->new( $self->CurrentUser );
420 $QueueObj->Load( $args{'Queue'} );
421 $args{'Queue'} = $QueueObj->Id;
422 $self->SetContextObject( $QueueObj )
423 unless $self->ContextObject;
426 # XXX - really naive implementation. Slow. - not really. still just one query
428 my $CFs = RT::CustomFields->new( $self->CurrentUser );
429 $CFs->SetContextObject( $self->ContextObject );
430 my $field = $args{'Name'} =~ /\D/? 'Name' : 'id';
431 $CFs->Limit( FIELD => $field, VALUE => $args{'Name'}, CASESENSITIVE => 0);
433 # The context object may be a ticket, for example, as context for a
434 # queue CF. The valid lookup types are thus the entire set of
435 # ACLEquivalenceObjects for the context object.
436 $args{LookupType} ||= [
437 map {$_->CustomFieldLookupType}
438 ($self->ContextObject, $self->ContextObject->ACLEquivalenceObjects) ]
439 if $self->ContextObject;
441 $args{LookupType} = [ $args{LookupType} ]
442 if $args{LookupType} and not ref($args{LookupType});
443 $CFs->Limit( FIELD => "LookupType", OPERATOR => "IN", VALUE => $args{LookupType} )
444 if $args{LookupType};
446 # Don't limit to queue if queue is 0. Trying to do so breaks
447 # RT::Group type CFs.
448 if ( defined $args{'Queue'} ) {
449 $CFs->LimitToQueue( $args{'Queue'} );
452 # When loading by name, we _can_ load disabled fields, but prefer
453 # non-disabled fields.
456 { FIELD => "Disabled", ORDER => 'ASC' },
459 # We only want one entry.
460 $CFs->RowsPerPage(1);
462 # version before 3.8 just returns 0, so we need to test if wantarray to be
463 # backward compatible.
464 return wantarray ? (0, $self->loc("Not found")) : 0 unless my $first = $CFs->First;
466 return $self->LoadById( $first->id );
472 =head2 Custom field values
476 Return a object (collection) of all acceptable values for this Custom Field.
477 Class of the object can vary and depends on the return value
478 of the C<ValuesClass> method.
482 *ValuesObj = \&Values;
487 my $class = $self->ValuesClass;
488 if ( $class ne 'RT::CustomFieldValues') {
489 eval "require $class" or die "$@";
491 my $cf_values = $class->new( $self->CurrentUser );
492 # if the user has no rights, return an empty object
493 if ( $self->id && $self->CurrentUserHasRight( 'SeeCustomField') ) {
494 $cf_values->LimitToCustomField( $self->Id );
496 $cf_values->Limit( FIELD => 'id', VALUE => 0, SUBCLAUSE => 'acl' );
504 Create a new value for this CustomField. Takes a paramhash containing the elements Name, Description and SortOrder
512 unless ($self->CurrentUserHasRight('AdminCustomField') || $self->CurrentUserHasRight('AdminCustomFieldValues')) {
513 return (0, $self->loc('Permission Denied'));
517 if ( !defined $args{'Name'} || $args{'Name'} eq '' ) {
518 return (0, $self->loc("Can't add a custom field value without a name"));
521 my $newval = RT::CustomFieldValue->new( $self->CurrentUser );
522 return $newval->Create( %args, CustomField => $self->Id );
528 =head3 DeleteValue ID
530 Deletes a value from this custom field by id.
532 Does not remove this value for any article which has had it selected
539 unless ( $self->CurrentUserHasRight('AdminCustomField') || $self->CurrentUserHasRight('AdminCustomFieldValues') ) {
540 return (0, $self->loc('Permission Denied'));
543 my $val_to_del = RT::CustomFieldValue->new( $self->CurrentUser );
544 $val_to_del->Load( $id );
545 unless ( $val_to_del->Id ) {
546 return (0, $self->loc("Couldn't find that value"));
548 unless ( $val_to_del->CustomField == $self->Id ) {
549 return (0, $self->loc("That is not a value for this custom field"));
552 my $retval = $val_to_del->Delete;
554 return (0, $self->loc("Custom field value could not be deleted"));
556 return ($retval, $self->loc("Custom field value deleted"));
560 =head2 ValidateQueue Queue
562 Make sure that the name specified is valid
570 return 0 unless length $value;
572 return $self->SUPER::ValidateName($value);
575 =head2 ValidateQueue Queue
577 Make sure that the queue specified is a valid queue name
585 return undef unless defined $id;
586 # 0 means "Global" null would _not_ be ok.
587 return 1 if $id eq '0';
589 my $q = RT::Queue->new( RT->SystemUser );
591 return undef unless $q->id;
599 Retuns an array of the types of CustomField that are supported
604 return (sort {(($FieldTypes{$a}{sort_order}||999) <=> ($FieldTypes{$b}{sort_order}||999)) or ($a cmp $b)} keys %FieldTypes);
608 =head2 IsSelectionType
610 Retuns a boolean value indicating whether the C<Values> method makes sense
611 to this Custom Field.
615 sub IsSelectionType {
617 my $type = @_? shift : $self->Type;
618 return undef unless $type;
619 return $FieldTypes{$type}->{selection_type};
624 =head2 IsExternalValues
628 sub IsExternalValues {
630 return 0 unless $self->IsSelectionType( @_ );
631 return $self->ValuesClass eq 'RT::CustomFieldValues'? 0 : 1;
636 return $self->_Value( ValuesClass => @_ ) || 'RT::CustomFieldValues';
641 my $class = shift || 'RT::CustomFieldValues';
643 if ( $class eq 'RT::CustomFieldValues' ) {
644 return $self->_Set( Field => 'ValuesClass', Value => undef, @_ );
647 return (0, $self->loc("This Custom Field can not have list of values"))
648 unless $self->IsSelectionType;
650 unless ( $self->ValidateValuesClass( $class ) ) {
651 return (0, $self->loc("Invalid Custom Field values source"));
653 return $self->_Set( Field => 'ValuesClass', Value => $class, @_ );
656 sub ValidateValuesClass {
660 return 1 if !$class || $class eq 'RT::CustomFieldValues';
661 return 1 if grep $class eq $_, RT->Config->Get('CustomFieldValuesSources');
666 =head2 FriendlyType [TYPE, MAX_VALUES]
668 Returns a localized human-readable version of the custom field type.
669 If a custom field type is specified as the parameter, the friendly type for that type will be returned
676 my $type = @_ ? shift : $self->Type;
677 my $max = @_ ? shift : $self->MaxValues;
678 $max = 0 unless $max;
680 if (my $friendly_type = $FieldTypes{$type}->{labels}->[$max>2 ? 2 : $max]) {
681 return ( $self->loc( $friendly_type, $max ) );
684 return ( $self->loc( $type ) );
688 sub FriendlyTypeComposite {
690 my $composite = shift || $self->TypeComposite;
691 return $self->FriendlyType(split(/-/, $composite, 2));
695 =head2 ValidateType TYPE
697 Takes a single string. returns true if that string is a value
707 if ( $type =~ s/(?:Single|Multiple)$// ) {
709 Arguments => "suffix 'Single' or 'Multiple'",
710 Instead => "MaxValues",
715 if ( $FieldTypes{$type} ) {
727 if ($type =~ s/(?:(Single)|Multiple)$//) {
729 Arguments => "suffix 'Single' or 'Multiple'",
730 Instead => "MaxValues",
733 $self->SetMaxValues($1 ? 1 : 0);
735 $self->_Set(Field => 'Type', Value =>$type);
738 =head2 SetPattern STRING
740 Takes a single string representing a regular expression. Performs basic
741 validation on that regex, and sets the C<Pattern> field for the CF if it
750 my ($ok, $msg) = $self->_IsValidRegex($regex);
752 return $self->_Set(Field => 'Pattern', Value => $regex);
755 return (0, $self->loc("Invalid pattern: [_1]", $msg));
759 =head2 _IsValidRegex(Str $regex) returns (Bool $success, Str $msg)
761 Tests if the string contains an invalid regex.
767 my $regex = shift or return (1, 'valid');
770 local $SIG{__DIE__} = sub { 1 };
771 local $SIG{__WARN__} = sub { 1 };
773 if (eval { qr/$regex/; 1 }) {
778 $err =~ s{[,;].*}{}; # strip debug info from error
786 Returns true if this CustomField only accepts a single value.
787 Returns false if it accepts multiple values
793 if (($self->MaxValues||0) == 1) {
801 sub UnlimitedValues {
803 if (($self->MaxValues||0) == 0) {
812 =head2 ACLEquivalenceObjects
814 Returns list of objects via which users can get rights on this custom field. For custom fields
815 these objects can be set using L<ContextObject|/"ContextObject and SetContextObject">.
819 sub ACLEquivalenceObjects {
822 my $ctx = $self->ContextObject
824 return ($ctx, $ctx->ACLEquivalenceObjects);
827 =head2 ContextObject and SetContextObject
829 Set or get a context for this object. It can be ticket, queue or another
830 object this CF added to. Used for ACL control, for example
831 SeeCustomField can be granted on queue level to allow people to see all
832 fields added to the queue.
836 sub SetContextObject {
838 return $self->{'context_object'} = shift;
843 return $self->{'context_object'};
846 sub ValidContextType {
851 $valid{$_}++ for split '-', $self->LookupType;
852 delete $valid{'RT::Transaction'};
854 return $valid{$class};
857 =head2 LoadContextObject
859 Takes an Id for a Context Object and loads the right kind of RT::Object
860 for this particular Custom Field (based on the LookupType) and returns it.
861 This is a good way to ensure you don't try to use a Queue as a Context
862 Object on a User Custom Field.
866 sub LoadContextObject {
869 my $contextid = shift;
871 unless ( $self->ValidContextType($type) ) {
872 RT->Logger->debug("Invalid ContextType $type for Custom Field ".$self->Id);
876 my $context_object = $type->new( $self->CurrentUser );
877 my ($id, $msg) = $context_object->LoadById( $contextid );
879 RT->Logger->debug("Invalid ContextObject id: $msg");
882 return $context_object;
885 =head2 ValidateContextObject
887 Ensure that a given ContextObject applies to this Custom Field. For
888 custom fields that are assigned to Queues or to Classes, this checks
889 that the Custom Field is actually added to that object. For Global
890 Custom Fields, it returns true as long as the Object is of the right
891 type, because you may be using your permissions on a given Queue of
892 Class to see a Global CF. For CFs that are only added globally, you
893 don't need a ContextObject.
897 sub ValidateContextObject {
901 return 1 if $self->IsGlobal;
903 # global only custom fields don't have objects
904 # that should be used as context objects.
905 return if $self->IsOnlyGlobal;
907 # Otherwise, make sure we weren't passed a user object that we're
908 # supposed to treat as a queue.
909 return unless $self->ValidContextType(ref $object);
911 # Check that it is added correctly
912 my ($added_to) = grep {ref($_) eq $self->RecordClassFromLookupType} ($object, $object->ACLEquivalenceObjects);
913 return unless $added_to;
914 return $self->IsAdded($added_to->id);
921 unless ( $self->CurrentUserHasRight('AdminCustomField') ) {
922 return ( 0, $self->loc('Permission Denied') );
924 return $self->SUPER::_Set( @_ );
932 Takes the name of a table column.
933 Returns its value as a string, if the user passes an ACL check
939 return undef unless $self->id;
941 # we need to do the rights check
942 unless ( $self->CurrentUserHasRight('SeeCustomField') ) {
944 "Permission denied. User #". $self->CurrentUser->id
945 ." has no SeeCustomField right on CF #". $self->id
949 return $self->__Value( @_ );
956 1 will cause this custom field to no longer be avaialble for objects.
957 0 will re-enable this field.
962 =head2 SetTypeComposite
964 Set this custom field's type and maximum values as a composite value
968 sub SetTypeComposite {
970 my $composite = shift;
972 my $old = $self->TypeComposite;
974 my ($type, $max_values) = split(/-/, $composite, 2);
975 if ( $type ne $self->Type ) {
976 my ($status, $msg) = $self->SetType( $type );
977 return ($status, $msg) unless $status;
979 if ( ($max_values || 0) != ($self->MaxValues || 0) ) {
980 my ($status, $msg) = $self->SetMaxValues( $max_values );
981 return ($status, $msg) unless $status;
983 my $render = $self->RenderType;
984 if ( $render and not grep { $_ eq $render } $self->RenderTypes ) {
985 # We switched types and our render type is no longer valid, so unset it
986 # and use the default
987 $self->SetRenderType( undef );
989 return 1, $self->loc(
990 "Type changed from '[_1]' to '[_2]'",
991 $self->FriendlyTypeComposite( $old ),
992 $self->FriendlyTypeComposite( $composite ),
998 Returns a composite value composed of this object's type and maximum values
1005 return join '-', ($self->Type || ''), ($self->MaxValues || 0);
1008 =head2 TypeComposites
1010 Returns an array of all possible composite values for custom fields.
1014 sub TypeComposites {
1016 return grep !/(?:[Tt]ext|Combobox|Date|DateTime)-0/, map { ("$_-1", "$_-0") } $self->Types;
1021 Returns the type of form widget to render for this custom field. Currently
1022 this only affects fields which return true for L</HasRenderTypes>.
1028 return '' unless $self->HasRenderTypes;
1030 return $self->_Value( 'RenderType', @_ )
1031 || $self->DefaultRenderType;
1034 =head2 SetRenderType TYPE
1036 Sets this custom field's render type.
1043 return (0, $self->loc("This custom field has no Render Types"))
1044 unless $self->HasRenderTypes;
1046 if ( !$type || $type eq $self->DefaultRenderType ) {
1047 return $self->_Set( Field => 'RenderType', Value => undef, @_ );
1050 if ( not grep { $_ eq $type } $self->RenderTypes ) {
1051 return (0, $self->loc("Invalid Render Type for custom field of type [_1]",
1052 $self->FriendlyType));
1055 # XXX: Remove this restriction once we support lists and cascaded selects
1056 if ( $self->BasedOnObj->id and $type =~ /List/ ) {
1057 return (0, $self->loc("We can't currently render as a List when basing categories on another custom field. Please use another render type."));
1060 return $self->_Set( Field => 'RenderType', Value => $type, @_ );
1063 =head2 DefaultRenderType [TYPE COMPOSITE]
1065 Returns the default render type for this custom field's type or the TYPE
1066 COMPOSITE specified as an argument.
1070 sub DefaultRenderType {
1072 my $composite = @_ ? shift : $self->TypeComposite;
1073 my ($type, $max) = split /-/, $composite, 2;
1074 return unless $type and $self->HasRenderTypes($composite);
1075 return $FieldTypes{$type}->{render_types}->{ $max == 1 ? 'single' : 'multiple' }[0];
1078 =head2 HasRenderTypes [TYPE_COMPOSITE]
1080 Returns a boolean value indicating whether the L</RenderTypes> and
1081 L</RenderType> methods make sense for this custom field.
1083 Currently true only for type C<Select>.
1087 sub HasRenderTypes {
1089 my ($type, $max) = split /-/, (@_ ? shift : $self->TypeComposite), 2;
1090 return undef unless $type;
1091 return defined $FieldTypes{$type}->{render_types}
1092 ->{ $max == 1 ? 'single' : 'multiple' };
1095 =head2 RenderTypes [TYPE COMPOSITE]
1097 Returns the valid render types for this custom field's type or the TYPE
1098 COMPOSITE specified as an argument.
1104 my $composite = @_ ? shift : $self->TypeComposite;
1105 my ($type, $max) = split /-/, $composite, 2;
1106 return unless $type and $self->HasRenderTypes($composite);
1107 return @{$FieldTypes{$type}->{render_types}->{ $max == 1 ? 'single' : 'multiple' }};
1110 =head2 SetLookupType
1112 Autrijus: care to doc how LookupTypes work?
1119 if ( $lookup ne $self->LookupType ) {
1120 # Okay... We need to invalidate our existing relationships
1121 RT::ObjectCustomField->new($self->CurrentUser)->DeleteAll( CustomField => $self );
1123 return $self->_Set(Field => 'LookupType', Value =>$lookup);
1128 Returns an array of LookupTypes available
1135 return sort keys %FRIENDLY_LOOKUP_TYPES;
1138 =head2 FriendlyLookupType
1140 Returns a localized description of the type of this custom field
1144 sub FriendlyLookupType {
1146 my $lookup = shift || $self->LookupType;
1148 return ($self->loc( $FRIENDLY_LOOKUP_TYPES{$lookup} ))
1149 if defined $FRIENDLY_LOOKUP_TYPES{$lookup};
1151 my @types = map { s/^RT::// ? $self->loc($_) : $_ }
1152 grep { defined and length }
1153 split( /-/, $lookup )
1156 state $LocStrings = [
1157 "[_1] objects", # loc
1158 "[_1]'s [_2] objects", # loc
1159 "[_1]'s [_2]'s [_3] objects", # loc
1161 return ( $self->loc( $LocStrings->[$#types], @types ) );
1164 =head1 RecordClassFromLookupType
1166 Returns the type of Object referred to by ObjectCustomFields' ObjectId column
1168 Optionally takes a LookupType to use instead of using the value on the loaded
1169 record. In this case, the method may be called on the class instead of an
1174 sub RecordClassFromLookupType {
1176 my $type = shift || $self->LookupType;
1177 my ($class) = ($type =~ /^([^-]+)/);
1179 if (blessed($self) and $self->LookupType eq $type) {
1181 "Custom Field #". $self->id
1182 ." has incorrect LookupType '$type'"
1185 RT->Logger->error("Invalid LookupType passed as argument: $type");
1192 =head1 ObjectTypeFromLookupType
1194 Returns the ObjectType used in ObjectCustomFieldValues rows for this CF
1196 Optionally takes a LookupType to use instead of using the value on the loaded
1197 record. In this case, the method may be called on the class instead of an
1202 sub ObjectTypeFromLookupType {
1204 my $type = shift || $self->LookupType;
1205 my ($class) = ($type =~ /([^-]+)$/);
1207 if (blessed($self) and $self->LookupType eq $type) {
1209 "Custom Field #". $self->id
1210 ." has incorrect LookupType '$type'"
1213 RT->Logger->error("Invalid LookupType passed as argument: $type");
1220 sub CollectionClassFromLookupType {
1223 my $record_class = $self->RecordClassFromLookupType;
1224 return undef unless $record_class;
1226 my $collection_class;
1227 if ( UNIVERSAL::can($record_class.'Collection', 'new') ) {
1228 $collection_class = $record_class.'Collection';
1229 } elsif ( UNIVERSAL::can($record_class.'es', 'new') ) {
1230 $collection_class = $record_class.'es';
1231 } elsif ( UNIVERSAL::can($record_class.'s', 'new') ) {
1232 $collection_class = $record_class.'s';
1234 $RT::Logger->error("Can not find a collection class for record class '$record_class'");
1237 return $collection_class;
1242 Returns a (sorted and lowercased) list of the groupings in which this custom
1245 If called on a loaded object, the returned list is limited to groupings which
1246 apply to the record class this CF applies to (L</RecordClassFromLookupType>).
1248 If passed a loaded object or a class name, the returned list is limited to
1249 groupings which apply to the class of the object or the specified class.
1251 If called on an unloaded object, all potential groupings are returned.
1257 my $record_class = $self->_GroupingClass(shift);
1259 my $config = RT->Config->Get('CustomFieldGroupings');
1260 $config = {} unless ref($config) eq 'HASH';
1263 if ( $record_class ) {
1264 push @groups, sort {lc($a) cmp lc($b)} keys %{ $BUILTIN_GROUPINGS{$record_class} || {} };
1265 if ( ref($config->{$record_class} ||= []) eq "ARRAY") {
1266 my @order = @{ $config->{$record_class} };
1268 push @groups, shift(@order);
1272 @groups = sort {lc($a) cmp lc($b)} keys %{ $config->{$record_class} };
1275 my %all = (%$config, %BUILTIN_GROUPINGS);
1276 @groups = sort {lc($a) cmp lc($b)} map {$self->Groupings($_)} grep {$_} keys(%all);
1281 grep defined && length && !$seen{lc $_}++,
1285 =head2 CustomGroupings
1287 Identical to L</Groupings> but filters out built-in groupings from the the
1292 sub CustomGroupings {
1294 my $record_class = $self->_GroupingClass(shift);
1295 return grep !$BUILTIN_GROUPINGS{$record_class}{$_}, $self->Groupings( $record_class );
1298 sub _GroupingClass {
1302 my $record_class = ref($record) || $record || '';
1303 $record_class = $self->RecordClassFromLookupType
1304 if !$record_class and blessed($self) and $self->id;
1306 return $record_class;
1309 =head2 RegisterBuiltInGroupings
1311 Registers groupings to be considered a fundamental part of RT, either via use
1312 in core RT or via an extension. These groupings must be rendered explicitly in
1313 Mason by specific calls to F</Elements/ShowCustomFields> and
1314 F</Elements/EditCustomFields>. They will not show up automatically on normal
1315 display pages like configured custom groupings.
1317 Takes a set of key-value pairs of class names (valid L<RT::Record> subclasses)
1318 and array refs of grouping names to consider built-in.
1320 If a class already contains built-in groupings (such as L<RT::Ticket> and
1321 L<RT::User>), new groupings are appended.
1325 sub RegisterBuiltInGroupings {
1329 while (my ($k,$v) = each %new) {
1330 $v = [$v] unless ref($v) eq 'ARRAY';
1331 $BUILTIN_GROUPINGS{$k} = {
1332 %{$BUILTIN_GROUPINGS{$k} || {}},
1336 $BUILTIN_GROUPINGS{''} = { map { %$_ } values %BUILTIN_GROUPINGS };
1341 Certain custom fields (users, groups) should only be added globally;
1342 codify that set here for reference.
1349 return ($self->LookupType =~ /^RT::(?:Group|User)/io);
1354 Instead => "IsOnlyGlobal",
1357 return shift->IsOnlyGlobal(@_);
1362 Returns collection with objects this custom field is added to.
1363 Class of the collection depends on L</LookupType>.
1364 See all L</NotAddedTo> .
1366 Doesn't takes into account if object is added globally.
1372 return RT::ObjectCustomField->new( $self->CurrentUser )
1373 ->AddedTo( CustomField => $self );
1377 Instead => "AddedTo",
1385 Returns collection with objects this custom field is not added to.
1386 Class of the collection depends on L</LookupType>.
1387 See all L</AddedTo> .
1389 Doesn't take into account if the object is added globally.
1395 return RT::ObjectCustomField->new( $self->CurrentUser )
1396 ->NotAddedTo( CustomField => $self );
1400 Instead => "NotAddedTo",
1403 shift->NotAddedTo(@_)
1408 Takes object id and returns corresponding L<RT::ObjectCustomField>
1409 record if this custom field is added to the object. Use 0 to check
1410 if custom field is added globally.
1417 my $ocf = RT::ObjectCustomField->new( $self->CurrentUser );
1418 $ocf->LoadByCols( CustomField => $self->id, ObjectId => $id || 0 );
1419 return undef unless $ocf->id;
1424 Instead => "IsAdded",
1430 sub IsGlobal { return shift->IsAdded(0) }
1434 Returns true if custom field is applied to any object.
1441 my $ocf = RT::ObjectCustomField->new( $self->CurrentUser );
1442 $ocf->LoadByCols( CustomField => $self->id );
1443 return $ocf->id ? 1 : 0;
1446 =head2 AddToObject OBJECT
1448 Add this custom field as a custom field for a single object, such as a queue or group.
1457 my $id = $object->Id || 0;
1459 unless (index($self->LookupType, ref($object)) == 0) {
1460 return ( 0, $self->loc('Lookup type mismatch') );
1463 unless ( $object->CurrentUserHasRight('AssignCustomFields') ) {
1464 return ( 0, $self->loc('Permission Denied') );
1467 my $ocf = RT::ObjectCustomField->new( $self->CurrentUser );
1468 my ( $oid, $msg ) = $ocf->Add(
1469 CustomField => $self->id, ObjectId => $id,
1471 return ( $oid, $msg );
1475 =head2 RemoveFromObject OBJECT
1477 Remove this custom field for a single object, such as a queue or group.
1483 sub RemoveFromObject {
1486 my $id = $object->Id || 0;
1488 unless (index($self->LookupType, ref($object)) == 0) {
1489 return ( 0, $self->loc('Object type mismatch') );
1492 unless ( $object->CurrentUserHasRight('AssignCustomFields') ) {
1493 return ( 0, $self->loc('Permission Denied') );
1496 my $ocf = $self->IsAdded( $id );
1498 return ( 0, $self->loc("This custom field cannot be added to that object") );
1501 # XXX: Delete doesn't return anything
1502 my ( $oid, $msg ) = $ocf->Delete;
1503 return ( $oid, $msg );
1507 =head2 AddValueForObject HASH
1509 Adds a custom field value for a record object of some kind.
1510 Takes a param hash of
1524 sub AddValueForObject {
1529 LargeContent => undef,
1530 ContentType => undef,
1533 my $obj = $args{'Object'} or return ( 0, $self->loc('Invalid object') );
1535 unless ( $self->CurrentUserHasRight('ModifyCustomField') ) {
1536 return ( 0, $self->loc('Permission Denied') );
1539 unless ( $self->MatchPattern($args{'Content'}) ) {
1540 return ( 0, $self->loc('Input must match [_1]', $self->FriendlyPattern) );
1543 $RT::Handle->BeginTransaction;
1545 if ( $self->MaxValues ) {
1546 my $current_values = $self->ValuesForObject($obj);
1547 my $extra_values = ( $current_values->Count + 1 ) - $self->MaxValues;
1549 # (The +1 is for the new value we're adding)
1551 # If we have a set of current values and we've gone over the maximum
1552 # allowed number of values, we'll need to delete some to make room.
1553 # which former values are blown away is not guaranteed
1555 while ($extra_values) {
1556 my $extra_item = $current_values->Next;
1557 unless ( $extra_item->id ) {
1558 $RT::Logger->crit( "We were just asked to delete "
1559 ."a custom field value that doesn't exist!" );
1560 $RT::Handle->Rollback();
1563 $extra_item->Delete;
1568 if (my $canonicalizer = $self->can('_CanonicalizeValue'.$self->Type)) {
1569 $canonicalizer->($self, \%args);
1574 my $newval = RT::ObjectCustomFieldValue->new( $self->CurrentUser );
1575 my ($val, $msg) = $newval->Create(
1576 ObjectType => ref($obj),
1577 ObjectId => $obj->Id,
1578 Content => $args{'Content'},
1579 LargeContent => $args{'LargeContent'},
1580 ContentType => $args{'ContentType'},
1581 CustomField => $self->Id
1585 $RT::Handle->Rollback();
1586 return ($val, $self->loc("Couldn't create record: [_1]", $msg));
1589 $RT::Handle->Commit();
1596 sub _CanonicalizeValueDateTime {
1599 my $DateObj = RT::Date->new( $self->CurrentUser );
1600 $DateObj->Set( Format => 'unknown',
1601 Value => $args->{'Content'} );
1602 $args->{'Content'} = $DateObj->ISO;
1605 # For date, we need to store Content as ISO date
1606 sub _CanonicalizeValueDate {
1610 # in case user input date with time, let's omit it by setting timezone
1611 # to utc so "hour" won't affect "day"
1612 my $DateObj = RT::Date->new( $self->CurrentUser );
1613 $DateObj->Set( Format => 'unknown',
1614 Value => $args->{'Content'},
1616 $args->{'Content'} = $DateObj->Date( Timezone => 'user' );
1619 =head2 MatchPattern STRING
1621 Tests the incoming string against the Pattern of this custom field object
1622 and returns a boolean; returns true if the Pattern is empty.
1628 my $regex = $self->Pattern or return 1;
1630 return (( defined $_[0] ? $_[0] : '') =~ $regex);
1636 =head2 FriendlyPattern
1638 Prettify the pattern of this custom field, by taking the text in C<(?#text)>
1643 sub FriendlyPattern {
1645 my $regex = $self->Pattern;
1647 return '' unless length $regex;
1648 if ( $regex =~ /\(\?#([^)]*)\)/ ) {
1649 return '[' . $self->loc($1) . ']';
1659 =head2 DeleteValueForObject HASH
1661 Deletes a custom field value for a ticket. Takes a param hash of Object and Content
1663 Returns a tuple of (STATUS, MESSAGE). If the call succeeded, the STATUS is true. otherwise it's false
1667 sub DeleteValueForObject {
1669 my %args = ( Object => undef,
1675 unless ($self->CurrentUserHasRight('ModifyCustomField')) {
1676 return (0, $self->loc('Permission Denied'));
1679 my $oldval = RT::ObjectCustomFieldValue->new($self->CurrentUser);
1681 if (my $id = $args{'Id'}) {
1684 unless ($oldval->id) {
1685 $oldval->LoadByObjectContentAndCustomField(
1686 Object => $args{'Object'},
1687 Content => $args{'Content'},
1688 CustomField => $self->Id,
1693 # check to make sure we found it
1694 unless ($oldval->Id) {
1695 return(0, $self->loc("Custom field value [_1] could not be found for custom field [_2]", $args{'Content'}, $self->Name));
1698 # for single-value fields, we need to validate that empty string is a valid value for it
1699 if ( $self->SingleValue and not $self->MatchPattern( '' ) ) {
1700 return ( 0, $self->loc('Input must match [_1]', $self->FriendlyPattern) );
1705 my $ret = $oldval->Delete();
1707 return(0, $self->loc("Custom field value could not be found"));
1709 return($oldval->Id, $self->loc("Custom field value deleted"));
1713 =head2 ValuesForObject OBJECT
1715 Return an L<RT::ObjectCustomFieldValues> object containing all of this custom field's values for OBJECT
1719 sub ValuesForObject {
1723 my $values = RT::ObjectCustomFieldValues->new($self->CurrentUser);
1724 unless ($self->id and $self->CurrentUserHasRight('SeeCustomField')) {
1725 # Return an empty object if they have no rights to see
1726 $values->Limit( FIELD => "id", VALUE => 0, SUBCLAUSE => "ACL" );
1730 $values->LimitToCustomField($self->Id);
1731 $values->LimitToObject($object);
1737 =head2 RegisterLookupType LOOKUPTYPE FRIENDLYNAME
1739 Tell RT that a certain object accepts custom fields via a lookup type and
1740 provide a friendly name for such CFs.
1744 'RT::Queue-RT::Ticket' => "Tickets", # loc
1745 'RT::Queue-RT::Ticket-RT::Transaction' => "Ticket Transactions", # loc
1746 'RT::User' => "Users", # loc
1747 'RT::Group' => "Groups", # loc
1748 'RT::Queue' => "Queues", # loc
1750 This is a class method.
1754 sub RegisterLookupType {
1757 my $friendly_name = shift;
1759 $FRIENDLY_LOOKUP_TYPES{$path} = $friendly_name;
1762 sub _ForObjectType {
1764 Instead => 'RegisterLookupType',
1768 $self->RegisterLookupType(@_);
1772 =head2 IncludeContentForValue [VALUE] (and SetIncludeContentForValue)
1774 Gets or sets the C<IncludeContentForValue> for this custom field. RT
1775 uses this field to automatically include content into the user's browser
1776 as they display records with custom fields in RT.
1780 sub SetIncludeContentForValue {
1781 shift->IncludeContentForValue(@_);
1783 sub IncludeContentForValue{
1785 $self->_URLTemplate('IncludeContentForValue', @_);
1790 =head2 LinkValueTo [VALUE] (and SetLinkValueTo)
1792 Gets or sets the C<LinkValueTo> for this custom field. RT
1793 uses this field to make custom field values into hyperlinks in the user's
1794 browser as they display records with custom fields in RT.
1799 sub SetLinkValueTo {
1800 shift->LinkValueTo(@_);
1805 $self->_URLTemplate('LinkValueTo', @_);
1810 =head2 _URLTemplate NAME [VALUE]
1812 With one argument, returns the _URLTemplate named C<NAME>, but only if
1813 the current user has the right to see this custom field.
1815 With two arguments, attemptes to set the relevant template value.
1821 my $template_name = shift;
1825 unless ( $self->CurrentUserHasRight('AdminCustomField') ) {
1826 return ( 0, $self->loc('Permission Denied') );
1828 $self->SetAttribute( Name => $template_name, Content => $value );
1829 return ( 1, $self->loc('Updated') );
1831 unless ( $self->id && $self->CurrentUserHasRight('SeeCustomField') ) {
1835 my @attr = $self->Attributes->Named($template_name);
1836 my $attr = shift @attr;
1838 if ($attr) { return $attr->Content }
1847 return $self->_Set( Field => 'BasedOn', Value => $value, @_ )
1848 unless defined $value and length $value;
1850 my $cf = RT::CustomField->new( $self->CurrentUser );
1851 $cf->SetContextObject( $self->ContextObject );
1852 $cf->Load( ref $value ? $value->id : $value );
1854 return (0, "Permission Denied")
1855 unless $cf->id && $cf->CurrentUserHasRight('SeeCustomField');
1857 # XXX: Remove this restriction once we support lists and cascaded selects
1858 if ( $self->RenderType =~ /List/ ) {
1859 return (0, $self->loc("We can't currently render as a List when basing categories on another custom field. Please use another render type."));
1862 return $self->_Set( Field => 'BasedOn', Value => $value, @_ )
1868 my $obj = RT::CustomField->new( $self->CurrentUser );
1869 $obj->SetContextObject( $self->ContextObject );
1870 if ( $self->BasedOn ) {
1871 $obj->Load( $self->BasedOn );
1883 Returns the current value of id.
1884 (In the database, id is stored as int(11).)
1892 Returns the current value of Name.
1893 (In the database, Name is stored as varchar(200).)
1897 =head2 SetName VALUE
1901 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1902 (In the database, Name will be stored as a varchar(200).)
1910 Returns the current value of Type.
1911 (In the database, Type is stored as varchar(200).)
1915 =head2 SetType VALUE
1919 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1920 (In the database, Type will be stored as a varchar(200).)
1928 Returns the current value of RenderType.
1929 (In the database, RenderType is stored as varchar(64).)
1933 =head2 SetRenderType VALUE
1936 Set RenderType to VALUE.
1937 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1938 (In the database, RenderType will be stored as a varchar(64).)
1946 Returns the current value of MaxValues.
1947 (In the database, MaxValues is stored as int(11).)
1951 =head2 SetMaxValues VALUE
1954 Set MaxValues to VALUE.
1955 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1956 (In the database, MaxValues will be stored as a int(11).)
1964 Returns the current value of Pattern.
1965 (In the database, Pattern is stored as text.)
1969 =head2 SetPattern VALUE
1972 Set Pattern to VALUE.
1973 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1974 (In the database, Pattern will be stored as a text.)
1982 Returns the current value of BasedOn.
1983 (In the database, BasedOn is stored as int(11).)
1987 =head2 SetBasedOn VALUE
1990 Set BasedOn to VALUE.
1991 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1992 (In the database, BasedOn will be stored as a int(11).)
2000 Returns the current value of Description.
2001 (In the database, Description is stored as varchar(255).)
2005 =head2 SetDescription VALUE
2008 Set Description to VALUE.
2009 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2010 (In the database, Description will be stored as a varchar(255).)
2018 Returns the current value of SortOrder.
2019 (In the database, SortOrder is stored as int(11).)
2023 =head2 SetSortOrder VALUE
2026 Set SortOrder to VALUE.
2027 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2028 (In the database, SortOrder will be stored as a int(11).)
2036 Returns the current value of LookupType.
2037 (In the database, LookupType is stored as varchar(255).)
2041 =head2 SetLookupType VALUE
2044 Set LookupType to VALUE.
2045 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2046 (In the database, LookupType will be stored as a varchar(255).)
2054 Returns the current value of Creator.
2055 (In the database, Creator is stored as int(11).)
2063 Returns the current value of Created.
2064 (In the database, Created is stored as datetime.)
2070 =head2 LastUpdatedBy
2072 Returns the current value of LastUpdatedBy.
2073 (In the database, LastUpdatedBy is stored as int(11).)
2081 Returns the current value of LastUpdated.
2082 (In the database, LastUpdated is stored as datetime.)
2090 Returns the current value of Disabled.
2091 (In the database, Disabled is stored as smallint(6).)
2095 =head2 SetDisabled VALUE
2098 Set Disabled to VALUE.
2099 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2100 (In the database, Disabled will be stored as a smallint(6).)
2107 sub _CoreAccessible {
2111 {read => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''},
2113 {read => 1, write => 1, sql_type => 12, length => 200, is_blob => 0, is_numeric => 0, type => 'varchar(200)', default => ''},
2115 {read => 1, write => 1, sql_type => 12, length => 200, is_blob => 0, is_numeric => 0, type => 'varchar(200)', default => ''},
2117 {read => 1, write => 1, sql_type => 12, length => 64, is_blob => 0, is_numeric => 0, type => 'varchar(64)', default => ''},
2119 {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''},
2121 {read => 1, write => 1, sql_type => -4, length => 0, is_blob => 1, is_numeric => 0, type => 'text', default => ''},
2123 {read => 1, write => 1, sql_type => 12, length => 64, is_blob => 0, is_numeric => 0, type => 'varchar(64)', default => ''},
2125 {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''},
2127 {read => 1, write => 1, sql_type => 12, length => 255, is_blob => 0, is_numeric => 0, type => 'varchar(255)', default => ''},
2129 {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
2131 {read => 1, write => 1, sql_type => 12, length => 255, is_blob => 0, is_numeric => 0, type => 'varchar(255)', default => ''},
2133 {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
2135 {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''},
2137 {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
2139 {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''},
2141 {read => 1, write => 1, sql_type => 5, length => 6, is_blob => 0, is_numeric => 1, type => 'smallint(6)', default => '0'},
2146 sub FindDependencies {
2148 my ($walker, $deps) = @_;
2150 $self->SUPER::FindDependencies($walker, $deps);
2152 $deps->Add( out => $self->BasedOnObj )
2153 if $self->BasedOnObj->id;
2155 my $applied = RT::ObjectCustomFields->new( $self->CurrentUser );
2156 $applied->LimitToCustomField( $self->id );
2157 $deps->Add( in => $applied );
2159 $deps->Add( in => $self->Values ) if $self->ValuesClass eq "RT::CustomFieldValues";
2163 RT::Base->_ImportOverlays();