[Rt-commit] rt branch 5.0/article-html-content created. rt-5.0.2-89-g67dfd92f14

BPS Git Server git at git.bestpractical.com
Tue Mar 15 19:50:50 UTC 2022


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  67dfd92f149eb8cb62ae07a2a8d2d32a357be800 (commit)

- Log -----------------------------------------------------------------
commit 67dfd92f149eb8cb62ae07a2a8d2d32a357be800
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 a15aa5edc1..f939074e59 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 a5fa9c5bfa635bdae4a77eae0e1b957ca6a9a5a1
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Wed Mar 16 01:16:43 2022 +0800

    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 9717ac3a75..873cd5d452 100644
--- a/etc/RT_Config.pm.in
+++ b/etc/RT_Config.pm.in
@@ -994,11 +994,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 5f89b3d9b9..2815157f83 100644
--- a/lib/RT/CustomField.pm
+++ b/lib/RT/CustomField.pm
@@ -1950,6 +1950,35 @@ sub _CanonicalizeValueIPAddressRange {
     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 efe4af7447..5e993d9974 100644
--- a/lib/RT/Interface/Web.pm
+++ b/lib/RT/Interface/Web.pm
@@ -3604,6 +3604,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 34629a14f70ae2a1ea5ac53a896b82adf7c0e61d
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Sat Mar 12 04:28:20 2022 +0800

    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 67197f4bb7..5f89b3d9b9 100644
--- a/lib/RT/CustomField.pm
+++ b/lib/RT/CustomField.pm
@@ -1950,6 +1950,12 @@ sub _CanonicalizeValueIPAddressRange {
     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 c02f49af75..efe4af7447 100644
--- a/lib/RT/Interface/Web.pm
+++ b/lib/RT/Interface/Web.pm
@@ -70,6 +70,7 @@ use URI qw();
 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();
@@ -4708,7 +4709,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
 
@@ -4721,14 +4722,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 9c6ca7d3dc..16ba83442c 100644
--- a/lib/RT/ObjectCustomFieldValue.pm
+++ b/lib/RT/ObjectCustomFieldValue.pm
@@ -806,6 +806,11 @@ sub ExternalStoreDigest {
     return $self->_Value( 'LargeContent' );
 }
 
+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 119adc5f74..af7f594c06 100644
--- a/share/html/Elements/ScrubHTML
+++ b/share/html/Elements/ScrubHTML
@@ -46,8 +46,5 @@
 %#
 %# END BPS TAGGED BLOCK }}}
 <%init>
-return ScrubHTML($Content);
+return ScrubHTML(%ARGS);
 </%init>
-<%args>
-$Content => undef
-</%args>
diff --git a/share/html/Elements/ShowCustomFieldHTML b/share/html/Elements/ShowCustomFieldHTML
index 86e4bbfd95..11985dd755 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 d20b1d4d4e..88a63f82c6 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 0ef6c7c140..e59175643e 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 65142d1da912a09de8a009df1252a12064e5e2e3
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Sat Feb 26 02:13:29 2022 +0800

    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 f48ea4f89e..8dddf591f3 100644
--- a/share/html/Articles/Article/Elements/Preformatted
+++ b/share/html/Articles/Article/Elements/Preformatted
@@ -50,7 +50,7 @@
 <%'-' x length("#".$Article->Id.": ".($Article->Name || loc('(no name)'))) %><% $newline |n %>\
 % }
 % if ( $include{Summary} && ($Article->Summary||'') =~ /\S/ ) {
-<% $Article->Summary %><% $newline |n %>\
+<% $Article->Summary %><% $newline |n %><% $newline |n %>\
 % }
 % while (my $cf = $cfs->Next) {
 %   next unless $include{"CF-Title-".$cf->Id} or $include{"CF-Value-".$cf->Id};
@@ -83,6 +83,7 @@
 %       }
 %     } 
 %   }
+<%  $newline |n %>\
 % }
 <%init>
 my $class = $Article->ClassObj;
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 d295ce61330ebe5c9b48451b5801edcd31d8f35f
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Fri Feb 25 23:36:14 2022 +0800

    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 8166ca5652..f48ea4f89e 100644
--- a/share/html/Articles/Article/Elements/Preformatted
+++ b/share/html/Articles/Article/Elements/Preformatted
@@ -46,11 +46,11 @@
 %#
 %# END BPS TAGGED BLOCK }}}
 % if ($include{Name}) {
-#<%$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 ( $include{Summary} && ($Article->Summary||'') =~ /\S/ ) {
-<% $Article->Summary %>
+<% $Article->Summary %><% $newline |n %>\
 % }
 % while (my $cf = $cfs->Next) {
 %   next unless $include{"CF-Title-".$cf->Id} or $include{"CF-Value-".$cf->Id};
@@ -58,26 +58,28 @@
 %   if ($values->Count == 1) {
 %     my $value = $values->First; 
 %     if ($include{"CF-Title-".$cf->Id}) {
-<%      $cf->Name%>:
-<%      '-' x length($cf->Name) %>
+<%      $cf->Name%>:<% $newline |n %>\
+<%      '-' x length($cf->Name) %><% $newline |n %>\
 %     }
 %     if ($value && $include{"CF-Value-".$cf->Id}) {
-<%      $get_content->( $value ) %>
+<%      $get_content->( $value ) %>\
 %     }
+<%    $newline |n %>\
 %   } else {
 %     my $val = $values->Next;
 %     if ($include{"CF-Title-".$cf->Id}) {
 <%      $cf->Name%>: \
 %     }
 %     if ($val && $include{"CF-Value-".$cf->Id}) {
-<%      $get_content->( $val ) %>
+<%      $get_content->( $val ) %>\
 %     }
+<%    $newline |n %>\
 %     while ($val = $values->Next) { 
 %       if ($include{"CF-Title-".$cf->Id}) {
-<%        ' ' x length($cf->Name)%>  \
+<%        $space x length($cf->Name) |n %>  \
 %       }
 %       if ($include{"CF-Value-".$cf->Id}) {
-<%        $get_content->( $val ) %>
+<%        $get_content->( $val ) %><% $newline |n %>\
 %       }
 %     } 
 %   }
@@ -113,6 +115,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 && !$include{'EscapeHTML'}                             ? '<br />' : "\n";
+my $space    = $richtext && !$include{'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 d452d4be019e0a4ec756afb51b8beb5a478732cc
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Fri Feb 25 05:24:00 2022 +0800

    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 fbe8e017c0..1a91064273 100644
--- a/docs/UPGRADING-5.0
+++ b/docs/UPGRADING-5.0
@@ -438,4 +438,16 @@ it will order by EmailAddress.
 
 =back
 
+=head1 UPGRADING FROM 5.0.2 AND EARLIER
+
+=over 4
+
+=item *
+
+RT now supports C<HTML> type for custom fields, so if you have custom fields
+containing C<HTML>(e.g. C<Content> for articles), you can switch type to
+C<HTML> to use CKEditor for editing.
+
+=back
+
 =cut
diff --git a/etc/initialdata b/etc/initialdata
index b7daaf4239..35aad1df89 100644
--- a/etc/initialdata
+++ b/etc/initialdata
@@ -932,6 +932,12 @@ Hour:         { $SubscriptionObj->SubValue('Hour') }
     {
         Name              => 'General',
         Description       => 'The default class',
+        Attributes        => [
+            {
+                Name    => 'Skip-EscapeHTML',
+                Content => 1,
+            },
+        ],
     },
 );
 
@@ -940,7 +946,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 75956e5f06..a5eab03678 100644
--- a/share/html/Admin/Articles/Classes/Modify.html
+++ b/share/html/Admin/Articles/Classes/Modify.html
@@ -137,7 +137,7 @@
     <div class="value col-9">
       <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>
     </div>
   </div>
@@ -270,7 +270,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/share/html/Articles/Article/Elements/Preformatted b/share/html/Articles/Article/Elements/Preformatted
index 22eaba10a7..8166ca5652 100644
--- a/share/html/Articles/Article/Elements/Preformatted
+++ b/share/html/Articles/Article/Elements/Preformatted
@@ -84,7 +84,7 @@
 % }
 <%init>
 my $class = $Article->ClassObj;
-my %include = (Name => 1, Summary => 1, EscapeHTML => 1);
+my %include = (Name => 1, Summary => 1, EscapeHTML => 0);
 my $cfs = $class->ArticleCustomFields;
 while ( my $cf = $cfs->Next ) {
     $include{"CF-Title-" . $cf->Id} = 1;
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 03d90d11ea..83360d80fc 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