1 # BEGIN BPS TAGGED BLOCK {{{
5 # This software is Copyright (c) 1996-2014 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 [quant,_1,value,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 [quant,_1,value,values]', # loc
106 'Fill in multiple text areas', # loc
107 'Fill in one text area', # loc
108 'Fill in up to [quant,_1,text area,text areas]', # loc
115 'Fill in multiple wikitext areas', # loc
116 'Fill in one wikitext area', # loc
117 'Fill in up to [quant,_1,wikitext area,wikitext areas]', # loc
125 'Upload multiple images', # loc
126 'Upload one image', # loc
127 'Upload up to [quant,_1,image,images]', # loc
134 'Upload multiple files', # loc
135 'Upload one file', # loc
136 'Upload up to [quant,_1,file,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 [quant,_1,value,values]', # loc
153 'Enter multiple values with autocompletion', # loc
154 'Enter one value with autocompletion', # loc
155 'Enter up to [quant,_1,value,values] with autocompletion', # loc
163 'Select multiple dates', # loc
165 'Select up to [quant,_1,date,dates]', # loc
172 'Select multiple datetimes', # loc
173 'Select datetime', # loc
174 'Select up to [quant,_1,datetime,datetimes]', # loc
182 labels => [ 'Enter multiple IP addresses', # loc
183 'Enter one IP address', # loc
184 'Enter up to [quant,_1,IP address,IP addresses]', # loc
191 labels => [ 'Enter multiple IP address ranges', # loc
192 'Enter one IP address range', # loc
193 'Enter up to [quant,_1,IP address range,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
214 __PACKAGE__->AddRight( Admin => AdminCustomField => 'Create, modify and delete custom fields'); # loc
215 __PACKAGE__->AddRight( Admin => AdminCustomFieldValues => 'Create, modify and delete custom fields values'); # loc
216 __PACKAGE__->AddRight( Staff => ModifyCustomField => 'Add, modify and delete custom field values for objects'); # loc
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 Name => C<NAME>, [...]
383 Loads the Custom field named NAME. As other optional parameters, takes:
387 =item LookupType => C<LOOKUPTYPE>
389 The type of Custom Field to look for; while this parameter is not
390 required, it is highly suggested, or you may not find the Custom Field
391 you are expecting. It should be passed a C<LookupType> such as
392 L<RT::Ticket/CustomFieldLookupType> or
393 L<RT::User/CustomFieldLookupType>.
395 =item ObjectType => C<CLASS>
397 The class of object that the custom field is applied to. This can be
398 intuited from the provided C<LookupType>.
400 =item ObjectId => C<ID>
402 limits the custom field search to one applied to the relevant id. For
403 example, if a C<LookupType> of C<< RT::Ticket->CustomFieldLookupType >>
404 is used, this is which Queue the CF must be applied to. Pass 0 to only
405 search custom fields that are applied globally.
407 =item IncludeDisabled => C<BOOLEAN>
409 Whether it should return Disabled custom fields if they match; defaults
410 to on, though non-Disabled custom fields are returned preferentially.
412 =item IncludeGlobal => C<BOOLEAN>
414 Whether to also search global custom fields, even if a value is provided
415 for C<ObjectId>; defaults to off. Non-global custom fields are returned
420 For backwards compatibility, a value passed for C<Queue> is equivalent
421 to specifying a C<LookupType> of L<RT::Ticket/CustomFieldLookupType>,
422 and a C<ObjectId> of the value passed as C<Queue>.
424 If multiple custom fields match the above constraints, the first
425 according to C<SortOrder> will be returned; ties are broken by C<id>,
428 =head2 LoadNameAndQueue
430 =head2 LoadByNameAndQueue
432 Deprecated alternate names for L</LoadByName>.
436 # Compatibility for API change after 3.0 beta 1
437 *LoadNameAndQueue = \&LoadByName;
438 # Change after 3.4 beta.
439 *LoadByNameAndQueue = \&LoadByName;
449 IncludeDisabled => 1,
458 unless ( defined $args{'Name'} && length $args{'Name'} ) {
459 $RT::Logger->error("Couldn't load Custom Field without Name");
460 return wantarray ? (0, $self->loc("No name provided")) : 0;
463 if ( defined $args{'Queue'} ) {
464 # Set a LookupType for backcompat, otherwise we'll calculate
465 # one of RT::Queue from your ContextObj. Older code was relying
466 # on us defaulting to RT::Queue-RT::Ticket in old LimitToQueue call.
467 $args{LookupType} ||= 'RT::Queue-RT::Ticket';
468 $args{ObjectId} //= delete $args{Queue};
471 # Default the ObjectType to the top category of the LookupType; it's
472 # what the CFs are assigned on.
473 $args{ObjectType} ||= $1 if $args{LookupType} and $args{LookupType} =~ /^([^-]+)/;
475 # Resolve the ObjectId/ObjectType; this is necessary to properly
476 # limit ObjectId, and also possibly useful to set a ContextObj if we
477 # are currently lacking one. It is not strictly necessary if we
478 # have a context object and were passed a numeric ObjectId, but it
479 # cannot hurt to verify its sanity. Skip if we have a false
480 # ObjectId, which means "global", or if we lack an ObjectType
481 if ($args{ObjectId} and $args{ObjectType}) {
482 my ($obj, $ok, $msg);
484 $obj = $args{ObjectType}->new( $self->CurrentUser );
485 ($ok, $msg) = $obj->Load( $args{ObjectId} );
489 $args{ObjectId} = $obj->id;
490 $self->SetContextObject( $obj )
491 unless $self->ContextObject;
493 $RT::Logger->warning("Failed to load $args{ObjectType} '$args{ObjectId}'");
494 if ($args{IncludeGlobal}) {
495 # Fall back to acting like we were only asked about the
499 # If they didn't also want global results, there's no
500 # point in searching; abort
501 return wantarray ? (0, $self->loc("Not found")) : 0;
504 } elsif (not $args{ObjectType} and $args{ObjectId}) {
505 # If we skipped out on the above due to lack of ObjectType, make
506 # sure we clear out ObjectId of anything lingering
507 $RT::Logger->warning("No LookupType or ObjectType passed; ignoring ObjectId");
508 delete $args{ObjectId};
511 my $CFs = RT::CustomFields->new( $self->CurrentUser );
512 $CFs->SetContextObject( $self->ContextObject );
513 my $field = $args{'Name'} =~ /\D/? 'Name' : 'id';
514 $CFs->Limit( FIELD => $field, VALUE => $args{'Name'}, CASESENSITIVE => 0);
516 # The context object may be a ticket, for example, as context for a
517 # queue CF. The valid lookup types are thus the entire set of
518 # ACLEquivalenceObjects for the context object.
519 $args{LookupType} ||= [
520 map {$_->CustomFieldLookupType}
521 ($self->ContextObject, $self->ContextObject->ACLEquivalenceObjects) ]
522 if $self->ContextObject;
524 # Apply LookupType limits
525 $args{LookupType} = [ $args{LookupType} ]
526 if $args{LookupType} and not ref($args{LookupType});
527 $CFs->Limit( FIELD => "LookupType", OPERATOR => "IN", VALUE => $args{LookupType} )
528 if $args{LookupType};
530 # Default to by SortOrder and id; this mirrors the standard ordering
531 # of RT::CustomFields (minus the Name, which is guaranteed to be
534 { FIELD => 'SortOrder',
540 if (defined $args{ObjectId}) {
541 # The join to OCFs is distinct -- either we have a global
542 # application or an objectid match, but never both. Even if
543 # this were not the case, we care only for the first row.
544 my $ocfs = $CFs->_OCFAlias( Distinct => 1);
545 if ($args{IncludeGlobal}) {
550 VALUE => [ $args{ObjectId}, 0 ],
552 # Find the queue-specific first
553 unshift @order, { ALIAS => $ocfs, FIELD => "ObjectId", ORDER => "DESC" };
558 VALUE => $args{ObjectId},
563 if ($args{IncludeDisabled}) {
564 # Load disabled fields, but return them only as a last resort.
565 # This goes at the front of @order, as we prefer the
566 # non-disabled global CF to the disabled Queue-specific CF.
568 unshift @order, { FIELD => "Disabled", ORDER => 'ASC' };
571 # Apply the above orderings
572 $CFs->OrderByCols( @order );
574 # We only want one entry.
575 $CFs->RowsPerPage(1);
577 # version before 3.8 just returns 0, so we need to test if wantarray to be
578 # backward compatible.
579 return wantarray ? (0, $self->loc("Not found")) : 0 unless my $first = $CFs->First;
581 return $self->LoadById( $first->id );
587 =head2 Custom field values
591 Return a object (collection) of all acceptable values for this Custom Field.
592 Class of the object can vary and depends on the return value
593 of the C<ValuesClass> method.
597 *ValuesObj = \&Values;
602 my $class = $self->ValuesClass;
603 if ( $class ne 'RT::CustomFieldValues') {
604 $class->require or die "Can't load $class: $@";
606 my $cf_values = $class->new( $self->CurrentUser );
607 # if the user has no rights, return an empty object
608 if ( $self->id && $self->CurrentUserHasRight( 'SeeCustomField') ) {
609 $cf_values->LimitToCustomField( $self->Id );
611 $cf_values->Limit( FIELD => 'id', VALUE => 0, SUBCLAUSE => 'acl' );
619 Create a new value for this CustomField. Takes a paramhash containing the elements Name, Description and SortOrder
627 unless ($self->CurrentUserHasRight('AdminCustomField') || $self->CurrentUserHasRight('AdminCustomFieldValues')) {
628 return (0, $self->loc('Permission Denied'));
632 if ( !defined $args{'Name'} || $args{'Name'} eq '' ) {
633 return (0, $self->loc("Can't add a custom field value without a name"));
636 my $newval = RT::CustomFieldValue->new( $self->CurrentUser );
637 return $newval->Create( %args, CustomField => $self->Id );
643 =head3 DeleteValue ID
645 Deletes a value from this custom field by id.
647 Does not remove this value for any article which has had it selected
654 unless ( $self->CurrentUserHasRight('AdminCustomField') || $self->CurrentUserHasRight('AdminCustomFieldValues') ) {
655 return (0, $self->loc('Permission Denied'));
658 my $val_to_del = RT::CustomFieldValue->new( $self->CurrentUser );
659 $val_to_del->Load( $id );
660 unless ( $val_to_del->Id ) {
661 return (0, $self->loc("Couldn't find that value"));
663 unless ( $val_to_del->CustomField == $self->Id ) {
664 return (0, $self->loc("That is not a value for this custom field"));
667 my $retval = $val_to_del->Delete;
669 return (0, $self->loc("Custom field value could not be deleted"));
671 return ($retval, $self->loc("Custom field value deleted"));
675 =head2 ValidateQueue Queue
677 Make sure that the name specified is valid
685 return 0 unless length $value;
687 return $self->SUPER::ValidateName($value);
690 =head2 ValidateQueue Queue
692 Make sure that the queue specified is a valid queue name
700 return undef unless defined $id;
701 # 0 means "Global" null would _not_ be ok.
702 return 1 if $id eq '0';
704 my $q = RT::Queue->new( RT->SystemUser );
706 return undef unless $q->id;
714 Retuns an array of the types of CustomField that are supported
719 return (sort {(($FieldTypes{$a}{sort_order}||999) <=> ($FieldTypes{$b}{sort_order}||999)) or ($a cmp $b)} keys %FieldTypes);
723 =head2 IsSelectionType
725 Retuns a boolean value indicating whether the C<Values> method makes sense
726 to this Custom Field.
730 sub IsSelectionType {
732 my $type = @_? shift : $self->Type;
733 return undef unless $type;
734 return $FieldTypes{$type}->{selection_type};
739 =head2 IsExternalValues
743 sub IsExternalValues {
745 return 0 unless $self->IsSelectionType( @_ );
746 return $self->ValuesClass eq 'RT::CustomFieldValues'? 0 : 1;
751 return $self->_Value( ValuesClass => @_ ) || 'RT::CustomFieldValues';
756 my $class = shift || 'RT::CustomFieldValues';
758 if ( $class eq 'RT::CustomFieldValues' ) {
759 return $self->_Set( Field => 'ValuesClass', Value => undef, @_ );
762 return (0, $self->loc("This Custom Field can not have list of values"))
763 unless $self->IsSelectionType;
765 unless ( $self->ValidateValuesClass( $class ) ) {
766 return (0, $self->loc("Invalid Custom Field values source"));
768 return $self->_Set( Field => 'ValuesClass', Value => $class, @_ );
771 sub ValidateValuesClass {
775 return 1 if !$class || $class eq 'RT::CustomFieldValues';
776 return 1 if grep $class eq $_, RT->Config->Get('CustomFieldValuesSources');
781 =head2 FriendlyType [TYPE, MAX_VALUES]
783 Returns a localized human-readable version of the custom field type.
784 If a custom field type is specified as the parameter, the friendly type for that type will be returned
791 my $type = @_ ? shift : $self->Type;
792 my $max = @_ ? shift : $self->MaxValues;
793 $max = 0 unless $max;
795 if (my $friendly_type = $FieldTypes{$type}->{labels}->[$max>2 ? 2 : $max]) {
796 return ( $self->loc( $friendly_type, $max ) );
799 return ( $self->loc( $type ) );
803 sub FriendlyTypeComposite {
805 my $composite = shift || $self->TypeComposite;
806 return $self->FriendlyType(split(/-/, $composite, 2));
810 =head2 ValidateType TYPE
812 Takes a single string. returns true if that string is a value
822 if ( $type =~ s/(?:Single|Multiple)$// ) {
824 Arguments => "suffix 'Single' or 'Multiple'",
825 Instead => "MaxValues",
830 if ( $FieldTypes{$type} ) {
842 if ($type =~ s/(?:(Single)|Multiple)$//) {
844 Arguments => "suffix 'Single' or 'Multiple'",
845 Instead => "MaxValues",
848 $self->SetMaxValues($1 ? 1 : 0);
850 $self->_Set(Field => 'Type', Value =>$type);
853 =head2 SetPattern STRING
855 Takes a single string representing a regular expression. Performs basic
856 validation on that regex, and sets the C<Pattern> field for the CF if it
865 my ($ok, $msg) = $self->_IsValidRegex($regex);
867 return $self->_Set(Field => 'Pattern', Value => $regex);
870 return (0, $self->loc("Invalid pattern: [_1]", $msg));
874 =head2 _IsValidRegex(Str $regex) returns (Bool $success, Str $msg)
876 Tests if the string contains an invalid regex.
882 my $regex = shift or return (1, 'valid');
885 local $SIG{__DIE__} = sub { 1 };
886 local $SIG{__WARN__} = sub { 1 };
888 if (eval { qr/$regex/; 1 }) {
893 $err =~ s{[,;].*}{}; # strip debug info from error
901 Returns true if this CustomField only accepts a single value.
902 Returns false if it accepts multiple values
908 if (($self->MaxValues||0) == 1) {
916 sub UnlimitedValues {
918 if (($self->MaxValues||0) == 0) {
927 =head2 ACLEquivalenceObjects
929 Returns list of objects via which users can get rights on this custom field. For custom fields
930 these objects can be set using L<ContextObject|/"ContextObject and SetContextObject">.
934 sub ACLEquivalenceObjects {
937 my $ctx = $self->ContextObject
939 return ($ctx, $ctx->ACLEquivalenceObjects);
942 =head2 ContextObject and SetContextObject
944 Set or get a context for this object. It can be ticket, queue or another
945 object this CF added to. Used for ACL control, for example
946 SeeCustomField can be granted on queue level to allow people to see all
947 fields added to the queue.
951 sub SetContextObject {
953 return $self->{'context_object'} = shift;
958 return $self->{'context_object'};
961 sub ValidContextType {
966 $valid{$_}++ for split '-', $self->LookupType;
967 delete $valid{'RT::Transaction'};
969 return $valid{$class};
972 =head2 LoadContextObject
974 Takes an Id for a Context Object and loads the right kind of RT::Object
975 for this particular Custom Field (based on the LookupType) and returns it.
976 This is a good way to ensure you don't try to use a Queue as a Context
977 Object on a User Custom Field.
981 sub LoadContextObject {
984 my $contextid = shift;
986 unless ( $self->ValidContextType($type) ) {
987 RT->Logger->debug("Invalid ContextType $type for Custom Field ".$self->Id);
991 my $context_object = $type->new( $self->CurrentUser );
992 my ($id, $msg) = $context_object->LoadById( $contextid );
994 RT->Logger->debug("Invalid ContextObject id: $msg");
997 return $context_object;
1000 =head2 ValidateContextObject
1002 Ensure that a given ContextObject applies to this Custom Field. For
1003 custom fields that are assigned to Queues or to Classes, this checks
1004 that the Custom Field is actually added to that object. For Global
1005 Custom Fields, it returns true as long as the Object is of the right
1006 type, because you may be using your permissions on a given Queue of
1007 Class to see a Global CF. For CFs that are only added globally, you
1008 don't need a ContextObject.
1012 sub ValidateContextObject {
1016 return 1 if $self->IsGlobal;
1018 # global only custom fields don't have objects
1019 # that should be used as context objects.
1020 return if $self->IsOnlyGlobal;
1022 # Otherwise, make sure we weren't passed a user object that we're
1023 # supposed to treat as a queue.
1024 return unless $self->ValidContextType(ref $object);
1026 # Check that it is added correctly
1027 my ($added_to) = grep {ref($_) eq $self->RecordClassFromLookupType} ($object, $object->ACLEquivalenceObjects);
1028 return unless $added_to;
1029 return $self->IsAdded($added_to->id);
1036 unless ( $self->CurrentUserHasRight('AdminCustomField') ) {
1037 return ( 0, $self->loc('Permission Denied') );
1039 return $self->SUPER::_Set( @_ );
1047 Takes the name of a table column.
1048 Returns its value as a string, if the user passes an ACL check
1054 return undef unless $self->id;
1056 # we need to do the rights check
1057 unless ( $self->CurrentUserHasRight('SeeCustomField') ) {
1059 "Permission denied. User #". $self->CurrentUser->id
1060 ." has no SeeCustomField right on CF #". $self->id
1064 return $self->__Value( @_ );
1071 1 will cause this custom field to no longer be avaialble for objects.
1072 0 will re-enable this field.
1077 =head2 SetTypeComposite
1079 Set this custom field's type and maximum values as a composite value
1083 sub SetTypeComposite {
1085 my $composite = shift;
1087 my $old = $self->TypeComposite;
1089 my ($type, $max_values) = split(/-/, $composite, 2);
1090 if ( $type ne $self->Type ) {
1091 my ($status, $msg) = $self->SetType( $type );
1092 return ($status, $msg) unless $status;
1094 if ( ($max_values || 0) != ($self->MaxValues || 0) ) {
1095 my ($status, $msg) = $self->SetMaxValues( $max_values );
1096 return ($status, $msg) unless $status;
1098 my $render = $self->RenderType;
1099 if ( $render and not grep { $_ eq $render } $self->RenderTypes ) {
1100 # We switched types and our render type is no longer valid, so unset it
1101 # and use the default
1102 $self->SetRenderType( undef );
1104 return 1, $self->loc(
1105 "Type changed from '[_1]' to '[_2]'",
1106 $self->FriendlyTypeComposite( $old ),
1107 $self->FriendlyTypeComposite( $composite ),
1111 =head2 TypeComposite
1113 Returns a composite value composed of this object's type and maximum values
1120 return join '-', ($self->Type || ''), ($self->MaxValues || 0);
1123 =head2 TypeComposites
1125 Returns an array of all possible composite values for custom fields.
1129 sub TypeComposites {
1131 return grep !/(?:[Tt]ext|Combobox|Date|DateTime)-0/, map { ("$_-1", "$_-0") } $self->Types;
1136 Returns the type of form widget to render for this custom field. Currently
1137 this only affects fields which return true for L</HasRenderTypes>.
1143 return '' unless $self->HasRenderTypes;
1145 return $self->_Value( 'RenderType', @_ )
1146 || $self->DefaultRenderType;
1149 =head2 SetRenderType TYPE
1151 Sets this custom field's render type.
1158 return (0, $self->loc("This custom field has no Render Types"))
1159 unless $self->HasRenderTypes;
1161 if ( !$type || $type eq $self->DefaultRenderType ) {
1162 return $self->_Set( Field => 'RenderType', Value => undef, @_ );
1165 if ( not grep { $_ eq $type } $self->RenderTypes ) {
1166 return (0, $self->loc("Invalid Render Type for custom field of type [_1]",
1167 $self->FriendlyType));
1170 return $self->_Set( Field => 'RenderType', Value => $type, @_ );
1173 =head2 DefaultRenderType [TYPE COMPOSITE]
1175 Returns the default render type for this custom field's type or the TYPE
1176 COMPOSITE specified as an argument.
1180 sub DefaultRenderType {
1182 my $composite = @_ ? shift : $self->TypeComposite;
1183 my ($type, $max) = split /-/, $composite, 2;
1184 return unless $type and $self->HasRenderTypes($composite);
1185 return $FieldTypes{$type}->{render_types}->{ $max == 1 ? 'single' : 'multiple' }[0];
1188 =head2 HasRenderTypes [TYPE_COMPOSITE]
1190 Returns a boolean value indicating whether the L</RenderTypes> and
1191 L</RenderType> methods make sense for this custom field.
1193 Currently true only for type C<Select>.
1197 sub HasRenderTypes {
1199 my ($type, $max) = split /-/, (@_ ? shift : $self->TypeComposite), 2;
1200 return undef unless $type;
1201 return defined $FieldTypes{$type}->{render_types}
1202 ->{ $max == 1 ? 'single' : 'multiple' };
1205 =head2 RenderTypes [TYPE COMPOSITE]
1207 Returns the valid render types for this custom field's type or the TYPE
1208 COMPOSITE specified as an argument.
1214 my $composite = @_ ? shift : $self->TypeComposite;
1215 my ($type, $max) = split /-/, $composite, 2;
1216 return unless $type and $self->HasRenderTypes($composite);
1217 return @{$FieldTypes{$type}->{render_types}->{ $max == 1 ? 'single' : 'multiple' }};
1220 =head2 SetLookupType
1222 Autrijus: care to doc how LookupTypes work?
1229 if ( $lookup ne $self->LookupType ) {
1230 # Okay... We need to invalidate our existing relationships
1231 RT::ObjectCustomField->new($self->CurrentUser)->DeleteAll( CustomField => $self );
1233 return $self->_Set(Field => 'LookupType', Value =>$lookup);
1238 Returns an array of LookupTypes available
1245 return sort keys %FRIENDLY_LOOKUP_TYPES;
1248 =head2 FriendlyLookupType
1250 Returns a localized description of the type of this custom field
1254 sub FriendlyLookupType {
1256 my $lookup = shift || $self->LookupType;
1258 return ($self->loc( $FRIENDLY_LOOKUP_TYPES{$lookup} ))
1259 if defined $FRIENDLY_LOOKUP_TYPES{$lookup};
1261 my @types = map { s/^RT::// ? $self->loc($_) : $_ }
1262 grep { defined and length }
1263 split( /-/, $lookup )
1266 state $LocStrings = [
1267 "[_1] objects", # loc
1268 "[_1]'s [_2] objects", # loc
1269 "[_1]'s [_2]'s [_3] objects", # loc
1271 return ( $self->loc( $LocStrings->[$#types], @types ) );
1274 =head1 RecordClassFromLookupType
1276 Returns the type of Object referred to by ObjectCustomFields' ObjectId column
1278 Optionally takes a LookupType to use instead of using the value on the loaded
1279 record. In this case, the method may be called on the class instead of an
1284 sub RecordClassFromLookupType {
1286 my $type = shift || $self->LookupType;
1287 my ($class) = ($type =~ /^([^-]+)/);
1289 if (blessed($self) and $self->LookupType eq $type) {
1291 "Custom Field #". $self->id
1292 ." has incorrect LookupType '$type'"
1295 RT->Logger->error("Invalid LookupType passed as argument: $type");
1302 =head1 ObjectTypeFromLookupType
1304 Returns the ObjectType used in ObjectCustomFieldValues rows for this CF
1306 Optionally takes a LookupType to use instead of using the value on the loaded
1307 record. In this case, the method may be called on the class instead of an
1312 sub ObjectTypeFromLookupType {
1314 my $type = shift || $self->LookupType;
1315 my ($class) = ($type =~ /([^-]+)$/);
1317 if (blessed($self) and $self->LookupType eq $type) {
1319 "Custom Field #". $self->id
1320 ." has incorrect LookupType '$type'"
1323 RT->Logger->error("Invalid LookupType passed as argument: $type");
1330 sub CollectionClassFromLookupType {
1333 my $record_class = $self->RecordClassFromLookupType;
1334 return undef unless $record_class;
1336 my $collection_class;
1337 if ( UNIVERSAL::can($record_class.'Collection', 'new') ) {
1338 $collection_class = $record_class.'Collection';
1339 } elsif ( UNIVERSAL::can($record_class.'es', 'new') ) {
1340 $collection_class = $record_class.'es';
1341 } elsif ( UNIVERSAL::can($record_class.'s', 'new') ) {
1342 $collection_class = $record_class.'s';
1344 $RT::Logger->error("Can not find a collection class for record class '$record_class'");
1347 return $collection_class;
1352 Returns a (sorted and lowercased) list of the groupings in which this custom
1355 If called on a loaded object, the returned list is limited to groupings which
1356 apply to the record class this CF applies to (L</RecordClassFromLookupType>).
1358 If passed a loaded object or a class name, the returned list is limited to
1359 groupings which apply to the class of the object or the specified class.
1361 If called on an unloaded object, all potential groupings are returned.
1367 my $record_class = $self->_GroupingClass(shift);
1369 my $config = RT->Config->Get('CustomFieldGroupings');
1370 $config = {} unless ref($config) eq 'HASH';
1373 if ( $record_class ) {
1374 push @groups, sort {lc($a) cmp lc($b)} keys %{ $BUILTIN_GROUPINGS{$record_class} || {} };
1375 if ( ref($config->{$record_class} ||= []) eq "ARRAY") {
1376 my @order = @{ $config->{$record_class} };
1378 push @groups, shift(@order);
1382 @groups = sort {lc($a) cmp lc($b)} keys %{ $config->{$record_class} };
1385 my %all = (%$config, %BUILTIN_GROUPINGS);
1386 @groups = sort {lc($a) cmp lc($b)} map {$self->Groupings($_)} grep {$_} keys(%all);
1391 grep defined && length && !$seen{lc $_}++,
1395 =head2 CustomGroupings
1397 Identical to L</Groupings> but filters out built-in groupings from the the
1402 sub CustomGroupings {
1404 my $record_class = $self->_GroupingClass(shift);
1405 return grep !$BUILTIN_GROUPINGS{$record_class}{$_}, $self->Groupings( $record_class );
1408 sub _GroupingClass {
1412 my $record_class = ref($record) || $record || '';
1413 $record_class = $self->RecordClassFromLookupType
1414 if !$record_class and blessed($self) and $self->id;
1416 return $record_class;
1419 =head2 RegisterBuiltInGroupings
1421 Registers groupings to be considered a fundamental part of RT, either via use
1422 in core RT or via an extension. These groupings must be rendered explicitly in
1423 Mason by specific calls to F</Elements/ShowCustomFields> and
1424 F</Elements/EditCustomFields>. They will not show up automatically on normal
1425 display pages like configured custom groupings.
1427 Takes a set of key-value pairs of class names (valid L<RT::Record> subclasses)
1428 and array refs of grouping names to consider built-in.
1430 If a class already contains built-in groupings (such as L<RT::Ticket> and
1431 L<RT::User>), new groupings are appended.
1435 sub RegisterBuiltInGroupings {
1439 while (my ($k,$v) = each %new) {
1440 $v = [$v] unless ref($v) eq 'ARRAY';
1441 $BUILTIN_GROUPINGS{$k} = {
1442 %{$BUILTIN_GROUPINGS{$k} || {}},
1446 $BUILTIN_GROUPINGS{''} = { map { %$_ } values %BUILTIN_GROUPINGS };
1451 Certain custom fields (users, groups) should only be added globally;
1452 codify that set here for reference.
1459 return ($self->LookupType =~ /^RT::(?:Group|User)/io);
1464 Instead => "IsOnlyGlobal",
1467 return shift->IsOnlyGlobal(@_);
1472 Returns collection with objects this custom field is added to.
1473 Class of the collection depends on L</LookupType>.
1474 See all L</NotAddedTo> .
1476 Doesn't takes into account if object is added globally.
1482 return RT::ObjectCustomField->new( $self->CurrentUser )
1483 ->AddedTo( CustomField => $self );
1487 Instead => "AddedTo",
1495 Returns collection with objects this custom field is not added to.
1496 Class of the collection depends on L</LookupType>.
1497 See all L</AddedTo> .
1499 Doesn't take into account if the object is added globally.
1505 return RT::ObjectCustomField->new( $self->CurrentUser )
1506 ->NotAddedTo( CustomField => $self );
1510 Instead => "NotAddedTo",
1513 shift->NotAddedTo(@_)
1518 Takes object id and returns corresponding L<RT::ObjectCustomField>
1519 record if this custom field is added to the object. Use 0 to check
1520 if custom field is added globally.
1527 my $ocf = RT::ObjectCustomField->new( $self->CurrentUser );
1528 $ocf->LoadByCols( CustomField => $self->id, ObjectId => $id || 0 );
1529 return undef unless $ocf->id;
1534 Instead => "IsAdded",
1540 sub IsGlobal { return shift->IsAdded(0) }
1544 Returns true if custom field is applied to any object.
1551 my $ocf = RT::ObjectCustomField->new( $self->CurrentUser );
1552 $ocf->LoadByCols( CustomField => $self->id );
1553 return $ocf->id ? 1 : 0;
1556 =head2 AddToObject OBJECT
1558 Add this custom field as a custom field for a single object, such as a queue or group.
1567 my $id = $object->Id || 0;
1569 unless (index($self->LookupType, ref($object)) == 0) {
1570 return ( 0, $self->loc('Lookup type mismatch') );
1573 unless ( $object->CurrentUserHasRight('AssignCustomFields') ) {
1574 return ( 0, $self->loc('Permission Denied') );
1577 my $ocf = RT::ObjectCustomField->new( $self->CurrentUser );
1578 my ( $oid, $msg ) = $ocf->Add(
1579 CustomField => $self->id, ObjectId => $id,
1581 return ( $oid, $msg );
1585 =head2 RemoveFromObject OBJECT
1587 Remove this custom field for a single object, such as a queue or group.
1593 sub RemoveFromObject {
1596 my $id = $object->Id || 0;
1598 unless (index($self->LookupType, ref($object)) == 0) {
1599 return ( 0, $self->loc('Object type mismatch') );
1602 unless ( $object->CurrentUserHasRight('AssignCustomFields') ) {
1603 return ( 0, $self->loc('Permission Denied') );
1606 my $ocf = $self->IsAdded( $id );
1608 return ( 0, $self->loc("This custom field cannot be added to that object") );
1611 # XXX: Delete doesn't return anything
1612 my ( $oid, $msg ) = $ocf->Delete;
1613 return ( $oid, $msg );
1617 =head2 AddValueForObject HASH
1619 Adds a custom field value for a record object of some kind.
1620 Takes a param hash of
1634 sub AddValueForObject {
1639 LargeContent => undef,
1640 ContentType => undef,
1643 my $obj = $args{'Object'} or return ( 0, $self->loc('Invalid object') );
1645 unless ( $self->CurrentUserHasRight('ModifyCustomField') ) {
1646 return ( 0, $self->loc('Permission Denied') );
1649 unless ( $self->MatchPattern($args{'Content'}) ) {
1650 return ( 0, $self->loc('Input must match [_1]', $self->FriendlyPattern) );
1653 $RT::Handle->BeginTransaction;
1655 if ( $self->MaxValues ) {
1656 my $current_values = $self->ValuesForObject($obj);
1657 my $extra_values = ( $current_values->Count + 1 ) - $self->MaxValues;
1659 # (The +1 is for the new value we're adding)
1661 # If we have a set of current values and we've gone over the maximum
1662 # allowed number of values, we'll need to delete some to make room.
1663 # which former values are blown away is not guaranteed
1665 while ($extra_values) {
1666 my $extra_item = $current_values->Next;
1667 unless ( $extra_item->id ) {
1668 $RT::Logger->crit( "We were just asked to delete "
1669 ."a custom field value that doesn't exist!" );
1670 $RT::Handle->Rollback();
1673 $extra_item->Delete;
1678 my $newval = RT::ObjectCustomFieldValue->new( $self->CurrentUser );
1679 my ($val, $msg) = $newval->Create(
1680 ObjectType => ref($obj),
1681 ObjectId => $obj->Id,
1682 Content => $args{'Content'},
1683 LargeContent => $args{'LargeContent'},
1684 ContentType => $args{'ContentType'},
1685 CustomField => $self->Id
1689 $RT::Handle->Rollback();
1690 return ($val, $self->loc("Couldn't create record: [_1]", $msg));
1693 $RT::Handle->Commit();
1699 sub _CanonicalizeValue {
1703 my $type = $self->_Value('Type');
1704 return 1 unless $type;
1706 my $method = '_CanonicalizeValue'. $type;
1707 return 1 unless $self->can($method);
1708 $self->$method($args);
1711 sub _CanonicalizeValueDateTime {
1714 my $DateObj = RT::Date->new( $self->CurrentUser );
1715 $DateObj->Set( Format => 'unknown',
1716 Value => $args->{'Content'} );
1717 $args->{'Content'} = $DateObj->ISO;
1721 # For date, we need to store Content as ISO date
1722 sub _CanonicalizeValueDate {
1726 # in case user input date with time, let's omit it by setting timezone
1727 # to utc so "hour" won't affect "day"
1728 my $DateObj = RT::Date->new( $self->CurrentUser );
1729 $DateObj->Set( Format => 'unknown',
1730 Value => $args->{'Content'},
1732 $args->{'Content'} = $DateObj->Date( Timezone => 'user' );
1736 sub _CanonicalizeValueIPAddress {
1740 $args->{Content} = RT::ObjectCustomFieldValue->ParseIP( $args->{Content} );
1741 return (0, $self->loc("Content is not a valid IP address"))
1742 unless $args->{Content};
1746 sub _CanonicalizeValueIPAddressRange {
1750 my $content = $args->{Content};
1751 $content .= "-".$args->{LargeContent} if $args->{LargeContent};
1753 ($args->{Content}, $args->{LargeContent})
1754 = RT::ObjectCustomFieldValue->ParseIPRange( $content );
1756 $args->{ContentType} = 'text/plain';
1757 return (0, $self->loc("Content is not a valid IP address range"))
1758 unless $args->{Content};
1762 =head2 MatchPattern STRING
1764 Tests the incoming string against the Pattern of this custom field object
1765 and returns a boolean; returns true if the Pattern is empty.
1771 my $regex = $self->Pattern or return 1;
1773 return (( defined $_[0] ? $_[0] : '') =~ $regex);
1779 =head2 FriendlyPattern
1781 Prettify the pattern of this custom field, by taking the text in C<(?#text)>
1786 sub FriendlyPattern {
1788 my $regex = $self->Pattern;
1790 return '' unless length $regex;
1791 if ( $regex =~ /\(\?#([^)]*)\)/ ) {
1792 return '[' . $self->loc($1) . ']';
1802 =head2 DeleteValueForObject HASH
1804 Deletes a custom field value for a ticket. Takes a param hash of Object and Content
1806 Returns a tuple of (STATUS, MESSAGE). If the call succeeded, the STATUS is true. otherwise it's false
1810 sub DeleteValueForObject {
1812 my %args = ( Object => undef,
1818 unless ($self->CurrentUserHasRight('ModifyCustomField')) {
1819 return (0, $self->loc('Permission Denied'));
1822 my $oldval = RT::ObjectCustomFieldValue->new($self->CurrentUser);
1824 if (my $id = $args{'Id'}) {
1827 unless ($oldval->id) {
1828 $oldval->LoadByObjectContentAndCustomField(
1829 Object => $args{'Object'},
1830 Content => $args{'Content'},
1831 CustomField => $self->Id,
1836 # check to make sure we found it
1837 unless ($oldval->Id) {
1838 return(0, $self->loc("Custom field value [_1] could not be found for custom field [_2]", $args{'Content'}, $self->Name));
1841 # for single-value fields, we need to validate that empty string is a valid value for it
1842 if ( $self->SingleValue and not $self->MatchPattern( '' ) ) {
1843 return ( 0, $self->loc('Input must match [_1]', $self->FriendlyPattern) );
1848 my $ret = $oldval->Delete();
1850 return(0, $self->loc("Custom field value could not be found"));
1852 return($oldval->Id, $self->loc("Custom field value deleted"));
1856 =head2 ValuesForObject OBJECT
1858 Return an L<RT::ObjectCustomFieldValues> object containing all of this custom field's values for OBJECT
1862 sub ValuesForObject {
1866 my $values = RT::ObjectCustomFieldValues->new($self->CurrentUser);
1867 unless ($self->id and $self->CurrentUserHasRight('SeeCustomField')) {
1868 # Return an empty object if they have no rights to see
1869 $values->Limit( FIELD => "id", VALUE => 0, SUBCLAUSE => "ACL" );
1873 $values->LimitToCustomField($self->Id);
1874 $values->LimitToObject($object);
1880 =head2 RegisterLookupType LOOKUPTYPE FRIENDLYNAME
1882 Tell RT that a certain object accepts custom fields via a lookup type and
1883 provide a friendly name for such CFs.
1887 'RT::Queue-RT::Ticket' => "Tickets", # loc
1888 'RT::Queue-RT::Ticket-RT::Transaction' => "Ticket Transactions", # loc
1889 'RT::User' => "Users", # loc
1890 'RT::Group' => "Groups", # loc
1891 'RT::Queue' => "Queues", # loc
1893 This is a class method.
1897 sub RegisterLookupType {
1900 my $friendly_name = shift;
1902 $FRIENDLY_LOOKUP_TYPES{$path} = $friendly_name;
1905 sub _ForObjectType {
1907 Instead => 'RegisterLookupType',
1911 $self->RegisterLookupType(@_);
1915 =head2 IncludeContentForValue [VALUE] (and SetIncludeContentForValue)
1917 Gets or sets the C<IncludeContentForValue> for this custom field. RT
1918 uses this field to automatically include content into the user's browser
1919 as they display records with custom fields in RT.
1923 sub SetIncludeContentForValue {
1924 shift->IncludeContentForValue(@_);
1926 sub IncludeContentForValue{
1928 $self->_URLTemplate('IncludeContentForValue', @_);
1933 =head2 LinkValueTo [VALUE] (and SetLinkValueTo)
1935 Gets or sets the C<LinkValueTo> for this custom field. RT
1936 uses this field to make custom field values into hyperlinks in the user's
1937 browser as they display records with custom fields in RT.
1942 sub SetLinkValueTo {
1943 shift->LinkValueTo(@_);
1948 $self->_URLTemplate('LinkValueTo', @_);
1953 =head2 _URLTemplate NAME [VALUE]
1955 With one argument, returns the _URLTemplate named C<NAME>, but only if
1956 the current user has the right to see this custom field.
1958 With two arguments, attemptes to set the relevant template value.
1964 my $template_name = shift;
1968 unless ( $self->CurrentUserHasRight('AdminCustomField') ) {
1969 return ( 0, $self->loc('Permission Denied') );
1971 if (length $value and defined $value) {
1972 $self->SetAttribute( Name => $template_name, Content => $value );
1974 $self->DeleteAttribute( $template_name );
1976 return ( 1, $self->loc('Updated') );
1978 unless ( $self->id && $self->CurrentUserHasRight('SeeCustomField') ) {
1982 my ($attr) = $self->Attributes->Named($template_name);
1983 return undef unless $attr;
1984 return $attr->Content;
1992 return $self->_Set( Field => 'BasedOn', Value => $value, @_ )
1993 unless defined $value and length $value;
1995 my $cf = RT::CustomField->new( $self->CurrentUser );
1996 $cf->SetContextObject( $self->ContextObject );
1997 $cf->Load( ref $value ? $value->id : $value );
1999 return (0, "Permission Denied")
2000 unless $cf->id && $cf->CurrentUserHasRight('SeeCustomField');
2002 # XXX: Remove this restriction once we support lists and cascaded selects
2003 if ( $self->RenderType =~ /List/ ) {
2004 return (0, $self->loc("We can't currently render as a List when basing categories on another custom field. Please use another render type."));
2007 return $self->_Set( Field => 'BasedOn', Value => $value, @_ )
2013 my $obj = RT::CustomField->new( $self->CurrentUser );
2014 $obj->SetContextObject( $self->ContextObject );
2015 if ( $self->BasedOn ) {
2016 $obj->Load( $self->BasedOn );
2028 Returns the current value of id.
2029 (In the database, id is stored as int(11).)
2037 Returns the current value of Name.
2038 (In the database, Name is stored as varchar(200).)
2042 =head2 SetName VALUE
2046 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2047 (In the database, Name will be stored as a varchar(200).)
2055 Returns the current value of Type.
2056 (In the database, Type is stored as varchar(200).)
2060 =head2 SetType VALUE
2064 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2065 (In the database, Type will be stored as a varchar(200).)
2073 Returns the current value of RenderType.
2074 (In the database, RenderType is stored as varchar(64).)
2078 =head2 SetRenderType VALUE
2081 Set RenderType to VALUE.
2082 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2083 (In the database, RenderType will be stored as a varchar(64).)
2091 Returns the current value of MaxValues.
2092 (In the database, MaxValues is stored as int(11).)
2096 =head2 SetMaxValues VALUE
2099 Set MaxValues to VALUE.
2100 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2101 (In the database, MaxValues will be stored as a int(11).)
2109 Returns the current value of Pattern.
2110 (In the database, Pattern is stored as text.)
2114 =head2 SetPattern VALUE
2117 Set Pattern to VALUE.
2118 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2119 (In the database, Pattern will be stored as a text.)
2127 Returns the current value of BasedOn.
2128 (In the database, BasedOn is stored as int(11).)
2132 =head2 SetBasedOn VALUE
2135 Set BasedOn to VALUE.
2136 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2137 (In the database, BasedOn will be stored as a int(11).)
2145 Returns the current value of Description.
2146 (In the database, Description is stored as varchar(255).)
2150 =head2 SetDescription VALUE
2153 Set Description to VALUE.
2154 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2155 (In the database, Description will be stored as a varchar(255).)
2163 Returns the current value of SortOrder.
2164 (In the database, SortOrder is stored as int(11).)
2168 =head2 SetSortOrder VALUE
2171 Set SortOrder to VALUE.
2172 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2173 (In the database, SortOrder will be stored as a int(11).)
2181 Returns the current value of LookupType.
2182 (In the database, LookupType is stored as varchar(255).)
2186 =head2 SetLookupType VALUE
2189 Set LookupType to VALUE.
2190 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2191 (In the database, LookupType will be stored as a varchar(255).)
2199 Returns the current value of Creator.
2200 (In the database, Creator is stored as int(11).)
2208 Returns the current value of Created.
2209 (In the database, Created is stored as datetime.)
2215 =head2 LastUpdatedBy
2217 Returns the current value of LastUpdatedBy.
2218 (In the database, LastUpdatedBy is stored as int(11).)
2226 Returns the current value of LastUpdated.
2227 (In the database, LastUpdated is stored as datetime.)
2235 Returns the current value of Disabled.
2236 (In the database, Disabled is stored as smallint(6).)
2240 =head2 SetDisabled VALUE
2243 Set Disabled to VALUE.
2244 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2245 (In the database, Disabled will be stored as a smallint(6).)
2252 sub _CoreAccessible {
2256 {read => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''},
2258 {read => 1, write => 1, sql_type => 12, length => 200, is_blob => 0, is_numeric => 0, type => 'varchar(200)', default => ''},
2260 {read => 1, write => 1, sql_type => 12, length => 200, is_blob => 0, is_numeric => 0, type => 'varchar(200)', default => ''},
2262 {read => 1, write => 1, sql_type => 12, length => 64, is_blob => 0, is_numeric => 0, type => 'varchar(64)', default => ''},
2264 {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''},
2266 {read => 1, write => 1, sql_type => -4, length => 0, is_blob => 1, is_numeric => 0, type => 'text', default => ''},
2268 {read => 1, write => 1, sql_type => 12, length => 64, is_blob => 0, is_numeric => 0, type => 'varchar(64)', default => ''},
2270 {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''},
2272 {read => 1, write => 1, sql_type => 12, length => 255, is_blob => 0, is_numeric => 0, type => 'varchar(255)', default => ''},
2274 {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
2276 {read => 1, write => 1, sql_type => 12, length => 255, is_blob => 0, is_numeric => 0, type => 'varchar(255)', default => ''},
2278 {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
2280 {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''},
2282 {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
2284 {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''},
2286 {read => 1, write => 1, sql_type => 5, length => 6, is_blob => 0, is_numeric => 1, type => 'smallint(6)', default => '0'},
2291 sub FindDependencies {
2293 my ($walker, $deps) = @_;
2295 $self->SUPER::FindDependencies($walker, $deps);
2297 $deps->Add( out => $self->BasedOnObj )
2298 if $self->BasedOnObj->id;
2300 my $applied = RT::ObjectCustomFields->new( $self->CurrentUser );
2301 $applied->LimitToCustomField( $self->id );
2302 $deps->Add( in => $applied );
2304 $deps->Add( in => $self->Values ) if $self->ValuesClass eq "RT::CustomFieldValues";
2308 RT::Base->_ImportOverlays();