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
103 our %FRIENDLY_OBJECT_TYPES = ();
105 RT::CustomField->_ForObjectType( 'RT::Queue-RT::Ticket' => "Tickets", ); #loc
106 RT::CustomField->_ForObjectType(
107 'RT::Queue-RT::Ticket-RT::Transaction' => "Ticket Transactions", ); #loc
108 RT::CustomField->_ForObjectType( 'RT::User' => "Users", ); #loc
109 RT::CustomField->_ForObjectType( 'RT::Queue' => "Queues", ); #loc
110 RT::CustomField->_ForObjectType( 'RT::Group' => "Groups", ); #loc
113 SeeCustomField => 'See custom fields', # loc_pair
114 AdminCustomField => 'Create, delete and modify custom fields', # loc_pair
115 AdminCustomFieldValues => 'Create, delete and modify custom fields values', # loc_pair
116 ModifyCustomField => 'Add, delete and modify custom field values for objects' #loc_pair
119 # Tell RT::ACE that this sort of object can get acls granted
120 $RT::ACE::OBJECT_TYPES{'RT::CustomField'} = 1;
122 foreach my $right ( keys %{$RIGHTS} ) {
123 $RT::ACE::LOWERCASERIGHTNAMES{ lc $right } = $right;
126 =head2 AddRights C<RIGHT>, C<DESCRIPTION> [, ...]
128 Adds the given rights to the list of possible rights. This method
129 should be called during server startup, not at runtime.
136 $RIGHTS = { %$RIGHTS, %new };
137 %RT::ACE::LOWERCASERIGHTNAMES = ( %RT::ACE::LOWERCASERIGHTNAMES,
138 map { lc($_) => $_ } keys %new);
141 sub AvailableRights {
148 RT::CustomField_Overlay - overlay for RT::CustomField
152 =head1 'CORE' METHODS
154 =head2 Create PARAMHASH
156 Create takes a hash of values and creates a row in the database:
161 varchar(255) 'Pattern'.
162 smallint(6) 'Repeated'.
163 varchar(255) 'Description'.
165 varchar(255) 'LookupType'.
166 smallint(6) 'Disabled'.
168 C<LookupType> is generally the result of either
169 C<RT::Ticket->CustomFieldLookupType> or C<RT::Transaction->CustomFieldLookupType>.
185 IncludeContentForValue => '',
189 unless ( $self->CurrentUser->HasRight(Object => $RT::System, Right => 'AdminCustomField') ) {
190 return (0, $self->loc('Permission Denied'));
193 if ( $args{TypeComposite} ) {
194 @args{'Type', 'MaxValues'} = split(/-/, $args{TypeComposite}, 2);
196 elsif ( $args{Type} =~ s/(?:(Single)|Multiple)$// ) {
197 # old style Type string
198 $args{'MaxValues'} = $1 ? 1 : 0;
200 $args{'MaxValues'} = int $args{'MaxValues'};
202 if ( !exists $args{'Queue'}) {
203 # do nothing -- things below are strictly backward compat
205 elsif ( ! $args{'Queue'} ) {
206 unless ( $self->CurrentUser->HasRight( Object => $RT::System, Right => 'AssignCustomFields') ) {
207 return ( 0, $self->loc('Permission Denied') );
209 $args{'LookupType'} = 'RT::Queue-RT::Ticket';
212 my $queue = RT::Queue->new($self->CurrentUser);
213 $queue->Load($args{'Queue'});
214 unless ($queue->Id) {
215 return (0, $self->loc("Queue not found"));
217 unless ( $queue->CurrentUserHasRight('AssignCustomFields') ) {
218 return ( 0, $self->loc('Permission Denied') );
220 $args{'LookupType'} = 'RT::Queue-RT::Ticket';
221 $args{'Queue'} = $queue->Id;
224 my ($ok, $msg) = $self->_IsValidRegex( $args{'Pattern'} );
225 return (0, $self->loc("Invalid pattern: [_1]", $msg)) unless $ok;
227 if ( $args{'MaxValues'} != 1 && $args{'Type'} =~ /(text|combobox)$/i ) {
228 $RT::Logger->warning("Support for 'multiple' Texts or Comboboxes is not implemented");
229 $args{'MaxValues'} = 1;
232 (my $rv, $msg) = $self->SUPER::Create(
233 Name => $args{'Name'},
234 Type => $args{'Type'},
235 MaxValues => $args{'MaxValues'},
236 Pattern => $args{'Pattern'},
237 Description => $args{'Description'},
238 Disabled => $args{'Disabled'},
239 LookupType => $args{'LookupType'},
240 Repeated => $args{'Repeated'},
243 if ( exists $args{'LinkValueTo'}) {
244 $self->SetLinkValueTo($args{'LinkValueTo'});
247 if ( exists $args{'IncludeContentForValue'}) {
248 $self->SetIncludeContentForValue($args{'IncludeContentForValue'});
251 if ( exists $args{'ValuesClass'} ) {
252 $self->SetValuesClass( $args{'ValuesClass'} );
255 if ( exists $args{'BasedOn'} ) {
256 $self->SetBasedOn( $args{'BasedOn'} );
259 return ($rv, $msg) unless exists $args{'Queue'};
261 # Compat code -- create a new ObjectCustomField mapping
262 my $OCF = RT::ObjectCustomField->new( $self->CurrentUser );
264 CustomField => $self->Id,
265 ObjectId => $args{'Queue'},
273 Load a custom field. If the value handed in is an integer, load by custom field ID. Otherwise, Load by name.
279 my $id = shift || '';
281 if ( $id =~ /^\d+$/ ) {
282 return $self->SUPER::Load( $id );
284 return $self->LoadByName( Name => $id );
291 =head2 LoadByName (Queue => QUEUEID, Name => NAME)
293 Loads the Custom field named NAME.
295 Will load a Disabled Custom Field even if there is a non-disabled Custom Field
298 If a Queue parameter is specified, only look for ticket custom fields tied to that Queue.
300 If the Queue parameter is '0', look for global ticket custom fields.
302 If no queue parameter is specified, look for any and all custom fields with this name.
304 BUG/TODO, this won't let you specify that you only want user or group CFs.
308 # Compatibility for API change after 3.0 beta 1
309 *LoadNameAndQueue = \&LoadByName;
310 # Change after 3.4 beta.
311 *LoadByNameAndQueue = \&LoadByName;
321 unless ( defined $args{'Name'} && length $args{'Name'} ) {
322 $RT::Logger->error("Couldn't load Custom Field without Name");
323 return wantarray ? (0, $self->loc("No name provided")) : 0;
326 # if we're looking for a queue by name, make it a number
327 if ( defined $args{'Queue'} && $args{'Queue'} =~ /\D/ ) {
328 my $QueueObj = RT::Queue->new( $self->CurrentUser );
329 $QueueObj->Load( $args{'Queue'} );
330 $args{'Queue'} = $QueueObj->Id;
333 # XXX - really naive implementation. Slow. - not really. still just one query
335 my $CFs = RT::CustomFields->new( $self->CurrentUser );
336 $CFs->SetContextObject( $self->ContextObject );
337 my $field = $args{'Name'} =~ /\D/? 'Name' : 'id';
338 $CFs->Limit( FIELD => $field, VALUE => $args{'Name'}, CASESENSITIVE => 0);
339 # Don't limit to queue if queue is 0. Trying to do so breaks
340 # RT::Group type CFs.
341 if ( defined $args{'Queue'} ) {
342 $CFs->LimitToQueue( $args{'Queue'} );
345 # When loading by name, we _can_ load disabled fields, but prefer
346 # non-disabled fields.
349 { FIELD => "Disabled", ORDER => 'ASC' },
352 # We only want one entry.
353 $CFs->RowsPerPage(1);
355 # version before 3.8 just returns 0, so we need to test if wantarray to be
356 # backward compatible.
357 return wantarray ? (0, $self->loc("Not found")) : 0 unless my $first = $CFs->First;
359 return $self->LoadById( $first->id );
364 # {{{ Dealing with custom field values
367 =head2 Custom field values
371 Return a object (collection) of all acceptable values for this Custom Field.
372 Class of the object can vary and depends on the return value
373 of the C<ValuesClass> method.
377 *ValuesObj = \&Values;
382 my $class = $self->ValuesClass || 'RT::CustomFieldValues';
383 eval "require $class" or die "$@";
384 my $cf_values = $class->new( $self->CurrentUser );
385 # if the user has no rights, return an empty object
386 if ( $self->id && $self->CurrentUserHasRight( 'SeeCustomField') ) {
387 $cf_values->LimitToCustomField( $self->Id );
396 Create a new value for this CustomField. Takes a paramhash containing the elements Name, Description and SortOrder
404 unless ($self->CurrentUserHasRight('AdminCustomField') || $self->CurrentUserHasRight('AdminCustomFieldValues')) {
405 return (0, $self->loc('Permission Denied'));
409 if ( !defined $args{'Name'} || $args{'Name'} eq '' ) {
410 return (0, $self->loc("Can't add a custom field value without a name"));
413 my $newval = RT::CustomFieldValue->new( $self->CurrentUser );
414 return $newval->Create( %args, CustomField => $self->Id );
422 =head3 DeleteValue ID
424 Deletes a value from this custom field by id.
426 Does not remove this value for any article which has had it selected
433 unless ( $self->CurrentUserHasRight('AdminCustomField') || $self->CurrentUserHasRight('AdminCustomFieldValues') ) {
434 return (0, $self->loc('Permission Denied'));
437 my $val_to_del = RT::CustomFieldValue->new( $self->CurrentUser );
438 $val_to_del->Load( $id );
439 unless ( $val_to_del->Id ) {
440 return (0, $self->loc("Couldn't find that value"));
442 unless ( $val_to_del->CustomField == $self->Id ) {
443 return (0, $self->loc("That is not a value for this custom field"));
446 my $retval = $val_to_del->Delete;
448 return (0, $self->loc("Custom field value could not be deleted"));
450 return ($retval, $self->loc("Custom field value deleted"));
456 =head2 ValidateQueue Queue
458 Make sure that the queue specified is a valid queue name
466 return undef unless defined $id;
467 # 0 means "Global" null would _not_ be ok.
468 return 1 if $id eq '0';
470 my $q = RT::Queue->new( $RT::SystemUser );
472 return undef unless $q->id;
481 Retuns an array of the types of CustomField that are supported
486 return (keys %FieldTypes);
491 # {{{ IsSelectionType
493 =head2 IsSelectionType
495 Retuns a boolean value indicating whether the C<Values> method makes sense
496 to this Custom Field.
500 sub IsSelectionType {
502 my $type = @_? shift : $self->Type;
503 return undef unless $type;
505 $type =~ /(?:Select|Combobox|Autocomplete)/;
511 =head2 IsExternalValues
515 sub IsExternalValues {
517 my $selectable = $self->IsSelectionType( @_ );
518 return $selectable unless $selectable;
520 my $class = $self->ValuesClass;
521 return 0 if $class eq 'RT::CustomFieldValues';
527 return '' unless $self->IsSelectionType;
529 my $class = $self->FirstAttribute( 'ValuesClass' );
530 $class = $class->Content if $class;
531 return $class || 'RT::CustomFieldValues';
536 my $class = shift || 'RT::CustomFieldValues';
538 if( $class eq 'RT::CustomFieldValues' ) {
539 return $self->DeleteAttribute( 'ValuesClass' );
541 return $self->SetAttribute( Name => 'ValuesClass', Content => $class );
545 =head2 FriendlyType [TYPE, MAX_VALUES]
547 Returns a localized human-readable version of the custom field type.
548 If a custom field type is specified as the parameter, the friendly type for that type will be returned
555 my $type = @_ ? shift : $self->Type;
556 my $max = @_ ? shift : $self->MaxValues;
557 $max = 0 unless $max;
559 if (my $friendly_type = $FieldTypes{$type}[$max>2 ? 2 : $max]) {
560 return ( $self->loc( $friendly_type, $max ) );
563 return ( $self->loc( $type ) );
567 sub FriendlyTypeComposite {
569 my $composite = shift || $self->TypeComposite;
570 return $self->FriendlyType(split(/-/, $composite, 2));
574 =head2 ValidateType TYPE
576 Takes a single string. returns true if that string is a value
586 if ( $type =~ s/(?:Single|Multiple)$// ) {
587 $RT::Logger->warning( "Prefix 'Single' and 'Multiple' to Type deprecated, use MaxValues instead at (". join(":",caller).")");
590 if ( $FieldTypes{$type} ) {
602 if ($type =~ s/(?:(Single)|Multiple)$//) {
603 $RT::Logger->warning("'Single' and 'Multiple' on SetType deprecated, use SetMaxValues instead at (". join(":",caller).")");
604 $self->SetMaxValues($1 ? 1 : 0);
606 $self->SUPER::SetType($type);
609 =head2 SetPattern STRING
611 Takes a single string representing a regular expression. Performs basic
612 validation on that regex, and sets the C<Pattern> field for the CF if it
621 my ($ok, $msg) = $self->_IsValidRegex($regex);
623 return $self->SUPER::SetPattern($regex);
626 return (0, $self->loc("Invalid pattern: [_1]", $msg));
630 =head2 _IsValidRegex(Str $regex) returns (Bool $success, Str $msg)
632 Tests if the string contains an invalid regex.
638 my $regex = shift or return (1, 'valid');
641 local $SIG{__DIE__} = sub { 1 };
642 local $SIG{__WARN__} = sub { 1 };
644 if (eval { qr/$regex/; 1 }) {
649 $err =~ s{[,;].*}{}; # strip debug info from error
658 Returns true if this CustomField only accepts a single value.
659 Returns false if it accepts multiple values
665 if (($self->MaxValues||0) == 1) {
673 sub UnlimitedValues {
675 if (($self->MaxValues||0) == 0) {
685 =head2 CurrentUserHasRight RIGHT
687 Helper function to call the custom field's queue's CurrentUserHasRight with the passed in args.
691 sub CurrentUserHasRight {
695 return $self->CurrentUser->HasRight(
701 =head2 ACLEquivalenceObjects
703 Returns list of objects via which users can get rights on this custom field. For custom fields
704 these objects can be set using L<ContextObject|/"ContextObject and SetContextObject">.
708 sub ACLEquivalenceObjects {
711 my $ctx = $self->ContextObject
713 return ($ctx, $ctx->ACLEquivalenceObjects);
716 =head2 ContextObject and SetContextObject
718 Set or get a context for this object. It can be ticket, queue or another object
719 this CF applies to. Used for ACL control, for example SeeCustomField can be granted on
720 queue level to allow people to see all fields applied to the queue.
724 sub SetContextObject {
726 return $self->{'context_object'} = shift;
731 return $self->{'context_object'};
739 unless ( $self->CurrentUserHasRight('AdminCustomField') ) {
740 return ( 0, $self->loc('Permission Denied') );
742 return $self->SUPER::_Set( @_ );
752 Takes the name of a table column.
753 Returns its value as a string, if the user passes an ACL check
759 return undef unless $self->id;
761 # we need to do the rights check
762 unless ( $self->CurrentUserHasRight('SeeCustomField') ) {
764 "Permission denied. User #". $self->CurrentUser->id
765 ." has no SeeCustomField right on CF #". $self->id
769 return $self->__Value( @_ );
773 # {{{ sub SetDisabled
778 1 will cause this custom field to no longer be avaialble for objects.
779 0 will re-enable this field.
785 =head2 SetTypeComposite
787 Set this custom field's type and maximum values as a composite value
791 sub SetTypeComposite {
793 my $composite = shift;
795 my $old = $self->TypeComposite;
797 my ($type, $max_values) = split(/-/, $composite, 2);
798 if ( $type ne $self->Type ) {
799 my ($status, $msg) = $self->SetType( $type );
800 return ($status, $msg) unless $status;
802 if ( ($max_values || 0) != ($self->MaxValues || 0) ) {
803 my ($status, $msg) = $self->SetMaxValues( $max_values );
804 return ($status, $msg) unless $status;
806 return 1, $self->loc(
807 "Type changed from '[_1]' to '[_2]'",
808 $self->FriendlyTypeComposite( $old ),
809 $self->FriendlyTypeComposite( $composite ),
815 Returns a composite value composed of this object's type and maximum values
822 return join '-', ($self->Type || ''), ($self->MaxValues || 0);
825 =head2 TypeComposites
827 Returns an array of all possible composite values for custom fields.
833 return grep !/(?:[Tt]ext|Combobox)-0/, map { ("$_-1", "$_-0") } $self->Types;
838 Autrijus: care to doc how LookupTypes work?
845 if ( $lookup ne $self->LookupType ) {
846 # Okay... We need to invalidate our existing relationships
847 my $ObjectCustomFields = RT::ObjectCustomFields->new($self->CurrentUser);
848 $ObjectCustomFields->LimitToCustomField($self->Id);
849 $_->Delete foreach @{$ObjectCustomFields->ItemsArrayRef};
851 return $self->SUPER::SetLookupType($lookup);
856 Returns an array of LookupTypes available
863 return keys %FRIENDLY_OBJECT_TYPES;
866 my @FriendlyObjectTypes = (
867 "[_1] objects", # loc
868 "[_1]'s [_2] objects", # loc
869 "[_1]'s [_2]'s [_3] objects", # loc
872 =head2 FriendlyLookupType
874 Returns a localized description of the type of this custom field
878 sub FriendlyLookupType {
880 my $lookup = shift || $self->LookupType;
882 return ($self->loc( $FRIENDLY_OBJECT_TYPES{$lookup} ))
883 if (defined $FRIENDLY_OBJECT_TYPES{$lookup} );
885 my @types = map { s/^RT::// ? $self->loc($_) : $_ }
886 grep { defined and length }
887 split( /-/, $lookup )
889 return ( $self->loc( $FriendlyObjectTypes[$#types], @types ) );
892 sub RecordClassFromLookupType {
894 my ($class) = ($self->LookupType =~ /^([^-]+)/);
897 "Custom Field #". $self->id
898 ." has incorrect LookupType '". $self->LookupType ."'"
905 sub CollectionClassFromLookupType {
908 my $record_class = $self->RecordClassFromLookupType;
909 return undef unless $record_class;
911 my $collection_class;
912 if ( UNIVERSAL::can($record_class.'Collection', 'new') ) {
913 $collection_class = $record_class.'Collection';
914 } elsif ( UNIVERSAL::can($record_class.'es', 'new') ) {
915 $collection_class = $record_class.'es';
916 } elsif ( UNIVERSAL::can($record_class.'s', 'new') ) {
917 $collection_class = $record_class.'s';
919 $RT::Logger->error("Can not find a collection class for record class '$record_class'");
922 return $collection_class;
927 Returns collection with objects this custom field is applied to.
928 Class of the collection depends on L</LookupType>.
929 See all L</NotAppliedTo> .
931 Doesn't takes into account if object is applied globally.
938 my ($res, $ocfs_alias) = $self->_AppliedTo;
939 return $res unless $res;
942 ALIAS => $ocfs_alias,
944 OPERATOR => 'IS NOT',
953 Returns collection with objects this custom field is not applied to.
954 Class of the collection depends on L</LookupType>.
955 See all L</AppliedTo> .
957 Doesn't takes into account if object is applied globally.
964 my ($res, $ocfs_alias) = $self->_AppliedTo;
965 return $res unless $res;
968 ALIAS => $ocfs_alias,
980 my ($class) = $self->CollectionClassFromLookupType;
981 return undef unless $class;
983 my $res = $class->new( $self->CurrentUser );
985 # If CF is a Group CF, only display user-defined groups
986 if ( $class eq 'RT::Groups' ) {
987 $res->LimitToUserDefinedGroups;
990 $res->OrderBy( FIELD => 'Name' );
991 my $ocfs_alias = $res->Join(
995 TABLE2 => 'ObjectCustomFields',
996 FIELD2 => 'ObjectId',
999 LEFTJOIN => $ocfs_alias,
1000 ALIAS => $ocfs_alias,
1001 FIELD => 'CustomField',
1004 return ($res, $ocfs_alias);
1009 Takes object id and returns corresponding L<RT::ObjectCustomField>
1010 record if this custom field is applied to the object. Use 0 to check
1011 if custom field is applied globally.
1018 my $ocf = RT::ObjectCustomField->new( $self->CurrentUser );
1019 $ocf->LoadByCols( CustomField => $self->id, ObjectId => $id || 0 );
1020 return undef unless $ocf->id;
1024 =head2 AddToObject OBJECT
1026 Add this custom field as a custom field for a single object, such as a queue or group.
1036 my $id = $object->Id || 0;
1038 unless (index($self->LookupType, ref($object)) == 0) {
1039 return ( 0, $self->loc('Lookup type mismatch') );
1042 unless ( $object->CurrentUserHasRight('AssignCustomFields') ) {
1043 return ( 0, $self->loc('Permission Denied') );
1046 if ( $self->IsApplied( $id ) ) {
1047 return ( 0, $self->loc("Custom field is already applied to the object") );
1052 return (0, $self->loc("Couldn't apply custom field to an object as it's global already") )
1053 if $self->IsApplied( 0 );
1056 my $applied = RT::ObjectCustomFields->new( $self->CurrentUser );
1057 $applied->LimitToCustomField( $self->id );
1058 while ( my $record = $applied->Next ) {
1063 my $ocf = RT::ObjectCustomField->new( $self->CurrentUser );
1064 my ( $oid, $msg ) = $ocf->Create(
1065 ObjectId => $id, CustomField => $self->id,
1067 return ( $oid, $msg );
1071 =head2 RemoveFromObject OBJECT
1073 Remove this custom field for a single object, such as a queue or group.
1079 sub RemoveFromObject {
1082 my $id = $object->Id || 0;
1084 unless (index($self->LookupType, ref($object)) == 0) {
1085 return ( 0, $self->loc('Object type mismatch') );
1088 unless ( $object->CurrentUserHasRight('AssignCustomFields') ) {
1089 return ( 0, $self->loc('Permission Denied') );
1092 my $ocf = $self->IsApplied( $id );
1094 return ( 0, $self->loc("This custom field does not apply to that object") );
1097 # XXX: Delete doesn't return anything
1098 my ( $oid, $msg ) = $ocf->Delete;
1099 return ( $oid, $msg );
1102 # {{{ AddValueForObject
1104 =head2 AddValueForObject HASH
1106 Adds a custom field value for a record object of some kind.
1107 Takes a param hash of
1121 sub AddValueForObject {
1126 LargeContent => undef,
1127 ContentType => undef,
1130 my $obj = $args{'Object'} or return ( 0, $self->loc('Invalid object') );
1132 unless ( $self->CurrentUserHasRight('ModifyCustomField') ) {
1133 return ( 0, $self->loc('Permission Denied') );
1136 unless ( $self->MatchPattern($args{'Content'}) ) {
1137 return ( 0, $self->loc('Input must match [_1]', $self->FriendlyPattern) );
1140 $RT::Handle->BeginTransaction;
1142 if ( $self->MaxValues ) {
1143 my $current_values = $self->ValuesForObject($obj);
1144 my $extra_values = ( $current_values->Count + 1 ) - $self->MaxValues;
1146 # (The +1 is for the new value we're adding)
1148 # If we have a set of current values and we've gone over the maximum
1149 # allowed number of values, we'll need to delete some to make room.
1150 # which former values are blown away is not guaranteed
1152 while ($extra_values) {
1153 my $extra_item = $current_values->Next;
1154 unless ( $extra_item->id ) {
1155 $RT::Logger->crit( "We were just asked to delete "
1156 ."a custom field value that doesn't exist!" );
1157 $RT::Handle->Rollback();
1160 $extra_item->Delete;
1164 my $newval = RT::ObjectCustomFieldValue->new( $self->CurrentUser );
1165 my $val = $newval->Create(
1166 ObjectType => ref($obj),
1167 ObjectId => $obj->Id,
1168 Content => $args{'Content'},
1169 LargeContent => $args{'LargeContent'},
1170 ContentType => $args{'ContentType'},
1171 CustomField => $self->Id
1175 $RT::Handle->Rollback();
1176 return ($val, $self->loc("Couldn't create record"));
1179 $RT::Handle->Commit();
1188 =head2 MatchPattern STRING
1190 Tests the incoming string against the Pattern of this custom field object
1191 and returns a boolean; returns true if the Pattern is empty.
1197 my $regex = $self->Pattern or return 1;
1199 return (( defined $_[0] ? $_[0] : '') =~ $regex);
1205 # {{{ FriendlyPattern
1207 =head2 FriendlyPattern
1209 Prettify the pattern of this custom field, by taking the text in C<(?#text)>
1214 sub FriendlyPattern {
1216 my $regex = $self->Pattern;
1218 return '' unless length $regex;
1219 if ( $regex =~ /\(\?#([^)]*)\)/ ) {
1220 return '[' . $self->loc($1) . ']';
1230 # {{{ DeleteValueForObject
1232 =head2 DeleteValueForObject HASH
1234 Deletes a custom field value for a ticket. Takes a param hash of Object and Content
1236 Returns a tuple of (STATUS, MESSAGE). If the call succeeded, the STATUS is true. otherwise it's false
1240 sub DeleteValueForObject {
1242 my %args = ( Object => undef,
1248 unless ($self->CurrentUserHasRight('ModifyCustomField')) {
1249 return (0, $self->loc('Permission Denied'));
1252 my $oldval = RT::ObjectCustomFieldValue->new($self->CurrentUser);
1254 if (my $id = $args{'Id'}) {
1257 unless ($oldval->id) {
1258 $oldval->LoadByObjectContentAndCustomField(
1259 Object => $args{'Object'},
1260 Content => $args{'Content'},
1261 CustomField => $self->Id,
1266 # check to make sure we found it
1267 unless ($oldval->Id) {
1268 return(0, $self->loc("Custom field value [_1] could not be found for custom field [_2]", $args{'Content'}, $self->Name));
1271 # for single-value fields, we need to validate that empty string is a valid value for it
1272 if ( $self->SingleValue and not $self->MatchPattern( '' ) ) {
1273 return ( 0, $self->loc('Input must match [_1]', $self->FriendlyPattern) );
1278 my $ret = $oldval->Delete();
1280 return(0, $self->loc("Custom field value could not be found"));
1282 return($oldval->Id, $self->loc("Custom field value deleted"));
1286 =head2 ValuesForObject OBJECT
1288 Return an L<RT::ObjectCustomFieldValues> object containing all of this custom field's values for OBJECT
1292 sub ValuesForObject {
1296 my $values = new RT::ObjectCustomFieldValues($self->CurrentUser);
1297 unless ($self->CurrentUserHasRight('SeeCustomField')) {
1298 # Return an empty object if they have no rights to see
1303 $values->LimitToCustomField($self->Id);
1304 $values->LimitToEnabled();
1305 $values->LimitToObject($object);
1311 =head2 _ForObjectType PATH FRIENDLYNAME
1313 Tell RT that a certain object accepts custom fields
1317 'RT::Queue-RT::Ticket' => "Tickets", # loc
1318 'RT::Queue-RT::Ticket-RT::Transaction' => "Ticket Transactions", # loc
1319 'RT::User' => "Users", # loc
1320 'RT::Group' => "Groups", # loc
1322 This is a class method.
1326 sub _ForObjectType {
1329 my $friendly_name = shift;
1331 $FRIENDLY_OBJECT_TYPES{$path} = $friendly_name;
1336 =head2 IncludeContentForValue [VALUE] (and SetIncludeContentForValue)
1338 Gets or sets the C<IncludeContentForValue> for this custom field. RT
1339 uses this field to automatically include content into the user's browser
1340 as they display records with custom fields in RT.
1344 sub SetIncludeContentForValue {
1345 shift->IncludeContentForValue(@_);
1347 sub IncludeContentForValue{
1349 $self->_URLTemplate('IncludeContentForValue', @_);
1354 =head2 LinkValueTo [VALUE] (and SetLinkValueTo)
1356 Gets or sets the C<LinkValueTo> for this custom field. RT
1357 uses this field to make custom field values into hyperlinks in the user's
1358 browser as they display records with custom fields in RT.
1363 sub SetLinkValueTo {
1364 shift->LinkValueTo(@_);
1369 $self->_URLTemplate('LinkValueTo', @_);
1374 =head2 _URLTemplate NAME [VALUE]
1376 With one argument, returns the _URLTemplate named C<NAME>, but only if
1377 the current user has the right to see this custom field.
1379 With two arguments, attemptes to set the relevant template value.
1385 my $template_name = shift;
1389 unless ( $self->CurrentUserHasRight('AdminCustomField') ) {
1390 return ( 0, $self->loc('Permission Denied') );
1392 $self->SetAttribute( Name => $template_name, Content => $value );
1393 return ( 1, $self->loc('Updated') );
1395 unless ( $self->id && $self->CurrentUserHasRight('SeeCustomField') ) {
1399 my @attr = $self->Attributes->Named($template_name);
1400 my $attr = shift @attr;
1402 if ($attr) { return $attr->Content }
1411 return $self->DeleteAttribute( "BasedOn" )
1412 unless defined $value and length $value;
1414 my $cf = RT::CustomField->new( $self->CurrentUser );
1415 $cf->Load( ref $value ? $value->Id : $value );
1417 return (0, "Permission denied")
1418 unless $cf->Id && $cf->CurrentUserHasRight('SeeCustomField');
1420 return $self->SetAttribute(
1422 Description => "Custom field whose CF we depend on",
1429 my $obj = RT::CustomField->new( $self->CurrentUser );
1431 my $attribute = $self->FirstAttribute("BasedOn");
1432 $obj->Load($attribute->Content) if defined $attribute;