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
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 ModifyCustomField => 'Add, delete and modify custom field values for objects' #loc_pair
123 # Tell RT::ACE that this sort of object can get acls granted
124 $RT::ACE::OBJECT_TYPES{'RT::CustomField'} = 1;
126 foreach my $right ( keys %{$RIGHTS} ) {
127 $RT::ACE::LOWERCASERIGHTNAMES{ lc $right } = $right;
130 =head2 AddRights C<RIGHT>, C<DESCRIPTION> [, ...]
132 Adds the given rights to the list of possible rights. This method
133 should be called during server startup, not at runtime.
140 $RIGHTS = { %$RIGHTS, %new };
141 %RT::ACE::LOWERCASERIGHTNAMES = ( %RT::ACE::LOWERCASERIGHTNAMES,
142 map { lc($_) => $_ } keys %new);
145 sub AvailableRights {
152 RT::CustomField_Overlay - overlay for RT::CustomField
156 =head1 'CORE' METHODS
158 =head2 Create PARAMHASH
160 Create takes a hash of values and creates a row in the database:
165 varchar(255) 'Pattern'.
166 smallint(6) 'Repeated'.
167 varchar(255) 'Description'.
169 varchar(255) 'LookupType'.
170 smallint(6) 'Disabled'.
172 C<LookupType> is generally the result of either
173 C<RT::Ticket->CustomFieldLookupType> or C<RT::Transaction->CustomFieldLookupType>.
189 IncludeContentForValue => '',
193 unless ( $self->CurrentUser->HasRight(Object => $RT::System, Right => 'AdminCustomField') ) {
194 return (0, $self->loc('Permission Denied'));
197 if ( $args{TypeComposite} ) {
198 @args{'Type', 'MaxValues'} = split(/-/, $args{TypeComposite}, 2);
200 elsif ( $args{Type} =~ s/(?:(Single)|Multiple)$// ) {
201 # old style Type string
202 $args{'MaxValues'} = $1 ? 1 : 0;
204 $args{'MaxValues'} = int $args{'MaxValues'};
206 if ( !exists $args{'Queue'}) {
207 # do nothing -- things below are strictly backward compat
209 elsif ( ! $args{'Queue'} ) {
210 unless ( $self->CurrentUser->HasRight( Object => $RT::System, Right => 'AssignCustomFields') ) {
211 return ( 0, $self->loc('Permission Denied') );
213 $args{'LookupType'} = 'RT::Queue-RT::Ticket';
216 my $queue = RT::Queue->new($self->CurrentUser);
217 $queue->Load($args{'Queue'});
218 unless ($queue->Id) {
219 return (0, $self->loc("Queue not found"));
221 unless ( $queue->CurrentUserHasRight('AssignCustomFields') ) {
222 return ( 0, $self->loc('Permission Denied') );
224 $args{'LookupType'} = 'RT::Queue-RT::Ticket';
225 $args{'Queue'} = $queue->Id;
228 my ($ok, $msg) = $self->_IsValidRegex( $args{'Pattern'} );
229 return (0, $self->loc("Invalid pattern: [_1]", $msg)) unless $ok;
231 if ( $args{'MaxValues'} != 1 && $args{'Type'} =~ /(text|combobox)$/i ) {
232 $RT::Logger->warning("Support for 'multiple' Texts or Comboboxes is not implemented");
233 $args{'MaxValues'} = 1;
236 (my $rv, $msg) = $self->SUPER::Create(
237 Name => $args{'Name'},
238 Type => $args{'Type'},
239 MaxValues => $args{'MaxValues'},
240 Pattern => $args{'Pattern'},
241 Description => $args{'Description'},
242 Disabled => $args{'Disabled'},
243 LookupType => $args{'LookupType'},
244 Repeated => $args{'Repeated'},
247 if ( exists $args{'LinkValueTo'}) {
248 $self->SetLinkValueTo($args{'LinkValueTo'});
251 if ( exists $args{'IncludeContentForValue'}) {
252 $self->SetIncludeContentForValue($args{'IncludeContentForValue'});
255 if ( exists $args{'ValuesClass'} ) {
256 $self->SetValuesClass( $args{'ValuesClass'} );
259 if ( exists $args{'BasedOn'} ) {
260 $self->SetBasedOn( $args{'BasedOn'} );
263 return ($rv, $msg) unless exists $args{'Queue'};
265 # Compat code -- create a new ObjectCustomField mapping
266 my $OCF = RT::ObjectCustomField->new( $self->CurrentUser );
268 CustomField => $self->Id,
269 ObjectId => $args{'Queue'},
277 Load a custom field. If the value handed in is an integer, load by custom field ID. Otherwise, Load by name.
283 my $id = shift || '';
285 if ( $id =~ /^\d+$/ ) {
286 return $self->SUPER::Load( $id );
288 return $self->LoadByName( Name => $id );
295 =head2 LoadByName (Queue => QUEUEID, Name => NAME)
297 Loads the Custom field named NAME.
299 Will load a Disabled Custom Field even if there is a non-disabled Custom Field
302 If a Queue parameter is specified, only look for ticket custom fields tied to that Queue.
304 If the Queue parameter is '0', look for global ticket custom fields.
306 If no queue parameter is specified, look for any and all custom fields with this name.
308 BUG/TODO, this won't let you specify that you only want user or group CFs.
312 # Compatibility for API change after 3.0 beta 1
313 *LoadNameAndQueue = \&LoadByName;
314 # Change after 3.4 beta.
315 *LoadByNameAndQueue = \&LoadByName;
325 unless ( defined $args{'Name'} && length $args{'Name'} ) {
326 $RT::Logger->error("Couldn't load Custom Field without Name");
327 return wantarray ? (0, $self->loc("No name provided")) : 0;
330 # if we're looking for a queue by name, make it a number
331 if ( defined $args{'Queue'} && $args{'Queue'} =~ /\D/ ) {
332 my $QueueObj = RT::Queue->new( $self->CurrentUser );
333 $QueueObj->Load( $args{'Queue'} );
334 $args{'Queue'} = $QueueObj->Id;
337 # XXX - really naive implementation. Slow. - not really. still just one query
339 my $CFs = RT::CustomFields->new( $self->CurrentUser );
340 $CFs->SetContextObject( $self->ContextObject );
341 my $field = $args{'Name'} =~ /\D/? 'Name' : 'id';
342 $CFs->Limit( FIELD => $field, VALUE => $args{'Name'}, CASESENSITIVE => 0);
343 # Don't limit to queue if queue is 0. Trying to do so breaks
344 # RT::Group type CFs.
345 if ( defined $args{'Queue'} ) {
346 $CFs->LimitToQueue( $args{'Queue'} );
349 # When loading by name, we _can_ load disabled fields, but prefer
350 # non-disabled fields.
353 { FIELD => "Disabled", ORDER => 'ASC' },
356 # We only want one entry.
357 $CFs->RowsPerPage(1);
359 # version before 3.8 just returns 0, so we need to test if wantarray to be
360 # backward compatible.
361 return wantarray ? (0, $self->loc("Not found")) : 0 unless my $first = $CFs->First;
363 return $self->LoadById( $first->id );
368 # {{{ Dealing with custom field values
371 =head2 Custom field values
375 Return a object (collection) of all acceptable values for this Custom Field.
376 Class of the object can vary and depends on the return value
377 of the C<ValuesClass> method.
381 *ValuesObj = \&Values;
386 my $class = $self->ValuesClass || 'RT::CustomFieldValues';
387 eval "require $class" or die "$@";
388 my $cf_values = $class->new( $self->CurrentUser );
389 # if the user has no rights, return an empty object
390 if ( $self->id && $self->CurrentUserHasRight( 'SeeCustomField') ) {
391 $cf_values->LimitToCustomField( $self->Id );
400 Create a new value for this CustomField. Takes a paramhash containing the elements Name, Description and SortOrder
408 unless ($self->CurrentUserHasRight('AdminCustomField')) {
409 return (0, $self->loc('Permission Denied'));
413 if ( !defined $args{'Name'} || $args{'Name'} eq '' ) {
414 return (0, $self->loc("Can't add a custom field value without a name"));
417 my $newval = RT::CustomFieldValue->new( $self->CurrentUser );
418 return $newval->Create( %args, CustomField => $self->Id );
426 =head3 DeleteValue ID
428 Deletes a value from this custom field by id.
430 Does not remove this value for any article which has had it selected
437 unless ( $self->CurrentUserHasRight('AdminCustomField') ) {
438 return (0, $self->loc('Permission Denied'));
441 my $val_to_del = RT::CustomFieldValue->new( $self->CurrentUser );
442 $val_to_del->Load( $id );
443 unless ( $val_to_del->Id ) {
444 return (0, $self->loc("Couldn't find that value"));
446 unless ( $val_to_del->CustomField == $self->Id ) {
447 return (0, $self->loc("That is not a value for this custom field"));
450 my $retval = $val_to_del->Delete;
452 return (0, $self->loc("Custom field value could not be deleted"));
454 return ($retval, $self->loc("Custom field value deleted"));
460 =head2 ValidateQueue Queue
462 Make sure that the queue specified is a valid queue name
470 return undef unless defined $id;
471 # 0 means "Global" null would _not_ be ok.
472 return 1 if $id eq '0';
474 my $q = RT::Queue->new( $RT::SystemUser );
476 return undef unless $q->id;
485 Retuns an array of the types of CustomField that are supported
490 return (keys %FieldTypes);
495 # {{{ IsSelectionType
497 =head2 IsSelectionType
499 Retuns a boolean value indicating whether the C<Values> method makes sense
500 to this Custom Field.
504 sub IsSelectionType {
506 my $type = @_? shift : $self->Type;
507 return undef unless $type;
509 $type =~ /(?:Select|Combobox|Autocomplete)/;
515 =head2 IsExternalValues
519 sub IsExternalValues {
521 my $selectable = $self->IsSelectionType( @_ );
522 return $selectable unless $selectable;
524 my $class = $self->ValuesClass;
525 return 0 if $class eq 'RT::CustomFieldValues';
531 return '' unless $self->IsSelectionType;
533 my $class = $self->FirstAttribute( 'ValuesClass' );
534 $class = $class->Content if $class;
535 return $class || 'RT::CustomFieldValues';
540 my $class = shift || 'RT::CustomFieldValues';
542 if( $class eq 'RT::CustomFieldValues' ) {
543 return $self->DeleteAttribute( 'ValuesClass' );
545 return $self->SetAttribute( Name => 'ValuesClass', Content => $class );
549 =head2 FriendlyType [TYPE, MAX_VALUES]
551 Returns a localized human-readable version of the custom field type.
552 If a custom field type is specified as the parameter, the friendly type for that type will be returned
559 my $type = @_ ? shift : $self->Type;
560 my $max = @_ ? shift : $self->MaxValues;
561 $max = 0 unless $max;
563 if (my $friendly_type = $FieldTypes{$type}[$max>2 ? 2 : $max]) {
564 return ( $self->loc( $friendly_type, $max ) );
567 return ( $self->loc( $type ) );
571 sub FriendlyTypeComposite {
573 my $composite = shift || $self->TypeComposite;
574 return $self->FriendlyType(split(/-/, $composite, 2));
578 =head2 ValidateType TYPE
580 Takes a single string. returns true if that string is a value
590 if ( $type =~ s/(?:Single|Multiple)$// ) {
591 $RT::Logger->warning( "Prefix 'Single' and 'Multiple' to Type deprecated, use MaxValues instead at (". join(":",caller).")");
594 if ( $FieldTypes{$type} ) {
606 if ($type =~ s/(?:(Single)|Multiple)$//) {
607 $RT::Logger->warning("'Single' and 'Multiple' on SetType deprecated, use SetMaxValues instead at (". join(":",caller).")");
608 $self->SetMaxValues($1 ? 1 : 0);
610 $self->SUPER::SetType($type);
613 =head2 SetPattern STRING
615 Takes a single string representing a regular expression. Performs basic
616 validation on that regex, and sets the C<Pattern> field for the CF if it
625 my ($ok, $msg) = $self->_IsValidRegex($regex);
627 return $self->SUPER::SetPattern($regex);
630 return (0, $self->loc("Invalid pattern: [_1]", $msg));
634 =head2 _IsValidRegex(Str $regex) returns (Bool $success, Str $msg)
636 Tests if the string contains an invalid regex.
642 my $regex = shift or return (1, 'valid');
645 local $SIG{__DIE__} = sub { 1 };
646 local $SIG{__WARN__} = sub { 1 };
648 if (eval { qr/$regex/; 1 }) {
653 $err =~ s{[,;].*}{}; # strip debug info from error
662 Returns true if this CustomField only accepts a single value.
663 Returns false if it accepts multiple values
669 if (($self->MaxValues||0) == 1) {
677 sub UnlimitedValues {
679 if (($self->MaxValues||0) == 0) {
689 =head2 CurrentUserHasRight RIGHT
691 Helper function to call the custom field's queue's CurrentUserHasRight with the passed in args.
695 sub CurrentUserHasRight {
699 return $self->CurrentUser->HasRight(
705 =head2 ACLEquivalenceObjects
707 Returns list of objects via which users can get rights on this custom field. For custom fields
708 these objects can be set using L<ContextObject|/"ContextObject and SetContextObject">.
712 sub ACLEquivalenceObjects {
715 my $ctx = $self->ContextObject
717 return ($ctx, $ctx->ACLEquivalenceObjects);
720 =head2 ContextObject and SetContextObject
722 Set or get a context for this object. It can be ticket, queue or another object
723 this CF applies to. Used for ACL control, for example SeeCustomField can be granted on
724 queue level to allow people to see all fields applied to the queue.
728 sub SetContextObject {
730 return $self->{'context_object'} = shift;
735 return $self->{'context_object'};
743 unless ( $self->CurrentUserHasRight('AdminCustomField') ) {
744 return ( 0, $self->loc('Permission Denied') );
746 return $self->SUPER::_Set( @_ );
756 Takes the name of a table column.
757 Returns its value as a string, if the user passes an ACL check
763 return undef unless $self->id;
765 # we need to do the rights check
766 unless ( $self->CurrentUserHasRight('SeeCustomField') ) {
768 "Permission denied. User #". $self->CurrentUser->id
769 ." has no SeeCustomField right on CF #". $self->id
773 return $self->__Value( @_ );
777 # {{{ sub SetDisabled
782 1 will cause this custom field to no longer be avaialble for objects.
783 0 will re-enable this field.
789 =head2 SetTypeComposite
791 Set this custom field's type and maximum values as a composite value
795 sub SetTypeComposite {
797 my $composite = shift;
799 my $old = $self->TypeComposite;
801 my ($type, $max_values) = split(/-/, $composite, 2);
802 if ( $type ne $self->Type ) {
803 my ($status, $msg) = $self->SetType( $type );
804 return ($status, $msg) unless $status;
806 if ( ($max_values || 0) != ($self->MaxValues || 0) ) {
807 my ($status, $msg) = $self->SetMaxValues( $max_values );
808 return ($status, $msg) unless $status;
810 return 1, $self->loc(
811 "Type changed from '[_1]' to '[_2]'",
812 $self->FriendlyTypeComposite( $old ),
813 $self->FriendlyTypeComposite( $composite ),
819 Returns a composite value composed of this object's type and maximum values
826 return join '-', ($self->Type || ''), ($self->MaxValues || 0);
829 =head2 TypeComposites
831 Returns an array of all possible composite values for custom fields.
837 return grep !/(?:[Tt]ext|Combobox|Date)-0/, map { ("$_-1", "$_-0") } $self->Types;
842 Autrijus: care to doc how LookupTypes work?
849 if ( $lookup ne $self->LookupType ) {
850 # Okay... We need to invalidate our existing relationships
851 my $ObjectCustomFields = RT::ObjectCustomFields->new($self->CurrentUser);
852 $ObjectCustomFields->LimitToCustomField($self->Id);
853 $_->Delete foreach @{$ObjectCustomFields->ItemsArrayRef};
855 return $self->SUPER::SetLookupType($lookup);
860 Returns an array of LookupTypes available
867 return keys %FRIENDLY_OBJECT_TYPES;
870 my @FriendlyObjectTypes = (
871 "[_1] objects", # loc
872 "[_1]'s [_2] objects", # loc
873 "[_1]'s [_2]'s [_3] objects", # loc
876 =head2 FriendlyLookupType
878 Returns a localized description of the type of this custom field
882 sub FriendlyLookupType {
884 my $lookup = shift || $self->LookupType;
886 return ($self->loc( $FRIENDLY_OBJECT_TYPES{$lookup} ))
887 if (defined $FRIENDLY_OBJECT_TYPES{$lookup} );
889 my @types = map { s/^RT::// ? $self->loc($_) : $_ }
890 grep { defined and length }
891 split( /-/, $lookup )
893 return ( $self->loc( $FriendlyObjectTypes[$#types], @types ) );
896 sub RecordClassFromLookupType {
898 my ($class) = ($self->LookupType =~ /^([^-]+)/);
901 "Custom Field #". $self->id
902 ." has incorrect LookupType '". $self->LookupType ."'"
909 sub CollectionClassFromLookupType {
912 my $record_class = $self->RecordClassFromLookupType;
913 return undef unless $record_class;
915 my $collection_class;
916 if ( UNIVERSAL::can($record_class.'Collection', 'new') ) {
917 $collection_class = $record_class.'Collection';
918 } elsif ( UNIVERSAL::can($record_class.'es', 'new') ) {
919 $collection_class = $record_class.'es';
920 } elsif ( UNIVERSAL::can($record_class.'s', 'new') ) {
921 $collection_class = $record_class.'s';
923 $RT::Logger->error("Can not find a collection class for record class '$record_class'");
926 return $collection_class;
931 Returns collection with objects this custom field is applied to.
932 Class of the collection depends on L</LookupType>.
933 See all L</NotAppliedTo> .
935 Doesn't takes into account if object is applied globally.
942 my ($res, $ocfs_alias) = $self->_AppliedTo;
943 return $res unless $res;
946 ALIAS => $ocfs_alias,
948 OPERATOR => 'IS NOT',
957 Returns collection with objects this custom field is not applied to.
958 Class of the collection depends on L</LookupType>.
959 See all L</AppliedTo> .
961 Doesn't takes into account if object is applied globally.
968 my ($res, $ocfs_alias) = $self->_AppliedTo;
969 return $res unless $res;
972 ALIAS => $ocfs_alias,
984 my ($class) = $self->CollectionClassFromLookupType;
985 return undef unless $class;
987 my $res = $class->new( $self->CurrentUser );
989 # If CF is a Group CF, only display user-defined groups
990 if ( $class eq 'RT::Groups' ) {
991 $res->LimitToUserDefinedGroups;
994 $res->OrderBy( FIELD => 'Name' );
995 my $ocfs_alias = $res->Join(
999 TABLE2 => 'ObjectCustomFields',
1000 FIELD2 => 'ObjectId',
1003 LEFTJOIN => $ocfs_alias,
1004 ALIAS => $ocfs_alias,
1005 FIELD => 'CustomField',
1008 return ($res, $ocfs_alias);
1013 Takes object id and returns corresponding L<RT::ObjectCustomField>
1014 record if this custom field is applied to the object. Use 0 to check
1015 if custom field is applied globally.
1022 my $ocf = RT::ObjectCustomField->new( $self->CurrentUser );
1023 $ocf->LoadByCols( CustomField => $self->id, ObjectId => $id || 0 );
1024 return undef unless $ocf->id;
1028 =head2 AddToObject OBJECT
1030 Add this custom field as a custom field for a single object, such as a queue or group.
1040 my $id = $object->Id || 0;
1042 unless (index($self->LookupType, ref($object)) == 0) {
1043 return ( 0, $self->loc('Lookup type mismatch') );
1046 unless ( $object->CurrentUserHasRight('AssignCustomFields') ) {
1047 return ( 0, $self->loc('Permission Denied') );
1050 if ( $self->IsApplied( $id ) ) {
1051 return ( 0, $self->loc("Custom field is already applied to the object") );
1056 return (0, $self->loc("Couldn't apply custom field to an object as it's global already") )
1057 if $self->IsApplied( 0 );
1060 my $applied = RT::ObjectCustomFields->new( $self->CurrentUser );
1061 $applied->LimitToCustomField( $self->id );
1062 while ( my $record = $applied->Next ) {
1067 my $ocf = RT::ObjectCustomField->new( $self->CurrentUser );
1068 my ( $oid, $msg ) = $ocf->Create(
1069 ObjectId => $id, CustomField => $self->id,
1071 return ( $oid, $msg );
1075 =head2 RemoveFromObject OBJECT
1077 Remove this custom field for a single object, such as a queue or group.
1083 sub RemoveFromObject {
1086 my $id = $object->Id || 0;
1088 unless (index($self->LookupType, ref($object)) == 0) {
1089 return ( 0, $self->loc('Object type mismatch') );
1092 unless ( $object->CurrentUserHasRight('AssignCustomFields') ) {
1093 return ( 0, $self->loc('Permission Denied') );
1096 my $ocf = $self->IsApplied( $id );
1098 return ( 0, $self->loc("This custom field does not apply to that object") );
1101 # XXX: Delete doesn't return anything
1102 my ( $oid, $msg ) = $ocf->Delete;
1103 return ( $oid, $msg );
1106 # {{{ AddValueForObject
1108 =head2 AddValueForObject HASH
1110 Adds a custom field value for a record object of some kind.
1111 Takes a param hash of
1125 sub AddValueForObject {
1130 LargeContent => undef,
1131 ContentType => undef,
1134 my $obj = $args{'Object'} or return ( 0, $self->loc('Invalid object') );
1136 unless ( $self->CurrentUserHasRight('ModifyCustomField') ) {
1137 return ( 0, $self->loc('Permission Denied') );
1140 unless ( $self->MatchPattern($args{'Content'}) ) {
1141 return ( 0, $self->loc('Input must match [_1]', $self->FriendlyPattern) );
1144 $RT::Handle->BeginTransaction;
1146 if ( $self->MaxValues ) {
1147 my $current_values = $self->ValuesForObject($obj);
1148 my $extra_values = ( $current_values->Count + 1 ) - $self->MaxValues;
1150 # (The +1 is for the new value we're adding)
1152 # If we have a set of current values and we've gone over the maximum
1153 # allowed number of values, we'll need to delete some to make room.
1154 # which former values are blown away is not guaranteed
1156 while ($extra_values) {
1157 my $extra_item = $current_values->Next;
1158 unless ( $extra_item->id ) {
1159 $RT::Logger->crit( "We were just asked to delete "
1160 ."a custom field value that doesn't exist!" );
1161 $RT::Handle->Rollback();
1164 $extra_item->Delete;
1168 # For date, we need to store Content as ISO date
1169 if ($self->Type eq 'Date') {
1170 my $DateObj = new RT::Date( $self->CurrentUser );
1172 Format => 'unknown',
1173 Value => $args{'Content'},
1175 $args{'Content'} = $DateObj->ISO;
1177 my $newval = RT::ObjectCustomFieldValue->new( $self->CurrentUser );
1178 my $val = $newval->Create(
1179 ObjectType => ref($obj),
1180 ObjectId => $obj->Id,
1181 Content => $args{'Content'},
1182 LargeContent => $args{'LargeContent'},
1183 ContentType => $args{'ContentType'},
1184 CustomField => $self->Id
1188 $RT::Handle->Rollback();
1189 return ($val, $self->loc("Couldn't create record"));
1192 $RT::Handle->Commit();
1201 =head2 MatchPattern STRING
1203 Tests the incoming string against the Pattern of this custom field object
1204 and returns a boolean; returns true if the Pattern is empty.
1210 my $regex = $self->Pattern or return 1;
1212 return (( defined $_[0] ? $_[0] : '') =~ $regex);
1218 # {{{ FriendlyPattern
1220 =head2 FriendlyPattern
1222 Prettify the pattern of this custom field, by taking the text in C<(?#text)>
1227 sub FriendlyPattern {
1229 my $regex = $self->Pattern;
1231 return '' unless length $regex;
1232 if ( $regex =~ /\(\?#([^)]*)\)/ ) {
1233 return '[' . $self->loc($1) . ']';
1243 # {{{ DeleteValueForObject
1245 =head2 DeleteValueForObject HASH
1247 Deletes a custom field value for a ticket. Takes a param hash of Object and Content
1249 Returns a tuple of (STATUS, MESSAGE). If the call succeeded, the STATUS is true. otherwise it's false
1253 sub DeleteValueForObject {
1255 my %args = ( Object => undef,
1261 unless ($self->CurrentUserHasRight('ModifyCustomField')) {
1262 return (0, $self->loc('Permission Denied'));
1265 my $oldval = RT::ObjectCustomFieldValue->new($self->CurrentUser);
1267 if (my $id = $args{'Id'}) {
1270 unless ($oldval->id) {
1271 $oldval->LoadByObjectContentAndCustomField(
1272 Object => $args{'Object'},
1273 Content => $args{'Content'},
1274 CustomField => $self->Id,
1279 # check to make sure we found it
1280 unless ($oldval->Id) {
1281 return(0, $self->loc("Custom field value [_1] could not be found for custom field [_2]", $args{'Content'}, $self->Name));
1284 # for single-value fields, we need to validate that empty string is a valid value for it
1285 if ( $self->SingleValue and not $self->MatchPattern( '' ) ) {
1286 return ( 0, $self->loc('Input must match [_1]', $self->FriendlyPattern) );
1291 my $ret = $oldval->Delete();
1293 return(0, $self->loc("Custom field value could not be found"));
1295 return($oldval->Id, $self->loc("Custom field value deleted"));
1299 =head2 ValuesForObject OBJECT
1301 Return an L<RT::ObjectCustomFieldValues> object containing all of this custom field's values for OBJECT
1305 sub ValuesForObject {
1309 my $values = new RT::ObjectCustomFieldValues($self->CurrentUser);
1310 unless ($self->CurrentUserHasRight('SeeCustomField')) {
1311 # Return an empty object if they have no rights to see
1316 $values->LimitToCustomField($self->Id);
1317 $values->LimitToEnabled();
1318 $values->LimitToObject($object);
1324 =head2 _ForObjectType PATH FRIENDLYNAME
1326 Tell RT that a certain object accepts custom fields
1330 'RT::Queue-RT::Ticket' => "Tickets", # loc
1331 'RT::Queue-RT::Ticket-RT::Transaction' => "Ticket Transactions", # loc
1332 'RT::User' => "Users", # loc
1333 'RT::Group' => "Groups", # loc
1335 This is a class method.
1339 sub _ForObjectType {
1342 my $friendly_name = shift;
1344 $FRIENDLY_OBJECT_TYPES{$path} = $friendly_name;
1349 =head2 IncludeContentForValue [VALUE] (and SetIncludeContentForValue)
1351 Gets or sets the C<IncludeContentForValue> for this custom field. RT
1352 uses this field to automatically include content into the user's browser
1353 as they display records with custom fields in RT.
1357 sub SetIncludeContentForValue {
1358 shift->IncludeContentForValue(@_);
1360 sub IncludeContentForValue{
1362 $self->_URLTemplate('IncludeContentForValue', @_);
1367 =head2 LinkValueTo [VALUE] (and SetLinkValueTo)
1369 Gets or sets the C<LinkValueTo> for this custom field. RT
1370 uses this field to make custom field values into hyperlinks in the user's
1371 browser as they display records with custom fields in RT.
1376 sub SetLinkValueTo {
1377 shift->LinkValueTo(@_);
1382 $self->_URLTemplate('LinkValueTo', @_);
1387 =head2 _URLTemplate NAME [VALUE]
1389 With one argument, returns the _URLTemplate named C<NAME>, but only if
1390 the current user has the right to see this custom field.
1392 With two arguments, attemptes to set the relevant template value.
1398 my $template_name = shift;
1402 unless ( $self->CurrentUserHasRight('AdminCustomField') ) {
1403 return ( 0, $self->loc('Permission Denied') );
1405 $self->SetAttribute( Name => $template_name, Content => $value );
1406 return ( 1, $self->loc('Updated') );
1408 unless ( $self->id && $self->CurrentUserHasRight('SeeCustomField') ) {
1412 my @attr = $self->Attributes->Named($template_name);
1413 my $attr = shift @attr;
1415 if ($attr) { return $attr->Content }
1424 return $self->DeleteAttribute( "BasedOn" )
1425 unless defined $value and length $value;
1427 my $cf = RT::CustomField->new( $self->CurrentUser );
1428 $cf->Load( ref $value ? $value->Id : $value );
1430 return (0, "Permission denied")
1431 unless $cf->Id && $cf->CurrentUserHasRight('SeeCustomField');
1433 return $self->AddAttribute(
1435 Description => "Custom field whose CF we depend on",
1442 my $obj = RT::CustomField->new( $self->CurrentUser );
1444 my $attribute = $self->FirstAttribute("BasedOn");
1445 $obj->Load($attribute->Content) if defined $attribute;