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;
54 use Scalar::Util 'blessed';
56 use base 'RT::Record';
58 sub Table {'CustomFields'}
61 use RT::CustomFieldValues;
62 use RT::ObjectCustomFields;
63 use RT::ObjectCustomFieldValues;
70 labels => [ 'Select multiple values', # loc
71 'Select one value', # loc
72 'Select up to [_1] values', # loc
78 # Default is the first one
82 single => [ 'Select box', # loc
93 labels => [ 'Enter multiple values', # loc
94 'Enter one value', # loc
95 'Enter up to [_1] values', # loc
102 'Fill in multiple text areas', # loc
103 'Fill in one text area', # loc
104 'Fill in up to [_1] text areas', # loc
111 'Fill in multiple wikitext areas', # loc
112 'Fill in one wikitext area', # loc
113 'Fill in up to [_1] wikitext areas', # loc
121 'Upload multiple images', # loc
122 'Upload one image', # loc
123 'Upload up to [_1] images', # loc
130 'Upload multiple files', # loc
131 'Upload one file', # loc
132 'Upload up to [_1] files', # loc
140 'Combobox: Select or enter multiple values', # loc
141 'Combobox: Select or enter one value', # loc
142 'Combobox: Select or enter up to [_1] values', # loc
149 'Enter multiple values with autocompletion', # loc
150 'Enter one value with autocompletion', # loc
151 'Enter up to [_1] values with autocompletion', # loc
159 'Select multiple dates', # loc
161 'Select up to [_1] dates', # loc
168 'Select multiple datetimes', # loc
169 'Select datetime', # loc
170 'Select up to [_1] datetimes', # loc
177 'Enter multiple time values (UNSUPPORTED)',
178 'Enter a time value',
179 'Enter [_1] time values (UNSUPPORTED)',
187 labels => [ 'Enter multiple IP addresses', # loc
188 'Enter one IP address', # loc
189 'Enter up to [_1] IP addresses', # loc
196 labels => [ 'Enter multiple IP address ranges', # loc
197 'Enter one IP address range', # loc
198 'Enter up to [_1] IP address ranges', # loc
204 our %FRIENDLY_OBJECT_TYPES = ();
206 RT::CustomField->_ForObjectType( 'RT::Queue-RT::Ticket' => "Tickets", ); #loc
207 RT::CustomField->_ForObjectType(
208 'RT::Queue-RT::Ticket-RT::Transaction' => "Ticket Transactions", ); #loc
209 RT::CustomField->_ForObjectType( 'RT::User' => "Users", ); #loc
210 RT::CustomField->_ForObjectType( 'RT::Queue' => "Queues", ); #loc
211 RT::CustomField->_ForObjectType( 'RT::Group' => "Groups", ); #loc
214 SeeCustomField => 'View custom fields', # loc_pair
215 AdminCustomField => 'Create, modify and delete custom fields', # loc_pair
216 AdminCustomFieldValues => 'Create, modify and delete custom fields values', # loc_pair
217 ModifyCustomField => 'Add, modify and delete custom field values for objects' # loc_pair
220 our $RIGHT_CATEGORIES = {
221 SeeCustomField => 'General',
222 AdminCustomField => 'Admin',
223 AdminCustomFieldValues => 'Admin',
224 ModifyCustomField => 'Staff',
227 # Tell RT::ACE that this sort of object can get acls granted
228 $RT::ACE::OBJECT_TYPES{'RT::CustomField'} = 1;
230 __PACKAGE__->AddRights(%$RIGHTS);
231 __PACKAGE__->AddRightCategories(%$RIGHT_CATEGORIES);
233 =head2 AddRights C<RIGHT>, C<DESCRIPTION> [, ...]
235 Adds the given rights to the list of possible rights. This method
236 should be called during server startup, not at runtime.
243 $RIGHTS = { %$RIGHTS, %new };
244 %RT::ACE::LOWERCASERIGHTNAMES = ( %RT::ACE::LOWERCASERIGHTNAMES,
245 map { lc($_) => $_ } keys %new);
248 sub AvailableRights {
253 =head2 RightCategories
255 Returns a hashref where the keys are rights for this type of object and the
256 values are the category (General, Staff, Admin) the right falls into.
260 sub RightCategories {
261 return $RIGHT_CATEGORIES;
264 =head2 AddRightCategories C<RIGHT>, C<CATEGORY> [, ...]
266 Adds the given right and category pairs to the list of right categories. This
267 method should be called during server startup, not at runtime.
271 sub AddRightCategories {
272 my $self = shift if ref $_[0] or $_[0] eq __PACKAGE__;
274 $RIGHT_CATEGORIES = { %$RIGHT_CATEGORIES, %new };
279 RT::CustomField_Overlay - overlay for RT::CustomField
283 =head1 'CORE' METHODS
285 =head2 Create PARAMHASH
287 Create takes a hash of values and creates a row in the database:
292 varchar(255) 'Pattern'.
293 smallint(6) 'Repeated'.
294 varchar(255) 'Description'.
296 varchar(255) 'LookupType'.
297 smallint(6) 'Disabled'.
299 C<LookupType> is generally the result of either
300 C<RT::Ticket->CustomFieldLookupType> or C<RT::Transaction->CustomFieldLookupType>.
316 IncludeContentForValue => '',
320 unless ( $self->CurrentUser->HasRight(Object => $RT::System, Right => 'AdminCustomField') ) {
321 return (0, $self->loc('Permission Denied'));
324 if ( $args{TypeComposite} ) {
325 @args{'Type', 'MaxValues'} = split(/-/, $args{TypeComposite}, 2);
327 elsif ( $args{Type} =~ s/(?:(Single)|Multiple)$// ) {
328 # old style Type string
329 $args{'MaxValues'} = $1 ? 1 : 0;
331 $args{'MaxValues'} = int $args{'MaxValues'};
333 if ( !exists $args{'Queue'}) {
334 # do nothing -- things below are strictly backward compat
336 elsif ( ! $args{'Queue'} ) {
337 unless ( $self->CurrentUser->HasRight( Object => $RT::System, Right => 'AssignCustomFields') ) {
338 return ( 0, $self->loc('Permission Denied') );
340 $args{'LookupType'} = 'RT::Queue-RT::Ticket';
343 my $queue = RT::Queue->new($self->CurrentUser);
344 $queue->Load($args{'Queue'});
345 unless ($queue->Id) {
346 return (0, $self->loc("Queue not found"));
348 unless ( $queue->CurrentUserHasRight('AssignCustomFields') ) {
349 return ( 0, $self->loc('Permission Denied') );
351 $args{'LookupType'} = 'RT::Queue-RT::Ticket';
352 $args{'Queue'} = $queue->Id;
355 my ($ok, $msg) = $self->_IsValidRegex( $args{'Pattern'} );
356 return (0, $self->loc("Invalid pattern: [_1]", $msg)) unless $ok;
358 if ( $args{'MaxValues'} != 1 && $args{'Type'} =~ /(text|combobox)$/i ) {
359 $RT::Logger->debug("Support for 'multiple' Texts or Comboboxes is not implemented");
360 $args{'MaxValues'} = 1;
363 if ( $args{'RenderType'} ||= undef ) {
364 my $composite = join '-', @args{'Type', 'MaxValues'};
365 return (0, $self->loc("This custom field has no Render Types"))
366 unless $self->HasRenderTypes( $composite );
368 if ( $args{'RenderType'} eq $self->DefaultRenderType( $composite ) ) {
369 $args{'RenderType'} = undef;
371 return (0, $self->loc("Invalid Render Type") )
372 unless grep $_ eq $args{'RenderType'}, $self->RenderTypes( $composite );
376 $args{'ValuesClass'} = undef if ($args{'ValuesClass'} || '') eq 'RT::CustomFieldValues';
377 if ( $args{'ValuesClass'} ||= undef ) {
378 return (0, $self->loc("This Custom Field can not have list of values"))
379 unless $self->IsSelectionType( $args{'Type'} );
381 unless ( $self->ValidateValuesClass( $args{'ValuesClass'} ) ) {
382 return (0, $self->loc("Invalid Custom Field values source"));
386 (my $rv, $msg) = $self->SUPER::Create(
387 Name => $args{'Name'},
388 Type => $args{'Type'},
389 RenderType => $args{'RenderType'},
390 MaxValues => $args{'MaxValues'},
391 Pattern => $args{'Pattern'},
392 BasedOn => $args{'BasedOn'},
393 ValuesClass => $args{'ValuesClass'},
394 Description => $args{'Description'},
395 Disabled => $args{'Disabled'},
396 LookupType => $args{'LookupType'},
397 Repeated => $args{'Repeated'},
401 if ( exists $args{'LinkValueTo'}) {
402 $self->SetLinkValueTo($args{'LinkValueTo'});
405 if ( exists $args{'IncludeContentForValue'}) {
406 $self->SetIncludeContentForValue($args{'IncludeContentForValue'});
409 if ( exists $args{'UILocation'} ) {
410 $self->SetUILocation( $args{'UILocation'} );
413 if ( exists $args{'NoClone'} ) {
414 $self->SetNoClone( $args{'NoClone'} );
417 return ($rv, $msg) unless exists $args{'Queue'};
419 # Compat code -- create a new ObjectCustomField mapping
420 my $OCF = RT::ObjectCustomField->new( $self->CurrentUser );
422 CustomField => $self->Id,
423 ObjectId => $args{'Queue'},
432 Load a custom field. If the value handed in is an integer, load by custom field ID. Otherwise, Load by name.
438 my $id = shift || '';
440 if ( $id =~ /^\d+$/ ) {
441 return $self->SUPER::Load( $id );
443 return $self->LoadByName( Name => $id );
449 =head2 LoadByName (Queue => QUEUEID, Name => NAME)
451 Loads the Custom field named NAME.
453 Will load a Disabled Custom Field even if there is a non-disabled Custom Field
456 If a Queue parameter is specified, only look for ticket custom fields tied to that Queue.
458 If the Queue parameter is '0', look for global ticket custom fields.
460 If no queue parameter is specified, look for any and all custom fields with this name.
462 BUG/TODO, this won't let you specify that you only want user or group CFs.
466 # Compatibility for API change after 3.0 beta 1
467 *LoadNameAndQueue = \&LoadByName;
468 # Change after 3.4 beta.
469 *LoadByNameAndQueue = \&LoadByName;
479 unless ( defined $args{'Name'} && length $args{'Name'} ) {
480 $RT::Logger->error("Couldn't load Custom Field without Name");
481 return wantarray ? (0, $self->loc("No name provided")) : 0;
484 # if we're looking for a queue by name, make it a number
485 if ( defined $args{'Queue'} && ($args{'Queue'} =~ /\D/ || !$self->ContextObject) ) {
486 my $QueueObj = RT::Queue->new( $self->CurrentUser );
487 $QueueObj->Load( $args{'Queue'} );
488 $args{'Queue'} = $QueueObj->Id;
489 $self->SetContextObject( $QueueObj )
490 unless $self->ContextObject;
493 # XXX - really naive implementation. Slow. - not really. still just one query
495 my $CFs = RT::CustomFields->new( $self->CurrentUser );
496 $CFs->SetContextObject( $self->ContextObject );
497 my $field = $args{'Name'} =~ /\D/? 'Name' : 'id';
498 $CFs->Limit( FIELD => $field, VALUE => $args{'Name'}, CASESENSITIVE => 0);
499 # Don't limit to queue if queue is 0. Trying to do so breaks
500 # RT::Group type CFs.
501 if ( defined $args{'Queue'} ) {
502 $CFs->LimitToQueue( $args{'Queue'} );
505 # When loading by name, we _can_ load disabled fields, but prefer
506 # non-disabled fields.
509 { FIELD => "Disabled", ORDER => 'ASC' },
512 # We only want one entry.
513 $CFs->RowsPerPage(1);
515 # version before 3.8 just returns 0, so we need to test if wantarray to be
516 # backward compatible.
517 return wantarray ? (0, $self->loc("Not found")) : 0 unless my $first = $CFs->First;
519 return $self->LoadById( $first->id );
525 =head2 Custom field values
529 Return a object (collection) of all acceptable values for this Custom Field.
530 Class of the object can vary and depends on the return value
531 of the C<ValuesClass> method.
535 *ValuesObj = \&Values;
540 my $class = $self->ValuesClass;
541 if ( $class ne 'RT::CustomFieldValues') {
542 eval "require $class" or die "$@";
544 my $cf_values = $class->new( $self->CurrentUser );
545 # if the user has no rights, return an empty object
546 if ( $self->id && $self->CurrentUserHasRight( 'SeeCustomField') ) {
547 $cf_values->LimitToCustomField( $self->Id );
549 $cf_values->Limit( FIELD => 'id', VALUE => 0, SUBCLAUSE => 'acl' );
557 Create a new value for this CustomField. Takes a paramhash containing the elements Name, Description and SortOrder
565 unless ($self->CurrentUserHasRight('AdminCustomField') || $self->CurrentUserHasRight('AdminCustomFieldValues')) {
566 return (0, $self->loc('Permission Denied'));
570 if ( !defined $args{'Name'} || $args{'Name'} eq '' ) {
571 return (0, $self->loc("Can't add a custom field value without a name"));
574 my $newval = RT::CustomFieldValue->new( $self->CurrentUser );
575 return $newval->Create( %args, CustomField => $self->Id );
581 =head3 DeleteValue ID
583 Deletes a value from this custom field by id.
585 Does not remove this value for any article which has had it selected
592 unless ( $self->CurrentUserHasRight('AdminCustomField') || $self->CurrentUserHasRight('AdminCustomFieldValues') ) {
593 return (0, $self->loc('Permission Denied'));
596 my $val_to_del = RT::CustomFieldValue->new( $self->CurrentUser );
597 $val_to_del->Load( $id );
598 unless ( $val_to_del->Id ) {
599 return (0, $self->loc("Couldn't find that value"));
601 unless ( $val_to_del->CustomField == $self->Id ) {
602 return (0, $self->loc("That is not a value for this custom field"));
605 my $retval = $val_to_del->Delete;
607 return (0, $self->loc("Custom field value could not be deleted"));
609 return ($retval, $self->loc("Custom field value deleted"));
613 =head2 ValidateQueue Queue
615 Make sure that the name specified is valid
623 return 0 unless length $value;
625 return $self->SUPER::ValidateName($value);
628 =head2 ValidateQueue Queue
630 Make sure that the queue specified is a valid queue name
638 return undef unless defined $id;
639 # 0 means "Global" null would _not_ be ok.
640 return 1 if $id eq '0';
642 my $q = RT::Queue->new( RT->SystemUser );
644 return undef unless $q->id;
652 Retuns an array of the types of CustomField that are supported
657 return (sort {(($FieldTypes{$a}{sort_order}||999) <=> ($FieldTypes{$b}{sort_order}||999)) or ($a cmp $b)} keys %FieldTypes);
661 =head2 IsSelectionType
663 Retuns a boolean value indicating whether the C<Values> method makes sense
664 to this Custom Field.
668 sub IsSelectionType {
670 my $type = @_? shift : $self->Type;
671 return undef unless $type;
672 return $FieldTypes{$type}->{selection_type};
677 =head2 IsExternalValues
681 sub IsExternalValues {
683 return 0 unless $self->IsSelectionType( @_ );
684 return $self->ValuesClass eq 'RT::CustomFieldValues'? 0 : 1;
689 return $self->_Value( ValuesClass => @_ ) || 'RT::CustomFieldValues';
694 my $class = shift || 'RT::CustomFieldValues';
696 if ( $class eq 'RT::CustomFieldValues' ) {
697 return $self->_Set( Field => 'ValuesClass', Value => undef, @_ );
700 return (0, $self->loc("This Custom Field can not have list of values"))
701 unless $self->IsSelectionType;
703 unless ( $self->ValidateValuesClass( $class ) ) {
704 return (0, $self->loc("Invalid Custom Field values source"));
706 return $self->_Set( Field => 'ValuesClass', Value => $class, @_ );
709 sub ValidateValuesClass {
713 return 1 if !$class || $class eq 'RT::CustomFieldValues';
714 return 1 if grep $class eq $_, RT->Config->Get('CustomFieldValuesSources');
719 =head2 FriendlyType [TYPE, MAX_VALUES]
721 Returns a localized human-readable version of the custom field type.
722 If a custom field type is specified as the parameter, the friendly type for that type will be returned
729 my $type = @_ ? shift : $self->Type;
730 my $max = @_ ? shift : $self->MaxValues;
731 $max = 0 unless $max;
733 if (my $friendly_type = $FieldTypes{$type}->{labels}->[$max>2 ? 2 : $max]) {
734 return ( $self->loc( $friendly_type, $max ) );
737 return ( $self->loc( $type ) );
741 sub FriendlyTypeComposite {
743 my $composite = shift || $self->TypeComposite;
744 return $self->FriendlyType(split(/-/, $composite, 2));
748 =head2 ValidateType TYPE
750 Takes a single string. returns true if that string is a value
760 if ( $type =~ s/(?:Single|Multiple)$// ) {
761 $RT::Logger->warning( "Prefix 'Single' and 'Multiple' to Type deprecated, use MaxValues instead at (". join(":",caller).")");
764 if ( $FieldTypes{$type} ) {
776 if ($type =~ s/(?:(Single)|Multiple)$//) {
777 $RT::Logger->warning("'Single' and 'Multiple' on SetType deprecated, use SetMaxValues instead at (". join(":",caller).")");
778 $self->SetMaxValues($1 ? 1 : 0);
780 $self->_Set(Field => 'Type', Value =>$type);
783 =head2 SetPattern STRING
785 Takes a single string representing a regular expression. Performs basic
786 validation on that regex, and sets the C<Pattern> field for the CF if it
795 my ($ok, $msg) = $self->_IsValidRegex($regex);
797 return $self->_Set(Field => 'Pattern', Value => $regex);
800 return (0, $self->loc("Invalid pattern: [_1]", $msg));
804 =head2 _IsValidRegex(Str $regex) returns (Bool $success, Str $msg)
806 Tests if the string contains an invalid regex.
812 my $regex = shift or return (1, 'valid');
815 local $SIG{__DIE__} = sub { 1 };
816 local $SIG{__WARN__} = sub { 1 };
818 if (eval { qr/$regex/; 1 }) {
823 $err =~ s{[,;].*}{}; # strip debug info from error
831 Returns true if this CustomField only accepts a single value.
832 Returns false if it accepts multiple values
838 if (($self->MaxValues||0) == 1) {
846 sub UnlimitedValues {
848 if (($self->MaxValues||0) == 0) {
857 =head2 CurrentUserHasRight RIGHT
859 Helper function to call the custom field's queue's CurrentUserHasRight with the passed in args.
863 sub CurrentUserHasRight {
867 return $self->CurrentUser->HasRight(
873 =head2 ACLEquivalenceObjects
875 Returns list of objects via which users can get rights on this custom field. For custom fields
876 these objects can be set using L<ContextObject|/"ContextObject and SetContextObject">.
880 sub ACLEquivalenceObjects {
883 my $ctx = $self->ContextObject
885 return ($ctx, $ctx->ACLEquivalenceObjects);
888 =head2 ContextObject and SetContextObject
890 Set or get a context for this object. It can be ticket, queue or another object
891 this CF applies to. Used for ACL control, for example SeeCustomField can be granted on
892 queue level to allow people to see all fields applied to the queue.
896 sub SetContextObject {
898 return $self->{'context_object'} = shift;
903 return $self->{'context_object'};
906 sub ValidContextType {
911 $valid{$_}++ for split '-', $self->LookupType;
912 delete $valid{'RT::Transaction'};
914 return $valid{$class};
917 =head2 LoadContextObject
919 Takes an Id for a Context Object and loads the right kind of RT::Object
920 for this particular Custom Field (based on the LookupType) and returns it.
921 This is a good way to ensure you don't try to use a Queue as a Context
922 Object on a User Custom Field.
926 sub LoadContextObject {
929 my $contextid = shift;
931 unless ( $self->ValidContextType($type) ) {
932 RT->Logger->debug("Invalid ContextType $type for Custom Field ".$self->Id);
936 my $context_object = $type->new( $self->CurrentUser );
937 my ($id, $msg) = $context_object->LoadById( $contextid );
939 RT->Logger->debug("Invalid ContextObject id: $msg");
942 return $context_object;
945 =head2 ValidateContextObject
947 Ensure that a given ContextObject applies to this Custom Field.
948 For custom fields that are assigned to Queues or to Classes, this checks that the Custom
949 Field is actually applied to that objects. For Global Custom Fields, it returns true
950 as long as the Object is of the right type, because you may be using
951 your permissions on a given Queue of Class to see a Global CF.
952 For CFs that are only applied Globally, you don't need a ContextObject.
956 sub ValidateContextObject {
960 return 1 if $self->IsApplied(0);
962 # global only custom fields don't have objects
963 # that should be used as context objects.
964 return if $self->ApplyGlobally;
966 # Otherwise, make sure we weren't passed a user object that we're
967 # supposed to treat as a queue.
968 return unless $self->ValidContextType(ref $object);
970 # Check that it is applied correctly
971 my ($applied_to) = grep {ref($_) eq $self->RecordClassFromLookupType} ($object, $object->ACLEquivalenceObjects);
972 return unless $applied_to;
973 return $self->IsApplied($applied_to->id);
980 unless ( $self->CurrentUserHasRight('AdminCustomField') ) {
981 return ( 0, $self->loc('Permission Denied') );
983 return $self->SUPER::_Set( @_ );
991 Takes the name of a table column.
992 Returns its value as a string, if the user passes an ACL check
998 return undef unless $self->id;
1000 # we need to do the rights check
1001 unless ( $self->CurrentUserHasRight('SeeCustomField') ) {
1003 "Permission denied. User #". $self->CurrentUser->id
1004 ." has no SeeCustomField right on CF #". $self->id
1008 return $self->__Value( @_ );
1015 1 will cause this custom field to no longer be avaialble for objects.
1016 0 will re-enable this field.
1021 =head2 SetTypeComposite
1023 Set this custom field's type and maximum values as a composite value
1027 sub SetTypeComposite {
1029 my $composite = shift;
1031 my $old = $self->TypeComposite;
1033 my ($type, $max_values) = split(/-/, $composite, 2);
1034 if ( $type ne $self->Type ) {
1035 my ($status, $msg) = $self->SetType( $type );
1036 return ($status, $msg) unless $status;
1038 if ( ($max_values || 0) != ($self->MaxValues || 0) ) {
1039 my ($status, $msg) = $self->SetMaxValues( $max_values );
1040 return ($status, $msg) unless $status;
1042 my $render = $self->RenderType;
1043 if ( $render and not grep { $_ eq $render } $self->RenderTypes ) {
1044 # We switched types and our render type is no longer valid, so unset it
1045 # and use the default
1046 $self->SetRenderType( undef );
1048 return 1, $self->loc(
1049 "Type changed from '[_1]' to '[_2]'",
1050 $self->FriendlyTypeComposite( $old ),
1051 $self->FriendlyTypeComposite( $composite ),
1055 =head2 TypeComposite
1057 Returns a composite value composed of this object's type and maximum values
1064 return join '-', ($self->Type || ''), ($self->MaxValues || 0);
1067 =head2 TypeComposites
1069 Returns an array of all possible composite values for custom fields.
1073 sub TypeComposites {
1075 return grep !/(?:[Tt]ext|Combobox|Date|DateTime|TimeValue)-0/, map { ("$_-1", "$_-0") } $self->Types;
1080 Returns the type of form widget to render for this custom field. Currently
1081 this only affects fields which return true for L</HasRenderTypes>.
1087 return '' unless $self->HasRenderTypes;
1089 return $self->_Value( 'RenderType', @_ )
1090 || $self->DefaultRenderType;
1093 =head2 SetRenderType TYPE
1095 Sets this custom field's render type.
1102 return (0, $self->loc("This custom field has no Render Types"))
1103 unless $self->HasRenderTypes;
1105 if ( !$type || $type eq $self->DefaultRenderType ) {
1106 return $self->_Set( Field => 'RenderType', Value => undef, @_ );
1109 if ( not grep { $_ eq $type } $self->RenderTypes ) {
1110 return (0, $self->loc("Invalid Render Type for custom field of type [_1]",
1111 $self->FriendlyType));
1114 return $self->_Set( Field => 'RenderType', Value => $type, @_ );
1117 =head2 DefaultRenderType [TYPE COMPOSITE]
1119 Returns the default render type for this custom field's type or the TYPE
1120 COMPOSITE specified as an argument.
1124 sub DefaultRenderType {
1126 my $composite = @_ ? shift : $self->TypeComposite;
1127 my ($type, $max) = split /-/, $composite, 2;
1128 return unless $type and $self->HasRenderTypes($composite);
1129 return $FieldTypes{$type}->{render_types}->{ $max == 1 ? 'single' : 'multiple' }[0];
1132 =head2 HasRenderTypes [TYPE_COMPOSITE]
1134 Returns a boolean value indicating whether the L</RenderTypes> and
1135 L</RenderType> methods make sense for this custom field.
1137 Currently true only for type C<Select>.
1141 sub HasRenderTypes {
1143 my ($type, $max) = split /-/, (@_ ? shift : $self->TypeComposite), 2;
1144 return undef unless $type;
1145 return defined $FieldTypes{$type}->{render_types}
1146 ->{ $max == 1 ? 'single' : 'multiple' };
1149 =head2 RenderTypes [TYPE COMPOSITE]
1151 Returns the valid render types for this custom field's type or the TYPE
1152 COMPOSITE specified as an argument.
1158 my $composite = @_ ? shift : $self->TypeComposite;
1159 my ($type, $max) = split /-/, $composite, 2;
1160 return unless $type and $self->HasRenderTypes($composite);
1161 return @{$FieldTypes{$type}->{render_types}->{ $max == 1 ? 'single' : 'multiple' }};
1164 =head2 SetLookupType
1166 Autrijus: care to doc how LookupTypes work?
1173 if ( $lookup ne $self->LookupType ) {
1174 # Okay... We need to invalidate our existing relationships
1175 my $ObjectCustomFields = RT::ObjectCustomFields->new($self->CurrentUser);
1176 $ObjectCustomFields->LimitToCustomField($self->Id);
1177 $_->Delete foreach @{$ObjectCustomFields->ItemsArrayRef};
1179 return $self->_Set(Field => 'LookupType', Value =>$lookup);
1184 Returns an array of LookupTypes available
1191 return sort keys %FRIENDLY_OBJECT_TYPES;
1194 my @FriendlyObjectTypes = (
1195 "[_1] objects", # loc
1196 "[_1]'s [_2] objects", # loc
1197 "[_1]'s [_2]'s [_3] objects", # loc
1200 =head2 FriendlyLookupType
1202 Returns a localized description of the type of this custom field
1206 sub FriendlyLookupType {
1208 my $lookup = shift || $self->LookupType;
1210 return ($self->loc( $FRIENDLY_OBJECT_TYPES{$lookup} ))
1211 if (defined $FRIENDLY_OBJECT_TYPES{$lookup} );
1213 my @types = map { s/^RT::// ? $self->loc($_) : $_ }
1214 grep { defined and length }
1215 split( /-/, $lookup )
1217 return ( $self->loc( $FriendlyObjectTypes[$#types], @types ) );
1220 =head1 RecordClassFromLookupType
1222 Returns the type of Object referred to by ObjectCustomFields' ObjectId column
1224 Optionally takes a LookupType to use instead of using the value on the loaded
1225 record. In this case, the method may be called on the class instead of an
1230 sub RecordClassFromLookupType {
1232 my $type = shift || $self->LookupType;
1233 my ($class) = ($type =~ /^([^-]+)/);
1235 if (blessed($self) and $self->LookupType eq $type) {
1237 "Custom Field #". $self->id
1238 ." has incorrect LookupType '$type'"
1241 RT->Logger->error("Invalid LookupType passed as argument: $type");
1248 =head1 ObjectTypeFromLookupType
1250 Returns the ObjectType used in ObjectCustomFieldValues rows for this CF
1252 Optionally takes a LookupType to use instead of using the value on the loaded
1253 record. In this case, the method may be called on the class instead of an
1258 sub ObjectTypeFromLookupType {
1260 my $type = shift || $self->LookupType;
1261 my ($class) = ($type =~ /([^-]+)$/);
1263 if (blessed($self) and $self->LookupType eq $type) {
1265 "Custom Field #". $self->id
1266 ." has incorrect LookupType '$type'"
1269 RT->Logger->error("Invalid LookupType passed as argument: $type");
1276 sub CollectionClassFromLookupType {
1279 my $record_class = $self->RecordClassFromLookupType;
1280 return undef unless $record_class;
1282 my $collection_class;
1283 if ( UNIVERSAL::can($record_class.'Collection', 'new') ) {
1284 $collection_class = $record_class.'Collection';
1285 } elsif ( UNIVERSAL::can($record_class.'es', 'new') ) {
1286 $collection_class = $record_class.'es';
1287 } elsif ( UNIVERSAL::can($record_class.'s', 'new') ) {
1288 $collection_class = $record_class.'s';
1290 $RT::Logger->error("Can not find a collection class for record class '$record_class'");
1293 return $collection_class;
1296 =head1 ApplyGlobally
1298 Certain custom fields (users, groups) should only be applied globally
1299 but rather than regexing in code for LookupType =~ RT::Queue, we'll codify
1307 return ($self->LookupType =~ /^RT::(?:Group|User)/io);
1313 Returns collection with objects this custom field is applied to.
1314 Class of the collection depends on L</LookupType>.
1315 See all L</NotAppliedTo> .
1317 Doesn't takes into account if object is applied globally.
1324 my ($res, $ocfs_alias) = $self->_AppliedTo;
1325 return $res unless $res;
1328 ALIAS => $ocfs_alias,
1330 OPERATOR => 'IS NOT',
1339 Returns collection with objects this custom field is not applied to.
1340 Class of the collection depends on L</LookupType>.
1341 See all L</AppliedTo> .
1343 Doesn't takes into account if object is applied globally.
1350 my ($res, $ocfs_alias) = $self->_AppliedTo;
1351 return $res unless $res;
1354 ALIAS => $ocfs_alias,
1366 my ($class) = $self->CollectionClassFromLookupType;
1367 return undef unless $class;
1369 my $res = $class->new( $self->CurrentUser );
1371 # If CF is a Group CF, only display user-defined groups
1372 if ( $class eq 'RT::Groups' ) {
1373 $res->LimitToUserDefinedGroups;
1376 $res->OrderBy( FIELD => 'Name' );
1377 my $ocfs_alias = $res->Join(
1381 TABLE2 => 'ObjectCustomFields',
1382 FIELD2 => 'ObjectId',
1385 LEFTJOIN => $ocfs_alias,
1386 ALIAS => $ocfs_alias,
1387 FIELD => 'CustomField',
1390 return ($res, $ocfs_alias);
1395 Takes object id and returns corresponding L<RT::ObjectCustomField>
1396 record if this custom field is applied to the object. Use 0 to check
1397 if custom field is applied globally.
1404 my $ocf = RT::ObjectCustomField->new( $self->CurrentUser );
1405 $ocf->LoadByCols( CustomField => $self->id, ObjectId => $id || 0 );
1406 return undef unless $ocf->id;
1410 =head2 AddToObject OBJECT
1412 Add this custom field as a custom field for a single object, such as a queue or group.
1422 my $id = $object->Id || 0;
1424 unless (index($self->LookupType, ref($object)) == 0) {
1425 return ( 0, $self->loc('Lookup type mismatch') );
1428 unless ( $object->CurrentUserHasRight('AssignCustomFields') ) {
1429 return ( 0, $self->loc('Permission Denied') );
1432 if ( $self->IsApplied( $id ) ) {
1433 return ( 0, $self->loc("Custom field is already applied to the object") );
1438 return (0, $self->loc("Couldn't apply custom field to an object as it's global already") )
1439 if $self->IsApplied( 0 );
1442 my $applied = RT::ObjectCustomFields->new( $self->CurrentUser );
1443 $applied->LimitToCustomField( $self->id );
1444 while ( my $record = $applied->Next ) {
1449 my $ocf = RT::ObjectCustomField->new( $self->CurrentUser );
1450 my ( $oid, $msg ) = $ocf->Create(
1451 ObjectId => $id, CustomField => $self->id,
1453 return ( $oid, $msg );
1457 =head2 RemoveFromObject OBJECT
1459 Remove this custom field for a single object, such as a queue or group.
1465 sub RemoveFromObject {
1468 my $id = $object->Id || 0;
1470 unless (index($self->LookupType, ref($object)) == 0) {
1471 return ( 0, $self->loc('Object type mismatch') );
1474 unless ( $object->CurrentUserHasRight('AssignCustomFields') ) {
1475 return ( 0, $self->loc('Permission Denied') );
1478 my $ocf = $self->IsApplied( $id );
1480 return ( 0, $self->loc("This custom field does not apply to that object") );
1483 # XXX: Delete doesn't return anything
1484 my ( $oid, $msg ) = $ocf->Delete;
1485 return ( $oid, $msg );
1489 =head2 AddValueForObject HASH
1491 Adds a custom field value for a record object of some kind.
1492 Takes a param hash of
1506 sub AddValueForObject {
1511 LargeContent => undef,
1512 ContentType => undef,
1515 my $obj = $args{'Object'} or return ( 0, $self->loc('Invalid object') );
1517 unless ( $self->CurrentUserHasRight('ModifyCustomField') ) {
1518 return ( 0, $self->loc('Permission Denied') );
1521 unless ( $self->MatchPattern($args{'Content'}) ) {
1522 return ( 0, $self->loc('Input must match [_1]', $self->FriendlyPattern) );
1525 $RT::Handle->BeginTransaction;
1527 if ( $self->MaxValues ) {
1528 my $current_values = $self->ValuesForObject($obj);
1529 my $extra_values = ( $current_values->Count + 1 ) - $self->MaxValues;
1531 # (The +1 is for the new value we're adding)
1533 # If we have a set of current values and we've gone over the maximum
1534 # allowed number of values, we'll need to delete some to make room.
1535 # which former values are blown away is not guaranteed
1537 while ($extra_values) {
1538 my $extra_item = $current_values->Next;
1539 unless ( $extra_item->id ) {
1540 $RT::Logger->crit( "We were just asked to delete "
1541 ."a custom field value that doesn't exist!" );
1542 $RT::Handle->Rollback();
1545 $extra_item->Delete;
1550 my $newval = RT::ObjectCustomFieldValue->new( $self->CurrentUser );
1551 my ($val, $msg) = $newval->Create(
1552 ObjectType => ref($obj),
1553 ObjectId => $obj->Id,
1554 Content => $args{'Content'},
1555 LargeContent => $args{'LargeContent'},
1556 ContentType => $args{'ContentType'},
1557 CustomField => $self->Id
1561 $RT::Handle->Rollback();
1562 return ($val, $self->loc("Couldn't create record: [_1]", $msg));
1565 $RT::Handle->Commit();
1571 sub _CanonicalizeValue {
1575 my $type = $self->_Value('Type');
1576 return 1 unless $type;
1578 my $method = '_CanonicalizeValue'. $type;
1579 return 1 unless $self->can($method);
1580 $self->$method($args);
1583 sub _CanonicalizeValueDateTime {
1586 my $DateObj = RT::Date->new( $self->CurrentUser );
1587 $DateObj->Set( Format => 'unknown',
1588 Value => $args->{'Content'} );
1589 $args->{'Content'} = $DateObj->ISO;
1593 # For date, we need to store Content as ISO date
1594 sub _CanonicalizeValueDate {
1598 # in case user input date with time, let's omit it by setting timezone
1599 # to utc so "hour" won't affect "day"
1600 my $DateObj = RT::Date->new( $self->CurrentUser );
1601 $DateObj->Set( Format => 'unknown',
1602 Value => $args->{'Content'},
1604 $args->{'Content'} = $DateObj->Date( Timezone => 'user' );
1608 sub _CanonicalizeValueIPAddress {
1612 $args->{Content} = RT::ObjectCustomFieldValue->ParseIP( $args->{Content} );
1613 return (0, $self->loc("Content is not a valid IP address"))
1614 unless $args->{Content};
1618 sub _CanonicalizeValueIPAddressRange {
1622 my $content = $args->{Content};
1623 $content .= "-".$args->{LargeContent} if $args->{LargeContent};
1625 ($args->{Content}, $args->{LargeContent})
1626 = RT::ObjectCustomFieldValue->ParseIPRange( $content );
1628 $args->{ContentType} = 'text/plain';
1629 return (0, $self->loc("Content is not a valid IP address range"))
1630 unless $args->{Content};
1634 =head2 MatchPattern STRING
1636 Tests the incoming string against the Pattern of this custom field object
1637 and returns a boolean; returns true if the Pattern is empty.
1643 my $regex = $self->Pattern or return 1;
1645 return (( defined $_[0] ? $_[0] : '') =~ $regex);
1651 =head2 FriendlyPattern
1653 Prettify the pattern of this custom field, by taking the text in C<(?#text)>
1658 sub FriendlyPattern {
1660 my $regex = $self->Pattern;
1662 return '' unless length $regex;
1663 if ( $regex =~ /\(\?#([^)]*)\)/ ) {
1664 return '[' . $self->loc($1) . ']';
1674 =head2 DeleteValueForObject HASH
1676 Deletes a custom field value for a ticket. Takes a param hash of Object and Content
1678 Returns a tuple of (STATUS, MESSAGE). If the call succeeded, the STATUS is true. otherwise it's false
1682 sub DeleteValueForObject {
1684 my %args = ( Object => undef,
1690 unless ($self->CurrentUserHasRight('ModifyCustomField')) {
1691 return (0, $self->loc('Permission Denied'));
1694 my $oldval = RT::ObjectCustomFieldValue->new($self->CurrentUser);
1696 if (my $id = $args{'Id'}) {
1699 unless ($oldval->id) {
1700 $oldval->LoadByObjectContentAndCustomField(
1701 Object => $args{'Object'},
1702 Content => $args{'Content'},
1703 CustomField => $self->Id,
1708 # check to make sure we found it
1709 unless ($oldval->Id) {
1710 return(0, $self->loc("Custom field value [_1] could not be found for custom field [_2]", $args{'Content'}, $self->Name));
1713 # for single-value fields, we need to validate that empty string is a valid value for it
1714 if ( $self->SingleValue and not $self->MatchPattern( '' ) ) {
1715 return ( 0, $self->loc('Input must match [_1]', $self->FriendlyPattern) );
1720 my $ret = $oldval->Delete();
1722 return(0, $self->loc("Custom field value could not be found"));
1724 return($oldval->Id, $self->loc("Custom field value deleted"));
1728 =head2 ValuesForObject OBJECT
1730 Return an L<RT::ObjectCustomFieldValues> object containing all of this custom field's values for OBJECT
1734 sub ValuesForObject {
1738 my $values = RT::ObjectCustomFieldValues->new($self->CurrentUser);
1739 unless ($self->id and $self->CurrentUserHasRight('SeeCustomField')) {
1740 # Return an empty object if they have no rights to see
1741 $values->Limit( FIELD => "id", VALUE => 0, SUBCLAUSE => "ACL" );
1745 $values->LimitToCustomField($self->Id);
1746 $values->LimitToObject($object);
1752 =head2 _ForObjectType PATH FRIENDLYNAME
1754 Tell RT that a certain object accepts custom fields
1758 'RT::Queue-RT::Ticket' => "Tickets", # loc
1759 'RT::Queue-RT::Ticket-RT::Transaction' => "Ticket Transactions", # loc
1760 'RT::User' => "Users", # loc
1761 'RT::Group' => "Groups", # loc
1762 'RT::Queue' => "Queues", # loc
1764 This is a class method.
1768 sub _ForObjectType {
1771 my $friendly_name = shift;
1773 $FRIENDLY_OBJECT_TYPES{$path} = $friendly_name;
1778 =head2 IncludeContentForValue [VALUE] (and SetIncludeContentForValue)
1780 Gets or sets the C<IncludeContentForValue> for this custom field. RT
1781 uses this field to automatically include content into the user's browser
1782 as they display records with custom fields in RT.
1786 sub SetIncludeContentForValue {
1787 shift->IncludeContentForValue(@_);
1789 sub IncludeContentForValue{
1791 $self->_URLTemplate('IncludeContentForValue', @_);
1796 =head2 LinkValueTo [VALUE] (and SetLinkValueTo)
1798 Gets or sets the C<LinkValueTo> for this custom field. RT
1799 uses this field to make custom field values into hyperlinks in the user's
1800 browser as they display records with custom fields in RT.
1805 sub SetLinkValueTo {
1806 shift->LinkValueTo(@_);
1811 $self->_URLTemplate('LinkValueTo', @_);
1816 =head2 _URLTemplate NAME [VALUE]
1818 With one argument, returns the _URLTemplate named C<NAME>, but only if
1819 the current user has the right to see this custom field.
1821 With two arguments, attemptes to set the relevant template value.
1827 my $template_name = shift;
1831 unless ( $self->CurrentUserHasRight('AdminCustomField') ) {
1832 return ( 0, $self->loc('Permission Denied') );
1834 $self->SetAttribute( Name => $template_name, Content => $value );
1835 return ( 1, $self->loc('Updated') );
1837 unless ( $self->id && $self->CurrentUserHasRight('SeeCustomField') ) {
1841 my @attr = $self->Attributes->Named($template_name);
1842 my $attr = shift @attr;
1844 if ($attr) { return $attr->Content }
1853 return $self->_Set( Field => 'BasedOn', Value => $value, @_ )
1854 unless defined $value and length $value;
1856 my $cf = RT::CustomField->new( $self->CurrentUser );
1857 $cf->SetContextObject( $self->ContextObject );
1858 $cf->Load( ref $value ? $value->id : $value );
1860 return (0, "Permission denied")
1861 unless $cf->id && $cf->CurrentUserHasRight('SeeCustomField');
1863 # XXX: Remove this restriction once we support lists and cascaded selects
1864 if ( $self->RenderType =~ /List/ ) {
1865 return (0, $self->loc("We can't currently render as a List when basing categories on another custom field. Please use another render type."));
1868 return $self->_Set( Field => 'BasedOn', Value => $value, @_ )
1874 my $obj = RT::CustomField->new( $self->CurrentUser );
1875 $obj->SetContextObject( $self->ContextObject );
1876 if ( $self->BasedOn ) {
1877 $obj->Load( $self->BasedOn );
1884 my $tag = $self->FirstAttribute( 'UILocation' );
1885 return $tag ? $tag->Content : '';
1892 return $self->SetAttribute( Name => 'UILocation', Content => $tag );
1895 return $self->DeleteAttribute('UILocation');
1901 $self->FirstAttribute('NoClone') ? 1 : '';
1908 return $self->SetAttribute( Name => 'NoClone', Content => 1 );
1910 return $self->DeleteAttribute('NoClone');
1917 Returns the current value of id.
1918 (In the database, id is stored as int(11).)
1926 Returns the current value of Name.
1927 (In the database, Name is stored as varchar(200).)
1931 =head2 SetName VALUE
1935 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1936 (In the database, Name will be stored as a varchar(200).)
1944 Returns the current value of Type.
1945 (In the database, Type is stored as varchar(200).)
1949 =head2 SetType VALUE
1953 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1954 (In the database, Type will be stored as a varchar(200).)
1962 Returns the current value of RenderType.
1963 (In the database, RenderType is stored as varchar(64).)
1967 =head2 SetRenderType VALUE
1970 Set RenderType to VALUE.
1971 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1972 (In the database, RenderType will be stored as a varchar(64).)
1980 Returns the current value of MaxValues.
1981 (In the database, MaxValues is stored as int(11).)
1985 =head2 SetMaxValues VALUE
1988 Set MaxValues to VALUE.
1989 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1990 (In the database, MaxValues will be stored as a int(11).)
1998 Returns the current value of Pattern.
1999 (In the database, Pattern is stored as text.)
2003 =head2 SetPattern VALUE
2006 Set Pattern to VALUE.
2007 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2008 (In the database, Pattern will be stored as a text.)
2016 Returns the current value of Repeated.
2017 (In the database, Repeated is stored as smallint(6).)
2021 =head2 SetRepeated VALUE
2024 Set Repeated to VALUE.
2025 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2026 (In the database, Repeated will be stored as a smallint(6).)
2034 Returns the current value of BasedOn.
2035 (In the database, BasedOn is stored as int(11).)
2039 =head2 SetBasedOn VALUE
2042 Set BasedOn to VALUE.
2043 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2044 (In the database, BasedOn will be stored as a int(11).)
2052 Returns the current value of Description.
2053 (In the database, Description is stored as varchar(255).)
2057 =head2 SetDescription VALUE
2060 Set Description to VALUE.
2061 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2062 (In the database, Description will be stored as a varchar(255).)
2070 Returns the current value of SortOrder.
2071 (In the database, SortOrder is stored as int(11).)
2075 =head2 SetSortOrder VALUE
2078 Set SortOrder to VALUE.
2079 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2080 (In the database, SortOrder will be stored as a int(11).)
2088 Returns the current value of LookupType.
2089 (In the database, LookupType is stored as varchar(255).)
2093 =head2 SetLookupType VALUE
2096 Set LookupType to VALUE.
2097 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2098 (In the database, LookupType will be stored as a varchar(255).)
2106 Returns the current value of Creator.
2107 (In the database, Creator is stored as int(11).)
2115 Returns the current value of Created.
2116 (In the database, Created is stored as datetime.)
2122 =head2 LastUpdatedBy
2124 Returns the current value of LastUpdatedBy.
2125 (In the database, LastUpdatedBy is stored as int(11).)
2133 Returns the current value of LastUpdated.
2134 (In the database, LastUpdated is stored as datetime.)
2142 Returns the current value of Disabled.
2143 (In the database, Disabled is stored as smallint(6).)
2147 =head2 SetDisabled VALUE
2150 Set Disabled to VALUE.
2151 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2152 (In the database, Disabled will be stored as a smallint(6).)
2159 sub _CoreAccessible {
2163 {read => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''},
2165 {read => 1, write => 1, sql_type => 12, length => 200, is_blob => 0, is_numeric => 0, type => 'varchar(200)', default => ''},
2167 {read => 1, write => 1, sql_type => 12, length => 200, is_blob => 0, is_numeric => 0, type => 'varchar(200)', default => ''},
2169 {read => 1, write => 1, sql_type => 12, length => 64, is_blob => 0, is_numeric => 0, type => 'varchar(64)', default => ''},
2171 {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''},
2173 {read => 1, write => 1, sql_type => -4, length => 0, is_blob => 1, is_numeric => 0, type => 'text', default => ''},
2175 {read => 1, write => 1, sql_type => 5, length => 6, is_blob => 0, is_numeric => 1, type => 'smallint(6)', default => '0'},
2177 {read => 1, write => 1, sql_type => 12, length => 64, is_blob => 0, is_numeric => 0, type => 'varchar(64)', default => ''},
2179 {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''},
2181 {read => 1, write => 1, sql_type => 12, length => 255, is_blob => 0, is_numeric => 0, type => 'varchar(255)', default => ''},
2183 {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
2185 {read => 1, write => 1, sql_type => 12, length => 255, is_blob => 0, is_numeric => 0, type => 'varchar(255)', default => ''},
2187 {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
2189 {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''},
2191 {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
2193 {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''},
2195 {read => 1, write => 1, sql_type => 5, length => 6, is_blob => 0, is_numeric => 1, type => 'smallint(6)', default => '0'},
2201 RT::Base->_ImportOverlays();