%# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: %# %# This software is Copyright (c) 1996-2025 Best Practical Solutions, LLC %# %# %# (Except where explicitly superseded by other copyright notices) %# %# %# LICENSE: %# %# This work is made available to you under the terms of Version 2 of %# the GNU General Public License. A copy of that license should have %# been provided with this software, but in any event can be snarfed %# from www.gnu.org. %# %# This work is distributed in the hope that it will be useful, but %# WITHOUT ANY WARRANTY; without even the implied warranty of %# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU %# General Public License for more details. %# %# You should have received a copy of the GNU General Public License %# along with this program; if not, write to the Free Software %# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA %# 02110-1301 or visit their web page on the internet at %# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html. %# %# %# CONTRIBUTION SUBMISSION POLICY: %# %# (The following paragraph is not intended to limit the rights granted %# to you to modify and distribute this software under the terms of %# the GNU General Public License and is only of importance to you if %# you choose to contribute your changes and enhancements to the %# community by submitting them to Best Practical Solutions, LLC.) %# %# By intentionally submitting any modifications, corrections or %# derivatives to this work, or any other work intended for use with %# Request Tracker, to Best Practical Solutions, LLC, you confirm that %# you are the copyright holder for those contributions and you grant %# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable, %# royalty-free, perpetual, license to use, copy, create derivative %# works based on those contributions, and sublicense and distribute %# those contributions and any derivatives thereof. %# %# END BPS TAGGED BLOCK }}} <& /Admin/Elements/Header, Title => $title, &> <& /Elements/Tabs &> <& /Elements/ListActions, actions => \@results &> <&| /Widgets/TitleBox, title => 'Basics', class => 'custom-field-info-basics', content_class => 'mx-auto width-sm' &> <&| /Elements/LabeledValue, Label => loc("Name") &> <&| /Elements/LabeledValue, Label => loc("Description") &> <&| /Elements/LabeledValue, Label => loc("Type") &> <& /Admin/Elements/SelectCustomFieldType, Name => "TypeComposite", Default => $CustomFieldObj->TypeComposite, &> % if ( $CustomFieldObj->Id and $CustomFieldObj->HasRenderTypes ) { <&| /Elements/LabeledValue, Label => loc("Render Type") &> <& /Admin/Elements/SelectCustomFieldRenderType, Name => "RenderType", TypeComposite => $CustomFieldObj->TypeComposite, Default => $CustomFieldObj->RenderType, BasedOn => $CustomFieldObj->BasedOnObj->id, &> % } % if ( $CustomFieldObj->Id and $CustomFieldObj->IsSelectionType and RT->Config->Get('CustomFieldValuesSources') and ( scalar(@{RT->Config->Get('CustomFieldValuesSources')}) > 0 ) ) { <&| /Elements/LabeledValue, Label => loc("Field values source") &> <& /Admin/Elements/EditCustomFieldValuesSource, CustomField => $CustomFieldObj &> % } % if ( $CustomFieldObj->Id and $CustomFieldObj->IsCanonicalizeType and RT->Config->Get('CustomFieldValuesCanonicalizers') and ( scalar(@{RT->Config->Get('CustomFieldValuesCanonicalizers')}) > 0 ) ) { <&| /Elements/LabeledValue, Label => loc("Canonicalizer") &> <& /Admin/Elements/EditCustomFieldValuesCanonicalizer, CustomField => $CustomFieldObj &> % } <&| /Elements/LabeledValue, Label => loc("Applies to") &> <& /Admin/Elements/SelectLookupType, Class => 'RT::CustomField', Name => "LookupType", Object => $CustomFieldObj, Default => $CustomFieldObj->LookupType || $LookupType, &>
<&| /Elements/LabeledValue, Label => loc("Entry Hint") &> <&| /Elements/LabeledValue, Label => loc("Validation") &> <& /Widgets/ComboBox, Name => 'Pattern', Default => $CustomFieldObj->Pattern || $Pattern, Size => 20, Values => \@CFvalidations, &>
<&|/l&>Validation Hint
% if ( $CustomFieldObj->SupportDefaultValues ) { <&| /Elements/LabeledValue, Label => loc("Default [numerate,_1,value,values]", $CustomFieldObj->MaxValues), Class => $default_value_class &> <& /Elements/EditCustomField, NamePrefix => 'Default-', CustomField => $CustomFieldObj, ShowEmptyOption => 1 &> % } % if ( $CustomFieldObj->Id && $CustomFieldObj->IsSelectionType ) { <&| /Elements/LabeledValue, Label => loc("Categories are based on") &> <& /Admin/Elements/SelectCustomField, Name => "BasedOn", LookupType => $CustomFieldObj->LookupType, Default => $CustomFieldObj->BasedOnObj || $BasedOn, Not => $CustomFieldObj->id, &> % } <&| /Elements/LabeledValue, Label => "" &>
/> <&|/l&>New values must be unique
% $m->callback(CallbackName => 'BeforeEnabled', CustomField => $CustomFieldObj, CFvalidations => \@CFvalidations); <&| /Elements/LabeledValue, Label => "" &>
/> <&|/l&>Enabled (Unchecking this box disables this custom field)
% $m->callback(CallbackName => 'EndOfTable', CustomField => $CustomFieldObj, CFvalidations => \@CFvalidations); <&| /Widgets/TitleBox, title => 'Integration', class => 'custom-field-info-integration', content_class => 'mx-auto width-lg' &>
<&|/l&>Link values to
<&|/l&>RT can make this custom field's values into hyperlinks to another service. <&|/l&>Fill in this field with a URL. <&|/l_unsafe, '`__id__`', '`__CustomField__`' &>RT will replace [_1] and [_2] with the record's id and the custom field's value, respectively.
<&|/l&>Include page
<&|/l&>RT can include content from another web service when showing this custom field. <&|/l&>Fill in this field with a URL. <&|/l_unsafe, '`__id__`', '`__CustomField__`' &>RT will replace [_1] and [_2] with the record's id and the custom field's value, respectively. <&|/l&>Some browsers may only load content from the same domain as your RT server.
% if ( $CustomFieldObj->Id && $CustomFieldObj->IsSelectionType && !$CustomFieldObj->IsExternalValues ) { <&| /Widgets/TitleBox, title => 'Values', class => 'custom-field-info-values', content_class => 'mx-auto width-lg' &>
<& /Admin/Elements/EditCustomFieldValues, CustomField => $CustomFieldObj &>
% }
<& /Elements/Submit, Name => 'Update', Label => $id eq 'new'? loc('Create'): loc('Save Changes') &>
% if ( $CustomFieldObj->Id && $CustomFieldObj->IsSelectionType && !$CustomFieldObj->IsExternalValues ) {
<&| /Widgets/TitleBox, title => 'Add Values', class => 'custom-field-info-add-values', content_class => 'mx-auto width-lg' &>
<& /Admin/Elements/AddCustomFieldValue, CustomField => $CustomFieldObj &>
% } % $m->callback(%ARGS, CallbackName => 'EndOfPage', CustomFieldObj => $CustomFieldObj); <%INIT> my ($title, @results, $added_cfv); my $CustomFieldObj = RT::CustomField->new( $session{'CurrentUser'} ); my $default_value_class = ''; # Extra classes for default CF value input $m->callback(CallbackName => 'Initial', Pattern => \$Pattern, Results => \@results, ARGSRef => \%ARGS); unless ( $id ) { $title = loc("Create a CustomField"); $id = 'new'; } else { if ( $id eq 'new' ) { my ( $val, $msg ) = $CustomFieldObj->Create( Name => $Name, TypeComposite => $TypeComposite, LookupType => $LookupType, Description => $Description, Pattern => $Pattern, LinkValueTo => $LinkValueTo, IncludeContentForValue => $IncludeContentForValue, BasedOn => $BasedOn, Disabled => ($Enabled ? 0 : 1), EntryHint => $EntryHint, ValidationHint => $ValidationHint, UniqueValues => $UniqueValues, ); if (!$val) { push @results, loc("Could not create CustomField: [_1]", $msg); $title = loc( 'Create a CustomField'); } else { push @results, loc("Object created"); $title = loc( 'Created CustomField [_1]', $CustomFieldObj->Name ); } } else { push @results, loc('No CustomField') unless $CustomFieldObj->Load( $id ); $title = loc( 'Editing CustomField [_1]', $CustomFieldObj->Name ); } } if ( $ARGS{'Update'} && $id ne 'new' ) { #we're asking about enabled on the web page but really care about disabled. $ARGS{'Disabled'} = $Enabled? 0 : 1; # make sure the unchecked checkbox still causes an update $ARGS{UniqueValues} ||= 0 if $SetUniqueValues; my @attribs = qw(Disabled Pattern Name TypeComposite LookupType Description LinkValueTo IncludeContentForValue EntryHint ValidationHint UniqueValues); push @results, UpdateRecordObject( AttributesRef => \@attribs, Object => $CustomFieldObj, ARGSRef => \%ARGS ); if ( ($ValuesClass||'RT::CustomFieldValues') ne $CustomFieldObj->ValuesClass ) { my $original = $CustomFieldObj->ValuesClass; my ($good, $msg) = $CustomFieldObj->SetValuesClass( $ValuesClass ); if ( $good ) { $msg = loc("[_1] changed from '[_2]' to '[_3]'", loc("Field values source"), $original, $ValuesClass ); } else { RT->Logger->debug("Unable to SetValuesClass to '$ValuesClass': $msg"); } push @results, $msg; } if ( ($CanonicalizeClass||'') ne ($CustomFieldObj->CanonicalizeClass||'') ) { my $original = $CustomFieldObj->CanonicalizeClass; my ($good, $msg) = $CustomFieldObj->SetCanonicalizeClass( $CanonicalizeClass ); if ( $good ) { # Improve message from class names to their friendly descriptions $original = $original->Description if $original && RT::StaticUtil::RequireModule($original); $CanonicalizeClass = $CanonicalizeClass->Description if $CanonicalizeClass && RT::StaticUtil::RequireModule($CanonicalizeClass); if (!$original) { $msg = loc("[_1] '[_2]' added", loc("Canonicalizer"), $CanonicalizeClass); } elsif (!$CanonicalizeClass) { $msg = loc("[_1] '[_2]' removed", loc("Canonicalizer"), $original); } else { $msg = loc("[_1] changed from '[_2]' to '[_3]'", loc("Canonicalizer"), $original, $CanonicalizeClass ); } } else { RT->Logger->debug("Unable to SetCanonicalizeClass to '$CanonicalizeClass': $msg"); } push @results, $msg; } # Set the render type if we have it, but unset it if the new type doesn't # support render types if ( $CustomFieldObj->HasRenderTypes($TypeComposite) ) { my $original = $CustomFieldObj->RenderType; if ( defined $RenderType and $RenderType ne $original ) { # It's changed! Let's update it. my ($good, $msg) = $CustomFieldObj->SetRenderType( $RenderType ); if ( $good ) { $msg = loc("[_1] changed from '[_2]' to '[_3]'", loc("Render Type"), $original, $RenderType ); } else { RT->Logger->debug("Unable to SetRenderType to '$RenderType': $msg"); } push @results, $msg; } } else { # Delete it if we no longer support render types $CustomFieldObj->SetRenderType( undef ); } if (($CustomFieldObj->BasedOn||'') ne ($BasedOn||'')) { my ($good, $msg) = $CustomFieldObj->SetBasedOn( $BasedOn ); push @results, $msg; } if ( $CustomFieldObj->SupportDefaultValues ) { my ($ret, $msg) = $CustomFieldObj->SetDefaultValues( Object => RT->System, Values => $ARGS{'Default-' . $CustomFieldObj->id . '-Value'} // $ARGS{'Default-' . $CustomFieldObj->id . '-Values'}, ); push @results, $msg; } my $paramtag = "CustomField-". $CustomFieldObj->Id ."-Value"; # Delete any fields that want to be deleted foreach my $key ( keys %ARGS ) { next unless $key =~ /^Delete-$paramtag-(\d+)$/; my ($val, $msg) = $CustomFieldObj->DeleteValue( $1 ); my $cf_delete = $ARGS{ $paramtag . "-$1-Name" }; $msg = loc("Custom field value [_1] deleted", $cf_delete) if $val; push (@results, $msg); } $m->callback(CallbackName => 'Massage', CustomField => $CustomFieldObj, Results => \@results, ARGSRef => \%ARGS); # Update any existing values my $values = $CustomFieldObj->ValuesObj; while ( my $value = $values->Next ) { foreach my $attr (qw(Name Description SortOrder Category)) { my $param = join("-", $paramtag, $value->Id, $attr); next unless exists $ARGS{$param}; $ARGS{$param} =~ s/^\s+//; $ARGS{$param} =~ s/\s+$//; next if ($value->$attr()||'') eq ($ARGS{$param}||''); my $mutator = "Set$attr"; my ($id, $msg) = $value->$mutator( $ARGS{$param} ); push (@results, $msg); } } } # for old tests only, users should use ajax calls and this should never be called in real usage. if ( $ARGS{'AddValue'} ) { # Add any new values my $paramtag = "CustomField-". $CustomFieldObj->Id ."-Value"; if ( defined $ARGS{ $paramtag ."-new-Name" } && length $ARGS{ $paramtag ."-new-Name" } ) { my ($id, $msg) = $CustomFieldObj->AddValue( map { $ARGS{$paramtag."-new-$_"} =~ s/^\s+//; $ARGS{$paramtag."-new-$_"} =~ s/\s+$//; $_ => $ARGS{ $paramtag ."-new-$_" } } grep { defined $ARGS{ $paramtag ."-new-$_" } } qw/ Name Description SortOrder Category/ ); my $cf_add = $ARGS{ $paramtag.'-new-Name' }; $msg = loc("Custom field value [_1] added", $cf_add) if $id; push (@results, $msg); } } if ( $CustomFieldObj->id && $CustomFieldObj->IsOnlyGlobal ) { my ( $ret, $msg ); my $object = $CustomFieldObj->RecordClassFromLookupType->new( $session{'CurrentUser'} ); if ( $CustomFieldObj->Disabled && $CustomFieldObj->IsGlobal ) { ( $ret, $msg ) = $CustomFieldObj->RemoveFromObject($object); } elsif ( !$CustomFieldObj->Disabled && !$CustomFieldObj->IsGlobal ) { ( $ret, $msg ) = $CustomFieldObj->AddToObject($object); } # successful msg("object created" or "object deleted ) is useless here push @results, $msg unless $ret; } $id = $CustomFieldObj->id if $CustomFieldObj->id; # This code does automatic redirection if any updates happen. MaybeRedirectForResults( Actions => \@results, Arguments => { id => $id }, ) if $CustomFieldObj->id; if ( $CustomFieldObj->RenderType eq 'Checkbox' ) { $default_value_class = 'cfrendertype-Checkbox'; if ( $CustomFieldObj->Values->Count < 2 ) { # Remind users a Checkbox should have two values push @results, 'Checkbox custom fields require two values, first for unchecked and the second for checked.'; } } my $EnabledChecked = qq[checked="checked"]; $EnabledChecked = '' if $CustomFieldObj->Disabled; my $UniqueValuesChecked = qq[checked="checked"]; $UniqueValuesChecked = '' if !$CustomFieldObj->UniqueValues; my @CFvalidations = RT->Config->Get('CustomFieldValuesValidations'); $m->callback(CallbackName => 'ValidationPatterns', Values => \@CFvalidations); <%ARGS> $id => undef $TypeComposite => undef $LookupType => RT::Ticket->CustomFieldLookupType $MaxValues => undef $SortOrder => undef $Description => undef $Pattern => undef $Name => undef $SetEnabled => undef $Enabled => 0 $SetUniqueValues => undef $UniqueValues => 0 $ValuesClass => 'RT::CustomFieldValues' $CanonicalizeClass => undef $RenderType => undef $LinkValueTo => undef $IncludeContentForValue => undef $BasedOn => undef $EntryHint => undef $ValidationHint => undef