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