67efc4d9055a3b1a2fd53aa065e99d0ecb2ff053
[freeside.git] / rt / lib / RT / CustomField.pm
1 # BEGIN BPS TAGGED BLOCK {{{
2 #
3 # COPYRIGHT:
4 #
5 # This software is Copyright (c) 1996-2018 Best Practical Solutions, LLC
6 #                                          <sales@bestpractical.com>
7 #
8 # (Except where explicitly superseded by other copyright notices)
9 #
10 #
11 # LICENSE:
12 #
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
16 # from www.gnu.org.
17 #
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.
22 #
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.
28 #
29 #
30 # CONTRIBUTION SUBMISSION POLICY:
31 #
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.)
37 #
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.
46 #
47 # END BPS TAGGED BLOCK }}}
48
49 package RT::CustomField;
50
51 use strict;
52 use warnings;
53 use 5.010;
54
55 use Scalar::Util 'blessed';
56
57 use base 'RT::Record';
58
59 use Role::Basic 'with';
60 with "RT::Record::Role::Rights";
61
62 sub Table {'CustomFields'}
63
64 use Scalar::Util qw(blessed);
65 use RT::CustomFieldValues;
66 use RT::ObjectCustomFields;
67 use RT::ObjectCustomFieldValues;
68
69 our %FieldTypes = (
70     Select => {
71         sort_order => 10,
72         selection_type => 1,
73
74         labels => [ 'Select multiple values',               # loc
75                     'Select one value',                     # loc
76                     'Select up to [quant,_1,value,values]', # loc
77                   ],
78
79         render_types => {
80             multiple => [
81
82                 # Default is the first one
83                 'Select box',              # loc
84                 'List',                    # loc
85             ],
86             single => [ 'Select box',              # loc
87                         'Dropdown',                # loc
88                         'List',                    # loc
89                       ]
90         },
91
92     },
93     Freeform => {
94         sort_order => 20,
95         selection_type => 0,
96
97         labels => [ 'Enter multiple values',               # loc
98                     'Enter one value',                     # loc
99                     'Enter up to [quant,_1,value,values]', # loc
100                   ]
101                 },
102     Text => {
103         sort_order => 30,
104         selection_type => 0,
105         labels         => [
106                     'Fill in multiple text areas',                   # loc
107                     'Fill in one text area',                         # loc
108                     'Fill in up to [quant,_1,text area,text areas]', # loc
109                   ]
110             },
111     Wikitext => {
112         sort_order => 40,
113         selection_type => 0,
114         labels         => [
115                     'Fill in multiple wikitext areas',                       # loc
116                     'Fill in one wikitext area',                             # loc
117                     'Fill in up to [quant,_1,wikitext area,wikitext areas]', # loc
118                   ]
119                 },
120
121     Image => {
122         sort_order => 50,
123         selection_type => 0,
124         labels         => [
125                     'Upload multiple images',               # loc
126                     'Upload one image',                     # loc
127                     'Upload up to [quant,_1,image,images]', # loc
128                   ]
129              },
130     Binary => {
131         sort_order => 60,
132         selection_type => 0,
133         labels         => [
134                     'Upload multiple files',              # loc
135                     'Upload one file',                    # loc
136                     'Upload up to [quant,_1,file,files]', # loc
137                   ]
138               },
139
140     Combobox => {
141         sort_order => 70,
142         selection_type => 1,
143         labels         => [
144                     'Combobox: Select or enter multiple values',               # loc
145                     'Combobox: Select or enter one value',                     # loc
146                     'Combobox: Select or enter up to [quant,_1,value,values]', # loc
147                   ]
148                 },
149     Autocomplete => {
150         sort_order => 80,
151         selection_type => 1,
152         labels         => [
153                     'Enter multiple values with autocompletion',               # loc
154                     'Enter one value with autocompletion',                     # loc
155                     'Enter up to [quant,_1,value,values] with autocompletion', # loc
156                   ]
157     },
158
159     Date => {
160         sort_order => 90,
161         selection_type => 0,
162         labels         => [
163                     'Select multiple dates',              # loc
164                     'Select date',                        # loc
165                     'Select up to [quant,_1,date,dates]', # loc
166                   ]
167             },
168     DateTime => {
169         sort_order => 100,
170         selection_type => 0,
171         labels         => [
172                     'Select multiple datetimes',                  # loc
173                     'Select datetime',                            # loc
174                     'Select up to [quant,_1,datetime,datetimes]', # loc
175                   ]
176                 },
177     TimeValue => {
178         sort_order => 105,
179         selection_type => 0,
180         labels         => [
181                      'Enter multiple time values (UNSUPPORTED)',
182                      'Enter a time value',
183                      'Enter [_1] time values (UNSUPPORTED)',
184                    ]
185                  },
186
187     IPAddress => {
188         sort_order => 110,
189         selection_type => 0,
190
191         labels => [ 'Enter multiple IP addresses',                    # loc
192                     'Enter one IP address',                           # loc
193                     'Enter up to [quant,_1,IP address,IP addresses]', # loc
194                   ]
195                 },
196     IPAddressRange => {
197         sort_order => 120,
198         selection_type => 0,
199
200         labels => [ 'Enter multiple IP address ranges',                          # loc
201                     'Enter one IP address range',                                # loc
202                     'Enter up to [quant,_1,IP address range,IP address ranges]', # loc
203                   ]
204                 },
205 );
206
207
208 my %BUILTIN_GROUPINGS;
209 my %FRIENDLY_LOOKUP_TYPES = ();
210
211 __PACKAGE__->RegisterLookupType( 'RT::Queue-RT::Ticket' => "Tickets", );    #loc
212 __PACKAGE__->RegisterLookupType( 'RT::Queue-RT::Ticket-RT::Transaction' => "Ticket Transactions", ); #loc
213 __PACKAGE__->RegisterLookupType( 'RT::User'  => "Users", );                           #loc
214 __PACKAGE__->RegisterLookupType( 'RT::Queue'  => "Queues", );                         #loc
215 __PACKAGE__->RegisterLookupType( 'RT::Group' => "Groups", );                          #loc
216
217 __PACKAGE__->RegisterBuiltInGroupings(
218     'RT::Ticket'    => [ qw(Basics Dates Links People) ],
219     'RT::User'      => [ 'Identity', 'Access control', 'Location', 'Phones' ],
220 );
221
222 __PACKAGE__->AddRight( General => SeeCustomField         => 'View custom fields'); # loc
223 __PACKAGE__->AddRight( Admin   => AdminCustomField       => 'Create, modify and delete custom fields'); # loc
224 __PACKAGE__->AddRight( Admin   => AdminCustomFieldValues => 'Create, modify and delete custom fields values'); # loc
225 __PACKAGE__->AddRight( Staff   => ModifyCustomField      => 'Add, modify and delete custom field values for objects'); # loc
226
227 =head1 NAME
228
229   RT::CustomField_Overlay - overlay for RT::CustomField
230
231 =head1 DESCRIPTION
232
233 =head1 'CORE' METHODS
234
235 =head2 Create PARAMHASH
236
237 Create takes a hash of values and creates a row in the database:
238
239   varchar(200) 'Name'.
240   varchar(200) 'Type'.
241   int(11) 'MaxValues'.
242   varchar(255) 'Pattern'.
243   varchar(255) 'Description'.
244   int(11) 'SortOrder'.
245   varchar(255) 'LookupType'.
246   smallint(6) 'Disabled'.
247
248 C<LookupType> is generally the result of either
249 C<RT::Ticket->CustomFieldLookupType> or C<RT::Transaction->CustomFieldLookupType>.
250
251 =cut
252
253 sub Create {
254     my $self = shift;
255     my %args = (
256         Name        => '',
257         Type        => '',
258         MaxValues   => 0,
259         Pattern     => '',
260         Description => '',
261         Disabled    => 0,
262         LookupType  => '',
263         LinkValueTo => '',
264         IncludeContentForValue => '',
265         @_,
266     );
267
268     unless ( $self->CurrentUser->HasRight(Object => $RT::System, Right => 'AdminCustomField') ) {
269         return (0, $self->loc('Permission Denied'));
270     }
271
272     if ( $args{TypeComposite} ) {
273         @args{'Type', 'MaxValues'} = split(/-/, $args{TypeComposite}, 2);
274     }
275     elsif ( $args{Type} =~ s/(?:(Single)|Multiple)$// ) {
276         # old style Type string
277         $args{'MaxValues'} = $1 ? 1 : 0;
278     }
279     $args{'MaxValues'} = int $args{'MaxValues'};
280
281     if ( !exists $args{'Queue'}) {
282     # do nothing -- things below are strictly backward compat
283     }
284     elsif (  ! $args{'Queue'} ) {
285         unless ( $self->CurrentUser->HasRight( Object => $RT::System, Right => 'AssignCustomFields') ) {
286             return ( 0, $self->loc('Permission Denied') );
287         }
288         $args{'LookupType'} = 'RT::Queue-RT::Ticket';
289     }
290     else {
291         my $queue = RT::Queue->new($self->CurrentUser);
292         $queue->Load($args{'Queue'});
293         unless ($queue->Id) {
294             return (0, $self->loc("Queue not found"));
295         }
296         unless ( $queue->CurrentUserHasRight('AssignCustomFields') ) {
297             return ( 0, $self->loc('Permission Denied') );
298         }
299         $args{'LookupType'} = 'RT::Queue-RT::Ticket';
300         $args{'Queue'} = $queue->Id;
301     }
302
303     my ($ok, $msg) = $self->_IsValidRegex( $args{'Pattern'} );
304     return (0, $self->loc("Invalid pattern: [_1]", $msg)) unless $ok;
305
306     if ( $args{'MaxValues'} != 1 && $args{'Type'} =~ /(text|combobox)$/i ) {
307         $RT::Logger->debug("Support for 'multiple' Texts or Comboboxes is not implemented");
308         $args{'MaxValues'} = 1;
309     }
310
311     if ( $args{'RenderType'} ||= undef ) {
312         my $composite = join '-', @args{'Type', 'MaxValues'};
313         return (0, $self->loc("This custom field has no Render Types"))
314             unless $self->HasRenderTypes( $composite );
315
316         if ( $args{'RenderType'} eq $self->DefaultRenderType( $composite ) ) {
317             $args{'RenderType'} = undef;
318         } else {
319             return (0, $self->loc("Invalid Render Type") )
320                 unless grep $_ eq  $args{'RenderType'}, $self->RenderTypes( $composite );
321         }
322     }
323
324     $args{'ValuesClass'} = undef if ($args{'ValuesClass'} || '') eq 'RT::CustomFieldValues';
325     if ( $args{'ValuesClass'} ||= undef ) {
326         return (0, $self->loc("This Custom Field can not have list of values"))
327             unless $self->IsSelectionType( $args{'Type'} );
328
329         unless ( $self->ValidateValuesClass( $args{'ValuesClass'} ) ) {
330             return (0, $self->loc("Invalid Custom Field values source"));
331         }
332     }
333
334     $args{'Disabled'} ||= 0;
335
336     (my $rv, $msg) = $self->SUPER::Create(
337         Name        => $args{'Name'},
338         Type        => $args{'Type'},
339         RenderType  => $args{'RenderType'},
340         MaxValues   => $args{'MaxValues'},
341         Pattern     => $args{'Pattern'},
342         BasedOn     => $args{'BasedOn'},
343         ValuesClass => $args{'ValuesClass'},
344         Description => $args{'Description'},
345         Disabled    => $args{'Disabled'},
346         LookupType  => $args{'LookupType'},
347     );
348
349     if ($rv) {
350         if ( exists $args{'LinkValueTo'}) {
351             $self->SetLinkValueTo($args{'LinkValueTo'});
352         }
353
354         if ( exists $args{'IncludeContentForValue'}) {
355             $self->SetIncludeContentForValue($args{'IncludeContentForValue'});
356         }
357
358         if ( exists $args{'UILocation'} ) {
359             $self->SetUILocation( $args{'UILocation'} );
360         }
361
362         if ( exists $args{'NoClone'} ) {
363             $self->SetNoClone( $args{'NoClone'} );
364         }
365
366         return ($rv, $msg) unless exists $args{'Queue'};
367
368         # Compat code -- create a new ObjectCustomField mapping
369         my $OCF = RT::ObjectCustomField->new( $self->CurrentUser );
370         $OCF->Create(
371             CustomField => $self->Id,
372             ObjectId => $args{'Queue'},
373         );
374     }
375
376     return ($rv, $msg);
377 }
378
379 =head2 Load ID/NAME
380
381 Load a custom field.  If the value handed in is an integer, load by custom field ID. Otherwise, Load by name.
382
383 =cut
384
385 sub Load {
386     my $self = shift;
387     my $id = shift || '';
388
389     if ( $id =~ /^\d+$/ ) {
390         return $self->SUPER::Load( $id );
391     } else {
392         return $self->LoadByName( Name => $id );
393     }
394 }
395
396
397
398 =head2 LoadByName Name => C<NAME>, [...]
399
400 Loads the Custom field named NAME.  As other optional parameters, takes:
401
402 =over
403
404 =item LookupType => C<LOOKUPTYPE>
405
406 The type of Custom Field to look for; while this parameter is not
407 required, it is highly suggested, or you may not find the Custom Field
408 you are expecting.  It should be passed a C<LookupType> such as
409 L<RT::Ticket/CustomFieldLookupType> or
410 L<RT::User/CustomFieldLookupType>.
411
412 =item ObjectType => C<CLASS>
413
414 The class of object that the custom field is applied to.  This can be
415 intuited from the provided C<LookupType>.
416
417 =item ObjectId => C<ID>
418
419 limits the custom field search to one applied to the relevant id.  For
420 example, if a C<LookupType> of C<< RT::Ticket->CustomFieldLookupType >>
421 is used, this is which Queue the CF must be applied to.  Pass 0 to only
422 search custom fields that are applied globally.
423
424 =item IncludeDisabled => C<BOOLEAN>
425
426 Whether it should return Disabled custom fields if they match; defaults
427 to on, though non-Disabled custom fields are returned preferentially.
428
429 =item IncludeGlobal => C<BOOLEAN>
430
431 Whether to also search global custom fields, even if a value is provided
432 for C<ObjectId>; defaults to off.  Non-global custom fields are returned
433 preferentially.
434
435 =back
436
437 For backwards compatibility, a value passed for C<Queue> is equivalent
438 to specifying a C<LookupType> of L<RT::Ticket/CustomFieldLookupType>,
439 and a C<ObjectId> of the value passed as C<Queue>.
440
441 If multiple custom fields match the above constraints, the first
442 according to C<SortOrder> will be returned; ties are broken by C<id>,
443 lowest-first.
444
445 =head2 LoadNameAndQueue
446
447 =head2 LoadByNameAndQueue
448
449 Deprecated alternate names for L</LoadByName>.
450
451 =cut
452
453 # Compatibility for API change after 3.0 beta 1
454 *LoadNameAndQueue = \&LoadByName;
455 # Change after 3.4 beta.
456 *LoadByNameAndQueue = \&LoadByName;
457
458 sub LoadByName {
459     my $self = shift;
460     my %args = (
461         Name       => undef,
462         LookupType => undef,
463         ObjectType => undef,
464         ObjectId   => undef,
465
466         IncludeDisabled => 1,
467         IncludeGlobal   => 0,
468
469         # Back-compat
470         Queue => undef,
471
472         @_,
473     );
474
475     unless ( defined $args{'Name'} && length $args{'Name'} ) {
476         $RT::Logger->error("Couldn't load Custom Field without Name");
477         return wantarray ? (0, $self->loc("No name provided")) : 0;
478     }
479
480     if ( defined $args{'Queue'} ) {
481         # Set a LookupType for backcompat, otherwise we'll calculate
482         # one of RT::Queue from your ContextObj.  Older code was relying
483         # on us defaulting to RT::Queue-RT::Ticket in old LimitToQueue call.
484         $args{LookupType} ||= 'RT::Queue-RT::Ticket';
485         $args{ObjectId}   //= delete $args{Queue};
486     }
487
488     # Default the ObjectType to the top category of the LookupType; it's
489     # what the CFs are assigned on.
490     $args{ObjectType} ||= $1 if $args{LookupType} and $args{LookupType} =~ /^([^-]+)/;
491
492     # Resolve the ObjectId/ObjectType; this is necessary to properly
493     # limit ObjectId, and also possibly useful to set a ContextObj if we
494     # are currently lacking one.  It is not strictly necessary if we
495     # have a context object and were passed a numeric ObjectId, but it
496     # cannot hurt to verify its sanity.  Skip if we have a false
497     # ObjectId, which means "global", or if we lack an ObjectType
498     if ($args{ObjectId} and $args{ObjectType}) {
499         my ($obj, $ok, $msg);
500         eval {
501             $obj = $args{ObjectType}->new( $self->CurrentUser );
502             ($ok, $msg) = $obj->Load( $args{ObjectId} );
503         };
504
505         if ($ok) {
506             $args{ObjectId} = $obj->id;
507             $self->SetContextObject( $obj )
508                 unless $self->ContextObject;
509         } else {
510             $RT::Logger->warning("Failed to load $args{ObjectType} '$args{ObjectId}'");
511             if ($args{IncludeGlobal}) {
512                 # Fall back to acting like we were only asked about the
513                 # global case
514                 $args{ObjectId} = 0;
515             } else {
516                 # If they didn't also want global results, there's no
517                 # point in searching; abort
518                 return wantarray ? (0, $self->loc("Not found")) : 0;
519             }
520         }
521     } elsif (not $args{ObjectType} and $args{ObjectId}) {
522         # If we skipped out on the above due to lack of ObjectType, make
523         # sure we clear out ObjectId of anything lingering
524         $RT::Logger->warning("No LookupType or ObjectType passed; ignoring ObjectId");
525         delete $args{ObjectId};
526     }
527
528     my $CFs = RT::CustomFields->new( $self->CurrentUser );
529     $CFs->SetContextObject( $self->ContextObject );
530     my $field = $args{'Name'} =~ /\D/? 'Name' : 'id';
531     $CFs->Limit( FIELD => $field, VALUE => $args{'Name'}, CASESENSITIVE => 0);
532
533     # The context object may be a ticket, for example, as context for a
534     # queue CF.  The valid lookup types are thus the entire set of
535     # ACLEquivalenceObjects for the context object.
536     $args{LookupType} ||= [
537         map {$_->CustomFieldLookupType}
538             ($self->ContextObject, $self->ContextObject->ACLEquivalenceObjects) ]
539         if $self->ContextObject;
540
541     # Apply LookupType limits
542     $args{LookupType} = [ $args{LookupType} ]
543         if $args{LookupType} and not ref($args{LookupType});
544     $CFs->Limit( FIELD => "LookupType", OPERATOR => "IN", VALUE => $args{LookupType} )
545         if $args{LookupType};
546
547     # Default to by SortOrder and id; this mirrors the standard ordering
548     # of RT::CustomFields (minus the Name, which is guaranteed to be
549     # fixed)
550     my @order = (
551         { FIELD => 'SortOrder',
552           ORDER => 'ASC' },
553         { FIELD => 'id',
554           ORDER => 'ASC' },
555     );
556
557     if (defined $args{ObjectId}) {
558         # The join to OCFs is distinct -- either we have a global
559         # application or an objectid match, but never both.  Even if
560         # this were not the case, we care only for the first row.
561         my $ocfs = $CFs->_OCFAlias( Distinct => 1);
562         if ($args{IncludeGlobal}) {
563             $CFs->Limit(
564                 ALIAS    => $ocfs,
565                 FIELD    => 'ObjectId',
566                 OPERATOR => 'IN',
567                 VALUE    => [ $args{ObjectId}, 0 ],
568             );
569             # Find the queue-specific first
570             unshift @order, { ALIAS => $ocfs, FIELD => "ObjectId", ORDER => "DESC" };
571         } else {
572             $CFs->Limit(
573                 ALIAS => $ocfs,
574                 FIELD => 'ObjectId',
575                 VALUE => $args{ObjectId},
576             );
577         }
578     }
579
580     if ($args{IncludeDisabled}) {
581         # Load disabled fields, but return them only as a last resort.
582         # This goes at the front of @order, as we prefer the
583         # non-disabled global CF to the disabled Queue-specific CF.
584         $CFs->FindAllRows;
585         unshift @order, { FIELD => "Disabled", ORDER => 'ASC' };
586     }
587
588     # Apply the above orderings
589     $CFs->OrderByCols( @order );
590
591     # We only want one entry.
592     $CFs->RowsPerPage(1);
593
594     # version before 3.8 just returns 0, so we need to test if wantarray to be
595     # backward compatible.
596     return wantarray ? (0, $self->loc("Not found")) : 0 unless my $first = $CFs->First;
597
598     return $self->LoadById( $first->id );
599 }
600
601
602
603
604 =head2 Custom field values
605
606 =head3 Values FIELD
607
608 Return a object (collection) of all acceptable values for this Custom Field.
609 Class of the object can vary and depends on the return value
610 of the C<ValuesClass> method.
611
612 =cut
613
614 *ValuesObj = \&Values;
615
616 sub Values {
617     my $self = shift;
618
619     my $class = $self->ValuesClass;
620     if ( $class ne 'RT::CustomFieldValues') {
621         $class->require or die "Can't load $class: $@";
622     }
623     my $cf_values = $class->new( $self->CurrentUser );
624     $cf_values->SetCustomFieldObject( $self );
625     # if the user has no rights, return an empty object
626     if ( $self->id && $self->CurrentUserHasRight( 'SeeCustomField') ) {
627         $cf_values->LimitToCustomField( $self->Id );
628     } else {
629         $cf_values->Limit( FIELD => 'id', VALUE => 0, SUBCLAUSE => 'acl' );
630     }
631     return ($cf_values);
632 }
633
634
635 =head3 AddValue HASH
636
637 Create a new value for this CustomField.  Takes a paramhash containing the elements Name, Description and SortOrder
638
639 =cut
640
641 sub AddValue {
642     my $self = shift;
643     my %args = @_;
644
645     unless ($self->CurrentUserHasRight('AdminCustomField') || $self->CurrentUserHasRight('AdminCustomFieldValues')) {
646         return (0, $self->loc('Permission Denied'));
647     }
648
649     # allow zero value
650     if ( !defined $args{'Name'} || $args{'Name'} eq '' ) {
651         return (0, $self->loc("Can't add a custom field value without a name"));
652     }
653
654     my $newval = RT::CustomFieldValue->new( $self->CurrentUser );
655     return $newval->Create( %args, CustomField => $self->Id );
656 }
657
658
659
660
661 =head3 DeleteValue ID
662
663 Deletes a value from this custom field by id.
664
665 Does not remove this value for any article which has had it selected
666
667 =cut
668
669 sub DeleteValue {
670     my $self = shift;
671     my $id = shift;
672     unless ( $self->CurrentUserHasRight('AdminCustomField') || $self->CurrentUserHasRight('AdminCustomFieldValues') ) {
673         return (0, $self->loc('Permission Denied'));
674     }
675
676     my $val_to_del = RT::CustomFieldValue->new( $self->CurrentUser );
677     $val_to_del->Load( $id );
678     unless ( $val_to_del->Id ) {
679         return (0, $self->loc("Couldn't find that value"));
680     }
681     unless ( $val_to_del->CustomField == $self->Id ) {
682         return (0, $self->loc("That is not a value for this custom field"));
683     }
684
685     my $retval = $val_to_del->Delete;
686     unless ( $retval ) {
687         return (0, $self->loc("Custom field value could not be deleted"));
688     }
689     return ($retval, $self->loc("Custom field value deleted"));
690 }
691
692
693 =head2 ValidateQueue Queue
694
695 Make sure that the name specified is valid
696
697 =cut
698
699 sub ValidateName {
700     my $self = shift;
701     my $value = shift;
702
703     return 0 unless length $value;
704
705     return $self->SUPER::ValidateName($value);
706 }
707
708 =head2 ValidateQueue Queue
709
710 Make sure that the queue specified is a valid queue name
711
712 =cut
713
714 sub ValidateQueue {
715     my $self = shift;
716     my $id = shift;
717
718     return undef unless defined $id;
719     # 0 means "Global" null would _not_ be ok.
720     return 1 if $id eq '0';
721
722     my $q = RT::Queue->new( RT->SystemUser );
723     $q->Load( $id );
724     return undef unless $q->id;
725     return 1;
726 }
727
728
729
730 =head2 Types 
731
732 Retuns an array of the types of CustomField that are supported
733
734 =cut
735
736 sub Types {
737     return (sort {(($FieldTypes{$a}{sort_order}||999) <=> ($FieldTypes{$b}{sort_order}||999)) or ($a cmp $b)} keys %FieldTypes);
738 }
739
740
741 =head2 IsSelectionType 
742
743 Retuns a boolean value indicating whether the C<Values> method makes sense
744 to this Custom Field.
745
746 =cut
747
748 sub IsSelectionType {
749     my $self = shift;
750     my $type = @_? shift : $self->Type;
751     return undef unless $type;
752     return $FieldTypes{$type}->{selection_type};
753 }
754
755
756
757 =head2 IsExternalValues
758
759 =cut
760
761 sub IsExternalValues {
762     my $self = shift;
763     return 0 unless $self->IsSelectionType( @_ );
764     return $self->ValuesClass eq 'RT::CustomFieldValues'? 0 : 1;
765 }
766
767 sub ValuesClass {
768     my $self = shift;
769     return $self->_Value( ValuesClass => @_ ) || 'RT::CustomFieldValues';
770 }
771
772 sub SetValuesClass {
773     my $self = shift;
774     my $class = shift || 'RT::CustomFieldValues';
775     
776     if ( $class eq 'RT::CustomFieldValues' ) {
777         return $self->_Set( Field => 'ValuesClass', Value => undef, @_ );
778     }
779
780     return (0, $self->loc("This Custom Field can not have list of values"))
781         unless $self->IsSelectionType;
782
783     unless ( $self->ValidateValuesClass( $class ) ) {
784         return (0, $self->loc("Invalid Custom Field values source"));
785     }
786     return $self->_Set( Field => 'ValuesClass', Value => $class, @_ );
787 }
788
789 sub ValidateValuesClass {
790     my $self = shift;
791     my $class = shift;
792
793     return 1 if !$class || $class eq 'RT::CustomFieldValues';
794     return 1 if grep $class eq $_, RT->Config->Get('CustomFieldValuesSources');
795     return undef;
796 }
797
798
799 =head2 FriendlyType [TYPE, MAX_VALUES]
800
801 Returns a localized human-readable version of the custom field type.
802 If a custom field type is specified as the parameter, the friendly type for that type will be returned
803
804 =cut
805
806 sub FriendlyType {
807     my $self = shift;
808
809     my $type = @_ ? shift : $self->Type;
810     my $max  = @_ ? shift : $self->MaxValues;
811     $max = 0 unless $max;
812
813     if (my $friendly_type = $FieldTypes{$type}->{labels}->[$max>2 ? 2 : $max]) {
814         return ( $self->loc( $friendly_type, $max ) );
815     }
816     else {
817         return ( $self->loc( $type ) );
818     }
819 }
820
821 sub FriendlyTypeComposite {
822     my $self = shift;
823     my $composite = shift || $self->TypeComposite;
824     return $self->FriendlyType(split(/-/, $composite, 2));
825 }
826
827
828 =head2 ValidateType TYPE
829
830 Takes a single string. returns true if that string is a value
831 type of custom field
832
833
834 =cut
835
836 sub ValidateType {
837     my $self = shift;
838     my $type = shift;
839
840     if ( $type =~ s/(?:Single|Multiple)$// ) {
841         RT->Deprecated(
842             Arguments => "suffix 'Single' or 'Multiple'",
843             Instead   => "MaxValues",
844             Remove    => "4.4",
845         );
846     }
847
848     if ( $FieldTypes{$type} ) {
849         return 1;
850     }
851     else {
852         return undef;
853     }
854 }
855
856
857 sub SetType {
858     my $self = shift;
859     my $type = shift;
860     if ($type =~ s/(?:(Single)|Multiple)$//) {
861         RT->Deprecated(
862             Arguments => "suffix 'Single' or 'Multiple'",
863             Instead   => "MaxValues",
864             Remove    => "4.4",
865         );
866         $self->SetMaxValues($1 ? 1 : 0);
867     }
868     $self->_Set(Field => 'Type', Value =>$type);
869 }
870
871 =head2 SetPattern STRING
872
873 Takes a single string representing a regular expression.  Performs basic
874 validation on that regex, and sets the C<Pattern> field for the CF if it
875 is valid.
876
877 =cut
878
879 sub SetPattern {
880     my $self = shift;
881     my $regex = shift;
882
883     my ($ok, $msg) = $self->_IsValidRegex($regex);
884     if ($ok) {
885         return $self->_Set(Field => 'Pattern', Value => $regex);
886     }
887     else {
888         return (0, $self->loc("Invalid pattern: [_1]", $msg));
889     }
890 }
891
892 =head2 _IsValidRegex(Str $regex) returns (Bool $success, Str $msg)
893
894 Tests if the string contains an invalid regex.
895
896 =cut
897
898 sub _IsValidRegex {
899     my $self  = shift;
900     my $regex = shift or return (1, 'valid');
901
902     local $^W; local $@;
903     local $SIG{__DIE__} = sub { 1 };
904     local $SIG{__WARN__} = sub { 1 };
905
906     if (eval { qr/$regex/; 1 }) {
907         return (1, 'valid');
908     }
909
910     my $err = $@;
911     $err =~ s{[,;].*}{};    # strip debug info from error
912     chomp $err;
913     return (0, $err);
914 }
915
916
917 =head2 SingleValue
918
919 Returns true if this CustomField only accepts a single value. 
920 Returns false if it accepts multiple values
921
922 =cut
923
924 sub SingleValue {
925     my $self = shift;
926     if (($self->MaxValues||0) == 1) {
927         return 1;
928     } 
929     else {
930         return undef;
931     }
932 }
933
934 sub UnlimitedValues {
935     my $self = shift;
936     if (($self->MaxValues||0) == 0) {
937         return 1;
938     } 
939     else {
940         return undef;
941     }
942 }
943
944
945 =head2 ACLEquivalenceObjects
946
947 Returns list of objects via which users can get rights on this custom field. For custom fields
948 these objects can be set using L<ContextObject|/"ContextObject and SetContextObject">.
949
950 =cut
951
952 sub ACLEquivalenceObjects {
953     my $self = shift;
954
955     my $ctx = $self->ContextObject
956         or return;
957     return ($ctx, $ctx->ACLEquivalenceObjects);
958 }
959
960 =head2 ContextObject and SetContextObject
961
962 Set or get a context for this object. It can be ticket, queue or another
963 object this CF added to. Used for ACL control, for example
964 SeeCustomField can be granted on queue level to allow people to see all
965 fields added to the queue.
966
967 =cut
968
969 sub SetContextObject {
970     my $self = shift;
971     return $self->{'context_object'} = shift;
972 }
973   
974 sub ContextObject {
975     my $self = shift;
976     return $self->{'context_object'};
977 }
978
979 sub ValidContextType {
980     my $self = shift;
981     my $class = shift;
982
983     my %valid;
984     $valid{$_}++ for split '-', $self->LookupType;
985     delete $valid{'RT::Transaction'};
986
987     return $valid{$class};
988 }
989
990 =head2 LoadContextObject
991
992 Takes an Id for a Context Object and loads the right kind of RT::Object
993 for this particular Custom Field (based on the LookupType) and returns it.
994 This is a good way to ensure you don't try to use a Queue as a Context
995 Object on a User Custom Field.
996
997 =cut
998
999 sub LoadContextObject {
1000     my $self = shift;
1001     my $type = shift;
1002     my $contextid = shift;
1003
1004     unless ( $self->ValidContextType($type) ) {
1005         RT->Logger->debug("Invalid ContextType $type for Custom Field ".$self->Id);
1006         return;
1007     }
1008
1009     my $context_object = $type->new( $self->CurrentUser );
1010     my ($id, $msg) = $context_object->LoadById( $contextid );
1011     unless ( $id ) {
1012         RT->Logger->debug("Invalid ContextObject id: $msg");
1013         return;
1014     }
1015     return $context_object;
1016 }
1017
1018 =head2 ValidateContextObject
1019
1020 Ensure that a given ContextObject applies to this Custom Field.  For
1021 custom fields that are assigned to Queues or to Classes, this checks
1022 that the Custom Field is actually added to that object.  For Global
1023 Custom Fields, it returns true as long as the Object is of the right
1024 type, because you may be using your permissions on a given Queue of
1025 Class to see a Global CF.  For CFs that are only added globally, you
1026 don't need a ContextObject.
1027
1028 =cut
1029
1030 sub ValidateContextObject {
1031     my $self = shift;
1032     my $object = shift;
1033
1034     return 1 if $self->IsGlobal;
1035
1036     # global only custom fields don't have objects
1037     # that should be used as context objects.
1038     return if $self->IsOnlyGlobal;
1039
1040     # Otherwise, make sure we weren't passed a user object that we're
1041     # supposed to treat as a queue.
1042     return unless $self->ValidContextType(ref $object);
1043
1044     # Check that it is added correctly
1045     my ($added_to) = grep {ref($_) eq $self->RecordClassFromLookupType} ($object, $object->ACLEquivalenceObjects);
1046     return unless $added_to;
1047     return $self->IsAdded($added_to->id);
1048 }
1049
1050 sub _Set {
1051     my $self = shift;
1052
1053     unless ( $self->CurrentUserHasRight('AdminCustomField') ) {
1054         return ( 0, $self->loc('Permission Denied') );
1055     }
1056     return $self->SUPER::_Set( @_ );
1057
1058 }
1059
1060
1061
1062 =head2 _Value
1063
1064 Takes the name of a table column.
1065 Returns its value as a string, if the user passes an ACL check
1066
1067 =cut
1068
1069 sub _Value {
1070     my $self  = shift;
1071     return undef unless $self->id;
1072
1073     # we need to do the rights check
1074     unless ( $self->CurrentUserHasRight('SeeCustomField') ) {
1075         $RT::Logger->debug(
1076             "Permission denied. User #". $self->CurrentUser->id
1077             ." has no SeeCustomField right on CF #". $self->id
1078         );
1079         return (undef);
1080     }
1081     return $self->__Value( @_ );
1082 }
1083
1084
1085 =head2 SetDisabled
1086
1087 Takes a boolean.
1088 1 will cause this custom field to no longer be avaialble for objects.
1089 0 will re-enable this field.
1090
1091 =cut
1092
1093
1094 =head2 SetTypeComposite
1095
1096 Set this custom field's type and maximum values as a composite value
1097
1098 =cut
1099
1100 sub SetTypeComposite {
1101     my $self = shift;
1102     my $composite = shift;
1103
1104     my $old = $self->TypeComposite;
1105
1106     my ($type, $max_values) = split(/-/, $composite, 2);
1107     if ( $type ne $self->Type ) {
1108         my ($status, $msg) = $self->SetType( $type );
1109         return ($status, $msg) unless $status;
1110     }
1111     if ( ($max_values || 0) != ($self->MaxValues || 0) ) {
1112         my ($status, $msg) = $self->SetMaxValues( $max_values );
1113         return ($status, $msg) unless $status;
1114     }
1115     my $render = $self->RenderType;
1116     if ( $render and not grep { $_ eq $render } $self->RenderTypes ) {
1117         # We switched types and our render type is no longer valid, so unset it
1118         # and use the default
1119         $self->SetRenderType( undef );
1120     }
1121     return 1, $self->loc(
1122         "Type changed from '[_1]' to '[_2]'",
1123         $self->FriendlyTypeComposite( $old ),
1124         $self->FriendlyTypeComposite( $composite ),
1125     );
1126 }
1127
1128 =head2 TypeComposite
1129
1130 Returns a composite value composed of this object's type and maximum values
1131
1132 =cut
1133
1134
1135 sub TypeComposite {
1136     my $self = shift;
1137     return join '-', ($self->Type || ''), ($self->MaxValues || 0);
1138 }
1139
1140 =head2 TypeComposites
1141
1142 Returns an array of all possible composite values for custom fields.
1143
1144 =cut
1145
1146 sub TypeComposites {
1147     my $self = shift;
1148     return grep !/(?:[Tt]ext|Combobox|Date|DateTime|TimeValue)-0/, map { ("$_-1", "$_-0") } $self->Types;
1149 }
1150
1151 =head2 RenderType
1152
1153 Returns the type of form widget to render for this custom field.  Currently
1154 this only affects fields which return true for L</HasRenderTypes>. 
1155
1156 =cut
1157
1158 sub RenderType {
1159     my $self = shift;
1160     return '' unless $self->HasRenderTypes;
1161
1162     return $self->_Value( 'RenderType', @_ )
1163         || $self->DefaultRenderType;
1164 }
1165
1166 =head2 SetRenderType TYPE
1167
1168 Sets this custom field's render type.
1169
1170 =cut
1171
1172 sub SetRenderType {
1173     my $self = shift;
1174     my $type = shift;
1175     return (0, $self->loc("This custom field has no Render Types"))
1176         unless $self->HasRenderTypes;
1177
1178     if ( !$type || $type eq $self->DefaultRenderType ) {
1179         return $self->_Set( Field => 'RenderType', Value => undef, @_ );
1180     }
1181
1182     if ( not grep { $_ eq $type } $self->RenderTypes ) {
1183         return (0, $self->loc("Invalid Render Type for custom field of type [_1]",
1184                                 $self->FriendlyType));
1185     }
1186
1187     return $self->_Set( Field => 'RenderType', Value => $type, @_ );
1188 }
1189
1190 =head2 DefaultRenderType [TYPE COMPOSITE]
1191
1192 Returns the default render type for this custom field's type or the TYPE
1193 COMPOSITE specified as an argument.
1194
1195 =cut
1196
1197 sub DefaultRenderType {
1198     my $self = shift;
1199     my $composite    = @_ ? shift : $self->TypeComposite;
1200     my ($type, $max) = split /-/, $composite, 2;
1201     return unless $type and $self->HasRenderTypes($composite);
1202     return $FieldTypes{$type}->{render_types}->{ $max == 1 ? 'single' : 'multiple' }[0];
1203 }
1204
1205 =head2 HasRenderTypes [TYPE_COMPOSITE]
1206
1207 Returns a boolean value indicating whether the L</RenderTypes> and
1208 L</RenderType> methods make sense for this custom field.
1209
1210 Currently true only for type C<Select>.
1211
1212 =cut
1213
1214 sub HasRenderTypes {
1215     my $self = shift;
1216     my ($type, $max) = split /-/, (@_ ? shift : $self->TypeComposite), 2;
1217     return undef unless $type;
1218     return defined $FieldTypes{$type}->{render_types}
1219         ->{ $max == 1 ? 'single' : 'multiple' };
1220 }
1221
1222 =head2 RenderTypes [TYPE COMPOSITE]
1223
1224 Returns the valid render types for this custom field's type or the TYPE
1225 COMPOSITE specified as an argument.
1226
1227 =cut
1228
1229 sub RenderTypes {
1230     my $self = shift;
1231     my $composite    = @_ ? shift : $self->TypeComposite;
1232     my ($type, $max) = split /-/, $composite, 2;
1233     return unless $type and $self->HasRenderTypes($composite);
1234     return @{$FieldTypes{$type}->{render_types}->{ $max == 1 ? 'single' : 'multiple' }};
1235 }
1236
1237 =head2 SetLookupType
1238
1239 Autrijus: care to doc how LookupTypes work?
1240
1241 =cut
1242
1243 sub SetLookupType {
1244     my $self = shift;
1245     my $lookup = shift;
1246     if ( $lookup ne $self->LookupType ) {
1247         # Okay... We need to invalidate our existing relationships
1248         RT::ObjectCustomField->new($self->CurrentUser)->DeleteAll( CustomField => $self );
1249     }
1250     return $self->_Set(Field => 'LookupType', Value =>$lookup);
1251 }
1252
1253 =head2 LookupTypes
1254
1255 Returns an array of LookupTypes available
1256
1257 =cut
1258
1259
1260 sub LookupTypes {
1261     my $self = shift;
1262     return sort keys %FRIENDLY_LOOKUP_TYPES;
1263 }
1264
1265 =head2 FriendlyLookupType
1266
1267 Returns a localized description of the type of this custom field
1268
1269 =cut
1270
1271 sub FriendlyLookupType {
1272     my $self = shift;
1273     my $lookup = shift || $self->LookupType;
1274
1275     return ($self->loc( $FRIENDLY_LOOKUP_TYPES{$lookup} ))
1276         if defined $FRIENDLY_LOOKUP_TYPES{$lookup};
1277
1278     my @types = map { s/^RT::// ? $self->loc($_) : $_ }
1279       grep { defined and length }
1280       split( /-/, $lookup )
1281       or return;
1282
1283     state $LocStrings = [
1284         "[_1] objects",            # loc
1285         "[_1]'s [_2] objects",        # loc
1286         "[_1]'s [_2]'s [_3] objects",   # loc
1287     ];
1288     return ( $self->loc( $LocStrings->[$#types], @types ) );
1289 }
1290
1291 =head1 RecordClassFromLookupType
1292
1293 Returns the type of Object referred to by ObjectCustomFields' ObjectId column
1294
1295 Optionally takes a LookupType to use instead of using the value on the loaded
1296 record.  In this case, the method may be called on the class instead of an
1297 object.
1298
1299 =cut
1300
1301 sub RecordClassFromLookupType {
1302     my $self = shift;
1303     my $type = shift || $self->LookupType;
1304     my ($class) = ($type =~ /^([^-]+)/);
1305     unless ( $class ) {
1306         if (blessed($self) and $self->LookupType eq $type) {
1307             $RT::Logger->error(
1308                 "Custom Field #". $self->id
1309                 ." has incorrect LookupType '$type'"
1310             );
1311         } else {
1312             RT->Logger->error("Invalid LookupType passed as argument: $type");
1313         }
1314         return undef;
1315     }
1316     return $class;
1317 }
1318
1319 =head1 ObjectTypeFromLookupType
1320
1321 Returns the ObjectType used in ObjectCustomFieldValues rows for this CF
1322
1323 Optionally takes a LookupType to use instead of using the value on the loaded
1324 record.  In this case, the method may be called on the class instead of an
1325 object.
1326
1327 =cut
1328
1329 sub ObjectTypeFromLookupType {
1330     my $self = shift;
1331     my $type = shift || $self->LookupType;
1332     my ($class) = ($type =~ /([^-]+)$/);
1333     unless ( $class ) {
1334         if (blessed($self) and $self->LookupType eq $type) {
1335             $RT::Logger->error(
1336                 "Custom Field #". $self->id
1337                 ." has incorrect LookupType '$type'"
1338             );
1339         } else {
1340             RT->Logger->error("Invalid LookupType passed as argument: $type");
1341         }
1342         return undef;
1343     }
1344     return $class;
1345 }
1346
1347 sub CollectionClassFromLookupType {
1348     my $self = shift;
1349
1350     my $record_class = $self->RecordClassFromLookupType;
1351     return undef unless $record_class;
1352
1353     my $collection_class;
1354     if ( UNIVERSAL::can($record_class.'Collection', 'new') ) {
1355         $collection_class = $record_class.'Collection';
1356     } elsif ( UNIVERSAL::can($record_class.'es', 'new') ) {
1357         $collection_class = $record_class.'es';
1358     } elsif ( UNIVERSAL::can($record_class.'s', 'new') ) {
1359         $collection_class = $record_class.'s';
1360     } else {
1361         $RT::Logger->error("Can not find a collection class for record class '$record_class'");
1362         return undef;
1363     }
1364     return $collection_class;
1365 }
1366
1367 =head2 Groupings
1368
1369 Returns a (sorted and lowercased) list of the groupings in which this custom
1370 field appears.
1371
1372 If called on a loaded object, the returned list is limited to groupings which
1373 apply to the record class this CF applies to (L</RecordClassFromLookupType>).
1374
1375 If passed a loaded object or a class name, the returned list is limited to
1376 groupings which apply to the class of the object or the specified class.
1377
1378 If called on an unloaded object, all potential groupings are returned.
1379
1380 =cut
1381
1382 sub Groupings {
1383     my $self = shift;
1384     my $record_class = $self->_GroupingClass(shift);
1385
1386     my $config = RT->Config->Get('CustomFieldGroupings');
1387        $config = {} unless ref($config) eq 'HASH';
1388
1389     my @groups;
1390     if ( $record_class ) {
1391         push @groups, sort {lc($a) cmp lc($b)} keys %{ $BUILTIN_GROUPINGS{$record_class} || {} };
1392         if ( ref($config->{$record_class} ||= []) eq "ARRAY") {
1393             my @order = @{ $config->{$record_class} };
1394             while (@order) {
1395                 push @groups, shift(@order);
1396                 shift(@order);
1397             }
1398         } else {
1399             @groups = sort {lc($a) cmp lc($b)} keys %{ $config->{$record_class} };
1400         }
1401     } else {
1402         my %all = (%$config, %BUILTIN_GROUPINGS);
1403         @groups = sort {lc($a) cmp lc($b)} map {$self->Groupings($_)} grep {$_} keys(%all);
1404     }
1405
1406     my %seen;
1407     return
1408         grep defined && length && !$seen{lc $_}++,
1409         @groups;
1410 }
1411
1412 =head2 CustomGroupings
1413
1414 Identical to L</Groupings> but filters out built-in groupings from the the
1415 returned list.
1416
1417 =cut
1418
1419 sub CustomGroupings {
1420     my $self = shift;
1421     my $record_class = $self->_GroupingClass(shift);
1422     return grep !$BUILTIN_GROUPINGS{$record_class}{$_}, $self->Groupings( $record_class );
1423 }
1424
1425 sub _GroupingClass {
1426     my $self    = shift;
1427     my $record  = shift;
1428
1429     my $record_class = ref($record) || $record || '';
1430     $record_class = $self->RecordClassFromLookupType
1431         if !$record_class and blessed($self) and $self->id;
1432
1433     return $record_class;
1434 }
1435
1436 =head2 RegisterBuiltInGroupings
1437
1438 Registers groupings to be considered a fundamental part of RT, either via use
1439 in core RT or via an extension.  These groupings must be rendered explicitly in
1440 Mason by specific calls to F</Elements/ShowCustomFields> and
1441 F</Elements/EditCustomFields>.  They will not show up automatically on normal
1442 display pages like configured custom groupings.
1443
1444 Takes a set of key-value pairs of class names (valid L<RT::Record> subclasses)
1445 and array refs of grouping names to consider built-in.
1446
1447 If a class already contains built-in groupings (such as L<RT::Ticket> and
1448 L<RT::User>), new groupings are appended.
1449
1450 =cut
1451
1452 sub RegisterBuiltInGroupings {
1453     my $self = shift;
1454     my %new  = @_;
1455
1456     while (my ($k,$v) = each %new) {
1457         $v = [$v] unless ref($v) eq 'ARRAY';
1458         $BUILTIN_GROUPINGS{$k} = {
1459             %{$BUILTIN_GROUPINGS{$k} || {}},
1460             map { $_ => 1 } @$v
1461         };
1462     }
1463     $BUILTIN_GROUPINGS{''} = { map { %$_ } values %BUILTIN_GROUPINGS  };
1464 }
1465
1466 =head1 IsOnlyGlobal
1467
1468 Certain custom fields (users, groups) should only be added globally;
1469 codify that set here for reference.
1470
1471 =cut
1472
1473 sub IsOnlyGlobal {
1474     my $self = shift;
1475
1476     return ($self->LookupType =~ /^RT::(?:Group|User)/io);
1477
1478 }
1479 sub ApplyGlobally {
1480     RT->Deprecated(
1481         Instead   => "IsOnlyGlobal",
1482         Remove    => "4.4",
1483     );
1484     return shift->IsOnlyGlobal(@_);
1485 }
1486
1487 =head1 AddedTo
1488
1489 Returns collection with objects this custom field is added to.
1490 Class of the collection depends on L</LookupType>.
1491 See all L</NotAddedTo> .
1492
1493 Doesn't takes into account if object is added globally.
1494
1495 =cut
1496
1497 sub AddedTo {
1498     my $self = shift;
1499     return RT::ObjectCustomField->new( $self->CurrentUser )
1500         ->AddedTo( CustomField => $self );
1501 }
1502 sub AppliedTo {
1503     RT->Deprecated(
1504         Instead   => "AddedTo",
1505         Remove    => "4.4",
1506     );
1507     shift->AddedTo(@_);
1508 };
1509
1510 =head1 NotAddedTo
1511
1512 Returns collection with objects this custom field is not added to.
1513 Class of the collection depends on L</LookupType>.
1514 See all L</AddedTo> .
1515
1516 Doesn't take into account if the object is added globally.
1517
1518 =cut
1519
1520 sub NotAddedTo {
1521     my $self = shift;
1522     return RT::ObjectCustomField->new( $self->CurrentUser )
1523         ->NotAddedTo( CustomField => $self );
1524 }
1525 sub NotAppliedTo {
1526     RT->Deprecated(
1527         Instead   => "NotAddedTo",
1528         Remove    => "4.4",
1529     );
1530     shift->NotAddedTo(@_)
1531 };
1532
1533 =head2 IsAdded
1534
1535 Takes object id and returns corresponding L<RT::ObjectCustomField>
1536 record if this custom field is added to the object. Use 0 to check
1537 if custom field is added globally.
1538
1539 =cut
1540
1541 sub IsAdded {
1542     my $self = shift;
1543     my $id = shift;
1544     my $ocf = RT::ObjectCustomField->new( $self->CurrentUser );
1545     $ocf->LoadByCols( CustomField => $self->id, ObjectId => $id || 0 );
1546     return undef unless $ocf->id;
1547     return $ocf;
1548 }
1549 sub IsApplied {
1550     RT->Deprecated(
1551         Instead   => "IsAdded",
1552         Remove    => "4.4",
1553     );
1554     shift->IsAdded(@_);
1555 };
1556
1557 sub IsGlobal { return shift->IsAdded(0) }
1558
1559 =head2 IsAddedToAny
1560
1561 Returns true if custom field is applied to any object.
1562
1563 =cut
1564
1565 sub IsAddedToAny {
1566     my $self = shift;
1567     my $id = shift;
1568     my $ocf = RT::ObjectCustomField->new( $self->CurrentUser );
1569     $ocf->LoadByCols( CustomField => $self->id );
1570     return $ocf->id ? 1 : 0;
1571 }
1572
1573 =head2 AddToObject OBJECT
1574
1575 Add this custom field as a custom field for a single object, such as a queue or group.
1576
1577 Takes an object 
1578
1579 =cut
1580
1581 sub AddToObject {
1582     my $self  = shift;
1583     my $object = shift;
1584     my $id = $object->Id || 0;
1585
1586     unless (index($self->LookupType, ref($object)) == 0) {
1587         return ( 0, $self->loc('Lookup type mismatch') );
1588     }
1589
1590     unless ( $object->CurrentUserHasRight('AssignCustomFields') ) {
1591         return ( 0, $self->loc('Permission Denied') );
1592     }
1593
1594     my $ocf = RT::ObjectCustomField->new( $self->CurrentUser );
1595     my ( $oid, $msg ) = $ocf->Add(
1596         CustomField => $self->id, ObjectId => $id,
1597     );
1598     return ( $oid, $msg );
1599 }
1600
1601
1602 =head2 RemoveFromObject OBJECT
1603
1604 Remove this custom field  for a single object, such as a queue or group.
1605
1606 Takes an object 
1607
1608 =cut
1609
1610 sub RemoveFromObject {
1611     my $self = shift;
1612     my $object = shift;
1613     my $id = $object->Id || 0;
1614
1615     unless (index($self->LookupType, ref($object)) == 0) {
1616         return ( 0, $self->loc('Object type mismatch') );
1617     }
1618
1619     unless ( $object->CurrentUserHasRight('AssignCustomFields') ) {
1620         return ( 0, $self->loc('Permission Denied') );
1621     }
1622
1623     my $ocf = $self->IsAdded( $id );
1624     unless ( $ocf ) {
1625         return ( 0, $self->loc("This custom field cannot be added to that object") );
1626     }
1627
1628     # XXX: Delete doesn't return anything
1629     my ( $oid, $msg ) = $ocf->Delete;
1630     return ( $oid, $msg );
1631 }
1632
1633
1634 =head2 AddValueForObject HASH
1635
1636 Adds a custom field value for a record object of some kind. 
1637 Takes a param hash of 
1638
1639 Required:
1640
1641     Object
1642     Content
1643
1644 Optional:
1645
1646     LargeContent
1647     ContentType
1648
1649 =cut
1650
1651 sub AddValueForObject {
1652     my $self = shift;
1653     my %args = (
1654         Object       => undef,
1655         Content      => undef,
1656         LargeContent => undef,
1657         ContentType  => undef,
1658         @_
1659     );
1660     my $obj = $args{'Object'} or return ( 0, $self->loc('Invalid object') );
1661
1662     unless ( $self->CurrentUserHasRight('ModifyCustomField') ) {
1663         return ( 0, $self->loc('Permission Denied') );
1664     }
1665
1666     unless ( $self->MatchPattern($args{'Content'}) ) {
1667         return ( 0, $self->loc('Input must match [_1]', $self->FriendlyPattern) );
1668     }
1669
1670     $RT::Handle->BeginTransaction;
1671
1672     if ( $self->MaxValues ) {
1673         my $current_values = $self->ValuesForObject($obj);
1674         my $extra_values = ( $current_values->Count + 1 ) - $self->MaxValues;
1675
1676         # (The +1 is for the new value we're adding)
1677
1678         # If we have a set of current values and we've gone over the maximum
1679         # allowed number of values, we'll need to delete some to make room.
1680         # which former values are blown away is not guaranteed
1681
1682         while ($extra_values) {
1683             my $extra_item = $current_values->Next;
1684             unless ( $extra_item->id ) {
1685                 $RT::Logger->crit( "We were just asked to delete "
1686                     ."a custom field value that doesn't exist!" );
1687                 $RT::Handle->Rollback();
1688                 return (undef);
1689             }
1690             $extra_item->Delete;
1691             $extra_values--;
1692         }
1693     }
1694
1695     my $newval = RT::ObjectCustomFieldValue->new( $self->CurrentUser );
1696     my ($val, $msg) = $newval->Create(
1697         ObjectType   => ref($obj),
1698         ObjectId     => $obj->Id,
1699         Content      => $args{'Content'},
1700         LargeContent => $args{'LargeContent'},
1701         ContentType  => $args{'ContentType'},
1702         CustomField  => $self->Id
1703     );
1704
1705     unless ($val) {
1706         $RT::Handle->Rollback();
1707         return ($val, $self->loc("Couldn't create record: [_1]", $msg));
1708     }
1709
1710     $RT::Handle->Commit();
1711     return ($val);
1712
1713 }
1714
1715
1716 sub _CanonicalizeValue {
1717     my $self = shift;
1718     my $args = shift;
1719
1720     my $type = $self->__Value('Type');
1721     return 1 unless $type;
1722
1723     my $method = '_CanonicalizeValue'. $type;
1724     return 1 unless $self->can($method);
1725     $self->$method($args);
1726 }
1727
1728 sub _CanonicalizeValueDateTime {
1729     my $self    = shift;
1730     my $args    = shift;
1731     my $DateObj = RT::Date->new( $self->CurrentUser );
1732     $DateObj->Set( Format => 'unknown',
1733                    Value  => $args->{'Content'} );
1734     $args->{'Content'} = $DateObj->ISO;
1735     return 1;
1736 }
1737
1738 # For date, we need to store Content as ISO date
1739 sub _CanonicalizeValueDate {
1740     my $self = shift;
1741     my $args = shift;
1742
1743     # in case user input date with time, let's omit it by setting timezone
1744     # to utc so "hour" won't affect "day"
1745     my $DateObj = RT::Date->new( $self->CurrentUser );
1746     $DateObj->Set( Format   => 'unknown',
1747                    Value    => $args->{'Content'},
1748                  );
1749     $args->{'Content'} = $DateObj->Date( Timezone => 'user' );
1750     return 1;
1751 }
1752
1753 sub _CanonicalizeValueIPAddress {
1754     my $self = shift;
1755     my $args = shift;
1756
1757     $args->{Content} = RT::ObjectCustomFieldValue->ParseIP( $args->{Content} );
1758     return (0, $self->loc("Content is not a valid IP address"))
1759         unless $args->{Content};
1760     return 1;
1761 }
1762
1763 sub _CanonicalizeValueIPAddressRange {
1764     my $self = shift;
1765     my $args = shift;
1766
1767     my $content = $args->{Content};
1768     $content .= "-".$args->{LargeContent} if $args->{LargeContent};
1769
1770     ($args->{Content}, $args->{LargeContent})
1771         = RT::ObjectCustomFieldValue->ParseIPRange( $content );
1772
1773     $args->{ContentType} = 'text/plain';
1774     return (0, $self->loc("Content is not a valid IP address range"))
1775         unless $args->{Content};
1776     return 1;
1777 }
1778
1779 =head2 MatchPattern STRING
1780
1781 Tests the incoming string against the Pattern of this custom field object
1782 and returns a boolean; returns true if the Pattern is empty.
1783
1784 =cut
1785
1786 sub MatchPattern {
1787     my $self = shift;
1788     my $regex = $self->Pattern or return 1;
1789
1790     return (( defined $_[0] ? $_[0] : '') =~ $regex);
1791 }
1792
1793
1794
1795
1796 =head2 FriendlyPattern
1797
1798 Prettify the pattern of this custom field, by taking the text in C<(?#text)>
1799 and localizing it.
1800
1801 =cut
1802
1803 sub FriendlyPattern {
1804     my $self = shift;
1805     my $regex = $self->Pattern;
1806
1807     return '' unless length $regex;
1808     if ( $regex =~ /\(\?#([^)]*)\)/ ) {
1809         return '[' . $self->loc($1) . ']';
1810     }
1811     else {
1812         return $regex;
1813     }
1814 }
1815
1816
1817
1818
1819 =head2 DeleteValueForObject HASH
1820
1821 Deletes a custom field value for a ticket. Takes a param hash of Object and Content
1822
1823 Returns a tuple of (STATUS, MESSAGE). If the call succeeded, the STATUS is true. otherwise it's false
1824
1825 =cut
1826
1827 sub DeleteValueForObject {
1828     my $self = shift;
1829     my %args = ( Object => undef,
1830                  Content => undef,
1831                  Id => undef,
1832              @_ );
1833
1834
1835     unless ($self->CurrentUserHasRight('ModifyCustomField')) {
1836         return (0, $self->loc('Permission Denied'));
1837     }
1838
1839     my $oldval = RT::ObjectCustomFieldValue->new($self->CurrentUser);
1840
1841     if (my $id = $args{'Id'}) {
1842         $oldval->Load($id);
1843     }
1844     unless ($oldval->id) { 
1845         $oldval->LoadByObjectContentAndCustomField(
1846             Object => $args{'Object'}, 
1847             Content =>  $args{'Content'}, 
1848             CustomField => $self->Id,
1849         );
1850     }
1851
1852
1853     # check to make sure we found it
1854     unless ($oldval->Id) {
1855         return(0, $self->loc("Custom field value [_1] could not be found for custom field [_2]", $args{'Content'}, $self->Name));
1856     }
1857
1858     # for single-value fields, we need to validate that empty string is a valid value for it
1859     if ( $self->SingleValue and not $self->MatchPattern( '' ) ) {
1860         return ( 0, $self->loc('Input must match [_1]', $self->FriendlyPattern) );
1861     }
1862
1863     # delete it
1864
1865     my $ret = $oldval->Delete();
1866     unless ($ret) {
1867         return(0, $self->loc("Custom field value could not be found"));
1868     }
1869     return($oldval->Id, $self->loc("Custom field value deleted"));
1870 }
1871
1872
1873 =head2 ValuesForObject OBJECT
1874
1875 Return an L<RT::ObjectCustomFieldValues> object containing all of this custom field's values for OBJECT 
1876
1877 =cut
1878
1879 sub ValuesForObject {
1880     my $self = shift;
1881     my $object = shift;
1882
1883     my $values = RT::ObjectCustomFieldValues->new($self->CurrentUser);
1884     unless ($self->id and $self->CurrentUserHasRight('SeeCustomField')) {
1885         # Return an empty object if they have no rights to see
1886         $values->Limit( FIELD => "id", VALUE => 0, SUBCLAUSE => "ACL" );
1887         return ($values);
1888     }
1889
1890     $values->LimitToCustomField($self->Id);
1891     $values->LimitToObject($object);
1892
1893     return ($values);
1894 }
1895
1896
1897 =head2 RegisterLookupType LOOKUPTYPE FRIENDLYNAME
1898
1899 Tell RT that a certain object accepts custom fields via a lookup type and
1900 provide a friendly name for such CFs.
1901
1902 Examples:
1903
1904     'RT::Queue-RT::Ticket'                 => "Tickets",                # loc
1905     'RT::Queue-RT::Ticket-RT::Transaction' => "Ticket Transactions",    # loc
1906     'RT::User'                             => "Users",                  # loc
1907     'RT::Group'                            => "Groups",                 # loc
1908     'RT::Queue'                            => "Queues",                 # loc
1909
1910 This is a class method. 
1911
1912 =cut
1913
1914 sub RegisterLookupType {
1915     my $self = shift;
1916     my $path = shift;
1917     my $friendly_name = shift;
1918
1919     $FRIENDLY_LOOKUP_TYPES{$path} = $friendly_name;
1920 }
1921
1922 sub _ForObjectType {
1923     RT->Deprecated(
1924         Instead => 'RegisterLookupType',
1925         Remove  => '4.4',
1926     );
1927     my $self = shift;
1928     $self->RegisterLookupType(@_);
1929 }
1930
1931
1932 =head2 IncludeContentForValue [VALUE] (and SetIncludeContentForValue)
1933
1934 Gets or sets the  C<IncludeContentForValue> for this custom field. RT
1935 uses this field to automatically include content into the user's browser
1936 as they display records with custom fields in RT.
1937
1938 =cut
1939
1940 sub SetIncludeContentForValue {
1941     shift->IncludeContentForValue(@_);
1942 }
1943 sub IncludeContentForValue{
1944     my $self = shift;
1945     $self->_URLTemplate('IncludeContentForValue', @_);
1946 }
1947
1948
1949
1950 =head2 LinkValueTo [VALUE] (and SetLinkValueTo)
1951
1952 Gets or sets the  C<LinkValueTo> for this custom field. RT
1953 uses this field to make custom field values into hyperlinks in the user's
1954 browser as they display records with custom fields in RT.
1955
1956 =cut
1957
1958
1959 sub SetLinkValueTo {
1960     shift->LinkValueTo(@_);
1961 }
1962
1963 sub LinkValueTo {
1964     my $self = shift;
1965     $self->_URLTemplate('LinkValueTo', @_);
1966
1967 }
1968
1969
1970 =head2 _URLTemplate  NAME [VALUE]
1971
1972 With one argument, returns the _URLTemplate named C<NAME>, but only if
1973 the current user has the right to see this custom field.
1974
1975 With two arguments, attemptes to set the relevant template value.
1976
1977 =cut
1978
1979 sub _URLTemplate {
1980     my $self          = shift;
1981     my $template_name = shift;
1982     if (@_) {
1983
1984         my $value = shift;
1985         unless ( $self->CurrentUserHasRight('AdminCustomField') ) {
1986             return ( 0, $self->loc('Permission Denied') );
1987         }
1988         if (length $value and defined $value) {
1989             $self->SetAttribute( Name => $template_name, Content => $value );
1990         } else {
1991             $self->DeleteAttribute( $template_name );
1992         }
1993         return ( 1, $self->loc('Updated') );
1994     } else {
1995         unless ( $self->id && $self->CurrentUserHasRight('SeeCustomField') ) {
1996             return (undef);
1997         }
1998
1999         my ($attr) = $self->Attributes->Named($template_name);
2000         return undef unless $attr;
2001         return $attr->Content;
2002     }
2003 }
2004
2005 sub SetBasedOn {
2006     my $self = shift;
2007     my $value = shift;
2008
2009     return $self->_Set( Field => 'BasedOn', Value => $value, @_ )
2010         unless defined $value and length $value;
2011
2012     my $cf = RT::CustomField->new( $self->CurrentUser );
2013     $cf->SetContextObject( $self->ContextObject );
2014     $cf->Load( ref $value ? $value->id : $value );
2015
2016     return (0, "Permission Denied")
2017         unless $cf->id && $cf->CurrentUserHasRight('SeeCustomField');
2018
2019     # XXX: Remove this restriction once we support lists and cascaded selects
2020     if ( $self->RenderType =~ /List/ ) {
2021         return (0, $self->loc("We can't currently render as a List when basing categories on another custom field.  Please use another render type."));
2022     }
2023
2024     return $self->_Set( Field => 'BasedOn', Value => $value, @_ )
2025 }
2026
2027 sub BasedOnObj {
2028     my $self = shift;
2029
2030     my $obj = RT::CustomField->new( $self->CurrentUser );
2031     $obj->SetContextObject( $self->ContextObject );
2032     if ( $self->BasedOn ) {
2033         $obj->Load( $self->BasedOn );
2034     }
2035     return $obj;
2036 }
2037
2038 sub UILocation {
2039     my $self = shift;
2040     my $tag = $self->FirstAttribute( 'UILocation' );
2041     return $tag ? $tag->Content : '';
2042 }
2043
2044 sub SetUILocation {
2045     my $self = shift;
2046     my $tag = shift;
2047     if ( $tag ) {
2048         return $self->SetAttribute( Name => 'UILocation', Content => $tag );
2049     }
2050     else {
2051         return $self->DeleteAttribute('UILocation');
2052     }
2053 }
2054
2055 sub NoClone {
2056     my $self = shift;
2057     $self->FirstAttribute('NoClone') ? 1 : '';
2058 }
2059
2060 sub SetNoClone {
2061     my $self = shift;
2062     my $value = shift;
2063     if ( $value ) {
2064         return $self->SetAttribute( Name => 'NoClone', Content => 1 );
2065     } else {
2066         return $self->DeleteAttribute('NoClone');
2067     }
2068 }
2069
2070
2071 =head2 id
2072
2073 Returns the current value of id. 
2074 (In the database, id is stored as int(11).)
2075
2076
2077 =cut
2078
2079
2080 =head2 Name
2081
2082 Returns the current value of Name. 
2083 (In the database, Name is stored as varchar(200).)
2084
2085
2086
2087 =head2 SetName VALUE
2088
2089
2090 Set Name to VALUE. 
2091 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2092 (In the database, Name will be stored as a varchar(200).)
2093
2094
2095 =cut
2096
2097
2098 =head2 Type
2099
2100 Returns the current value of Type. 
2101 (In the database, Type is stored as varchar(200).)
2102
2103
2104
2105 =head2 SetType VALUE
2106
2107
2108 Set Type to VALUE. 
2109 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2110 (In the database, Type will be stored as a varchar(200).)
2111
2112
2113 =cut
2114
2115
2116 =head2 RenderType
2117
2118 Returns the current value of RenderType. 
2119 (In the database, RenderType is stored as varchar(64).)
2120
2121
2122
2123 =head2 SetRenderType VALUE
2124
2125
2126 Set RenderType to VALUE. 
2127 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2128 (In the database, RenderType will be stored as a varchar(64).)
2129
2130
2131 =cut
2132
2133
2134 =head2 MaxValues
2135
2136 Returns the current value of MaxValues. 
2137 (In the database, MaxValues is stored as int(11).)
2138
2139
2140
2141 =head2 SetMaxValues VALUE
2142
2143
2144 Set MaxValues to VALUE. 
2145 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2146 (In the database, MaxValues will be stored as a int(11).)
2147
2148
2149 =cut
2150
2151
2152 =head2 Pattern
2153
2154 Returns the current value of Pattern. 
2155 (In the database, Pattern is stored as text.)
2156
2157
2158
2159 =head2 SetPattern VALUE
2160
2161
2162 Set Pattern to VALUE. 
2163 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2164 (In the database, Pattern will be stored as a text.)
2165
2166
2167 =cut
2168
2169
2170 =head2 BasedOn
2171
2172 Returns the current value of BasedOn. 
2173 (In the database, BasedOn is stored as int(11).)
2174
2175
2176
2177 =head2 SetBasedOn VALUE
2178
2179
2180 Set BasedOn to VALUE. 
2181 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2182 (In the database, BasedOn will be stored as a int(11).)
2183
2184
2185 =cut
2186
2187
2188 =head2 Description
2189
2190 Returns the current value of Description. 
2191 (In the database, Description is stored as varchar(255).)
2192
2193
2194
2195 =head2 SetDescription VALUE
2196
2197
2198 Set Description to VALUE. 
2199 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2200 (In the database, Description will be stored as a varchar(255).)
2201
2202
2203 =cut
2204
2205
2206 =head2 SortOrder
2207
2208 Returns the current value of SortOrder. 
2209 (In the database, SortOrder is stored as int(11).)
2210
2211
2212
2213 =head2 SetSortOrder VALUE
2214
2215
2216 Set SortOrder to VALUE. 
2217 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2218 (In the database, SortOrder will be stored as a int(11).)
2219
2220
2221 =cut
2222
2223
2224 =head2 LookupType
2225
2226 Returns the current value of LookupType. 
2227 (In the database, LookupType is stored as varchar(255).)
2228
2229
2230
2231 =head2 SetLookupType VALUE
2232
2233
2234 Set LookupType to VALUE. 
2235 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2236 (In the database, LookupType will be stored as a varchar(255).)
2237
2238
2239 =cut
2240
2241
2242 =head2 Creator
2243
2244 Returns the current value of Creator. 
2245 (In the database, Creator is stored as int(11).)
2246
2247
2248 =cut
2249
2250
2251 =head2 Created
2252
2253 Returns the current value of Created. 
2254 (In the database, Created is stored as datetime.)
2255
2256
2257 =cut
2258
2259
2260 =head2 LastUpdatedBy
2261
2262 Returns the current value of LastUpdatedBy. 
2263 (In the database, LastUpdatedBy is stored as int(11).)
2264
2265
2266 =cut
2267
2268
2269 =head2 LastUpdated
2270
2271 Returns the current value of LastUpdated. 
2272 (In the database, LastUpdated is stored as datetime.)
2273
2274
2275 =cut
2276
2277
2278 =head2 Disabled
2279
2280 Returns the current value of Disabled. 
2281 (In the database, Disabled is stored as smallint(6).)
2282
2283
2284
2285 =head2 SetDisabled VALUE
2286
2287
2288 Set Disabled to VALUE. 
2289 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2290 (In the database, Disabled will be stored as a smallint(6).)
2291
2292
2293 =cut
2294
2295
2296
2297 sub _CoreAccessible {
2298     {
2299      
2300         id =>
2301         {read => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => ''},
2302         Name => 
2303         {read => 1, write => 1, sql_type => 12, length => 200,  is_blob => 0,  is_numeric => 0,  type => 'varchar(200)', default => ''},
2304         Type => 
2305         {read => 1, write => 1, sql_type => 12, length => 200,  is_blob => 0,  is_numeric => 0,  type => 'varchar(200)', default => ''},
2306         RenderType => 
2307         {read => 1, write => 1, sql_type => 12, length => 64,  is_blob => 0,  is_numeric => 0,  type => 'varchar(64)', default => ''},
2308         MaxValues => 
2309         {read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => ''},
2310         Pattern => 
2311         {read => 1, write => 1, sql_type => -4, length => 0,  is_blob => 1,  is_numeric => 0,  type => 'text', default => ''},
2312         ValuesClass => 
2313         {read => 1, write => 1, sql_type => 12, length => 64,  is_blob => 0,  is_numeric => 0,  type => 'varchar(64)', default => ''},
2314         BasedOn => 
2315         {read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => ''},
2316         Description => 
2317         {read => 1, write => 1, sql_type => 12, length => 255,  is_blob => 0,  is_numeric => 0,  type => 'varchar(255)', default => ''},
2318         SortOrder => 
2319         {read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
2320         LookupType => 
2321         {read => 1, write => 1, sql_type => 12, length => 255,  is_blob => 0,  is_numeric => 0,  type => 'varchar(255)', default => ''},
2322         Creator => 
2323         {read => 1, auto => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
2324         Created => 
2325         {read => 1, auto => 1, sql_type => 11, length => 0,  is_blob => 0,  is_numeric => 0,  type => 'datetime', default => ''},
2326         LastUpdatedBy => 
2327         {read => 1, auto => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
2328         LastUpdated => 
2329         {read => 1, auto => 1, sql_type => 11, length => 0,  is_blob => 0,  is_numeric => 0,  type => 'datetime', default => ''},
2330         Disabled => 
2331         {read => 1, write => 1, sql_type => 5, length => 6,  is_blob => 0,  is_numeric => 1,  type => 'smallint(6)', default => '0'},
2332
2333  }
2334 };
2335
2336 sub FindDependencies {
2337     my $self = shift;
2338     my ($walker, $deps) = @_;
2339
2340     $self->SUPER::FindDependencies($walker, $deps);
2341
2342     $deps->Add( out => $self->BasedOnObj )
2343         if $self->BasedOnObj->id;
2344
2345     my $applied = RT::ObjectCustomFields->new( $self->CurrentUser );
2346     $applied->LimitToCustomField( $self->id );
2347     $deps->Add( in => $applied );
2348
2349     $deps->Add( in => $self->Values ) if $self->ValuesClass eq "RT::CustomFieldValues";
2350 }
2351
2352 sub __DependsOn {
2353     my $self = shift;
2354     my %args = (
2355         Shredder => undef,
2356         Dependencies => undef,
2357         @_,
2358     );
2359     my $deps = $args{'Dependencies'};
2360     my $list = [];
2361
2362 # Custom field values
2363     push( @$list, $self->Values );
2364
2365 # Applications of this CF
2366     my $applied = RT::ObjectCustomFields->new( $self->CurrentUser );
2367     $applied->LimitToCustomField( $self->Id );
2368     push @$list, $applied;
2369
2370 # Ticket custom field values
2371     my $objs = RT::ObjectCustomFieldValues->new( $self->CurrentUser );
2372     $objs->LimitToCustomField( $self->Id );
2373     push( @$list, $objs );
2374
2375     $deps->_PushDependencies(
2376         BaseObject => $self,
2377         Flags => RT::Shredder::Constants::DEPENDS_ON,
2378         TargetObjects => $list,
2379         Shredder => $args{'Shredder'}
2380     );
2381     return $self->SUPER::__DependsOn( %args );
2382 }
2383
2384 RT::Base->_ImportOverlays();
2385
2386 1;