diff options
Diffstat (limited to 'rt/lib/RT/CustomField.pm')
-rw-r--r-- | rt/lib/RT/CustomField.pm | 1796 |
1 files changed, 1712 insertions, 84 deletions
diff --git a/rt/lib/RT/CustomField.pm b/rt/lib/RT/CustomField.pm index 0582edd5b..095caa52f 100644 --- a/rt/lib/RT/CustomField.pm +++ b/rt/lib/RT/CustomField.pm @@ -2,7 +2,7 @@ # # COPYRIGHT: # -# This software is Copyright (c) 1996-2011 Best Practical Solutions, LLC +# This software is Copyright (c) 1996-2012 Best Practical Solutions, LLC # <sales@bestpractical.com> # # (Except where explicitly superseded by other copyright notices) @@ -46,45 +46,237 @@ # # END BPS TAGGED BLOCK }}} -# Autogenerated by DBIx::SearchBuilder factory (by <jesse@bestpractical.com>) -# WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST. -# -# !! DO NOT EDIT THIS FILE !! -# +package RT::CustomField; use strict; +use warnings; + + + +use base 'RT::Record'; + +sub Table {'CustomFields'} + + +use RT::CustomFieldValues; +use RT::ObjectCustomFields; +use RT::ObjectCustomFieldValues; + +our %FieldTypes = ( + Select => { + sort_order => 10, + selection_type => 1, + + labels => [ 'Select multiple values', # loc + 'Select one value', # loc + 'Select up to [_1] values', # loc + ], + + render_types => { + multiple => [ + + # Default is the first one + 'Select box', # loc + 'List', # loc + ], + single => [ 'Select box', # loc + 'Dropdown', # loc + 'List', # loc + ] + }, + + }, + Freeform => { + sort_order => 20, + selection_type => 0, + + labels => [ 'Enter multiple values', # loc + 'Enter one value', # loc + 'Enter up to [_1] values', # loc + ] + }, + Text => { + sort_order => 30, + selection_type => 0, + labels => [ + 'Fill in multiple text areas', # loc + 'Fill in one text area', # loc + 'Fill in up to [_1] text areas', # loc + ] + }, + Wikitext => { + sort_order => 40, + selection_type => 0, + labels => [ + 'Fill in multiple wikitext areas', # loc + 'Fill in one wikitext area', # loc + 'Fill in up to [_1] wikitext areas', # loc + ] + }, + + Image => { + sort_order => 50, + selection_type => 0, + labels => [ + 'Upload multiple images', # loc + 'Upload one image', # loc + 'Upload up to [_1] images', # loc + ] + }, + Binary => { + sort_order => 60, + selection_type => 0, + labels => [ + 'Upload multiple files', # loc + 'Upload one file', # loc + 'Upload up to [_1] files', # loc + ] + }, + + Combobox => { + sort_order => 70, + selection_type => 1, + labels => [ + 'Combobox: Select or enter multiple values', # loc + 'Combobox: Select or enter one value', # loc + 'Combobox: Select or enter up to [_1] values', # loc + ] + }, + Autocomplete => { + sort_order => 80, + selection_type => 1, + labels => [ + 'Enter multiple values with autocompletion', # loc + 'Enter one value with autocompletion', # loc + 'Enter up to [_1] values with autocompletion', # loc + ] + }, + + Date => { + sort_order => 90, + selection_type => 0, + labels => [ + 'Select multiple dates', # loc + 'Select date', # loc + 'Select up to [_1] dates', # loc + ] + }, + DateTime => { + sort_order => 100, + selection_type => 0, + labels => [ + 'Select multiple datetimes', # loc + 'Select datetime', # loc + 'Select up to [_1] datetimes', # loc + ] + }, + TimeValue => [ + 'Enter multiple time values (UNSUPPORTED)', + 'Enter a time value', + 'Enter [_1] time values (UNSUPPORTED)', + ], + + IPAddress => { + sort_order => 110, + selection_type => 0, + + labels => [ 'Enter multiple IP addresses', # loc + 'Enter one IP address', # loc + 'Enter up to [_1] IP addresses', # loc + ] + }, + IPAddressRange => { + sort_order => 120, + selection_type => 0, + + labels => [ 'Enter multiple IP address ranges', # loc + 'Enter one IP address range', # loc + 'Enter up to [_1] IP address ranges', # loc + ] + }, +); -=head1 NAME +our %FRIENDLY_OBJECT_TYPES = (); -RT::CustomField +RT::CustomField->_ForObjectType( 'RT::Queue-RT::Ticket' => "Tickets", ); #loc +RT::CustomField->_ForObjectType( + 'RT::Queue-RT::Ticket-RT::Transaction' => "Ticket Transactions", ); #loc +RT::CustomField->_ForObjectType( 'RT::User' => "Users", ); #loc +RT::CustomField->_ForObjectType( 'RT::Queue' => "Queues", ); #loc +RT::CustomField->_ForObjectType( 'RT::Group' => "Groups", ); #loc +our $RIGHTS = { + SeeCustomField => 'View custom fields', # loc_pair + AdminCustomField => 'Create, modify and delete custom fields', # loc_pair + AdminCustomFieldValues => 'Create, modify and delete custom fields values', # loc_pair + ModifyCustomField => 'Add, modify and delete custom field values for objects' # loc_pair +}; -=head1 SYNOPSIS +our $RIGHT_CATEGORIES = { + SeeCustomField => 'General', + AdminCustomField => 'Admin', + AdminCustomFieldValues => 'Admin', + ModifyCustomField => 'Staff', +}; -=head1 DESCRIPTION +# Tell RT::ACE that this sort of object can get acls granted +$RT::ACE::OBJECT_TYPES{'RT::CustomField'} = 1; + +__PACKAGE__->AddRights(%$RIGHTS); +__PACKAGE__->AddRightCategories(%$RIGHT_CATEGORIES); + +=head2 AddRights C<RIGHT>, C<DESCRIPTION> [, ...] -=head1 METHODS +Adds the given rights to the list of possible rights. This method +should be called during server startup, not at runtime. =cut -package RT::CustomField; -use RT::Record; +sub AddRights { + my $self = shift; + my %new = @_; + $RIGHTS = { %$RIGHTS, %new }; + %RT::ACE::LOWERCASERIGHTNAMES = ( %RT::ACE::LOWERCASERIGHTNAMES, + map { lc($_) => $_ } keys %new); +} +sub AvailableRights { + my $self = shift; + return $RIGHTS; +} -use vars qw( @ISA ); -@ISA= qw( RT::Record ); +=head2 RightCategories -sub _Init { - my $self = shift; +Returns a hashref where the keys are rights for this type of object and the +values are the category (General, Staff, Admin) the right falls into. - $self->Table('CustomFields'); - $self->SUPER::_Init(@_); +=cut + +sub RightCategories { + return $RIGHT_CATEGORIES; } +=head2 AddRightCategories C<RIGHT>, C<CATEGORY> [, ...] +Adds the given right and category pairs to the list of right categories. This +method should be called during server startup, not at runtime. +=cut +sub AddRightCategories { + my $self = shift if ref $_[0] or $_[0] eq __PACKAGE__; + my %new = @_; + $RIGHT_CATEGORIES = { %$RIGHT_CATEGORIES, %new }; +} + +=head1 NAME + + RT::CustomField_Overlay - overlay for RT::CustomField + +=head1 DESCRIPTION + +=head1 'CORE' METHODS =head2 Create PARAMHASH @@ -93,53 +285,1466 @@ Create takes a hash of values and creates a row in the database: varchar(200) 'Name'. varchar(200) 'Type'. int(11) 'MaxValues'. - longtext 'Pattern'. + varchar(255) 'Pattern'. smallint(6) 'Repeated'. varchar(255) 'Description'. int(11) 'SortOrder'. varchar(255) 'LookupType'. smallint(6) 'Disabled'. - 'LookupType' is generally the result of either - RT::Ticket->CustomFieldLookupType or RT::Transaction->CustomFieldLookupType +C<LookupType> is generally the result of either +C<RT::Ticket->CustomFieldLookupType> or C<RT::Transaction->CustomFieldLookupType>. =cut +sub Create { + my $self = shift; + my %args = ( + Name => '', + Type => '', + MaxValues => 0, + Pattern => '', + Description => '', + Disabled => 0, + LookupType => '', + Repeated => 0, + LinkValueTo => '', + IncludeContentForValue => '', + @_, + ); + + unless ( $self->CurrentUser->HasRight(Object => $RT::System, Right => 'AdminCustomField') ) { + return (0, $self->loc('Permission Denied')); + } + + if ( $args{TypeComposite} ) { + @args{'Type', 'MaxValues'} = split(/-/, $args{TypeComposite}, 2); + } + elsif ( $args{Type} =~ s/(?:(Single)|Multiple)$// ) { + # old style Type string + $args{'MaxValues'} = $1 ? 1 : 0; + } + $args{'MaxValues'} = int $args{'MaxValues'}; + + if ( !exists $args{'Queue'}) { + # do nothing -- things below are strictly backward compat + } + elsif ( ! $args{'Queue'} ) { + unless ( $self->CurrentUser->HasRight( Object => $RT::System, Right => 'AssignCustomFields') ) { + return ( 0, $self->loc('Permission Denied') ); + } + $args{'LookupType'} = 'RT::Queue-RT::Ticket'; + } + else { + my $queue = RT::Queue->new($self->CurrentUser); + $queue->Load($args{'Queue'}); + unless ($queue->Id) { + return (0, $self->loc("Queue not found")); + } + unless ( $queue->CurrentUserHasRight('AssignCustomFields') ) { + return ( 0, $self->loc('Permission Denied') ); + } + $args{'LookupType'} = 'RT::Queue-RT::Ticket'; + $args{'Queue'} = $queue->Id; + } + + my ($ok, $msg) = $self->_IsValidRegex( $args{'Pattern'} ); + return (0, $self->loc("Invalid pattern: [_1]", $msg)) unless $ok; + + if ( $args{'MaxValues'} != 1 && $args{'Type'} =~ /(text|combobox)$/i ) { + $RT::Logger->debug("Support for 'multiple' Texts or Comboboxes is not implemented"); + $args{'MaxValues'} = 1; + } + + if ( $args{'RenderType'} ||= undef ) { + my $composite = join '-', @args{'Type', 'MaxValues'}; + return (0, $self->loc("This custom field has no Render Types")) + unless $self->HasRenderTypes( $composite ); + + if ( $args{'RenderType'} eq $self->DefaultRenderType( $composite ) ) { + $args{'RenderType'} = undef; + } else { + return (0, $self->loc("Invalid Render Type") ) + unless grep $_ eq $args{'RenderType'}, $self->RenderTypes( $composite ); + } + } + + $args{'ValuesClass'} = undef if ($args{'ValuesClass'} || '') eq 'RT::CustomFieldValues'; + if ( $args{'ValuesClass'} ||= undef ) { + return (0, $self->loc("This Custom Field can not have list of values")) + unless $self->IsSelectionType( $args{'Type'} ); + + unless ( $self->ValidateValuesClass( $args{'ValuesClass'} ) ) { + return (0, $self->loc("Invalid Custom Field values source")); + } + } + + (my $rv, $msg) = $self->SUPER::Create( + Name => $args{'Name'}, + Type => $args{'Type'}, + RenderType => $args{'RenderType'}, + MaxValues => $args{'MaxValues'}, + Pattern => $args{'Pattern'}, + BasedOn => $args{'BasedOn'}, + ValuesClass => $args{'ValuesClass'}, + Description => $args{'Description'}, + Disabled => $args{'Disabled'}, + LookupType => $args{'LookupType'}, + Repeated => $args{'Repeated'}, + ); + + if ($rv) { + if ( exists $args{'LinkValueTo'}) { + $self->SetLinkValueTo($args{'LinkValueTo'}); + } + + if ( exists $args{'IncludeContentForValue'}) { + $self->SetIncludeContentForValue($args{'IncludeContentForValue'}); + } + + if ( exists $args{'UILocation'} ) { + $self->SetUILocation( $args{'UILocation'} ); + } + + return ($rv, $msg) unless exists $args{'Queue'}; + + # Compat code -- create a new ObjectCustomField mapping + my $OCF = RT::ObjectCustomField->new( $self->CurrentUser ); + $OCF->Create( + CustomField => $self->Id, + ObjectId => $args{'Queue'}, + ); + } + + return ($rv, $msg); +} +=head2 Load ID/NAME +Load a custom field. If the value handed in is an integer, load by custom field ID. Otherwise, Load by name. -sub Create { +=cut + +sub Load { + my $self = shift; + my $id = shift || ''; + + if ( $id =~ /^\d+$/ ) { + return $self->SUPER::Load( $id ); + } else { + return $self->LoadByName( Name => $id ); + } +} + + + +=head2 LoadByName (Queue => QUEUEID, Name => NAME) + +Loads the Custom field named NAME. + +Will load a Disabled Custom Field even if there is a non-disabled Custom Field +with the same Name. + +If a Queue parameter is specified, only look for ticket custom fields tied to that Queue. + +If the Queue parameter is '0', look for global ticket custom fields. + +If no queue parameter is specified, look for any and all custom fields with this name. + +BUG/TODO, this won't let you specify that you only want user or group CFs. + +=cut + +# Compatibility for API change after 3.0 beta 1 +*LoadNameAndQueue = \&LoadByName; +# Change after 3.4 beta. +*LoadByNameAndQueue = \&LoadByName; + +sub LoadByName { + my $self = shift; + my %args = ( + Queue => undef, + Name => undef, + @_, + ); + + unless ( defined $args{'Name'} && length $args{'Name'} ) { + $RT::Logger->error("Couldn't load Custom Field without Name"); + return wantarray ? (0, $self->loc("No name provided")) : 0; + } + + # if we're looking for a queue by name, make it a number + if ( defined $args{'Queue'} && $args{'Queue'} =~ /\D/ ) { + my $QueueObj = RT::Queue->new( $self->CurrentUser ); + $QueueObj->Load( $args{'Queue'} ); + $args{'Queue'} = $QueueObj->Id; + } + + # XXX - really naive implementation. Slow. - not really. still just one query + + my $CFs = RT::CustomFields->new( $self->CurrentUser ); + $CFs->SetContextObject( $self->ContextObject ); + my $field = $args{'Name'} =~ /\D/? 'Name' : 'id'; + $CFs->Limit( FIELD => $field, VALUE => $args{'Name'}, CASESENSITIVE => 0); + # Don't limit to queue if queue is 0. Trying to do so breaks + # RT::Group type CFs. + if ( defined $args{'Queue'} ) { + $CFs->LimitToQueue( $args{'Queue'} ); + } + + # When loading by name, we _can_ load disabled fields, but prefer + # non-disabled fields. + $CFs->FindAllRows; + $CFs->OrderByCols( + { FIELD => "Disabled", ORDER => 'ASC' }, + ); + + # We only want one entry. + $CFs->RowsPerPage(1); + + # version before 3.8 just returns 0, so we need to test if wantarray to be + # backward compatible. + return wantarray ? (0, $self->loc("Not found")) : 0 unless my $first = $CFs->First; + + return $self->LoadById( $first->id ); +} + + + + +=head2 Custom field values + +=head3 Values FIELD + +Return a object (collection) of all acceptable values for this Custom Field. +Class of the object can vary and depends on the return value +of the C<ValuesClass> method. + +=cut + +*ValuesObj = \&Values; + +sub Values { + my $self = shift; + + my $class = $self->ValuesClass; + if ( $class ne 'RT::CustomFieldValues') { + eval "require $class" or die "$@"; + } + my $cf_values = $class->new( $self->CurrentUser ); + # if the user has no rights, return an empty object + if ( $self->id && $self->CurrentUserHasRight( 'SeeCustomField') ) { + $cf_values->LimitToCustomField( $self->Id ); + } + return ($cf_values); +} + + +=head3 AddValue HASH + +Create a new value for this CustomField. Takes a paramhash containing the elements Name, Description and SortOrder + +=cut + +sub AddValue { + my $self = shift; + my %args = @_; + + unless ($self->CurrentUserHasRight('AdminCustomField') || $self->CurrentUserHasRight('AdminCustomFieldValues')) { + return (0, $self->loc('Permission Denied')); + } + + # allow zero value + if ( !defined $args{'Name'} || $args{'Name'} eq '' ) { + return (0, $self->loc("Can't add a custom field value without a name")); + } + + my $newval = RT::CustomFieldValue->new( $self->CurrentUser ); + return $newval->Create( %args, CustomField => $self->Id ); +} + + + + +=head3 DeleteValue ID + +Deletes a value from this custom field by id. + +Does not remove this value for any article which has had it selected + +=cut + +sub DeleteValue { + my $self = shift; + my $id = shift; + unless ( $self->CurrentUserHasRight('AdminCustomField') || $self->CurrentUserHasRight('AdminCustomFieldValues') ) { + return (0, $self->loc('Permission Denied')); + } + + my $val_to_del = RT::CustomFieldValue->new( $self->CurrentUser ); + $val_to_del->Load( $id ); + unless ( $val_to_del->Id ) { + return (0, $self->loc("Couldn't find that value")); + } + unless ( $val_to_del->CustomField == $self->Id ) { + return (0, $self->loc("That is not a value for this custom field")); + } + + my $retval = $val_to_del->Delete; + unless ( $retval ) { + return (0, $self->loc("Custom field value could not be deleted")); + } + return ($retval, $self->loc("Custom field value deleted")); +} + + +=head2 ValidateQueue Queue + +Make sure that the name specified is valid + +=cut + +sub ValidateName { + my $self = shift; + my $value = shift; + + return 0 unless length $value; + + return $self->SUPER::ValidateName($value); +} + +=head2 ValidateQueue Queue + +Make sure that the queue specified is a valid queue name + +=cut + +sub ValidateQueue { + my $self = shift; + my $id = shift; + + return undef unless defined $id; + # 0 means "Global" null would _not_ be ok. + return 1 if $id eq '0'; + + my $q = RT::Queue->new( RT->SystemUser ); + $q->Load( $id ); + return undef unless $q->id; + return 1; +} + + + +=head2 Types + +Retuns an array of the types of CustomField that are supported + +=cut + +sub Types { + return (sort {(($FieldTypes{$a}{sort_order}||999) <=> ($FieldTypes{$b}{sort_order}||999)) or ($a cmp $b)} keys %FieldTypes); +} + + +=head2 IsSelectionType + +Retuns a boolean value indicating whether the C<Values> method makes sense +to this Custom Field. + +=cut + +sub IsSelectionType { + my $self = shift; + my $type = @_? shift : $self->Type; + return undef unless $type; + return $FieldTypes{$type}->{selection_type}; +} + + + +=head2 IsExternalValues + +=cut + +sub IsExternalValues { + my $self = shift; + return 0 unless $self->IsSelectionType( @_ ); + return $self->ValuesClass eq 'RT::CustomFieldValues'? 0 : 1; +} + +sub ValuesClass { + my $self = shift; + return $self->_Value( ValuesClass => @_ ) || 'RT::CustomFieldValues'; +} + +sub SetValuesClass { + my $self = shift; + my $class = shift || 'RT::CustomFieldValues'; + + if ( $class eq 'RT::CustomFieldValues' ) { + return $self->_Set( Field => 'ValuesClass', Value => undef, @_ ); + } + + return (0, $self->loc("This Custom Field can not have list of values")) + unless $self->IsSelectionType; + + unless ( $self->ValidateValuesClass( $class ) ) { + return (0, $self->loc("Invalid Custom Field values source")); + } + return $self->_Set( Field => 'ValuesClass', Value => $class, @_ ); +} + +sub ValidateValuesClass { + my $self = shift; + my $class = shift; + + return 1 if !defined $class || $class eq 'RT::CustomFieldValues'; + return 1 if grep $class eq $_, RT->Config->Get('CustomFieldValuesSources'); + return undef; +} + + +=head2 FriendlyType [TYPE, MAX_VALUES] + +Returns a localized human-readable version of the custom field type. +If a custom field type is specified as the parameter, the friendly type for that type will be returned + +=cut + +sub FriendlyType { + my $self = shift; + + my $type = @_ ? shift : $self->Type; + my $max = @_ ? shift : $self->MaxValues; + $max = 0 unless $max; + + if (my $friendly_type = $FieldTypes{$type}->{labels}->[$max>2 ? 2 : $max]) { + return ( $self->loc( $friendly_type, $max ) ); + } + else { + return ( $self->loc( $type ) ); + } +} + +sub FriendlyTypeComposite { my $self = shift; - my %args = ( - Name => '', - Type => '', - MaxValues => '', - Pattern => '', - Repeated => '0', - Description => '', - SortOrder => '0', - LookupType => '', - Disabled => '0', - LinkToValue => '', - IncludeContentForValue => '', - - @_); - $self->SUPER::Create( - Name => $args{'Name'}, - Type => $args{'Type'}, - MaxValues => $args{'MaxValues'}, - Pattern => $args{'Pattern'}, - Repeated => $args{'Repeated'}, - Description => $args{'Description'}, - SortOrder => $args{'SortOrder'}, - LookupType => $args{'LookupType'}, - Disabled => $args{'Disabled'}, - LinkToValue => $args{'LinkToValue'}, - IncludeContentForValue => $args{'IncludeContentForValue'} + my $composite = shift || $self->TypeComposite; + return $self->FriendlyType(split(/-/, $composite, 2)); +} + + +=head2 ValidateType TYPE + +Takes a single string. returns true if that string is a value +type of custom field + + +=cut + +sub ValidateType { + my $self = shift; + my $type = shift; + + if ( $type =~ s/(?:Single|Multiple)$// ) { + $RT::Logger->warning( "Prefix 'Single' and 'Multiple' to Type deprecated, use MaxValues instead at (". join(":",caller).")"); + } + + if ( $FieldTypes{$type} ) { + return 1; + } + else { + return undef; + } +} + + +sub SetType { + my $self = shift; + my $type = shift; + if ($type =~ s/(?:(Single)|Multiple)$//) { + $RT::Logger->warning("'Single' and 'Multiple' on SetType deprecated, use SetMaxValues instead at (". join(":",caller).")"); + $self->SetMaxValues($1 ? 1 : 0); + } + $self->_Set(Field => 'Type', Value =>$type); +} + +=head2 SetPattern STRING + +Takes a single string representing a regular expression. Performs basic +validation on that regex, and sets the C<Pattern> field for the CF if it +is valid. + +=cut + +sub SetPattern { + my $self = shift; + my $regex = shift; + + my ($ok, $msg) = $self->_IsValidRegex($regex); + if ($ok) { + return $self->_Set(Field => 'Pattern', Value => $regex); + } + else { + return (0, $self->loc("Invalid pattern: [_1]", $msg)); + } +} + +=head2 _IsValidRegex(Str $regex) returns (Bool $success, Str $msg) + +Tests if the string contains an invalid regex. + +=cut + +sub _IsValidRegex { + my $self = shift; + my $regex = shift or return (1, 'valid'); + + local $^W; local $@; + local $SIG{__DIE__} = sub { 1 }; + local $SIG{__WARN__} = sub { 1 }; + + if (eval { qr/$regex/; 1 }) { + return (1, 'valid'); + } + + my $err = $@; + $err =~ s{[,;].*}{}; # strip debug info from error + chomp $err; + return (0, $err); +} + + +=head2 SingleValue + +Returns true if this CustomField only accepts a single value. +Returns false if it accepts multiple values + +=cut + +sub SingleValue { + my $self = shift; + if (($self->MaxValues||0) == 1) { + return 1; + } + else { + return undef; + } +} + +sub UnlimitedValues { + my $self = shift; + if (($self->MaxValues||0) == 0) { + return 1; + } + else { + return undef; + } +} + + +=head2 CurrentUserHasRight RIGHT + +Helper function to call the custom field's queue's CurrentUserHasRight with the passed in args. + +=cut + +sub CurrentUserHasRight { + my $self = shift; + my $right = shift; + + return $self->CurrentUser->HasRight( + Object => $self, + Right => $right, + ); +} + +=head2 ACLEquivalenceObjects + +Returns list of objects via which users can get rights on this custom field. For custom fields +these objects can be set using L<ContextObject|/"ContextObject and SetContextObject">. + +=cut + +sub ACLEquivalenceObjects { + my $self = shift; + + my $ctx = $self->ContextObject + or return; + return ($ctx, $ctx->ACLEquivalenceObjects); +} + +=head2 ContextObject and SetContextObject + +Set or get a context for this object. It can be ticket, queue or another object +this CF applies to. Used for ACL control, for example SeeCustomField can be granted on +queue level to allow people to see all fields applied to the queue. + +=cut + +sub SetContextObject { + my $self = shift; + return $self->{'context_object'} = shift; +} + +sub ContextObject { + my $self = shift; + return $self->{'context_object'}; +} + + +sub _Set { + my $self = shift; + + unless ( $self->CurrentUserHasRight('AdminCustomField') ) { + return ( 0, $self->loc('Permission Denied') ); + } + return $self->SUPER::_Set( @_ ); + +} + + + +=head2 _Value + +Takes the name of a table column. +Returns its value as a string, if the user passes an ACL check + +=cut + +sub _Value { + my $self = shift; + return undef unless $self->id; + + # we need to do the rights check + unless ( $self->CurrentUserHasRight('SeeCustomField') ) { + $RT::Logger->debug( + "Permission denied. User #". $self->CurrentUser->id + ." has no SeeCustomField right on CF #". $self->id + ); + return (undef); + } + return $self->__Value( @_ ); +} + + +=head2 SetDisabled + +Takes a boolean. +1 will cause this custom field to no longer be avaialble for objects. +0 will re-enable this field. + +=cut + + +=head2 SetTypeComposite + +Set this custom field's type and maximum values as a composite value + +=cut + +sub SetTypeComposite { + my $self = shift; + my $composite = shift; + + my $old = $self->TypeComposite; + + my ($type, $max_values) = split(/-/, $composite, 2); + if ( $type ne $self->Type ) { + my ($status, $msg) = $self->SetType( $type ); + return ($status, $msg) unless $status; + } + if ( ($max_values || 0) != ($self->MaxValues || 0) ) { + my ($status, $msg) = $self->SetMaxValues( $max_values ); + return ($status, $msg) unless $status; + } + my $render = $self->RenderType; + if ( $render and not grep { $_ eq $render } $self->RenderTypes ) { + # We switched types and our render type is no longer valid, so unset it + # and use the default + $self->SetRenderType( undef ); + } + return 1, $self->loc( + "Type changed from '[_1]' to '[_2]'", + $self->FriendlyTypeComposite( $old ), + $self->FriendlyTypeComposite( $composite ), + ); +} + +=head2 TypeComposite + +Returns a composite value composed of this object's type and maximum values + +=cut + + +sub TypeComposite { + my $self = shift; + return join '-', ($self->Type || ''), ($self->MaxValues || 0); +} + +=head2 TypeComposites + +Returns an array of all possible composite values for custom fields. + +=cut + +sub TypeComposites { + my $self = shift; + return grep !/(?:[Tt]ext|Combobox|Date|DateTime|TimeValue)-0/, map { ("$_-1", "$_-0") } $self->Types; +} + +=head2 RenderType + +Returns the type of form widget to render for this custom field. Currently +this only affects fields which return true for L</HasRenderTypes>. + +=cut + +sub RenderType { + my $self = shift; + return '' unless $self->HasRenderTypes; + + return $self->_Value( 'RenderType', @_ ) + || $self->DefaultRenderType; +} + +=head2 SetRenderType TYPE + +Sets this custom field's render type. + +=cut + +sub SetRenderType { + my $self = shift; + my $type = shift; + return (0, $self->loc("This custom field has no Render Types")) + unless $self->HasRenderTypes; + + if ( !$type || $type eq $self->DefaultRenderType ) { + return $self->_Set( Field => 'RenderType', Value => undef, @_ ); + } + + if ( not grep { $_ eq $type } $self->RenderTypes ) { + return (0, $self->loc("Invalid Render Type for custom field of type [_1]", + $self->FriendlyType)); + } + + # XXX: Remove this restriction once we support lists and cascaded selects + if ( $self->BasedOnObj->id and $type =~ /List/ ) { + return (0, $self->loc("We can't currently render as a List when basing categories on another custom field. Please use another render type.")); + } + + return $self->_Set( Field => 'RenderType', Value => $type, @_ ); +} + +=head2 DefaultRenderType [TYPE COMPOSITE] + +Returns the default render type for this custom field's type or the TYPE +COMPOSITE specified as an argument. + +=cut + +sub DefaultRenderType { + my $self = shift; + my $composite = @_ ? shift : $self->TypeComposite; + my ($type, $max) = split /-/, $composite, 2; + return unless $type and $self->HasRenderTypes($composite); + return $FieldTypes{$type}->{render_types}->{ $max == 1 ? 'single' : 'multiple' }[0]; +} + +=head2 HasRenderTypes [TYPE_COMPOSITE] + +Returns a boolean value indicating whether the L</RenderTypes> and +L</RenderType> methods make sense for this custom field. + +Currently true only for type C<Select>. + +=cut + +sub HasRenderTypes { + my $self = shift; + my ($type, $max) = split /-/, (@_ ? shift : $self->TypeComposite), 2; + return undef unless $type; + return defined $FieldTypes{$type}->{render_types} + ->{ $max == 1 ? 'single' : 'multiple' }; +} + +=head2 RenderTypes [TYPE COMPOSITE] + +Returns the valid render types for this custom field's type or the TYPE +COMPOSITE specified as an argument. + +=cut + +sub RenderTypes { + my $self = shift; + my $composite = @_ ? shift : $self->TypeComposite; + my ($type, $max) = split /-/, $composite, 2; + return unless $type and $self->HasRenderTypes($composite); + return @{$FieldTypes{$type}->{render_types}->{ $max == 1 ? 'single' : 'multiple' }}; +} + +=head2 SetLookupType + +Autrijus: care to doc how LookupTypes work? + +=cut + +sub SetLookupType { + my $self = shift; + my $lookup = shift; + if ( $lookup ne $self->LookupType ) { + # Okay... We need to invalidate our existing relationships + my $ObjectCustomFields = RT::ObjectCustomFields->new($self->CurrentUser); + $ObjectCustomFields->LimitToCustomField($self->Id); + $_->Delete foreach @{$ObjectCustomFields->ItemsArrayRef}; + } + return $self->_Set(Field => 'LookupType', Value =>$lookup); +} + +=head2 LookupTypes + +Returns an array of LookupTypes available + +=cut + + +sub LookupTypes { + my $self = shift; + return keys %FRIENDLY_OBJECT_TYPES; +} + +my @FriendlyObjectTypes = ( + "[_1] objects", # loc + "[_1]'s [_2] objects", # loc + "[_1]'s [_2]'s [_3] objects", # loc ); +=head2 FriendlyLookupType + +Returns a localized description of the type of this custom field + +=cut + +sub FriendlyLookupType { + my $self = shift; + my $lookup = shift || $self->LookupType; + + return ($self->loc( $FRIENDLY_OBJECT_TYPES{$lookup} )) + if (defined $FRIENDLY_OBJECT_TYPES{$lookup} ); + + my @types = map { s/^RT::// ? $self->loc($_) : $_ } + grep { defined and length } + split( /-/, $lookup ) + or return; + return ( $self->loc( $FriendlyObjectTypes[$#types], @types ) ); +} + +sub RecordClassFromLookupType { + my $self = shift; + my ($class) = ($self->LookupType =~ /^([^-]+)/); + unless ( $class ) { + $RT::Logger->error( + "Custom Field #". $self->id + ." has incorrect LookupType '". $self->LookupType ."'" + ); + return undef; + } + return $class; +} + +sub CollectionClassFromLookupType { + my $self = shift; + + my $record_class = $self->RecordClassFromLookupType; + return undef unless $record_class; + + my $collection_class; + if ( UNIVERSAL::can($record_class.'Collection', 'new') ) { + $collection_class = $record_class.'Collection'; + } elsif ( UNIVERSAL::can($record_class.'es', 'new') ) { + $collection_class = $record_class.'es'; + } elsif ( UNIVERSAL::can($record_class.'s', 'new') ) { + $collection_class = $record_class.'s'; + } else { + $RT::Logger->error("Can not find a collection class for record class '$record_class'"); + return undef; + } + return $collection_class; +} + +=head1 ApplyGlobally + +Certain custom fields (users, groups) should only be applied globally +but rather than regexing in code for LookupType =~ RT::Queue, we'll codify +the rules here. + +=cut + +sub ApplyGlobally { + my $self = shift; + + return ($self->LookupType =~ /^RT::(?:Group|User)/io); + +} + +=head1 AppliedTo + +Returns collection with objects this custom field is applied to. +Class of the collection depends on L</LookupType>. +See all L</NotAppliedTo> . + +Doesn't takes into account if object is applied globally. + +=cut + +sub AppliedTo { + my $self = shift; + + my ($res, $ocfs_alias) = $self->_AppliedTo; + return $res unless $res; + + $res->Limit( + ALIAS => $ocfs_alias, + FIELD => 'id', + OPERATOR => 'IS NOT', + VALUE => 'NULL', + ); + + return $res; } +=head1 NotAppliedTo + +Returns collection with objects this custom field is not applied to. +Class of the collection depends on L</LookupType>. +See all L</AppliedTo> . + +Doesn't takes into account if object is applied globally. + +=cut + +sub NotAppliedTo { + my $self = shift; + + my ($res, $ocfs_alias) = $self->_AppliedTo; + return $res unless $res; + + $res->Limit( + ALIAS => $ocfs_alias, + FIELD => 'id', + OPERATOR => 'IS', + VALUE => 'NULL', + ); + + return $res; +} + +sub _AppliedTo { + my $self = shift; + + my ($class) = $self->CollectionClassFromLookupType; + return undef unless $class; + + my $res = $class->new( $self->CurrentUser ); + + # If CF is a Group CF, only display user-defined groups + if ( $class eq 'RT::Groups' ) { + $res->LimitToUserDefinedGroups; + } + + $res->OrderBy( FIELD => 'Name' ); + my $ocfs_alias = $res->Join( + TYPE => 'LEFT', + ALIAS1 => 'main', + FIELD1 => 'id', + TABLE2 => 'ObjectCustomFields', + FIELD2 => 'ObjectId', + ); + $res->Limit( + LEFTJOIN => $ocfs_alias, + ALIAS => $ocfs_alias, + FIELD => 'CustomField', + VALUE => $self->id, + ); + return ($res, $ocfs_alias); +} + +=head2 IsApplied + +Takes object id and returns corresponding L<RT::ObjectCustomField> +record if this custom field is applied to the object. Use 0 to check +if custom field is applied globally. + +=cut + +sub IsApplied { + my $self = shift; + my $id = shift; + my $ocf = RT::ObjectCustomField->new( $self->CurrentUser ); + $ocf->LoadByCols( CustomField => $self->id, ObjectId => $id || 0 ); + return undef unless $ocf->id; + return $ocf; +} + +=head2 AddToObject OBJECT + +Add this custom field as a custom field for a single object, such as a queue or group. + +Takes an object + +=cut + + +sub AddToObject { + my $self = shift; + my $object = shift; + my $id = $object->Id || 0; + + unless (index($self->LookupType, ref($object)) == 0) { + return ( 0, $self->loc('Lookup type mismatch') ); + } + + unless ( $object->CurrentUserHasRight('AssignCustomFields') ) { + return ( 0, $self->loc('Permission Denied') ); + } + + if ( $self->IsApplied( $id ) ) { + return ( 0, $self->loc("Custom field is already applied to the object") ); + } + + if ( $id ) { + # applying locally + return (0, $self->loc("Couldn't apply custom field to an object as it's global already") ) + if $self->IsApplied( 0 ); + } + else { + my $applied = RT::ObjectCustomFields->new( $self->CurrentUser ); + $applied->LimitToCustomField( $self->id ); + while ( my $record = $applied->Next ) { + $record->Delete; + } + } + + my $ocf = RT::ObjectCustomField->new( $self->CurrentUser ); + my ( $oid, $msg ) = $ocf->Create( + ObjectId => $id, CustomField => $self->id, + ); + return ( $oid, $msg ); +} + + +=head2 RemoveFromObject OBJECT + +Remove this custom field for a single object, such as a queue or group. + +Takes an object + +=cut + +sub RemoveFromObject { + my $self = shift; + my $object = shift; + my $id = $object->Id || 0; + + unless (index($self->LookupType, ref($object)) == 0) { + return ( 0, $self->loc('Object type mismatch') ); + } + + unless ( $object->CurrentUserHasRight('AssignCustomFields') ) { + return ( 0, $self->loc('Permission Denied') ); + } + + my $ocf = $self->IsApplied( $id ); + unless ( $ocf ) { + return ( 0, $self->loc("This custom field does not apply to that object") ); + } + + # XXX: Delete doesn't return anything + my ( $oid, $msg ) = $ocf->Delete; + return ( $oid, $msg ); +} + + +=head2 AddValueForObject HASH + +Adds a custom field value for a record object of some kind. +Takes a param hash of + +Required: + + Object + Content + +Optional: + + LargeContent + ContentType + +=cut + +sub AddValueForObject { + my $self = shift; + my %args = ( + Object => undef, + Content => undef, + LargeContent => undef, + ContentType => undef, + @_ + ); + my $obj = $args{'Object'} or return ( 0, $self->loc('Invalid object') ); + + unless ( $self->CurrentUserHasRight('ModifyCustomField') ) { + return ( 0, $self->loc('Permission Denied') ); + } + + unless ( $self->MatchPattern($args{'Content'}) ) { + return ( 0, $self->loc('Input must match [_1]', $self->FriendlyPattern) ); + } + + $RT::Handle->BeginTransaction; + + if ( $self->MaxValues ) { + my $current_values = $self->ValuesForObject($obj); + my $extra_values = ( $current_values->Count + 1 ) - $self->MaxValues; + + # (The +1 is for the new value we're adding) + + # If we have a set of current values and we've gone over the maximum + # allowed number of values, we'll need to delete some to make room. + # which former values are blown away is not guaranteed + + while ($extra_values) { + my $extra_item = $current_values->Next; + unless ( $extra_item->id ) { + $RT::Logger->crit( "We were just asked to delete " + ."a custom field value that doesn't exist!" ); + $RT::Handle->Rollback(); + return (undef); + } + $extra_item->Delete; + $extra_values--; + } + } + + if (my $canonicalizer = $self->can('_CanonicalizeValue'.$self->Type)) { + $canonicalizer->($self, \%args); + } + + + + my $newval = RT::ObjectCustomFieldValue->new( $self->CurrentUser ); + my ($val, $msg) = $newval->Create( + ObjectType => ref($obj), + ObjectId => $obj->Id, + Content => $args{'Content'}, + LargeContent => $args{'LargeContent'}, + ContentType => $args{'ContentType'}, + CustomField => $self->Id + ); + + unless ($val) { + $RT::Handle->Rollback(); + return ($val, $self->loc("Couldn't create record: [_1]", $msg)); + } + + $RT::Handle->Commit(); + return ($val); + +} + + + +sub _CanonicalizeValueDateTime { + my $self = shift; + my $args = shift; + my $DateObj = RT::Date->new( $self->CurrentUser ); + $DateObj->Set( Format => 'unknown', + Value => $args->{'Content'} ); + $args->{'Content'} = $DateObj->ISO; +} + +# For date, we need to store Content as ISO date +sub _CanonicalizeValueDate { + my $self = shift; + my $args = shift; + + # in case user input date with time, let's omit it by setting timezone + # to utc so "hour" won't affect "day" + my $DateObj = RT::Date->new( $self->CurrentUser ); + $DateObj->Set( Format => 'unknown', + Value => $args->{'Content'}, + Timezone => 'UTC', + ); + $args->{'Content'} = $DateObj->Date( Timezone => 'UTC' ); +} + +=head2 MatchPattern STRING + +Tests the incoming string against the Pattern of this custom field object +and returns a boolean; returns true if the Pattern is empty. + +=cut + +sub MatchPattern { + my $self = shift; + my $regex = $self->Pattern or return 1; + + return (( defined $_[0] ? $_[0] : '') =~ $regex); +} + + + + +=head2 FriendlyPattern + +Prettify the pattern of this custom field, by taking the text in C<(?#text)> +and localizing it. + +=cut + +sub FriendlyPattern { + my $self = shift; + my $regex = $self->Pattern; + + return '' unless length $regex; + if ( $regex =~ /\(\?#([^)]*)\)/ ) { + return '[' . $self->loc($1) . ']'; + } + else { + return $regex; + } +} + + + + +=head2 DeleteValueForObject HASH + +Deletes a custom field value for a ticket. Takes a param hash of Object and Content + +Returns a tuple of (STATUS, MESSAGE). If the call succeeded, the STATUS is true. otherwise it's false + +=cut + +sub DeleteValueForObject { + my $self = shift; + my %args = ( Object => undef, + Content => undef, + Id => undef, + @_ ); + + + unless ($self->CurrentUserHasRight('ModifyCustomField')) { + return (0, $self->loc('Permission Denied')); + } + + my $oldval = RT::ObjectCustomFieldValue->new($self->CurrentUser); + + if (my $id = $args{'Id'}) { + $oldval->Load($id); + } + unless ($oldval->id) { + $oldval->LoadByObjectContentAndCustomField( + Object => $args{'Object'}, + Content => $args{'Content'}, + CustomField => $self->Id, + ); + } + + + # check to make sure we found it + unless ($oldval->Id) { + return(0, $self->loc("Custom field value [_1] could not be found for custom field [_2]", $args{'Content'}, $self->Name)); + } + + # for single-value fields, we need to validate that empty string is a valid value for it + if ( $self->SingleValue and not $self->MatchPattern( '' ) ) { + return ( 0, $self->loc('Input must match [_1]', $self->FriendlyPattern) ); + } + + # delete it + + my $ret = $oldval->Delete(); + unless ($ret) { + return(0, $self->loc("Custom field value could not be found")); + } + return($oldval->Id, $self->loc("Custom field value deleted")); +} + + +=head2 ValuesForObject OBJECT + +Return an L<RT::ObjectCustomFieldValues> object containing all of this custom field's values for OBJECT + +=cut + +sub ValuesForObject { + my $self = shift; + my $object = shift; + + my $values = RT::ObjectCustomFieldValues->new($self->CurrentUser); + unless ($self->CurrentUserHasRight('SeeCustomField')) { + # Return an empty object if they have no rights to see + return ($values); + } + + + $values->LimitToCustomField($self->Id); + $values->LimitToEnabled(); + $values->LimitToObject($object); + + return ($values); +} + + +=head2 _ForObjectType PATH FRIENDLYNAME + +Tell RT that a certain object accepts custom fields + +Examples: + + 'RT::Queue-RT::Ticket' => "Tickets", # loc + 'RT::Queue-RT::Ticket-RT::Transaction' => "Ticket Transactions", # loc + 'RT::User' => "Users", # loc + 'RT::Group' => "Groups", # loc + +This is a class method. + +=cut + +sub _ForObjectType { + my $self = shift; + my $path = shift; + my $friendly_name = shift; + + $FRIENDLY_OBJECT_TYPES{$path} = $friendly_name; + +} + + +=head2 IncludeContentForValue [VALUE] (and SetIncludeContentForValue) + +Gets or sets the C<IncludeContentForValue> for this custom field. RT +uses this field to automatically include content into the user's browser +as they display records with custom fields in RT. + +=cut + +sub SetIncludeContentForValue { + shift->IncludeContentForValue(@_); +} +sub IncludeContentForValue{ + my $self = shift; + $self->_URLTemplate('IncludeContentForValue', @_); +} + + + +=head2 LinkValueTo [VALUE] (and SetLinkValueTo) + +Gets or sets the C<LinkValueTo> for this custom field. RT +uses this field to make custom field values into hyperlinks in the user's +browser as they display records with custom fields in RT. + +=cut + + +sub SetLinkValueTo { + shift->LinkValueTo(@_); +} + +sub LinkValueTo { + my $self = shift; + $self->_URLTemplate('LinkValueTo', @_); + +} + + +=head2 _URLTemplate NAME [VALUE] + +With one argument, returns the _URLTemplate named C<NAME>, but only if +the current user has the right to see this custom field. + +With two arguments, attemptes to set the relevant template value. + +=cut + +sub _URLTemplate { + my $self = shift; + my $template_name = shift; + if (@_) { + + my $value = shift; + unless ( $self->CurrentUserHasRight('AdminCustomField') ) { + return ( 0, $self->loc('Permission Denied') ); + } + $self->SetAttribute( Name => $template_name, Content => $value ); + return ( 1, $self->loc('Updated') ); + } else { + unless ( $self->id && $self->CurrentUserHasRight('SeeCustomField') ) { + return (undef); + } + + my @attr = $self->Attributes->Named($template_name); + my $attr = shift @attr; + + if ($attr) { return $attr->Content } + + } +} + +sub SetBasedOn { + my $self = shift; + my $value = shift; + + return $self->_Set( Field => 'BasedOn', Value => $value, @_ ) + unless defined $value and length $value; + + my $cf = RT::CustomField->new( $self->CurrentUser ); + $cf->Load( ref $value ? $value->id : $value ); + + return (0, "Permission denied") + unless $cf->id && $cf->CurrentUserHasRight('SeeCustomField'); + + # XXX: Remove this restriction once we support lists and cascaded selects + if ( $self->RenderType =~ /List/ ) { + return (0, $self->loc("We can't currently render as a List when basing categories on another custom field. Please use another render type.")); + } + + return $self->_Set( Field => 'BasedOn', Value => $value, @_ ) +} + +sub BasedOnObj { + my $self = shift; + + my $obj = RT::CustomField->new( $self->CurrentUser ); + if ( $self->BasedOn ) { + $obj->Load( $self->BasedOn ); + } + return $obj; +} + +sub UILocation { + my $self = shift; + my $tag = $self->FirstAttribute( 'UILocation' ); + return $tag ? $tag->Content : ''; +} + +sub SetUILocation { + my $self = shift; + my $tag = shift; + if ( $tag ) { + return $self->SetAttribute( Name => 'UILocation', Content => $tag ); + } + else { + return $self->DeleteAttribute('UILocation'); + } +} + + + + =head2 id @@ -187,6 +1792,24 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure. =cut +=head2 RenderType + +Returns the current value of RenderType. +(In the database, RenderType is stored as varchar(64).) + + + +=head2 SetRenderType VALUE + + +Set RenderType to VALUE. +Returns (1, 'Status message') on success and (0, 'Error Message') on failure. +(In the database, RenderType will be stored as a varchar(64).) + + +=cut + + =head2 MaxValues Returns the current value of MaxValues. @@ -208,7 +1831,7 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure. =head2 Pattern Returns the current value of Pattern. -(In the database, Pattern is stored as longtext.) +(In the database, Pattern is stored as text.) @@ -217,7 +1840,7 @@ Returns the current value of Pattern. Set Pattern to VALUE. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. -(In the database, Pattern will be stored as a longtext.) +(In the database, Pattern will be stored as a text.) =cut @@ -241,6 +1864,24 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure. =cut +=head2 BasedOn + +Returns the current value of BasedOn. +(In the database, BasedOn is stored as int(11).) + + + +=head2 SetBasedOn VALUE + + +Set BasedOn to VALUE. +Returns (1, 'Status message') on success and (0, 'Error Message') on failure. +(In the database, BasedOn will be stored as a int(11).) + + +=cut + + =head2 Description Returns the current value of Description. @@ -354,55 +1995,42 @@ sub _CoreAccessible { { id => - {read => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''}, + {read => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''}, Name => - {read => 1, write => 1, sql_type => 12, length => 200, is_blob => 0, is_numeric => 0, type => 'varchar(200)', default => ''}, + {read => 1, write => 1, sql_type => 12, length => 200, is_blob => 0, is_numeric => 0, type => 'varchar(200)', default => ''}, Type => - {read => 1, write => 1, sql_type => 12, length => 200, is_blob => 0, is_numeric => 0, type => 'varchar(200)', default => ''}, + {read => 1, write => 1, sql_type => 12, length => 200, is_blob => 0, is_numeric => 0, type => 'varchar(200)', default => ''}, + RenderType => + {read => 1, write => 1, sql_type => 12, length => 64, is_blob => 0, is_numeric => 0, type => 'varchar(64)', default => ''}, MaxValues => - {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''}, + {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''}, Pattern => - {read => 1, write => 1, sql_type => -4, length => 0, is_blob => 1, is_numeric => 0, type => 'longtext', default => ''}, + {read => 1, write => 1, sql_type => -4, length => 0, is_blob => 1, is_numeric => 0, type => 'text', default => ''}, Repeated => - {read => 1, write => 1, sql_type => 5, length => 6, is_blob => 0, is_numeric => 1, type => 'smallint(6)', default => '0'}, + {read => 1, write => 1, sql_type => 5, length => 6, is_blob => 0, is_numeric => 1, type => 'smallint(6)', default => '0'}, + BasedOn => + {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''}, Description => - {read => 1, write => 1, sql_type => 12, length => 255, is_blob => 0, is_numeric => 0, type => 'varchar(255)', default => ''}, + {read => 1, write => 1, sql_type => 12, length => 255, is_blob => 0, is_numeric => 0, type => 'varchar(255)', default => ''}, SortOrder => - {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, + {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, LookupType => - {read => 1, write => 1, sql_type => 12, length => 255, is_blob => 0, is_numeric => 0, type => 'varchar(255)', default => ''}, + {read => 1, write => 1, sql_type => 12, length => 255, is_blob => 0, is_numeric => 0, type => 'varchar(255)', default => ''}, Creator => - {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, + {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, Created => - {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''}, + {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''}, LastUpdatedBy => - {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, + {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, LastUpdated => - {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''}, + {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''}, Disabled => - {read => 1, write => 1, sql_type => 5, length => 6, is_blob => 0, is_numeric => 1, type => 'smallint(6)', default => '0'}, + {read => 1, write => 1, sql_type => 5, length => 6, is_blob => 0, is_numeric => 1, type => 'smallint(6)', default => '0'}, + } }; -RT::Base->_ImportOverlays(); - -=head1 SEE ALSO - -This class allows "overlay" methods to be placed -into the following files _Overlay is for a System overlay by the original author, -_Vendor is for 3rd-party vendor add-ons, while _Local is for site-local customizations. - -These overlay files can contain new subs or subs to replace existing subs in this module. - -Each of these files should begin with the line - - no warnings qw(redefine); - -so that perl does not kick and scream when you redefine a subroutine or variable in your overlay. - -RT::CustomField_Overlay, RT::CustomField_Vendor, RT::CustomField_Local - -=cut +RT::Base->_ImportOverlays(); 1; |