[Rt-commit] rt branch, 4.2.10-releng, created. rt-4.2.9-155-ga9a3513

Alex Vandiver alexmv at bestpractical.com
Fri Feb 6 12:17:41 EST 2015


The branch, 4.2.10-releng has been created
        at  a9a35134f25d9a1854b929e86323cdba6470d1f9 (commit)

- Log -----------------------------------------------------------------
commit 4cf6c915e218ce90f8c32fdb09a9f8536be50551
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Wed Dec 31 02:11:00 2014 -0500

    Support arbitrary database names
    
    Fixes: I#7568

diff --git a/configure.ac b/configure.ac
index 26fb8e9..1bcf2ba 100755
--- a/configure.ac
+++ b/configure.ac
@@ -231,16 +231,6 @@ AC_MSG_CHECKING([if database name is set])
                 [ AC_MSG_ERROR([no.  database name is not set]) ]
                )
 
-AS_IF([ test "$DB_TYPE" = "mysql" ],
-      [ AC_MSG_CHECKING([if database name is valid])
-          AS_IF([ echo $DB_DATABASE | $PERL -e 'exit(1) if <> =~ /-/'],
-                [ AC_MSG_RESULT([yes]) ],
-                [ AC_MSG_ERROR([no.  database name ($DB_DATABASE) contains '-' which is not valid for mysql]) ]
-               )
-      ]
-     )
-
-
 dnl Dependencies for testing and developing RT
 AC_ARG_WITH(developer,[],RT_DEVELOPER=$withval,RT_DEVELOPER="0")
 AC_ARG_ENABLE(developer,
diff --git a/etc/acl.mysql b/etc/acl.mysql
index 7e56e97..26e27fb 100644
--- a/etc/acl.mysql
+++ b/etc/acl.mysql
@@ -12,7 +12,7 @@ sub acl {
         RT->Logger->warn("DatabaseUser is root. Skipping...");
         return;
     }
-    $db_name =~ s/([_%])/\\$1/g;
+    $db_name =~ s/([_%\\])/\\$1/g;
     return (
         "GRANT SELECT,INSERT,CREATE,INDEX,UPDATE,DELETE
                ON `$db_name`.*
diff --git a/lib/RT/Handle.pm b/lib/RT/Handle.pm
index 29802ad..673c772 100644
--- a/lib/RT/Handle.pm
+++ b/lib/RT/Handle.pm
@@ -365,7 +365,7 @@ sub CreateDatabase {
         $status = $dbh->do("CREATE DATABASE $db_name WITH ENCODING='UNICODE' TEMPLATE template0");
     }
     elsif ( $db_type eq 'mysql' ) {
-        $status = $dbh->do("CREATE DATABASE $db_name DEFAULT CHARACTER SET utf8");
+        $status = $dbh->do("CREATE DATABASE `$db_name` DEFAULT CHARACTER SET utf8");
     }
     else {
         $status = $dbh->do("CREATE DATABASE $db_name");
@@ -406,6 +406,9 @@ sub DropDatabase {
         $path = "$RT::VarPath/$path" unless substr($path, 0, 1) eq '/';
         unlink $path or return (0, "Couldn't remove '$path': $!");
         return (1);
+    } elsif ( $db_type eq 'mysql' ) {
+        $dbh->do("DROP DATABASE `$db_name`")
+            or return (0, $DBI::errstr);
     } else {
         $dbh->do("DROP DATABASE ". $db_name)
             or return (0, $DBI::errstr);

commit 0475d2d8fb1f40bff6f89b2a195eb48f6a6b8853
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Fri Jan 30 23:15:39 2015 +0800

    use main.css to identify if a directory in css is a theme
    
    it's consistent since we load main.css when loading a theme
    
    Fixes: I#30554

diff --git a/lib/RT/Config.pm b/lib/RT/Config.pm
index b4ae6c8..9a1bef8 100644
--- a/lib/RT/Config.pm
+++ b/lib/RT/Config.pm
@@ -212,7 +212,7 @@ our %META;
                         next unless -d $css_path;
                         if ( opendir my $dh, $css_path ) {
                             push @stylesheets, grep {
-                                -e File::Spec->catfile( $css_path, $_, 'base.css' )
+                                $_ ne 'base' && -e File::Spec->catfile( $css_path, $_, 'main.css' )
                             } readdir $dh;
                         }
                         else {

commit f6a41992ecb4ba8ca381f1bb40e21e69e71d1319
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Mon Jul 7 22:57:18 2014 +0800

    don't chdir to make tests happy on inline server + devel mode
    
    before, if you have a relative "lib" only in PERL5LIB and run tests via
    "prove" without "-l", tests could fail badly.

diff --git a/t/web/compilation_errors.t b/t/web/compilation_errors.t
index 399268a..38256d1 100644
--- a/t/web/compilation_errors.t
+++ b/t/web/compilation_errors.t
@@ -37,7 +37,7 @@ is($agent->status, 200, "Fetched the page ok");
 $agent->content_contains('Logout', "Found a logout link");
 
 
-find ( sub { wanted() and test_get($agent, $File::Find::name) } , 'share/html/');
+find ( { wanted => sub { wanted() and test_get($agent, $File::Find::name) }, no_chdir => 1 } , 'share/html/');
 
 # We expect to spew a lot of warnings; toss them away
 $agent->get_warnings;

commit 0bf126329169b2774045bdac23bd00fad54b5ac8
Merge: 7cfc99e 0475d2d
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Mon Feb 2 22:00:40 2015 -0500

    Merge branch '4.2/css-theme-detect-file' into 4.2-trunk


commit 888d3384aead9191229c165c7bbb8fb85a8c62ea
Merge: 0bf1263 f6a4199
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Mon Feb 2 22:07:42 2015 -0500

    Merge branch '4.2/inline-server-failed-tests-on-devel-mode' into 4.2-trunk


commit c40ba502ce56fe1d948d0593d9172989d96a80b0
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Thu Jan 2 21:37:16 2014 -0500

    Use HTML::FormatExternal to allow external HTML → text formatters
    
    The HTML::FormatText::WithLinks::AndTables module is unmaintained, and
    contains multiple bugs which trigger runtime errors.  In such cases, RT
    sends out an empty text/plain part -- if HTML templates are not in use,
    this results in an entirely empty email.
    
    Add HTML::FormatExternal as an optional dependency for more reliable
    HTML to text conversion using external text-based web browsers and text
    conversion tools.
    
    Benchmarking shows that shelling out to an external program on complex
    HTML is comparable in speed (or faster!) than the pure-perl formatter
    (20/s).  On simple HTML, the pure-perl formatter is an order of
    magnitude faster -- but the slowest external formatter (elinks) still
    runs at 5/sec, and most run at 20-50/s.  This is judged to not be likely
    to cause a performance impact, as it is run at most once per scrip.

diff --git a/docs/UPGRADING-4.2 b/docs/UPGRADING-4.2
index 2ed98e4..3ab005e 100644
--- a/docs/UPGRADING-4.2
+++ b/docs/UPGRADING-4.2
@@ -345,4 +345,12 @@ have been overridden by CSS since 4.0.0, and thus did not affect
 display.  They have been removed, and setting them will trigger an
 informational message that setting them is ineffective.
 
+=head1 UPGRADING FROM 4.2.9 AND EARLIER
+
+An additional optional dependency, L<HTML::FormatExternal>, has been
+added.  This allows RT to use C<w3m>, C<elinks>, C<html2text>, or other
+external tools to render HTML to text.  This dependency is not installed
+by default; however, its use is strongly encouraged, and will resolve
+issues with blank outgoing emails.
+
 =cut
diff --git a/etc/RT_Config.pm.in b/etc/RT_Config.pm.in
index 294b255..591feec 100644
--- a/etc/RT_Config.pm.in
+++ b/etc/RT_Config.pm.in
@@ -703,6 +703,26 @@ will use the address of the current user and remove RT's subject tag.
 
 Set($ForwardFromUser, 0);
 
+=item C<$HTMLFormatter>
+
+RT's default pure-perl formatter may fail to successfully convert even
+on some relatively simple HTML; this will result in blank C<text/plain>
+parts, which is particuarly unfortunate if HTML templates are not in
+use.
+
+If the optional dependency L<HTML::FormatExternal> is installed, RT will
+use external programs to render HTML to plain text.  The default is to
+try, in order, C<w3m>, C<elinks>, C<html2text>, C<links>, C<lynx>, and
+then fall back to the C<core> pure-perl formatter if none are installed.
+
+Set C<$HTMLFormatter> to one of the above programs (or the full path to
+such) to use a different program than the above would choose by default.
+Setting this requires that L<HTML::FormatExternal> be installed.
+
+=cut
+
+Set($HTMLFormatter, undef);
+
 =back
 
 =head2 Email dashboards
diff --git a/lib/RT/Config.pm b/lib/RT/Config.pm
index 9a1bef8..b6bfce5 100644
--- a/lib/RT/Config.pm
+++ b/lib/RT/Config.pm
@@ -631,6 +631,10 @@ our %META;
             $self->Set( MailCommand => 'sendmailpipe' );
         },
     },
+    HTMLFormatter => {
+        Type => 'SCALAR',
+        PostLoadCheck => sub { RT::Interface::Email->_HTMLFormatter },
+    },
     MailPlugins  => {
         Type => 'ARRAY',
         PostLoadCheck => sub {
diff --git a/lib/RT/Interface/Email.pm b/lib/RT/Interface/Email.pm
index cd6b332..8830070 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -50,6 +50,7 @@ package RT::Interface::Email;
 
 use strict;
 use warnings;
+use 5.010;
 
 use Email::Address;
 use MIME::Entity;
@@ -57,6 +58,7 @@ use RT::EmailParser;
 use File::Temp;
 use Mail::Mailer ();
 use Text::ParseWords qw/shellwords/;
+use RT::Util 'safe_run_child';
 
 BEGIN {
     use base 'Exporter';
@@ -1794,9 +1796,64 @@ conversion fails.
 =cut
 
 sub ConvertHTMLToText {
+    return _HTMLFormatter()->(@_);
+}
+
+sub _HTMLFormatter {
+    state $formatter;
+    return $formatter if defined $formatter;
+
+    my $wanted = RT->Config->Get("HTMLFormatter");
+
+    my @order;
+    if ($wanted) {
+        @order = ($wanted, "core");
+    } else {
+        @order = ("w3m", "elinks", "html2text", "links", "lynx", "core");
+    }
+    # Always fall back to core, even if it is not listed
+    for my $prog (@order) {
+        if ($prog eq "core") {
+            RT->Logger->info("Using internal Perl HTML -> text conversion");
+            require HTML::FormatText::WithLinks::AndTables;
+            $formatter = \&_HTMLFormatText;
+        } else {
+            unless (HTML::FormatExternal->require) {
+                RT->Logger->warn("HTML::FormatExternal is not installed; falling back to internal perl formatter")
+                    if $wanted;
+                next;
+            }
+
+            my $package = "HTML::FormatText::" . ucfirst($prog);
+            unless ($package->require) {
+                RT->Logger->warn("$prog is not a valid formatter provided by HTML::FormatExternal")
+                    if $wanted;
+                next;
+            }
+
+            unless (defined $package->program_version) {
+                RT->Logger->warn("Could not find external '$prog' HTML formatter; is it installed?")
+                    if $wanted;
+                next;
+            }
+
+            RT->Logger->info("Using $prog for HTML -> text conversion");
+            $formatter = sub {
+                my $html = shift;
+                RT::Util::safe_run_child {
+                    $package->format_string($html, leftmargin => 0, rightmargin => 78);
+                };
+            };
+        }
+        RT->Config->Set( HTMLFormatter => $prog );
+        last;
+    }
+    return $formatter;
+}
+
+sub _HTMLFormatText {
     my $html = shift;
 
-    require HTML::FormatText::WithLinks::AndTables;
     my $text;
     eval {
         $text = HTML::FormatText::WithLinks::AndTables->convert(

commit 23b64b50fb69c12d124e905ffafe5398bf8b3919
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Wed Jun 18 18:44:52 2014 -0400

    Allow $HTMLFormatter to contain the full path

diff --git a/etc/RT_Config.pm.in b/etc/RT_Config.pm.in
index 591feec..c3f7890 100644
--- a/etc/RT_Config.pm.in
+++ b/etc/RT_Config.pm.in
@@ -719,6 +719,9 @@ Set C<$HTMLFormatter> to one of the above programs (or the full path to
 such) to use a different program than the above would choose by default.
 Setting this requires that L<HTML::FormatExternal> be installed.
 
+If the chosen formatter is not in the webserver's $PATH, you may set
+this option the full path to one of the aforementioned executables.
+
 =cut
 
 Set($HTMLFormatter, undef);
diff --git a/lib/RT/Interface/Email.pm b/lib/RT/Interface/Email.pm
index 8830070..b89d467 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -1824,6 +1824,7 @@ sub _HTMLFormatter {
                 next;
             }
 
+            my $path = $prog =~ s{(.*/)}{} ? $1 : undef;
             my $package = "HTML::FormatText::" . ucfirst($prog);
             unless ($package->require) {
                 RT->Logger->warn("$prog is not a valid formatter provided by HTML::FormatExternal")
@@ -1831,8 +1832,15 @@ sub _HTMLFormatter {
                 next;
             }
 
-            unless (defined $package->program_version) {
-                RT->Logger->warn("Could not find external '$prog' HTML formatter; is it installed?")
+            if ($path) {
+                local $ENV{PATH} = $path;
+                if (not defined $package->program_version) {
+                    RT->Logger->warn("Could not find or run external '$prog' HTML formatter in $path$prog")
+                        if $wanted;
+                    next;
+                }
+            } elsif (not defined $package->program_version) {
+                RT->Logger->warn("Could not find or run external '$prog' HTML formatter in \$PATH ($ENV{PATH}) -- you may need to install it or provide the full path")
                     if $wanted;
                 next;
             }
@@ -1841,6 +1849,7 @@ sub _HTMLFormatter {
             $formatter = sub {
                 my $html = shift;
                 RT::Util::safe_run_child {
+                    local $ENV{PATH} = $path || $ENV{PATH};
                     $package->format_string($html, leftmargin => 0, rightmargin => 78);
                 };
             };

commit 24365b3f3cbeaf6d8f9a31fd59dbd8628e0bafb8
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Wed Jun 25 21:38:19 2014 -0400

    Provide a default PATH in cases where it is unset (mod_fastcgi)
    
    See 4ef0d833 for the full explanation, which applies in this context as
    well.

diff --git a/lib/RT/Interface/Email.pm b/lib/RT/Interface/Email.pm
index b89d467..39fdabc 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -1839,17 +1839,22 @@ sub _HTMLFormatter {
                         if $wanted;
                     next;
                 }
-            } elsif (not defined $package->program_version) {
-                RT->Logger->warn("Could not find or run external '$prog' HTML formatter in \$PATH ($ENV{PATH}) -- you may need to install it or provide the full path")
-                    if $wanted;
-                next;
+            } else {
+                local $ENV{PATH} = '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin'
+                    unless defined $ENV{PATH};
+                if (not defined $package->program_version) {
+                    RT->Logger->warn("Could not find or run external '$prog' HTML formatter in \$PATH ($ENV{PATH}) -- you may need to install it or provide the full path")
+                        if $wanted;
+                    next;
+                }
             }
 
             RT->Logger->info("Using $prog for HTML -> text conversion");
             $formatter = sub {
                 my $html = shift;
                 RT::Util::safe_run_child {
-                    local $ENV{PATH} = $path || $ENV{PATH};
+                    local $ENV{PATH} = $path || $ENV{PATH}
+                        || '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin';
                     $package->format_string($html, leftmargin => 0, rightmargin => 78);
                 };
             };

commit 927ece5881ec232db1dbaa53fcb32cc7b1d036bf
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Wed Dec 31 14:42:17 2014 -0500

    Set an $ENV{HOME} that the external formatters can write under
    
    Many of them attempt to read a configuration file or directory under
    $ENV{HOME}; some (like w3m) even attempt to write a blank template of
    one if it does not exist.
    
    Provide a suitable $ENV{HOME} which will be writable.  This prevents
    errors being written to STDERR (and thus the webserver error log) on
    every invocation.

diff --git a/lib/RT/Interface/Email.pm b/lib/RT/Interface/Email.pm
index 39fdabc..b1ac2bd 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -1834,6 +1834,7 @@ sub _HTMLFormatter {
 
             if ($path) {
                 local $ENV{PATH} = $path;
+                local $ENV{HOME} = $RT::VarPath;
                 if (not defined $package->program_version) {
                     RT->Logger->warn("Could not find or run external '$prog' HTML formatter in $path$prog")
                         if $wanted;
@@ -1842,6 +1843,7 @@ sub _HTMLFormatter {
             } else {
                 local $ENV{PATH} = '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin'
                     unless defined $ENV{PATH};
+                local $ENV{HOME} = $RT::VarPath;
                 if (not defined $package->program_version) {
                     RT->Logger->warn("Could not find or run external '$prog' HTML formatter in \$PATH ($ENV{PATH}) -- you may need to install it or provide the full path")
                         if $wanted;
@@ -1855,6 +1857,7 @@ sub _HTMLFormatter {
                 RT::Util::safe_run_child {
                     local $ENV{PATH} = $path || $ENV{PATH}
                         || '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin';
+                    local $ENV{HOME} = $RT::VarPath;
                     $package->format_string($html, leftmargin => 0, rightmargin => 78);
                 };
             };

commit 6337505bc81b49b8d402a8dda12a3ddef56eb411
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Fri Jan 3 17:16:38 2014 -0500

    Update tests to work with any of the HTML formatters

diff --git a/t/api/txn_content.t b/t/api/txn_content.t
index d44862d..672d6c2 100644
--- a/t/api/txn_content.t
+++ b/t/api/txn_content.t
@@ -19,5 +19,5 @@ ok( $txn, 'got Create txn' );
 
 # ->Content converts from text/html to plain text if we don't explicitly ask
 # for html. Our html -> text converter seems to add an extra trailing newline
-is( $txn->Content, "this is body\n\n", "txn's html content converted to plain text" );
+like( $txn->Content, qr/^\s*this is body\s*$/, "txn's html content converted to plain text" );
 is( $txn->Content(Type => 'text/html'), "this is body\n", "txn's html content" );
diff --git a/t/mail/html-outgoing.t b/t/mail/html-outgoing.t
index 6a0f2be..a37f52c 100644
--- a/t/mail/html-outgoing.t
+++ b/t/mail/html-outgoing.t
@@ -39,7 +39,7 @@ mail_ok {
     to      => 'enduser at example.com',
     subject => qr/\Q[example.com #1] AutoReply: The internet is broken\E/,
     body    => parts_regex(
-        'trouble ticket regarding The internet is broken',
+        'trouble ticket regarding \*?The internet is broken\*?',
         'trouble ticket regarding <b>The internet is broken</b>'
     ),
     'Content-Type' => qr{multipart},
@@ -47,7 +47,7 @@ mail_ok {
     bcc     => 'root at localhost',
     subject => qr/\Q[example.com #1] The internet is broken\E/,
     body    => parts_regex(
-        'Request 1 \(http://localhost:\d+/Ticket/Display\.html\?id=1\)\s+?was acted upon by RT_System',
+        'Request (\[\d+\])?1(\s*[(<]http://localhost:\d+/Ticket/Display\.html\?id=1[)>])?\s*was acted upon by RT_System',
         'Request <a href="http://localhost:\d+/Ticket/Display\.html\?id=1">1</a> was acted upon by RT_System\.</b>'
     ),
     'Content-Type' => qr{multipart},
@@ -65,8 +65,8 @@ mail_ok {
     bcc     => 'root at localhost',
     subject => qr/\Q[example.com #1] The internet is broken\E/,
     body    => parts_regex(
-        'Ticket URL: http://localhost:\d+/Ticket/Display\.html\?id=1.+?'.
-        'This is a test of HTML correspondence\.',
+        'Ticket URL: (?:\[\d+\])?http://localhost:\d+/Ticket/Display\.html\?id=1.+?'.
+        'This is a test of \*?HTML\*? correspondence\.',
         'Ticket URL: <a href="(http://localhost:\d+/Ticket/Display\.html\?id=1)">\1</a>.+?'.
         '<p>This is a test of <b>HTML</b> correspondence\.</p>'
     ),
@@ -75,35 +75,39 @@ mail_ok {
     to      => 'enduser at example.com',
     subject => qr/\Q[example.com #1] The internet is broken\E/,
     body    => parts_regex(
-        'This is a test of HTML correspondence\.',
+        'This is a test of \*?HTML\*? correspondence\.',
         '<p>This is a test of <b>HTML</b> correspondence\.</p>'
     ),
     'Content-Type' => qr{multipart},
 };
 
+SKIP: {
+    skip "Only fails on core HTMLFormatter", 9
+        unless RT->Config->Get("HTMLFormatter") eq "core";
+    diag "Failing HTML -> Text conversion";
+    warnings_like {
+        my $body = '<table><tr><td><table><tr><td>Foo</td></tr></table></td></tr></table>';
+        mail_ok {
+            ($ok, $tmsg) = $t->Correspond(
+                MIMEObj => HTML::Mason::Commands::MakeMIMEEntity(
+                    Body => $body,
+                    Type => 'text/html',
+                ),
+            );
+        } { from    => qr/RT System/,
+            bcc     => 'root at localhost',
+            subject => qr/\Q[example.com #1] The internet is broken\E/,
+            body    => qr{Ticket URL: <a href="(http://localhost:\d+/Ticket/Display\.html\?id=1)">\1</a>.+?$body}s,
+            'Content-Type' => qr{text/html},  # TODO
+        },{ from    => qr/RT System/,
+            to      => 'enduser at example.com',
+            subject => qr/\Q[example.com #1] The internet is broken\E/,
+            body    => qr{$body},
+            'Content-Type' => qr{text/html},  # TODO
+        };
+    } [(qr/uninitialized value/, qr/Failed to downgrade HTML/)x3];
+}
 
-diag "Failing HTML -> Text conversion";
-warnings_like {
-    my $body = '<table><tr><td><table><tr><td>Foo</td></tr></table></td></tr></table>';
-    mail_ok {
-        ($ok, $tmsg) = $t->Correspond(
-            MIMEObj => HTML::Mason::Commands::MakeMIMEEntity(
-                Body => $body,
-                Type => 'text/html',
-            ),
-        );
-    } { from    => qr/RT System/,
-        bcc     => 'root at localhost',
-        subject => qr/\Q[example.com #1] The internet is broken\E/,
-        body    => qr{Ticket URL: <a href="(http://localhost:\d+/Ticket/Display\.html\?id=1)">\1</a>.+?$body}s,
-        'Content-Type' => qr{text/html},  # TODO
-    },{ from    => qr/RT System/,
-        to      => 'enduser at example.com',
-        subject => qr/\Q[example.com #1] The internet is broken\E/,
-        body    => qr{<table><tr><td><table><tr><td>Foo</td></tr></table></td></tr></table>},
-        'Content-Type' => qr{text/html},  # TODO
-    };
-} [(qr/uninitialized value/, qr/Failed to downgrade HTML/)x3];
 
 diag "Admin Comment in HTML";
 mail_ok {
@@ -117,9 +121,9 @@ mail_ok {
     bcc     => 'root at localhost',
     subject => qr/\Q[example.com #1] [Comment] The internet is broken\E/,
     body    => parts_regex(
-        'This is a comment about ticket 1 \(http://localhost:\d+/Ticket/Display\.html\?id=1\)\..+?'.
+        'This is a comment about (\[\d+\])?ticket.1(\s*[(<]http://localhost:\d+/Ticket/Display\.html\?id=1[)>])?\..+?'.
         'It is not sent to the Requestor\(s\):.+?'.
-        'Comment test, please!',
+        'Comment test, _?please!_?',
 
         '<p>This is a comment about <a href="http://localhost:\d+/Ticket/Display\.html\?id=1">ticket 1</a>\. '.
         'It is not sent to the Requestor\(s\):</p>.+?'.

commit 195666755ac957831fade67f7a86dcc712107499
Merge: 888d338 6edbbf0
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Tue Feb 3 16:01:02 2015 -0500

    Merge branch '4.0-trunk' into 4.2-trunk
    
    Most of the below conflicts are due to M/D conflicts due to updated year
    in the license tag.
    
    Conflicts:
    	README
    	devel/tools/factory
    	lib/RT/Shredder/Plugin/Users.pm
    	lib/RT/Tickets_SQL.pm
    	sbin/rt-message-catalog
    	share/html/Admin/Elements/EditScrip
    	share/html/Admin/Elements/ListGlobalCustomFields
    	share/html/Admin/Elements/PickCustomFields
    	share/html/Admin/Elements/SelectRights
    	share/html/Elements/QuickCreate
    	share/html/Elements/SelectTicketTypes
    	share/html/NoAuth/RichText/dhandler
    	share/html/NoAuth/css/aileron/base.css
    	share/html/NoAuth/css/aileron/forms.css
    	share/html/NoAuth/css/aileron/images/dhandler
    	share/html/NoAuth/css/aileron/layout.css
    	share/html/NoAuth/css/aileron/main.css
    	share/html/NoAuth/css/aileron/misc.css
    	share/html/NoAuth/css/aileron/msie.css
    	share/html/NoAuth/css/aileron/msie6.css
    	share/html/NoAuth/css/aileron/ticket.css
    	share/html/NoAuth/css/ballard/base.css
    	share/html/NoAuth/css/ballard/images/dhandler
    	share/html/NoAuth/css/ballard/layout.css
    	share/html/NoAuth/css/ballard/main.css
    	share/html/NoAuth/css/ballard/misc.css
    	share/html/NoAuth/css/ballard/msie6.css
    	share/html/NoAuth/css/ballard/ticket.css
    	share/html/NoAuth/css/base/admin.css
    	share/html/NoAuth/css/base/articles.css
    	share/html/NoAuth/css/base/collection.css
    	share/html/NoAuth/css/base/login.css
    	share/html/NoAuth/css/base/main.css
    	share/html/NoAuth/css/base/misc.css
    	share/html/NoAuth/css/base/msie6.css
    	share/html/NoAuth/css/base/nav.css
    	share/html/NoAuth/css/base/portlets.css
    	share/html/NoAuth/css/base/theme-editor.css
    	share/html/NoAuth/css/base/ticket-form.css
    	share/html/NoAuth/css/base/ticket.css
    	share/html/NoAuth/css/base/tools.css
    	share/html/NoAuth/css/print.css
    	share/html/NoAuth/css/web2/base.css
    	share/html/NoAuth/css/web2/images/dhandler
    	share/html/NoAuth/css/web2/main.css
    	share/html/NoAuth/css/web2/misc.css
    	share/html/NoAuth/css/web2/msie6.css
    	share/html/NoAuth/css/web2/ticket.css
    	share/html/NoAuth/images/autohandler
    	share/html/NoAuth/js/jquery-ui-patch-datepicker.js
    	share/html/NoAuth/js/jquery_noconflict.js
    	share/html/NoAuth/js/late.js
    	share/html/NoAuth/js/userautocomplete.js
    	share/html/REST/1.0/search/ticket
    	share/html/Search/Graph.html
    	share/html/Search/Results.tsv
    	share/html/Ticket/Elements/ShowDependencies
    	share/html/Ticket/Elements/ShowHistory
    	share/html/Ticket/Elements/ShowTransaction
    	share/html/Ticket/Elements/ShowUserEntry
    	share/html/Ticket/ModifyPeople.html
    	share/html/Tools/Offline.html
    	share/static/css/aileron/boxes.css
    	share/static/css/aileron/nav.css
    	share/static/css/aileron/ticket-lists.css
    	share/static/css/aileron/ticket-search.css
    	share/static/css/ballard/boxes.css
    	share/static/css/ballard/msie.css
    	share/static/css/ballard/nav.css
    	share/static/css/ballard/ticket-lists.css
    	share/static/css/ballard/ticket-search.css
    	share/static/css/base/forms.css
    	share/static/css/base/rights-editor.css
    	share/static/css/mobile.css
    	share/static/css/web2/boxes.css
    	share/static/css/web2/layout.css
    	share/static/css/web2/msie.css
    	share/static/css/web2/nav.css
    	share/static/css/web2/ticket-lists.css
    	share/static/css/web2/ticket-search.css
    	share/static/js/cascaded.js
    	share/static/js/combobox.js
    	share/static/js/util.js

diff --cc README
index 1a403e3,5a4b923..bdd8e09
--- a/README
+++ b/README
@@@ -66,13 -66,15 +66,15 @@@ GENERAL INSTALLATIO
  
      If you are upgrading from a previous version of RT, please review
      the upgrade notes for the appropriate versions, which can be found
 -    in docs/UPGRADING-* If you are coming from 3.8.6 to 4.0.x you should
 -    review both the UPGRADING-3.8 and UPGRADING-4.0 file.  Similarly, if
 -    you were coming from 3.6.7, you would want to review UPGRADING-3.6,
 -    UPGRADING-3.8 and UPGRADING-4.0
 +    in docs/UPGRADING-* If you are coming from 4.0.x to 4.2.x you should
 +    review both the UPGRADING-4.0 and UPGRADING-4.2 file.  Similarly, if
 +    you were coming from 3.8.x, you would want to review UPGRADING-3.8,
 +    UPGRADING-4.0 and UPGRADING-4.2
  
-     It is particularly important that you read the warnings at the top of
-     UPGRADING-4.0 for some common issues.
+     Any upgrade steps given in version-specific UPGRADING files should
+     be run after the rest of the steps below; however, please read the
+     relevant documentation before beginning the upgrade, soas to be
+     aware of important changes.
  
      RT stores the arguments given to ./configure at the top of the
      etc/RT_Config.pm file in case you need to recreate your previous use
@@@ -157,11 -168,17 +159,13 @@@
  
        You should back up your database before running this command.
        When you run it, you will be prompted for your previous version of
 -      RT (such as 3.6.4) so that the appropriate set of database
 +      RT (such as 3.8.1) so that the appropriate set of database
        upgrades can be applied.
  
 -      Finally, clear the Mason cache dir:
 -
 -          rm -fr /opt/rt4/var/mason_data/obj
 -
        If 'make upgrade-database' completes without error, your upgrade
-       has been successful and you may restart your webserver.
+       has been successful; you should now run any commands that were
+       supplied in version-specific UPGRADING documentation.  You should
+       then restart your webserver.
  
   7) Configure the web server, as described in docs/web_deployment.pod,
      and the email gateway, as described below.
diff --cc lib/RT/Action/SendEmail.pm
index c933731,c668d15..cee612e
--- a/lib/RT/Action/SendEmail.pm
+++ b/lib/RT/Action/SendEmail.pm
@@@ -610,9 -616,10 +610,10 @@@ sub SetRTSpecialHeaders 
  # XXX, TODO: use /ShowUser/ShowUserEntry(or something like that) when it would be
  #            refactored into user's method.
      if ( my $email = $self->TransactionObj->CreatorObj->EmailAddress
+          and ! defined $self->TemplateObj->MIMEObj->head->get("RT-Originator")
           and RT->Config->Get('UseOriginatorHeader')
      ) {
 -        $self->SetHeader( 'RT-Originator', $email );
 +        $self->SetHeader( 'X-RT-Originator', $email );
      }
  
  }
diff --cc lib/RT/Shredder/Plugin/Users.pm
index ebba0d1,2f6fbd9..7e1c31f
--- a/lib/RT/Shredder/Plugin/Users.pm
+++ b/lib/RT/Shredder/Plugin/Users.pm
@@@ -79,14 -79,9 +79,14 @@@ be selected for deletion. Identifier i
  or id of a group, as well C<Privileged> or <unprivileged> can used
  to select people from system groups.
  
 +=head2 not_member_of - group identifier
 +
 +Like member_of, but selects users who are not members of the provided
 +group.
 +
  =head2 replace_relations - user identifier
  
- When you delete an user there could be minor links to them in the RT database.
+ When you delete a user there could be minor links to them in the RT database.
  This option allow you to replace these links with links to the new user.
  The replaceable links are Creator and LastUpdatedBy, but NOT any watcher roles.
  This means that if the user is a watcher(Requestor, Owner,
diff --cc share/html/Elements/QuickCreate
index b3d0d6f,fa03f20..b016314
--- a/share/html/Elements/QuickCreate
+++ b/share/html/Elements/QuickCreate
@@@ -68,7 -69,7 +69,7 @@@
  </tr>
  <tr class="input-row">
      <td class="label"><&|/l&>Requestors</&>:</td>
-     <td colspan="3" class="value"><& /Elements/EmailInput, Name => 'Requestors', Size => '40', Default => $ARGS{Requestors} || $session{CurrentUser}->EmailAddress, AutocompleteMultiple => 1 &></td>
 -    <td colspan="3" class="value"><& /Elements/EmailInput, Name => 'Requestors', Size => '40', Default => $args->{Requestors} || $session{CurrentUser}->EmailAddress &></td>
++    <td colspan="3" class="value"><& /Elements/EmailInput, Name => 'Requestors', Size => '40', Default => $args->{Requestors} || $session{CurrentUser}->EmailAddress, AutocompleteMultiple => 1 &></td>
  </tr>
  <tr class="input-row">
  <td class="labeltop"><&|/l&>Content</&>:</td>
diff --cc share/html/Elements/TSVExport
index cf037e1,0000000..ae59455
mode 100644,000000..100644
--- a/share/html/Elements/TSVExport
+++ b/share/html/Elements/TSVExport
@@@ -1,128 -1,0 +1,130 @@@
 +%# BEGIN BPS TAGGED BLOCK {{{
 +%#
 +%# COPYRIGHT:
 +%#
 +%# This software is Copyright (c) 1996-2015 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 }}}
 +<%ARGS>
 +$Class => undef
 +$Collection
 +$Format
 +$PreserveNewLines => 0
 +</%ARGS>
 +<%ONCE>
 +my $no_html = HTML::Scrubber->new( deny => '*' );
 +</%ONCE>
 +<%INIT>
 +require HTML::Entities;
 +$Class ||= $Collection->ColumnMapClassName;
 +
 +$r->content_type('application/vnd.ms-excel');
 +
 +my $DisplayFormat = $m->comp('/Elements/ScrubHTML', Content => $Format);
 +
 +my @Format = $m->comp('/Elements/CollectionAsTable/ParseFormat', Format => $DisplayFormat);
 +
 +my @columns;
 +
 +my $should_loc = { map { $_ => 1 } qw(Status) };
 +
 +my $col_entry = sub {
 +    my $col = shift;
 +    # in tsv output, "#" is often a comment character but we use it for "id"
 +    delete $col->{title}
 +        if $col->{title} and $col->{title} =~ /^\s*#\s*$/;
 +    return {
 +        header => loc($col->{title} || $col->{attribute}),
 +        map    => $m->comp(
 +            "/Elements/ColumnMap",
 +            Name  => $col->{attribute},
 +            Attr  => 'value',
 +            Class => $Class,
 +        ),
 +        should_loc => $should_loc->{$col->{attribute}},
 +    }
 +};
 +
 +if ($PreserveNewLines) {
 +    my $col = [];
 +    push @columns, $col;
 +    for (@Format) {
 +        if ($_->{title} eq 'NEWLINE') {
 +            $col = [];
 +            push @columns, $col;
 +        }
 +        else {
 +            push @$col, $col_entry->($_);
 +        }
 +    }
 +}
 +else {
 +    push @columns, [map { $_->{attribute}
 +                          ? $col_entry->($_)
 +                          : () } @Format];
 +}
 +
 +for (@columns) {
 +    $m->out(join("\t", map { $_->{header} } @$_)."\n");
 +}
 +
++my $i = 0;
 +my $ii = 0;
 +while (my $row = $Collection->Next) {
 +    for my $col (@columns) {
 +        $m->out(join("\t", map {
 +            my $val = ProcessColumnMapValue($_->{map}, Arguments => [$row, $ii++], Escape => 0);
 +            $val = loc($val) if $_->{should_loc};
 +            # remove tabs from all field values, they screw up the tsv
 +            $val = '' unless defined $val;
 +            $val =~ s/(?:\n|\r)+/ /g; $val =~ s{\t}{    }g;
 +            $val = $no_html->scrub($val);
 +            $val = HTML::Entities::decode_entities($val);
 +            $val;
 +        } @$col)."\n");
 +    }
++    $m->flush_buffer unless ++$i % 10;
 +}
 +$m->abort();
 +
 +</%INIT>
diff --cc share/html/Ticket/Create.html
index 4639c1d,c65ec94..1bfb58a
--- a/share/html/Ticket/Create.html
+++ b/share/html/Ticket/Create.html
@@@ -53,8 -53,8 +53,9 @@@
  <& /Elements/ListActions, actions => \@results &>
  
  <form action="<% RT->Config->Get('WebPath') %>/Ticket/Create.html" method="post" enctype="multipart/form-data" name="TicketCreate">
+   <input type="submit" name="SubmitTicket" value="Create" style="display:none">
    <input type="hidden" class="hidden" name="id" value="new" />
 +  <input type="hidden" class="hidden" name="Token" value="<% $ARGS{'Token'} %>" />
    
  % $m->callback( CallbackName => 'FormStart', QueueObj => $QueueObj, ARGSRef => \%ARGS );
  
diff --cc share/html/Ticket/ModifyAll.html
index 5fb4a5b,af4756e..fcf449e
--- a/share/html/Ticket/ModifyAll.html
+++ b/share/html/Ticket/ModifyAll.html
@@@ -53,12 -53,12 +53,13 @@@
  
  <form method="post" action="ModifyAll.html" name="TicketModifyAll" enctype="multipart/form-data">
  % $m->callback( CallbackName => 'FormStart', ARGSRef => \%ARGS );
+ <input type="submit" name="SubmitTicket" value="Save Changes" style="display:none">
  <input type="hidden" class="hidden" name="id" value="<%$Ticket->Id%>" />
 +<input type="hidden" class="hidden" name="Token" value="<% $ARGS{'Token'} %>" />
  
  <&| /Widgets/TitleBox, title => loc('Modify ticket # [_1]', $Ticket->Id), class=>'ticket-info-basics' &>
 -<& Elements/EditBasics, TicketObj => $Ticket &>
 -<& Elements/EditCustomFields, TicketObj => $Ticket &>
 +<& Elements/EditBasics, TicketObj => $Ticket, defaults => \%ARGS &>
 +<& /Elements/EditCustomFields, Object => $Ticket, Grouping => 'Basics' &>
  </&>
  
  % $m->callback(CallbackName => 'AfterBasics', Ticket => $Ticket);
diff --cc share/html/Ticket/ModifyPeople.html
index 46791ba,93f616a..f216ec8
--- a/share/html/Ticket/ModifyPeople.html
+++ b/share/html/Ticket/ModifyPeople.html
@@@ -51,7 -51,8 +51,8 @@@
  % $m->callback(CallbackName => 'BeforeActionList', Actions => \@results, ARGSRef => \%ARGS, Ticket => $Ticket);
  <& /Elements/ListActions, actions => \@results &>
  
 -<form method="post" action="ModifyPeople.html">
 +<form method="post" action="ModifyPeople.html" name="TicketPeople">
+ <input type="submit" name="SubmitTicket" value="Save Changes" style="display:none">
  <input type="hidden" class="hidden" name="id" value="<%$Ticket->Id%>" />
  % $m->callback( CallbackName => 'FormStart', ARGSRef => \%ARGS );
  <&| /Widgets/TitleBox, title => loc('Modify people related to ticket #[_1]', $Ticket->Id),   width => "100%", color=> "#333399", class=>'ticket-info-people' &>
diff --cc share/html/User/Prefs.html
index df5b297,e374e63..3ae793e
--- a/share/html/User/Prefs.html
+++ b/share/html/User/Prefs.html
@@@ -160,15 -154,24 +160,17 @@@
  </table>
  </&>
  
 -<&| /Widgets/TitleBox, title => loc('Custom Fields') &>
 -<table>
 -% my $CustomFields = $UserObj->CustomFields;
 -% while ( my $CF = $CustomFields->Next ) {
 -<tr valign="top">
 -<td align="right"><% loc( $CF->Name ) %>:</td>
 -<td><& /Elements/EditCustomField,
 -    %ARGS, Object => $UserObj, CustomField => $CF
 -&></td></tr>
 -% }
 -</table>
 -</&>
 +
 +
 +<& /Elements/EditCustomFieldCustomGroupings, Object => $UserObj &>
 +
 +
  
+ <& /Elements/Submit, Label => loc('Save Preferences') &>
+ 
  <&| /Widgets/TitleBox, title => loc('Secret authentication token'), id => "user-prefs-feeds" &>
  
 -<&|/l&>All iCal feeds embed a secret token which authorizes you.  If the URL one of your iCal feeds got exposed to the outside world, you can get a new secret, <b>breaking all existing iCal feeds</b> below.</&>
 +<&|/l&>All iCal feeds embed a secret token which authorizes you.  If the URL for one of your iCal feeds was exposed to the outside world, you can get a new secret, <b>breaking all existing iCal feeds</b>, below.</&>
  
  <a href="#" id="ResetAuthTokenPrompt" style="display: none">
    <&|/l&>I want to reset my secret token.</&>
diff --cc share/html/index.html
index a307814,ae15c91..58274db
--- a/share/html/index.html
+++ b/share/html/index.html
@@@ -115,33 -115,25 +116,39 @@@ if ( $ARGS{'QuickCreate'} ) 
                          Subject => $ARGS{'Subject'});
          push @results, $msg;
  
-         if ( $t && $t->Id && RT->Config->Get('DisplayTicketAfterQuickCreate', $session{'CurrentUser'}) ) {
-             MaybeRedirectForResults(
-                 Actions   => \@results,
-                 Path      => '/Ticket/Display.html',
-                 Arguments => { id => $t->Id },
-             );
+         if ( $t && $t->Id ) {
+             $created = 1;
+             if ( RT->Config->Get('DisplayTicketAfterQuickCreate', $session{'CurrentUser'}) ) {
+                 MaybeRedirectForResults(
+                     Actions   => \@results,
+                     Path      => '/Ticket/Display.html',
+                     Arguments => { id => $t->Id },
+                 );
+             }
          }
 -
      }
      elsif ( !$ValidCFs ) {
 -        push @results, "can't quickly create ticket in queue " .
 -            $QueueObj->Name . ' because some custom fields need to be set, please go to normal ticket creation page to do that.';
 +        push @results, loc("Can't quickly create ticket in queue [_1] because custom fields are required.  Please finish by using the normal ticket creation page.", $QueueObj->Name);
 +        push @results, @msg;
 +
 +        MaybeRedirectForResults(
 +            Actions     => \@results,
 +            Path        => "/Ticket/Create.html",
 +            Arguments   => {
 +                (map { $_ => $ARGS{$_} } qw(Queue Owner Status Content Subject)),
 +                Requestors => $ARGS{Requestors},
 +                # From is set above when CFs are OK, but not here since we're
 +                # not calling CreateTicket() directly.  The proper place to set
 +                # a default for From, if desired in the future, is in
 +                # CreateTicket() itself, or at least /Ticket/Display.html
 +                # (which processes /Ticket/Create.html).  From is rarely used
 +                # overall.
 +            },
 +        );
      }
+ 
+     $session{QuickCreate} = \%ARGS unless $created;
+ 
      MaybeRedirectForResults(
          Actions   => \@results,
          Path      => '/',

commit b01f8750b01e65456ed58dd82b67504ec52751bc
Merge: 1956667 6337505
Author: Todd Wade <todd at bestpractical.com>
Date:   Tue Feb 3 17:03:54 2015 -0500

    Merge branch '4.2/html-external-formatter' into 4.2-trunk


commit e82b9c0fc1ef8b6c9003076e51b386b231c82b88
Author: Wallace Reis <wreis at bestpractical.com>
Date:   Mon Jan 26 20:56:19 2015 -0200

    Lexically cache current user in ticket creation

diff --git a/lib/RT/Interface/Web.pm b/lib/RT/Interface/Web.pm
index efdd301..0603138 100644
--- a/lib/RT/Interface/Web.pm
+++ b/lib/RT/Interface/Web.pm
@@ -2066,9 +2066,10 @@ sub CreateTicket {
 
     my (@Actions);
 
-    my $Ticket = RT::Ticket->new( $session{'CurrentUser'} );
+    my $current_user = $session{'CurrentUser'};
+    my $Ticket = RT::Ticket->new( $current_user );
 
-    my $Queue = RT::Queue->new( $session{'CurrentUser'} );
+    my $Queue = RT::Queue->new( $current_user );
     unless ( $Queue->Load( $ARGS{'Queue'} ) ) {
         Abort('Queue not found');
     }
@@ -2079,12 +2080,12 @@ sub CreateTicket {
 
     my $due;
     if ( defined $ARGS{'Due'} and $ARGS{'Due'} =~ /\S/ ) {
-        $due = RT::Date->new( $session{'CurrentUser'} );
+        $due = RT::Date->new( $current_user );
         $due->Set( Format => 'unknown', Value => $ARGS{'Due'} );
     }
     my $starts;
     if ( defined $ARGS{'Starts'} and $ARGS{'Starts'} =~ /\S/ ) {
-        $starts = RT::Date->new( $session{'CurrentUser'} );
+        $starts = RT::Date->new( $current_user );
         $starts->Set( Format => 'unknown', Value => $ARGS{'Starts'} );
     }
 
@@ -2092,7 +2093,7 @@ sub CreateTicket {
         Content        => $ARGS{Content},
         ContentType    => $ARGS{ContentType},
         StripSignature => 1,
-        CurrentUser    => $session{'CurrentUser'},
+        CurrentUser    => $current_user,
     );
 
     my $MIMEObj = MakeMIMEEntity(
diff --git a/share/html/Ticket/Create.html b/share/html/Ticket/Create.html
index 4639c1d..dcbc0fb 100644
--- a/share/html/Ticket/Create.html
+++ b/share/html/Ticket/Create.html
@@ -299,6 +299,8 @@ $m->callback( CallbackName => "Init", ARGSRef => \%ARGS );
 my $Queue = $ARGS{Queue};
 $session{DefaultQueue} = $Queue;
 
+my $current_user = $session{'CurrentUser'};
+
 if ($CloneTicket) {
     my $CloneTicketObj = RT::Ticket->new( $session{CurrentUser} );
     $CloneTicketObj->Load($CloneTicket)
@@ -374,7 +376,7 @@ my @results;
 
 my $title = loc("Create a new ticket");
 
-my $QueueObj = RT::Queue->new($session{'CurrentUser'});
+my $QueueObj = RT::Queue->new($current_user);
 $QueueObj->Load($Queue) || Abort(loc("Queue [_1] could not be loaded.", $Queue||''));
 
 $m->callback( QueueObj => $QueueObj, title => \$title, results => \@results, ARGSRef => \%ARGS );
@@ -383,7 +385,7 @@ $m->scomp( '/Articles/Elements/SubjectOverride', ARGSRef => \%ARGS, QueueObj =>
 
 $QueueObj->Disabled && Abort(loc("Cannot create tickets in a disabled queue."));
 
-my $ticket = RT::Ticket->new($session{'CurrentUser'}); # empty ticket object
+my $ticket = RT::Ticket->new($current_user); # empty ticket object
 
 ProcessAttachments(ARGSRef => \%ARGS);
 

commit 134a478be1b63ca418380fc355923ea849812bfa
Author: Wallace Reis <wreis at bestpractical.com>
Date:   Mon Jan 26 20:56:33 2015 -0200

    Consistency on ticket creation
    
    RT mocks a MIME object using ticket metadata during creation through web
    UI which doesn't bother to include the basic email headers -- except for
    quick ticket creation form.
    
    Fixes: I#30602

diff --git a/lib/RT/Interface/Web.pm b/lib/RT/Interface/Web.pm
index 0603138..0777ee2 100644
--- a/lib/RT/Interface/Web.pm
+++ b/lib/RT/Interface/Web.pm
@@ -2096,10 +2096,15 @@ sub CreateTicket {
         CurrentUser    => $current_user,
     );
 
+    my $date_now = RT::Date->new( $current_user );
+    $date_now->SetToNow;
     my $MIMEObj = MakeMIMEEntity(
         Subject => $ARGS{'Subject'},
-        From    => $ARGS{'From'},
+        From    => $ARGS{'From'} || $current_user->EmailAddress,
+        To      => $ARGS{'To'} || $Queue->CorrespondAddress
+                               || RT->Config->Get('CorrespondAddress'),
         Cc      => $ARGS{'Cc'},
+        Date    => $date_now->RFC2822(Timezone => 'user'),
         Body    => $sigless,
         Type    => $ARGS{'ContentType'},
         Interface => RT::Interface::Web::MobileClient() ? 'Mobile' : 'Web',
@@ -2466,7 +2471,7 @@ sub MakeMIMEEntity {
         "Message-Id" => Encode::encode( "UTF-8", RT::Interface::Email::GenMessageId ),
         "X-RT-Interface" => $args{Interface},
         map { $_ => Encode::encode( "UTF-8", $args{ $_} ) }
-            grep defined $args{$_}, qw(Subject From Cc)
+            grep defined $args{$_}, qw(Subject From Cc To Date)
     );
 
     if ( defined $args{'Body'} && length $args{'Body'} ) {
diff --git a/share/html/index.html b/share/html/index.html
index a307814..8f89afb 100644
--- a/share/html/index.html
+++ b/share/html/index.html
@@ -110,7 +110,6 @@ if ( $ARGS{'QuickCreate'} ) {
                         Status => $ARGS{'Status'},
                         # yes! it's Requestors, not Requestor
                         Requestors => $ARGS{'Requestors'},
-                        From => $session{'CurrentUser'}->EmailAddress,
                         Content => $ARGS{'Content'},
                         Subject => $ARGS{'Subject'});
         push @results, $msg;

commit ca64a561d36331b8322e4de1296a465bb58e049c
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Wed Feb 4 13:32:41 2015 -0500

    Explicitly encode the HTML before passing it to external programs
    
    HTML::FormatExternal prints the data that it recieves to the external
    program, without explicitly applying an encoding -- that is, it takes
    bytes, not characters.  When perl attempts to use "print" to send these
    to the external program, it (correctly) produces "wide character in
    print" warnings.
    
    Explicitly encode the HTML to format as UTF-8 before sending it over the
    wire; this makes perl's internal representation of the characters
    irrelevant, and squashes the wide character warnings.

diff --git a/lib/RT/Interface/Email.pm b/lib/RT/Interface/Email.pm
index b1ac2bd..5c7c71e 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -1858,7 +1858,10 @@ sub _HTMLFormatter {
                     local $ENV{PATH} = $path || $ENV{PATH}
                         || '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin';
                     local $ENV{HOME} = $RT::VarPath;
-                    $package->format_string($html, leftmargin => 0, rightmargin => 78);
+                    $package->format_string(
+                        Encode::encode( "UTF-8", $html),
+                        leftmargin => 0, rightmargin => 78
+                    );
                 };
             };
         }

commit 5c85b33da06f3a9543d113462cb15f4bbb21bf30
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Wed Feb 4 15:10:25 2015 -0500

    Be explicit about encodings to and from the external formatters
    
    HTML::FormatExternal supports input_charset and output_charset
    parameters controlling how it the input is encoded, and the encoding
    that the formatter should attempt to output; otherwise, the formatter
    attempts to determine based on the <meta> tag.  Many of the snippets
    passed to ConvertHTMLToText do not have meta tags -- and, more
    importantly, in all cases they have already been transcoded to UTF-8,
    making the meta tag possibly incorrect.  Fortunately, the input_charset
    argument is documented to override the charset found in the <meta> tag,
    if any.
    
    Not all formatters have the same support, of course.  The formatters RT
    allows, and their encoding support, are (in preference order):
     * w3m supports arbitrary input and output character sets.
     * elinks supports arbitrary input and output character sets.
     * html2text supports only latin-1 input, and has no control over output
       character set.
     * links supports arbitrary input and output character sets.
     * lynx supports specifying a default input character set, but this does
       not override a <meta> tag.  It supports arbitrary output character
       sets.
    
    Installs with only html2text or lynks are thus likely to suffer from
    encoding problems; move them to be lowest on the priority list.  They
    are still listed because mis-encoded mail is still superior to the
    possibly-empty mail that the "core" formatter would send.
    
    Tests need adjustment because EmailOutputEncoding is a suggestion -- if
    the content contains characters which the given character set cannot
    express, the character set is left as the internal default, UTF-8.  In
    the case of w3m, an <hr /> tag is rendered as a series of code point
    0x2501 ("BOX DRAWINGS HEAVY HORIZONTAL"), which is not in ISO-8859-1; as
    such, the text/plain part is sent in UTF-8, not ISO-8859-1.

diff --git a/lib/RT/Interface/Email.pm b/lib/RT/Interface/Email.pm
index 5c7c71e..18fd1d3 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -1789,9 +1789,9 @@ sub _RecordSendEmailFailure {
 
 =head2 ConvertHTMLToText HTML
 
-Takes HTML and converts it to plain text.  Appropriate for generating a
-plain text part from an HTML part of an email.  Returns undef if
-conversion fails.
+Takes HTML characters and converts it to plain text characters.
+Appropriate for generating a plain text part from an HTML part of an
+email.  Returns undef if conversion fails.
 
 =cut
 
@@ -1809,7 +1809,7 @@ sub _HTMLFormatter {
     if ($wanted) {
         @order = ($wanted, "core");
     } else {
-        @order = ("w3m", "elinks", "html2text", "links", "lynx", "core");
+        @order = ("w3m", "elinks", "links", "html2text", "lynx", "core");
     }
     # Always fall back to core, even if it is not listed
     for my $prog (@order) {
@@ -1854,15 +1854,19 @@ sub _HTMLFormatter {
             RT->Logger->info("Using $prog for HTML -> text conversion");
             $formatter = sub {
                 my $html = shift;
-                RT::Util::safe_run_child {
+                my $text = RT::Util::safe_run_child {
                     local $ENV{PATH} = $path || $ENV{PATH}
                         || '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin';
                     local $ENV{HOME} = $RT::VarPath;
                     $package->format_string(
-                        Encode::encode( "UTF-8", $html),
+                        Encode::encode( "UTF-8", $html ),
+                        input_charset => "UTF-8",
+                        output_charset => "UTF-8",
                         leftmargin => 0, rightmargin => 78
                     );
                 };
+                $text = Encode::decode( "UTF-8", $text );
+                return $text;
             };
         }
         RT->Config->Set( HTMLFormatter => $prog );
diff --git a/t/mail/sendmail.t b/t/mail/sendmail.t
index 76975a1..4ef3206 100644
--- a/t/mail/sendmail.t
+++ b/t/mail/sendmail.t
@@ -49,14 +49,28 @@ for my $encoding ('ISO-8859-1', 'UTF-8') {
     is(@mail, 1);
     like( $mail[0]->head->get('Content-Type'), qr/multipart\/alternative/,
           "Its content type is multipart/alternative" );
-    like( $mail[0]->parts(0)->head->get('Content-Type'), qr/text\/plain.+?$encoding/,
-          "First part's content type is text/plain $encoding" );
+
+    # The text/html part is guaranteed to not have had non-latin-1
+    # characters introduced by the HTML-to-text conversion, so it is
+    # guaranteed to be able to be represented in latin-1
     like( $mail[0]->parts(1)->head->get('Content-Type'), qr/text\/html.+?$encoding/,
           "Second part's content type is text/html $encoding" );
-    my $message_as_string = $mail[0]->parts(0)->bodyhandle->as_string();
+    my $message_as_string = $mail[0]->parts(1)->bodyhandle->as_string();
     $message_as_string = Encode::decode($encoding, $message_as_string);
     like( $message_as_string , qr/H\x{e5}vard/,
           "The message's content contains havard's name in $encoding");
+
+    # The text/plain part may have utf-8 characters in it.  Accept either encoding.
+    like( $mail[0]->parts(0)->head->get('Content-Type'), qr/text\/plain.+?(ISO-8859-1|UTF-8)/i,
+          "First part's content type is text/plain (ISO-8859-1 or UTF-8)" );
+
+    # Make sure it checks out in whatever encoding it ended up in
+    $mail[0]->parts(0)->head->get('Content-Type') =~ /text\/plain.+?(ISO-8859-1|UTF-8)/i;
+    my $found = $1 || $encoding;
+    $message_as_string = $mail[0]->parts(0)->bodyhandle->as_string();
+    $message_as_string = Encode::decode($found, $message_as_string);
+    like( $message_as_string , qr/H\x{e5}vard/,
+          "The message's content contains havard's name in $encoding");
 }
 
 {

commit 155e7d5499b65911272984e43bfa98da59b3a450
Merge: ca64a56 5c85b33
Author: Kevin Falcone <falcone at bestpractical.com>
Date:   Wed Feb 4 16:08:58 2015 -0500

    Merge branch '4.2/external-html-encoding' into 4.2-trunk


commit 8450f0a9f233d6a761ac22dbdf14926abc54d7fa
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Wed Feb 4 16:17:33 2015 -0500

    Only search Content, not Content and Subject, for better indexing
    
    A TicketSQL query of « Subject LIKE 'word' OR Content LIKE 'word' »
    generates the following SQL (simplified):
    
        SELECT DISTINCT main.*
          FROM Tickets main
               JOIN Transactions Transactions_1
                 ON ( Transactions_1.ObjectType = 'RT::Ticket' )
                AND ( Transactions_1.ObjectId = main.id )
          LEFT JOIN Attachments Attachments_2
                 ON ( Attachments_2.TransactionId = Transactions_1.id )
         WHERE main.Subject LIKE '%word%'
            OR Attachments_2.Content MATCHES 'word'
    
    This leaves both MySQL and Postgres unable to use their full-text-search
    indexes on the Content search, as their query planner cannot separate
    this unto a union of two queries which are both well-indexed.  Instead,
    both databases elect to perform sequential scans of all three involved
    tables.  While the full-text index does speed this process, the
    sequential scans nonethless cause the query to take noticable time.
    
    For the common case of simple search with FTS enabled, default to
    searching the Content rather than suffer the performance penalties of
    searching both.  In nearly all cases, the words of the subject are
    contained within the words of the content; later work will ensure that
    the full-text-search index contains the information from the subject,
    allowing a performant but fully inclusive search.

diff --git a/lib/RT/Search/Simple.pm b/lib/RT/Search/Simple.pm
index 039f7c7..4cb2482 100644
--- a/lib/RT/Search/Simple.pm
+++ b/lib/RT/Search/Simple.pm
@@ -245,7 +245,7 @@ sub GuessType {
 sub HandleDefault   {
     my $fts = RT->Config->Get('FullTextSearch');
     if ($fts->{Enable} and $fts->{Indexed}) {
-        return default => "(Subject LIKE '$_[1]' OR Content LIKE '$_[1]')";
+        return default => "Content LIKE '$_[1]'";
     } else {
         return default => "Subject LIKE '$_[1]'";
     }

commit 5d96c768c8f66e59d08736c9bfa61850c2ea9436
Merge: 155e7d5 93051de
Author: Kevin Falcone <falcone at bestpractical.com>
Date:   Wed Feb 4 16:29:21 2015 -0500

    Merge branch '4.2/mysql-native-fts' into 4.2-trunk


commit d7f66b5ff37ff8393fb0454aeb6ffcfe2f6dfc89
Merge: 5d96c76 8450f0a
Author: Kevin Falcone <falcone at bestpractical.com>
Date:   Wed Feb 4 16:41:37 2015 -0500

    Merge branch '4.2/simple-search-content-only' into 4.2-trunk


commit d47ec8242f48bd55fd2a0d1af5200e9410f5325b
Merge: d7f66b5 4cf6c91
Author: Kevin Falcone <falcone at bestpractical.com>
Date:   Wed Feb 4 16:59:04 2015 -0500

    Merge branch '4.2/mysql-database-names' into 4.2-trunk


commit fca65159ddd41d8ee030a44214a2f337dfa05dbd
Author: pccowboy <dswift at tdl.com>
Date:   Thu Feb 5 13:36:50 2015 -0800

    Expose Rows and Cols parameters to the EditCustomField callback

diff --git a/share/html/Elements/EditCustomField b/share/html/Elements/EditCustomField
index c506d48..db6d181 100644
--- a/share/html/Elements/EditCustomField
+++ b/share/html/Elements/EditCustomField
@@ -105,7 +105,7 @@ $m->out("\n".'<input type="hidden" class="hidden" name="'
 
 
 my $EditComponent = "EditCustomField$Type";
-$m->callback( %ARGS, CallbackName => 'EditComponentName', Name => \$EditComponent, CustomField => $CustomField, Object => $Object );
+$m->callback( %ARGS, CallbackName => 'EditComponentName', Name => \$EditComponent, CustomField => $CustomField, Object => $Object, Rows => \$Rows, Cols => \$Cols);
 $EditComponent = "EditCustomField$Type" unless $m->comp_exists($EditComponent);
 
 return $m->comp(

commit 94ec5d1079767e8e4b54fb04c51cadb571ffc017
Merge: fca6515 134a478
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Thu Feb 5 17:54:04 2015 -0500

    Merge branch '4.2/pseudo-from-ticket-creation' into 4.2-trunk


commit 6876523b8d7a889575bf61d304db6cb2ba9bc47f
Author: Jesse Vincent <jesse at bestpractical.com>
Date:   Thu Feb 5 14:33:18 2015 -0800

    Add the ability to search for tickets based on queue lifecycle
    
    It's sometimes useful to be able to search for all tickets in a
    specific lifecycle.  This commit adds the 'Lifecycle' field to
    TicketSQL, as well as associated tests.
    
    Code by Alex Vandiver. Tests by Jesse Vincent.

diff --git a/lib/RT/Tickets.pm b/lib/RT/Tickets.pm
index 58900a2..c641cd2 100644
--- a/lib/RT/Tickets.pm
+++ b/lib/RT/Tickets.pm
@@ -154,6 +154,7 @@ our %FIELD_METADATA = (
     TxnCF            => [ 'CUSTOMFIELD' => 'Transaction' ], #loc_left_pair
     TransactionCF    => [ 'CUSTOMFIELD' => 'Transaction' ], #loc_left_pair
     QueueCF          => [ 'CUSTOMFIELD' => 'Queue' ], #loc_left_pair
+    Lifecycle        => [ 'LIFECYCLE' ], #loc_left_pair
     Updated          => [ 'TRANSDATE', ], #loc_left_pair
     UpdatedBy        => [ 'TRANSCREATOR', ], #loc_left_pair
     OwnerGroup       => [ 'MEMBERSHIPFIELD' => 'Owner', ], #loc_left_pair
@@ -191,6 +192,7 @@ our %dispatch = (
     MEMBERSHIPFIELD => \&_WatcherMembershipLimit,
     CUSTOMFIELD     => \&_CustomFieldLimit,
     HASATTRIBUTE    => \&_HasAttributeLimit,
+    LIFECYCLE       => \&_LifecycleLimit,
 );
 
 # Default EntryAggregator per type
@@ -1236,6 +1238,26 @@ sub _HasAttributeLimit {
 }
 
 
+sub _LifecycleLimit {
+    my ( $self, $field, $op, $value, %rest ) = @_;
+
+    die "Invalid Operator $op for $field" if $op =~ /^(IS|IS NOT)$/io;
+    my $queue = $self->{_sql_aliases}{queues} ||= $_[0]->Join(
+        ALIAS1 => 'main',
+        FIELD1 => 'Queue',
+        TABLE2 => 'Queues',
+        FIELD2 => 'id',
+    );
+
+    $self->Limit(
+        ALIAS    => $queue,
+        FIELD    => 'Lifecycle',
+        OPERATOR => $op,
+        VALUE    => $value,
+        %rest,
+    );
+}
+
 # End Helper Functions
 
 # End of SQL Stuff -------------------------------------------------
diff --git a/t/ticket/search.t b/t/ticket/search.t
index 852241f..a43433f 100644
--- a/t/ticket/search.t
+++ b/t/ticket/search.t
@@ -283,4 +283,35 @@ like($tix->BuildSelectCountQuery, qr/\bNULL\b/, "Contains upper-case NULL");
 unlike($tix->BuildSelectCountQuery, qr/\bnull\b/, "Lacks lower-case NULL");
 
 
+# tests for searching by queue lifecycle
+$tix = RT::Tickets->new(RT->SystemUser);
+$tix->FromSQL('Lifecycle="default"');
+is($tix->Count,7,"We found all 7 tickets in a queue with the default lifecycle");
+
+$tix = RT::Tickets->new(RT->SystemUser);
+$tix->FromSQL('Lifecycle ="approvals" OR Lifecycle="default"');
+is($tix->Count,7,"We found 7 tickets in a queue with a lifecycle of default or approvals");
+
+$tix = RT::Tickets->new(RT->SystemUser);
+$tix->FromSQL('Lifecycle ="approvals" AND Lifecycle="default"');
+is($tix->Count,0,"We found 0 tickets in a queue with a lifecycle of default AND approvals...(because that's impossible");
+
+$tix = RT::Tickets->new(RT->SystemUser);
+$tix->FromSQL('Queue="'.$queue.'" AND Lifecycle="default"');
+is($tix->Count,7,"We found 7 tickets in $queue with a lifecycle of default");
+
+
+$tix = RT::Tickets->new(RT->SystemUser);
+$tix->FromSQL('Lifecycle !="approvals"');
+is($tix->Count,7,"We found 7 tickets in a queue with a lifecycle other than approvals");
+
+$tix = RT::Tickets->new(RT->SystemUser);
+$tix->FromSQL('Lifecycle!="default"');
+is($tix->Count,0,"We found 0 tickets in a queue with a lifecycle other than default");
+
+$tix = RT::Tickets->new(RT->SystemUser);
+$tix->FromSQL('Lifecycle="approvals"');
+is($tix->Count,0,"We found 0 tickets in a queue with the approvals lifecycle");
+
+
 done_testing;

commit a9a35134f25d9a1854b929e86323cdba6470d1f9
Merge: 94ec5d1 6876523
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Fri Feb 6 12:15:57 2015 -0500

    Merge branch '4.2/search-by-lifecycle' into 4.2-trunk


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


More information about the rt-commit mailing list