import rt 3.4.6
[freeside.git] / rt / lib / RT / CustomField_Overlay.pm
1 # BEGIN BPS TAGGED BLOCK {{{
2
3 # COPYRIGHT:
4 #  
5 # This software is Copyright (c) 1996-2005 Best Practical Solutions, LLC 
6 #                                          <jesse@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., 675 Mass Ave, Cambridge, MA 02139, USA.
26
27
28 # CONTRIBUTION SUBMISSION POLICY:
29
30 # (The following paragraph is not intended to limit the rights granted
31 # to you to modify and distribute this software under the terms of
32 # the GNU General Public License and is only of importance to you if
33 # you choose to contribute your changes and enhancements to the
34 # community by submitting them to Best Practical Solutions, LLC.)
35
36 # By intentionally submitting any modifications, corrections or
37 # derivatives to this work, or any other work intended for use with
38 # Request Tracker, to Best Practical Solutions, LLC, you confirm that
39 # you are the copyright holder for those contributions and you grant
40 # Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
41 # royalty-free, perpetual, license to use, copy, create derivative
42 # works based on those contributions, and sublicense and distribute
43 # those contributions and any derivatives thereof.
44
45 # END BPS TAGGED BLOCK }}}
46 package RT::CustomField;
47
48 use strict;
49 no warnings qw(redefine);
50
51 use vars qw(%FieldTypes $RIGHTS %FRIENDLY_OBJECT_TYPES);
52
53 use RT::CustomFieldValues;
54 use RT::ObjectCustomFieldValues;
55
56
57 %FieldTypes = (
58     Select => [
59         'Select multiple values',       # loc
60         'Select one value',             # loc
61         'Select up to [_1] values',     # loc
62     ],
63     Freeform => [
64         'Enter multiple values',        # loc
65         'Enter one value',              # loc
66         'Enter up to [_1] values',      # loc
67     ],
68     Text => [
69         'Fill in multiple text areas',  # loc
70         'Fill in one text area',        # loc
71         'Fill in up to [_1] text areas',# loc
72     ],
73     Wikitext => [
74         'Fill in multiple wikitext areas',      # loc
75         'Fill in one wikitext area',    # loc
76         'Fill in up to [_1] wikitext areas',# loc
77     ],
78     Image => [
79         'Upload multiple images',       # loc
80         'Upload one image',             # loc
81         'Upload up to [_1] images',     # loc
82     ],
83     Binary => [
84         'Upload multiple files',        # loc
85         'Upload one file',              # loc
86         'Upload up to [_1] files',      # loc
87     ],
88 );
89
90
91 %FRIENDLY_OBJECT_TYPES =  ();
92
93 RT::CustomField->_ForObjectType( 'RT::Queue-RT::Ticket' => "Tickets", );    #loc
94 RT::CustomField->_ForObjectType(
95     'RT::Queue-RT::Ticket-RT::Transaction' => "Ticket Transactions", );    #loc
96 RT::CustomField->_ForObjectType( 'RT::User'  => "Users", );                           #loc
97 RT::CustomField->_ForObjectType( 'RT::Group' => "Groups", );                          #loc
98
99 $RIGHTS = {
100     SeeCustomField            => 'See custom fields',       # loc_pair
101     AdminCustomField          => 'Create, delete and modify custom fields',        # loc_pair
102     ModifyCustomField         => 'Add, delete and modify custom field values for objects' #loc_pair
103
104 };
105
106 # Tell RT::ACE that this sort of object can get acls granted
107 $RT::ACE::OBJECT_TYPES{'RT::CustomField'} = 1;
108
109 foreach my $right ( keys %{$RIGHTS} ) {
110     $RT::ACE::LOWERCASERIGHTNAMES{ lc $right } = $right;
111 }
112
113 sub AvailableRights {
114     my $self = shift;
115     return($RIGHTS);
116 }
117
118 =head1 NAME
119
120   RT::CustomField_Overlay 
121
122 =head1 DESCRIPTION
123
124 =head1 'CORE' METHODS
125
126 =cut
127
128
129
130 =head2 Create PARAMHASH
131
132 Create takes a hash of values and creates a row in the database:
133
134   varchar(200) 'Name'.
135   varchar(200) 'Type'.
136   int(11) 'MaxValues'.
137   varchar(255) 'Pattern'.
138   smallint(6) 'Repeated'.
139   varchar(255) 'Description'.
140   int(11) 'SortOrder'.
141   varchar(255) 'LookupType'.
142   smallint(6) 'Disabled'.
143
144   'LookupType' is generally the result of either 
145   RT::Ticket->CustomFieldLookupType or RT::Transaction->CustomFieldLookupType
146
147 =cut
148
149
150
151
152 sub Create {
153     my $self = shift;
154     my %args = ( 
155                 Name => '',
156                 Type => '',
157                 MaxValues => '0',
158                 Pattern  => '',
159                 Description => '',
160                 Disabled => '0',
161                 LookupType  => '',
162                 Repeated  => '0',
163
164                   @_);
165
166     unless ($self->CurrentUser->HasRight(Object => $RT::System, Right => 'AdminCustomField')) {
167         return (0, $self->loc('Permission Denied'));
168     }
169
170
171     if ($args{TypeComposite}) {
172         @args{'Type', 'MaxValues'} = split(/-/, $args{TypeComposite}, 2);
173     }
174     elsif ($args{Type} =~ s/(?:(Single)|Multiple)$//) {
175         # old style Type string
176         $args{'MaxValues'} = $1 ? 1 : 0;
177     }
178     
179     if ( !exists $args{'Queue'}) {
180         # do nothing -- things below are strictly backward compat
181     }
182     elsif (  ! $args{'Queue'} ) {
183         unless ( $self->CurrentUser->HasRight( Object => $RT::System, Right => 'AssignCustomFields') ) {
184             return ( 0, $self->loc('Permission Denied') );
185         }
186         $args{'LookupType'} = 'RT::Queue-RT::Ticket';
187     }
188     else {
189         my $queue = RT::Queue->new($self->CurrentUser);
190         $queue->Load($args{'Queue'});
191         unless ($queue->Id) {
192             return (0, $self->loc("Queue not found"));
193         }
194         unless ( $queue->CurrentUserHasRight('AssignCustomFields') ) {
195             return ( 0, $self->loc('Permission Denied') );
196         }
197         $args{'LookupType'} = 'RT::Queue-RT::Ticket';
198     }
199     my $rv = $self->SUPER::Create(
200                          Name => $args{'Name'},
201                          Type => $args{'Type'},
202                          MaxValues => $args{'MaxValues'},
203                          Pattern  => $args{'Pattern'},
204                          Description => $args{'Description'},
205                          Disabled => $args{'Disabled'},
206                          LookupType => $args{'LookupType'},
207                          Repeated => $args{'Repeated'},
208 );
209
210     return $rv unless exists $args{'Queue'};
211
212     # Compat code -- create a new ObjectCustomField mapping
213     my $OCF = RT::ObjectCustomField->new($self->CurrentUser);
214     $OCF->Create(
215         CustomField => $self->Id,
216         ObjectId => $args{'Queue'},
217     );
218
219     return $rv;
220 }
221
222 =head2 Load ID/NAME
223
224 Load a custom field.  If the value handed in is an integer, load by custom field ID. Otherwise, Load by name.
225
226 =cut
227
228
229 sub Load {
230     my $self = shift;
231     my $id = shift;
232
233     if ($id =~ /^\d+$/) {
234         return ($self->SUPER::Load($id));
235     } else {
236         return($self->LoadByName(Name => $id));
237     }
238 }
239
240
241 # {{{ sub LoadByName
242
243 =head2  LoadByName (Queue => QUEUEID, Name => NAME)
244
245 Loads the Custom field named NAME.
246
247 If a Queue parameter is specified, only look for ticket custom fields tied to that Queue.
248
249 If the Queue parameter is '0', look for global ticket custom fields.
250
251 If no queue parameter is specified, look for any and all custom fields with this name.
252
253 BUG/TODO, this won't let you specify that you only want user or group CFs.
254
255 =cut
256
257 # Compatibility for API change after 3.0 beta 1
258 *LoadNameAndQueue = \&LoadByName;
259 # Change after 3.4 beta.
260 *LoadByNameAndQueue = \&LoadByName;
261
262 sub LoadByName {
263     my $self = shift;
264     my %args = (
265         Queue => undef,
266         Name  => undef,
267         @_,
268     );
269
270     # if we're looking for a queue by name, make it a number
271     if  (defined $args{'Queue'}  &&  $args{'Queue'} !~ /^\d+$/) {
272         my $QueueObj = RT::Queue->new($self->CurrentUser);
273         $QueueObj->Load($args{'Queue'});
274         $args{'Queue'} = $QueueObj->Id;
275     }
276
277     # XXX - really naive implementation.  Slow. - not really. still just one query
278
279     my $CFs = RT::CustomFields->new($self->CurrentUser);
280
281     $CFs->Limit( FIELD => 'Name', VALUE => $args{'Name'} );
282     # Don't limit to queue if queue is 0.  Trying to do so breaks
283     # RT::Group type CFs.
284     if (defined $args{'Queue'}) {
285         $CFs->LimitToQueue( $args{'Queue'} );
286     }
287
288     # When loading by name, it's ok if they're disabled. That's not a big deal.
289     $CFs->{'find_disabled_rows'}=1;
290
291     # We only want one entry.
292     $CFs->RowsPerPage(1);
293     unless ($CFs->First) {
294         return(0);
295     }
296     return($self->Load($CFs->First->id));
297
298 }
299
300 # }}}
301
302 # {{{ Dealing with custom field values 
303
304 =begin testing
305
306 use_ok(RT::CustomField);
307 ok(my $cf = RT::CustomField->new($RT::SystemUser));
308 ok(my ($id, $msg)=  $cf->Create( Name => 'TestingCF',
309                                  Queue => '0',
310                                  SortOrder => '1',
311                                  Description => 'A Testing custom field',
312                                  Type=> 'SelectSingle'), 'Created a global CustomField');
313 ok($id != 0, 'Global custom field correctly created');
314 ok ($cf->SingleValue);
315 is($cf->Type, 'Select');
316 is($cf->MaxValues, 1);
317
318 my ($val, $msg) = $cf->SetMaxValues('0');
319 ok($val, $msg);
320 is($cf->Type, 'Select');
321 is($cf->MaxValues, 0);
322 ok(!$cf->SingleValue );
323 ok(my ($bogus_val, $bogus_msg) = $cf->SetType('BogusType') , "Trying to set a custom field's type to a bogus type");
324 ok($bogus_val == 0, "Unable to set a custom field's type to a bogus type");
325
326 ok(my $bad_cf = RT::CustomField->new($RT::SystemUser));
327 ok(my ($bad_id, $bad_msg)=  $cf->Create( Name => 'TestingCF-bad',
328                                  Queue => '0',
329                                  SortOrder => '1',
330                                  Description => 'A Testing custom field with a bogus Type',
331                                  Type=> 'SelectSingleton'), 'Created a global CustomField with a bogus type');
332 ok($bad_id == 0, 'Global custom field correctly decided to not create a cf with a bogus type ');
333
334 =end testing
335
336 =cut
337
338 # {{{ AddValue
339
340 =head2 AddValue HASH
341
342 Create a new value for this CustomField.  Takes a paramhash containing the elements Name, Description and SortOrder
343
344 =begin testing
345
346 ok(my $cf = RT::CustomField->new($RT::SystemUser));
347 $cf->Load(1);
348 ok($cf->Id == 1);
349 ok(my ($val,$msg)  = $cf->AddValue(Name => 'foo' , Description => 'TestCFValue', SortOrder => '6'));
350 ok($val != 0);
351 ok (my ($delval, $delmsg) = $cf->DeleteValue($val));
352 ok ($delval,"Deleting a cf value: $delmsg");
353
354 =end testing
355
356 =cut
357
358 sub AddValue {
359         my $self = shift;
360         my %args = ( Name => undef,
361                      Description => undef,
362                      SortOrder => undef,
363                      @_ );
364
365     unless ($self->CurrentUserHasRight('AdminCustomField')) {
366         return (0, $self->loc('Permission Denied'));
367     }
368
369     # allow zero value
370     if ( !defined $args{'Name'} || $args{'Name'} eq '' ) {
371         return(0, $self->loc("Can't add a custom field value without a name"));
372     }
373         my $newval = RT::CustomFieldValue->new($self->CurrentUser);
374         return($newval->Create(
375                      CustomField => $self->Id,
376              Name =>$args{'Name'},
377              Description => ($args{'Description'} || ''),
378              SortOrder => ($args{'SortOrder'} || '0')
379         ));    
380 }
381
382
383 # }}}
384
385 # {{{ DeleteValue
386
387 =head2 DeleteValue ID
388
389 Deletes a value from this custom field by id. 
390
391 Does not remove this value for any article which has had it selected    
392
393 =cut
394
395 sub DeleteValue {
396         my $self = shift;
397     my $id = shift;
398     unless ($self->CurrentUserHasRight('AdminCustomField')) {
399         return (0, $self->loc('Permission Denied'));
400     }
401
402         my $val_to_del = RT::CustomFieldValue->new($self->CurrentUser);
403         $val_to_del->Load($id);
404         unless ($val_to_del->Id) {
405                 return (0, $self->loc("Couldn't find that value"));
406         }
407         unless ($val_to_del->CustomField == $self->Id) {
408                 return (0, $self->loc("That is not a value for this custom field"));
409         }
410
411         my $retval = $val_to_del->Delete();
412     if ($retval) {
413         return ($retval, $self->loc("Custom field value deleted"));
414     } else {
415         return(0, $self->loc("Custom field value could not be deleted"));
416     }
417 }
418
419 # }}}
420
421 # {{{ Values
422
423 =head2 Values FIELD
424
425 Return a CustomFieldeValues object of all acceptable values for this Custom Field.
426
427
428 =cut
429
430 *ValuesObj = \&Values;
431
432 sub Values {
433     my $self = shift;
434
435     my $cf_values = RT::CustomFieldValues->new($self->CurrentUser);
436     # if the user has no rights, return an empty object
437     if ($self->id && $self->CurrentUserHasRight( 'SeeCustomField') ) {
438         $cf_values->LimitToCustomField($self->Id);
439     }
440     return ($cf_values);
441 }
442
443 # }}}
444
445 # }}}
446
447 # {{{ Ticket related routines
448
449 # {{{ ValuesForTicket
450
451 =head2 ValuesForTicket TICKET
452
453 Returns a RT::ObjectCustomFieldValues object of this Field's values for TICKET.
454 TICKET is a ticket id.
455
456 This is deprecated -- use ValuesForObject instead.
457
458
459 =cut
460
461 sub ValuesForTicket {
462         my $self = shift;
463     my $ticket_id = shift;
464     
465     $RT::Logger->debug( ref($self) . " -> ValuesForTicket deprecated in favor of ValuesForObject at (". join(":",caller).")"); 
466     my $ticket = RT::Ticket->new($self->CurrentUser);
467     $ticket->Load($ticket_id);
468
469     return $self->ValuesForObject($ticket);
470 }
471
472 # }}}
473
474 # {{{ AddValueForTicket
475
476 =head2 AddValueForTicket HASH
477
478 Adds a custom field value for a ticket. Takes a param hash of Ticket and Content
479
480 This is deprecated -- use AddValueForObject instead.
481
482 =cut
483
484 sub AddValueForTicket {
485         my $self = shift;
486         my %args = ( Ticket => undef,
487                  Content => undef,
488                      @_ );
489     $RT::Logger->debug( ref($self) . " -> AddValueForTicket deprecated in favor of AddValueForObject at (". join(":",caller).")");
490
491
492     my $ticket = RT::Ticket->new($self->CurrentUser);
493     $ticket->Load($args{'Ticket'});
494     return($self->AddValueForObject(Content => $args{'Content'}, Object => $ticket,@_));
495
496 }
497
498
499 # }}}
500
501 # {{{ DeleteValueForTicket
502
503 =head2 DeleteValueForTicket HASH
504
505 Adds a custom field value for a ticket. Takes a param hash of Ticket and Content
506
507 This is deprecated -- use DeleteValueForObject instead.
508
509 =cut
510
511 sub DeleteValueForTicket {
512         my $self = shift;
513         my %args = ( Ticket => undef,
514                  Content => undef,
515                      @_ );
516
517     $RT::Logger->debug( ref($self) . " -> DeleteValueForTicket deprecated in favor of DeleteValueForObject at (". join(":",caller).")"); 
518
519
520     my $ticket = RT::Ticket->new($self->CurrentUser);
521     $ticket->load($args{'Ticket'});
522     return ($self->DeleteValueForObject(Object => $ticket, Content => $args{'Content'}, @_));
523
524 }
525
526 # }}}
527 # }}}
528
529
530 =head2 ValidateQueue Queue
531
532 Make sure that the queue specified is a valid queue name
533
534 =cut
535
536 sub ValidateQueue {
537     my $self = shift;
538     my $id = shift;
539
540     if ($id eq '0') { # 0 means "Global" null would _not_ be ok.
541         return (1); 
542     }
543
544     my $q = RT::Queue->new($RT::SystemUser);
545     $q->Load($id);
546     unless ($q->id) {
547         return undef;
548     }
549     return (1);
550
551
552 }
553
554
555 # {{{ Types
556
557 =head2 Types 
558
559 Retuns an array of the types of CustomField that are supported
560
561 =cut
562
563 sub Types {
564         return (keys %FieldTypes);
565 }
566
567 # }}}
568
569
570 =head2 FriendlyType [TYPE, MAX_VALUES]
571
572 Returns a localized human-readable version of the custom field type.
573 If a custom field type is specified as the parameter, the friendly type for that type will be returned
574
575 =cut
576
577 sub FriendlyType {
578     my $self = shift;
579
580     my $type = @_ ? shift : $self->Type;
581     my $max  = @_ ? shift : $self->MaxValues;
582
583     if (my $friendly_type = $FieldTypes{$type}[$max>2 ? 2 : $max]) {
584         return ( $self->loc( $friendly_type, $max ) );
585     }
586     else {
587         return ( $self->loc( $type ) );
588     }
589 }
590
591 sub FriendlyTypeComposite {
592     my $self = shift;
593     my $composite = shift || $self->TypeComposite;
594     return $self->FriendlyType(split(/-/, $composite, 2));
595 }
596
597
598 =head2 ValidateType TYPE
599
600 Takes a single string. returns true if that string is a value
601 type of custom field
602
603 =begin testing
604
605 ok(my $cf = RT::CustomField->new($RT::SystemUser));
606 ok($cf->ValidateType('SelectSingle'));
607 ok($cf->ValidateType('SelectMultiple'));
608 ok(!$cf->ValidateType('SelectFooMultiple'));
609
610 =end testing
611
612 =cut
613
614 sub ValidateType {
615     my $self = shift;
616     my $type = shift;
617
618     if ($type =~ s/(?:Single|Multiple)$//) {
619         $RT::Logger->warning( "Prefix 'Single' and 'Multiple' to Type deprecated, use MaxValues instead at (". join(":",caller).")");
620     }
621
622     if( $FieldTypes{$type}) {
623         return(1);
624     }
625     else {
626         return undef;
627     }
628 }
629
630
631 sub SetType {
632     my $self = shift;
633     my $type = shift;
634     if ($type =~ s/(?:(Single)|Multiple)$//) {
635         $RT::Logger->warning("'Single' and 'Multiple' on SetType deprecated, use SetMaxValues instead at (". join(":",caller).")");
636         $self->SetMaxValues($1 ? 1 : 0);
637     }
638     $self->SUPER::SetType($type);
639 }
640
641 # {{{ SingleValue
642
643 =head2 SingleValue
644
645 Returns true if this CustomField only accepts a single value. 
646 Returns false if it accepts multiple values
647
648 =cut
649
650 sub SingleValue {
651     my $self = shift;
652     if ($self->MaxValues == 1) {
653         return 1;
654     } 
655     else {
656         return undef;
657     }
658 }
659
660 sub UnlimitedValues {
661     my $self = shift;
662     if ($self->MaxValues == 0) {
663         return 1;
664     } 
665     else {
666         return undef;
667     }
668 }
669
670 # }}}
671
672 # {{{ sub CurrentUserHasRight
673
674 =head2 CurrentUserHasRight RIGHT
675
676 Helper function to call the custom field's queue's CurrentUserHasRight with the passed in args.
677
678 =cut
679
680 sub CurrentUserHasRight {
681     my $self  = shift;
682     my $right = shift;
683
684     return $self->CurrentUser->HasRight(
685         Object => $self,
686         Right  => $right,
687     );
688 }
689
690 # }}}
691
692 # {{{ sub _Set
693
694 sub _Set {
695     my $self = shift;
696
697     unless ( $self->CurrentUserHasRight('AdminCustomField') ) {
698         return ( 0, $self->loc('Permission Denied') );
699     }
700     return ( $self->SUPER::_Set(@_) );
701
702 }
703
704 # }}}
705
706 # {{{ sub _Value 
707
708 =head2 _Value
709
710 Takes the name of a table column.
711 Returns its value as a string, if the user passes an ACL check
712
713 =cut
714
715 sub _Value {
716
717     my $self  = shift;
718     my $field = shift;
719
720     # we need to do the rights check
721     unless ( $self->id && $self->CurrentUserHasRight( 'SeeCustomField') ) {
722             return (undef);
723     }
724     return ( $self->__Value($field) );
725
726 }
727
728 # }}}
729 # {{{ sub SetDisabled
730
731 =head2 SetDisabled
732
733 Takes a boolean.
734 1 will cause this custom field to no longer be avaialble for tickets.
735 0 will re-enable this queue
736
737 =cut
738
739 # }}}
740
741 sub Queue {
742     $RT::Logger->debug( ref($_[0]) . " -> Queue deprecated at (". join(":",caller).")");
743     
744     return 0;
745 }
746
747 sub SetQueue {
748     $RT::Logger->debug( ref($_[0]) . " -> SetQueue deprecated at (". join(":",caller).")");
749
750     return 0;
751 }
752
753 sub QueueObj {
754     $RT::Logger->debug( ref($_[0]) . " -> QueueObj deprecated at (". join(":",caller).")");
755
756     return undef;
757 }
758
759 =head2 SetTypeComposite
760
761 Set this custom field's type and maximum values as a composite value
762
763
764 =cut
765
766 sub SetTypeComposite {
767     my $self = shift;
768     my $composite = shift;
769     my ($type, $max_values) = split(/-/, $composite, 2);
770     $self->SetType($type);
771     $self->SetMaxValues($max_values);
772 }
773
774 =head2 SetLookupType
775
776 Autrijus: care to doc how LookupTypes work?
777
778 =cut
779
780 sub SetLookupType {
781     my $self = shift;
782     my $lookup = shift;
783     if ($lookup ne $self->LookupType) {
784         # Okay... We need to invalidate our existing relationships
785         my $ObjectCustomFields = RT::ObjectCustomFields->new($self->CurrentUser);
786         $ObjectCustomFields->LimitToCustomField($self->Id);
787         $_->Delete foreach @{$ObjectCustomFields->ItemsArrayRef};
788     }
789     $self->SUPER::SetLookupType($lookup);
790 }
791
792 =head2 TypeComposite
793
794 Returns a composite value composed of this object's type and maximum values
795
796 =cut
797
798
799 sub TypeComposite {
800     my $self = shift;
801     join('-', $self->Type, $self->MaxValues);
802 }
803
804 =head2 TypeComposites
805
806 Returns an array of all possible composite values for custom fields.
807
808 =cut
809
810 sub TypeComposites {
811     my $self = shift;
812     return grep !/Text-0/, map { ("$_-1", "$_-0") } $self->Types;
813 }
814
815 =head2 LookupTypes
816
817 Returns an array of LookupTypes available
818
819 =cut
820
821
822 sub LookupTypes {
823     my $self = shift;
824     return keys %FRIENDLY_OBJECT_TYPES;
825 }
826
827 my @FriendlyObjectTypes = (
828     "[_1] objects",                 # loc
829     "[_1]'s [_2] objects",          # loc
830     "[_1]'s [_2]'s [_3] objects",   # loc
831 );
832
833 =head2 FriendlyTypeLookup
834
835 =cut
836
837 sub FriendlyLookupType {
838     my $self = shift;
839     my $lookup = shift || $self->LookupType;
840    
841     return ($self->loc( $FRIENDLY_OBJECT_TYPES{$lookup} ))
842                    if (defined  $FRIENDLY_OBJECT_TYPES{$lookup} );
843
844     my @types = map { s/^RT::// ? $self->loc($_) : $_ }
845       grep { defined and length }
846       split( /-/, $lookup )
847       or return;
848     return ( $self->loc( $FriendlyObjectTypes[$#types], @types ) );
849 }
850
851
852 =head2 AddToObject OBJECT
853
854 Add this custom field as a custom field for a single object, such as a queue or group.
855
856 Takes an object 
857
858 =cut
859
860
861 sub AddToObject {
862     my $self  = shift;
863     my $object = shift;
864     my $id = $object->Id || 0;
865
866     unless (index($self->LookupType, ref($object)) == 0) {
867         return ( 0, $self->loc('Lookup type mismatch') );
868     }
869
870     unless ( $object->CurrentUserHasRight('AssignCustomFields') ) {
871         return ( 0, $self->loc('Permission Denied') );
872     }
873
874     my $ObjectCF = RT::ObjectCustomField->new( $self->CurrentUser );
875
876     $ObjectCF->LoadByCols( ObjectId => $id, CustomField => $self->Id );
877     if ( $ObjectCF->Id ) {
878         return ( 0, $self->loc("That is already the current value") );
879     }
880     my ( $oid, $msg ) =
881       $ObjectCF->Create( ObjectId => $id, CustomField => $self->Id );
882
883     return ( $oid, $msg );
884 }
885
886
887 =head2 RemoveFromObject OBJECT
888
889 Remove this custom field  for a single object, such as a queue or group.
890
891 Takes an object 
892
893 =cut
894
895
896 sub RemoveFromObject {
897     my $self = shift;
898     my $object = shift;
899     my $id = $object->Id || 0;
900
901     unless (index($self->LookupType, ref($object)) == 0) {
902         return ( 0, $self->loc('Object type mismatch') );
903     }
904
905     unless ( $object->CurrentUserHasRight('AssignCustomFields') ) {
906         return ( 0, $self->loc('Permission Denied') );
907     }
908
909     my $ObjectCF = RT::ObjectCustomField->new( $self->CurrentUser );
910
911     $ObjectCF->LoadByCols( ObjectId => $id, CustomField => $self->Id );
912     unless ( $ObjectCF->Id ) {
913         return ( 0, $self->loc("This custom field does not apply to that object") );
914     }
915     # XXX: Delete doesn't return anything
916     my ( $oid, $msg ) = $ObjectCF->Delete;
917
918     return ( $oid, $msg );
919 }
920
921 # {{{ AddValueForObject
922
923 =head2 AddValueForObject HASH
924
925 Adds a custom field value for a record object of some kind. 
926 Takes a param hash of 
927
928 Required:
929
930     Object
931     Content
932
933 Optional:
934
935     LargeContent
936     ContentType
937
938 =cut
939
940 sub AddValueForObject {
941     my $self = shift;
942     my %args = (
943         Object       => undef,
944         Content      => undef,
945         LargeContent => undef,
946         ContentType  => undef,
947         @_
948     );
949     my $obj = $args{'Object'} or return;
950
951     unless ( $self->CurrentUserHasRight('ModifyCustomField') ) {
952         return ( 0, $self->loc('Permission Denied') );
953     }
954
955     $RT::Handle->BeginTransaction;
956
957     my $current_values = $self->ValuesForObject($obj);
958
959     if ( $self->MaxValues ) {
960         my $extra_values = ( $current_values->Count + 1 ) - $self->MaxValues;
961
962         # (The +1 is for the new value we're adding)
963
964         # If we have a set of current values and we've gone over the maximum
965         # allowed number of values, we'll need to delete some to make room.
966         # which former values are blown away is not guaranteed
967
968         while ($extra_values) {
969             my $extra_item = $current_values->Next;
970
971             unless ( $extra_item->id ) {
972                 $RT::Logger->crit(
973 "We were just asked to delete a custom fieldvalue that doesn't exist!"
974                 );
975                 $RT::Handle->Rollback();
976                 return (undef);
977             }
978             $extra_item->Delete;
979             $extra_values--;
980
981         }
982     }
983     my $newval = RT::ObjectCustomFieldValue->new( $self->CurrentUser );
984     my $val    = $newval->Create(
985         ObjectType   => ref($obj),
986         ObjectId     => $obj->Id,
987         Content      => $args{'Content'},
988         LargeContent => $args{'LargeContent'},
989         ContentType  => $args{'ContentType'},
990         CustomField  => $self->Id
991     );
992
993     unless ($val) {
994         $RT::Handle->Rollback();
995         return ($val);
996     }
997
998     $RT::Handle->Commit();
999     return ($val);
1000
1001 }
1002
1003 # }}}
1004
1005 # {{{ DeleteValueForObject
1006
1007 =head2 DeleteValueForObject HASH
1008
1009 Deletes a custom field value for a ticket. Takes a param hash of Object and Content
1010
1011 Returns a tuple of (STATUS, MESSAGE). If the call succeeded, the STATUS is true. otherwise it's false
1012
1013 =cut
1014
1015 sub DeleteValueForObject {
1016     my $self = shift;
1017     my %args = ( Object => undef,
1018                  Content => undef,
1019                  Id => undef,
1020                      @_ );
1021
1022
1023     unless ($self->CurrentUserHasRight('ModifyCustomField')) {
1024         return (0, $self->loc('Permission Denied'));
1025     }
1026
1027     my $oldval = RT::ObjectCustomFieldValue->new($self->CurrentUser);
1028
1029     if (my $id = $args{'Id'}) {
1030         $oldval->Load($id);
1031     }
1032     unless ($oldval->id) { 
1033         $oldval->LoadByObjectContentAndCustomField(
1034             Object => $args{'Object'}, 
1035             Content =>  $args{'Content'}, 
1036             CustomField => $self->Id,
1037         );
1038     }
1039
1040
1041     # check ot make sure we found it
1042     unless ($oldval->Id) {
1043         return(0, $self->loc("Custom field value [_1] could not be found for custom field [_2]", $args{'Content'}, $self->Name));
1044     }
1045     # delete it
1046
1047     my $ret = $oldval->Delete();
1048     unless ($ret) {
1049         return(0, $self->loc("Custom field value could not be found"));
1050     }
1051     return($oldval->Id, $self->loc("Custom field value deleted"));
1052 }
1053
1054
1055 =head2 ValuesForObject OBJECT
1056
1057 Return an RT::ObjectCustomFieldValues object containing all of this custom field's values for OBJECT 
1058
1059 =cut
1060
1061 sub ValuesForObject {
1062         my $self = shift;
1063     my $object = shift;
1064
1065         my $values = new RT::ObjectCustomFieldValues($self->CurrentUser);
1066         unless ($self->CurrentUserHasRight('SeeCustomField')) {
1067         # Return an empty object if they have no rights to see
1068         return ($values);
1069     }
1070         
1071         
1072         $values->LimitToCustomField($self->Id);
1073         $values->LimitToEnabled();
1074     $values->LimitToObject($object);
1075
1076         return ($values);
1077 }
1078
1079
1080 =head2 _ForObjectType PATH FRIENDLYNAME
1081
1082 Tell RT that a certain object accepts custom fields
1083
1084 Examples:
1085
1086     'RT::Queue-RT::Ticket'                 => "Tickets",                # loc
1087     'RT::Queue-RT::Ticket-RT::Transaction' => "Ticket Transactions",    # loc
1088     'RT::User'                             => "Users",                  # loc
1089     'RT::Group'                            => "Groups",                 # loc
1090
1091 This is a class method. 
1092
1093 =cut
1094
1095 sub _ForObjectType {
1096     my $self = shift;
1097     my $path = shift;
1098     my $friendly_name = shift;
1099
1100     $FRIENDLY_OBJECT_TYPES{$path} = $friendly_name;
1101
1102 }
1103
1104 # }}}
1105
1106 1;