[Rt-commit] rt branch 5.0/article-html-content created. rt-5.0.3-446-g9dde8d42b4

BPS Git Server git at git.bestpractical.com
Mon Apr 10 19:44:53 UTC 2023


This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "rt".

The branch, 5.0/article-html-content has been created
        at  9dde8d42b460da898e5abf6f6a99e56ddf604fe2 (commit)

- Log -----------------------------------------------------------------
commit 9dde8d42b460da898e5abf6f6a99e56ddf604fe2
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Wed Mar 16 02:30:12 2022 +0800

    Test scrubbing custom field values on save

diff --git a/t/web/cf_textarea.t b/t/web/cf_textarea.t
index 85df90969f..3ca8d62dc6 100644
--- a/t/web/cf_textarea.t
+++ b/t/web/cf_textarea.t
@@ -129,4 +129,38 @@ $m->text_like(
     'textarea change details'
 );
 
+$m->back;
+$m->submit_form_ok(
+    {
+        with_fields => {
+            $cfs->{area}{input}            => '<div class="form-row">test</div>',
+            $cfs->{area}{input} . '-Magic' => "1",
+        },
+    },
+    'submitted form to update textarea CF'
+);
+$m->text_contains('TheTextarea scrubbed');
+$m->text_contains( "TheTextarea <div>test</div> added", 'textarea was updated' );
+
+RT::Test->stop_server;
+RT->Config->Set( ScrubCustomFieldOnSave => Default => 1, 'RT::Ticket' => 0 );
+
+( $base, $m ) = RT::Test->started_ok;
+$m->login;
+$m->get_ok( $EditUrl, "Fetched $EditUrl" );
+$m->submit_form_ok(
+    {
+        with_fields => {
+            $cfs->{area}{input}            => '<div class="form-row">test2</div>',
+            $cfs->{area}{input} . '-Magic' => "1",
+        },
+    },
+    'submitted form to update textarea CF'
+);
+$m->text_lacks('TheTextarea scrubbed');
+$m->text_contains( qq{TheTextarea <div>test</div> changed to <div class="form-row">test2</div>},
+    'textarea was updated without scrubbing' );
+$m->follow_link_ok( { text => 'Display' } );
+$m->content_contains( '<div>test2</div>', 'Content is scrubbed on display' );
+
 done_testing;

commit cf37fc31f57067174342d3d3ee8c0d4ec2160213
Author: Jim Brandt <jbrandt at bestpractical.com>
Date:   Mon Apr 10 15:12:55 2023 -0400

    Add %ScrubCustomFieldOnSave config to scrub custom field values on save

diff --git a/etc/RT_Config.pm.in b/etc/RT_Config.pm.in
index 76b077caef..a505ee262b 100644
--- a/etc/RT_Config.pm.in
+++ b/etc/RT_Config.pm.in
@@ -1074,11 +1074,29 @@ is redirected to
 
 With this option set to 0, the redirect won't happen.
 
+=cut
+
+Set($ForceApprovalsView, 1);
+
+=item C<%ScrubCustomFieldOnSave>
+
+This determines if custom field values should be scrubbed on save, by
+default it's enabled for all custom fields. This could be customized per
+object type, e.g. to scrub ticket and transaction custom fields only, the
+config is:
+
+    Set(
+        %ScrubCustomFieldOnSave,
+        Default           => 0,
+        'RT::Ticket'      => 1,
+        'RT::Transaction' => 1,
+    );
+
 =back
 
 =cut
 
-Set($ForceApprovalsView, 1);
+Set(%ScrubCustomFieldOnSave, Default => 1);
 
 =head2 Extra security
 
diff --git a/lib/RT/CustomField.pm b/lib/RT/CustomField.pm
index a7c9db5727..941dd92ce5 100644
--- a/lib/RT/CustomField.pm
+++ b/lib/RT/CustomField.pm
@@ -1882,6 +1882,35 @@ sub _CanonicalizeValueSelect {
     return 1;
 }
 
+sub _CanonicalizeValueText {
+    my $self         = shift;
+    my $args         = shift;
+    my $scrub_config = RT->Config->Get('ScrubCustomFieldOnSave') || {};
+
+    my $msg;
+    if ( $scrub_config->{ $self->ObjectTypeFromLookupType( $self->__Value('LookupType') ) }
+        // $scrub_config->{Default} )
+    {
+        $self->_ScrubHTML($args);
+    }
+
+    return 1;
+}
+
+*_CanonicalizeValueHTML = *_CanonicalizeValueWikiText = \&_CanonicalizeValueText;
+
+sub _ScrubHTML {
+    my $self = shift;
+    my $args = shift;
+
+    for my $field (qw/Content LargeContent/) {
+        next unless $args->{$field};
+        $args->{$field}
+            = HTML::Mason::Commands::ScrubHTML( Content => $args->{$field}, Permissive => $self->_ContentIsPermissive );
+    }
+    return 1;
+}
+
 sub _ContentIsPermissive {
     my $self = shift;
     # All non-ticket related custom field values are considered permissive by default
diff --git a/lib/RT/Interface/Web.pm b/lib/RT/Interface/Web.pm
index c89736e720..f632d09214 100644
--- a/lib/RT/Interface/Web.pm
+++ b/lib/RT/Interface/Web.pm
@@ -3678,6 +3678,32 @@ sub _NormalizeObjectCustomFieldValue {
         @values = _UploadedFile( $args{'Param'} ) || ();
     }
 
+    # checking $values[0] is enough as Text/WikiText/HTML only support one value
+    if ( $values[0] && $args{CustomField}->Type =~ /^(?:Text|WikiText|HTML)$/ ) {
+        my $scrub_config = RT->Config->Get('ScrubCustomFieldOnSave') || {};
+        my $msg          = loc( '[_1] scrubbed', $args{CustomField}->Name );
+
+        # Scrubbed message could already exist as _NormalizeObjectCustomFieldValue can run multiple
+        # times for a cf, e.g. in both /Elements/ValidateCustomFields and _ProcessObjectCustomFieldUpdates.
+        if (
+            (
+                $scrub_config->{
+                    $args{CustomField}->ObjectTypeFromLookupType( $args{CustomField}->__Value('LookupType') )
+                } // $scrub_config->{Default}
+            )
+            && !grep { $_ eq $msg } @{ $session{"Actions"}->{''} ||= [] }
+            )
+        {
+            my $new_value
+                = ScrubHTML( Content => $values[0], Permissive => $args{CustomField}->_ContentIsPermissive );
+            if ( $values[0] ne $new_value ) {
+                push @{ $session{"Actions"}->{''} }, $msg;
+                $HTML::Mason::Commands::session{'i'}++;
+                $values[0] = $new_value;
+            }
+        }
+    }
+
     return @values;
 }
 

commit d36aae73947aea7655802a8592fc9bc1d3c6d505
Author: Jim Brandt <jbrandt at bestpractical.com>
Date:   Mon Apr 10 15:09:18 2023 -0400

    Scrub permissively for non-ticket related custom field values
    
    Ticket and transaction custom fields are scrubbed normally as they could
    be set by external sources in many cases, which is not quite trustable.

diff --git a/lib/RT/CustomField.pm b/lib/RT/CustomField.pm
index e81a83ee90..a7c9db5727 100644
--- a/lib/RT/CustomField.pm
+++ b/lib/RT/CustomField.pm
@@ -1882,6 +1882,12 @@ sub _CanonicalizeValueSelect {
     return 1;
 }
 
+sub _ContentIsPermissive {
+    my $self = shift;
+    # All non-ticket related custom field values are considered permissive by default
+    return ( $self->__Value('LookupType') // '' ) =~ /RT::Ticket/ ? 0 : 1;
+}
+
 =head2 MatchPattern STRING
 
 Tests the incoming string against the Pattern of this custom field object
diff --git a/lib/RT/Interface/Web.pm b/lib/RT/Interface/Web.pm
index 363d12a955..c89736e720 100644
--- a/lib/RT/Interface/Web.pm
+++ b/lib/RT/Interface/Web.pm
@@ -71,6 +71,7 @@ use URI::QueryParam;
 use RT::Interface::Web::Menu;
 use RT::Interface::Web::Session;
 use RT::Interface::Web::Scrubber;
+use RT::Interface::Web::Scrubber::Permissive;
 use Digest::MD5 ();
 use List::MoreUtils qw();
 use JSON qw();
@@ -4789,7 +4790,7 @@ sub _parse_saved_search {
     return ( _load_container_object( $obj_type, $obj_id ), $search_id );
 }
 
-=head2 ScrubHTML content
+=head2 ScrubHTML Content => CONTENT, Permissive => 1|0
 
 Removes unsafe and undesired HTML from the passed content
 
@@ -4802,14 +4803,18 @@ Removes unsafe and undesired HTML from the passed content
 our $ReloadScrubber;
 
 sub ScrubHTML {
+    my %args = @_ % 2 ? ( Content => @_ ) : @_;
+
     state $scrubber = RT::Interface::Web::Scrubber->new;
+    state $permissive_scrubber = RT::Interface::Web::Scrubber::Permissive->new;
 
     if ( $HTML::Mason::Commands::ReloadScrubber ) {
         $scrubber = RT::Interface::Web::Scrubber->new;
+        $permissive_scrubber = RT::Interface::Web::Scrubber::Permissive->new;
         $HTML::Mason::Commands::ReloadScrubber = 0;
     }
 
-    return $scrubber->scrub(@_);
+    return ( $args{Permissive} ? $permissive_scrubber : $scrubber )->scrub( $args{Content} );
 }
 
 =head2 JSON
diff --git a/lib/RT/Interface/Web/Scrubber/Permissive.pm b/lib/RT/Interface/Web/Scrubber/Permissive.pm
new file mode 100644
index 0000000000..e402390cd9
--- /dev/null
+++ b/lib/RT/Interface/Web/Scrubber/Permissive.pm
@@ -0,0 +1,146 @@
+# BEGIN BPS TAGGED BLOCK {{{
+#
+# COPYRIGHT:
+#
+# This software is Copyright (c) 1996-2021 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::Interface::Web::Scrubber::Permissive;
+use strict;
+use warnings;
+use 5.010;
+use base qw/RT::Interface::Web::Scrubber/;
+
+
+=head1 NAME
+
+RT::Interface::Web::Scrubber::Permissive
+
+=head1 DESCRIPTION
+
+This is a subclass of RT::Interface::Web::Scrubber. As a permissive version,
+it's more suitable for trusted content. It permits nearly all items allowed
+in HTML body except <script>, <style> and comments by default.
+
+=head1 VARIABLES
+
+These variables can be altered by creating a C<Permissive_Local.pm> file,
+containing something of the form:
+
+    package RT::Interface::Web::Scrubber::Permissive;
+
+    # Deny the "style" attribute
+    $ATTRIBUTES{style} = 0;
+
+=over
+
+=item C<@DENIED_TAGS>
+
+Passed to L<HTML::Scrubber/deny>.
+
+=item C<%ATTRIBUTES>
+
+Passed into L<HTML::Scrubber/default>.
+
+=item C<%RULES>
+
+Passed to L<HTML::Scrubber/rules>.
+
+=back
+
+=cut
+
+our @DENIED_TAGS;
+
+# Initally from PermissiveHTMLMail extension.
+our %ATTRIBUTES = (
+    '*'    => 1,
+    'href' => qr{^(?!(?:java)?script)}i,
+    'src'  => qr{^(?!(?:java)?script)}i,
+    'cite' => qr{^(?!(?:java)?script)}i,
+    (
+        map { +( "on$_" => 0 ) }
+            qw/blur change click dblclick error focus
+            keydown keypress keyup load mousedown
+            mousemove mouseout mouseover mouseup reset
+            select submit unload/
+    ),
+);
+
+our %RULES = (
+    script => 0,
+    html   => 0,
+    head   => 0,
+    body   => 0,
+    meta   => 0,
+    base   => 0,
+);
+
+=head1 METHODS
+
+=head2 new
+
+Returns a new L<RT::Interface::Web::Scrubber::Permissive> object, configured
+with the above globals. Takes no arguments.
+
+=cut
+
+sub new {
+    my $class = shift;
+    my $self  = $class->SUPER::new(@_);
+
+    $self->default( 1, \%ATTRIBUTES );
+    $self->deny(@DENIED_TAGS);
+    $self->rules(%RULES);
+
+    # Scrubbing comments is vital since IE conditional comments can contain
+    # arbitrary HTML and we'd pass it right on through.
+    $self->comment(0);
+
+    return $self;
+}
+
+RT::Base->_ImportOverlays();
+
+1;
diff --git a/lib/RT/ObjectCustomFieldValue.pm b/lib/RT/ObjectCustomFieldValue.pm
index e28c33765c..7d8d86268a 100644
--- a/lib/RT/ObjectCustomFieldValue.pm
+++ b/lib/RT/ObjectCustomFieldValue.pm
@@ -832,6 +832,11 @@ sub _Value {
     return $self->SUPER::_Value(@_);
 }
 
+sub _ContentIsPermissive {
+    my $self = shift;
+    return $self->CustomFieldObj->_ContentIsPermissive;
+}
+
 RT::Base->_ImportOverlays();
 
 1;
diff --git a/share/html/Elements/ScrubHTML b/share/html/Elements/ScrubHTML
index b3016bd155..9d813a8487 100644
--- a/share/html/Elements/ScrubHTML
+++ b/share/html/Elements/ScrubHTML
@@ -46,9 +46,8 @@
 %#
 %# END BPS TAGGED BLOCK }}}
 <%init>
-return ScrubHTML($Content, $SkipStructureCheck);
+return ScrubHTML(%ARGS);
 </%init>
 <%args>
-$Content => undef
 $SkipStructureCheck => 0
 </%args>
diff --git a/share/html/Elements/ShowCustomFieldHTML b/share/html/Elements/ShowCustomFieldHTML
index 5805bf402b..cdb0132ced 100644
--- a/share/html/Elements/ShowCustomFieldHTML
+++ b/share/html/Elements/ShowCustomFieldHTML
@@ -48,7 +48,7 @@
 <%$content|n%>
 <%init>
 my $content = $Object->LargeContent || $Object->Content;
-$content = $m->comp('/Elements/ScrubHTML', Content => $content);
+$content = $m->comp('/Elements/ScrubHTML', Content => $content, Permissive => $Object->_ContentIsPermissive);
 </%init>
 <%ARGS>
 $Object
diff --git a/share/html/Elements/ShowCustomFieldText b/share/html/Elements/ShowCustomFieldText
index 0d9fea4036..fffc622ad3 100644
--- a/share/html/Elements/ShowCustomFieldText
+++ b/share/html/Elements/ShowCustomFieldText
@@ -47,7 +47,7 @@
 %# END BPS TAGGED BLOCK }}}
 <%init>
  my $content = $Object->LargeContent || $Object->Content;
- $content = $m->comp('/Elements/ScrubHTML', Content => $content);
+ $content = $m->comp('/Elements/ScrubHTML', Content => $content, Permissive => $Object->_ContentIsPermissive);
  $content =~ s|\n|<br />|g;
 </%init>
 <%$content|n%>
diff --git a/share/html/Elements/ShowCustomFieldWikitext b/share/html/Elements/ShowCustomFieldWikitext
index e3308dfd6d..e392898562 100644
--- a/share/html/Elements/ShowCustomFieldWikitext
+++ b/share/html/Elements/ShowCustomFieldWikitext
@@ -48,7 +48,7 @@
 <%$wiki_content|n%>
 <%init>
 my $content = $Object->LargeContent || $Object->Content;
-$content = $m->comp('/Elements/ScrubHTML', Content => $content);
+$content = $m->comp('/Elements/ScrubHTML', Content => $content, Permissive => $Object->_ContentIsPermissive);
 my $base = $Object->Object->WikiBase;
 my %wiki_args = (
     extended => 1,

commit 8035b121f518f2c4d1ba9b6383fc3d763026b7cd
Author: Jim Brandt <jbrandt at bestpractical.com>
Date:   Mon Apr 10 15:02:19 2023 -0400

    Add extra newlines to make boundaries of different article fields clear
    
    Previously if article name, summary, custom field Content and Extra Note
    are selected, it would be rendered like:
    
        #1: name
        --------
        this is summary
        Content:
        -------
        this is content
        Extra Note:
        ----------
        this is extra content
    
    This commit tweaks it to be:
    
        #1: name
        --------
        this is summary
    
        Content:
        --------
        this is content
    
        Extra Note:
        ----------
        this is extra content
    
    which is less confusing.

diff --git a/share/html/Articles/Article/Elements/Preformatted b/share/html/Articles/Article/Elements/Preformatted
index 366feb5daf..ef93661d66 100644
--- a/share/html/Articles/Article/Elements/Preformatted
+++ b/share/html/Articles/Article/Elements/Preformatted
@@ -51,7 +51,7 @@
 <%'-' x length("#".$Article->Id.": ".($Article->Name || loc('(no name)'))) %><% $newline |n %>\
 % }
 % if ( $Article->IncludeSummary && ($Article->Summary||'') =~ /\S/ ) {
-<% $Article->Summary %><% $newline |n %>\
+<% $Article->Summary %><% $newline |n %><% $newline |n %>\
 % }
 % while (my $cf = $cfs->Next) {
 %   my $values = $Article->CustomFieldValues($cf->Id);
@@ -83,6 +83,7 @@
 %       }
 %     } 
 %   }
+<%  $newline |n %>\
 % }
 <%init>
 my $escape_html = $Article->EscapeHTML;
diff --git a/t/web/ticket-create-utf8.t b/t/web/ticket-create-utf8.t
index 09051c194d..caf2827e0b 100644
--- a/t/web/ticket-create-utf8.t
+++ b/t/web/ticket-create-utf8.t
@@ -81,6 +81,6 @@ ok( $ret, $msg );
 
 ok $m->login(root => 'password'), "logged in";
 $m->goto_create_ticket('General');
-$m->scraped_id_is('Content', '#1: My Article<br />--------------<br />Content:<br />-------<br />My Article Test Content<br />');
+$m->scraped_id_is('Content', '#1: My Article<br />--------------<br />Content:<br />-------<br />My Article Test Content<br /><br />');
 
 done_testing;

commit 2323c6fb575a29e042a1afc87aa8a53f83207a58
Author: Jim Brandt <jbrandt at bestpractical.com>
Date:   Mon Apr 10 15:01:18 2023 -0400

    Format article HTML content correctly when EscapeHTML is disabled
    
    Articles can be inserted into tickets on ticket create/update. When
    multiple articles fields are selected(e.g. name, summary and various
    custom fields), previously these fields were separated using plain
    newlines("\n"), which is not correct for HTML(all fields would be put in
    the same row).
    
    As continuous whitespaces are treated as a single one in HTML, we need
    to use " " instead.

diff --git a/share/html/Articles/Article/Elements/Preformatted b/share/html/Articles/Article/Elements/Preformatted
index edd3c22549..366feb5daf 100644
--- a/share/html/Articles/Article/Elements/Preformatted
+++ b/share/html/Articles/Article/Elements/Preformatted
@@ -45,38 +45,41 @@
 %# those contributions and any derivatives thereof.
 %#
 %# END BPS TAGGED BLOCK }}}
+
 % if ($Article->IncludeName) {
-#<%$Article->Id%>: <%$Article->Name || loc('(no name)')%>
-<%'-' x length("#".$Article->Id.": ".($Article->Name || loc('(no name)'))) %>
+#<%$Article->Id%>: <%$Article->Name || loc('(no name)')%><% $newline |n %>\
+<%'-' x length("#".$Article->Id.": ".($Article->Name || loc('(no name)'))) %><% $newline |n %>\
 % }
 % if ( $Article->IncludeSummary && ($Article->Summary||'') =~ /\S/ ) {
-<% $Article->Summary %>
+<% $Article->Summary %><% $newline |n %>\
 % }
 % while (my $cf = $cfs->Next) {
 %   my $values = $Article->CustomFieldValues($cf->Id);
 %   if ($values->Count == 1) {
 %     my $value = $values->First; 
 %     if ($Article->IncludeCFTitle($cf)) {
-<%      $cf->Name%>:
-<%      '-' x length($cf->Name) %>
+<%      $cf->Name%>:<% $newline |n %>\
+<%      '-' x length($cf->Name) %><% $newline |n %>\
 %     }
 %     if ($value && $Article->IncludeCFValue($cf)) {
-<%      $get_content->( $value ) %>
+<%      $get_content->( $value ) %>\
 %     }
+<%    $newline |n %>\
 %   } else {
 %     my $val = $values->Next;
 %     if ($Article->IncludeCFTitle($cf)) {
 <%      $cf->Name%>: \
 %     }
 %     if ($val && $Article->IncludeCFValue($cf)) {
-<%      $get_content->( $val ) %>
+<%      $get_content->( $val ) %>\
 %     }
+<%    $newline |n %>\
 %     while ($val = $values->Next) { 
 %       if ($Article->IncludeCFTitle($cf)) {
-<%        ' ' x length($cf->Name)%>  \
+<%        $space x length($cf->Name) |n %>  \
 %       }
 %       if ($Article->IncludeCFValue($cf)) {
-<%        $get_content->( $val ) %>
+<%        $get_content->( $val ) %><% $newline |n %>\
 %       }
 %     } 
 %   }
@@ -104,6 +107,10 @@ my $get_content = sub {
     return $content;
 };
 
+# Use HTML version of newline and space if possible, to not collapse content.
+my $richtext = RT->Config->Get( 'MessageBoxRichText', $session{'CurrentUser'} ) ? 1        : 0;
+my $newline  = $richtext && !$Article->EscapeHTML                               ? '<br />' : "\n";
+my $space    = $richtext && !$Article->EscapeHTML                               ? ' ' : ' ';
 </%init>
 <%args>
 $Article
diff --git a/t/web/ticket-create-utf8.t b/t/web/ticket-create-utf8.t
index e1ce7ed0ee..09051c194d 100644
--- a/t/web/ticket-create-utf8.t
+++ b/t/web/ticket-create-utf8.t
@@ -81,6 +81,6 @@ ok( $ret, $msg );
 
 ok $m->login(root => 'password'), "logged in";
 $m->goto_create_ticket('General');
-$m->scraped_id_is('Content', '#1: My Article <br />-------------- <br />Content: <br />------- <br />My Article Test Content <br />');
+$m->scraped_id_is('Content', '#1: My Article<br />--------------<br />Content:<br />-------<br />My Article Test Content<br />');
 
 done_testing;

commit 86c935123aed2dc58bb3239de51b6feb0ed6377e
Author: Jim Brandt <jbrandt at bestpractical.com>
Date:   Mon Apr 10 14:54:46 2023 -0400

    Use HTML content for articles by default
    
    Since it's the default behavior and content is scrubbed, remove the
    paranoid "potentially unsafe" warning.

diff --git a/docs/UPGRADING-5.0 b/docs/UPGRADING-5.0
index 0757d90928..c25b688678 100644
--- a/docs/UPGRADING-5.0
+++ b/docs/UPGRADING-5.0
@@ -597,6 +597,13 @@ as part of the upgrade process. If you prefer not to use shortened URLs,
 you can disable this new feature by setting C<$EnableURLShortener> to
 false.
 
+=item * HTML Custom Fields
+
+RT now supports an C<HTML> type for custom fields, so if you have custom
+fields containing C<HTML>, you can update them and switch the type to C<HTML>
+to use CKEditor for editing. This is specifically useful for the Content
+custom field in articles.
+
 =back
 
 =cut
diff --git a/etc/initialdata b/etc/initialdata
index fd92695b58..32da7e63e7 100644
--- a/etc/initialdata
+++ b/etc/initialdata
@@ -938,6 +938,12 @@ Hour:         { $SubscriptionObj->SubValue('Hour') }
     {
         Name              => 'General',
         Description       => 'The default class',
+        Attributes        => [
+            {
+                Name    => 'Skip-EscapeHTML',
+                Content => 1,
+            },
+        ],
     },
 );
 
@@ -946,7 +952,7 @@ Hour:         { $SubscriptionObj->SubValue('Hour') }
         Name              => 'Content',
         Description       => 'Content',
         LookupType        => 'RT::Class-RT::Article',
-        Type              => 'Text',
+        Type              => 'HTML',
         MaxValues         => 1,
     },
 );
diff --git a/share/html/Admin/Articles/Classes/Modify.html b/share/html/Admin/Articles/Classes/Modify.html
index ff0de8a58a..99193c9c12 100644
--- a/share/html/Admin/Articles/Classes/Modify.html
+++ b/share/html/Admin/Articles/Classes/Modify.html
@@ -108,7 +108,7 @@
   <&| /Elements/LabeledValue, Label => "" &>
       <div class="custom-control custom-checkbox">
         <input type="checkbox" class="custom-control-input checkbox" id="Include-EscapeHTML" name="Include-EscapeHTML" value="1" <% $include{EscapeHTML} %>>
-        <label class="custom-control-label" for="Include-EscapeHTML"><&|/l&>Escape HTML (Unchecking this box is potentially unsafe)</&></label>
+        <label class="custom-control-label" for="Include-EscapeHTML"><&|/l&>Escape HTML</&></label>
       </div>
   </&>
 
@@ -229,7 +229,7 @@ if ((defined $Enabled && $Enabled == 1) or (not defined $Enabled and $Create)) {
     $Disabled = 1;
 }
 
-my %include = (Name => 1, Summary => 1, EscapeHTML => 1);
+my %include = (Name => 1, Summary => 1, EscapeHTML => 0);
 $include{LinkToTicket} = 1 if RT->Config->Get('LinkArticlesOnInclude');
 
 my $subject_cfs = [];
diff --git a/t/api/initialdata-roundtrip.t b/t/api/initialdata-roundtrip.t
index e55223acc7..aa33e50541 100644
--- a/t/api/initialdata-roundtrip.t
+++ b/t/api/initialdata-roundtrip.t
@@ -923,7 +923,7 @@ my @tests = (
             my $content = RT::CustomField->new(RT->SystemUser);
             $content->LoadByCols(
                 Name => "Content",
-                Type => "Text",
+                Type => "HTML",
                 LookupType => RT::Article->CustomFieldLookupType,
             );
             ok($content->Id, "loaded builtin Content CF");
diff --git a/t/rest2/article-customfields.t b/t/rest2/article-customfields.t
index 8020242631..ed25f2aa77 100644
--- a/t/rest2/article-customfields.t
+++ b/t/rest2/article-customfields.t
@@ -134,7 +134,7 @@ my $no_article_cf_values = bag(
                 LookupType => RT::Article->CustomFieldLookupType,
                 MaxValues  => 1,
                 Name       => 'Content',
-                Type       => 'Text',
+                Type       => 'HTML',
             }
         ),
         'single cf'

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


hooks/post-receive
-- 
rt


More information about the rt-commit mailing list