1 # BEGIN BPS TAGGED BLOCK {{{
5 # This software is Copyright (c) 1996-2011 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;
52 no warnings qw(redefine);
54 use RT::CustomFieldValues;
55 use RT::ObjectCustomFields;
56 use RT::ObjectCustomFieldValues;
61 'Select multiple values', # loc
62 'Select one value', # loc
63 'Select up to [_1] values', # loc
66 'Enter multiple values', # loc
67 'Enter one value', # loc
68 'Enter up to [_1] values', # loc
71 'Fill in multiple text areas', # loc
72 'Fill in one text area', # loc
73 'Fill in up to [_1] text areas',# loc
76 'Fill in multiple wikitext areas', # loc
77 'Fill in one wikitext area', # loc
78 'Fill in up to [_1] wikitext areas',# loc
81 'Upload multiple images', # loc
82 'Upload one image', # loc
83 'Upload up to [_1] images', # loc
86 'Upload multiple files', # loc
87 'Upload one file', # loc
88 'Upload up to [_1] files', # loc
91 'Combobox: Select or enter multiple values', # loc
92 'Combobox: Select or enter one value', # loc
93 'Combobox: Select or enter up to [_1] values', # loc
96 'Enter multiple values with autocompletion', # loc
97 'Enter one value with autocompletion', # loc
98 'Enter up to [_1] values with autocompletion', # loc
101 'Select multiple dates', # loc
103 'Select up to [_1] dates', # loc
106 'Enter multiple time values (UNSUPPORTED)',
107 'Enter a time value',
108 'Enter [_1] time values (UNSUPPORTED)',
113 our %FRIENDLY_OBJECT_TYPES = ();
115 RT::CustomField->_ForObjectType( 'RT::Queue-RT::Ticket' => "Tickets", ); #loc
116 RT::CustomField->_ForObjectType(
117 'RT::Queue-RT::Ticket-RT::Transaction' => "Ticket Transactions", ); #loc
118 RT::CustomField->_ForObjectType( 'RT::User' => "Users", ); #loc
119 RT::CustomField->_ForObjectType( 'RT::Queue' => "Queues", ); #loc
120 RT::CustomField->_ForObjectType( 'RT::Group' => "Groups", ); #loc
123 SeeCustomField => 'See custom fields', # loc_pair
124 AdminCustomField => 'Create, delete and modify custom fields', # loc_pair
125 AdminCustomFieldValues => 'Create, delete and modify custom fields values', # loc_pair
126 ModifyCustomField => 'Add, delete and modify custom field values for objects' #loc_pair
129 # Tell RT::ACE that this sort of object can get acls granted
130 $RT::ACE::OBJECT_TYPES{'RT::CustomField'} = 1;
132 foreach my $right ( keys %{$RIGHTS} ) {
133 $RT::ACE::LOWERCASERIGHTNAMES{ lc $right } = $right;
136 =head2 AddRights C<RIGHT>, C<DESCRIPTION> [, ...]
138 Adds the given rights to the list of possible rights. This method
139 should be called during server startup, not at runtime.
146 $RIGHTS = { %$RIGHTS, %new };
147 %RT::ACE::LOWERCASERIGHTNAMES = ( %RT::ACE::LOWERCASERIGHTNAMES,
148 map { lc($_) => $_ } keys %new);
151 sub AvailableRights {
158 RT::CustomField_Overlay - overlay for RT::CustomField
162 =head1 'CORE' METHODS
164 =head2 Create PARAMHASH
166 Create takes a hash of values and creates a row in the database:
171 varchar(255) 'Pattern'.
172 smallint(6) 'Repeated'.
173 varchar(255) 'Description'.
175 varchar(255) 'LookupType'.
176 smallint(6) 'Disabled'.
178 C<LookupType> is generally the result of either
179 C<RT::Ticket->CustomFieldLookupType> or C<RT::Transaction->CustomFieldLookupType>.
195 IncludeContentForValue => '',
199 unless ( $self->CurrentUser->HasRight(Object => $RT::System, Right => 'AdminCustomField') ) {
200 return (0, $self->loc('Permission Denied'));
203 if ( $args{TypeComposite} ) {
204 @args{'Type', 'MaxValues'} = split(/-/, $args{TypeComposite}, 2);
206 elsif ( $args{Type} =~ s/(?:(Single)|Multiple)$// ) {
207 # old style Type string
208 $args{'MaxValues'} = $1 ? 1 : 0;
210 $args{'MaxValues'} = int $args{'MaxValues'};
212 if ( !exists $args{'Queue'}) {
213 # do nothing -- things below are strictly backward compat
215 elsif ( ! $args{'Queue'} ) {
216 unless ( $self->CurrentUser->HasRight( Object => $RT::System, Right => 'AssignCustomFields') ) {
217 return ( 0, $self->loc('Permission Denied') );
219 $args{'LookupType'} = 'RT::Queue-RT::Ticket';
222 my $queue = RT::Queue->new($self->CurrentUser);
223 $queue->Load($args{'Queue'});
224 unless ($queue->Id) {
225 return (0, $self->loc("Queue not found"));
227 unless ( $queue->CurrentUserHasRight('AssignCustomFields') ) {
228 return ( 0, $self->loc('Permission Denied') );
230 $args{'LookupType'} = 'RT::Queue-RT::Ticket';
231 $args{'Queue'} = $queue->Id;
234 my ($ok, $msg) = $self->_IsValidRegex( $args{'Pattern'} );
235 return (0, $self->loc("Invalid pattern: [_1]", $msg)) unless $ok;
237 if ( $args{'MaxValues'} != 1 && $args{'Type'} =~ /(text|combobox)$/i ) {
238 $RT::Logger->warning("Support for 'multiple' Texts or Comboboxes is not implemented");
239 $args{'MaxValues'} = 1;
242 (my $rv, $msg) = $self->SUPER::Create(
243 Name => $args{'Name'},
244 Type => $args{'Type'},
245 MaxValues => $args{'MaxValues'},
246 Pattern => $args{'Pattern'},
247 Description => $args{'Description'},
248 Disabled => $args{'Disabled'},
249 LookupType => $args{'LookupType'},
250 Repeated => $args{'Repeated'},
253 if ( exists $args{'LinkValueTo'}) {
254 $self->SetLinkValueTo($args{'LinkValueTo'});
257 if ( exists $args{'IncludeContentForValue'}) {
258 $self->SetIncludeContentForValue($args{'IncludeContentForValue'});
261 if ( exists $args{'ValuesClass'} ) {
262 $self->SetValuesClass( $args{'ValuesClass'} );
265 if ( exists $args{'BasedOn'} ) {
266 $self->SetBasedOn( $args{'BasedOn'} );
269 if ( exists $args{'UILocation'} ) {
270 $self->SetUILocation( $args{'UILocation'} );
273 return ($rv, $msg) unless exists $args{'Queue'};
275 # Compat code -- create a new ObjectCustomField mapping
276 my $OCF = RT::ObjectCustomField->new( $self->CurrentUser );
278 CustomField => $self->Id,
279 ObjectId => $args{'Queue'},
287 Load a custom field. If the value handed in is an integer, load by custom field ID. Otherwise, Load by name.
293 my $id = shift || '';
295 if ( $id =~ /^\d+$/ ) {
296 return $self->SUPER::Load( $id );
298 return $self->LoadByName( Name => $id );
305 =head2 LoadByName (Queue => QUEUEID, Name => NAME)
307 Loads the Custom field named NAME.
309 Will load a Disabled Custom Field even if there is a non-disabled Custom Field
312 If a Queue parameter is specified, only look for ticket custom fields tied to that Queue.
314 If the Queue parameter is '0', look for global ticket custom fields.
316 If no queue parameter is specified, look for any and all custom fields with this name.
318 BUG/TODO, this won't let you specify that you only want user or group CFs.
322 # Compatibility for API change after 3.0 beta 1
323 *LoadNameAndQueue = \&LoadByName;
324 # Change after 3.4 beta.
325 *LoadByNameAndQueue = \&LoadByName;
335 unless ( defined $args{'Name'} && length $args{'Name'} ) {
336 $RT::Logger->error("Couldn't load Custom Field without Name");
337 return wantarray ? (0, $self->loc("No name provided")) : 0;
340 # if we're looking for a queue by name, make it a number
341 if ( defined $args{'Queue'} && ($args{'Queue'} =~ /\D/ || !$self->ContextObject) ) {
342 my $QueueObj = RT::Queue->new( $self->CurrentUser );
343 $QueueObj->Load( $args{'Queue'} );
344 $args{'Queue'} = $QueueObj->Id;
345 $self->SetContextObject( $QueueObj )
346 unless $self->ContextObject;
349 # XXX - really naive implementation. Slow. - not really. still just one query
351 my $CFs = RT::CustomFields->new( $self->CurrentUser );
352 $CFs->SetContextObject( $self->ContextObject );
353 my $field = $args{'Name'} =~ /\D/? 'Name' : 'id';
354 $CFs->Limit( FIELD => $field, VALUE => $args{'Name'}, CASESENSITIVE => 0);
355 # Don't limit to queue if queue is 0. Trying to do so breaks
356 # RT::Group type CFs.
357 if ( defined $args{'Queue'} ) {
358 $CFs->LimitToQueue( $args{'Queue'} );
361 # When loading by name, we _can_ load disabled fields, but prefer
362 # non-disabled fields.
365 { FIELD => "Disabled", ORDER => 'ASC' },
368 # We only want one entry.
369 $CFs->RowsPerPage(1);
371 # version before 3.8 just returns 0, so we need to test if wantarray to be
372 # backward compatible.
373 return wantarray ? (0, $self->loc("Not found")) : 0 unless my $first = $CFs->First;
375 return $self->LoadById( $first->id );
380 # {{{ Dealing with custom field values
383 =head2 Custom field values
387 Return a object (collection) of all acceptable values for this Custom Field.
388 Class of the object can vary and depends on the return value
389 of the C<ValuesClass> method.
393 *ValuesObj = \&Values;
398 my $class = $self->ValuesClass || 'RT::CustomFieldValues';
399 eval "require $class" or die "$@";
400 my $cf_values = $class->new( $self->CurrentUser );
401 # if the user has no rights, return an empty object
402 if ( $self->id && $self->CurrentUserHasRight( 'SeeCustomField') ) {
403 $cf_values->LimitToCustomField( $self->Id );
405 $cf_values->Limit( FIELD => 'id', VALUE => 0, SUBCLAUSE => 'acl' );
414 Create a new value for this CustomField. Takes a paramhash containing the elements Name, Description and SortOrder
422 unless ($self->CurrentUserHasRight('AdminCustomField') || $self->CurrentUserHasRight('AdminCustomFieldValues')) {
423 return (0, $self->loc('Permission Denied'));
427 if ( !defined $args{'Name'} || $args{'Name'} eq '' ) {
428 return (0, $self->loc("Can't add a custom field value without a name"));
431 my $newval = RT::CustomFieldValue->new( $self->CurrentUser );
432 return $newval->Create( %args, CustomField => $self->Id );
440 =head3 DeleteValue ID
442 Deletes a value from this custom field by id.
444 Does not remove this value for any article which has had it selected
451 unless ( $self->CurrentUserHasRight('AdminCustomField') || $self->CurrentUserHasRight('AdminCustomFieldValues') ) {
452 return (0, $self->loc('Permission Denied'));
455 my $val_to_del = RT::CustomFieldValue->new( $self->CurrentUser );
456 $val_to_del->Load( $id );
457 unless ( $val_to_del->Id ) {
458 return (0, $self->loc("Couldn't find that value"));
460 unless ( $val_to_del->CustomField == $self->Id ) {
461 return (0, $self->loc("That is not a value for this custom field"));
464 my $retval = $val_to_del->Delete;
466 return (0, $self->loc("Custom field value could not be deleted"));
468 return ($retval, $self->loc("Custom field value deleted"));
474 =head2 ValidateQueue Queue
476 Make sure that the queue specified is a valid queue name
484 return undef unless defined $id;
485 # 0 means "Global" null would _not_ be ok.
486 return 1 if $id eq '0';
488 my $q = RT::Queue->new( $RT::SystemUser );
490 return undef unless $q->id;
499 Retuns an array of the types of CustomField that are supported
504 return (keys %FieldTypes);
509 # {{{ IsSelectionType
511 =head2 IsSelectionType
513 Retuns a boolean value indicating whether the C<Values> method makes sense
514 to this Custom Field.
518 sub IsSelectionType {
520 my $type = @_? shift : $self->Type;
521 return undef unless $type;
523 $type =~ /(?:Select|Combobox|Autocomplete)/;
529 =head2 IsExternalValues
533 sub IsExternalValues {
535 my $selectable = $self->IsSelectionType( @_ );
536 return $selectable unless $selectable;
538 my $class = $self->ValuesClass;
539 return 0 if $class eq 'RT::CustomFieldValues';
545 return '' unless $self->IsSelectionType;
547 my $class = $self->FirstAttribute( 'ValuesClass' );
548 $class = $class->Content if $class;
549 return $class || 'RT::CustomFieldValues';
554 my $class = shift || 'RT::CustomFieldValues';
556 if( $class eq 'RT::CustomFieldValues' ) {
557 return $self->DeleteAttribute( 'ValuesClass' );
559 return $self->SetAttribute( Name => 'ValuesClass', Content => $class );
563 =head2 FriendlyType [TYPE, MAX_VALUES]
565 Returns a localized human-readable version of the custom field type.
566 If a custom field type is specified as the parameter, the friendly type for that type will be returned
573 my $type = @_ ? shift : $self->Type;
574 my $max = @_ ? shift : $self->MaxValues;
575 $max = 0 unless $max;
577 if (my $friendly_type = $FieldTypes{$type}[$max>2 ? 2 : $max]) {
578 return ( $self->loc( $friendly_type, $max ) );
581 return ( $self->loc( $type ) );
585 sub FriendlyTypeComposite {
587 my $composite = shift || $self->TypeComposite;
588 return $self->FriendlyType(split(/-/, $composite, 2));
592 =head2 ValidateType TYPE
594 Takes a single string. returns true if that string is a value
604 if ( $type =~ s/(?:Single|Multiple)$// ) {
605 $RT::Logger->warning( "Prefix 'Single' and 'Multiple' to Type deprecated, use MaxValues instead at (". join(":",caller).")");
608 if ( $FieldTypes{$type} ) {
620 if ($type =~ s/(?:(Single)|Multiple)$//) {
621 $RT::Logger->warning("'Single' and 'Multiple' on SetType deprecated, use SetMaxValues instead at (". join(":",caller).")");
622 $self->SetMaxValues($1 ? 1 : 0);
624 $self->SUPER::SetType($type);
627 =head2 SetPattern STRING
629 Takes a single string representing a regular expression. Performs basic
630 validation on that regex, and sets the C<Pattern> field for the CF if it
639 my ($ok, $msg) = $self->_IsValidRegex($regex);
641 return $self->SUPER::SetPattern($regex);
644 return (0, $self->loc("Invalid pattern: [_1]", $msg));
648 =head2 _IsValidRegex(Str $regex) returns (Bool $success, Str $msg)
650 Tests if the string contains an invalid regex.
656 my $regex = shift or return (1, 'valid');
659 local $SIG{__DIE__} = sub { 1 };
660 local $SIG{__WARN__} = sub { 1 };
662 if (eval { qr/$regex/; 1 }) {
667 $err =~ s{[,;].*}{}; # strip debug info from error
676 Returns true if this CustomField only accepts a single value.
677 Returns false if it accepts multiple values
683 if (($self->MaxValues||0) == 1) {
691 sub UnlimitedValues {
693 if (($self->MaxValues||0) == 0) {
703 =head2 CurrentUserHasRight RIGHT
705 Helper function to call the custom field's queue's CurrentUserHasRight with the passed in args.
709 sub CurrentUserHasRight {
713 return $self->CurrentUser->HasRight(
719 =head2 ACLEquivalenceObjects
721 Returns list of objects via which users can get rights on this custom field. For custom fields
722 these objects can be set using L<ContextObject|/"ContextObject and SetContextObject">.
726 sub ACLEquivalenceObjects {
729 my $ctx = $self->ContextObject
731 return ($ctx, $ctx->ACLEquivalenceObjects);
734 =head2 ContextObject and SetContextObject
736 Set or get a context for this object. It can be ticket, queue or another object
737 this CF applies to. Used for ACL control, for example SeeCustomField can be granted on
738 queue level to allow people to see all fields applied to the queue.
742 sub SetContextObject {
744 return $self->{'context_object'} = shift;
749 return $self->{'context_object'};
752 sub ValidContextType {
757 $valid{$_}++ for split '-', $self->LookupType;
758 delete $valid{'RT::Transaction'};
760 return $valid{$class};
763 =head2 LoadContextObject
765 Takes an Id for a Context Object and loads the right kind of RT::Object
766 for this particular Custom Field (based on the LookupType) and returns it.
767 This is a good way to ensure you don't try to use a Queue as a Context
768 Object on a User Custom Field.
772 sub LoadContextObject {
775 my $contextid = shift;
777 unless ( $self->ValidContextType($type) ) {
778 RT->Logger->debug("Invalid ContextType $type for Custom Field ".$self->Id);
782 my $context_object = $type->new( $self->CurrentUser );
783 my ($id, $msg) = $context_object->LoadById( $contextid );
785 RT->Logger->debug("Invalid ContextObject id: $msg");
788 return $context_object;
791 =head2 ValidateContextObject
793 Ensure that a given ContextObject applies to this Custom Field.
794 For custom fields that are assigned to Queues or to Classes, this checks that the Custom
795 Field is actually applied to that objects. For Global Custom Fields, it returns true
796 as long as the Object is of the right type, because you may be using
797 your permissions on a given Queue of Class to see a Global CF.
798 For CFs that are only applied Globally, you don't need a ContextObject.
802 sub ValidateContextObject {
806 return 1 if $self->IsApplied(0);
808 # global only custom fields don't have objects
809 # that should be used as context objects.
810 return if $self->ApplyGlobally;
812 # Otherwise, make sure we weren't passed a user object that we're
813 # supposed to treat as a queue.
814 return unless $self->ValidContextType(ref $object);
816 # Check that it is applied correctly
817 my ($applied_to) = grep {ref($_) eq $self->RecordClassFromLookupType} ($object, $object->ACLEquivalenceObjects);
818 return unless $applied_to;
819 return $self->IsApplied($applied_to->id);
827 unless ( $self->CurrentUserHasRight('AdminCustomField') ) {
828 return ( 0, $self->loc('Permission Denied') );
830 return $self->SUPER::_Set( @_ );
840 Takes the name of a table column.
841 Returns its value as a string, if the user passes an ACL check
847 return undef unless $self->id;
849 # we need to do the rights check
850 unless ( $self->CurrentUserHasRight('SeeCustomField') ) {
852 "Permission denied. User #". $self->CurrentUser->id
853 ." has no SeeCustomField right on CF #". $self->id
857 return $self->__Value( @_ );
861 # {{{ sub SetDisabled
866 1 will cause this custom field to no longer be avaialble for objects.
867 0 will re-enable this field.
873 =head2 SetTypeComposite
875 Set this custom field's type and maximum values as a composite value
879 sub SetTypeComposite {
881 my $composite = shift;
883 my $old = $self->TypeComposite;
885 my ($type, $max_values) = split(/-/, $composite, 2);
886 if ( $type ne $self->Type ) {
887 my ($status, $msg) = $self->SetType( $type );
888 return ($status, $msg) unless $status;
890 if ( ($max_values || 0) != ($self->MaxValues || 0) ) {
891 my ($status, $msg) = $self->SetMaxValues( $max_values );
892 return ($status, $msg) unless $status;
894 return 1, $self->loc(
895 "Type changed from '[_1]' to '[_2]'",
896 $self->FriendlyTypeComposite( $old ),
897 $self->FriendlyTypeComposite( $composite ),
903 Returns a composite value composed of this object's type and maximum values
910 return join '-', ($self->Type || ''), ($self->MaxValues || 0);
913 =head2 TypeComposites
915 Returns an array of all possible composite values for custom fields.
921 return grep !/(?:[Tt]ext|Combobox|Date|TimeValue)-0/, map { ("$_-1", "$_-0") } $self->Types;
926 Autrijus: care to doc how LookupTypes work?
933 if ( $lookup ne $self->LookupType ) {
934 # Okay... We need to invalidate our existing relationships
935 my $ObjectCustomFields = RT::ObjectCustomFields->new($self->CurrentUser);
936 $ObjectCustomFields->LimitToCustomField($self->Id);
937 $_->Delete foreach @{$ObjectCustomFields->ItemsArrayRef};
939 return $self->SUPER::SetLookupType($lookup);
944 Returns an array of LookupTypes available
951 return keys %FRIENDLY_OBJECT_TYPES;
954 my @FriendlyObjectTypes = (
955 "[_1] objects", # loc
956 "[_1]'s [_2] objects", # loc
957 "[_1]'s [_2]'s [_3] objects", # loc
960 =head2 FriendlyLookupType
962 Returns a localized description of the type of this custom field
966 sub FriendlyLookupType {
968 my $lookup = shift || $self->LookupType;
970 return ($self->loc( $FRIENDLY_OBJECT_TYPES{$lookup} ))
971 if (defined $FRIENDLY_OBJECT_TYPES{$lookup} );
973 my @types = map { s/^RT::// ? $self->loc($_) : $_ }
974 grep { defined and length }
975 split( /-/, $lookup )
977 return ( $self->loc( $FriendlyObjectTypes[$#types], @types ) );
980 sub RecordClassFromLookupType {
982 my ($class) = ($self->LookupType =~ /^([^-]+)/);
985 "Custom Field #". $self->id
986 ." has incorrect LookupType '". $self->LookupType ."'"
993 sub CollectionClassFromLookupType {
996 my $record_class = $self->RecordClassFromLookupType;
997 return undef unless $record_class;
999 my $collection_class;
1000 if ( UNIVERSAL::can($record_class.'Collection', 'new') ) {
1001 $collection_class = $record_class.'Collection';
1002 } elsif ( UNIVERSAL::can($record_class.'es', 'new') ) {
1003 $collection_class = $record_class.'es';
1004 } elsif ( UNIVERSAL::can($record_class.'s', 'new') ) {
1005 $collection_class = $record_class.'s';
1007 $RT::Logger->error("Can not find a collection class for record class '$record_class'");
1010 return $collection_class;
1015 Returns collection with objects this custom field is applied to.
1016 Class of the collection depends on L</LookupType>.
1017 See all L</NotAppliedTo> .
1019 Doesn't takes into account if object is applied globally.
1026 my ($res, $ocfs_alias) = $self->_AppliedTo;
1027 return $res unless $res;
1030 ALIAS => $ocfs_alias,
1032 OPERATOR => 'IS NOT',
1041 Returns collection with objects this custom field is not applied to.
1042 Class of the collection depends on L</LookupType>.
1043 See all L</AppliedTo> .
1045 Doesn't takes into account if object is applied globally.
1052 my ($res, $ocfs_alias) = $self->_AppliedTo;
1053 return $res unless $res;
1056 ALIAS => $ocfs_alias,
1068 my ($class) = $self->CollectionClassFromLookupType;
1069 return undef unless $class;
1071 my $res = $class->new( $self->CurrentUser );
1073 # If CF is a Group CF, only display user-defined groups
1074 if ( $class eq 'RT::Groups' ) {
1075 $res->LimitToUserDefinedGroups;
1078 $res->OrderBy( FIELD => 'Name' );
1079 my $ocfs_alias = $res->Join(
1083 TABLE2 => 'ObjectCustomFields',
1084 FIELD2 => 'ObjectId',
1087 LEFTJOIN => $ocfs_alias,
1088 ALIAS => $ocfs_alias,
1089 FIELD => 'CustomField',
1092 return ($res, $ocfs_alias);
1097 Takes object id and returns corresponding L<RT::ObjectCustomField>
1098 record if this custom field is applied to the object. Use 0 to check
1099 if custom field is applied globally.
1106 my $ocf = RT::ObjectCustomField->new( $self->CurrentUser );
1107 $ocf->LoadByCols( CustomField => $self->id, ObjectId => $id || 0 );
1108 return undef unless $ocf->id;
1112 =head2 AddToObject OBJECT
1114 Add this custom field as a custom field for a single object, such as a queue or group.
1124 my $id = $object->Id || 0;
1126 unless (index($self->LookupType, ref($object)) == 0) {
1127 return ( 0, $self->loc('Lookup type mismatch') );
1130 unless ( $object->CurrentUserHasRight('AssignCustomFields') ) {
1131 return ( 0, $self->loc('Permission Denied') );
1134 if ( $self->IsApplied( $id ) ) {
1135 return ( 0, $self->loc("Custom field is already applied to the object") );
1140 return (0, $self->loc("Couldn't apply custom field to an object as it's global already") )
1141 if $self->IsApplied( 0 );
1144 my $applied = RT::ObjectCustomFields->new( $self->CurrentUser );
1145 $applied->LimitToCustomField( $self->id );
1146 while ( my $record = $applied->Next ) {
1151 my $ocf = RT::ObjectCustomField->new( $self->CurrentUser );
1152 my ( $oid, $msg ) = $ocf->Create(
1153 ObjectId => $id, CustomField => $self->id,
1155 return ( $oid, $msg );
1159 =head2 RemoveFromObject OBJECT
1161 Remove this custom field for a single object, such as a queue or group.
1167 sub RemoveFromObject {
1170 my $id = $object->Id || 0;
1172 unless (index($self->LookupType, ref($object)) == 0) {
1173 return ( 0, $self->loc('Object type mismatch') );
1176 unless ( $object->CurrentUserHasRight('AssignCustomFields') ) {
1177 return ( 0, $self->loc('Permission Denied') );
1180 my $ocf = $self->IsApplied( $id );
1182 return ( 0, $self->loc("This custom field does not apply to that object") );
1185 # XXX: Delete doesn't return anything
1186 my ( $oid, $msg ) = $ocf->Delete;
1187 return ( $oid, $msg );
1190 # {{{ AddValueForObject
1192 =head2 AddValueForObject HASH
1194 Adds a custom field value for a record object of some kind.
1195 Takes a param hash of
1209 sub AddValueForObject {
1214 LargeContent => undef,
1215 ContentType => undef,
1218 my $obj = $args{'Object'} or return ( 0, $self->loc('Invalid object') );
1220 unless ( $self->CurrentUserHasRight('ModifyCustomField') ) {
1221 return ( 0, $self->loc('Permission Denied') );
1224 unless ( $self->MatchPattern($args{'Content'}) ) {
1225 return ( 0, $self->loc('Input must match [_1]', $self->FriendlyPattern) );
1228 $RT::Handle->BeginTransaction;
1230 if ( $self->MaxValues ) {
1231 my $current_values = $self->ValuesForObject($obj);
1232 my $extra_values = ( $current_values->Count + 1 ) - $self->MaxValues;
1234 # (The +1 is for the new value we're adding)
1236 # If we have a set of current values and we've gone over the maximum
1237 # allowed number of values, we'll need to delete some to make room.
1238 # which former values are blown away is not guaranteed
1240 while ($extra_values) {
1241 my $extra_item = $current_values->Next;
1242 unless ( $extra_item->id ) {
1243 $RT::Logger->crit( "We were just asked to delete "
1244 ."a custom field value that doesn't exist!" );
1245 $RT::Handle->Rollback();
1248 $extra_item->Delete;
1252 # For date, we need to store Content as ISO date
1253 if ($self->Type eq 'Date') {
1254 my $DateObj = new RT::Date( $self->CurrentUser );
1256 Format => 'unknown',
1257 Value => $args{'Content'},
1259 $args{'Content'} = $DateObj->ISO;
1261 my $newval = RT::ObjectCustomFieldValue->new( $self->CurrentUser );
1262 my $val = $newval->Create(
1263 ObjectType => ref($obj),
1264 ObjectId => $obj->Id,
1265 Content => $args{'Content'},
1266 LargeContent => $args{'LargeContent'},
1267 ContentType => $args{'ContentType'},
1268 CustomField => $self->Id
1272 $RT::Handle->Rollback();
1273 return ($val, $self->loc("Couldn't create record"));
1276 $RT::Handle->Commit();
1285 =head2 MatchPattern STRING
1287 Tests the incoming string against the Pattern of this custom field object
1288 and returns a boolean; returns true if the Pattern is empty.
1294 my $regex = $self->Pattern or return 1;
1296 return (( defined $_[0] ? $_[0] : '') =~ $regex);
1302 # {{{ FriendlyPattern
1304 =head2 FriendlyPattern
1306 Prettify the pattern of this custom field, by taking the text in C<(?#text)>
1311 sub FriendlyPattern {
1313 my $regex = $self->Pattern;
1315 return '' unless length $regex;
1316 if ( $regex =~ /\(\?#([^)]*)\)/ ) {
1317 return '[' . $self->loc($1) . ']';
1327 # {{{ DeleteValueForObject
1329 =head2 DeleteValueForObject HASH
1331 Deletes a custom field value for a ticket. Takes a param hash of Object and Content
1333 Returns a tuple of (STATUS, MESSAGE). If the call succeeded, the STATUS is true. otherwise it's false
1337 sub DeleteValueForObject {
1339 my %args = ( Object => undef,
1345 unless ($self->CurrentUserHasRight('ModifyCustomField')) {
1346 return (0, $self->loc('Permission Denied'));
1349 my $oldval = RT::ObjectCustomFieldValue->new($self->CurrentUser);
1351 if (my $id = $args{'Id'}) {
1354 unless ($oldval->id) {
1355 $oldval->LoadByObjectContentAndCustomField(
1356 Object => $args{'Object'},
1357 Content => $args{'Content'},
1358 CustomField => $self->Id,
1363 # check to make sure we found it
1364 unless ($oldval->Id) {
1365 return(0, $self->loc("Custom field value [_1] could not be found for custom field [_2]", $args{'Content'}, $self->Name));
1368 # for single-value fields, we need to validate that empty string is a valid value for it
1369 if ( $self->SingleValue and not $self->MatchPattern( '' ) ) {
1370 return ( 0, $self->loc('Input must match [_1]', $self->FriendlyPattern) );
1375 my $ret = $oldval->Delete();
1377 return(0, $self->loc("Custom field value could not be found"));
1379 return($oldval->Id, $self->loc("Custom field value deleted"));
1383 =head2 ValuesForObject OBJECT
1385 Return an L<RT::ObjectCustomFieldValues> object containing all of this custom field's values for OBJECT
1389 sub ValuesForObject {
1393 my $values = new RT::ObjectCustomFieldValues($self->CurrentUser);
1394 unless ($self->CurrentUserHasRight('SeeCustomField')) {
1395 # Return an empty object if they have no rights to see
1400 $values->LimitToCustomField($self->Id);
1401 $values->LimitToEnabled();
1402 $values->LimitToObject($object);
1408 =head2 _ForObjectType PATH FRIENDLYNAME
1410 Tell RT that a certain object accepts custom fields
1414 'RT::Queue-RT::Ticket' => "Tickets", # loc
1415 'RT::Queue-RT::Ticket-RT::Transaction' => "Ticket Transactions", # loc
1416 'RT::User' => "Users", # loc
1417 'RT::Group' => "Groups", # loc
1419 This is a class method.
1423 sub _ForObjectType {
1426 my $friendly_name = shift;
1428 $FRIENDLY_OBJECT_TYPES{$path} = $friendly_name;
1433 =head2 IncludeContentForValue [VALUE] (and SetIncludeContentForValue)
1435 Gets or sets the C<IncludeContentForValue> for this custom field. RT
1436 uses this field to automatically include content into the user's browser
1437 as they display records with custom fields in RT.
1441 sub SetIncludeContentForValue {
1442 shift->IncludeContentForValue(@_);
1444 sub IncludeContentForValue{
1446 $self->_URLTemplate('IncludeContentForValue', @_);
1451 =head2 LinkValueTo [VALUE] (and SetLinkValueTo)
1453 Gets or sets the C<LinkValueTo> for this custom field. RT
1454 uses this field to make custom field values into hyperlinks in the user's
1455 browser as they display records with custom fields in RT.
1460 sub SetLinkValueTo {
1461 shift->LinkValueTo(@_);
1466 $self->_URLTemplate('LinkValueTo', @_);
1471 =head2 _URLTemplate NAME [VALUE]
1473 With one argument, returns the _URLTemplate named C<NAME>, but only if
1474 the current user has the right to see this custom field.
1476 With two arguments, attemptes to set the relevant template value.
1482 my $template_name = shift;
1486 unless ( $self->CurrentUserHasRight('AdminCustomField') ) {
1487 return ( 0, $self->loc('Permission Denied') );
1489 $self->SetAttribute( Name => $template_name, Content => $value );
1490 return ( 1, $self->loc('Updated') );
1492 unless ( $self->id && $self->CurrentUserHasRight('SeeCustomField') ) {
1496 my @attr = $self->Attributes->Named($template_name);
1497 my $attr = shift @attr;
1499 if ($attr) { return $attr->Content }
1508 return $self->DeleteAttribute( "BasedOn" )
1509 unless defined $value and length $value;
1511 my $cf = RT::CustomField->new( $self->CurrentUser );
1512 $cf->SetContextObject( $self->ContextObject );
1513 $cf->Load( ref $value ? $value->Id : $value );
1515 return (0, "Permission denied")
1516 unless $cf->Id && $cf->CurrentUserHasRight('SeeCustomField');
1518 return $self->SetAttribute(
1520 Description => "Custom field whose CF we depend on",
1527 my $obj = RT::CustomField->new( $self->CurrentUser );
1528 $obj->SetContextObject( $self->ContextObject );
1530 my $attribute = $self->FirstAttribute("BasedOn");
1531 $obj->Load($attribute->Content) if defined $attribute;
1537 my $tag = $self->FirstAttribute( 'UILocation' );
1538 return $tag ? $tag->Content : '';
1545 return $self->SetAttribute( Name => 'UILocation', Content => $tag );
1548 return $self->DeleteAttribute('UILocation');