1 # BEGIN BPS TAGGED BLOCK {{{
5 # This software is Copyright (c) 1996-2016 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
181 'Enter multiple time values (UNSUPPORTED)',
182 'Enter a time value',
183 'Enter [_1] time values (UNSUPPORTED)',
191 labels => [ 'Enter multiple IP addresses', # loc
192 'Enter one IP address', # loc
193 'Enter up to [quant,_1,IP address,IP addresses]', # loc
200 labels => [ 'Enter multiple IP address ranges', # loc
201 'Enter one IP address range', # loc
202 'Enter up to [quant,_1,IP address range,IP address ranges]', # loc
208 my %BUILTIN_GROUPINGS;
209 my %FRIENDLY_LOOKUP_TYPES = ();
211 __PACKAGE__->RegisterLookupType( 'RT::Queue-RT::Ticket' => "Tickets", ); #loc
212 __PACKAGE__->RegisterLookupType( 'RT::Queue-RT::Ticket-RT::Transaction' => "Ticket Transactions", ); #loc
213 __PACKAGE__->RegisterLookupType( 'RT::User' => "Users", ); #loc
214 __PACKAGE__->RegisterLookupType( 'RT::Queue' => "Queues", ); #loc
215 __PACKAGE__->RegisterLookupType( 'RT::Group' => "Groups", ); #loc
217 __PACKAGE__->RegisterBuiltInGroupings(
218 'RT::Ticket' => [ qw(Basics Dates Links People) ],
219 'RT::User' => [ 'Identity', 'Access control', 'Location', 'Phones' ],
222 __PACKAGE__->AddRight( General => SeeCustomField => 'View custom fields'); # loc
223 __PACKAGE__->AddRight( Admin => AdminCustomField => 'Create, modify and delete custom fields'); # loc
224 __PACKAGE__->AddRight( Admin => AdminCustomFieldValues => 'Create, modify and delete custom fields values'); # loc
225 __PACKAGE__->AddRight( Staff => ModifyCustomField => 'Add, modify and delete custom field values for objects'); # loc
229 RT::CustomField_Overlay - overlay for RT::CustomField
233 =head1 'CORE' METHODS
235 =head2 Create PARAMHASH
237 Create takes a hash of values and creates a row in the database:
242 varchar(255) 'Pattern'.
243 varchar(255) 'Description'.
245 varchar(255) 'LookupType'.
246 smallint(6) 'Disabled'.
248 C<LookupType> is generally the result of either
249 C<RT::Ticket->CustomFieldLookupType> or C<RT::Transaction->CustomFieldLookupType>.
264 IncludeContentForValue => '',
268 unless ( $self->CurrentUser->HasRight(Object => $RT::System, Right => 'AdminCustomField') ) {
269 return (0, $self->loc('Permission Denied'));
272 if ( $args{TypeComposite} ) {
273 @args{'Type', 'MaxValues'} = split(/-/, $args{TypeComposite}, 2);
275 elsif ( $args{Type} =~ s/(?:(Single)|Multiple)$// ) {
276 # old style Type string
277 $args{'MaxValues'} = $1 ? 1 : 0;
279 $args{'MaxValues'} = int $args{'MaxValues'};
281 if ( !exists $args{'Queue'}) {
282 # do nothing -- things below are strictly backward compat
284 elsif ( ! $args{'Queue'} ) {
285 unless ( $self->CurrentUser->HasRight( Object => $RT::System, Right => 'AssignCustomFields') ) {
286 return ( 0, $self->loc('Permission Denied') );
288 $args{'LookupType'} = 'RT::Queue-RT::Ticket';
291 my $queue = RT::Queue->new($self->CurrentUser);
292 $queue->Load($args{'Queue'});
293 unless ($queue->Id) {
294 return (0, $self->loc("Queue not found"));
296 unless ( $queue->CurrentUserHasRight('AssignCustomFields') ) {
297 return ( 0, $self->loc('Permission Denied') );
299 $args{'LookupType'} = 'RT::Queue-RT::Ticket';
300 $args{'Queue'} = $queue->Id;
303 my ($ok, $msg) = $self->_IsValidRegex( $args{'Pattern'} );
304 return (0, $self->loc("Invalid pattern: [_1]", $msg)) unless $ok;
306 if ( $args{'MaxValues'} != 1 && $args{'Type'} =~ /(text|combobox)$/i ) {
307 $RT::Logger->debug("Support for 'multiple' Texts or Comboboxes is not implemented");
308 $args{'MaxValues'} = 1;
311 if ( $args{'RenderType'} ||= undef ) {
312 my $composite = join '-', @args{'Type', 'MaxValues'};
313 return (0, $self->loc("This custom field has no Render Types"))
314 unless $self->HasRenderTypes( $composite );
316 if ( $args{'RenderType'} eq $self->DefaultRenderType( $composite ) ) {
317 $args{'RenderType'} = undef;
319 return (0, $self->loc("Invalid Render Type") )
320 unless grep $_ eq $args{'RenderType'}, $self->RenderTypes( $composite );
324 $args{'ValuesClass'} = undef if ($args{'ValuesClass'} || '') eq 'RT::CustomFieldValues';
325 if ( $args{'ValuesClass'} ||= undef ) {
326 return (0, $self->loc("This Custom Field can not have list of values"))
327 unless $self->IsSelectionType( $args{'Type'} );
329 unless ( $self->ValidateValuesClass( $args{'ValuesClass'} ) ) {
330 return (0, $self->loc("Invalid Custom Field values source"));
334 $args{'Disabled'} ||= 0;
336 (my $rv, $msg) = $self->SUPER::Create(
337 Name => $args{'Name'},
338 Type => $args{'Type'},
339 RenderType => $args{'RenderType'},
340 MaxValues => $args{'MaxValues'},
341 Pattern => $args{'Pattern'},
342 BasedOn => $args{'BasedOn'},
343 ValuesClass => $args{'ValuesClass'},
344 Description => $args{'Description'},
345 Disabled => $args{'Disabled'},
346 LookupType => $args{'LookupType'},
350 if ( exists $args{'LinkValueTo'}) {
351 $self->SetLinkValueTo($args{'LinkValueTo'});
354 if ( exists $args{'IncludeContentForValue'}) {
355 $self->SetIncludeContentForValue($args{'IncludeContentForValue'});
358 if ( exists $args{'UILocation'} ) {
359 $self->SetUILocation( $args{'UILocation'} );
362 if ( exists $args{'NoClone'} ) {
363 $self->SetNoClone( $args{'NoClone'} );
366 return ($rv, $msg) unless exists $args{'Queue'};
368 # Compat code -- create a new ObjectCustomField mapping
369 my $OCF = RT::ObjectCustomField->new( $self->CurrentUser );
371 CustomField => $self->Id,
372 ObjectId => $args{'Queue'},
381 Load a custom field. If the value handed in is an integer, load by custom field ID. Otherwise, Load by name.
387 my $id = shift || '';
389 if ( $id =~ /^\d+$/ ) {
390 return $self->SUPER::Load( $id );
392 return $self->LoadByName( Name => $id );
398 =head2 LoadByName Name => C<NAME>, [...]
400 Loads the Custom field named NAME. As other optional parameters, takes:
404 =item LookupType => C<LOOKUPTYPE>
406 The type of Custom Field to look for; while this parameter is not
407 required, it is highly suggested, or you may not find the Custom Field
408 you are expecting. It should be passed a C<LookupType> such as
409 L<RT::Ticket/CustomFieldLookupType> or
410 L<RT::User/CustomFieldLookupType>.
412 =item ObjectType => C<CLASS>
414 The class of object that the custom field is applied to. This can be
415 intuited from the provided C<LookupType>.
417 =item ObjectId => C<ID>
419 limits the custom field search to one applied to the relevant id. For
420 example, if a C<LookupType> of C<< RT::Ticket->CustomFieldLookupType >>
421 is used, this is which Queue the CF must be applied to. Pass 0 to only
422 search custom fields that are applied globally.
424 =item IncludeDisabled => C<BOOLEAN>
426 Whether it should return Disabled custom fields if they match; defaults
427 to on, though non-Disabled custom fields are returned preferentially.
429 =item IncludeGlobal => C<BOOLEAN>
431 Whether to also search global custom fields, even if a value is provided
432 for C<ObjectId>; defaults to off. Non-global custom fields are returned
437 For backwards compatibility, a value passed for C<Queue> is equivalent
438 to specifying a C<LookupType> of L<RT::Ticket/CustomFieldLookupType>,
439 and a C<ObjectId> of the value passed as C<Queue>.
441 If multiple custom fields match the above constraints, the first
442 according to C<SortOrder> will be returned; ties are broken by C<id>,
445 =head2 LoadNameAndQueue
447 =head2 LoadByNameAndQueue
449 Deprecated alternate names for L</LoadByName>.
453 # Compatibility for API change after 3.0 beta 1
454 *LoadNameAndQueue = \&LoadByName;
455 # Change after 3.4 beta.
456 *LoadByNameAndQueue = \&LoadByName;
466 IncludeDisabled => 1,
475 unless ( defined $args{'Name'} && length $args{'Name'} ) {
476 $RT::Logger->error("Couldn't load Custom Field without Name");
477 return wantarray ? (0, $self->loc("No name provided")) : 0;
480 if ( defined $args{'Queue'} ) {
481 # Set a LookupType for backcompat, otherwise we'll calculate
482 # one of RT::Queue from your ContextObj. Older code was relying
483 # on us defaulting to RT::Queue-RT::Ticket in old LimitToQueue call.
484 $args{LookupType} ||= 'RT::Queue-RT::Ticket';
485 $args{ObjectId} //= delete $args{Queue};
488 # Default the ObjectType to the top category of the LookupType; it's
489 # what the CFs are assigned on.
490 $args{ObjectType} ||= $1 if $args{LookupType} and $args{LookupType} =~ /^([^-]+)/;
492 # Resolve the ObjectId/ObjectType; this is necessary to properly
493 # limit ObjectId, and also possibly useful to set a ContextObj if we
494 # are currently lacking one. It is not strictly necessary if we
495 # have a context object and were passed a numeric ObjectId, but it
496 # cannot hurt to verify its sanity. Skip if we have a false
497 # ObjectId, which means "global", or if we lack an ObjectType
498 if ($args{ObjectId} and $args{ObjectType}) {
499 my ($obj, $ok, $msg);
501 $obj = $args{ObjectType}->new( $self->CurrentUser );
502 ($ok, $msg) = $obj->Load( $args{ObjectId} );
506 $args{ObjectId} = $obj->id;
507 $self->SetContextObject( $obj )
508 unless $self->ContextObject;
510 $RT::Logger->warning("Failed to load $args{ObjectType} '$args{ObjectId}'");
511 if ($args{IncludeGlobal}) {
512 # Fall back to acting like we were only asked about the
516 # If they didn't also want global results, there's no
517 # point in searching; abort
518 return wantarray ? (0, $self->loc("Not found")) : 0;
521 } elsif (not $args{ObjectType} and $args{ObjectId}) {
522 # If we skipped out on the above due to lack of ObjectType, make
523 # sure we clear out ObjectId of anything lingering
524 $RT::Logger->warning("No LookupType or ObjectType passed; ignoring ObjectId");
525 delete $args{ObjectId};
528 my $CFs = RT::CustomFields->new( $self->CurrentUser );
529 $CFs->SetContextObject( $self->ContextObject );
530 my $field = $args{'Name'} =~ /\D/? 'Name' : 'id';
531 $CFs->Limit( FIELD => $field, VALUE => $args{'Name'}, CASESENSITIVE => 0);
533 # The context object may be a ticket, for example, as context for a
534 # queue CF. The valid lookup types are thus the entire set of
535 # ACLEquivalenceObjects for the context object.
536 $args{LookupType} ||= [
537 map {$_->CustomFieldLookupType}
538 ($self->ContextObject, $self->ContextObject->ACLEquivalenceObjects) ]
539 if $self->ContextObject;
541 # Apply LookupType limits
542 $args{LookupType} = [ $args{LookupType} ]
543 if $args{LookupType} and not ref($args{LookupType});
544 $CFs->Limit( FIELD => "LookupType", OPERATOR => "IN", VALUE => $args{LookupType} )
545 if $args{LookupType};
547 # Default to by SortOrder and id; this mirrors the standard ordering
548 # of RT::CustomFields (minus the Name, which is guaranteed to be
551 { FIELD => 'SortOrder',
557 if (defined $args{ObjectId}) {
558 # The join to OCFs is distinct -- either we have a global
559 # application or an objectid match, but never both. Even if
560 # this were not the case, we care only for the first row.
561 my $ocfs = $CFs->_OCFAlias( Distinct => 1);
562 if ($args{IncludeGlobal}) {
567 VALUE => [ $args{ObjectId}, 0 ],
569 # Find the queue-specific first
570 unshift @order, { ALIAS => $ocfs, FIELD => "ObjectId", ORDER => "DESC" };
575 VALUE => $args{ObjectId},
580 if ($args{IncludeDisabled}) {
581 # Load disabled fields, but return them only as a last resort.
582 # This goes at the front of @order, as we prefer the
583 # non-disabled global CF to the disabled Queue-specific CF.
585 unshift @order, { FIELD => "Disabled", ORDER => 'ASC' };
588 # Apply the above orderings
589 $CFs->OrderByCols( @order );
591 # We only want one entry.
592 $CFs->RowsPerPage(1);
594 # version before 3.8 just returns 0, so we need to test if wantarray to be
595 # backward compatible.
596 return wantarray ? (0, $self->loc("Not found")) : 0 unless my $first = $CFs->First;
598 return $self->LoadById( $first->id );
604 =head2 Custom field values
608 Return a object (collection) of all acceptable values for this Custom Field.
609 Class of the object can vary and depends on the return value
610 of the C<ValuesClass> method.
614 *ValuesObj = \&Values;
619 my $class = $self->ValuesClass;
620 if ( $class ne 'RT::CustomFieldValues') {
621 $class->require or die "Can't load $class: $@";
623 my $cf_values = $class->new( $self->CurrentUser );
624 $cf_values->SetCustomFieldObject( $self );
625 # if the user has no rights, return an empty object
626 if ( $self->id && $self->CurrentUserHasRight( 'SeeCustomField') ) {
627 $cf_values->LimitToCustomField( $self->Id );
629 $cf_values->Limit( FIELD => 'id', VALUE => 0, SUBCLAUSE => 'acl' );
637 Create a new value for this CustomField. Takes a paramhash containing the elements Name, Description and SortOrder
645 unless ($self->CurrentUserHasRight('AdminCustomField') || $self->CurrentUserHasRight('AdminCustomFieldValues')) {
646 return (0, $self->loc('Permission Denied'));
650 if ( !defined $args{'Name'} || $args{'Name'} eq '' ) {
651 return (0, $self->loc("Can't add a custom field value without a name"));
654 my $newval = RT::CustomFieldValue->new( $self->CurrentUser );
655 return $newval->Create( %args, CustomField => $self->Id );
661 =head3 DeleteValue ID
663 Deletes a value from this custom field by id.
665 Does not remove this value for any article which has had it selected
672 unless ( $self->CurrentUserHasRight('AdminCustomField') || $self->CurrentUserHasRight('AdminCustomFieldValues') ) {
673 return (0, $self->loc('Permission Denied'));
676 my $val_to_del = RT::CustomFieldValue->new( $self->CurrentUser );
677 $val_to_del->Load( $id );
678 unless ( $val_to_del->Id ) {
679 return (0, $self->loc("Couldn't find that value"));
681 unless ( $val_to_del->CustomField == $self->Id ) {
682 return (0, $self->loc("That is not a value for this custom field"));
685 my $retval = $val_to_del->Delete;
687 return (0, $self->loc("Custom field value could not be deleted"));
689 return ($retval, $self->loc("Custom field value deleted"));
693 =head2 ValidateQueue Queue
695 Make sure that the name specified is valid
703 return 0 unless length $value;
705 return $self->SUPER::ValidateName($value);
708 =head2 ValidateQueue Queue
710 Make sure that the queue specified is a valid queue name
718 return undef unless defined $id;
719 # 0 means "Global" null would _not_ be ok.
720 return 1 if $id eq '0';
722 my $q = RT::Queue->new( RT->SystemUser );
724 return undef unless $q->id;
732 Retuns an array of the types of CustomField that are supported
737 return (sort {(($FieldTypes{$a}{sort_order}||999) <=> ($FieldTypes{$b}{sort_order}||999)) or ($a cmp $b)} keys %FieldTypes);
741 =head2 IsSelectionType
743 Retuns a boolean value indicating whether the C<Values> method makes sense
744 to this Custom Field.
748 sub IsSelectionType {
750 my $type = @_? shift : $self->Type;
751 return undef unless $type;
752 return $FieldTypes{$type}->{selection_type};
757 =head2 IsExternalValues
761 sub IsExternalValues {
763 return 0 unless $self->IsSelectionType( @_ );
764 return $self->ValuesClass eq 'RT::CustomFieldValues'? 0 : 1;
769 return $self->_Value( ValuesClass => @_ ) || 'RT::CustomFieldValues';
774 my $class = shift || 'RT::CustomFieldValues';
776 if ( $class eq 'RT::CustomFieldValues' ) {
777 return $self->_Set( Field => 'ValuesClass', Value => undef, @_ );
780 return (0, $self->loc("This Custom Field can not have list of values"))
781 unless $self->IsSelectionType;
783 unless ( $self->ValidateValuesClass( $class ) ) {
784 return (0, $self->loc("Invalid Custom Field values source"));
786 return $self->_Set( Field => 'ValuesClass', Value => $class, @_ );
789 sub ValidateValuesClass {
793 return 1 if !$class || $class eq 'RT::CustomFieldValues';
794 return 1 if grep $class eq $_, RT->Config->Get('CustomFieldValuesSources');
799 =head2 FriendlyType [TYPE, MAX_VALUES]
801 Returns a localized human-readable version of the custom field type.
802 If a custom field type is specified as the parameter, the friendly type for that type will be returned
809 my $type = @_ ? shift : $self->Type;
810 my $max = @_ ? shift : $self->MaxValues;
811 $max = 0 unless $max;
813 if (my $friendly_type = $FieldTypes{$type}->{labels}->[$max>2 ? 2 : $max]) {
814 return ( $self->loc( $friendly_type, $max ) );
817 return ( $self->loc( $type ) );
821 sub FriendlyTypeComposite {
823 my $composite = shift || $self->TypeComposite;
824 return $self->FriendlyType(split(/-/, $composite, 2));
828 =head2 ValidateType TYPE
830 Takes a single string. returns true if that string is a value
840 if ( $type =~ s/(?:Single|Multiple)$// ) {
842 Arguments => "suffix 'Single' or 'Multiple'",
843 Instead => "MaxValues",
848 if ( $FieldTypes{$type} ) {
860 if ($type =~ s/(?:(Single)|Multiple)$//) {
862 Arguments => "suffix 'Single' or 'Multiple'",
863 Instead => "MaxValues",
866 $self->SetMaxValues($1 ? 1 : 0);
868 $self->_Set(Field => 'Type', Value =>$type);
871 =head2 SetPattern STRING
873 Takes a single string representing a regular expression. Performs basic
874 validation on that regex, and sets the C<Pattern> field for the CF if it
883 my ($ok, $msg) = $self->_IsValidRegex($regex);
885 return $self->_Set(Field => 'Pattern', Value => $regex);
888 return (0, $self->loc("Invalid pattern: [_1]", $msg));
892 =head2 _IsValidRegex(Str $regex) returns (Bool $success, Str $msg)
894 Tests if the string contains an invalid regex.
900 my $regex = shift or return (1, 'valid');
903 local $SIG{__DIE__} = sub { 1 };
904 local $SIG{__WARN__} = sub { 1 };
906 if (eval { qr/$regex/; 1 }) {
911 $err =~ s{[,;].*}{}; # strip debug info from error
919 Returns true if this CustomField only accepts a single value.
920 Returns false if it accepts multiple values
926 if (($self->MaxValues||0) == 1) {
934 sub UnlimitedValues {
936 if (($self->MaxValues||0) == 0) {
945 =head2 ACLEquivalenceObjects
947 Returns list of objects via which users can get rights on this custom field. For custom fields
948 these objects can be set using L<ContextObject|/"ContextObject and SetContextObject">.
952 sub ACLEquivalenceObjects {
955 my $ctx = $self->ContextObject
957 return ($ctx, $ctx->ACLEquivalenceObjects);
960 =head2 ContextObject and SetContextObject
962 Set or get a context for this object. It can be ticket, queue or another
963 object this CF added to. Used for ACL control, for example
964 SeeCustomField can be granted on queue level to allow people to see all
965 fields added to the queue.
969 sub SetContextObject {
971 return $self->{'context_object'} = shift;
976 return $self->{'context_object'};
979 sub ValidContextType {
984 $valid{$_}++ for split '-', $self->LookupType;
985 delete $valid{'RT::Transaction'};
987 return $valid{$class};
990 =head2 LoadContextObject
992 Takes an Id for a Context Object and loads the right kind of RT::Object
993 for this particular Custom Field (based on the LookupType) and returns it.
994 This is a good way to ensure you don't try to use a Queue as a Context
995 Object on a User Custom Field.
999 sub LoadContextObject {
1002 my $contextid = shift;
1004 unless ( $self->ValidContextType($type) ) {
1005 RT->Logger->debug("Invalid ContextType $type for Custom Field ".$self->Id);
1009 my $context_object = $type->new( $self->CurrentUser );
1010 my ($id, $msg) = $context_object->LoadById( $contextid );
1012 RT->Logger->debug("Invalid ContextObject id: $msg");
1015 return $context_object;
1018 =head2 ValidateContextObject
1020 Ensure that a given ContextObject applies to this Custom Field. For
1021 custom fields that are assigned to Queues or to Classes, this checks
1022 that the Custom Field is actually added to that object. For Global
1023 Custom Fields, it returns true as long as the Object is of the right
1024 type, because you may be using your permissions on a given Queue of
1025 Class to see a Global CF. For CFs that are only added globally, you
1026 don't need a ContextObject.
1030 sub ValidateContextObject {
1034 return 1 if $self->IsGlobal;
1036 # global only custom fields don't have objects
1037 # that should be used as context objects.
1038 return if $self->IsOnlyGlobal;
1040 # Otherwise, make sure we weren't passed a user object that we're
1041 # supposed to treat as a queue.
1042 return unless $self->ValidContextType(ref $object);
1044 # Check that it is added correctly
1045 my ($added_to) = grep {ref($_) eq $self->RecordClassFromLookupType} ($object, $object->ACLEquivalenceObjects);
1046 return unless $added_to;
1047 return $self->IsAdded($added_to->id);
1053 unless ( $self->CurrentUserHasRight('AdminCustomField') ) {
1054 return ( 0, $self->loc('Permission Denied') );
1056 return $self->SUPER::_Set( @_ );
1064 Takes the name of a table column.
1065 Returns its value as a string, if the user passes an ACL check
1071 return undef unless $self->id;
1073 # we need to do the rights check
1074 unless ( $self->CurrentUserHasRight('SeeCustomField') ) {
1076 "Permission denied. User #". $self->CurrentUser->id
1077 ." has no SeeCustomField right on CF #". $self->id
1081 return $self->__Value( @_ );
1088 1 will cause this custom field to no longer be avaialble for objects.
1089 0 will re-enable this field.
1094 =head2 SetTypeComposite
1096 Set this custom field's type and maximum values as a composite value
1100 sub SetTypeComposite {
1102 my $composite = shift;
1104 my $old = $self->TypeComposite;
1106 my ($type, $max_values) = split(/-/, $composite, 2);
1107 if ( $type ne $self->Type ) {
1108 my ($status, $msg) = $self->SetType( $type );
1109 return ($status, $msg) unless $status;
1111 if ( ($max_values || 0) != ($self->MaxValues || 0) ) {
1112 my ($status, $msg) = $self->SetMaxValues( $max_values );
1113 return ($status, $msg) unless $status;
1115 my $render = $self->RenderType;
1116 if ( $render and not grep { $_ eq $render } $self->RenderTypes ) {
1117 # We switched types and our render type is no longer valid, so unset it
1118 # and use the default
1119 $self->SetRenderType( undef );
1121 return 1, $self->loc(
1122 "Type changed from '[_1]' to '[_2]'",
1123 $self->FriendlyTypeComposite( $old ),
1124 $self->FriendlyTypeComposite( $composite ),
1128 =head2 TypeComposite
1130 Returns a composite value composed of this object's type and maximum values
1137 return join '-', ($self->Type || ''), ($self->MaxValues || 0);
1140 =head2 TypeComposites
1142 Returns an array of all possible composite values for custom fields.
1146 sub TypeComposites {
1148 return grep !/(?:[Tt]ext|Combobox|Date|DateTime|TimeValue)-0/, map { ("$_-1", "$_-0") } $self->Types;
1153 Returns the type of form widget to render for this custom field. Currently
1154 this only affects fields which return true for L</HasRenderTypes>.
1160 return '' unless $self->HasRenderTypes;
1162 return $self->_Value( 'RenderType', @_ )
1163 || $self->DefaultRenderType;
1166 =head2 SetRenderType TYPE
1168 Sets this custom field's render type.
1175 return (0, $self->loc("This custom field has no Render Types"))
1176 unless $self->HasRenderTypes;
1178 if ( !$type || $type eq $self->DefaultRenderType ) {
1179 return $self->_Set( Field => 'RenderType', Value => undef, @_ );
1182 if ( not grep { $_ eq $type } $self->RenderTypes ) {
1183 return (0, $self->loc("Invalid Render Type for custom field of type [_1]",
1184 $self->FriendlyType));
1187 return $self->_Set( Field => 'RenderType', Value => $type, @_ );
1190 =head2 DefaultRenderType [TYPE COMPOSITE]
1192 Returns the default render type for this custom field's type or the TYPE
1193 COMPOSITE specified as an argument.
1197 sub DefaultRenderType {
1199 my $composite = @_ ? shift : $self->TypeComposite;
1200 my ($type, $max) = split /-/, $composite, 2;
1201 return unless $type and $self->HasRenderTypes($composite);
1202 return $FieldTypes{$type}->{render_types}->{ $max == 1 ? 'single' : 'multiple' }[0];
1205 =head2 HasRenderTypes [TYPE_COMPOSITE]
1207 Returns a boolean value indicating whether the L</RenderTypes> and
1208 L</RenderType> methods make sense for this custom field.
1210 Currently true only for type C<Select>.
1214 sub HasRenderTypes {
1216 my ($type, $max) = split /-/, (@_ ? shift : $self->TypeComposite), 2;
1217 return undef unless $type;
1218 return defined $FieldTypes{$type}->{render_types}
1219 ->{ $max == 1 ? 'single' : 'multiple' };
1222 =head2 RenderTypes [TYPE COMPOSITE]
1224 Returns the valid render types for this custom field's type or the TYPE
1225 COMPOSITE specified as an argument.
1231 my $composite = @_ ? shift : $self->TypeComposite;
1232 my ($type, $max) = split /-/, $composite, 2;
1233 return unless $type and $self->HasRenderTypes($composite);
1234 return @{$FieldTypes{$type}->{render_types}->{ $max == 1 ? 'single' : 'multiple' }};
1237 =head2 SetLookupType
1239 Autrijus: care to doc how LookupTypes work?
1246 if ( $lookup ne $self->LookupType ) {
1247 # Okay... We need to invalidate our existing relationships
1248 RT::ObjectCustomField->new($self->CurrentUser)->DeleteAll( CustomField => $self );
1250 return $self->_Set(Field => 'LookupType', Value =>$lookup);
1255 Returns an array of LookupTypes available
1262 return sort keys %FRIENDLY_LOOKUP_TYPES;
1265 =head2 FriendlyLookupType
1267 Returns a localized description of the type of this custom field
1271 sub FriendlyLookupType {
1273 my $lookup = shift || $self->LookupType;
1275 return ($self->loc( $FRIENDLY_LOOKUP_TYPES{$lookup} ))
1276 if defined $FRIENDLY_LOOKUP_TYPES{$lookup};
1278 my @types = map { s/^RT::// ? $self->loc($_) : $_ }
1279 grep { defined and length }
1280 split( /-/, $lookup )
1283 state $LocStrings = [
1284 "[_1] objects", # loc
1285 "[_1]'s [_2] objects", # loc
1286 "[_1]'s [_2]'s [_3] objects", # loc
1288 return ( $self->loc( $LocStrings->[$#types], @types ) );
1291 =head1 RecordClassFromLookupType
1293 Returns the type of Object referred to by ObjectCustomFields' ObjectId column
1295 Optionally takes a LookupType to use instead of using the value on the loaded
1296 record. In this case, the method may be called on the class instead of an
1301 sub RecordClassFromLookupType {
1303 my $type = shift || $self->LookupType;
1304 my ($class) = ($type =~ /^([^-]+)/);
1306 if (blessed($self) and $self->LookupType eq $type) {
1308 "Custom Field #". $self->id
1309 ." has incorrect LookupType '$type'"
1312 RT->Logger->error("Invalid LookupType passed as argument: $type");
1319 =head1 ObjectTypeFromLookupType
1321 Returns the ObjectType used in ObjectCustomFieldValues rows for this CF
1323 Optionally takes a LookupType to use instead of using the value on the loaded
1324 record. In this case, the method may be called on the class instead of an
1329 sub ObjectTypeFromLookupType {
1331 my $type = shift || $self->LookupType;
1332 my ($class) = ($type =~ /([^-]+)$/);
1334 if (blessed($self) and $self->LookupType eq $type) {
1336 "Custom Field #". $self->id
1337 ." has incorrect LookupType '$type'"
1340 RT->Logger->error("Invalid LookupType passed as argument: $type");
1347 sub CollectionClassFromLookupType {
1350 my $record_class = $self->RecordClassFromLookupType;
1351 return undef unless $record_class;
1353 my $collection_class;
1354 if ( UNIVERSAL::can($record_class.'Collection', 'new') ) {
1355 $collection_class = $record_class.'Collection';
1356 } elsif ( UNIVERSAL::can($record_class.'es', 'new') ) {
1357 $collection_class = $record_class.'es';
1358 } elsif ( UNIVERSAL::can($record_class.'s', 'new') ) {
1359 $collection_class = $record_class.'s';
1361 $RT::Logger->error("Can not find a collection class for record class '$record_class'");
1364 return $collection_class;
1369 Returns a (sorted and lowercased) list of the groupings in which this custom
1372 If called on a loaded object, the returned list is limited to groupings which
1373 apply to the record class this CF applies to (L</RecordClassFromLookupType>).
1375 If passed a loaded object or a class name, the returned list is limited to
1376 groupings which apply to the class of the object or the specified class.
1378 If called on an unloaded object, all potential groupings are returned.
1384 my $record_class = $self->_GroupingClass(shift);
1386 my $config = RT->Config->Get('CustomFieldGroupings');
1387 $config = {} unless ref($config) eq 'HASH';
1390 if ( $record_class ) {
1391 push @groups, sort {lc($a) cmp lc($b)} keys %{ $BUILTIN_GROUPINGS{$record_class} || {} };
1392 if ( ref($config->{$record_class} ||= []) eq "ARRAY") {
1393 my @order = @{ $config->{$record_class} };
1395 push @groups, shift(@order);
1399 @groups = sort {lc($a) cmp lc($b)} keys %{ $config->{$record_class} };
1402 my %all = (%$config, %BUILTIN_GROUPINGS);
1403 @groups = sort {lc($a) cmp lc($b)} map {$self->Groupings($_)} grep {$_} keys(%all);
1408 grep defined && length && !$seen{lc $_}++,
1412 =head2 CustomGroupings
1414 Identical to L</Groupings> but filters out built-in groupings from the the
1419 sub CustomGroupings {
1421 my $record_class = $self->_GroupingClass(shift);
1422 return grep !$BUILTIN_GROUPINGS{$record_class}{$_}, $self->Groupings( $record_class );
1425 sub _GroupingClass {
1429 my $record_class = ref($record) || $record || '';
1430 $record_class = $self->RecordClassFromLookupType
1431 if !$record_class and blessed($self) and $self->id;
1433 return $record_class;
1436 =head2 RegisterBuiltInGroupings
1438 Registers groupings to be considered a fundamental part of RT, either via use
1439 in core RT or via an extension. These groupings must be rendered explicitly in
1440 Mason by specific calls to F</Elements/ShowCustomFields> and
1441 F</Elements/EditCustomFields>. They will not show up automatically on normal
1442 display pages like configured custom groupings.
1444 Takes a set of key-value pairs of class names (valid L<RT::Record> subclasses)
1445 and array refs of grouping names to consider built-in.
1447 If a class already contains built-in groupings (such as L<RT::Ticket> and
1448 L<RT::User>), new groupings are appended.
1452 sub RegisterBuiltInGroupings {
1456 while (my ($k,$v) = each %new) {
1457 $v = [$v] unless ref($v) eq 'ARRAY';
1458 $BUILTIN_GROUPINGS{$k} = {
1459 %{$BUILTIN_GROUPINGS{$k} || {}},
1463 $BUILTIN_GROUPINGS{''} = { map { %$_ } values %BUILTIN_GROUPINGS };
1468 Certain custom fields (users, groups) should only be added globally;
1469 codify that set here for reference.
1476 return ($self->LookupType =~ /^RT::(?:Group|User)/io);
1481 Instead => "IsOnlyGlobal",
1484 return shift->IsOnlyGlobal(@_);
1489 Returns collection with objects this custom field is added to.
1490 Class of the collection depends on L</LookupType>.
1491 See all L</NotAddedTo> .
1493 Doesn't takes into account if object is added globally.
1499 return RT::ObjectCustomField->new( $self->CurrentUser )
1500 ->AddedTo( CustomField => $self );
1504 Instead => "AddedTo",
1512 Returns collection with objects this custom field is not added to.
1513 Class of the collection depends on L</LookupType>.
1514 See all L</AddedTo> .
1516 Doesn't take into account if the object is added globally.
1522 return RT::ObjectCustomField->new( $self->CurrentUser )
1523 ->NotAddedTo( CustomField => $self );
1527 Instead => "NotAddedTo",
1530 shift->NotAddedTo(@_)
1535 Takes object id and returns corresponding L<RT::ObjectCustomField>
1536 record if this custom field is added to the object. Use 0 to check
1537 if custom field is added globally.
1544 my $ocf = RT::ObjectCustomField->new( $self->CurrentUser );
1545 $ocf->LoadByCols( CustomField => $self->id, ObjectId => $id || 0 );
1546 return undef unless $ocf->id;
1551 Instead => "IsAdded",
1557 sub IsGlobal { return shift->IsAdded(0) }
1561 Returns true if custom field is applied to any object.
1568 my $ocf = RT::ObjectCustomField->new( $self->CurrentUser );
1569 $ocf->LoadByCols( CustomField => $self->id );
1570 return $ocf->id ? 1 : 0;
1573 =head2 AddToObject OBJECT
1575 Add this custom field as a custom field for a single object, such as a queue or group.
1584 my $id = $object->Id || 0;
1586 unless (index($self->LookupType, ref($object)) == 0) {
1587 return ( 0, $self->loc('Lookup type mismatch') );
1590 unless ( $object->CurrentUserHasRight('AssignCustomFields') ) {
1591 return ( 0, $self->loc('Permission Denied') );
1594 my $ocf = RT::ObjectCustomField->new( $self->CurrentUser );
1595 my ( $oid, $msg ) = $ocf->Add(
1596 CustomField => $self->id, ObjectId => $id,
1598 return ( $oid, $msg );
1602 =head2 RemoveFromObject OBJECT
1604 Remove this custom field for a single object, such as a queue or group.
1610 sub RemoveFromObject {
1613 my $id = $object->Id || 0;
1615 unless (index($self->LookupType, ref($object)) == 0) {
1616 return ( 0, $self->loc('Object type mismatch') );
1619 unless ( $object->CurrentUserHasRight('AssignCustomFields') ) {
1620 return ( 0, $self->loc('Permission Denied') );
1623 my $ocf = $self->IsAdded( $id );
1625 return ( 0, $self->loc("This custom field cannot be added to that object") );
1628 # XXX: Delete doesn't return anything
1629 my ( $oid, $msg ) = $ocf->Delete;
1630 return ( $oid, $msg );
1634 =head2 AddValueForObject HASH
1636 Adds a custom field value for a record object of some kind.
1637 Takes a param hash of
1651 sub AddValueForObject {
1656 LargeContent => undef,
1657 ContentType => undef,
1660 my $obj = $args{'Object'} or return ( 0, $self->loc('Invalid object') );
1662 unless ( $self->CurrentUserHasRight('ModifyCustomField') ) {
1663 return ( 0, $self->loc('Permission Denied') );
1666 unless ( $self->MatchPattern($args{'Content'}) ) {
1667 return ( 0, $self->loc('Input must match [_1]', $self->FriendlyPattern) );
1670 $RT::Handle->BeginTransaction;
1672 if ( $self->MaxValues ) {
1673 my $current_values = $self->ValuesForObject($obj);
1674 my $extra_values = ( $current_values->Count + 1 ) - $self->MaxValues;
1676 # (The +1 is for the new value we're adding)
1678 # If we have a set of current values and we've gone over the maximum
1679 # allowed number of values, we'll need to delete some to make room.
1680 # which former values are blown away is not guaranteed
1682 while ($extra_values) {
1683 my $extra_item = $current_values->Next;
1684 unless ( $extra_item->id ) {
1685 $RT::Logger->crit( "We were just asked to delete "
1686 ."a custom field value that doesn't exist!" );
1687 $RT::Handle->Rollback();
1690 $extra_item->Delete;
1695 my $newval = RT::ObjectCustomFieldValue->new( $self->CurrentUser );
1696 my ($val, $msg) = $newval->Create(
1697 ObjectType => ref($obj),
1698 ObjectId => $obj->Id,
1699 Content => $args{'Content'},
1700 LargeContent => $args{'LargeContent'},
1701 ContentType => $args{'ContentType'},
1702 CustomField => $self->Id
1706 $RT::Handle->Rollback();
1707 return ($val, $self->loc("Couldn't create record: [_1]", $msg));
1710 $RT::Handle->Commit();
1716 sub _CanonicalizeValue {
1720 my $type = $self->__Value('Type');
1721 return 1 unless $type;
1723 my $method = '_CanonicalizeValue'. $type;
1724 return 1 unless $self->can($method);
1725 $self->$method($args);
1728 sub _CanonicalizeValueDateTime {
1731 my $DateObj = RT::Date->new( $self->CurrentUser );
1732 $DateObj->Set( Format => 'unknown',
1733 Value => $args->{'Content'} );
1734 $args->{'Content'} = $DateObj->ISO;
1738 # For date, we need to store Content as ISO date
1739 sub _CanonicalizeValueDate {
1743 # in case user input date with time, let's omit it by setting timezone
1744 # to utc so "hour" won't affect "day"
1745 my $DateObj = RT::Date->new( $self->CurrentUser );
1746 $DateObj->Set( Format => 'unknown',
1747 Value => $args->{'Content'},
1749 $args->{'Content'} = $DateObj->Date( Timezone => 'user' );
1753 sub _CanonicalizeValueIPAddress {
1757 $args->{Content} = RT::ObjectCustomFieldValue->ParseIP( $args->{Content} );
1758 return (0, $self->loc("Content is not a valid IP address"))
1759 unless $args->{Content};
1763 sub _CanonicalizeValueIPAddressRange {
1767 my $content = $args->{Content};
1768 $content .= "-".$args->{LargeContent} if $args->{LargeContent};
1770 ($args->{Content}, $args->{LargeContent})
1771 = RT::ObjectCustomFieldValue->ParseIPRange( $content );
1773 $args->{ContentType} = 'text/plain';
1774 return (0, $self->loc("Content is not a valid IP address range"))
1775 unless $args->{Content};
1779 =head2 MatchPattern STRING
1781 Tests the incoming string against the Pattern of this custom field object
1782 and returns a boolean; returns true if the Pattern is empty.
1788 my $regex = $self->Pattern or return 1;
1790 return (( defined $_[0] ? $_[0] : '') =~ $regex);
1796 =head2 FriendlyPattern
1798 Prettify the pattern of this custom field, by taking the text in C<(?#text)>
1803 sub FriendlyPattern {
1805 my $regex = $self->Pattern;
1807 return '' unless length $regex;
1808 if ( $regex =~ /\(\?#([^)]*)\)/ ) {
1809 return '[' . $self->loc($1) . ']';
1819 =head2 DeleteValueForObject HASH
1821 Deletes a custom field value for a ticket. Takes a param hash of Object and Content
1823 Returns a tuple of (STATUS, MESSAGE). If the call succeeded, the STATUS is true. otherwise it's false
1827 sub DeleteValueForObject {
1829 my %args = ( Object => undef,
1835 unless ($self->CurrentUserHasRight('ModifyCustomField')) {
1836 return (0, $self->loc('Permission Denied'));
1839 my $oldval = RT::ObjectCustomFieldValue->new($self->CurrentUser);
1841 if (my $id = $args{'Id'}) {
1844 unless ($oldval->id) {
1845 $oldval->LoadByObjectContentAndCustomField(
1846 Object => $args{'Object'},
1847 Content => $args{'Content'},
1848 CustomField => $self->Id,
1853 # check to make sure we found it
1854 unless ($oldval->Id) {
1855 return(0, $self->loc("Custom field value [_1] could not be found for custom field [_2]", $args{'Content'}, $self->Name));
1858 # for single-value fields, we need to validate that empty string is a valid value for it
1859 if ( $self->SingleValue and not $self->MatchPattern( '' ) ) {
1860 return ( 0, $self->loc('Input must match [_1]', $self->FriendlyPattern) );
1865 my $ret = $oldval->Delete();
1867 return(0, $self->loc("Custom field value could not be found"));
1869 return($oldval->Id, $self->loc("Custom field value deleted"));
1873 =head2 ValuesForObject OBJECT
1875 Return an L<RT::ObjectCustomFieldValues> object containing all of this custom field's values for OBJECT
1879 sub ValuesForObject {
1883 my $values = RT::ObjectCustomFieldValues->new($self->CurrentUser);
1884 unless ($self->id and $self->CurrentUserHasRight('SeeCustomField')) {
1885 # Return an empty object if they have no rights to see
1886 $values->Limit( FIELD => "id", VALUE => 0, SUBCLAUSE => "ACL" );
1890 $values->LimitToCustomField($self->Id);
1891 $values->LimitToObject($object);
1897 =head2 RegisterLookupType LOOKUPTYPE FRIENDLYNAME
1899 Tell RT that a certain object accepts custom fields via a lookup type and
1900 provide a friendly name for such CFs.
1904 'RT::Queue-RT::Ticket' => "Tickets", # loc
1905 'RT::Queue-RT::Ticket-RT::Transaction' => "Ticket Transactions", # loc
1906 'RT::User' => "Users", # loc
1907 'RT::Group' => "Groups", # loc
1908 'RT::Queue' => "Queues", # loc
1910 This is a class method.
1914 sub RegisterLookupType {
1917 my $friendly_name = shift;
1919 $FRIENDLY_LOOKUP_TYPES{$path} = $friendly_name;
1922 sub _ForObjectType {
1924 Instead => 'RegisterLookupType',
1928 $self->RegisterLookupType(@_);
1932 =head2 IncludeContentForValue [VALUE] (and SetIncludeContentForValue)
1934 Gets or sets the C<IncludeContentForValue> for this custom field. RT
1935 uses this field to automatically include content into the user's browser
1936 as they display records with custom fields in RT.
1940 sub SetIncludeContentForValue {
1941 shift->IncludeContentForValue(@_);
1943 sub IncludeContentForValue{
1945 $self->_URLTemplate('IncludeContentForValue', @_);
1950 =head2 LinkValueTo [VALUE] (and SetLinkValueTo)
1952 Gets or sets the C<LinkValueTo> for this custom field. RT
1953 uses this field to make custom field values into hyperlinks in the user's
1954 browser as they display records with custom fields in RT.
1959 sub SetLinkValueTo {
1960 shift->LinkValueTo(@_);
1965 $self->_URLTemplate('LinkValueTo', @_);
1970 =head2 _URLTemplate NAME [VALUE]
1972 With one argument, returns the _URLTemplate named C<NAME>, but only if
1973 the current user has the right to see this custom field.
1975 With two arguments, attemptes to set the relevant template value.
1981 my $template_name = shift;
1985 unless ( $self->CurrentUserHasRight('AdminCustomField') ) {
1986 return ( 0, $self->loc('Permission Denied') );
1988 if (length $value and defined $value) {
1989 $self->SetAttribute( Name => $template_name, Content => $value );
1991 $self->DeleteAttribute( $template_name );
1993 return ( 1, $self->loc('Updated') );
1995 unless ( $self->id && $self->CurrentUserHasRight('SeeCustomField') ) {
1999 my ($attr) = $self->Attributes->Named($template_name);
2000 return undef unless $attr;
2001 return $attr->Content;
2009 return $self->_Set( Field => 'BasedOn', Value => $value, @_ )
2010 unless defined $value and length $value;
2012 my $cf = RT::CustomField->new( $self->CurrentUser );
2013 $cf->SetContextObject( $self->ContextObject );
2014 $cf->Load( ref $value ? $value->id : $value );
2016 return (0, "Permission Denied")
2017 unless $cf->id && $cf->CurrentUserHasRight('SeeCustomField');
2019 # XXX: Remove this restriction once we support lists and cascaded selects
2020 if ( $self->RenderType =~ /List/ ) {
2021 return (0, $self->loc("We can't currently render as a List when basing categories on another custom field. Please use another render type."));
2024 return $self->_Set( Field => 'BasedOn', Value => $value, @_ )
2030 my $obj = RT::CustomField->new( $self->CurrentUser );
2031 $obj->SetContextObject( $self->ContextObject );
2032 if ( $self->BasedOn ) {
2033 $obj->Load( $self->BasedOn );
2040 my $tag = $self->FirstAttribute( 'UILocation' );
2041 return $tag ? $tag->Content : '';
2048 return $self->SetAttribute( Name => 'UILocation', Content => $tag );
2051 return $self->DeleteAttribute('UILocation');
2057 $self->FirstAttribute('NoClone') ? 1 : '';
2064 return $self->SetAttribute( Name => 'NoClone', Content => 1 );
2066 return $self->DeleteAttribute('NoClone');
2073 Returns the current value of id.
2074 (In the database, id is stored as int(11).)
2082 Returns the current value of Name.
2083 (In the database, Name is stored as varchar(200).)
2087 =head2 SetName VALUE
2091 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2092 (In the database, Name will be stored as a varchar(200).)
2100 Returns the current value of Type.
2101 (In the database, Type is stored as varchar(200).)
2105 =head2 SetType VALUE
2109 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2110 (In the database, Type will be stored as a varchar(200).)
2118 Returns the current value of RenderType.
2119 (In the database, RenderType is stored as varchar(64).)
2123 =head2 SetRenderType VALUE
2126 Set RenderType to VALUE.
2127 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2128 (In the database, RenderType will be stored as a varchar(64).)
2136 Returns the current value of MaxValues.
2137 (In the database, MaxValues is stored as int(11).)
2141 =head2 SetMaxValues VALUE
2144 Set MaxValues to VALUE.
2145 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2146 (In the database, MaxValues will be stored as a int(11).)
2154 Returns the current value of Pattern.
2155 (In the database, Pattern is stored as text.)
2159 =head2 SetPattern VALUE
2162 Set Pattern to VALUE.
2163 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2164 (In the database, Pattern will be stored as a text.)
2172 Returns the current value of BasedOn.
2173 (In the database, BasedOn is stored as int(11).)
2177 =head2 SetBasedOn VALUE
2180 Set BasedOn to VALUE.
2181 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2182 (In the database, BasedOn will be stored as a int(11).)
2190 Returns the current value of Description.
2191 (In the database, Description is stored as varchar(255).)
2195 =head2 SetDescription VALUE
2198 Set Description to VALUE.
2199 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2200 (In the database, Description will be stored as a varchar(255).)
2208 Returns the current value of SortOrder.
2209 (In the database, SortOrder is stored as int(11).)
2213 =head2 SetSortOrder VALUE
2216 Set SortOrder to VALUE.
2217 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2218 (In the database, SortOrder will be stored as a int(11).)
2226 Returns the current value of LookupType.
2227 (In the database, LookupType is stored as varchar(255).)
2231 =head2 SetLookupType VALUE
2234 Set LookupType to VALUE.
2235 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2236 (In the database, LookupType will be stored as a varchar(255).)
2244 Returns the current value of Creator.
2245 (In the database, Creator is stored as int(11).)
2253 Returns the current value of Created.
2254 (In the database, Created is stored as datetime.)
2260 =head2 LastUpdatedBy
2262 Returns the current value of LastUpdatedBy.
2263 (In the database, LastUpdatedBy is stored as int(11).)
2271 Returns the current value of LastUpdated.
2272 (In the database, LastUpdated is stored as datetime.)
2280 Returns the current value of Disabled.
2281 (In the database, Disabled is stored as smallint(6).)
2285 =head2 SetDisabled VALUE
2288 Set Disabled to VALUE.
2289 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2290 (In the database, Disabled will be stored as a smallint(6).)
2297 sub _CoreAccessible {
2301 {read => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''},
2303 {read => 1, write => 1, sql_type => 12, length => 200, is_blob => 0, is_numeric => 0, type => 'varchar(200)', default => ''},
2305 {read => 1, write => 1, sql_type => 12, length => 200, is_blob => 0, is_numeric => 0, type => 'varchar(200)', default => ''},
2307 {read => 1, write => 1, sql_type => 12, length => 64, is_blob => 0, is_numeric => 0, type => 'varchar(64)', default => ''},
2309 {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''},
2311 {read => 1, write => 1, sql_type => -4, length => 0, is_blob => 1, is_numeric => 0, type => 'text', default => ''},
2313 {read => 1, write => 1, sql_type => 12, length => 64, is_blob => 0, is_numeric => 0, type => 'varchar(64)', default => ''},
2315 {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''},
2317 {read => 1, write => 1, sql_type => 12, length => 255, is_blob => 0, is_numeric => 0, type => 'varchar(255)', default => ''},
2319 {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
2321 {read => 1, write => 1, sql_type => 12, length => 255, is_blob => 0, is_numeric => 0, type => 'varchar(255)', default => ''},
2323 {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
2325 {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''},
2327 {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
2329 {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''},
2331 {read => 1, write => 1, sql_type => 5, length => 6, is_blob => 0, is_numeric => 1, type => 'smallint(6)', default => '0'},
2336 sub FindDependencies {
2338 my ($walker, $deps) = @_;
2340 $self->SUPER::FindDependencies($walker, $deps);
2342 $deps->Add( out => $self->BasedOnObj )
2343 if $self->BasedOnObj->id;
2345 my $applied = RT::ObjectCustomFields->new( $self->CurrentUser );
2346 $applied->LimitToCustomField( $self->id );
2347 $deps->Add( in => $applied );
2349 $deps->Add( in => $self->Values ) if $self->ValuesClass eq "RT::CustomFieldValues";
2356 Dependencies => undef,
2359 my $deps = $args{'Dependencies'};
2362 # Custom field values
2363 push( @$list, $self->Values );
2365 # Applications of this CF
2366 my $applied = RT::ObjectCustomFields->new( $self->CurrentUser );
2367 $applied->LimitToCustomField( $self->Id );
2368 push @$list, $applied;
2370 # Ticket custom field values
2371 my $objs = RT::ObjectCustomFieldValues->new( $self->CurrentUser );
2372 $objs->LimitToCustomField( $self->Id );
2373 push( @$list, $objs );
2375 $deps->_PushDependencies(
2376 BaseObject => $self,
2377 Flags => RT::Shredder::Constants::DEPENDS_ON,
2378 TargetObjects => $list,
2379 Shredder => $args{'Shredder'}
2381 return $self->SUPER::__DependsOn( %args );
2384 RT::Base->_ImportOverlays();