This commit was generated by cvs2svn to compensate for changes in r4407,
[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     unless ($args{'Name'}) {
370         return(0, $self->loc("Can't add a custom field value without a name"));
371     }
372         my $newval = RT::CustomFieldValue->new($self->CurrentUser);
373         return($newval->Create(
374                      CustomField => $self->Id,
375              Name =>$args{'Name'},
376              Description => ($args{'Description'} || ''),
377              SortOrder => ($args{'SortOrder'} || '0')
378         ));    
379 }
380
381
382 # }}}
383
384 # {{{ DeleteValue
385
386 =head2 DeleteValue ID
387
388 Deletes a value from this custom field by id. 
389
390 Does not remove this value for any article which has had it selected    
391
392 =cut
393
394 sub DeleteValue {
395         my $self = shift;
396     my $id = shift;
397     unless ($self->CurrentUserHasRight('AdminCustomField')) {
398         return (0, $self->loc('Permission Denied'));
399     }
400
401         my $val_to_del = RT::CustomFieldValue->new($self->CurrentUser);
402         $val_to_del->Load($id);
403         unless ($val_to_del->Id) {
404                 return (0, $self->loc("Couldn't find that value"));
405         }
406         unless ($val_to_del->CustomField == $self->Id) {
407                 return (0, $self->loc("That is not a value for this custom field"));
408         }
409
410         my $retval = $val_to_del->Delete();
411     if ($retval) {
412         return ($retval, $self->loc("Custom field value deleted"));
413     } else {
414         return(0, $self->loc("Custom field value could not be deleted"));
415     }
416 }
417
418 # }}}
419
420 # {{{ Values
421
422 =head2 Values FIELD
423
424 Return a CustomFieldeValues object of all acceptable values for this Custom Field.
425
426
427 =cut
428
429 *ValuesObj = \&Values;
430
431 sub Values {
432     my $self = shift;
433
434     my $cf_values = RT::CustomFieldValues->new($self->CurrentUser);
435     # if the user has no rights, return an empty object
436     if ($self->id && $self->CurrentUserHasRight( 'SeeCustomField') ) {
437         $cf_values->LimitToCustomField($self->Id);
438     }
439     return ($cf_values);
440 }
441
442 # }}}
443
444 # }}}
445
446 # {{{ Ticket related routines
447
448 # {{{ ValuesForTicket
449
450 =head2 ValuesForTicket TICKET
451
452 Returns a RT::ObjectCustomFieldValues object of this Field's values for TICKET.
453 TICKET is a ticket id.
454
455 This is deprecated -- use ValuesForObject instead.
456
457
458 =cut
459
460 sub ValuesForTicket {
461         my $self = shift;
462     my $ticket_id = shift;
463     
464     $RT::Logger->debug( ref($self) . " -> ValuesForTicket deprecated in favor of ValuesForObject"); 
465     my $ticket = RT::Ticket->new($self->CurrentUser);
466     $ticket->Load($ticket_id);
467
468     return $self->ValuesForObject($ticket);
469 }
470
471 # }}}
472
473 # {{{ AddValueForTicket
474
475 =head2 AddValueForTicket HASH
476
477 Adds a custom field value for a ticket. Takes a param hash of Ticket and Content
478
479 This is deprecated -- use AddValueForObject instead.
480
481 =cut
482
483 sub AddValueForTicket {
484         my $self = shift;
485         my %args = ( Ticket => undef,
486                  Content => undef,
487                      @_ );
488     $RT::Logger->debug( ref($self) . " -> AddValueForTicket deprecated in favor of AddValueForObject"); 
489
490
491     my $ticket = RT::Ticket->new($self->CurrentUser);
492     $ticket->Load($args{'Ticket'});
493     return($self->AddValueForObject(Content => $args{'Content'}, Object => $ticket,@_));
494
495 }
496
497
498 # }}}
499
500 # {{{ DeleteValueForTicket
501
502 =head2 DeleteValueForTicket HASH
503
504 Adds a custom field value for a ticket. Takes a param hash of Ticket and Content
505
506 This is deprecated -- use DeleteValueForObject instead.
507
508 =cut
509
510 sub DeleteValueForTicket {
511         my $self = shift;
512         my %args = ( Ticket => undef,
513                  Content => undef,
514                      @_ );
515
516     $RT::Logger->debug( ref($self) . " -> DeleteValueForTicket deprecated in favor of DeleteValueForObject"); 
517
518
519     my $ticket = RT::Ticket->new($self->CurrentUser);
520     $ticket->load($args{'Ticket'});
521     return ($self->DeleteValueForObject(Object => $ticket, Content => $args{'Content'}, @_));
522
523 }
524
525 # }}}
526 # }}}
527
528
529 =head2 ValidateQueue Queue
530
531 Make sure that the queue specified is a valid queue name
532
533 =cut
534
535 sub ValidateQueue {
536     my $self = shift;
537     my $id = shift;
538
539     if ($id eq '0') { # 0 means "Global" null would _not_ be ok.
540         return (1); 
541     }
542
543     my $q = RT::Queue->new($RT::SystemUser);
544     $q->Load($id);
545     unless ($q->id) {
546         return undef;
547     }
548     return (1);
549
550
551 }
552
553
554 # {{{ Types
555
556 =head2 Types 
557
558 Retuns an array of the types of CustomField that are supported
559
560 =cut
561
562 sub Types {
563         return (keys %FieldTypes);
564 }
565
566 # }}}
567
568
569 =head2 FriendlyType [TYPE, MAX_VALUES]
570
571 Returns a localized human-readable version of the custom field type.
572 If a custom field type is specified as the parameter, the friendly type for that type will be returned
573
574 =cut
575
576 sub FriendlyType {
577     my $self = shift;
578
579     my $type = @_ ? shift : $self->Type;
580     my $max  = @_ ? shift : $self->MaxValues;
581
582     if (my $friendly_type = $FieldTypes{$type}[$max>2 ? 2 : $max]) {
583         return ( $self->loc( $friendly_type, $max ) );
584     }
585     else {
586         return ( $self->loc( $type ) );
587     }
588 }
589
590 sub FriendlyTypeComposite {
591     my $self = shift;
592     my $composite = shift || $self->TypeComposite;
593     return $self->FriendlyType(split(/-/, $composite, 2));
594 }
595
596
597 =head2 ValidateType TYPE
598
599 Takes a single string. returns true if that string is a value
600 type of custom field
601
602 =begin testing
603
604 ok(my $cf = RT::CustomField->new($RT::SystemUser));
605 ok($cf->ValidateType('SelectSingle'));
606 ok($cf->ValidateType('SelectMultiple'));
607 ok(!$cf->ValidateType('SelectFooMultiple'));
608
609 =end testing
610
611 =cut
612
613 sub ValidateType {
614     my $self = shift;
615     my $type = shift;
616
617     if ($type =~ s/(?:Single|Multiple)$//) {
618         $RT::Logger->warning( "Prefix 'Single' and 'Multiple' to Type deprecated, use MaxValues instead");
619     }
620
621     if( $FieldTypes{$type}) {
622         return(1);
623     }
624     else {
625         return undef;
626     }
627 }
628
629
630 sub SetType {
631     my $self = shift;
632     my $type = shift;
633     if ($type =~ s/(?:(Single)|Multiple)$//) {
634         warn "'Single' and 'Multiple' on SetType deprecated, use SetMaxValues instead";
635         $self->SetMaxValues($1 ? 1 : 0);
636     }
637     $self->SUPER::SetType($type);
638 }
639
640 # {{{ SingleValue
641
642 =head2 SingleValue
643
644 Returns true if this CustomField only accepts a single value. 
645 Returns false if it accepts multiple values
646
647 =cut
648
649 sub SingleValue {
650     my $self = shift;
651     if ($self->MaxValues == 1) {
652         return 1;
653     } 
654     else {
655         return undef;
656     }
657 }
658
659 sub UnlimitedValues {
660     my $self = shift;
661     if ($self->MaxValues == 0) {
662         return 1;
663     } 
664     else {
665         return undef;
666     }
667 }
668
669 # }}}
670
671 # {{{ sub CurrentUserHasRight
672
673 =head2 CurrentUserHasRight RIGHT
674
675 Helper function to call the custom field's queue's CurrentUserHasRight with the passed in args.
676
677 =cut
678
679 sub CurrentUserHasRight {
680     my $self  = shift;
681     my $right = shift;
682
683     return $self->CurrentUser->HasRight(
684         Object => $self,
685         Right  => $right,
686     );
687 }
688
689 # }}}
690
691 # {{{ sub _Set
692
693 sub _Set {
694     my $self = shift;
695
696     unless ( $self->CurrentUserHasRight('AdminCustomField') ) {
697         return ( 0, $self->loc('Permission Denied') );
698     }
699     return ( $self->SUPER::_Set(@_) );
700
701 }
702
703 # }}}
704
705 # {{{ sub _Value 
706
707 =head2 _Value
708
709 Takes the name of a table column.
710 Returns its value as a string, if the user passes an ACL check
711
712 =cut
713
714 sub _Value {
715
716     my $self  = shift;
717     my $field = shift;
718
719     # we need to do the rights check
720     unless ( $self->id && $self->CurrentUserHasRight( 'SeeCustomField') ) {
721             return (undef);
722     }
723     return ( $self->__Value($field) );
724
725 }
726
727 # }}}
728 # {{{ sub SetDisabled
729
730 =head2 SetDisabled
731
732 Takes a boolean.
733 1 will cause this custom field to no longer be avaialble for tickets.
734 0 will re-enable this queue
735
736 =cut
737
738 # }}}
739
740 sub Queue {
741     $RT::Logger->debug( ref($_[0]) . " -> Queue deprecated");
742     
743     return 0;
744 }
745
746 sub SetQueue {
747     $RT::Logger->debug( ref($_[0]) . " -> SetQueue deprecated");
748
749     return 0;
750 }
751
752 sub QueueObj {
753     $RT::Logger->debug( ref($_[0]) . " -> QueueObj deprecated");
754
755     return undef;
756 }
757
758 =head2 SetTypeComposite
759
760 Set this custom field's type and maximum values as a composite value
761
762
763 =cut
764
765 sub SetTypeComposite {
766     my $self = shift;
767     my $composite = shift;
768     my ($type, $max_values) = split(/-/, $composite, 2);
769     $self->SetType($type);
770     $self->SetMaxValues($max_values);
771 }
772
773 =head2 SetLookupType
774
775 Autrijus: care to doc how LookupTypes work?
776
777 =cut
778
779 sub SetLookupType {
780     my $self = shift;
781     my $lookup = shift;
782     if ($lookup ne $self->LookupType) {
783         # Okay... We need to invalidate our existing relationships
784         my $ObjectCustomFields = RT::ObjectCustomFields->new($self->CurrentUser);
785         $ObjectCustomFields->LimitToCustomField($self->Id);
786         $_->Delete foreach @{$ObjectCustomFields->ItemsArrayRef};
787     }
788     $self->SUPER::SetLookupType($lookup);
789 }
790
791 =head2 TypeComposite
792
793 Returns a composite value composed of this object's type and maximum values
794
795 =cut
796
797
798 sub TypeComposite {
799     my $self = shift;
800     join('-', $self->Type, $self->MaxValues);
801 }
802
803 =head2 TypeComposites
804
805 Returns an array of all possible composite values for custom fields.
806
807 =cut
808
809 sub TypeComposites {
810     my $self = shift;
811     return grep !/Text-0/, map { ("$_-1", "$_-0") } $self->Types;
812 }
813
814 =head2 LookupTypes
815
816 Returns an array of LookupTypes available
817
818 =cut
819
820
821 sub LookupTypes {
822     my $self = shift;
823     return keys %FRIENDLY_OBJECT_TYPES;
824 }
825
826 my @FriendlyObjectTypes = (
827     "[_1] objects",                 # loc
828     "[_1]'s [_2] objects",          # loc
829     "[_1]'s [_2]'s [_3] objects",   # loc
830 );
831
832 =head2 FriendlyTypeLookup
833
834 =cut
835
836 sub FriendlyLookupType {
837     my $self = shift;
838     my $lookup = shift || $self->LookupType;
839    
840     return ($self->loc( $FRIENDLY_OBJECT_TYPES{$lookup} ))
841                    if (defined  $FRIENDLY_OBJECT_TYPES{$lookup} );
842
843     my @types = map { s/^RT::// ? $self->loc($_) : $_ }
844       grep { defined and length }
845       split( /-/, $lookup )
846       or return;
847     return ( $self->loc( $FriendlyObjectTypes[$#types], @types ) );
848 }
849
850
851 =head2 AddToObject OBJECT
852
853 Add this custom field as a custom field for a single object, such as a queue or group.
854
855 Takes an object 
856
857 =cut
858
859
860 sub AddToObject {
861     my $self  = shift;
862     my $object = shift;
863     my $id = $object->Id || 0;
864
865     unless (index($self->LookupType, ref($object)) == 0) {
866         return ( 0, $self->loc('Lookup type mismatch') );
867     }
868
869     unless ( $object->CurrentUserHasRight('AssignCustomFields') ) {
870         return ( 0, $self->loc('Permission Denied') );
871     }
872
873     my $ObjectCF = RT::ObjectCustomField->new( $self->CurrentUser );
874
875     $ObjectCF->LoadByCols( ObjectId => $id, CustomField => $self->Id );
876     if ( $ObjectCF->Id ) {
877         return ( 0, $self->loc("That is already the current value") );
878     }
879     my ( $id, $msg ) =
880       $ObjectCF->Create( ObjectId => $id, CustomField => $self->Id );
881
882     return ( $id, $msg );
883 }
884
885
886 =head2 RemoveFromObject OBJECT
887
888 Remove this custom field  for a single object, such as a queue or group.
889
890 Takes an object 
891
892 =cut
893
894
895 sub RemoveFromObject {
896     my $self = shift;
897     my $object = shift;
898     my $id = $object->Id || 0;
899
900     unless (index($self->LookupType, ref($object)) == 0) {
901         return ( 0, $self->loc('Object type mismatch') );
902     }
903
904     unless ( $object->CurrentUserHasRight('AssignCustomFields') ) {
905         return ( 0, $self->loc('Permission Denied') );
906     }
907
908     my $ObjectCF = RT::ObjectCustomField->new( $self->CurrentUser );
909
910     $ObjectCF->LoadByCols( ObjectId => $id, CustomField => $self->Id );
911     unless ( $ObjectCF->Id ) {
912         return ( 0, $self->loc("This custom field does not apply to that object") );
913     }
914     my ( $id, $msg ) = $ObjectCF->Delete;
915
916     return ( $id, $msg );
917 }
918
919 # {{{ AddValueForObject
920
921 =head2 AddValueForObject HASH
922
923 Adds a custom field value for a record object of some kind. 
924 Takes a param hash of 
925
926 Required:
927
928     Object
929     Content
930
931 Optional:
932
933     LargeContent
934     ContentType
935
936 =cut
937
938 sub AddValueForObject {
939     my $self = shift;
940     my %args = (
941         Object       => undef,
942         Content      => undef,
943         LargeContent => undef,
944         ContentType  => undef,
945         @_
946     );
947     my $obj = $args{'Object'} or return;
948
949     unless ( $self->CurrentUserHasRight('ModifyCustomField') ) {
950         return ( 0, $self->loc('Permission Denied') );
951     }
952
953     $RT::Handle->BeginTransaction;
954
955     my $current_values = $self->ValuesForObject($obj);
956
957     if ( $self->MaxValues ) {
958         my $extra_values = ( $current_values->Count + 1 ) - $self->MaxValues;
959
960         # (The +1 is for the new value we're adding)
961
962         # If we have a set of current values and we've gone over the maximum
963         # allowed number of values, we'll need to delete some to make room.
964         # which former values are blown away is not guaranteed
965
966         while ($extra_values) {
967             my $extra_item = $current_values->Next;
968
969             unless ( $extra_item->id ) {
970                 $RT::Logger->crit(
971 "We were just asked to delete a custom fieldvalue that doesn't exist!"
972                 );
973                 $RT::Handle->Rollback();
974                 return (undef);
975             }
976             $extra_item->Delete;
977             $extra_values--;
978
979         }
980     }
981     my $newval = RT::ObjectCustomFieldValue->new( $self->CurrentUser );
982     my $val    = $newval->Create(
983         ObjectType   => ref($obj),
984         ObjectId     => $obj->Id,
985         Content      => $args{'Content'},
986         LargeContent => $args{'LargeContent'},
987         ContentType  => $args{'ContentType'},
988         CustomField  => $self->Id
989     );
990
991     unless ($val) {
992         $RT::Handle->Rollback();
993         return ($val);
994     }
995
996     $RT::Handle->Commit();
997     return ($val);
998
999 }
1000
1001 # }}}
1002
1003 # {{{ DeleteValueForObject
1004
1005 =head2 DeleteValueForObject HASH
1006
1007 Deletes a custom field value for a ticket. Takes a param hash of Object and Content
1008
1009 Returns a tuple of (STATUS, MESSAGE). If the call succeeded, the STATUS is true. otherwise it's false
1010
1011 =cut
1012
1013 sub DeleteValueForObject {
1014     my $self = shift;
1015     my %args = ( Object => undef,
1016                  Content => undef,
1017                  Id => undef,
1018                      @_ );
1019
1020
1021     unless ($self->CurrentUserHasRight('ModifyCustomField')) {
1022         return (0, $self->loc('Permission Denied'));
1023     }
1024
1025     my $oldval = RT::ObjectCustomFieldValue->new($self->CurrentUser);
1026
1027     if (my $id = $args{'Id'}) {
1028         $oldval->Load($id);
1029     }
1030     unless ($oldval->id) { 
1031         $oldval->LoadByObjectContentAndCustomField(
1032             Object => $args{'Object'}, 
1033             Content =>  $args{'Content'}, 
1034             CustomField => $self->Id,
1035         );
1036     }
1037
1038
1039     # check ot make sure we found it
1040     unless ($oldval->Id) {
1041         return(0, $self->loc("Custom field value [_1] could not be found for custom field [_2]", $args{'Content'}, $self->Name));
1042     }
1043     # delete it
1044
1045     my $ret = $oldval->Delete();
1046     unless ($ret) {
1047         return(0, $self->loc("Custom field value could not be found"));
1048     }
1049     return($oldval->Id, $self->loc("Custom field value deleted"));
1050 }
1051
1052
1053 =head2 ValuesForObject OBJECT
1054
1055 Return an RT::ObjectCustomFieldValues object containing all of this custom field's values for OBJECT 
1056
1057 =cut
1058
1059 sub ValuesForObject {
1060         my $self = shift;
1061     my $object = shift;
1062
1063         my $values = new RT::ObjectCustomFieldValues($self->CurrentUser);
1064         unless ($self->CurrentUserHasRight('SeeCustomField')) {
1065         # Return an empty object if they have no rights to see
1066         return ($values);
1067     }
1068         
1069         
1070         $values->LimitToCustomField($self->Id);
1071         $values->LimitToEnabled();
1072     $values->LimitToObject($object);
1073
1074         return ($values);
1075 }
1076
1077
1078 =head2 _ForObjectType PATH FRIENDLYNAME
1079
1080 Tell RT that a certain object accepts custom fields
1081
1082 Examples:
1083
1084     'RT::Queue-RT::Ticket'                 => "Tickets",                # loc
1085     'RT::Queue-RT::Ticket-RT::Transaction' => "Ticket Transactions",    # loc
1086     'RT::User'                             => "Users",                  # loc
1087     'RT::Group'                            => "Groups",                 # loc
1088
1089 This is a class method. 
1090
1091 =cut
1092
1093 sub _ForObjectType {
1094     my $self = shift;
1095     my $path = shift;
1096     my $friendly_name = shift;
1097
1098     $FRIENDLY_OBJECT_TYPES{$path} = $friendly_name;
1099
1100 }
1101
1102 # }}}
1103
1104 1;