starting to work...
[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/ ) {
478         my $QueueObj = RT::Queue->new( $self->CurrentUser );
479         $QueueObj->Load( $args{'Queue'} );
480         $args{'Queue'} = $QueueObj->Id;
481     }
482
483     # XXX - really naive implementation.  Slow. - not really. still just one query
484
485     my $CFs = RT::CustomFields->new( $self->CurrentUser );
486     $CFs->SetContextObject( $self->ContextObject );
487     my $field = $args{'Name'} =~ /\D/? 'Name' : 'id';
488     $CFs->Limit( FIELD => $field, VALUE => $args{'Name'}, CASESENSITIVE => 0);
489     # Don't limit to queue if queue is 0.  Trying to do so breaks
490     # RT::Group type CFs.
491     if ( defined $args{'Queue'} ) {
492         $CFs->LimitToQueue( $args{'Queue'} );
493     }
494
495     # When loading by name, we _can_ load disabled fields, but prefer
496     # non-disabled fields.
497     $CFs->FindAllRows;
498     $CFs->OrderByCols(
499         { FIELD => "Disabled", ORDER => 'ASC' },
500     );
501
502     # We only want one entry.
503     $CFs->RowsPerPage(1);
504
505     # version before 3.8 just returns 0, so we need to test if wantarray to be
506     # backward compatible.
507     return wantarray ? (0, $self->loc("Not found")) : 0 unless my $first = $CFs->First;
508
509     return $self->LoadById( $first->id );
510 }
511
512
513
514
515 =head2 Custom field values
516
517 =head3 Values FIELD
518
519 Return a object (collection) of all acceptable values for this Custom Field.
520 Class of the object can vary and depends on the return value
521 of the C<ValuesClass> method.
522
523 =cut
524
525 *ValuesObj = \&Values;
526
527 sub Values {
528     my $self = shift;
529
530     my $class = $self->ValuesClass;
531     if ( $class ne 'RT::CustomFieldValues') {
532         eval "require $class" or die "$@";
533     }
534     my $cf_values = $class->new( $self->CurrentUser );
535     # if the user has no rights, return an empty object
536     if ( $self->id && $self->CurrentUserHasRight( 'SeeCustomField') ) {
537         $cf_values->LimitToCustomField( $self->Id );
538     }
539     return ($cf_values);
540 }
541
542
543 =head3 AddValue HASH
544
545 Create a new value for this CustomField.  Takes a paramhash containing the elements Name, Description and SortOrder
546
547 =cut
548
549 sub AddValue {
550     my $self = shift;
551     my %args = @_;
552
553     unless ($self->CurrentUserHasRight('AdminCustomField') || $self->CurrentUserHasRight('AdminCustomFieldValues')) {
554         return (0, $self->loc('Permission Denied'));
555     }
556
557     # allow zero value
558     if ( !defined $args{'Name'} || $args{'Name'} eq '' ) {
559         return (0, $self->loc("Can't add a custom field value without a name"));
560     }
561
562     my $newval = RT::CustomFieldValue->new( $self->CurrentUser );
563     return $newval->Create( %args, CustomField => $self->Id );
564 }
565
566
567
568
569 =head3 DeleteValue ID
570
571 Deletes a value from this custom field by id.
572
573 Does not remove this value for any article which has had it selected
574
575 =cut
576
577 sub DeleteValue {
578     my $self = shift;
579     my $id = shift;
580     unless ( $self->CurrentUserHasRight('AdminCustomField') || $self->CurrentUserHasRight('AdminCustomFieldValues') ) {
581         return (0, $self->loc('Permission Denied'));
582     }
583
584     my $val_to_del = RT::CustomFieldValue->new( $self->CurrentUser );
585     $val_to_del->Load( $id );
586     unless ( $val_to_del->Id ) {
587         return (0, $self->loc("Couldn't find that value"));
588     }
589     unless ( $val_to_del->CustomField == $self->Id ) {
590         return (0, $self->loc("That is not a value for this custom field"));
591     }
592
593     my $retval = $val_to_del->Delete;
594     unless ( $retval ) {
595         return (0, $self->loc("Custom field value could not be deleted"));
596     }
597     return ($retval, $self->loc("Custom field value deleted"));
598 }
599
600
601 =head2 ValidateQueue Queue
602
603 Make sure that the name specified is valid
604
605 =cut
606
607 sub ValidateName {
608     my $self = shift;
609     my $value = shift;
610
611     return 0 unless length $value;
612
613     return $self->SUPER::ValidateName($value);
614 }
615
616 =head2 ValidateQueue Queue
617
618 Make sure that the queue specified is a valid queue name
619
620 =cut
621
622 sub ValidateQueue {
623     my $self = shift;
624     my $id = shift;
625
626     return undef unless defined $id;
627     # 0 means "Global" null would _not_ be ok.
628     return 1 if $id eq '0';
629
630     my $q = RT::Queue->new( RT->SystemUser );
631     $q->Load( $id );
632     return undef unless $q->id;
633     return 1;
634 }
635
636
637
638 =head2 Types 
639
640 Retuns an array of the types of CustomField that are supported
641
642 =cut
643
644 sub Types {
645     return (sort {(($FieldTypes{$a}{sort_order}||999) <=> ($FieldTypes{$b}{sort_order}||999)) or ($a cmp $b)} keys %FieldTypes);
646 }
647
648
649 =head2 IsSelectionType 
650
651 Retuns a boolean value indicating whether the C<Values> method makes sense
652 to this Custom Field.
653
654 =cut
655
656 sub IsSelectionType {
657     my $self = shift;
658     my $type = @_? shift : $self->Type;
659     return undef unless $type;
660     return $FieldTypes{$type}->{selection_type};
661 }
662
663
664
665 =head2 IsExternalValues
666
667 =cut
668
669 sub IsExternalValues {
670     my $self = shift;
671     return 0 unless $self->IsSelectionType( @_ );
672     return $self->ValuesClass eq 'RT::CustomFieldValues'? 0 : 1;
673 }
674
675 sub ValuesClass {
676     my $self = shift;
677     return $self->_Value( ValuesClass => @_ ) || 'RT::CustomFieldValues';
678 }
679
680 sub SetValuesClass {
681     my $self = shift;
682     my $class = shift || 'RT::CustomFieldValues';
683     
684     if ( $class eq 'RT::CustomFieldValues' ) {
685         return $self->_Set( Field => 'ValuesClass', Value => undef, @_ );
686     }
687
688     return (0, $self->loc("This Custom Field can not have list of values"))
689         unless $self->IsSelectionType;
690
691     unless ( $self->ValidateValuesClass( $class ) ) {
692         return (0, $self->loc("Invalid Custom Field values source"));
693     }
694     return $self->_Set( Field => 'ValuesClass', Value => $class, @_ );
695 }
696
697 sub ValidateValuesClass {
698     my $self = shift;
699     my $class = shift;
700
701     return 1 if !defined $class || $class eq 'RT::CustomFieldValues';
702     return 1 if grep $class eq $_, RT->Config->Get('CustomFieldValuesSources');
703     return undef;
704 }
705
706
707 =head2 FriendlyType [TYPE, MAX_VALUES]
708
709 Returns a localized human-readable version of the custom field type.
710 If a custom field type is specified as the parameter, the friendly type for that type will be returned
711
712 =cut
713
714 sub FriendlyType {
715     my $self = shift;
716
717     my $type = @_ ? shift : $self->Type;
718     my $max  = @_ ? shift : $self->MaxValues;
719     $max = 0 unless $max;
720
721     if (my $friendly_type = $FieldTypes{$type}->{labels}->[$max>2 ? 2 : $max]) {
722         return ( $self->loc( $friendly_type, $max ) );
723     }
724     else {
725         return ( $self->loc( $type ) );
726     }
727 }
728
729 sub FriendlyTypeComposite {
730     my $self = shift;
731     my $composite = shift || $self->TypeComposite;
732     return $self->FriendlyType(split(/-/, $composite, 2));
733 }
734
735
736 =head2 ValidateType TYPE
737
738 Takes a single string. returns true if that string is a value
739 type of custom field
740
741
742 =cut
743
744 sub ValidateType {
745     my $self = shift;
746     my $type = shift;
747
748     if ( $type =~ s/(?:Single|Multiple)$// ) {
749         $RT::Logger->warning( "Prefix 'Single' and 'Multiple' to Type deprecated, use MaxValues instead at (". join(":",caller).")");
750     }
751
752     if ( $FieldTypes{$type} ) {
753         return 1;
754     }
755     else {
756         return undef;
757     }
758 }
759
760
761 sub SetType {
762     my $self = shift;
763     my $type = shift;
764     if ($type =~ s/(?:(Single)|Multiple)$//) {
765         $RT::Logger->warning("'Single' and 'Multiple' on SetType deprecated, use SetMaxValues instead at (". join(":",caller).")");
766         $self->SetMaxValues($1 ? 1 : 0);
767     }
768     $self->_Set(Field => 'Type', Value =>$type);
769 }
770
771 =head2 SetPattern STRING
772
773 Takes a single string representing a regular expression.  Performs basic
774 validation on that regex, and sets the C<Pattern> field for the CF if it
775 is valid.
776
777 =cut
778
779 sub SetPattern {
780     my $self = shift;
781     my $regex = shift;
782
783     my ($ok, $msg) = $self->_IsValidRegex($regex);
784     if ($ok) {
785         return $self->_Set(Field => 'Pattern', Value => $regex);
786     }
787     else {
788         return (0, $self->loc("Invalid pattern: [_1]", $msg));
789     }
790 }
791
792 =head2 _IsValidRegex(Str $regex) returns (Bool $success, Str $msg)
793
794 Tests if the string contains an invalid regex.
795
796 =cut
797
798 sub _IsValidRegex {
799     my $self  = shift;
800     my $regex = shift or return (1, 'valid');
801
802     local $^W; local $@;
803     local $SIG{__DIE__} = sub { 1 };
804     local $SIG{__WARN__} = sub { 1 };
805
806     if (eval { qr/$regex/; 1 }) {
807         return (1, 'valid');
808     }
809
810     my $err = $@;
811     $err =~ s{[,;].*}{};    # strip debug info from error
812     chomp $err;
813     return (0, $err);
814 }
815
816
817 =head2 SingleValue
818
819 Returns true if this CustomField only accepts a single value. 
820 Returns false if it accepts multiple values
821
822 =cut
823
824 sub SingleValue {
825     my $self = shift;
826     if (($self->MaxValues||0) == 1) {
827         return 1;
828     } 
829     else {
830         return undef;
831     }
832 }
833
834 sub UnlimitedValues {
835     my $self = shift;
836     if (($self->MaxValues||0) == 0) {
837         return 1;
838     } 
839     else {
840         return undef;
841     }
842 }
843
844
845 =head2 CurrentUserHasRight RIGHT
846
847 Helper function to call the custom field's queue's CurrentUserHasRight with the passed in args.
848
849 =cut
850
851 sub CurrentUserHasRight {
852     my $self  = shift;
853     my $right = shift;
854
855     return $self->CurrentUser->HasRight(
856         Object => $self,
857         Right  => $right,
858     );
859 }
860
861 =head2 ACLEquivalenceObjects
862
863 Returns list of objects via which users can get rights on this custom field. For custom fields
864 these objects can be set using L<ContextObject|/"ContextObject and SetContextObject">.
865
866 =cut
867
868 sub ACLEquivalenceObjects {
869     my $self = shift;
870
871     my $ctx = $self->ContextObject
872         or return;
873     return ($ctx, $ctx->ACLEquivalenceObjects);
874 }
875
876 =head2 ContextObject and SetContextObject
877
878 Set or get a context for this object. It can be ticket, queue or another object
879 this CF applies to. Used for ACL control, for example SeeCustomField can be granted on
880 queue level to allow people to see all fields applied to the queue.
881
882 =cut
883
884 sub SetContextObject {
885     my $self = shift;
886     return $self->{'context_object'} = shift;
887 }
888   
889 sub ContextObject {
890     my $self = shift;
891     return $self->{'context_object'};
892 }
893   
894
895 sub _Set {
896     my $self = shift;
897
898     unless ( $self->CurrentUserHasRight('AdminCustomField') ) {
899         return ( 0, $self->loc('Permission Denied') );
900     }
901     return $self->SUPER::_Set( @_ );
902
903 }
904
905
906
907 =head2 _Value
908
909 Takes the name of a table column.
910 Returns its value as a string, if the user passes an ACL check
911
912 =cut
913
914 sub _Value {
915     my $self  = shift;
916     return undef unless $self->id;
917
918     # we need to do the rights check
919     unless ( $self->CurrentUserHasRight('SeeCustomField') ) {
920         $RT::Logger->debug(
921             "Permission denied. User #". $self->CurrentUser->id
922             ." has no SeeCustomField right on CF #". $self->id
923         );
924         return (undef);
925     }
926     return $self->__Value( @_ );
927 }
928
929
930 =head2 SetDisabled
931
932 Takes a boolean.
933 1 will cause this custom field to no longer be avaialble for objects.
934 0 will re-enable this field.
935
936 =cut
937
938
939 =head2 SetTypeComposite
940
941 Set this custom field's type and maximum values as a composite value
942
943 =cut
944
945 sub SetTypeComposite {
946     my $self = shift;
947     my $composite = shift;
948
949     my $old = $self->TypeComposite;
950
951     my ($type, $max_values) = split(/-/, $composite, 2);
952     if ( $type ne $self->Type ) {
953         my ($status, $msg) = $self->SetType( $type );
954         return ($status, $msg) unless $status;
955     }
956     if ( ($max_values || 0) != ($self->MaxValues || 0) ) {
957         my ($status, $msg) = $self->SetMaxValues( $max_values );
958         return ($status, $msg) unless $status;
959     }
960     my $render = $self->RenderType;
961     if ( $render and not grep { $_ eq $render } $self->RenderTypes ) {
962         # We switched types and our render type is no longer valid, so unset it
963         # and use the default
964         $self->SetRenderType( undef );
965     }
966     return 1, $self->loc(
967         "Type changed from '[_1]' to '[_2]'",
968         $self->FriendlyTypeComposite( $old ),
969         $self->FriendlyTypeComposite( $composite ),
970     );
971 }
972
973 =head2 TypeComposite
974
975 Returns a composite value composed of this object's type and maximum values
976
977 =cut
978
979
980 sub TypeComposite {
981     my $self = shift;
982     return join '-', ($self->Type || ''), ($self->MaxValues || 0);
983 }
984
985 =head2 TypeComposites
986
987 Returns an array of all possible composite values for custom fields.
988
989 =cut
990
991 sub TypeComposites {
992     my $self = shift;
993     return grep !/(?:[Tt]ext|Combobox|Date|DateTime|TimeValue)-0/, map { ("$_-1", "$_-0") } $self->Types;
994 }
995
996 =head2 RenderType
997
998 Returns the type of form widget to render for this custom field.  Currently
999 this only affects fields which return true for L</HasRenderTypes>. 
1000
1001 =cut
1002
1003 sub RenderType {
1004     my $self = shift;
1005     return '' unless $self->HasRenderTypes;
1006
1007     return $self->_Value( 'RenderType', @_ )
1008         || $self->DefaultRenderType;
1009 }
1010
1011 =head2 SetRenderType TYPE
1012
1013 Sets this custom field's render type.
1014
1015 =cut
1016
1017 sub SetRenderType {
1018     my $self = shift;
1019     my $type = shift;
1020     return (0, $self->loc("This custom field has no Render Types"))
1021         unless $self->HasRenderTypes;
1022
1023     if ( !$type || $type eq $self->DefaultRenderType ) {
1024         return $self->_Set( Field => 'RenderType', Value => undef, @_ );
1025     }
1026
1027     if ( not grep { $_ eq $type } $self->RenderTypes ) {
1028         return (0, $self->loc("Invalid Render Type for custom field of type [_1]",
1029                                 $self->FriendlyType));
1030     }
1031
1032     # XXX: Remove this restriction once we support lists and cascaded selects
1033     if ( $self->BasedOnObj->id and $type =~ /List/ ) {
1034         return (0, $self->loc("We can't currently render as a List when basing categories on another custom field.  Please use another render type."));
1035     }
1036
1037     return $self->_Set( Field => 'RenderType', Value => $type, @_ );
1038 }
1039
1040 =head2 DefaultRenderType [TYPE COMPOSITE]
1041
1042 Returns the default render type for this custom field's type or the TYPE
1043 COMPOSITE specified as an argument.
1044
1045 =cut
1046
1047 sub DefaultRenderType {
1048     my $self = shift;
1049     my $composite    = @_ ? shift : $self->TypeComposite;
1050     my ($type, $max) = split /-/, $composite, 2;
1051     return unless $type and $self->HasRenderTypes($composite);
1052     return $FieldTypes{$type}->{render_types}->{ $max == 1 ? 'single' : 'multiple' }[0];
1053 }
1054
1055 =head2 HasRenderTypes [TYPE_COMPOSITE]
1056
1057 Returns a boolean value indicating whether the L</RenderTypes> and
1058 L</RenderType> methods make sense for this custom field.
1059
1060 Currently true only for type C<Select>.
1061
1062 =cut
1063
1064 sub HasRenderTypes {
1065     my $self = shift;
1066     my ($type, $max) = split /-/, (@_ ? shift : $self->TypeComposite), 2;
1067     return undef unless $type;
1068     return defined $FieldTypes{$type}->{render_types}
1069         ->{ $max == 1 ? 'single' : 'multiple' };
1070 }
1071
1072 =head2 RenderTypes [TYPE COMPOSITE]
1073
1074 Returns the valid render types for this custom field's type or the TYPE
1075 COMPOSITE specified as an argument.
1076
1077 =cut
1078
1079 sub RenderTypes {
1080     my $self = shift;
1081     my $composite    = @_ ? shift : $self->TypeComposite;
1082     my ($type, $max) = split /-/, $composite, 2;
1083     return unless $type and $self->HasRenderTypes($composite);
1084     return @{$FieldTypes{$type}->{render_types}->{ $max == 1 ? 'single' : 'multiple' }};
1085 }
1086
1087 =head2 SetLookupType
1088
1089 Autrijus: care to doc how LookupTypes work?
1090
1091 =cut
1092
1093 sub SetLookupType {
1094     my $self = shift;
1095     my $lookup = shift;
1096     if ( $lookup ne $self->LookupType ) {
1097         # Okay... We need to invalidate our existing relationships
1098         my $ObjectCustomFields = RT::ObjectCustomFields->new($self->CurrentUser);
1099         $ObjectCustomFields->LimitToCustomField($self->Id);
1100         $_->Delete foreach @{$ObjectCustomFields->ItemsArrayRef};
1101     }
1102     return $self->_Set(Field => 'LookupType', Value =>$lookup);
1103 }
1104
1105 =head2 LookupTypes
1106
1107 Returns an array of LookupTypes available
1108
1109 =cut
1110
1111
1112 sub LookupTypes {
1113     my $self = shift;
1114     return keys %FRIENDLY_OBJECT_TYPES;
1115 }
1116
1117 my @FriendlyObjectTypes = (
1118     "[_1] objects",            # loc
1119     "[_1]'s [_2] objects",        # loc
1120     "[_1]'s [_2]'s [_3] objects",   # loc
1121 );
1122
1123 =head2 FriendlyLookupType
1124
1125 Returns a localized description of the type of this custom field
1126
1127 =cut
1128
1129 sub FriendlyLookupType {
1130     my $self = shift;
1131     my $lookup = shift || $self->LookupType;
1132    
1133     return ($self->loc( $FRIENDLY_OBJECT_TYPES{$lookup} ))
1134                      if (defined  $FRIENDLY_OBJECT_TYPES{$lookup} );
1135
1136     my @types = map { s/^RT::// ? $self->loc($_) : $_ }
1137       grep { defined and length }
1138       split( /-/, $lookup )
1139       or return;
1140     return ( $self->loc( $FriendlyObjectTypes[$#types], @types ) );
1141 }
1142
1143 sub RecordClassFromLookupType {
1144     my $self = shift;
1145     my ($class) = ($self->LookupType =~ /^([^-]+)/);
1146     unless ( $class ) {
1147         $RT::Logger->error(
1148             "Custom Field #". $self->id 
1149             ." has incorrect LookupType '". $self->LookupType ."'"
1150         );
1151         return undef;
1152     }
1153     return $class;
1154 }
1155
1156 sub CollectionClassFromLookupType {
1157     my $self = shift;
1158
1159     my $record_class = $self->RecordClassFromLookupType;
1160     return undef unless $record_class;
1161
1162     my $collection_class;
1163     if ( UNIVERSAL::can($record_class.'Collection', 'new') ) {
1164         $collection_class = $record_class.'Collection';
1165     } elsif ( UNIVERSAL::can($record_class.'es', 'new') ) {
1166         $collection_class = $record_class.'es';
1167     } elsif ( UNIVERSAL::can($record_class.'s', 'new') ) {
1168         $collection_class = $record_class.'s';
1169     } else {
1170         $RT::Logger->error("Can not find a collection class for record class '$record_class'");
1171         return undef;
1172     }
1173     return $collection_class;
1174 }
1175
1176 =head1 ApplyGlobally
1177
1178 Certain custom fields (users, groups) should only be applied globally
1179 but rather than regexing in code for LookupType =~ RT::Queue, we'll codify
1180 the rules here.
1181
1182 =cut
1183
1184 sub ApplyGlobally {
1185     my $self = shift;
1186
1187     return ($self->LookupType =~ /^RT::(?:Group|User)/io);
1188
1189 }
1190
1191 =head1 AppliedTo
1192
1193 Returns collection with objects this custom field is applied to.
1194 Class of the collection depends on L</LookupType>.
1195 See all L</NotAppliedTo> .
1196
1197 Doesn't takes into account if object is applied globally.
1198
1199 =cut
1200
1201 sub AppliedTo {
1202     my $self = shift;
1203
1204     my ($res, $ocfs_alias) = $self->_AppliedTo;
1205     return $res unless $res;
1206
1207     $res->Limit(
1208         ALIAS     => $ocfs_alias,
1209         FIELD     => 'id',
1210         OPERATOR  => 'IS NOT',
1211         VALUE     => 'NULL',
1212     );
1213
1214     return $res;
1215 }
1216
1217 =head1 NotAppliedTo
1218
1219 Returns collection with objects this custom field is not applied to.
1220 Class of the collection depends on L</LookupType>.
1221 See all L</AppliedTo> .
1222
1223 Doesn't takes into account if object is applied globally.
1224
1225 =cut
1226
1227 sub NotAppliedTo {
1228     my $self = shift;
1229
1230     my ($res, $ocfs_alias) = $self->_AppliedTo;
1231     return $res unless $res;
1232
1233     $res->Limit(
1234         ALIAS     => $ocfs_alias,
1235         FIELD     => 'id',
1236         OPERATOR  => 'IS',
1237         VALUE     => 'NULL',
1238     );
1239
1240     return $res;
1241 }
1242
1243 sub _AppliedTo {
1244     my $self = shift;
1245
1246     my ($class) = $self->CollectionClassFromLookupType;
1247     return undef unless $class;
1248
1249     my $res = $class->new( $self->CurrentUser );
1250
1251     # If CF is a Group CF, only display user-defined groups
1252     if ( $class eq 'RT::Groups' ) {
1253         $res->LimitToUserDefinedGroups;
1254     }
1255
1256     $res->OrderBy( FIELD => 'Name' );
1257     my $ocfs_alias = $res->Join(
1258         TYPE   => 'LEFT',
1259         ALIAS1 => 'main',
1260         FIELD1 => 'id',
1261         TABLE2 => 'ObjectCustomFields',
1262         FIELD2 => 'ObjectId',
1263     );
1264     $res->Limit(
1265         LEFTJOIN => $ocfs_alias,
1266         ALIAS    => $ocfs_alias,
1267         FIELD    => 'CustomField',
1268         VALUE    => $self->id,
1269     );
1270     return ($res, $ocfs_alias);
1271 }
1272
1273 =head2 IsApplied
1274
1275 Takes object id and returns corresponding L<RT::ObjectCustomField>
1276 record if this custom field is applied to the object. Use 0 to check
1277 if custom field is applied globally.
1278
1279 =cut
1280
1281 sub IsApplied {
1282     my $self = shift;
1283     my $id = shift;
1284     my $ocf = RT::ObjectCustomField->new( $self->CurrentUser );
1285     $ocf->LoadByCols( CustomField => $self->id, ObjectId => $id || 0 );
1286     return undef unless $ocf->id;
1287     return $ocf;
1288 }
1289
1290 =head2 AddToObject OBJECT
1291
1292 Add this custom field as a custom field for a single object, such as a queue or group.
1293
1294 Takes an object 
1295
1296 =cut
1297
1298
1299 sub AddToObject {
1300     my $self  = shift;
1301     my $object = shift;
1302     my $id = $object->Id || 0;
1303
1304     unless (index($self->LookupType, ref($object)) == 0) {
1305         return ( 0, $self->loc('Lookup type mismatch') );
1306     }
1307
1308     unless ( $object->CurrentUserHasRight('AssignCustomFields') ) {
1309         return ( 0, $self->loc('Permission Denied') );
1310     }
1311
1312     if ( $self->IsApplied( $id ) ) {
1313         return ( 0, $self->loc("Custom field is already applied to the object") );
1314     }
1315
1316     if ( $id ) {
1317         # applying locally
1318         return (0, $self->loc("Couldn't apply custom field to an object as it's global already") )
1319             if $self->IsApplied( 0 );
1320     }
1321     else {
1322         my $applied = RT::ObjectCustomFields->new( $self->CurrentUser );
1323         $applied->LimitToCustomField( $self->id );
1324         while ( my $record = $applied->Next ) {
1325             $record->Delete;
1326         }
1327     }
1328
1329     my $ocf = RT::ObjectCustomField->new( $self->CurrentUser );
1330     my ( $oid, $msg ) = $ocf->Create(
1331         ObjectId => $id, CustomField => $self->id,
1332     );
1333     return ( $oid, $msg );
1334 }
1335
1336
1337 =head2 RemoveFromObject OBJECT
1338
1339 Remove this custom field  for a single object, such as a queue or group.
1340
1341 Takes an object 
1342
1343 =cut
1344
1345 sub RemoveFromObject {
1346     my $self = shift;
1347     my $object = shift;
1348     my $id = $object->Id || 0;
1349
1350     unless (index($self->LookupType, ref($object)) == 0) {
1351         return ( 0, $self->loc('Object type mismatch') );
1352     }
1353
1354     unless ( $object->CurrentUserHasRight('AssignCustomFields') ) {
1355         return ( 0, $self->loc('Permission Denied') );
1356     }
1357
1358     my $ocf = $self->IsApplied( $id );
1359     unless ( $ocf ) {
1360         return ( 0, $self->loc("This custom field does not apply to that object") );
1361     }
1362
1363     # XXX: Delete doesn't return anything
1364     my ( $oid, $msg ) = $ocf->Delete;
1365     return ( $oid, $msg );
1366 }
1367
1368
1369 =head2 AddValueForObject HASH
1370
1371 Adds a custom field value for a record object of some kind. 
1372 Takes a param hash of 
1373
1374 Required:
1375
1376     Object
1377     Content
1378
1379 Optional:
1380
1381     LargeContent
1382     ContentType
1383
1384 =cut
1385
1386 sub AddValueForObject {
1387     my $self = shift;
1388     my %args = (
1389         Object       => undef,
1390         Content      => undef,
1391         LargeContent => undef,
1392         ContentType  => undef,
1393         @_
1394     );
1395     my $obj = $args{'Object'} or return ( 0, $self->loc('Invalid object') );
1396
1397     unless ( $self->CurrentUserHasRight('ModifyCustomField') ) {
1398         return ( 0, $self->loc('Permission Denied') );
1399     }
1400
1401     unless ( $self->MatchPattern($args{'Content'}) ) {
1402         return ( 0, $self->loc('Input must match [_1]', $self->FriendlyPattern) );
1403     }
1404
1405     $RT::Handle->BeginTransaction;
1406
1407     if ( $self->MaxValues ) {
1408         my $current_values = $self->ValuesForObject($obj);
1409         my $extra_values = ( $current_values->Count + 1 ) - $self->MaxValues;
1410
1411         # (The +1 is for the new value we're adding)
1412
1413         # If we have a set of current values and we've gone over the maximum
1414         # allowed number of values, we'll need to delete some to make room.
1415         # which former values are blown away is not guaranteed
1416
1417         while ($extra_values) {
1418             my $extra_item = $current_values->Next;
1419             unless ( $extra_item->id ) {
1420                 $RT::Logger->crit( "We were just asked to delete "
1421                     ."a custom field value that doesn't exist!" );
1422                 $RT::Handle->Rollback();
1423                 return (undef);
1424             }
1425             $extra_item->Delete;
1426             $extra_values--;
1427         }
1428     }
1429
1430     if (my $canonicalizer = $self->can('_CanonicalizeValue'.$self->Type)) {
1431          $canonicalizer->($self, \%args);
1432     }
1433
1434
1435
1436     my $newval = RT::ObjectCustomFieldValue->new( $self->CurrentUser );
1437     my ($val, $msg) = $newval->Create(
1438         ObjectType   => ref($obj),
1439         ObjectId     => $obj->Id,
1440         Content      => $args{'Content'},
1441         LargeContent => $args{'LargeContent'},
1442         ContentType  => $args{'ContentType'},
1443         CustomField  => $self->Id
1444     );
1445
1446     unless ($val) {
1447         $RT::Handle->Rollback();
1448         return ($val, $self->loc("Couldn't create record: [_1]", $msg));
1449     }
1450
1451     $RT::Handle->Commit();
1452     return ($val);
1453
1454 }
1455
1456
1457
1458 sub _CanonicalizeValueDateTime {
1459     my $self    = shift;
1460     my $args    = shift;
1461     my $DateObj = RT::Date->new( $self->CurrentUser );
1462     $DateObj->Set( Format => 'unknown',
1463                    Value  => $args->{'Content'} );
1464     $args->{'Content'} = $DateObj->ISO;
1465 }
1466
1467 # For date, we need to store Content as ISO date
1468 sub _CanonicalizeValueDate {
1469     my $self = shift;
1470     my $args = shift;
1471
1472     # in case user input date with time, let's omit it by setting timezone
1473     # to utc so "hour" won't affect "day"
1474     my $DateObj = RT::Date->new( $self->CurrentUser );
1475     $DateObj->Set( Format   => 'unknown',
1476                    Value    => $args->{'Content'},
1477                    Timezone => 'UTC',
1478                  );
1479     $args->{'Content'} = $DateObj->Date( Timezone => 'UTC' );
1480 }
1481
1482 =head2 MatchPattern STRING
1483
1484 Tests the incoming string against the Pattern of this custom field object
1485 and returns a boolean; returns true if the Pattern is empty.
1486
1487 =cut
1488
1489 sub MatchPattern {
1490     my $self = shift;
1491     my $regex = $self->Pattern or return 1;
1492
1493     return (( defined $_[0] ? $_[0] : '') =~ $regex);
1494 }
1495
1496
1497
1498
1499 =head2 FriendlyPattern
1500
1501 Prettify the pattern of this custom field, by taking the text in C<(?#text)>
1502 and localizing it.
1503
1504 =cut
1505
1506 sub FriendlyPattern {
1507     my $self = shift;
1508     my $regex = $self->Pattern;
1509
1510     return '' unless length $regex;
1511     if ( $regex =~ /\(\?#([^)]*)\)/ ) {
1512         return '[' . $self->loc($1) . ']';
1513     }
1514     else {
1515         return $regex;
1516     }
1517 }
1518
1519
1520
1521
1522 =head2 DeleteValueForObject HASH
1523
1524 Deletes a custom field value for a ticket. Takes a param hash of Object and Content
1525
1526 Returns a tuple of (STATUS, MESSAGE). If the call succeeded, the STATUS is true. otherwise it's false
1527
1528 =cut
1529
1530 sub DeleteValueForObject {
1531     my $self = shift;
1532     my %args = ( Object => undef,
1533                  Content => undef,
1534                  Id => undef,
1535              @_ );
1536
1537
1538     unless ($self->CurrentUserHasRight('ModifyCustomField')) {
1539         return (0, $self->loc('Permission Denied'));
1540     }
1541
1542     my $oldval = RT::ObjectCustomFieldValue->new($self->CurrentUser);
1543
1544     if (my $id = $args{'Id'}) {
1545         $oldval->Load($id);
1546     }
1547     unless ($oldval->id) { 
1548         $oldval->LoadByObjectContentAndCustomField(
1549             Object => $args{'Object'}, 
1550             Content =>  $args{'Content'}, 
1551             CustomField => $self->Id,
1552         );
1553     }
1554
1555
1556     # check to make sure we found it
1557     unless ($oldval->Id) {
1558         return(0, $self->loc("Custom field value [_1] could not be found for custom field [_2]", $args{'Content'}, $self->Name));
1559     }
1560
1561     # for single-value fields, we need to validate that empty string is a valid value for it
1562     if ( $self->SingleValue and not $self->MatchPattern( '' ) ) {
1563         return ( 0, $self->loc('Input must match [_1]', $self->FriendlyPattern) );
1564     }
1565
1566     # delete it
1567
1568     my $ret = $oldval->Delete();
1569     unless ($ret) {
1570         return(0, $self->loc("Custom field value could not be found"));
1571     }
1572     return($oldval->Id, $self->loc("Custom field value deleted"));
1573 }
1574
1575
1576 =head2 ValuesForObject OBJECT
1577
1578 Return an L<RT::ObjectCustomFieldValues> object containing all of this custom field's values for OBJECT 
1579
1580 =cut
1581
1582 sub ValuesForObject {
1583     my $self = shift;
1584     my $object = shift;
1585
1586     my $values = RT::ObjectCustomFieldValues->new($self->CurrentUser);
1587     unless ($self->CurrentUserHasRight('SeeCustomField')) {
1588         # Return an empty object if they have no rights to see
1589         return ($values);
1590     }
1591     
1592     
1593     $values->LimitToCustomField($self->Id);
1594     $values->LimitToEnabled();
1595     $values->LimitToObject($object);
1596
1597     return ($values);
1598 }
1599
1600
1601 =head2 _ForObjectType PATH FRIENDLYNAME
1602
1603 Tell RT that a certain object accepts custom fields
1604
1605 Examples:
1606
1607     'RT::Queue-RT::Ticket'                 => "Tickets",                # loc
1608     'RT::Queue-RT::Ticket-RT::Transaction' => "Ticket Transactions",    # loc
1609     'RT::User'                             => "Users",                  # loc
1610     'RT::Group'                            => "Groups",                 # loc
1611
1612 This is a class method. 
1613
1614 =cut
1615
1616 sub _ForObjectType {
1617     my $self = shift;
1618     my $path = shift;
1619     my $friendly_name = shift;
1620
1621     $FRIENDLY_OBJECT_TYPES{$path} = $friendly_name;
1622
1623 }
1624
1625
1626 =head2 IncludeContentForValue [VALUE] (and SetIncludeContentForValue)
1627
1628 Gets or sets the  C<IncludeContentForValue> for this custom field. RT
1629 uses this field to automatically include content into the user's browser
1630 as they display records with custom fields in RT.
1631
1632 =cut
1633
1634 sub SetIncludeContentForValue {
1635     shift->IncludeContentForValue(@_);
1636 }
1637 sub IncludeContentForValue{
1638     my $self = shift;
1639     $self->_URLTemplate('IncludeContentForValue', @_);
1640 }
1641
1642
1643
1644 =head2 LinkValueTo [VALUE] (and SetLinkValueTo)
1645
1646 Gets or sets the  C<LinkValueTo> for this custom field. RT
1647 uses this field to make custom field values into hyperlinks in the user's
1648 browser as they display records with custom fields in RT.
1649
1650 =cut
1651
1652
1653 sub SetLinkValueTo {
1654     shift->LinkValueTo(@_);
1655 }
1656
1657 sub LinkValueTo {
1658     my $self = shift;
1659     $self->_URLTemplate('LinkValueTo', @_);
1660
1661 }
1662
1663
1664 =head2 _URLTemplate  NAME [VALUE]
1665
1666 With one argument, returns the _URLTemplate named C<NAME>, but only if
1667 the current user has the right to see this custom field.
1668
1669 With two arguments, attemptes to set the relevant template value.
1670
1671 =cut
1672
1673 sub _URLTemplate {
1674     my $self          = shift;
1675     my $template_name = shift;
1676     if (@_) {
1677
1678         my $value = shift;
1679         unless ( $self->CurrentUserHasRight('AdminCustomField') ) {
1680             return ( 0, $self->loc('Permission Denied') );
1681         }
1682         $self->SetAttribute( Name => $template_name, Content => $value );
1683         return ( 1, $self->loc('Updated') );
1684     } else {
1685         unless ( $self->id && $self->CurrentUserHasRight('SeeCustomField') ) {
1686             return (undef);
1687         }
1688
1689         my @attr = $self->Attributes->Named($template_name);
1690         my $attr = shift @attr;
1691
1692         if ($attr) { return $attr->Content }
1693
1694     }
1695 }
1696
1697 sub SetBasedOn {
1698     my $self = shift;
1699     my $value = shift;
1700
1701     return $self->_Set( Field => 'BasedOn', Value => $value, @_ )
1702         unless defined $value and length $value;
1703
1704     my $cf = RT::CustomField->new( $self->CurrentUser );
1705     $cf->Load( ref $value ? $value->id : $value );
1706
1707     return (0, "Permission denied")
1708         unless $cf->id && $cf->CurrentUserHasRight('SeeCustomField');
1709
1710     # XXX: Remove this restriction once we support lists and cascaded selects
1711     if ( $self->RenderType =~ /List/ ) {
1712         return (0, $self->loc("We can't currently render as a List when basing categories on another custom field.  Please use another render type."));
1713     }
1714
1715     return $self->_Set( Field => 'BasedOn', Value => $value, @_ )
1716 }
1717
1718 sub BasedOnObj {
1719     my $self = shift;
1720
1721     my $obj = RT::CustomField->new( $self->CurrentUser );
1722     if ( $self->BasedOn ) {
1723         $obj->Load( $self->BasedOn );
1724     }
1725     return $obj;
1726 }
1727
1728 sub UILocation {
1729     my $self = shift;
1730     my $tag = $self->FirstAttribute( 'UILocation' );
1731     return $tag ? $tag->Content : '';
1732 }
1733
1734 sub SetUILocation {
1735     my $self = shift;
1736     my $tag = shift;
1737     if ( $tag ) {
1738         return $self->SetAttribute( Name => 'UILocation', Content => $tag );
1739     }
1740     else {
1741         return $self->DeleteAttribute('UILocation');
1742     }
1743 }
1744
1745
1746
1747
1748
1749
1750 =head2 id
1751
1752 Returns the current value of id. 
1753 (In the database, id is stored as int(11).)
1754
1755
1756 =cut
1757
1758
1759 =head2 Name
1760
1761 Returns the current value of Name. 
1762 (In the database, Name is stored as varchar(200).)
1763
1764
1765
1766 =head2 SetName VALUE
1767
1768
1769 Set Name to VALUE. 
1770 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1771 (In the database, Name will be stored as a varchar(200).)
1772
1773
1774 =cut
1775
1776
1777 =head2 Type
1778
1779 Returns the current value of Type. 
1780 (In the database, Type is stored as varchar(200).)
1781
1782
1783
1784 =head2 SetType VALUE
1785
1786
1787 Set Type to VALUE. 
1788 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1789 (In the database, Type will be stored as a varchar(200).)
1790
1791
1792 =cut
1793
1794
1795 =head2 RenderType
1796
1797 Returns the current value of RenderType. 
1798 (In the database, RenderType is stored as varchar(64).)
1799
1800
1801
1802 =head2 SetRenderType VALUE
1803
1804
1805 Set RenderType to VALUE. 
1806 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1807 (In the database, RenderType will be stored as a varchar(64).)
1808
1809
1810 =cut
1811
1812
1813 =head2 MaxValues
1814
1815 Returns the current value of MaxValues. 
1816 (In the database, MaxValues is stored as int(11).)
1817
1818
1819
1820 =head2 SetMaxValues VALUE
1821
1822
1823 Set MaxValues to VALUE. 
1824 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1825 (In the database, MaxValues will be stored as a int(11).)
1826
1827
1828 =cut
1829
1830
1831 =head2 Pattern
1832
1833 Returns the current value of Pattern. 
1834 (In the database, Pattern is stored as text.)
1835
1836
1837
1838 =head2 SetPattern VALUE
1839
1840
1841 Set Pattern to VALUE. 
1842 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1843 (In the database, Pattern will be stored as a text.)
1844
1845
1846 =cut
1847
1848
1849 =head2 Repeated
1850
1851 Returns the current value of Repeated. 
1852 (In the database, Repeated is stored as smallint(6).)
1853
1854
1855
1856 =head2 SetRepeated VALUE
1857
1858
1859 Set Repeated to VALUE. 
1860 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1861 (In the database, Repeated will be stored as a smallint(6).)
1862
1863
1864 =cut
1865
1866
1867 =head2 BasedOn
1868
1869 Returns the current value of BasedOn. 
1870 (In the database, BasedOn is stored as int(11).)
1871
1872
1873
1874 =head2 SetBasedOn VALUE
1875
1876
1877 Set BasedOn to VALUE. 
1878 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1879 (In the database, BasedOn will be stored as a int(11).)
1880
1881
1882 =cut
1883
1884
1885 =head2 Description
1886
1887 Returns the current value of Description. 
1888 (In the database, Description is stored as varchar(255).)
1889
1890
1891
1892 =head2 SetDescription VALUE
1893
1894
1895 Set Description to VALUE. 
1896 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1897 (In the database, Description will be stored as a varchar(255).)
1898
1899
1900 =cut
1901
1902
1903 =head2 SortOrder
1904
1905 Returns the current value of SortOrder. 
1906 (In the database, SortOrder is stored as int(11).)
1907
1908
1909
1910 =head2 SetSortOrder VALUE
1911
1912
1913 Set SortOrder to VALUE. 
1914 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1915 (In the database, SortOrder will be stored as a int(11).)
1916
1917
1918 =cut
1919
1920
1921 =head2 LookupType
1922
1923 Returns the current value of LookupType. 
1924 (In the database, LookupType is stored as varchar(255).)
1925
1926
1927
1928 =head2 SetLookupType VALUE
1929
1930
1931 Set LookupType to VALUE. 
1932 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1933 (In the database, LookupType will be stored as a varchar(255).)
1934
1935
1936 =cut
1937
1938
1939 =head2 Creator
1940
1941 Returns the current value of Creator. 
1942 (In the database, Creator is stored as int(11).)
1943
1944
1945 =cut
1946
1947
1948 =head2 Created
1949
1950 Returns the current value of Created. 
1951 (In the database, Created is stored as datetime.)
1952
1953
1954 =cut
1955
1956
1957 =head2 LastUpdatedBy
1958
1959 Returns the current value of LastUpdatedBy. 
1960 (In the database, LastUpdatedBy is stored as int(11).)
1961
1962
1963 =cut
1964
1965
1966 =head2 LastUpdated
1967
1968 Returns the current value of LastUpdated. 
1969 (In the database, LastUpdated is stored as datetime.)
1970
1971
1972 =cut
1973
1974
1975 =head2 Disabled
1976
1977 Returns the current value of Disabled. 
1978 (In the database, Disabled is stored as smallint(6).)
1979
1980
1981
1982 =head2 SetDisabled VALUE
1983
1984
1985 Set Disabled to VALUE. 
1986 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1987 (In the database, Disabled will be stored as a smallint(6).)
1988
1989
1990 =cut
1991
1992
1993
1994 sub _CoreAccessible {
1995     {
1996      
1997         id =>
1998         {read => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => ''},
1999         Name => 
2000         {read => 1, write => 1, sql_type => 12, length => 200,  is_blob => 0,  is_numeric => 0,  type => 'varchar(200)', default => ''},
2001         Type => 
2002         {read => 1, write => 1, sql_type => 12, length => 200,  is_blob => 0,  is_numeric => 0,  type => 'varchar(200)', default => ''},
2003         RenderType => 
2004         {read => 1, write => 1, sql_type => 12, length => 64,  is_blob => 0,  is_numeric => 0,  type => 'varchar(64)', default => ''},
2005         MaxValues => 
2006         {read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => ''},
2007         Pattern => 
2008         {read => 1, write => 1, sql_type => -4, length => 0,  is_blob => 1,  is_numeric => 0,  type => 'text', default => ''},
2009         Repeated => 
2010         {read => 1, write => 1, sql_type => 5, length => 6,  is_blob => 0,  is_numeric => 1,  type => 'smallint(6)', default => '0'},
2011         BasedOn => 
2012         {read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => ''},
2013         Description => 
2014         {read => 1, write => 1, sql_type => 12, length => 255,  is_blob => 0,  is_numeric => 0,  type => 'varchar(255)', default => ''},
2015         SortOrder => 
2016         {read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
2017         LookupType => 
2018         {read => 1, write => 1, sql_type => 12, length => 255,  is_blob => 0,  is_numeric => 0,  type => 'varchar(255)', default => ''},
2019         Creator => 
2020         {read => 1, auto => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
2021         Created => 
2022         {read => 1, auto => 1, sql_type => 11, length => 0,  is_blob => 0,  is_numeric => 0,  type => 'datetime', default => ''},
2023         LastUpdatedBy => 
2024         {read => 1, auto => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
2025         LastUpdated => 
2026         {read => 1, auto => 1, sql_type => 11, length => 0,  is_blob => 0,  is_numeric => 0,  type => 'datetime', default => ''},
2027         Disabled => 
2028         {read => 1, write => 1, sql_type => 5, length => 6,  is_blob => 0,  is_numeric => 1,  type => 'smallint(6)', default => '0'},
2029
2030  }
2031 };
2032
2033
2034 RT::Base->_ImportOverlays();
2035
2036 1;