1 # BEGIN BPS TAGGED BLOCK {{{
5 # This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
6 # <jesse@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 ModifyCustomField => 'Add, delete and modify custom field values for objects' #loc_pair
118 # Tell RT::ACE that this sort of object can get acls granted
119 $RT::ACE::OBJECT_TYPES{'RT::CustomField'} = 1;
121 foreach my $right ( keys %{$RIGHTS} ) {
122 $RT::ACE::LOWERCASERIGHTNAMES{ lc $right } = $right;
125 =head2 AddRights C<RIGHT>, C<DESCRIPTION> [, ...]
127 Adds the given rights to the list of possible rights. This method
128 should be called during server startup, not at runtime.
135 $RIGHTS = { %$RIGHTS, %new };
136 %RT::ACE::LOWERCASERIGHTNAMES = ( %RT::ACE::LOWERCASERIGHTNAMES,
137 map { lc($_) => $_ } keys %new);
140 sub AvailableRights {
147 RT::CustomField_Overlay - overlay for RT::CustomField
151 =head1 'CORE' METHODS
153 =head2 Create PARAMHASH
155 Create takes a hash of values and creates a row in the database:
160 varchar(255) 'Pattern'.
161 smallint(6) 'Repeated'.
162 varchar(255) 'Description'.
164 varchar(255) 'LookupType'.
165 smallint(6) 'Disabled'.
167 C<LookupType> is generally the result of either
168 C<RT::Ticket->CustomFieldLookupType> or C<RT::Transaction->CustomFieldLookupType>.
184 IncludeContentForValue => '',
188 unless ( $self->CurrentUser->HasRight(Object => $RT::System, Right => 'AdminCustomField') ) {
189 return (0, $self->loc('Permission Denied'));
192 if ( $args{TypeComposite} ) {
193 @args{'Type', 'MaxValues'} = split(/-/, $args{TypeComposite}, 2);
195 elsif ( $args{Type} =~ s/(?:(Single)|Multiple)$// ) {
196 # old style Type string
197 $args{'MaxValues'} = $1 ? 1 : 0;
199 $args{'MaxValues'} = int $args{'MaxValues'};
201 if ( !exists $args{'Queue'}) {
202 # do nothing -- things below are strictly backward compat
204 elsif ( ! $args{'Queue'} ) {
205 unless ( $self->CurrentUser->HasRight( Object => $RT::System, Right => 'AssignCustomFields') ) {
206 return ( 0, $self->loc('Permission Denied') );
208 $args{'LookupType'} = 'RT::Queue-RT::Ticket';
211 my $queue = RT::Queue->new($self->CurrentUser);
212 $queue->Load($args{'Queue'});
213 unless ($queue->Id) {
214 return (0, $self->loc("Queue not found"));
216 unless ( $queue->CurrentUserHasRight('AssignCustomFields') ) {
217 return ( 0, $self->loc('Permission Denied') );
219 $args{'LookupType'} = 'RT::Queue-RT::Ticket';
220 $args{'Queue'} = $queue->Id;
223 my ($ok, $msg) = $self->_IsValidRegex( $args{'Pattern'} );
224 return (0, $self->loc("Invalid pattern: [_1]", $msg)) unless $ok;
226 if ( $args{'MaxValues'} != 1 && $args{'Type'} =~ /(text|combobox)$/i ) {
227 $RT::Logger->warning("Support for 'multiple' Texts or Comboboxes is not implemented");
228 $args{'MaxValues'} = 1;
231 (my $rv, $msg) = $self->SUPER::Create(
232 Name => $args{'Name'},
233 Type => $args{'Type'},
234 MaxValues => $args{'MaxValues'},
235 Pattern => $args{'Pattern'},
236 Description => $args{'Description'},
237 Disabled => $args{'Disabled'},
238 LookupType => $args{'LookupType'},
239 Repeated => $args{'Repeated'},
242 if ( exists $args{'LinkValueTo'}) {
243 $self->SetLinkValueTo($args{'LinkValueTo'});
246 if ( exists $args{'IncludeContentForValue'}) {
247 $self->SetIncludeContentForValue($args{'IncludeContentForValue'});
250 if ( exists $args{'ValuesClass'} ) {
251 $self->SetValuesClass( $args{'ValuesClass'} );
254 if ( exists $args{'BasedOn'} ) {
255 $self->SetBasedOn( $args{'BasedOn'} );
258 return ($rv, $msg) unless exists $args{'Queue'};
260 # Compat code -- create a new ObjectCustomField mapping
261 my $OCF = RT::ObjectCustomField->new( $self->CurrentUser );
263 CustomField => $self->Id,
264 ObjectId => $args{'Queue'},
272 Load a custom field. If the value handed in is an integer, load by custom field ID. Otherwise, Load by name.
278 my $id = shift || '';
280 if ( $id =~ /^\d+$/ ) {
281 return $self->SUPER::Load( $id );
283 return $self->LoadByName( Name => $id );
290 =head2 LoadByName (Queue => QUEUEID, Name => NAME)
292 Loads the Custom field named NAME.
294 Will load a Disabled Custom Field even if there is a non-disabled Custom Field
297 If a Queue parameter is specified, only look for ticket custom fields tied to that Queue.
299 If the Queue parameter is '0', look for global ticket custom fields.
301 If no queue parameter is specified, look for any and all custom fields with this name.
303 BUG/TODO, this won't let you specify that you only want user or group CFs.
307 # Compatibility for API change after 3.0 beta 1
308 *LoadNameAndQueue = \&LoadByName;
309 # Change after 3.4 beta.
310 *LoadByNameAndQueue = \&LoadByName;
320 unless ( defined $args{'Name'} && length $args{'Name'} ) {
321 $RT::Logger->error("Couldn't load Custom Field without Name");
322 return wantarray ? (0, $self->loc("No name provided")) : 0;
325 # if we're looking for a queue by name, make it a number
326 if ( defined $args{'Queue'} && $args{'Queue'} =~ /\D/ ) {
327 my $QueueObj = RT::Queue->new( $self->CurrentUser );
328 $QueueObj->Load( $args{'Queue'} );
329 $args{'Queue'} = $QueueObj->Id;
332 # XXX - really naive implementation. Slow. - not really. still just one query
334 my $CFs = RT::CustomFields->new( $self->CurrentUser );
335 $CFs->SetContextObject( $self->ContextObject );
336 my $field = $args{'Name'} =~ /\D/? 'Name' : 'id';
337 $CFs->Limit( FIELD => $field, VALUE => $args{'Name'}, CASESENSITIVE => 0);
338 # Don't limit to queue if queue is 0. Trying to do so breaks
339 # RT::Group type CFs.
340 if ( defined $args{'Queue'} ) {
341 $CFs->LimitToQueue( $args{'Queue'} );
344 # When loading by name, we _can_ load disabled fields, but prefer
345 # non-disabled fields.
348 { FIELD => "Disabled", ORDER => 'ASC' },
351 # We only want one entry.
352 $CFs->RowsPerPage(1);
354 # version before 3.8 just returns 0, so we need to test if wantarray to be
355 # backward compatible.
356 return wantarray ? (0, $self->loc("Not found")) : 0 unless my $first = $CFs->First;
358 return $self->LoadById( $first->id );
363 # {{{ Dealing with custom field values
366 =head2 Custom field values
370 Return a object (collection) of all acceptable values for this Custom Field.
371 Class of the object can vary and depends on the return value
372 of the C<ValuesClass> method.
376 *ValuesObj = \&Values;
381 my $class = $self->ValuesClass || 'RT::CustomFieldValues';
382 eval "require $class" or die "$@";
383 my $cf_values = $class->new( $self->CurrentUser );
384 # if the user has no rights, return an empty object
385 if ( $self->id && $self->CurrentUserHasRight( 'SeeCustomField') ) {
386 $cf_values->LimitToCustomField( $self->Id );
395 Create a new value for this CustomField. Takes a paramhash containing the elements Name, Description and SortOrder
403 unless ($self->CurrentUserHasRight('AdminCustomField')) {
404 return (0, $self->loc('Permission Denied'));
408 if ( !defined $args{'Name'} || $args{'Name'} eq '' ) {
409 return (0, $self->loc("Can't add a custom field value without a name"));
412 my $newval = RT::CustomFieldValue->new( $self->CurrentUser );
413 return $newval->Create( %args, CustomField => $self->Id );
421 =head3 DeleteValue ID
423 Deletes a value from this custom field by id.
425 Does not remove this value for any article which has had it selected
432 unless ( $self->CurrentUserHasRight('AdminCustomField') ) {
433 return (0, $self->loc('Permission Denied'));
436 my $val_to_del = RT::CustomFieldValue->new( $self->CurrentUser );
437 $val_to_del->Load( $id );
438 unless ( $val_to_del->Id ) {
439 return (0, $self->loc("Couldn't find that value"));
441 unless ( $val_to_del->CustomField == $self->Id ) {
442 return (0, $self->loc("That is not a value for this custom field"));
445 my $retval = $val_to_del->Delete;
447 return (0, $self->loc("Custom field value could not be deleted"));
449 return ($retval, $self->loc("Custom field value deleted"));
455 =head2 ValidateQueue Queue
457 Make sure that the queue specified is a valid queue name
465 return undef unless defined $id;
466 # 0 means "Global" null would _not_ be ok.
467 return 1 if $id eq '0';
469 my $q = RT::Queue->new( $RT::SystemUser );
471 return undef unless $q->id;
480 Retuns an array of the types of CustomField that are supported
485 return (keys %FieldTypes);
490 # {{{ IsSelectionType
492 =head2 IsSelectionType
494 Retuns a boolean value indicating whether the C<Values> method makes sense
495 to this Custom Field.
499 sub IsSelectionType {
501 my $type = @_? shift : $self->Type;
502 return undef unless $type;
504 $type =~ /(?:Select|Combobox|Autocomplete)/;
510 =head2 IsExternalValues
514 sub IsExternalValues {
516 my $selectable = $self->IsSelectionType( @_ );
517 return $selectable unless $selectable;
519 my $class = $self->ValuesClass;
520 return 0 if $class eq 'RT::CustomFieldValues';
526 return '' unless $self->IsSelectionType;
528 my $class = $self->FirstAttribute( 'ValuesClass' );
529 $class = $class->Content if $class;
530 return $class || 'RT::CustomFieldValues';
535 my $class = shift || 'RT::CustomFieldValues';
537 if( $class eq 'RT::CustomFieldValues' ) {
538 return $self->DeleteAttribute( 'ValuesClass' );
540 return $self->SetAttribute( Name => 'ValuesClass', Content => $class );
544 =head2 FriendlyType [TYPE, MAX_VALUES]
546 Returns a localized human-readable version of the custom field type.
547 If a custom field type is specified as the parameter, the friendly type for that type will be returned
554 my $type = @_ ? shift : $self->Type;
555 my $max = @_ ? shift : $self->MaxValues;
556 $max = 0 unless $max;
558 if (my $friendly_type = $FieldTypes{$type}[$max>2 ? 2 : $max]) {
559 return ( $self->loc( $friendly_type, $max ) );
562 return ( $self->loc( $type ) );
566 sub FriendlyTypeComposite {
568 my $composite = shift || $self->TypeComposite;
569 return $self->FriendlyType(split(/-/, $composite, 2));
573 =head2 ValidateType TYPE
575 Takes a single string. returns true if that string is a value
585 if ( $type =~ s/(?:Single|Multiple)$// ) {
586 $RT::Logger->warning( "Prefix 'Single' and 'Multiple' to Type deprecated, use MaxValues instead at (". join(":",caller).")");
589 if ( $FieldTypes{$type} ) {
601 if ($type =~ s/(?:(Single)|Multiple)$//) {
602 $RT::Logger->warning("'Single' and 'Multiple' on SetType deprecated, use SetMaxValues instead at (". join(":",caller).")");
603 $self->SetMaxValues($1 ? 1 : 0);
605 $self->SUPER::SetType($type);
608 =head2 SetPattern STRING
610 Takes a single string representing a regular expression. Performs basic
611 validation on that regex, and sets the C<Pattern> field for the CF if it
620 my ($ok, $msg) = $self->_IsValidRegex($regex);
622 return $self->SUPER::SetPattern($regex);
625 return (0, $self->loc("Invalid pattern: [_1]", $msg));
629 =head2 _IsValidRegex(Str $regex) returns (Bool $success, Str $msg)
631 Tests if the string contains an invalid regex.
637 my $regex = shift or return (1, 'valid');
640 local $SIG{__DIE__} = sub { 1 };
641 local $SIG{__WARN__} = sub { 1 };
643 if (eval { qr/$regex/; 1 }) {
648 $err =~ s{[,;].*}{}; # strip debug info from error
657 Returns true if this CustomField only accepts a single value.
658 Returns false if it accepts multiple values
664 if (($self->MaxValues||0) == 1) {
672 sub UnlimitedValues {
674 if (($self->MaxValues||0) == 0) {
684 =head2 CurrentUserHasRight RIGHT
686 Helper function to call the custom field's queue's CurrentUserHasRight with the passed in args.
690 sub CurrentUserHasRight {
694 return $self->CurrentUser->HasRight(
700 =head2 ACLEquivalenceObjects
702 Returns list of objects via which users can get rights on this custom field. For custom fields
703 these objects can be set using L<ContextObject|/"ContextObject and SetContextObject">.
707 sub ACLEquivalenceObjects {
710 my $ctx = $self->ContextObject
712 return ($ctx, $ctx->ACLEquivalenceObjects);
715 =head2 ContextObject and SetContextObject
717 Set or get a context for this object. It can be ticket, queue or another object
718 this CF applies to. Used for ACL control, for example SeeCustomField can be granted on
719 queue level to allow people to see all fields applied to the queue.
723 sub SetContextObject {
725 return $self->{'context_object'} = shift;
730 return $self->{'context_object'};
738 unless ( $self->CurrentUserHasRight('AdminCustomField') ) {
739 return ( 0, $self->loc('Permission Denied') );
741 return $self->SUPER::_Set( @_ );
751 Takes the name of a table column.
752 Returns its value as a string, if the user passes an ACL check
758 return undef unless $self->id;
760 # we need to do the rights check
761 unless ( $self->CurrentUserHasRight('SeeCustomField') ) {
763 "Permission denied. User #". $self->CurrentUser->id
764 ." has no SeeCustomField right on CF #". $self->id
768 return $self->__Value( @_ );
772 # {{{ sub SetDisabled
777 1 will cause this custom field to no longer be avaialble for objects.
778 0 will re-enable this field.
784 =head2 SetTypeComposite
786 Set this custom field's type and maximum values as a composite value
790 sub SetTypeComposite {
792 my $composite = shift;
794 my $old = $self->TypeComposite;
796 my ($type, $max_values) = split(/-/, $composite, 2);
797 if ( $type ne $self->Type ) {
798 my ($status, $msg) = $self->SetType( $type );
799 return ($status, $msg) unless $status;
801 if ( ($max_values || 0) != ($self->MaxValues || 0) ) {
802 my ($status, $msg) = $self->SetMaxValues( $max_values );
803 return ($status, $msg) unless $status;
805 return 1, $self->loc(
806 "Type changed from '[_1]' to '[_2]'",
807 $self->FriendlyTypeComposite( $old ),
808 $self->FriendlyTypeComposite( $composite ),
814 Returns a composite value composed of this object's type and maximum values
821 return join '-', ($self->Type || ''), ($self->MaxValues || 0);
824 =head2 TypeComposites
826 Returns an array of all possible composite values for custom fields.
832 return grep !/(?:[Tt]ext|Combobox)-0/, map { ("$_-1", "$_-0") } $self->Types;
837 Autrijus: care to doc how LookupTypes work?
844 if ( $lookup ne $self->LookupType ) {
845 # Okay... We need to invalidate our existing relationships
846 my $ObjectCustomFields = RT::ObjectCustomFields->new($self->CurrentUser);
847 $ObjectCustomFields->LimitToCustomField($self->Id);
848 $_->Delete foreach @{$ObjectCustomFields->ItemsArrayRef};
850 return $self->SUPER::SetLookupType($lookup);
855 Returns an array of LookupTypes available
862 return keys %FRIENDLY_OBJECT_TYPES;
865 my @FriendlyObjectTypes = (
866 "[_1] objects", # loc
867 "[_1]'s [_2] objects", # loc
868 "[_1]'s [_2]'s [_3] objects", # loc
871 =head2 FriendlyLookupType
873 Returns a localized description of the type of this custom field
877 sub FriendlyLookupType {
879 my $lookup = shift || $self->LookupType;
881 return ($self->loc( $FRIENDLY_OBJECT_TYPES{$lookup} ))
882 if (defined $FRIENDLY_OBJECT_TYPES{$lookup} );
884 my @types = map { s/^RT::// ? $self->loc($_) : $_ }
885 grep { defined and length }
886 split( /-/, $lookup )
888 return ( $self->loc( $FriendlyObjectTypes[$#types], @types ) );
891 sub RecordClassFromLookupType {
893 my ($class) = ($self->LookupType =~ /^([^-]+)/);
896 "Custom Field #". $self->id
897 ." has incorrect LookupType '". $self->LookupType ."'"
904 sub CollectionClassFromLookupType {
907 my $record_class = $self->RecordClassFromLookupType;
908 return undef unless $record_class;
910 my $collection_class;
911 if ( UNIVERSAL::can($record_class.'Collection', 'new') ) {
912 $collection_class = $record_class.'Collection';
913 } elsif ( UNIVERSAL::can($record_class.'es', 'new') ) {
914 $collection_class = $record_class.'es';
915 } elsif ( UNIVERSAL::can($record_class.'s', 'new') ) {
916 $collection_class = $record_class.'s';
918 $RT::Logger->error("Can not find a collection class for record class '$record_class'");
921 return $collection_class;
926 Returns collection with objects this custom field is applied to.
927 Class of the collection depends on L</LookupType>.
928 See all L</NotAppliedTo> .
930 Doesn't takes into account if object is applied globally.
937 my ($res, $ocfs_alias) = $self->_AppliedTo;
938 return $res unless $res;
941 ALIAS => $ocfs_alias,
943 OPERATOR => 'IS NOT',
952 Returns collection with objects this custom field is not applied to.
953 Class of the collection depends on L</LookupType>.
954 See all L</AppliedTo> .
956 Doesn't takes into account if object is applied globally.
963 my ($res, $ocfs_alias) = $self->_AppliedTo;
964 return $res unless $res;
967 ALIAS => $ocfs_alias,
979 my ($class) = $self->CollectionClassFromLookupType;
980 return undef unless $class;
982 my $res = $class->new( $self->CurrentUser );
984 # If CF is a Group CF, only display user-defined groups
985 if ( $class eq 'RT::Groups' ) {
986 $res->LimitToUserDefinedGroups;
989 $res->OrderBy( FIELD => 'Name' );
990 my $ocfs_alias = $res->Join(
994 TABLE2 => 'ObjectCustomFields',
995 FIELD2 => 'ObjectId',
998 LEFTJOIN => $ocfs_alias,
999 ALIAS => $ocfs_alias,
1000 FIELD => 'CustomField',
1003 return ($res, $ocfs_alias);
1008 Takes object id and returns corresponding L<RT::ObjectCustomField>
1009 record if this custom field is applied to the object. Use 0 to check
1010 if custom field is applied globally.
1017 my $ocf = RT::ObjectCustomField->new( $self->CurrentUser );
1018 $ocf->LoadByCols( CustomField => $self->id, ObjectId => $id || 0 );
1019 return undef unless $ocf->id;
1023 =head2 AddToObject OBJECT
1025 Add this custom field as a custom field for a single object, such as a queue or group.
1035 my $id = $object->Id || 0;
1037 unless (index($self->LookupType, ref($object)) == 0) {
1038 return ( 0, $self->loc('Lookup type mismatch') );
1041 unless ( $object->CurrentUserHasRight('AssignCustomFields') ) {
1042 return ( 0, $self->loc('Permission Denied') );
1045 if ( $self->IsApplied( $id ) ) {
1046 return ( 0, $self->loc("Custom field is already applied to the object") );
1051 return (0, $self->loc("Couldn't apply custom field to an object as it's global already") )
1052 if $self->IsApplied( 0 );
1055 my $applied = RT::ObjectCustomFields->new( $self->CurrentUser );
1056 $applied->LimitToCustomField( $self->id );
1057 while ( my $record = $applied->Next ) {
1062 my $ocf = RT::ObjectCustomField->new( $self->CurrentUser );
1063 my ( $oid, $msg ) = $ocf->Create(
1064 ObjectId => $id, CustomField => $self->id,
1066 return ( $oid, $msg );
1070 =head2 RemoveFromObject OBJECT
1072 Remove this custom field for a single object, such as a queue or group.
1078 sub RemoveFromObject {
1081 my $id = $object->Id || 0;
1083 unless (index($self->LookupType, ref($object)) == 0) {
1084 return ( 0, $self->loc('Object type mismatch') );
1087 unless ( $object->CurrentUserHasRight('AssignCustomFields') ) {
1088 return ( 0, $self->loc('Permission Denied') );
1091 my $ocf = $self->IsApplied( $id );
1093 return ( 0, $self->loc("This custom field does not apply to that object") );
1096 # XXX: Delete doesn't return anything
1097 my ( $oid, $msg ) = $ocf->Delete;
1098 return ( $oid, $msg );
1101 # {{{ AddValueForObject
1103 =head2 AddValueForObject HASH
1105 Adds a custom field value for a record object of some kind.
1106 Takes a param hash of
1120 sub AddValueForObject {
1125 LargeContent => undef,
1126 ContentType => undef,
1129 my $obj = $args{'Object'} or return ( 0, $self->loc('Invalid object') );
1131 unless ( $self->CurrentUserHasRight('ModifyCustomField') ) {
1132 return ( 0, $self->loc('Permission Denied') );
1135 unless ( $self->MatchPattern($args{'Content'}) ) {
1136 return ( 0, $self->loc('Input must match [_1]', $self->FriendlyPattern) );
1139 $RT::Handle->BeginTransaction;
1141 if ( $self->MaxValues ) {
1142 my $current_values = $self->ValuesForObject($obj);
1143 my $extra_values = ( $current_values->Count + 1 ) - $self->MaxValues;
1145 # (The +1 is for the new value we're adding)
1147 # If we have a set of current values and we've gone over the maximum
1148 # allowed number of values, we'll need to delete some to make room.
1149 # which former values are blown away is not guaranteed
1151 while ($extra_values) {
1152 my $extra_item = $current_values->Next;
1153 unless ( $extra_item->id ) {
1154 $RT::Logger->crit( "We were just asked to delete "
1155 ."a custom field value that doesn't exist!" );
1156 $RT::Handle->Rollback();
1159 $extra_item->Delete;
1163 my $newval = RT::ObjectCustomFieldValue->new( $self->CurrentUser );
1164 my $val = $newval->Create(
1165 ObjectType => ref($obj),
1166 ObjectId => $obj->Id,
1167 Content => $args{'Content'},
1168 LargeContent => $args{'LargeContent'},
1169 ContentType => $args{'ContentType'},
1170 CustomField => $self->Id
1174 $RT::Handle->Rollback();
1175 return ($val, $self->loc("Couldn't create record"));
1178 $RT::Handle->Commit();
1187 =head2 MatchPattern STRING
1189 Tests the incoming string against the Pattern of this custom field object
1190 and returns a boolean; returns true if the Pattern is empty.
1196 my $regex = $self->Pattern or return 1;
1198 return (( defined $_[0] ? $_[0] : '') =~ $regex);
1204 # {{{ FriendlyPattern
1206 =head2 FriendlyPattern
1208 Prettify the pattern of this custom field, by taking the text in C<(?#text)>
1213 sub FriendlyPattern {
1215 my $regex = $self->Pattern;
1217 return '' unless length $regex;
1218 if ( $regex =~ /\(\?#([^)]*)\)/ ) {
1219 return '[' . $self->loc($1) . ']';
1229 # {{{ DeleteValueForObject
1231 =head2 DeleteValueForObject HASH
1233 Deletes a custom field value for a ticket. Takes a param hash of Object and Content
1235 Returns a tuple of (STATUS, MESSAGE). If the call succeeded, the STATUS is true. otherwise it's false
1239 sub DeleteValueForObject {
1241 my %args = ( Object => undef,
1247 unless ($self->CurrentUserHasRight('ModifyCustomField')) {
1248 return (0, $self->loc('Permission Denied'));
1251 my $oldval = RT::ObjectCustomFieldValue->new($self->CurrentUser);
1253 if (my $id = $args{'Id'}) {
1256 unless ($oldval->id) {
1257 $oldval->LoadByObjectContentAndCustomField(
1258 Object => $args{'Object'},
1259 Content => $args{'Content'},
1260 CustomField => $self->Id,
1265 # check to make sure we found it
1266 unless ($oldval->Id) {
1267 return(0, $self->loc("Custom field value [_1] could not be found for custom field [_2]", $args{'Content'}, $self->Name));
1270 # for single-value fields, we need to validate that empty string is a valid value for it
1271 if ( $self->SingleValue and not $self->MatchPattern( '' ) ) {
1272 return ( 0, $self->loc('Input must match [_1]', $self->FriendlyPattern) );
1277 my $ret = $oldval->Delete();
1279 return(0, $self->loc("Custom field value could not be found"));
1281 return($oldval->Id, $self->loc("Custom field value deleted"));
1285 =head2 ValuesForObject OBJECT
1287 Return an L<RT::ObjectCustomFieldValues> object containing all of this custom field's values for OBJECT
1291 sub ValuesForObject {
1295 my $values = new RT::ObjectCustomFieldValues($self->CurrentUser);
1296 unless ($self->CurrentUserHasRight('SeeCustomField')) {
1297 # Return an empty object if they have no rights to see
1302 $values->LimitToCustomField($self->Id);
1303 $values->LimitToEnabled();
1304 $values->LimitToObject($object);
1310 =head2 _ForObjectType PATH FRIENDLYNAME
1312 Tell RT that a certain object accepts custom fields
1316 'RT::Queue-RT::Ticket' => "Tickets", # loc
1317 'RT::Queue-RT::Ticket-RT::Transaction' => "Ticket Transactions", # loc
1318 'RT::User' => "Users", # loc
1319 'RT::Group' => "Groups", # loc
1321 This is a class method.
1325 sub _ForObjectType {
1328 my $friendly_name = shift;
1330 $FRIENDLY_OBJECT_TYPES{$path} = $friendly_name;
1335 =head2 IncludeContentForValue [VALUE] (and SetIncludeContentForValue)
1337 Gets or sets the C<IncludeContentForValue> for this custom field. RT
1338 uses this field to automatically include content into the user's browser
1339 as they display records with custom fields in RT.
1343 sub SetIncludeContentForValue {
1344 shift->IncludeContentForValue(@_);
1346 sub IncludeContentForValue{
1348 $self->_URLTemplate('IncludeContentForValue', @_);
1353 =head2 LinkValueTo [VALUE] (and SetLinkValueTo)
1355 Gets or sets the C<LinkValueTo> for this custom field. RT
1356 uses this field to make custom field values into hyperlinks in the user's
1357 browser as they display records with custom fields in RT.
1362 sub SetLinkValueTo {
1363 shift->LinkValueTo(@_);
1368 $self->_URLTemplate('LinkValueTo', @_);
1373 =head2 _URLTemplate NAME [VALUE]
1375 With one argument, returns the _URLTemplate named C<NAME>, but only if
1376 the current user has the right to see this custom field.
1378 With two arguments, attemptes to set the relevant template value.
1384 my $template_name = shift;
1388 unless ( $self->CurrentUserHasRight('AdminCustomField') ) {
1389 return ( 0, $self->loc('Permission Denied') );
1391 $self->SetAttribute( Name => $template_name, Content => $value );
1392 return ( 1, $self->loc('Updated') );
1394 unless ( $self->id && $self->CurrentUserHasRight('SeeCustomField') ) {
1398 my @attr = $self->Attributes->Named($template_name);
1399 my $attr = shift @attr;
1401 if ($attr) { return $attr->Content }
1410 return $self->DeleteAttribute( "BasedOn" )
1411 unless defined $value and length $value;
1413 my $cf = RT::CustomField->new( $self->CurrentUser );
1414 $cf->Load( ref $value ? $value->Id : $value );
1416 return (0, "Permission denied")
1417 unless $cf->Id && $cf->CurrentUserHasRight('SeeCustomField');
1419 return $self->AddAttribute(
1421 Description => "Custom field whose CF we depend on",
1428 my $obj = RT::CustomField->new( $self->CurrentUser );
1430 my $attribute = $self->FirstAttribute("BasedOn");
1431 $obj->Load($attribute->Content) if defined $attribute;