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