[Rt-commit] rt branch, 4.4/canonicalize-custom-fields, created. rt-4.4.0-239-gf7f2d90
Shawn Moore
shawn at bestpractical.com
Wed Jun 8 14:22:17 EDT 2016
The branch, 4.4/canonicalize-custom-fields has been created
at f7f2d9070e61198627fbea4dff679549717f3703 (commit)
- Log -----------------------------------------------------------------
commit aa6e1a0830736aec829ed625c0d6f4d3479aa23e
Author: Shawn M Moore <shawn at bestpractical.com>
Date: Wed Jun 8 18:16:54 2016 +0000
Docs and logging for CF ValuesClass and RenderType
diff --git a/lib/RT/CustomField.pm b/lib/RT/CustomField.pm
index 1c0f715..ce28df3 100644
--- a/lib/RT/CustomField.pm
+++ b/lib/RT/CustomField.pm
@@ -737,7 +737,7 @@ to this Custom Field.
sub IsSelectionType {
my $self = shift;
- my $type = @_? shift : $self->Type;
+ my $type = @_ ? shift : $self->Type;
return undef unless $type;
return $FieldTypes{$type}->{selection_type};
}
@@ -759,6 +759,14 @@ sub ValuesClass {
return $self->_Value( ValuesClass => @_ ) || 'RT::CustomFieldValues';
}
+=head2 SetValuesClass CLASS
+
+Writer method for the ValuesClass field; validates that the custom field can
+use a ValuesClass, and that the provided ValuesClass passes
+L</ValidateValuesClass>.
+
+=cut
+
sub SetValuesClass {
my $self = shift;
my $class = shift || 'RT::CustomFieldValues';
@@ -776,6 +784,17 @@ sub SetValuesClass {
return $self->_Set( Field => 'ValuesClass', Value => $class, @_ );
}
+=head2 ValidateValuesClass CLASS
+
+Validates a potential ValuesClass value; the ValuesClass may be C<undef> or
+the string C<"RT::CustomFieldValues"> (both of which make this custom field
+use the ordinary values implementation), or a class name in the listed in
+the L<RT_Config/@CustomFieldValuesSources> setting.
+
+Returns true if valid; false if invalid.
+
+=cut
+
sub ValidateValuesClass {
my $self = shift;
my $class = shift;
diff --git a/share/html/Admin/CustomFields/Modify.html b/share/html/Admin/CustomFields/Modify.html
index a954fdd..a9d638c 100644
--- a/share/html/Admin/CustomFields/Modify.html
+++ b/share/html/Admin/CustomFields/Modify.html
@@ -257,6 +257,9 @@ if ( $ARGS{'Update'} && $id ne 'new' ) {
$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;
}
@@ -273,6 +276,9 @@ if ( $ARGS{'Update'} && $id ne 'new' ) {
$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;
}
commit 029b886b1f67e1c5137f621e3a8b2f559acd60a4
Author: Shawn M Moore <shawn at bestpractical.com>
Date: Fri May 27 19:21:13 2016 +0000
Add CanonicalizeClass to CustomField table
diff --git a/etc/schema.Oracle b/etc/schema.Oracle
index 9a75f4b..afa2c94 100644
--- a/etc/schema.Oracle
+++ b/etc/schema.Oracle
@@ -344,25 +344,26 @@ CREATE INDEX ObjectCustomFieldValues2 ON ObjectCustomFieldValues (CustomField,Ob
CREATE SEQUENCE CUSTOMFIELDS_seq;
CREATE TABLE CustomFields (
- id NUMBER(11,0)
+ id NUMBER(11,0)
CONSTRAINT CustomFields_Key PRIMARY KEY,
- Name VARCHAR2(200),
- Type VARCHAR2(200),
- RenderType VARCHAR2(64),
- MaxValues NUMBER(11,0) DEFAULT 0 NOT NULL,
- Pattern CLOB,
- ValuesClass VARCHAR2(64),
- BasedOn NUMBER(11,0) NULL,
- Description VARCHAR2(255),
- SortOrder NUMBER(11,0) DEFAULT 0 NOT NULL,
- LookupType VARCHAR2(255),
- EntryHint VARCHAR2(255) NULL,
- UniqueValues NUMBER(11,0) DEFAULT 0 NOT NULL,
- Creator NUMBER(11,0) DEFAULT 0 NOT NULL,
- Created DATE,
- LastUpdatedBy NUMBER(11,0) DEFAULT 0 NOT NULL,
- LastUpdated DATE,
- Disabled NUMBER(11,0) DEFAULT 0 NOT NULL
+ Name VARCHAR2(200),
+ Type VARCHAR2(200),
+ RenderType VARCHAR2(64),
+ MaxValues NUMBER(11,0) DEFAULT 0 NOT NULL,
+ Pattern CLOB,
+ ValuesClass VARCHAR2(64),
+ BasedOn NUMBER(11,0) NULL,
+ Description VARCHAR2(255),
+ SortOrder NUMBER(11,0) DEFAULT 0 NOT NULL,
+ LookupType VARCHAR2(255),
+ EntryHint VARCHAR2(255) NULL,
+ UniqueValues NUMBER(11,0) DEFAULT 0 NOT NULL,
+ CanonicalizeClass VARCHAR2(64),
+ Creator NUMBER(11,0) DEFAULT 0 NOT NULL,
+ Created DATE,
+ LastUpdatedBy NUMBER(11,0) DEFAULT 0 NOT NULL,
+ LastUpdated DATE,
+ Disabled NUMBER(11,0) DEFAULT 0 NOT NULL
);
diff --git a/etc/schema.Pg b/etc/schema.Pg
index 1c4f5a8..c758284 100644
--- a/etc/schema.Pg
+++ b/etc/schema.Pg
@@ -542,6 +542,7 @@ CREATE TABLE CustomFields (
Description varchar(255) NULL ,
SortOrder integer NOT NULL DEFAULT 0 ,
UniqueValues integer NOT NULL DEFAULT 0 ,
+ CanonicalizeClass varchar(64) NULL ,
Creator integer NOT NULL DEFAULT 0 ,
Created TIMESTAMP NULL ,
diff --git a/etc/schema.SQLite b/etc/schema.SQLite
index 4080598..3288a57 100644
--- a/etc/schema.SQLite
+++ b/etc/schema.SQLite
@@ -388,6 +388,7 @@ CREATE TABLE CustomFields (
LookupType varchar(255) collate NOCASE NOT NULL,
EntryHint varchar(255) NULL,
UniqueValues int2 NOT NULL DEFAULT 0,
+ CanonicalizeClass varchar(64) collate NOCASE NULL ,
Creator integer NOT NULL DEFAULT 0 ,
Created DATETIME NULL ,
diff --git a/etc/schema.mysql b/etc/schema.mysql
index f03862d..4baf28d 100644
--- a/etc/schema.mysql
+++ b/etc/schema.mysql
@@ -360,6 +360,7 @@ CREATE TABLE CustomFields (
LookupType varchar(255) CHARACTER SET ascii NOT NULL,
EntryHint varchar(255) NULL,
UniqueValues int2 NOT NULL DEFAULT 0 ,
+ CanonicalizeClass varchar(64) CHARACTER SET ascii NULL ,
Creator integer NOT NULL DEFAULT 0 ,
Created DATETIME NULL ,
diff --git a/etc/upgrade/4.4.2/schema.Oracle b/etc/upgrade/4.4.2/schema.Oracle
index b7ce195..095797e 100644
--- a/etc/upgrade/4.4.2/schema.Oracle
+++ b/etc/upgrade/4.4.2/schema.Oracle
@@ -1 +1,2 @@
ALTER TABLE CustomFields ADD UniqueValues NUMBER(11,0) DEFAULT 0 NOT NULL;
+ALTER TABLE CustomFields ADD CanonicalizeClass VARCHAR2(64);
diff --git a/etc/upgrade/4.4.2/schema.Pg b/etc/upgrade/4.4.2/schema.Pg
index 6da8b60..ce1825d 100644
--- a/etc/upgrade/4.4.2/schema.Pg
+++ b/etc/upgrade/4.4.2/schema.Pg
@@ -1 +1,2 @@
ALTER TABLE CustomFields ADD COLUMN UniqueValues integer NOT NULL DEFAULT 0;
+ALTER TABLE CustomFields ADD COLUMN CanonicalizeClass varchar(64) NULL;
diff --git a/etc/upgrade/4.4.2/schema.SQLite b/etc/upgrade/4.4.2/schema.SQLite
index 33540e7..4966485 100644
--- a/etc/upgrade/4.4.2/schema.SQLite
+++ b/etc/upgrade/4.4.2/schema.SQLite
@@ -1 +1,2 @@
ALTER TABLE CustomFields ADD COLUMN UniqueValues int2 NOT NULL DEFAULT 0;
+ALTER TABLE CustomFields ADD COLUMN CanonicalizeClass varchar(64) collate NOCASE NULL;
diff --git a/etc/upgrade/4.4.2/schema.mysql b/etc/upgrade/4.4.2/schema.mysql
index 33540e7..e359ded 100644
--- a/etc/upgrade/4.4.2/schema.mysql
+++ b/etc/upgrade/4.4.2/schema.mysql
@@ -1 +1,2 @@
ALTER TABLE CustomFields ADD COLUMN UniqueValues int2 NOT NULL DEFAULT 0;
+ALTER TABLE CustomFields ADD COLUMN CanonicalizeClass varchar(64) CHARACTER SET ascii NULL;
diff --git a/lib/RT/CustomField.pm b/lib/RT/CustomField.pm
index ce28df3..9149c68 100644
--- a/lib/RT/CustomField.pm
+++ b/lib/RT/CustomField.pm
@@ -2487,6 +2487,8 @@ sub _CoreAccessible {
{read => 1, write => 1, sql_type => 12, length => 255, is_blob => 0, is_numeric => 0, type => 'varchar(255)', default => undef },
UniqueValues =>
{read => 1, write => 1, sql_type => 5, length => 6, is_blob => 0, is_numeric => 1, type => 'smallint(6)', default => '0'},
+ CanonicalizeClass =>
+ {read => 1, write => 1, sql_type => 12, length => 64, is_blob => 0, is_numeric => 0, type => 'varchar(64)', default => ''},
Creator =>
{read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
Created =>
commit 53599b17f47e388af025319d099874de0c8d8989
Author: Shawn M Moore <shawn at bestpractical.com>
Date: Fri May 27 19:31:37 2016 +0000
Scaffolding for CustomField CanonicalizeClass
diff --git a/etc/RT_Config.pm.in b/etc/RT_Config.pm.in
index 76c337d..3c62781 100644
--- a/etc/RT_Config.pm.in
+++ b/etc/RT_Config.pm.in
@@ -909,6 +909,19 @@ custom field values from external sources at runtime.
Set(@CustomFieldValuesSources, ());
+=item C<@CustomFieldValuesCanonicalizers>
+
+Set C<@CustomFieldValuesCanonicalizers> to a list of class names which extend
+L<RT::CustomFieldValues::Canonicalizer>. This can be used to rewrite
+(canonicalize) values entered by users to fit some defined format.
+
+See the documentation in L<RT::CustomFieldValues::Canonicalizer> for adding
+your own canonicalizers.
+
+=cut
+
+Set(@CustomFieldValuesCanonicalizers, ());
+
=item C<%CustomFieldGroupings>
This option affects the display of ticket and user custom fields in the
diff --git a/lib/RT.pm b/lib/RT.pm
index ccf3c54..09d6873 100644
--- a/lib/RT.pm
+++ b/lib/RT.pm
@@ -484,6 +484,7 @@ sub InitClasses {
require RT::Catalogs;
require RT::Asset;
require RT::Assets;
+ require RT::CustomFieldValues::Canonicalizer;
_BuildTableAttributes();
diff --git a/lib/RT/Config.pm b/lib/RT/Config.pm
index 70df38f..9db2980 100644
--- a/lib/RT/Config.pm
+++ b/lib/RT/Config.pm
@@ -805,6 +805,7 @@ our %META;
GnuPGOptions => { Type => 'HASH' },
ReferrerWhitelist => { Type => 'ARRAY' },
EmailDashboardLanguageOrder => { Type => 'ARRAY' },
+ CustomFieldValuesCanonicalizers => { Type => 'ARRAY' },
WebPath => {
PostLoadCheck => sub {
my $self = shift;
diff --git a/lib/RT/CustomField.pm b/lib/RT/CustomField.pm
index 9149c68..043c942 100644
--- a/lib/RT/CustomField.pm
+++ b/lib/RT/CustomField.pm
@@ -70,6 +70,7 @@ our %FieldTypes = (
Select => {
sort_order => 10,
selection_type => 1,
+ canonicalizes => 0,
labels => [ 'Select multiple values', # loc
'Select one value', # loc
@@ -93,6 +94,7 @@ our %FieldTypes = (
Freeform => {
sort_order => 20,
selection_type => 0,
+ canonicalizes => 1,
labels => [ 'Enter multiple values', # loc
'Enter one value', # loc
@@ -102,6 +104,7 @@ our %FieldTypes = (
Text => {
sort_order => 30,
selection_type => 0,
+ canonicalizes => 1,
labels => [
'Fill in multiple text areas', # loc
'Fill in one text area', # loc
@@ -111,6 +114,7 @@ our %FieldTypes = (
Wikitext => {
sort_order => 40,
selection_type => 0,
+ canonicalizes => 1,
labels => [
'Fill in multiple wikitext areas', # loc
'Fill in one wikitext area', # loc
@@ -121,6 +125,7 @@ our %FieldTypes = (
Image => {
sort_order => 50,
selection_type => 0,
+ canonicalizes => 0,
labels => [
'Upload multiple images', # loc
'Upload one image', # loc
@@ -130,6 +135,7 @@ our %FieldTypes = (
Binary => {
sort_order => 60,
selection_type => 0,
+ canonicalizes => 0,
labels => [
'Upload multiple files', # loc
'Upload one file', # loc
@@ -140,6 +146,7 @@ our %FieldTypes = (
Combobox => {
sort_order => 70,
selection_type => 1,
+ canonicalizes => 1,
labels => [
'Combobox: Select or enter multiple values', # loc
'Combobox: Select or enter one value', # loc
@@ -149,6 +156,7 @@ our %FieldTypes = (
Autocomplete => {
sort_order => 80,
selection_type => 1,
+ canonicalizes => 1,
labels => [
'Enter multiple values with autocompletion', # loc
'Enter one value with autocompletion', # loc
@@ -159,6 +167,7 @@ our %FieldTypes = (
Date => {
sort_order => 90,
selection_type => 0,
+ canonicalizes => 0,
labels => [
'Select multiple dates', # loc
'Select date', # loc
@@ -168,6 +177,7 @@ our %FieldTypes = (
DateTime => {
sort_order => 100,
selection_type => 0,
+ canonicalizes => 0,
labels => [
'Select multiple datetimes', # loc
'Select datetime', # loc
@@ -178,6 +188,7 @@ our %FieldTypes = (
IPAddress => {
sort_order => 110,
selection_type => 0,
+ canonicalizes => 0,
labels => [ 'Enter multiple IP addresses', # loc
'Enter one IP address', # loc
@@ -187,6 +198,7 @@ our %FieldTypes = (
IPAddressRange => {
sort_order => 120,
selection_type => 0,
+ canonicalizes => 0,
labels => [ 'Enter multiple IP address ranges', # loc
'Enter one IP address range', # loc
@@ -246,17 +258,18 @@ C<RT::Ticket->CustomFieldLookupType> or C<RT::Transaction->CustomFieldLookupType
sub Create {
my $self = shift;
my %args = (
- Name => '',
- Type => '',
- MaxValues => 0,
- Pattern => '',
- Description => '',
- Disabled => 0,
- LookupType => '',
- LinkValueTo => '',
+ Name => '',
+ Type => '',
+ MaxValues => 0,
+ Pattern => '',
+ Description => '',
+ Disabled => 0,
+ LookupType => '',
+ LinkValueTo => '',
IncludeContentForValue => '',
- EntryHint => undef,
- UniqueValues => 0,
+ EntryHint => undef,
+ UniqueValues => 0,
+ CanonicalizeClass => undef,
@_,
);
@@ -326,20 +339,30 @@ sub Create {
}
}
+ if ( $args{'CanonicalizeClass'} ||= undef ) {
+ return (0, $self->loc("This custom field can not have a canonicalizer"))
+ unless $self->IsCanonicalizeType( $args{'Type'} );
+
+ unless ( $self->ValidateCanonicalizeClass( $args{'CanonicalizeClass'} ) ) {
+ return (0, $self->loc("Invalid custom field values canonicalizer"));
+ }
+ }
+
$args{'Disabled'} ||= 0;
(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'},
- UniqueValues => $args{'UniqueValues'},
+ 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'},
+ UniqueValues => $args{'UniqueValues'},
+ CanonicalizeClass => $args{'CanonicalizeClass'},
);
if ($rv) {
@@ -730,7 +753,7 @@ sub Types {
=head2 IsSelectionType
-Retuns a boolean value indicating whether the C<Values> method makes sense
+Returns a boolean value indicating whether the C<Values> method makes sense
to this Custom Field.
=cut
@@ -742,6 +765,19 @@ sub IsSelectionType {
return $FieldTypes{$type}->{selection_type};
}
+=head2 IsCanonicalizeType
+
+Returns a boolean value indicating whether the type of this custom field
+permits using a canonicalizer.
+
+=cut
+
+sub IsCanonicalizeType {
+ my $self = shift;
+ my $type = @_ ? shift : $self->Type;
+ return undef unless $type;
+ return $FieldTypes{$type}->{canonicalizes};
+}
=head2 IsExternalValues
@@ -804,6 +840,50 @@ sub ValidateValuesClass {
return undef;
}
+=head2 SetCanonicalizeClass CLASS
+
+Writer method for the CanonicalizeClass field; validates that the custom
+field can use a CanonicalizeClass, and that the provided CanonicalizeClass
+passes L</ValidateCanonicalizeClass>.
+
+=cut
+
+sub SetCanonicalizeClass {
+ my $self = shift;
+ my $class = shift;
+
+ if ( !$class ) {
+ return $self->_Set( Field => 'CanonicalizeClass', Value => undef, @_ );
+ }
+
+ return (0, $self->loc("This custom field can not have a canonicalizer"))
+ unless $self->IsCanonicalizeType;
+
+ unless ( $self->ValidateCanonicalizeClass( $class ) ) {
+ return (0, $self->loc("Invalid custom field values canonicalizer"));
+ }
+ return $self->_Set( Field => 'CanonicalizeClass', Value => $class, @_ );
+}
+
+=head2 ValidateCanonicalizeClass CLASS
+
+Validates a potential CanonicalizeClass value; the CanonicalizeClass may be
+C<undef> (which make this custom field use no special canonicalization), or
+a class name in the listed in the
+L<RT_Config/@CustomFieldValuesCanonicalizers> setting.
+
+Returns true if valid; false if invalid.
+
+=cut
+
+sub ValidateCanonicalizeClass {
+ my $self = shift;
+ my $class = shift;
+
+ return 1 if !$class;
+ return 1 if grep $class eq $_, RT->Config->Get('CustomFieldValuesCanonicalizers');
+ return undef;
+}
=head2 FriendlyType [TYPE, MAX_VALUES]
diff --git a/lib/RT/CustomFieldValues/Canonicalizer.pm b/lib/RT/CustomFieldValues/Canonicalizer.pm
new file mode 100644
index 0000000..1bb2df5
--- /dev/null
+++ b/lib/RT/CustomFieldValues/Canonicalizer.pm
@@ -0,0 +1,118 @@
+# BEGIN BPS TAGGED BLOCK {{{
+#
+# COPYRIGHT:
+#
+# This software is Copyright (c) 1996-2016 Best Practical Solutions, LLC
+# <sales at bestpractical.com>
+#
+# (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 }}}
+
+package RT::CustomFieldValues::Canonicalizer;
+
+use strict;
+use warnings;
+use base 'RT::Base';
+
+=head1 NAME
+
+RT::CustomFieldValues::Canonicalizer - base class for custom field value
+canonicalizers
+
+=head1 SYNOPSIS
+
+=head1 DESCRIPTION
+
+This class is the base class for custom field value canonicalizers. To
+implement a new canonicalizer, you must create a new class that subclasses
+this class. Your subclass must implement the methods L</CanonicalizeValue>
+and L</Description> as documented below. Finally, add the new class name to
+L<RT_Config/@CustomFieldValuesCanonicalizers>.
+
+=head2 new
+
+The object constructor takes one argument: L<RT::CurrentUser> object.
+
+=cut
+
+sub new {
+ my $proto = shift;
+ my $class = ref($proto) || $proto;
+ my $self = {};
+ bless ($self, $class);
+ $self->CurrentUser(@_);
+ return $self;
+}
+
+=head2 CanonicalizeValue
+
+Receives a parameter hash including C<CustomField> (an L<RT::CustomField>
+object) and C<Content> (a string of user-provided content).
+
+You may also access C<< $self->CurrentUser >> in case you need the user's
+language or locale.
+
+This method is expected to return the canonicalized C<Content>.
+
+=cut
+
+sub CanonicalizeValue {
+ my $self = shift;
+ die "Subclass " . ref($self) . " of " . __PACKAGE__ . " does not implement required method CanonicalizeValue";
+}
+
+=head2 Description
+
+A class method that returns the human-friendly name for this canonicalizer
+which appears in the admin UI. By default it is the class name, which is
+not so human friendly. You should override this in your subclass.
+
+=cut
+
+sub Description {
+ my $class = shift;
+ return $class;
+}
+
+RT::Base->_ImportOverlays();
+
+1;
+
commit b9918899557c7c7f6d6e063eb250e396f001af9c
Author: Shawn M Moore <shawn at bestpractical.com>
Date: Fri May 27 19:51:59 2016 +0000
Admin UI for CF canonicalizer
diff --git a/share/html/Admin/CustomFields/Modify.html b/share/html/Admin/CustomFields/Modify.html
index a9d638c..cd49eb7 100644
--- a/share/html/Admin/CustomFields/Modify.html
+++ b/share/html/Admin/CustomFields/Modify.html
@@ -91,6 +91,12 @@
</td></tr>
% }
+% if ( $CustomFieldObj->Id and $CustomFieldObj->IsCanonicalizeType and RT->Config->Get('CustomFieldValuesCanonicalizers') and ( scalar(@{RT->Config->Get('CustomFieldValuesCanonicalizers')}) > 0 ) ) {
+<tr><td class="label"><&|/l&>Canonicalizer:</&></td><td>
+<& /Admin/Elements/EditCustomFieldValuesCanonicalizer, CustomField => $CustomFieldObj &>
+</td></tr>
+% }
+
<tr><td class="label"><&|/l&>Applies to</&></td>
<td><& /Admin/Elements/SelectCustomFieldLookupType,
Name => "LookupType",
@@ -262,6 +268,35 @@ if ( $ARGS{'Update'} && $id ne 'new' ) {
}
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 && $original->require;
+ $CanonicalizeClass = $CanonicalizeClass->Description
+ if $CanonicalizeClass && $CanonicalizeClass->require;
+
+ 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
@@ -397,6 +432,7 @@ $Enabled => 0
$SetUniqueValues => undef
$UniqueValues => 0
$ValuesClass => 'RT::CustomFieldValues'
+$CanonicalizeClass => undef
$RenderType => undef
$LinkValueTo => undef
$IncludeContentForValue => undef
diff --git a/share/html/Admin/Elements/EditCustomFieldValuesCanonicalizer b/share/html/Admin/Elements/EditCustomFieldValuesCanonicalizer
new file mode 100644
index 0000000..541fdb0
--- /dev/null
+++ b/share/html/Admin/Elements/EditCustomFieldValuesCanonicalizer
@@ -0,0 +1,77 @@
+%# BEGIN BPS TAGGED BLOCK {{{
+%#
+%# COPYRIGHT:
+%#
+%# This software is Copyright (c) 1996-2016 Best Practical Solutions, LLC
+%# <sales at bestpractical.com>
+%#
+%# (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 }}}
+<div id="canonicalize-class-block">
+<select name="CanonicalizeClass">
+<option value="">-</option>
+% foreach my $canonicalizer (@canonicalizers) {
+<option value="<% $canonicalizer %>" <% $canonicalizer eq ($CustomField->CanonicalizeClass||'') && 'selected="selected"' %>><% $canonicalizer->Description %></option>
+% }
+</select>
+</div>
+
+<%INIT>
+return unless $CustomField->IsCanonicalizeType;
+
+my @canonicalizers;
+foreach my $class( RT->Config->Get('CustomFieldValuesCanonicalizers') ) {
+ next unless $class;
+
+ unless ($class->require) {
+ $RT::Logger->crit("Couldn't load class '$class': $@");
+ next;
+ }
+
+ push @canonicalizers, $class;
+}
+
+return if !@canonicalizers;
+
+</%INIT>
+<%ARGS>
+$CustomField => undef
+</%ARGS>
commit d2857302617599e6037da8b23a075a42bfe34ec7
Author: Shawn M Moore <shawn at bestpractical.com>
Date: Fri May 27 21:25:06 2016 +0000
Implement custom field canonicalization
diff --git a/lib/RT/CustomField.pm b/lib/RT/CustomField.pm
index 043c942..fee8419 100644
--- a/lib/RT/CustomField.pm
+++ b/lib/RT/CustomField.pm
@@ -1826,11 +1826,31 @@ sub _CanonicalizeValue {
my $type = $self->__Value('Type');
return 1 unless $type;
+ $self->_CanonicalizeValueWithCanonicalizer($args);
+
my $method = '_CanonicalizeValue'. $type;
return 1 unless $self->can($method);
$self->$method($args);
}
+sub _CanonicalizeValueWithCanonicalizer {
+ my $self = shift;
+ my $args = shift;
+
+ return 1 if !$self->CanonicalizeClass;
+
+ my $class = $self->CanonicalizeClass;
+ $class->require or die "Can't load $class: $@";
+ my $canonicalizer = $class->new($self->CurrentUser);
+
+ $args->{'Content'} = $canonicalizer->CanonicalizeValue(
+ CustomField => $self,
+ Content => $args->{'Content'},
+ );
+
+ return 1;
+}
+
sub _CanonicalizeValueDateTime {
my $self = shift;
my $args = shift;
commit 30c7bf5db081b61336db9563d3c4f4f4431229b7
Author: Shawn M Moore <shawn at bestpractical.com>
Date: Fri May 27 21:25:20 2016 +0000
Ship Uppercase and Lowercase canonicalizers in core
diff --git a/etc/RT_Config.pm.in b/etc/RT_Config.pm.in
index 3c62781..a3de206 100644
--- a/etc/RT_Config.pm.in
+++ b/etc/RT_Config.pm.in
@@ -920,7 +920,10 @@ your own canonicalizers.
=cut
-Set(@CustomFieldValuesCanonicalizers, ());
+Set(@CustomFieldValuesCanonicalizers, qw(
+ RT::CustomFieldValues::Canonicalizer::Uppercase
+ RT::CustomFieldValues::Canonicalizer::Lowercase
+));
=item C<%CustomFieldGroupings>
diff --git a/lib/RT/CustomFieldValues/Canonicalizer.pm b/lib/RT/CustomFieldValues/Canonicalizer.pm
index 1bb2df5..03afbc1 100644
--- a/lib/RT/CustomFieldValues/Canonicalizer.pm
+++ b/lib/RT/CustomFieldValues/Canonicalizer.pm
@@ -67,6 +67,9 @@ this class. Your subclass must implement the methods L</CanonicalizeValue>
and L</Description> as documented below. Finally, add the new class name to
L<RT_Config/@CustomFieldValuesCanonicalizers>.
+See L<RT::CustomFieldValues::Canonicalizer::Uppercase> for a complete
+example.
+
=head2 new
The object constructor takes one argument: L<RT::CurrentUser> object.
diff --git a/lib/RT/CustomFieldValues/Canonicalizer.pm b/lib/RT/CustomFieldValues/Canonicalizer/Lowercase.pm
similarity index 58%
copy from lib/RT/CustomFieldValues/Canonicalizer.pm
copy to lib/RT/CustomFieldValues/Canonicalizer/Lowercase.pm
index 1bb2df5..d1e8e1a 100644
--- a/lib/RT/CustomFieldValues/Canonicalizer.pm
+++ b/lib/RT/CustomFieldValues/Canonicalizer/Lowercase.pm
@@ -46,72 +46,39 @@
#
# END BPS TAGGED BLOCK }}}
-package RT::CustomFieldValues::Canonicalizer;
+package RT::CustomFieldValues::Canonicalizer::Lowercase;
use strict;
use warnings;
-use base 'RT::Base';
-=head1 NAME
-
-RT::CustomFieldValues::Canonicalizer - base class for custom field value
-canonicalizers
-
-=head1 SYNOPSIS
-
-=head1 DESCRIPTION
+use base qw(RT::CustomFieldValues::Canonicalizer);
-This class is the base class for custom field value canonicalizers. To
-implement a new canonicalizer, you must create a new class that subclasses
-this class. Your subclass must implement the methods L</CanonicalizeValue>
-and L</Description> as documented below. Finally, add the new class name to
-L<RT_Config/@CustomFieldValuesCanonicalizers>.
+=encoding utf-8
-=head2 new
-
-The object constructor takes one argument: L<RT::CurrentUser> object.
-
-=cut
-
-sub new {
- my $proto = shift;
- my $class = ref($proto) || $proto;
- my $self = {};
- bless ($self, $class);
- $self->CurrentUser(@_);
- return $self;
-}
-
-=head2 CanonicalizeValue
+=head1 NAME
-Receives a parameter hash including C<CustomField> (an L<RT::CustomField>
-object) and C<Content> (a string of user-provided content).
+RT::CustomFieldValues::Canonicalizer::Lowercase - lowercase custom field values
-You may also access C<< $self->CurrentUser >> in case you need the user's
-language or locale.
+=head1 DESCRIPTION
-This method is expected to return the canonicalized C<Content>.
+This canonicalizer adjusts the custom field value to have all lowercase
+characters. It is Unicode-aware, so for example "Ω" will become "ω".
=cut
sub CanonicalizeValue {
my $self = shift;
- die "Subclass " . ref($self) . " of " . __PACKAGE__ . " does not implement required method CanonicalizeValue";
-}
-
-=head2 Description
+ my %args = (
+ CustomField => undef,
+ Content => undef,
+ @_,
+ );
-A class method that returns the human-friendly name for this canonicalizer
-which appears in the admin UI. By default it is the class name, which is
-not so human friendly. You should override this in your subclass.
-
-=cut
-
-sub Description {
- my $class = shift;
- return $class;
+ return lc $args{Content};
}
+sub Description { "Lowercase" }
+
RT::Base->_ImportOverlays();
1;
diff --git a/lib/RT/CustomFieldValues/Canonicalizer.pm b/lib/RT/CustomFieldValues/Canonicalizer/Uppercase.pm
similarity index 58%
copy from lib/RT/CustomFieldValues/Canonicalizer.pm
copy to lib/RT/CustomFieldValues/Canonicalizer/Uppercase.pm
index 1bb2df5..d54f399 100644
--- a/lib/RT/CustomFieldValues/Canonicalizer.pm
+++ b/lib/RT/CustomFieldValues/Canonicalizer/Uppercase.pm
@@ -46,72 +46,39 @@
#
# END BPS TAGGED BLOCK }}}
-package RT::CustomFieldValues::Canonicalizer;
+package RT::CustomFieldValues::Canonicalizer::Uppercase;
use strict;
use warnings;
-use base 'RT::Base';
-=head1 NAME
-
-RT::CustomFieldValues::Canonicalizer - base class for custom field value
-canonicalizers
-
-=head1 SYNOPSIS
-
-=head1 DESCRIPTION
+use base qw(RT::CustomFieldValues::Canonicalizer);
-This class is the base class for custom field value canonicalizers. To
-implement a new canonicalizer, you must create a new class that subclasses
-this class. Your subclass must implement the methods L</CanonicalizeValue>
-and L</Description> as documented below. Finally, add the new class name to
-L<RT_Config/@CustomFieldValuesCanonicalizers>.
+=encoding utf-8
-=head2 new
-
-The object constructor takes one argument: L<RT::CurrentUser> object.
-
-=cut
-
-sub new {
- my $proto = shift;
- my $class = ref($proto) || $proto;
- my $self = {};
- bless ($self, $class);
- $self->CurrentUser(@_);
- return $self;
-}
-
-=head2 CanonicalizeValue
+=head1 NAME
-Receives a parameter hash including C<CustomField> (an L<RT::CustomField>
-object) and C<Content> (a string of user-provided content).
+RT::CustomFieldValues::Canonicalizer::Uppercase - uppercase custom field values
-You may also access C<< $self->CurrentUser >> in case you need the user's
-language or locale.
+=head1 DESCRIPTION
-This method is expected to return the canonicalized C<Content>.
+This canonicalizer adjusts the custom field value to have all uppercase
+characters. It is Unicode-aware, so for example "ω" will become "Ω".
=cut
sub CanonicalizeValue {
my $self = shift;
- die "Subclass " . ref($self) . " of " . __PACKAGE__ . " does not implement required method CanonicalizeValue";
-}
-
-=head2 Description
+ my %args = (
+ CustomField => undef,
+ Content => undef,
+ @_,
+ );
-A class method that returns the human-friendly name for this canonicalizer
-which appears in the admin UI. By default it is the class name, which is
-not so human friendly. You should override this in your subclass.
-
-=cut
-
-sub Description {
- my $class = shift;
- return $class;
+ return uc $args{Content};
}
+sub Description { "Uppercase" }
+
RT::Base->_ImportOverlays();
1;
commit baa8e1f7f3a5cf6140abee74864cf86aff4a78a8
Author: Shawn M Moore <shawn at bestpractical.com>
Date: Fri May 27 21:35:34 2016 +0000
Canonicalize default values on custom fields
diff --git a/lib/RT/CustomField.pm b/lib/RT/CustomField.pm
index fee8419..4cbdbab 100644
--- a/lib/RT/CustomField.pm
+++ b/lib/RT/CustomField.pm
@@ -1131,7 +1131,7 @@ sub _Set {
return ( 0, $self->loc('Permission Denied') );
}
my ($ret, $msg) = $self->SUPER::_Set( @_ );
- if ( $args{Field} =~ /^(?:MaxValues|Type|LookupType|ValuesClass)$/ ) {
+ if ( $args{Field} =~ /^(?:MaxValues|Type|LookupType|ValuesClass|CanonicalizeClass)$/ ) {
$self->CleanupDefaultValues;
}
return ($ret, $msg);
@@ -2309,6 +2309,30 @@ sub CleanupDefaultValues {
$content->{ $self->id } = join "\n", @$default_values;
$changed = 1;
}
+
+ if ($self->MaxValues == 1) {
+ my $args = { Content => $default_values };
+ $self->_CanonicalizeValueWithCanonicalizer($args);
+ if ($args->{Content} ne $default_values) {
+ $content->{ $self->id } = $default_values;
+ $changed = 1;
+ }
+ }
+ else {
+ my @new_values;
+ my $multi_changed = 0;
+ for my $value (split /\s*\n+\s*/, $default_values) {
+ my $args = { Content => $value };
+ $self->_CanonicalizeValueWithCanonicalizer($args);
+ push @new_values, $args->{Content};
+ $multi_changed = 1 if $args->{Content} ne $value;
+ }
+
+ if ($multi_changed) {
+ $content->{ $self->id } = join "\n", @new_values;
+ $changed = 1;
+ }
+ }
}
}
}
commit f7f2d9070e61198627fbea4dff679549717f3703
Author: Shawn M Moore <shawn at bestpractical.com>
Date: Wed Jun 1 16:54:35 2016 +0000
Tests for custom field canonicalizers
diff --git a/t/customfields/canonicalizer.t b/t/customfields/canonicalizer.t
new file mode 100644
index 0000000..5451de9
--- /dev/null
+++ b/t/customfields/canonicalizer.t
@@ -0,0 +1,113 @@
+use utf8;
+use warnings;
+use strict;
+
+use RT::Test tests => undef;
+
+my $t = RT::Test->create_ticket( Subject => 'test canonicalize values', Queue => 'General' );
+
+{
+ diag "testing invalid canonicalizer";
+ my $invalid = RT::CustomField->new(RT->SystemUser);
+ my ($ok, $msg) = $invalid->Create(
+ Name => 'uppercase',
+ Type => 'FreeformSingle',
+ Queue => 0,
+ CanonicalizeClass => 'RT::CustomFieldValues::Canonicalizer::NonExistent',
+ );
+ ok(!$ok, "Didn't create CF");
+ like($msg, qr/Invalid custom field values canonicalizer/);
+}
+
+{
+ diag "testing uppercase canonicalizer";
+ my $uppercase = RT::Test->load_or_create_custom_field(
+ Name => 'uppercase',
+ Type => 'FreeformSingle',
+ Queue => 0,
+ CanonicalizeClass => 'RT::CustomFieldValues::Canonicalizer::Uppercase',
+ );
+ is($uppercase->CanonicalizeClass, 'RT::CustomFieldValues::Canonicalizer::Uppercase', 'CanonicalizeClass');
+
+ my @tests = (
+ 'hello world' => 'HELLO WORLD',
+ 'Hello World' => 'HELLO WORLD',
+ 'ABC 123 xyz !@#' => 'ABC 123 XYZ !@#',
+ 'Unicode aware: "ω Ω"' => 'UNICODE AWARE: "Ω Ω"',
+ 'てすとテスト' => 'てすとテスト',
+ );
+
+ while (my ($input, $expected) = splice @tests, 0, 2) {
+ my ($ok, $msg) = $t->AddCustomFieldValue(
+ Field => $uppercase,
+ Value => $input,
+ );
+ ok( $ok, $msg );
+ is( $t->FirstCustomFieldValue($uppercase), $expected, 'canonicalized to uppercase' );
+ }
+}
+
+{
+ diag "testing lowercase canonicalizer";
+ my $lowercase = RT::Test->load_or_create_custom_field(
+ Name => 'lowercase',
+ Type => 'FreeformSingle',
+ Queue => 0,
+ CanonicalizeClass => 'RT::CustomFieldValues::Canonicalizer::Lowercase',
+ );
+ is($lowercase->CanonicalizeClass, 'RT::CustomFieldValues::Canonicalizer::Lowercase', 'CanonicalizeClass');
+
+ my @tests = (
+ 'hello world' => 'hello world',
+ 'Hello World' => 'hello world',
+ 'ABC 123 xyz !@#' => 'abc 123 xyz !@#',
+ 'Unicode aware: "ω Ω"' => 'unicode aware: "ω ω"',
+ 'てすとテスト' => 'てすとテスト',
+ );
+
+ while (my ($input, $expected) = splice @tests, 0, 2) {
+ my ($ok, $msg) = $t->AddCustomFieldValue(
+ Field => $lowercase,
+ Value => $input,
+ );
+ ok( $ok, $msg );
+ is( $t->FirstCustomFieldValue($lowercase), $expected, 'canonicalized to lowercase' );
+ }
+}
+
+{
+ diag "testing asset canonicalizer";
+
+ my $assetcf = RT::Test->load_or_create_custom_field(
+ Name => 'assetcf',
+ Type => 'FreeformSingle',
+ LookupType => RT::Asset->CustomFieldLookupType,
+ CanonicalizeClass => 'RT::CustomFieldValues::Canonicalizer::Uppercase',
+ );
+ $assetcf->AddToObject(RT::Catalog->new(RT->SystemUser));
+ is($assetcf->CanonicalizeClass, 'RT::CustomFieldValues::Canonicalizer::Uppercase', 'CanonicalizeClass');
+
+ my $asset = RT::Asset->new(RT->SystemUser);
+ my ($ok, $msg) = $asset->Create(Subject => 'test canonicalizers', Catalog => 'General assets');
+ ok($ok, $msg);
+
+ my @tests = (
+ 'hello world' => 'HELLO WORLD',
+ 'Hello World' => 'HELLO WORLD',
+ 'ABC 123 xyz !@#' => 'ABC 123 XYZ !@#',
+ 'Unicode aware: "ω Ω"' => 'UNICODE AWARE: "Ω Ω"',
+ 'てすとテスト' => 'てすとテスト',
+ );
+
+ while (my ($input, $expected) = splice @tests, 0, 2) {
+ my ($ok, $msg) = $asset->AddCustomFieldValue(
+ Field => $assetcf,
+ Value => $input,
+ );
+ ok( $ok, $msg );
+ is( $asset->FirstCustomFieldValue($assetcf), $expected, 'canonicalized to uppercase' );
+ }
+}
+
+done_testing;
+
-----------------------------------------------------------------------
More information about the rt-commit
mailing list