[Rt-commit] rt branch, 4.4-trunk, updated. rt-4.4.0-246-gb3e09a1

Shawn Moore shawn at bestpractical.com
Fri Jun 10 13:16:03 EDT 2016


The branch, 4.4-trunk has been updated
       via  b3e09a15d3e45c66e490de0d4193fd8b425ae057 (commit)
       via  22e5a213043e5d11b392c773c8d1d0505e1d5dc0 (commit)
       via  2e496cd2552685c7591c012d66e4ab8ca7a59f30 (commit)
       via  49bc1f1207831552ef1d981da49dc8850bdba488 (commit)
       via  bb7bb2a8daf4450fb1d1696bc1d01cb5e29e8c5a (commit)
       via  ab2ef40c8d828d0614b91c9402f6a19ba33fe26b (commit)
       via  230e866e088631b2a8629e0f0a258d7f67bb051a (commit)
       via  029b886b1f67e1c5137f621e3a8b2f559acd60a4 (commit)
       via  aa6e1a0830736aec829ed625c0d6f4d3479aa23e (commit)
      from  ed6bfab65be5f6fac0f44d0fd9ca044f6be26a0d (commit)

Summary of changes:
 etc/RT_Config.pm.in                                |  16 ++
 etc/schema.Oracle                                  |  37 ++--
 etc/schema.Pg                                      |   1 +
 etc/schema.SQLite                                  |   1 +
 etc/schema.mysql                                   |   1 +
 etc/upgrade/4.4.2/schema.Oracle                    |   1 +
 etc/upgrade/4.4.2/schema.Pg                        |   1 +
 etc/upgrade/4.4.2/schema.SQLite                    |   1 +
 etc/upgrade/4.4.2/schema.mysql                     |   1 +
 lib/RT.pm                                          |   1 +
 lib/RT/Config.pm                                   |   1 +
 lib/RT/CustomField.pm                              | 193 ++++++++++++++++++---
 .../Canonicalizer.pm}                              | 104 ++++-------
 .../Canonicalizer/Lowercase.pm}                    |  44 +++--
 .../Canonicalizer/Uppercase.pm}                    |  44 +++--
 share/html/Admin/CustomFields/Modify.html          |  42 +++++
 .../Elements/EditCustomFieldValuesCanonicalizer}   |  40 ++---
 t/customfields/canonicalizer.t                     | 113 ++++++++++++
 18 files changed, 465 insertions(+), 177 deletions(-)
 copy lib/RT/{SharedSettings.pm => CustomFieldValues/Canonicalizer.pm} (61%)
 copy lib/RT/{Principals.pm => CustomFieldValues/Canonicalizer/Lowercase.pm} (80%)
 copy lib/RT/{Principals.pm => CustomFieldValues/Canonicalizer/Uppercase.pm} (80%)
 copy share/html/{Elements/SelectWatcherType => Admin/Elements/EditCustomFieldValuesCanonicalizer} (76%)
 create mode 100644 t/customfields/canonicalizer.t

- Log -----------------------------------------------------------------
commit 230e866e088631b2a8629e0f0a258d7f67bb051a
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..f03de76
--- /dev/null
+++ b/lib/RT/CustomFieldValues/Canonicalizer.pm
@@ -0,0 +1,119 @@
+# 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 method L</CanonicalizeValue> as
+documented below. You should also implement the method L</Description> which
+is the label shown to users. 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 ab2ef40c8d828d0614b91c9402f6a19ba33fe26b
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 bb7bb2a8daf4450fb1d1696bc1d01cb5e29e8c5a
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 49bc1f1207831552ef1d981da49dc8850bdba488
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 f03de76..5615eb7 100644
--- a/lib/RT/CustomFieldValues/Canonicalizer.pm
+++ b/lib/RT/CustomFieldValues/Canonicalizer.pm
@@ -68,6 +68,9 @@ documented below. You should also implement the method L</Description> which
 is the label shown to users. 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 57%
copy from lib/RT/CustomFieldValues/Canonicalizer.pm
copy to lib/RT/CustomFieldValues/Canonicalizer/Lowercase.pm
index f03de76..d1e8e1a 100644
--- a/lib/RT/CustomFieldValues/Canonicalizer.pm
+++ b/lib/RT/CustomFieldValues/Canonicalizer/Lowercase.pm
@@ -46,73 +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 method L</CanonicalizeValue> as
-documented below. You should also implement the method L</Description> which
-is the label shown to users. 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 57%
copy from lib/RT/CustomFieldValues/Canonicalizer.pm
copy to lib/RT/CustomFieldValues/Canonicalizer/Uppercase.pm
index f03de76..d54f399 100644
--- a/lib/RT/CustomFieldValues/Canonicalizer.pm
+++ b/lib/RT/CustomFieldValues/Canonicalizer/Uppercase.pm
@@ -46,73 +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 method L</CanonicalizeValue> as
-documented below. You should also implement the method L</Description> which
-is the label shown to users. 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 2e496cd2552685c7591c012d66e4ab8ca7a59f30
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 22e5a213043e5d11b392c773c8d1d0505e1d5dc0
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;
+

commit b3e09a15d3e45c66e490de0d4193fd8b425ae057
Merge: ed6bfab 22e5a21
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Fri Jun 10 17:15:58 2016 +0000

    Merge branch '4.4/canonicalize-custom-fields' into 4.4-trunk


-----------------------------------------------------------------------


More information about the rt-commit mailing list