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
108 our %FRIENDLY_OBJECT_TYPES = ();
110 RT::CustomField->_ForObjectType( 'RT::Queue-RT::Ticket' => "Tickets", ); #loc
111 RT::CustomField->_ForObjectType(
112 'RT::Queue-RT::Ticket-RT::Transaction' => "Ticket Transactions", ); #loc
113 RT::CustomField->_ForObjectType( 'RT::User' => "Users", ); #loc
114 RT::CustomField->_ForObjectType( 'RT::Queue' => "Queues", ); #loc
115 RT::CustomField->_ForObjectType( 'RT::Group' => "Groups", ); #loc
118 SeeCustomField => 'See custom fields', # loc_pair
119 AdminCustomField => 'Create, delete and modify custom fields', # loc_pair
120 AdminCustomFieldValues => 'Create, delete and modify custom fields values', # loc_pair
121 ModifyCustomField => 'Add, delete and modify custom field values for objects' #loc_pair
124 # Tell RT::ACE that this sort of object can get acls granted
125 $RT::ACE::OBJECT_TYPES{'RT::CustomField'} = 1;
127 foreach my $right ( keys %{$RIGHTS} ) {
128 $RT::ACE::LOWERCASERIGHTNAMES{ lc $right } = $right;
131 =head2 AddRights C<RIGHT>, C<DESCRIPTION> [, ...]
133 Adds the given rights to the list of possible rights. This method
134 should be called during server startup, not at runtime.
141 $RIGHTS = { %$RIGHTS, %new };
142 %RT::ACE::LOWERCASERIGHTNAMES = ( %RT::ACE::LOWERCASERIGHTNAMES,
143 map { lc($_) => $_ } keys %new);
146 sub AvailableRights {
153 RT::CustomField_Overlay - overlay for RT::CustomField
157 =head1 'CORE' METHODS
159 =head2 Create PARAMHASH
161 Create takes a hash of values and creates a row in the database:
166 varchar(255) 'Pattern'.
167 smallint(6) 'Repeated'.
168 varchar(255) 'Description'.
170 varchar(255) 'LookupType'.
171 smallint(6) 'Disabled'.
173 C<LookupType> is generally the result of either
174 C<RT::Ticket->CustomFieldLookupType> or C<RT::Transaction->CustomFieldLookupType>.
190 IncludeContentForValue => '',
194 unless ( $self->CurrentUser->HasRight(Object => $RT::System, Right => 'AdminCustomField') ) {
195 return (0, $self->loc('Permission Denied'));
198 if ( $args{TypeComposite} ) {
199 @args{'Type', 'MaxValues'} = split(/-/, $args{TypeComposite}, 2);
201 elsif ( $args{Type} =~ s/(?:(Single)|Multiple)$// ) {
202 # old style Type string
203 $args{'MaxValues'} = $1 ? 1 : 0;
205 $args{'MaxValues'} = int $args{'MaxValues'};
207 if ( !exists $args{'Queue'}) {
208 # do nothing -- things below are strictly backward compat
210 elsif ( ! $args{'Queue'} ) {
211 unless ( $self->CurrentUser->HasRight( Object => $RT::System, Right => 'AssignCustomFields') ) {
212 return ( 0, $self->loc('Permission Denied') );
214 $args{'LookupType'} = 'RT::Queue-RT::Ticket';
217 my $queue = RT::Queue->new($self->CurrentUser);
218 $queue->Load($args{'Queue'});
219 unless ($queue->Id) {
220 return (0, $self->loc("Queue not found"));
222 unless ( $queue->CurrentUserHasRight('AssignCustomFields') ) {
223 return ( 0, $self->loc('Permission Denied') );
225 $args{'LookupType'} = 'RT::Queue-RT::Ticket';
226 $args{'Queue'} = $queue->Id;
229 my ($ok, $msg) = $self->_IsValidRegex( $args{'Pattern'} );
230 return (0, $self->loc("Invalid pattern: [_1]", $msg)) unless $ok;
232 if ( $args{'MaxValues'} != 1 && $args{'Type'} =~ /(text|combobox)$/i ) {
233 $RT::Logger->warning("Support for 'multiple' Texts or Comboboxes is not implemented");
234 $args{'MaxValues'} = 1;
237 (my $rv, $msg) = $self->SUPER::Create(
238 Name => $args{'Name'},
239 Type => $args{'Type'},
240 MaxValues => $args{'MaxValues'},
241 Pattern => $args{'Pattern'},
242 Description => $args{'Description'},
243 Disabled => $args{'Disabled'},
244 LookupType => $args{'LookupType'},
245 Repeated => $args{'Repeated'},
248 if ( exists $args{'LinkValueTo'}) {
249 $self->SetLinkValueTo($args{'LinkValueTo'});
252 if ( exists $args{'IncludeContentForValue'}) {
253 $self->SetIncludeContentForValue($args{'IncludeContentForValue'});
256 if ( exists $args{'ValuesClass'} ) {
257 $self->SetValuesClass( $args{'ValuesClass'} );
260 if ( exists $args{'BasedOn'} ) {
261 $self->SetBasedOn( $args{'BasedOn'} );
264 return ($rv, $msg) unless exists $args{'Queue'};
266 # Compat code -- create a new ObjectCustomField mapping
267 my $OCF = RT::ObjectCustomField->new( $self->CurrentUser );
269 CustomField => $self->Id,
270 ObjectId => $args{'Queue'},
278 Load a custom field. If the value handed in is an integer, load by custom field ID. Otherwise, Load by name.
284 my $id = shift || '';
286 if ( $id =~ /^\d+$/ ) {
287 return $self->SUPER::Load( $id );
289 return $self->LoadByName( Name => $id );
296 =head2 LoadByName (Queue => QUEUEID, Name => NAME)
298 Loads the Custom field named NAME.
300 Will load a Disabled Custom Field even if there is a non-disabled Custom Field
303 If a Queue parameter is specified, only look for ticket custom fields tied to that Queue.
305 If the Queue parameter is '0', look for global ticket custom fields.
307 If no queue parameter is specified, look for any and all custom fields with this name.
309 BUG/TODO, this won't let you specify that you only want user or group CFs.
313 # Compatibility for API change after 3.0 beta 1
314 *LoadNameAndQueue = \&LoadByName;
315 # Change after 3.4 beta.
316 *LoadByNameAndQueue = \&LoadByName;
326 unless ( defined $args{'Name'} && length $args{'Name'} ) {
327 $RT::Logger->error("Couldn't load Custom Field without Name");
328 return wantarray ? (0, $self->loc("No name provided")) : 0;
331 # if we're looking for a queue by name, make it a number
332 if ( defined $args{'Queue'} && $args{'Queue'} =~ /\D/ ) {
333 my $QueueObj = RT::Queue->new( $self->CurrentUser );
334 $QueueObj->Load( $args{'Queue'} );
335 $args{'Queue'} = $QueueObj->Id;
338 # XXX - really naive implementation. Slow. - not really. still just one query
340 my $CFs = RT::CustomFields->new( $self->CurrentUser );
341 $CFs->SetContextObject( $self->ContextObject );
342 my $field = $args{'Name'} =~ /\D/? 'Name' : 'id';
343 $CFs->Limit( FIELD => $field, VALUE => $args{'Name'}, CASESENSITIVE => 0);
344 # Don't limit to queue if queue is 0. Trying to do so breaks
345 # RT::Group type CFs.
346 if ( defined $args{'Queue'} ) {
347 $CFs->LimitToQueue( $args{'Queue'} );
350 # When loading by name, we _can_ load disabled fields, but prefer
351 # non-disabled fields.
354 { FIELD => "Disabled", ORDER => 'ASC' },
357 # We only want one entry.
358 $CFs->RowsPerPage(1);
360 # version before 3.8 just returns 0, so we need to test if wantarray to be
361 # backward compatible.
362 return wantarray ? (0, $self->loc("Not found")) : 0 unless my $first = $CFs->First;
364 return $self->LoadById( $first->id );
369 # {{{ Dealing with custom field values
372 =head2 Custom field values
376 Return a object (collection) of all acceptable values for this Custom Field.
377 Class of the object can vary and depends on the return value
378 of the C<ValuesClass> method.
382 *ValuesObj = \&Values;
387 my $class = $self->ValuesClass || 'RT::CustomFieldValues';
388 eval "require $class" or die "$@";
389 my $cf_values = $class->new( $self->CurrentUser );
390 # if the user has no rights, return an empty object
391 if ( $self->id && $self->CurrentUserHasRight( 'SeeCustomField') ) {
392 $cf_values->LimitToCustomField( $self->Id );
401 Create a new value for this CustomField. Takes a paramhash containing the elements Name, Description and SortOrder
409 unless ($self->CurrentUserHasRight('AdminCustomField') || $self->CurrentUserHasRight('AdminCustomFieldValues')) {
410 return (0, $self->loc('Permission Denied'));
414 if ( !defined $args{'Name'} || $args{'Name'} eq '' ) {
415 return (0, $self->loc("Can't add a custom field value without a name"));
418 my $newval = RT::CustomFieldValue->new( $self->CurrentUser );
419 return $newval->Create( %args, CustomField => $self->Id );
427 =head3 DeleteValue ID
429 Deletes a value from this custom field by id.
431 Does not remove this value for any article which has had it selected
438 unless ( $self->CurrentUserHasRight('AdminCustomField') || $self->CurrentUserHasRight('AdminCustomFieldValues') ) {
439 return (0, $self->loc('Permission Denied'));
442 my $val_to_del = RT::CustomFieldValue->new( $self->CurrentUser );
443 $val_to_del->Load( $id );
444 unless ( $val_to_del->Id ) {
445 return (0, $self->loc("Couldn't find that value"));
447 unless ( $val_to_del->CustomField == $self->Id ) {
448 return (0, $self->loc("That is not a value for this custom field"));
451 my $retval = $val_to_del->Delete;
453 return (0, $self->loc("Custom field value could not be deleted"));
455 return ($retval, $self->loc("Custom field value deleted"));
461 =head2 ValidateQueue Queue
463 Make sure that the queue specified is a valid queue name
471 return undef unless defined $id;
472 # 0 means "Global" null would _not_ be ok.
473 return 1 if $id eq '0';
475 my $q = RT::Queue->new( $RT::SystemUser );
477 return undef unless $q->id;
486 Retuns an array of the types of CustomField that are supported
491 return (keys %FieldTypes);
496 # {{{ IsSelectionType
498 =head2 IsSelectionType
500 Retuns a boolean value indicating whether the C<Values> method makes sense
501 to this Custom Field.
505 sub IsSelectionType {
507 my $type = @_? shift : $self->Type;
508 return undef unless $type;
510 $type =~ /(?:Select|Combobox|Autocomplete)/;
516 =head2 IsExternalValues
520 sub IsExternalValues {
522 my $selectable = $self->IsSelectionType( @_ );
523 return $selectable unless $selectable;
525 my $class = $self->ValuesClass;
526 return 0 if $class eq 'RT::CustomFieldValues';
532 return '' unless $self->IsSelectionType;
534 my $class = $self->FirstAttribute( 'ValuesClass' );
535 $class = $class->Content if $class;
536 return $class || 'RT::CustomFieldValues';
541 my $class = shift || 'RT::CustomFieldValues';
543 if( $class eq 'RT::CustomFieldValues' ) {
544 return $self->DeleteAttribute( 'ValuesClass' );
546 return $self->SetAttribute( Name => 'ValuesClass', Content => $class );
550 =head2 FriendlyType [TYPE, MAX_VALUES]
552 Returns a localized human-readable version of the custom field type.
553 If a custom field type is specified as the parameter, the friendly type for that type will be returned
560 my $type = @_ ? shift : $self->Type;
561 my $max = @_ ? shift : $self->MaxValues;
562 $max = 0 unless $max;
564 if (my $friendly_type = $FieldTypes{$type}[$max>2 ? 2 : $max]) {
565 return ( $self->loc( $friendly_type, $max ) );
568 return ( $self->loc( $type ) );
572 sub FriendlyTypeComposite {
574 my $composite = shift || $self->TypeComposite;
575 return $self->FriendlyType(split(/-/, $composite, 2));
579 =head2 ValidateType TYPE
581 Takes a single string. returns true if that string is a value
591 if ( $type =~ s/(?:Single|Multiple)$// ) {
592 $RT::Logger->warning( "Prefix 'Single' and 'Multiple' to Type deprecated, use MaxValues instead at (". join(":",caller).")");
595 if ( $FieldTypes{$type} ) {
607 if ($type =~ s/(?:(Single)|Multiple)$//) {
608 $RT::Logger->warning("'Single' and 'Multiple' on SetType deprecated, use SetMaxValues instead at (". join(":",caller).")");
609 $self->SetMaxValues($1 ? 1 : 0);
611 $self->SUPER::SetType($type);
614 =head2 SetPattern STRING
616 Takes a single string representing a regular expression. Performs basic
617 validation on that regex, and sets the C<Pattern> field for the CF if it
626 my ($ok, $msg) = $self->_IsValidRegex($regex);
628 return $self->SUPER::SetPattern($regex);
631 return (0, $self->loc("Invalid pattern: [_1]", $msg));
635 =head2 _IsValidRegex(Str $regex) returns (Bool $success, Str $msg)
637 Tests if the string contains an invalid regex.
643 my $regex = shift or return (1, 'valid');
646 local $SIG{__DIE__} = sub { 1 };
647 local $SIG{__WARN__} = sub { 1 };
649 if (eval { qr/$regex/; 1 }) {
654 $err =~ s{[,;].*}{}; # strip debug info from error
663 Returns true if this CustomField only accepts a single value.
664 Returns false if it accepts multiple values
670 if (($self->MaxValues||0) == 1) {
678 sub UnlimitedValues {
680 if (($self->MaxValues||0) == 0) {
690 =head2 CurrentUserHasRight RIGHT
692 Helper function to call the custom field's queue's CurrentUserHasRight with the passed in args.
696 sub CurrentUserHasRight {
700 return $self->CurrentUser->HasRight(
706 =head2 ACLEquivalenceObjects
708 Returns list of objects via which users can get rights on this custom field. For custom fields
709 these objects can be set using L<ContextObject|/"ContextObject and SetContextObject">.
713 sub ACLEquivalenceObjects {
716 my $ctx = $self->ContextObject
718 return ($ctx, $ctx->ACLEquivalenceObjects);
721 =head2 ContextObject and SetContextObject
723 Set or get a context for this object. It can be ticket, queue or another object
724 this CF applies to. Used for ACL control, for example SeeCustomField can be granted on
725 queue level to allow people to see all fields applied to the queue.
729 sub SetContextObject {
731 return $self->{'context_object'} = shift;
736 return $self->{'context_object'};
744 unless ( $self->CurrentUserHasRight('AdminCustomField') ) {
745 return ( 0, $self->loc('Permission Denied') );
747 return $self->SUPER::_Set( @_ );
757 Takes the name of a table column.
758 Returns its value as a string, if the user passes an ACL check
764 return undef unless $self->id;
766 # we need to do the rights check
767 unless ( $self->CurrentUserHasRight('SeeCustomField') ) {
769 "Permission denied. User #". $self->CurrentUser->id
770 ." has no SeeCustomField right on CF #". $self->id
774 return $self->__Value( @_ );
778 # {{{ sub SetDisabled
783 1 will cause this custom field to no longer be avaialble for objects.
784 0 will re-enable this field.
790 =head2 SetTypeComposite
792 Set this custom field's type and maximum values as a composite value
796 sub SetTypeComposite {
798 my $composite = shift;
800 my $old = $self->TypeComposite;
802 my ($type, $max_values) = split(/-/, $composite, 2);
803 if ( $type ne $self->Type ) {
804 my ($status, $msg) = $self->SetType( $type );
805 return ($status, $msg) unless $status;
807 if ( ($max_values || 0) != ($self->MaxValues || 0) ) {
808 my ($status, $msg) = $self->SetMaxValues( $max_values );
809 return ($status, $msg) unless $status;
811 return 1, $self->loc(
812 "Type changed from '[_1]' to '[_2]'",
813 $self->FriendlyTypeComposite( $old ),
814 $self->FriendlyTypeComposite( $composite ),
820 Returns a composite value composed of this object's type and maximum values
827 return join '-', ($self->Type || ''), ($self->MaxValues || 0);
830 =head2 TypeComposites
832 Returns an array of all possible composite values for custom fields.
838 return grep !/(?:[Tt]ext|Combobox|Date)-0/, map { ("$_-1", "$_-0") } $self->Types;
843 Autrijus: care to doc how LookupTypes work?
850 if ( $lookup ne $self->LookupType ) {
851 # Okay... We need to invalidate our existing relationships
852 my $ObjectCustomFields = RT::ObjectCustomFields->new($self->CurrentUser);
853 $ObjectCustomFields->LimitToCustomField($self->Id);
854 $_->Delete foreach @{$ObjectCustomFields->ItemsArrayRef};
856 return $self->SUPER::SetLookupType($lookup);
861 Returns an array of LookupTypes available
868 return keys %FRIENDLY_OBJECT_TYPES;
871 my @FriendlyObjectTypes = (
872 "[_1] objects", # loc
873 "[_1]'s [_2] objects", # loc
874 "[_1]'s [_2]'s [_3] objects", # loc
877 =head2 FriendlyLookupType
879 Returns a localized description of the type of this custom field
883 sub FriendlyLookupType {
885 my $lookup = shift || $self->LookupType;
887 return ($self->loc( $FRIENDLY_OBJECT_TYPES{$lookup} ))
888 if (defined $FRIENDLY_OBJECT_TYPES{$lookup} );
890 my @types = map { s/^RT::// ? $self->loc($_) : $_ }
891 grep { defined and length }
892 split( /-/, $lookup )
894 return ( $self->loc( $FriendlyObjectTypes[$#types], @types ) );
897 sub RecordClassFromLookupType {
899 my ($class) = ($self->LookupType =~ /^([^-]+)/);
902 "Custom Field #". $self->id
903 ." has incorrect LookupType '". $self->LookupType ."'"
910 sub CollectionClassFromLookupType {
913 my $record_class = $self->RecordClassFromLookupType;
914 return undef unless $record_class;
916 my $collection_class;
917 if ( UNIVERSAL::can($record_class.'Collection', 'new') ) {
918 $collection_class = $record_class.'Collection';
919 } elsif ( UNIVERSAL::can($record_class.'es', 'new') ) {
920 $collection_class = $record_class.'es';
921 } elsif ( UNIVERSAL::can($record_class.'s', 'new') ) {
922 $collection_class = $record_class.'s';
924 $RT::Logger->error("Can not find a collection class for record class '$record_class'");
927 return $collection_class;
932 Returns collection with objects this custom field is applied to.
933 Class of the collection depends on L</LookupType>.
934 See all L</NotAppliedTo> .
936 Doesn't takes into account if object is applied globally.
943 my ($res, $ocfs_alias) = $self->_AppliedTo;
944 return $res unless $res;
947 ALIAS => $ocfs_alias,
949 OPERATOR => 'IS NOT',
958 Returns collection with objects this custom field is not applied to.
959 Class of the collection depends on L</LookupType>.
960 See all L</AppliedTo> .
962 Doesn't takes into account if object is applied globally.
969 my ($res, $ocfs_alias) = $self->_AppliedTo;
970 return $res unless $res;
973 ALIAS => $ocfs_alias,
985 my ($class) = $self->CollectionClassFromLookupType;
986 return undef unless $class;
988 my $res = $class->new( $self->CurrentUser );
990 # If CF is a Group CF, only display user-defined groups
991 if ( $class eq 'RT::Groups' ) {
992 $res->LimitToUserDefinedGroups;
995 $res->OrderBy( FIELD => 'Name' );
996 my $ocfs_alias = $res->Join(
1000 TABLE2 => 'ObjectCustomFields',
1001 FIELD2 => 'ObjectId',
1004 LEFTJOIN => $ocfs_alias,
1005 ALIAS => $ocfs_alias,
1006 FIELD => 'CustomField',
1009 return ($res, $ocfs_alias);
1014 Takes object id and returns corresponding L<RT::ObjectCustomField>
1015 record if this custom field is applied to the object. Use 0 to check
1016 if custom field is applied globally.
1023 my $ocf = RT::ObjectCustomField->new( $self->CurrentUser );
1024 $ocf->LoadByCols( CustomField => $self->id, ObjectId => $id || 0 );
1025 return undef unless $ocf->id;
1029 =head2 AddToObject OBJECT
1031 Add this custom field as a custom field for a single object, such as a queue or group.
1041 my $id = $object->Id || 0;
1043 unless (index($self->LookupType, ref($object)) == 0) {
1044 return ( 0, $self->loc('Lookup type mismatch') );
1047 unless ( $object->CurrentUserHasRight('AssignCustomFields') ) {
1048 return ( 0, $self->loc('Permission Denied') );
1051 if ( $self->IsApplied( $id ) ) {
1052 return ( 0, $self->loc("Custom field is already applied to the object") );
1057 return (0, $self->loc("Couldn't apply custom field to an object as it's global already") )
1058 if $self->IsApplied( 0 );
1061 my $applied = RT::ObjectCustomFields->new( $self->CurrentUser );
1062 $applied->LimitToCustomField( $self->id );
1063 while ( my $record = $applied->Next ) {
1068 my $ocf = RT::ObjectCustomField->new( $self->CurrentUser );
1069 my ( $oid, $msg ) = $ocf->Create(
1070 ObjectId => $id, CustomField => $self->id,
1072 return ( $oid, $msg );
1076 =head2 RemoveFromObject OBJECT
1078 Remove this custom field for a single object, such as a queue or group.
1084 sub RemoveFromObject {
1087 my $id = $object->Id || 0;
1089 unless (index($self->LookupType, ref($object)) == 0) {
1090 return ( 0, $self->loc('Object type mismatch') );
1093 unless ( $object->CurrentUserHasRight('AssignCustomFields') ) {
1094 return ( 0, $self->loc('Permission Denied') );
1097 my $ocf = $self->IsApplied( $id );
1099 return ( 0, $self->loc("This custom field does not apply to that object") );
1102 # XXX: Delete doesn't return anything
1103 my ( $oid, $msg ) = $ocf->Delete;
1104 return ( $oid, $msg );
1107 # {{{ AddValueForObject
1109 =head2 AddValueForObject HASH
1111 Adds a custom field value for a record object of some kind.
1112 Takes a param hash of
1126 sub AddValueForObject {
1131 LargeContent => undef,
1132 ContentType => undef,
1135 my $obj = $args{'Object'} or return ( 0, $self->loc('Invalid object') );
1137 unless ( $self->CurrentUserHasRight('ModifyCustomField') ) {
1138 return ( 0, $self->loc('Permission Denied') );
1141 unless ( $self->MatchPattern($args{'Content'}) ) {
1142 return ( 0, $self->loc('Input must match [_1]', $self->FriendlyPattern) );
1145 $RT::Handle->BeginTransaction;
1147 if ( $self->MaxValues ) {
1148 my $current_values = $self->ValuesForObject($obj);
1149 my $extra_values = ( $current_values->Count + 1 ) - $self->MaxValues;
1151 # (The +1 is for the new value we're adding)
1153 # If we have a set of current values and we've gone over the maximum
1154 # allowed number of values, we'll need to delete some to make room.
1155 # which former values are blown away is not guaranteed
1157 while ($extra_values) {
1158 my $extra_item = $current_values->Next;
1159 unless ( $extra_item->id ) {
1160 $RT::Logger->crit( "We were just asked to delete "
1161 ."a custom field value that doesn't exist!" );
1162 $RT::Handle->Rollback();
1165 $extra_item->Delete;
1169 # For date, we need to store Content as ISO date
1170 if ($self->Type eq 'Date') {
1171 my $DateObj = new RT::Date( $self->CurrentUser );
1173 Format => 'unknown',
1174 Value => $args{'Content'},
1176 $args{'Content'} = $DateObj->ISO;
1178 my $newval = RT::ObjectCustomFieldValue->new( $self->CurrentUser );
1179 my $val = $newval->Create(
1180 ObjectType => ref($obj),
1181 ObjectId => $obj->Id,
1182 Content => $args{'Content'},
1183 LargeContent => $args{'LargeContent'},
1184 ContentType => $args{'ContentType'},
1185 CustomField => $self->Id
1189 $RT::Handle->Rollback();
1190 return ($val, $self->loc("Couldn't create record"));
1193 $RT::Handle->Commit();
1202 =head2 MatchPattern STRING
1204 Tests the incoming string against the Pattern of this custom field object
1205 and returns a boolean; returns true if the Pattern is empty.
1211 my $regex = $self->Pattern or return 1;
1213 return (( defined $_[0] ? $_[0] : '') =~ $regex);
1219 # {{{ FriendlyPattern
1221 =head2 FriendlyPattern
1223 Prettify the pattern of this custom field, by taking the text in C<(?#text)>
1228 sub FriendlyPattern {
1230 my $regex = $self->Pattern;
1232 return '' unless length $regex;
1233 if ( $regex =~ /\(\?#([^)]*)\)/ ) {
1234 return '[' . $self->loc($1) . ']';
1244 # {{{ DeleteValueForObject
1246 =head2 DeleteValueForObject HASH
1248 Deletes a custom field value for a ticket. Takes a param hash of Object and Content
1250 Returns a tuple of (STATUS, MESSAGE). If the call succeeded, the STATUS is true. otherwise it's false
1254 sub DeleteValueForObject {
1256 my %args = ( Object => undef,
1262 unless ($self->CurrentUserHasRight('ModifyCustomField')) {
1263 return (0, $self->loc('Permission Denied'));
1266 my $oldval = RT::ObjectCustomFieldValue->new($self->CurrentUser);
1268 if (my $id = $args{'Id'}) {
1271 unless ($oldval->id) {
1272 $oldval->LoadByObjectContentAndCustomField(
1273 Object => $args{'Object'},
1274 Content => $args{'Content'},
1275 CustomField => $self->Id,
1280 # check to make sure we found it
1281 unless ($oldval->Id) {
1282 return(0, $self->loc("Custom field value [_1] could not be found for custom field [_2]", $args{'Content'}, $self->Name));
1285 # for single-value fields, we need to validate that empty string is a valid value for it
1286 if ( $self->SingleValue and not $self->MatchPattern( '' ) ) {
1287 return ( 0, $self->loc('Input must match [_1]', $self->FriendlyPattern) );
1292 my $ret = $oldval->Delete();
1294 return(0, $self->loc("Custom field value could not be found"));
1296 return($oldval->Id, $self->loc("Custom field value deleted"));
1300 =head2 ValuesForObject OBJECT
1302 Return an L<RT::ObjectCustomFieldValues> object containing all of this custom field's values for OBJECT
1306 sub ValuesForObject {
1310 my $values = new RT::ObjectCustomFieldValues($self->CurrentUser);
1311 unless ($self->CurrentUserHasRight('SeeCustomField')) {
1312 # Return an empty object if they have no rights to see
1317 $values->LimitToCustomField($self->Id);
1318 $values->LimitToEnabled();
1319 $values->LimitToObject($object);
1325 =head2 _ForObjectType PATH FRIENDLYNAME
1327 Tell RT that a certain object accepts custom fields
1331 'RT::Queue-RT::Ticket' => "Tickets", # loc
1332 'RT::Queue-RT::Ticket-RT::Transaction' => "Ticket Transactions", # loc
1333 'RT::User' => "Users", # loc
1334 'RT::Group' => "Groups", # loc
1336 This is a class method.
1340 sub _ForObjectType {
1343 my $friendly_name = shift;
1345 $FRIENDLY_OBJECT_TYPES{$path} = $friendly_name;
1350 =head2 IncludeContentForValue [VALUE] (and SetIncludeContentForValue)
1352 Gets or sets the C<IncludeContentForValue> for this custom field. RT
1353 uses this field to automatically include content into the user's browser
1354 as they display records with custom fields in RT.
1358 sub SetIncludeContentForValue {
1359 shift->IncludeContentForValue(@_);
1361 sub IncludeContentForValue{
1363 $self->_URLTemplate('IncludeContentForValue', @_);
1368 =head2 LinkValueTo [VALUE] (and SetLinkValueTo)
1370 Gets or sets the C<LinkValueTo> for this custom field. RT
1371 uses this field to make custom field values into hyperlinks in the user's
1372 browser as they display records with custom fields in RT.
1377 sub SetLinkValueTo {
1378 shift->LinkValueTo(@_);
1383 $self->_URLTemplate('LinkValueTo', @_);
1388 =head2 _URLTemplate NAME [VALUE]
1390 With one argument, returns the _URLTemplate named C<NAME>, but only if
1391 the current user has the right to see this custom field.
1393 With two arguments, attemptes to set the relevant template value.
1399 my $template_name = shift;
1403 unless ( $self->CurrentUserHasRight('AdminCustomField') ) {
1404 return ( 0, $self->loc('Permission Denied') );
1406 $self->SetAttribute( Name => $template_name, Content => $value );
1407 return ( 1, $self->loc('Updated') );
1409 unless ( $self->id && $self->CurrentUserHasRight('SeeCustomField') ) {
1413 my @attr = $self->Attributes->Named($template_name);
1414 my $attr = shift @attr;
1416 if ($attr) { return $attr->Content }
1425 return $self->DeleteAttribute( "BasedOn" )
1426 unless defined $value and length $value;
1428 my $cf = RT::CustomField->new( $self->CurrentUser );
1429 $cf->Load( ref $value ? $value->Id : $value );
1431 return (0, "Permission denied")
1432 unless $cf->Id && $cf->CurrentUserHasRight('SeeCustomField');
1434 return $self->AddAttribute(
1436 Description => "Custom field whose CF we depend on",
1443 my $obj = RT::CustomField->new( $self->CurrentUser );
1445 my $attribute = $self->FirstAttribute("BasedOn");
1446 $obj->Load($attribute->Content) if defined $attribute;