[freeside-commits] branch master updated. c5922713bc199600bc8c203155a4016c9d24cbbc

Ivan ivan at 420.am
Tue May 27 15:20:12 PDT 2014


The branch, master has been updated
       via  c5922713bc199600bc8c203155a4016c9d24cbbc (commit)
      from  7c841dec307feed06ee532ac18e4114ef68243a8 (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
commit c5922713bc199600bc8c203155a4016c9d24cbbc
Author: Ivan Kohler <ivan at freeside.biz>
Date:   Tue May 27 15:20:05 2014 -0700

    rt 4.0.20 (RT#13852)

diff --git a/rt/configure b/rt/configure
index 3abb324..60d9fb1 100755
--- a/rt/configure
+++ b/rt/configure
@@ -1,7 +1,7 @@
 #! /bin/sh
 # From configure.ac Revision.
 # Guess values for system-dependent variables and create Makefiles.
-# Generated by GNU Autoconf 2.68 for RT rt-4.0.19.
+# Generated by GNU Autoconf 2.68 for RT rt-4.0.20.
 #
 # Report bugs to <rt-bugs at bestpractical.com>.
 #
@@ -560,8 +560,8 @@ MAKEFLAGS=
 # Identity of this package.
 PACKAGE_NAME='RT'
 PACKAGE_TARNAME='rt'
-PACKAGE_VERSION='rt-4.0.19'
-PACKAGE_STRING='RT rt-4.0.19'
+PACKAGE_VERSION='rt-4.0.20'
+PACKAGE_STRING='RT rt-4.0.20'
 PACKAGE_BUGREPORT='rt-bugs at bestpractical.com'
 PACKAGE_URL=''
 
@@ -1311,7 +1311,7 @@ if test "$ac_init_help" = "long"; then
   # Omit some internal or obsolete options to make the list less imposing.
   # This message is too long to be a string in the A/UX 3.1 sh.
   cat <<_ACEOF
-\`configure' configures RT rt-4.0.19 to adapt to many kinds of systems.
+\`configure' configures RT rt-4.0.20 to adapt to many kinds of systems.
 
 Usage: $0 [OPTION]... [VAR=VALUE]...
 
@@ -1372,7 +1372,7 @@ fi
 
 if test -n "$ac_init_help"; then
   case $ac_init_help in
-     short | recursive ) echo "Configuration of RT rt-4.0.19:";;
+     short | recursive ) echo "Configuration of RT rt-4.0.20:";;
    esac
   cat <<\_ACEOF
 
@@ -1496,7 +1496,7 @@ fi
 test -n "$ac_init_help" && exit $ac_status
 if $ac_init_version; then
   cat <<\_ACEOF
-RT configure rt-4.0.19
+RT configure rt-4.0.20
 generated by GNU Autoconf 2.68
 
 Copyright (C) 2010 Free Software Foundation, Inc.
@@ -1597,7 +1597,7 @@ cat >config.log <<_ACEOF
 This file contains any messages produced by compilers while
 running configure, to aid debugging if configure makes a mistake.
 
-It was created by RT $as_me rt-4.0.19, which was
+It was created by RT $as_me rt-4.0.20, which was
 generated by GNU Autoconf 2.68.  Invocation command line was
 
   $ $0 $@
@@ -1954,7 +1954,7 @@ rt_version_major=4
 
 rt_version_minor=0
 
-rt_version_patch=19
+rt_version_patch=20
 
 test "x$rt_version_major" = 'x' && rt_version_major=0
 test "x$rt_version_minor" = 'x' && rt_version_minor=0
@@ -3923,7 +3923,7 @@ RT_LOG_PATH_R=${exp_logfiledir}
 fi
 
 
-ac_config_files="$ac_config_files etc/upgrade/3.8-ical-extension etc/upgrade/split-out-cf-categories etc/upgrade/generate-rtaddressregexp etc/upgrade/upgrade-articles etc/upgrade/vulnerable-passwords sbin/rt-attributes-viewer sbin/rt-preferences-viewer sbin/rt-session-viewer sbin/rt-dump-metadata sbin/rt-setup-database sbin/rt-test-dependencies sbin/rt-email-digest sbin/rt-email-dashboards sbin/rt-clean-sessions sbin/rt-shredder sbin/rt-validator sbin/rt-validate-aliases sbin/rt-email-group-admin sbin/rt-server sbin/rt-server.fcgi sbin/standalone_httpd sbin/rt-setup-fulltext-index sbin/rt-fulltext-indexer bin/rt-crontool bin/rt-mailgate bin/rt"
+ac_config_files="$ac_config_files etc/upgrade/3.8-ical-extension etc/upgrade/4.0-customfield-checkbox-extension etc/upgrade/split-out-cf-categories etc/upgrade/generate-rtaddressregexp etc/upgrade/upgrade-articles etc/upgrade/vulnerable-passwords sbin/rt-attributes-viewer sbin/rt-preferences-viewer sbin/rt-session-viewer sbin/rt-dump-metadata sbin/rt-setup-database sbin/rt-test-dependencies sbin/rt-email-digest sbin/rt-email-dashboards sbin/rt-clean-sessions sbin/rt-shredder sbin/rt-validator sbin/rt-validate-aliases sbin/rt-email-group-admin sbin/rt-server sbin/rt-server.fcgi sbin/standalone_httpd sbin/rt-setup-fulltext-index sbin/rt-fulltext-indexer bin/rt-crontool bin/rt-mailgate bin/rt"
 
 
 ac_config_files="$ac_config_files Makefile etc/RT_Config.pm lib/RT/Generated.pm t/data/configs/apache2.2+mod_perl.conf t/data/configs/apache2.2+fastcgi.conf"
@@ -4482,7 +4482,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
 # report actual input values of CONFIG_FILES etc. instead of their
 # values after options handling.
 ac_log="
-This file was extended by RT $as_me rt-4.0.19, which was
+This file was extended by RT $as_me rt-4.0.20, which was
 generated by GNU Autoconf 2.68.  Invocation command line was
 
   CONFIG_FILES    = $CONFIG_FILES
@@ -4535,7 +4535,7 @@ _ACEOF
 cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
 ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`"
 ac_cs_version="\\
-RT config.status rt-4.0.19
+RT config.status rt-4.0.20
 configured by $0, generated by GNU Autoconf 2.68,
   with options \\"\$ac_cs_config\\"
 
@@ -4647,6 +4647,7 @@ for ac_config_target in $ac_config_targets
 do
   case $ac_config_target in
     "etc/upgrade/3.8-ical-extension") CONFIG_FILES="$CONFIG_FILES etc/upgrade/3.8-ical-extension" ;;
+    "etc/upgrade/4.0-customfield-checkbox-extension") CONFIG_FILES="$CONFIG_FILES etc/upgrade/4.0-customfield-checkbox-extension" ;;
     "etc/upgrade/split-out-cf-categories") CONFIG_FILES="$CONFIG_FILES etc/upgrade/split-out-cf-categories" ;;
     "etc/upgrade/generate-rtaddressregexp") CONFIG_FILES="$CONFIG_FILES etc/upgrade/generate-rtaddressregexp" ;;
     "etc/upgrade/upgrade-articles") CONFIG_FILES="$CONFIG_FILES etc/upgrade/upgrade-articles" ;;
@@ -5099,6 +5100,8 @@ which seems to be undefined.  Please make sure it is defined" >&2;}
   case $ac_file$ac_mode in
     "etc/upgrade/3.8-ical-extension":F) chmod ug+x $ac_file
                 ;;
+    "etc/upgrade/4.0-customfield-checkbox-extension":F) chmod ug+x $ac_file
+                ;;
     "etc/upgrade/split-out-cf-categories":F) chmod ug+x $ac_file
                 ;;
     "etc/upgrade/generate-rtaddressregexp":F) chmod ug+x $ac_file
diff --git a/rt/configure.ac b/rt/configure.ac
index 47ec7c9..e9f7d5f 100644
--- a/rt/configure.ac
+++ b/rt/configure.ac
@@ -408,6 +408,7 @@ dnl Configure the output files, and generate them.
 dnl Binaries that should be +x
 AC_CONFIG_FILES([
                  etc/upgrade/3.8-ical-extension
+                 etc/upgrade/4.0-customfield-checkbox-extension
                  etc/upgrade/split-out-cf-categories
                  etc/upgrade/generate-rtaddressregexp
                  etc/upgrade/upgrade-articles
diff --git a/rt/docs/UPGRADING-4.0 b/rt/docs/UPGRADING-4.0
index 63dd2ee..766964f 100644
--- a/rt/docs/UPGRADING-4.0
+++ b/rt/docs/UPGRADING-4.0
@@ -32,6 +32,9 @@ If you deploy RT with mod_perl, Apache will no longer start with C<SetHandler>
 set to `perl-script`. F<docs/web_deployment.pod> contains the
 new configuration.
 
+RT::Extension::CustomField::Checkbox has been integrated into core, so you
+MUST uninstall it before upgrading. In addition, you must run
+etc/upgrade/4.0-customfield-checkbox-extension script to convert old data.
 
 =head2 RT_SiteConfig.pm
 
diff --git a/rt/docs/extending/clickable_links.pod b/rt/docs/extending/clickable_links.pod
index 91e9eec..dd80ff1 100644
--- a/rt/docs/extending/clickable_links.pod
+++ b/rt/docs/extending/clickable_links.pod
@@ -89,11 +89,20 @@ add action types.  Values are subroutine references which will get
 called when needed.  They should return the modified string. Note that
 subroutine B<must escape> HTML.
 
-=item handler
+=item handle
 
 A subroutine reference; modify it only if you have to. This can be used
 to add pre- or post-processing around all actions.
 
+=item cache
+
+An undefined variable that should be replaced with a subroutine
+reference. This subroutine will be called twice, once with the arguments
+fetch => content_ref and once with store => content_ref. In the fetch
+case, if a cached copy is found, return the cached content, otherwise
+return a false value. When passed store, you should populate your cache
+with the content. The return value is ignored in this case.
+
 =back
 
 =head2 Actions' arguments
diff --git a/rt/etc/RT_Config.pm.in b/rt/etc/RT_Config.pm.in
index a52965a..dace2d7 100644
--- a/rt/etc/RT_Config.pm.in
+++ b/rt/etc/RT_Config.pm.in
@@ -296,8 +296,9 @@ Set(@LogToSyslogConf, ());
 =item C<$EmailSubjectTagRegex>
 
 This regexp controls what subject tags RT recognizes as its own.  If
-you're not dealing with historical C<$rtname> values, you'll likely
-never have to change this configuration.
+you're not dealing with historical C<$rtname> values, or historical
+queue-specific subject tags, you'll likely never have to change this
+configuration.
 
 Be B<very careful> with it. Note that it overrides C<$rtname> for
 subject token matching and that you should use only "non-capturing"
diff --git a/rt/etc/upgrade/3.8.9/content b/rt/etc/upgrade/3.8.9/content
index 898c19e..d7d64f5 100644
--- a/rt/etc/upgrade/3.8.9/content
+++ b/rt/etc/upgrade/3.8.9/content
@@ -56,6 +56,7 @@
 s!(?<=Your ticket has been (?:approved|rejected) by { eval { )\$Approval->OwnerObj->Name!\$Approver->Name!
               )
             {
+                $template->SetType('Perl');
                 $template->SetContent($content);
             }
         }
diff --git a/rt/lib/RT.pm b/rt/lib/RT.pm
index 0f0c79a..e71d6c9 100644
--- a/rt/lib/RT.pm
+++ b/rt/lib/RT.pm
@@ -707,7 +707,9 @@ sub InitPluginPaths {
     my @tmp_inc;
     my $added;
     for (@INC) {
-        if ( Cwd::realpath($_) eq $RT::LocalLibPath) {
+        my $realpath = Cwd::realpath($_);
+        next unless defined $realpath;
+        if ( $realpath eq $RT::LocalLibPath) {
             push @tmp_inc, $_, @lib_dirs;
             $added = 1;
         } else {
diff --git a/rt/lib/RT/Config.pm b/rt/lib/RT/Config.pm
index fee6c51..62aae1c 100644
--- a/rt/lib/RT/Config.pm
+++ b/rt/lib/RT/Config.pm
@@ -1219,11 +1219,14 @@ sub SetFromConfig {
             my $entry = ${$pack}{$k};
             next unless $entry;
 
-            # get entry for type we are looking for
-            # XXX skip references to scalars or other references.
-            # Otherwie 5.10 goes boom. maybe we should skip any
-            # reference
-            next if ref($entry) eq 'SCALAR' || ref($entry) eq 'REF';
+            # Inlined constants are simplified in the symbol table --
+            # namely, when possible, you only get a reference back in
+            # $entry, rather than a full GLOB.  In 5.10, scalar
+            # constants began being inlined this way; starting in 5.20,
+            # list constants are also inlined.  Notably, ref(GLOB) is
+            # undef, but inlined constants are currently either REF,
+            # SCALAR, or ARRAY.
+            next if ref($entry);
 
             my $ref_type = ref($ref);
 
diff --git a/rt/lib/RT/Date.pm b/rt/lib/RT/Date.pm
index db56cfe..52bdc01 100644
--- a/rt/lib/RT/Date.pm
+++ b/rt/lib/RT/Date.pm
@@ -509,7 +509,8 @@ Returns new unix time.
 
 sub AddDays {
     my $self = shift;
-    my $days = shift || 1;
+    my $days = shift;
+    $days = 1 unless defined $days;
     return $self->AddSeconds( $days * $DAY );
 }
 
diff --git a/rt/lib/RT/Generated.pm b/rt/lib/RT/Generated.pm
index 5edd7e3..eee892d 100644
--- a/rt/lib/RT/Generated.pm
+++ b/rt/lib/RT/Generated.pm
@@ -50,7 +50,7 @@ package RT;
 use warnings;
 use strict;
 
-our $VERSION = '4.0.19';
+our $VERSION = '4.0.20';
 
 
 
diff --git a/rt/lib/RT/Handle.pm b/rt/lib/RT/Handle.pm
index 4ea1576..e6ecdda 100644
--- a/rt/lib/RT/Handle.pm
+++ b/rt/lib/RT/Handle.pm
@@ -246,7 +246,7 @@ sub CheckIntegrity {
         return (0, 'no nobody user', "Couldn't find Nobody user in the DB '". $self->DSN ."'");
     }
 
-    return $RT::Handle->dbh;
+    return 1;
 }
 
 sub CheckCompatibility {
@@ -768,9 +768,9 @@ sub InsertData {
     );
 
     # Slurp in stuff to insert from the datafile. Possible things to go in here:-
-    our (@Groups, @Users, @ACL, @Queues, @ScripActions, @ScripConditions,
+    our (@Groups, @Users, @Members, @ACL, @Queues, @ScripActions, @ScripConditions,
            @Templates, @CustomFields, @Scrips, @Attributes, @Initial, @Final);
-    local (@Groups, @Users, @ACL, @Queues, @ScripActions, @ScripConditions,
+    local (@Groups, @Users, @Members, @ACL, @Queues, @ScripActions, @ScripConditions,
            @Templates, @CustomFields, @Scrips, @Attributes, @Initial, @Final);
 
     local $@;
@@ -790,7 +790,9 @@ sub InsertData {
         $RT::Logger->debug("Creating groups...");
         foreach my $item (@Groups) {
             my $new_entry = RT::Group->new( RT->SystemUser );
+            $item->{Domain} ||= 'UserDefined';
             my $member_of = delete $item->{'MemberOf'};
+            my $members = delete $item->{'Members'};
             my ( $return, $msg ) = $new_entry->_Create(%$item);
             unless ( $return ) {
                 $RT::Logger->error( $msg );
@@ -829,6 +831,12 @@ sub InsertData {
                     }
                 }
             }
+            push @Members, map { +{Group => $new_entry->id,
+                                   Class => "RT::User", Name => $_} }
+                @{ $members->{Users} || [] };
+            push @Members, map { +{Group => $new_entry->id,
+                                   Class => "RT::Group", Name => $_} }
+                @{ $members->{Groups} || [] };
         }
         $RT::Logger->debug("done.");
     }
@@ -848,6 +856,33 @@ sub InsertData {
         }
         $RT::Logger->debug("done.");
     }
+    if ( @Members ) {
+        $RT::Logger->debug("Adding users and groups to groups...");
+        for my $item (@Members) {
+            my $group = RT::Group->new(RT->SystemUser);
+            $group->LoadUserDefinedGroup( delete $item->{Group} );
+            unless ($group->Id) {
+                RT->Logger->error("Unable to find group '$group' to add members to");
+                next;
+            }
+
+            my $class = delete $item->{Class} || 'RT::User';
+            my $member = $class->new( RT->SystemUser );
+            $item->{Domain} = 'UserDefined' if $member->isa("RT::Group");
+            $member->LoadByCols( %$item );
+            unless ($member->Id) {
+                RT->Logger->error("Unable to find $class '".($item->{id} || $item->{Name})."' to add to ".$group->Name);
+                next;
+            }
+
+            my ( $return, $msg) = $group->AddMember( $member->PrincipalObj->Id );
+            unless ( $return ) {
+                $RT::Logger->error( $msg );
+            } else {
+                $RT::Logger->debug( $return ."." );
+            }
+        }
+    }
     if ( @Queues ) {
         $RT::Logger->debug("Creating queues...");
         for my $item (@Queues) {
diff --git a/rt/lib/RT/Interface/REST.pm b/rt/lib/RT/Interface/REST.pm
index 17fe446..06d7f83 100644
--- a/rt/lib/RT/Interface/REST.pm
+++ b/rt/lib/RT/Interface/REST.pm
@@ -328,7 +328,7 @@ sub process_attachments {
             Path => $tmp_fn,
             Type => $info->{'Content-Type'} || guess_media_type($tmp_fn),
             Filename => $file,
-            Disposition => "attachment",
+            Disposition => $info->{'Content-Disposition'} || "attachment",
         );
         $new_entity->bodyhandle->{'_dirty_hack_to_save_a_ref_tmp_fh'} = $tmp_fh;
         $i++;
diff --git a/rt/lib/RT/Interface/Web.pm b/rt/lib/RT/Interface/Web.pm
index 409cbdc..59d3154 100644
--- a/rt/lib/RT/Interface/Web.pm
+++ b/rt/lib/RT/Interface/Web.pm
@@ -962,7 +962,7 @@ not contain a slash-dot C</.>, and does not contain any nulls.
 sub ComponentPathIsSafe {
     my $self = shift;
     my $path = shift;
-    return $path !~ m{(?:^|/)\.} and $path !~ m{\0};
+    return($path !~ m{(?:^|/)\.} and $path !~ m{\0});
 }
 
 =head2 PathIsSafe
diff --git a/rt/lib/RT/Interface/Web/Handler.pm b/rt/lib/RT/Interface/Web/Handler.pm
index 37031b1..07e7707 100644
--- a/rt/lib/RT/Interface/Web/Handler.pm
+++ b/rt/lib/RT/Interface/Web/Handler.pm
@@ -278,7 +278,7 @@ sub PSGIApp {
         # CGI.pm normalizes .. out of paths so when you requested
         # /NoAuth/../Ticket/Display.html we saw Ticket/Display.html
         # PSGI doesn't normalize .. so we have to deal ourselves.
-        if ( $req->path_info =~ m{/\.} ) {
+        if ( $req->path_info =~ m{(^|/)\.\.?(/|$)} ) {
             $RT::Logger->crit("Invalid request for ".$req->path_info." aborting");
             my $res = Plack::Response->new(400);
             return $self->_psgi_response_cb($res->finalize,sub { $self->CleanupRequest });
diff --git a/rt/lib/RT/Lifecycle.pm b/rt/lib/RT/Lifecycle.pm
index accef22..bdb2ba6 100644
--- a/rt/lib/RT/Lifecycle.pm
+++ b/rt/lib/RT/Lifecycle.pm
@@ -298,7 +298,7 @@ sub IsActive {
     return 0;
 }
 
-=head3 inactive
+=head3 Inactive
 
 Returns an array of all inactive statuses for this lifecycle.
 
@@ -309,7 +309,7 @@ sub Inactive {
     return $self->Valid('inactive');
 }
 
-=head3 is_inactive
+=head3 IsInactive
 
 Takes a value and returns true if value is a valid inactive status.
 Otherwise, returns false.
diff --git a/rt/lib/RT/Shredder/Plugin/SQLDump.pm b/rt/lib/RT/Shredder/Plugin/SQLDump.pm
index cc0d4cc..d12cf0b 100644
--- a/rt/lib/RT/Shredder/Plugin/SQLDump.pm
+++ b/rt/lib/RT/Shredder/Plugin/SQLDump.pm
@@ -89,8 +89,8 @@ sub Run
     my $query = $args{'Object'}->_AsInsertQuery;
     $query .= "\n" unless $query =~ /\n$/;
 
-    return print $fh $query or return (0, "Couldn't write to filehandle");
-    return 1;
+    return 1 if print $fh $query;
+    return (0, "Couldn't write to filehandle");
 }
 
 1;
diff --git a/rt/lib/RT/StyleGuide.pod b/rt/lib/RT/StyleGuide.pod
index d958c87..8fdfc7b 100644
--- a/rt/lib/RT/StyleGuide.pod
+++ b/rt/lib/RT/StyleGuide.pod
@@ -2,6 +2,10 @@
 
 RT::StyleGuide - RT Style Guide
 
+=head1 CAVEATS
+
+This file is somewhat out of date; L<hacking> takes precedence over it.
+
 =head1 INTRODUCTION
 
 All code and documentation that is submitted to be included in the RT
@@ -92,21 +96,9 @@ versions.  Examples:
 	1.1.0		First development release of RT 1.2 (or 2.0)
 	2.0.0		First release of RT 2
 
-Versions can be modified with a hyphen followed by some text, for
-special versions, or to give extra information.  Examples:
-
-	2.0.0-pre1	Notes that this is not final, but preview
-
-In perl 5.6.0, you can have versions like C<v2.0.0>, but this is not
-allowed in previous versions of perl.  So to convert a tuple version
-string to a string to use with $VERSION, use a regular integer for
-the revision, and three digits for version and subversion.  Examples:
+Versions may end in "rc" and a number if they are release candidates:
 
-	1.1.6	->	1.001006
-	2.0.0	->	2.000000
-
-This way, perl can use the version strings in greater-than and
-less-than comparisons.
+	2.0.0rc1	First release candiate for real 2.0.0
 
 
 =head2 Comments
@@ -152,14 +144,6 @@ local() may also be used on elements of arrays and hashes, though there
 is seldom a need to do it, and you shouldn't.
 
 
-=head2 Exporting
-
-Do not export anything from a module by default.  Feel free to put
-anything you want to in @EXPORT_OK, so users of your modules can
-explicitly ask for symbols (e.g., "use Something::Something qw(getFoo
-setFoo)"), but do not export them by default.
-
-
 =head2 Pass by Reference
 
 Arrays and hashes should be passed to and from functions by reference
@@ -185,58 +169,6 @@ Although, usually, this is better (faster, easier to read, etc.):
 We need to talk about Class::ReturnValue here.
 
 
-=head2 Garbage Collection
-
-Perl does pretty good garbage collection for you.  It will automatically
-clean up lexical variables that have gone out of scope and objects whose
-references have gone away.  Normally you don't need to worry about
-cleaning up after yourself, if using lexicals.
-
-However, some glue code, code compiled in C and linked to Perl, might
-not automatically clean up for you.  In such cases, clean up for
-yourself.  If there is a method in that glue to dispose or destruct,
-then use it as appropriate.
-
-Also, if you have a long-running function that has a large data
-structure in it, it is polite to free up the memory as soon as you are
-done with it, if possible.
-
-	my $huge_data_structure = get_huge_data_structure();
-	do_something_with($huge_data_structure);
-	undef $huge_data_structure;
-
-=head2 DESTROY
-
-All object classes must provide a DESTROY method.  If it won't do
-anything, provide it anyway:
-
-	sub DESTROY { }
-
-
-
-=head2 die() and exit()
-
-Don't do it.  Do not die() or exit() from a web template or module.  Do
-not call C<kill 9, $$>.  Don't do it.
-
-In command-line programs, do as you please.
-
-
-=head2 shift and @_
-
-Do not use @_.  Use shift.  shift may take more lines, but Jesse thinks it 
-leads to cleaner code.
-
-	my $var = shift;			# right
-	my($var) = @_;				# ick. no
-	sub foo { uc $_[0] }			# icky. sometimes ok.
-
-
-	my($var1, $var2) = (shift, shift);	# Um, no.
-
-        my $var1 = shift;                       # right
-        my $var2 = shift;                       
-
 =head2 Method parameters
 
 If a method takes exactly one mandatory argument, the argument should be
@@ -249,15 +181,17 @@ In all other cases, the method needs to take named parameters, usually
 using a C<%args> hash to store them:
 
         my $self = shift;
-        my %args = ( Name => undef,
-                     Description => undef,
-                     @_ );
+        my %args = (
+            Name => undef,
+            Description => undef,
+            @_
+        );
 
 You may specify defaults to those named parameters instead of using
 C<undef> above, as long as it is documented as such.
 
 It is worth noting that the existing RT codebase had not followed this
-style perfectly; we are trying to fix it without breaking exsiting APIs.
+style perfectly; we are trying to fix it without breaking existing APIs.
 
 =head2 Tests
 
@@ -332,17 +266,6 @@ document, too.
 
 =over 4
 
-=item RT the name
-
-"RT" is the name of the project.  "RT" is, optionally, the
-specific name for the actual file distribution.  That's it. 
-
-While we sometimes use "RT2" or "RT3", that's shortand that's really
-not recommended. The name of the project is "RT".
-
-To specify a major version, use "RT 3.0".
-To specify a specific release, use "RT 3.0.12"
-
 =item function vs. sub(routine) vs. method
 
 Just because it is the Perl Way (not necessarily right for all
@@ -435,9 +358,9 @@ clear what is going on, or when it is required (such as with
 map() and grep()).
 
 	for (@list) {
-		print;			# OK; everyone knows this one
-		print uc;		# wrong; few people know this
-		print uc $_;		# better
+	    print;			# OK; everyone knows this one
+	    print uc;			# wrong; few people know this
+	    print uc $_;		# better
 	}
 
 Note that the special variable C<_> I<should> be used when possible.
@@ -448,9 +371,9 @@ C<_> for subsequent uses, is a performance hit.  You should be
 careful that the last-tested file is what you think it is, though.
 
 	if (-d $file) {		# $file is a directory
-		# ...
+	    # ...
 	} elsif (-l _) {	# $file is a symlink
-		# ...
+	    # ...
 	}
 
 Package names begin with a capital letter in each word, followed by
@@ -461,20 +384,16 @@ lower case letters (for the most part).  Multiple words should be StudlyCapped.
 	RT::Display::Provider	        # good
 	RT::CustomField			# not so good, but OK
 
-Plugin modules should begin with "RTx::", followed by the name
+Plugin modules should begin with "RT::Extension::", followed by the name
 of the plugin.  
 
 =head1 Code formatting
 
-Use perltidy. Anything we say here is wrong if it conflicts with what
-perltidy does. Your perltidyrc should read:
-
--lp -vt=2 -vtc=2 -nsfs -bar                                                                                             
+When in doubt, use perltidy; RT includes a F<.perltidyrc>.
 
 =head2 Indents and Blank Space
 
-All indents should be tabs.  Set your tab stops whatever you want them
-to be; I use 8 spaces per tabs.
+All indents should be four spaces; hard tabs are forbidden.
 
 No space before a semicolon that closes a statement.
 
@@ -507,15 +426,14 @@ An example:
 
 	# this is my function!
 	sub foo {
-                my $val = shift;
-		my $obj = new Constructor;
-		my($var1, $var2);
-
-		$obj->SetFoo($val);
-		$var1 = $obj->Foo();
+	    my $val = shift;
+	    my $obj = new Constructor;
+	    my($var1, $var2);
 
+	    $obj->SetFoo($val);
+	    $var1 = $obj->Foo();
 
-		return($val);
+	    return($val);
 	}
 
 	print 1;
@@ -555,14 +473,13 @@ the opening statement, or the opening parenthesis, whichever works best.
 Examples:
 
 	@list = qw(
-		bar
-		baz
+	    bar
+	    baz
 	);			# right
 
 	if ($foo && $bar && $baz
-		 && $buz && $xyzzy
-	) {
-		print $foo;
+	         && $buz && $xyzzy) {
+	    print $foo;
 	}
 
 Whether or not there is space following a closing parenthesis is
@@ -620,26 +537,16 @@ opening curly on the first line, and the ending curly lined up with the
 keyword at the end.
 
 	for (@list) {
-		print;
-		smell();
+	    print;
+	    smell();
 	}
 
-Generally, we prefer "uncuddled elses":
+Generally, we prefer "cuddled elses":
 
 	if ($foo) {
-		print;
-	}
-	else {
-		die;
-	}
-
-_If_ the if statement is very brief, sometimes "cuddling" the else makes code more readable. Feel free to cuddle them in that case:
-
-
-	if ($foo) {
-		print;
+	    print;
 	} else {
-		die;
+	    die;
 	}
 
 =head2 Operators
@@ -678,21 +585,21 @@ normally, you should, if there is any question at all -- then it doesn't
 matter which you use.  Use whichever is most readable and aesthetically
 pleasing to you at the time, and be consistent within your block of code.
 
-Break long lines AFTER operators, except for "and", "or", "&&", "||".
+Break long lines AFTER operators, except for ".", "and", "or", "&&", "||".
 Try to keep the two parts to a binary operator (an operator that
 has two operands) together when possible.
 
-	print "foo" . "bar" . "baz"
-		. "buz";			# wrong
-
 	print "foo" . "bar" . "baz" .
-		"buz";				# right
+	      "buz";				# wrong
+
+	print "foo" . "bar" . "baz"
+	    . "buz";				# right
 
 	print $foo unless $x == 3 && $y ==
-		4 && $z == 5;			# wrong
+	        4 && $z == 5;			# wrong
 
 	print $foo unless $x == 3 && $y == 4
-		&& $z == 5;			# right
+	               && $z == 5;		# right
 
 
 =head2 Other
@@ -722,7 +629,7 @@ When making compound statements, put the primary action first.
 
 Use here-docs instead of repeated print statements.
 
-		print <<EOT;
+	        print <<EOT;
 	This is a whole bunch of text.
 	I like it.  I don't need to worry about messing
 	with lots of print statements and lining them up.
@@ -754,7 +661,7 @@ grep the codebase for strings to be localized
 The string 		Foo
 			Bar
 			Baz
-			
+
 Should become		<&|/l&>Foo Bar Baz</&>
 
 
@@ -788,9 +695,8 @@ should become 	<& /Elements/TitleBoxStart,
 			titleright => loc("RT [_1] for [_2]",$RT::VERSION, RT->Config->Get('rtname')),
 			title => loc('Login'),
 	      	&>
-	
 
-=item Library code			
+=item Library code
 
 
 
@@ -855,19 +761,21 @@ guide contained in this document.
 =item Finish it up
 
 After the code is done (possibly going through multiple code reviews),
-if you do not have repository access, submit it to rt-<major-version>-bugs at fsck.com as a unified diff. From that point on, it'll be handled by someone with repository access.
+if you do not have repository access, submit it to rt-bugs at fsck.com as a
+unified diff. From that point on, it'll be handled by someone with
+repository access.
 
 =back
 
 
 =head1 BUG REPORTS, PATCHES
 
-Use rt-<major-version>-bugs at fsck.com for I<any> bug that is not
-being fixed immediately.  If it is not in RT, there
-is a good chance it will not be dealt with.
+Use rt-bugs at bestpractical.com for I<any> bug that is not being fixed
+immediately.  If it is not in RT, there is a good chance it will not be
+dealt with.
 
-Send patches to rt-<major-version>-bugs at fsck.com, too.  Use C<diff
--u> for patches.
+Send patches to rt-bugs at bestpractical.com, too.  Use C<diff -u> for
+patches.
 
 =head1 SCHEMA DESIGN
 
@@ -919,12 +827,3 @@ Talk about mason
 Talk about adding a new translation
 
 Talk more about logging
-
-=head1 CHANGES
-
-        Adapted from Slash Styleguide by jesse - 20 Dec, 2002
-
-
-=head1 VERSION
-
-0.1 
diff --git a/rt/lib/RT/Template.pm b/rt/lib/RT/Template.pm
index d15c1cd..0507997 100755
--- a/rt/lib/RT/Template.pm
+++ b/rt/lib/RT/Template.pm
@@ -470,6 +470,12 @@ sub _ParseContentPerl {
         TYPE   => 'STRING',
         SOURCE => $args{Content},
     );
+    my ($ok) = $template->compile;
+    unless ($ok) {
+        $RT::Logger->error("Template parsing error in @{[$self->Name]} (#@{[$self->id]}): $Text::Template::ERROR");
+        return ( undef, $self->loc('Template parsing error: [_1]', $Text::Template::ERROR) );
+    }
+
     my $is_broken = 0;
     my $retval = $template->fill_in(
         HASH => $args{TemplateArgs},
diff --git a/rt/lib/RT/Test.pm b/rt/lib/RT/Test.pm
index 2a1f52b..b15c03d 100644
--- a/rt/lib/RT/Test.pm
+++ b/rt/lib/RT/Test.pm
@@ -709,6 +709,39 @@ sub load_or_create_user {
     return $obj;
 }
 
+
+sub load_or_create_group {
+    my $self = shift;
+    my $name = shift;
+    my %args = (@_);
+
+    my $group = RT::Group->new( RT->SystemUser );
+    $group->LoadUserDefinedGroup( $name );
+    unless ( $group->id ) {
+        my ($id, $msg) = $group->CreateUserDefinedGroup(
+            Name => $name,
+        );
+        die "$msg" unless $id;
+    }
+
+    if ( $args{Members} ) {
+        my $cur = $group->MembersObj;
+        while ( my $entry = $cur->Next ) {
+            my ($status, $msg) = $entry->Delete;
+            die "$msg" unless $status;
+        }
+
+        foreach my $new ( @{ $args{Members} } ) {
+            my ($status, $msg) = $group->AddMember(
+                ref($new)? $new->id : $new,
+            );
+            die "$msg" unless $status;
+        }
+    }
+
+    return $group;
+}
+
 =head2 load_or_create_queue
 
 =cut
@@ -997,6 +1030,43 @@ sub run_mailgate {
     $self->run_and_capture(%args);
 }
 
+sub run_validator {
+    my $self = shift;
+    my %args = (check => 1, resolve => 0, force => 1, timeout => 0, @_ );
+
+    my $validator_path = "$RT::SbinPath/rt-validator";
+
+    my $cmd = $validator_path;
+    die "Couldn't find $cmd command" unless -f $cmd;
+
+    my $timeout = delete $args{timeout};
+
+    while( my ($k,$v) = each %args ) {
+        next unless $v;
+        $cmd .= " --$k '$v'";
+    }
+    $cmd .= ' 2>&1';
+
+    require IPC::Open2;
+    my ($child_out, $child_in);
+    my $pid = IPC::Open2::open2($child_out, $child_in, $cmd);
+    close $child_in;
+
+    local $SIG{ALRM} = sub { kill KILL => $pid; die "Timeout!" };
+
+    alarm $timeout if $timeout;
+    my $result = eval { local $/; <$child_out> };
+    warn $@ if $@;
+    close $child_out;
+    waitpid $pid, 0;
+    alarm 0;
+
+    DBIx::SearchBuilder::Record::Cachable->FlushCache
+        if $args{'resolve'};
+
+    return ($?, $result);
+}
+
 sub run_and_capture {
     my $self = shift;
     my %args = @_;
diff --git a/rt/lib/RT/User.pm b/rt/lib/RT/User.pm
index 018ac8a..af4a6ad 100755
--- a/rt/lib/RT/User.pm
+++ b/rt/lib/RT/User.pm
@@ -957,7 +957,7 @@ sub IsPassword {
         my $hash = MIME::Base64::decode_base64($stored);
         # Decoding yields 30 byes; first 4 are the salt, the rest are substr(SHA256,0,26)
         my $salt = substr($hash, 0, 4, "");
-        return 0 unless substr(Digest::SHA::sha256($salt . Digest::MD5::md5($value)), 0, 26) eq $hash;
+        return 0 unless substr(Digest::SHA::sha256($salt . Digest::MD5::md5(encode_utf8($value))), 0, 26) eq $hash;
     } elsif (length $stored == 32) {
         # Hex nonsalted-md5
         return 0 unless Digest::MD5::md5_hex(encode_utf8($value)) eq $stored;
@@ -1390,6 +1390,28 @@ sub SetPreferences {
     }
 }
 
+=head2 DeletePreferences NAME/OBJ VALUE
+
+Delete user preferences associated with given object or name.
+
+=cut
+
+sub DeletePreferences {
+    my $self = shift;
+    my $name = _PrefName( shift );
+
+    return (0, $self->loc("No permission to set preferences"))
+        unless $self->CurrentUserCanModify('Preferences');
+
+    my $attr = RT::Attribute->new( $self->CurrentUser );
+    $attr->LoadByNameAndObject( Object => $self, Name => $name );
+    if ( $attr->Id ) {
+        return $attr->Delete;
+    }
+
+    return (0, $self->loc("Preferences were not found"));
+}
+
 =head2 Stylesheet
 
 Returns a list of valid stylesheets take from preferences.
diff --git a/rt/lib/RT/Users.pm b/rt/lib/RT/Users.pm
index 1c75f42..f377d47 100755
--- a/rt/lib/RT/Users.pm
+++ b/rt/lib/RT/Users.pm
@@ -543,21 +543,31 @@ sub WhoHaveGroupRight
 }
 
 
-=head2 WhoBelongToGroups { Groups => ARRAYREF, IncludeSubgroupMembers => 1 }
+=head2 WhoBelongToGroups { Groups => ARRAYREF, IncludeSubgroupMembers => 1, IncludeUnprivileged => 0 }
+
+Return members who belong to any of the groups passed in the groups whose IDs
+are included in the Groups arrayref.
+
+If IncludeSubgroupMembers is true (default) then members of any group that's a
+member of one of the passed groups are returned. If it's cleared then only
+direct member users are returned.
+
+If IncludeUnprivileged is false (default) then only privileged members are
+returned; otherwise either privileged or unprivileged group members may be
+returned.
 
 =cut
 
-# XXX: should be generalized
 sub WhoBelongToGroups {
     my $self = shift;
     my %args = ( Groups                 => undef,
                  IncludeSubgroupMembers => 1,
+                 IncludeUnprivileged    => 0,
                  @_ );
 
-    # Unprivileged users can't be granted real system rights.
-    # is this really the right thing to be saying?
-    $self->LimitToPrivileged();
-
+    if (!$args{'IncludeUnprivileged'}) {
+        $self->LimitToPrivileged();
+    }
     my $group_members = $self->_JoinGroupMembers( %args );
 
     foreach my $groupid (@{$args{'Groups'}}) {
diff --git a/rt/sbin/rt-server.fcgi.in b/rt/sbin/rt-server.fcgi.in
index 0d11f01..5bd8f3e 100644
--- a/rt/sbin/rt-server.fcgi.in
+++ b/rt/sbin/rt-server.fcgi.in
@@ -138,6 +138,7 @@ EOF
 
 # we must disconnect DB before fork
 if ($RT::Handle) {
+    $RT::Handle->dbh->disconnect if $RT::Handle->dbh;
     $RT::Handle->dbh(undef);
     undef $RT::Handle;
 }
diff --git a/rt/sbin/rt-server.in b/rt/sbin/rt-server.in
index 0d11f01..5bd8f3e 100644
--- a/rt/sbin/rt-server.in
+++ b/rt/sbin/rt-server.in
@@ -138,6 +138,7 @@ EOF
 
 # we must disconnect DB before fork
 if ($RT::Handle) {
+    $RT::Handle->dbh->disconnect if $RT::Handle->dbh;
     $RT::Handle->dbh(undef);
     undef $RT::Handle;
 }
diff --git a/rt/sbin/rt-setup-fulltext-index.in b/rt/sbin/rt-setup-fulltext-index.in
index ade728f..7a1ede8 100644
--- a/rt/sbin/rt-setup-fulltext-index.in
+++ b/rt/sbin/rt-setup-fulltext-index.in
@@ -146,10 +146,16 @@ if ( $DB{'type'} eq 'mysql' ) {
         default => $DEFAULT{'table'},
         silent  => !$OPT{'ask'},
     );
-    my $url = $OPT{'url'} || prompt(
+
+    my $url = 'sphinx://localhost:3312/rt';
+    my $version = ($dbh->selectrow_array("show variables like 'version'"))[1];
+    $url = 'sphinx://127.0.0.1:3312/rt'
+        if $version and $version =~ /^(\d+\.\d+)/ and $1 >= 5.5;
+
+    $url = $OPT{'url'} || prompt(
         message => "Enter URL of the sphinx search server; this should be of the form\n"
                  . "sphinx://<server>:<port>/<index name>",
-        default => 'sphinx://localhost:3312/rt',
+        default => $url,
         silent  => !$OPT{'ask'},
     );
     my $maxmatches = $OPT{'maxmatches'} || prompt(
diff --git a/rt/sbin/rt-test-dependencies.in b/rt/sbin/rt-test-dependencies.in
index 8681054..66215ad 100644
--- a/rt/sbin/rt-test-dependencies.in
+++ b/rt/sbin/rt-test-dependencies.in
@@ -55,9 +55,13 @@ use strict;
 use warnings;
 no warnings qw(numeric redefine);
 use Getopt::Long;
+use Cwd qw(abs_path);
 my %args;
 my %deps;
 my @orig_argv = @ARGV;
+# Save our path because installers or tests can change cwd
+my $script_path = abs_path($0);
+
 GetOptions(
     \%args,                               'v|verbose',
     'install!',                           'with-MYSQL',
@@ -417,7 +421,7 @@ foreach my $type (sort grep $args{$_}, keys %args) {
 }
 
 if ( $args{'install'} && keys %Missing_By_Type ) {
-    exec($0, @orig_argv, '--no-install');
+    exec($script_path, @orig_argv, '--no-install');
 }
 else {
     conclude(%Missing_By_Type);
diff --git a/rt/sbin/rt-validator.in b/rt/sbin/rt-validator.in
index 128e60a..f0f1c59 100644
--- a/rt/sbin/rt-validator.in
+++ b/rt/sbin/rt-validator.in
@@ -222,7 +222,7 @@ foreach my $table ( qw(Users Groups) ) {
             bind_values => [ $type ],
             action => sub {
                 my $id = shift;
-                return unless my $a = prompt_action( ['Delete', 'create'], $msg );
+                return unless my $a = prompt_action( ['Create', 'delete'], $msg );
 
                 if ( $a eq 'd' ) {
                     delete_record( $table, $id );
@@ -1104,7 +1104,7 @@ sub prompt_action {
     my $token = shift || join ':', caller;
 
     return '' unless $opt{'resolve'};
-    return '' if $opt{'force'};
+    return lc substr $actions->[0], 0, 1 if $opt{'force'};
     return $cached_answer{ $token } if exists $cached_answer{ $token };
 
     print $msg, "\n";
diff --git a/rt/sbin/standalone_httpd.in b/rt/sbin/standalone_httpd.in
index 0d11f01..5bd8f3e 100644
--- a/rt/sbin/standalone_httpd.in
+++ b/rt/sbin/standalone_httpd.in
@@ -138,6 +138,7 @@ EOF
 
 # we must disconnect DB before fork
 if ($RT::Handle) {
+    $RT::Handle->dbh->disconnect if $RT::Handle->dbh;
     $RT::Handle->dbh(undef);
     undef $RT::Handle;
 }
diff --git a/rt/share/html/Dashboards/Queries.html b/rt/share/html/Dashboards/Queries.html
index 61f6195..c489f1c 100644
--- a/rt/share/html/Dashboards/Queries.html
+++ b/rt/share/html/Dashboards/Queries.html
@@ -190,7 +190,7 @@ $m->callback(
 );
 
 my @panes;
-for my $pane (keys %pane_name) {
+for my $pane (sort keys %pane_name) {
     my $sel = $m->comp(
         '/Widgets/SelectionBox:new',
         Action      => 'Queries.html',
diff --git a/rt/share/html/Elements/CollectionAsTable/ParseFormat b/rt/share/html/Elements/CollectionAsTable/ParseFormat
index e56e9ce..5d55ffb 100644
--- a/rt/share/html/Elements/CollectionAsTable/ParseFormat
+++ b/rt/share/html/Elements/CollectionAsTable/ParseFormat
@@ -55,6 +55,7 @@ my @Columns;
 
 while ($Format =~ /($RE{delimited}{-delim=>qq{\'"}}|[{}\w.]+)/go) {
     my $col = $1;
+    my $colref = { original_string => $col };
 
     if ($col =~ /^$RE{quoted}$/o) {
         substr($col,0,1) = "";
@@ -62,8 +63,6 @@ while ($Format =~ /($RE{delimited}{-delim=>qq{\'"}}|[{}\w.]+)/go) {
         $col =~ s/\\(.)/$1/g;
     }
 
-    my $colref = { };
-
     $m->callback(CallbackName => 'PreColumn', Column => $colref, col => \$col);
 
     while ( $col =~ s{/(STYLE|CLASS|TITLE|ALIGN|SPAN|ATTRIBUTE):([^/]*)}{}i ) {
diff --git a/rt/share/html/Elements/EditCustomFieldAutocomplete b/rt/share/html/Elements/EditCustomFieldAutocomplete
index 7bfe911..8eb7b42 100644
--- a/rt/share/html/Elements/EditCustomFieldAutocomplete
+++ b/rt/share/html/Elements/EditCustomFieldAutocomplete
@@ -46,7 +46,14 @@
 %#
 %# END BPS TAGGED BLOCK }}}
 % if ( $Multiple ) {
-<textarea cols="<% $Cols %>" rows="<% $Rows %>" name="<% $name %>-Values" id="<% $name %>-Values" class="CF-<%$CustomField->id%>-Edit"><% $Default || '' %></textarea>
+<textarea \
+% if ( defined $Cols ) {
+cols="<% $Cols %>" \
+% }
+% if ( defined $Rows ) {
+rows="<% $Rows %>" \
+% }
+name="<% $name %>-Values" id="<% $name %>-Values" class="CF-<%$CustomField->id%>-Edit"><% $Default || '' %></textarea>
 
 <script type="text/javascript">
 var id = <% "$name-Values" |n,j%>;
diff --git a/rt/share/html/Elements/EditCustomFieldDateTime b/rt/share/html/Elements/EditCustomFieldDateTime
index 25d1ce1..edf125e 100644
--- a/rt/share/html/Elements/EditCustomFieldDateTime
+++ b/rt/share/html/Elements/EditCustomFieldDateTime
@@ -50,7 +50,7 @@
 
 <%INIT>
 my $DateObj = RT::Date->new ( $session{'CurrentUser'} );
-$DateObj->Set( Format => 'ISO', Value => $Default );
+$DateObj->Set( Format => $Format, Value => $Default );
 </%INIT>
 <%ARGS>
 $Object => undef
@@ -59,4 +59,5 @@ $NamePrefix => undef
 $Default => undef
 $Values => undef
 $MaxValues => 1
+$Format => 'ISO'
 </%ARGS>
diff --git a/rt/share/html/Elements/EditCustomFieldFreeform b/rt/share/html/Elements/EditCustomFieldFreeform
index 6724873..f0f8ee6 100644
--- a/rt/share/html/Elements/EditCustomFieldFreeform
+++ b/rt/share/html/Elements/EditCustomFieldFreeform
@@ -47,9 +47,20 @@
 %# END BPS TAGGED BLOCK }}}
 % my $name = $NamePrefix . $CustomField->Id . '-Value';
 % if ($Multiple) {
-<textarea cols="<%$Cols%>" rows="<%$Rows%>" name="<%$name%>s" id="<%$name%>s" wrap="off" class="CF-<%$CustomField->id%>-Edit"><% defined($Default) ? $Default : '' %></textarea>
+<textarea \
+% if ( defined $Cols ) {
+cols="<% $Cols %>" \
+% }
+% if ( defined $Rows ) {
+rows="<% $Rows %>" \
+% }
+name="<%$name%>s" id="<%$name%>s" wrap="off" class="CF-<%$CustomField->id%>-Edit"><% defined($Default) ? $Default : '' %></textarea>
 % } else {
-<input name="<%$name%>" id="<%$name%>" size="<%$Cols%>" class="CF-<%$CustomField->id%>-Edit" value="<% defined($Default) ? $Default : ''%>" />
+<input type="text" name="<%$name%>" id="<%$name%>" \
+% if ( defined $Cols ) {
+size="<% $Cols %>" \
+% }
+class="CF-<%$CustomField->id%>-Edit" value="<% defined($Default) ? $Default : ''%>" />
 % }
 <%INIT>
 if ( $Multiple and $Values ) {
diff --git a/rt/share/html/Elements/EditCustomFieldSelect b/rt/share/html/Elements/EditCustomFieldSelect
index 87400ab..b343d82 100644
--- a/rt/share/html/Elements/EditCustomFieldSelect
+++ b/rt/share/html/Elements/EditCustomFieldSelect
@@ -104,7 +104,7 @@ jQuery(  function () {
 
 % if ( $RenderType eq 'List' ) {
 <fieldset class="cfedit">
-<div name="<%$id%>-Values" id="<%$id%>-Values">
+<div data-name="<%$id%>-Values" id="<%$id%>-Values">
 %   if ( $checktype eq 'radio' ) {
   <div class="none">
   <input class="none" type="<% $checktype %>" name="<% $name %>" id="<% $name %>-none" value="" <% keys %default ? '' : ' checked="checked"' |n%> />
@@ -115,7 +115,7 @@ jQuery(  function () {
 %   while ( my $value = $CFVs->Next ) {
 %     my $content = $value->Name;
 %     my $labelid = "$name-". $value->id;
-<div name="<% $value->Category %>">
+<div data-name="<% $value->Category || '' %>">
   <input type="<% $checktype %>" name="<% $name %>" id="<% $labelid %>" value="<% $content %>" <% $default{ lc $content }? ' checked="checked"' : '' |n%> />
   <label for="<% $labelid %>"><% $content %></label><br />
 </div>
diff --git a/rt/share/html/Elements/EditCustomFieldText b/rt/share/html/Elements/EditCustomFieldText
index 8664604..ca7a266 100644
--- a/rt/share/html/Elements/EditCustomFieldText
+++ b/rt/share/html/Elements/EditCustomFieldText
@@ -46,10 +46,24 @@
 %#
 %# END BPS TAGGED BLOCK }}}
 % while ($Values and my $value = $Values->Next ) {
-<textarea cols="<%$Cols%>" rows="<%$Rows%>" name="<%$NamePrefix%><%$CustomField->Id%>-Values" class="CF-<%$CustomField->id%>-Edit"><% $value->Content %></textarea><br />
+<textarea \
+% if ( defined $Cols ) {
+cols="<% $Cols %>" \
+% }
+% if ( defined $Rows ) {
+rows="<% $Rows %>" \
+% }
+name="<%$NamePrefix%><%$CustomField->Id%>-Values" class="CF-<%$CustomField->id%>-Edit"><% $value->Content %></textarea><br />
 % }
 % if (!$MaxValues or !$Values or $Values->Count < $MaxValues) {
-<textarea cols="<%$Cols%>" rows="<%$Rows%>" name="<%$NamePrefix%><%$CustomField->Id%>-Values" class="CF-<%$CustomField->id%>-Edit"><% defined($Default) ? $Default : '' %></textarea>
+<textarea \
+% if ( defined $Cols ) {
+cols="<% $Cols %>" \
+% }
+% if ( defined $Rows ) {
+rows="<% $Rows %>" \
+% }
+name="<%$NamePrefix%><%$CustomField->Id%>-Values" class="CF-<%$CustomField->id%>-Edit"><% defined($Default) ? $Default : '' %></textarea>
 % }
 <%INIT>
 # XXX - MultiValue textarea is for now outlawed.
diff --git a/rt/share/html/Elements/EditCustomFieldWikitext b/rt/share/html/Elements/EditCustomFieldWikitext
index 1a36ae3..d4b79cd 100644
--- a/rt/share/html/Elements/EditCustomFieldWikitext
+++ b/rt/share/html/Elements/EditCustomFieldWikitext
@@ -46,10 +46,24 @@
 %#
 %# END BPS TAGGED BLOCK }}}
 % while ($Values and my $value = $Values->Next ) {
-<textarea cols="<%$Cols%>" rows="<%$Rows%>" name="<%$NamePrefix%><%$CustomField->Id%>-Values" class="CF-<%$CustomField->id%>-Edit"><% $value->Content %></textarea><br />
+<textarea \
+% if ( defined $Cols ) {
+cols="<% $Cols %>" \
+% }
+% if ( defined $Rows ) {
+rows="<% $Rows %>" \
+% }
+name="<%$NamePrefix%><%$CustomField->Id%>-Values" class="CF-<%$CustomField->id%>-Edit"><% $value->Content %></textarea><br />
 % }
 % if (!$MaxValues or !$Values or $Values->Count < $MaxValues) {
-<textarea cols="<%$Cols%>" rows="<%$Rows%>" name="<%$NamePrefix%><%$CustomField->Id%>-Values" class="CF-<%$CustomField->id%>-Edit"><% $Default %></textarea>
+<textarea \
+% if ( defined $Cols ) {
+cols="<% $Cols %>" \
+% }
+% if ( defined $Rows ) {
+rows="<% $Rows %>" \
+% }
+name="<%$NamePrefix%><%$CustomField->Id%>-Values" class="CF-<%$CustomField->id%>-Edit"><% $Default %></textarea>
 % }
 <%INIT>
 # XXX - MultiValue textarea is for now outlawed.
diff --git a/rt/share/html/Elements/MakeClicky b/rt/share/html/Elements/MakeClicky
index e22e75f..8efe78c 100644
--- a/rt/share/html/Elements/MakeClicky
+++ b/rt/share/html/Elements/MakeClicky
@@ -96,6 +96,8 @@ my $handle = sub {
     }
 };
 
+my $cache; # only defined via callback
+
 # Hook to add more Clicky types
 # XXX Have to have Page argument, as Mason gets caller wrong in Callback?
 # This happens as we are in <%ONCE> block
@@ -104,6 +106,7 @@ $m->callback(
     types        => \@types,
     actions      => \%actions,
     handle       => \$handle,
+    cache        => \$cache,
 );
 
 
@@ -131,6 +134,15 @@ $html => undef
 </%ARGS>
 <%INIT>
 return unless defined $$content;
+if ( defined $cache ) {
+    my $cached_content = $cache->(fetch => $content);
+    if ( $cached_content ) {
+        RT->Logger->debug("Found MakeClicky cache");
+        $$content = $cached_content;
+        return;
+    }
+}
+
 unless ( $regexp ) {
     RT::Interface::Web::EscapeUTF8( $content ) unless $html;
     return;
@@ -165,40 +177,6 @@ substr( $$content, $pos ) = $escaper->( substr( $$content, $pos ) ) unless
 ($pos == length $$content) || $html;
 
 pos($$content) = 0;
+$cache->(store => $content) if defined $cache;
 
 </%INIT>
-<%doc>
-
-MakeClicky detects various formats of data in headers and email
-messages, and extends them with supporting links.  By default, RT
-provides two formats:
-
- * 'httpurl': detects http:// and https:// URLs and adds '[Open URL]'
-   link after the URL.
-
- * 'httpurl_overwrite': also detects URLs as 'httpurl' format, but
-   replace URL with link.
-
-To extend this with your own types of data, use the callback.
-It will be provided with:
-
- * 'types': An array reference of hash references.  Modify this array
-    reference to add your own types; the first matching type will be
-    used.  Each hashref should contain:
-   - 'name': The name of the data format; this is used in the
-      configuration file to enable the format.
-   - 'regex': A regular expression to match against
-   - 'action': The name of the action to run (see "actions", below)
-
- * 'actions': A hash reference of 'actions'.  Modify this hash
-    reference to change or add action types.  Values are subroutine
-    references which will get called when needed.  They should return
-    the modified string. Note that subroutine must escape HTML.
-
- * 'handler': A reference to a subroutine reference; modify it if you
-    have to. This can be used to add pre- or post-processing around
-    all actions.
-
-Read more about writing new actions in docs/extending/clickable_links.pod
-
-</%doc>
diff --git a/rt/share/html/Elements/MyRT b/rt/share/html/Elements/MyRT
index c59ec1c..84db949 100644
--- a/rt/share/html/Elements/MyRT
+++ b/rt/share/html/Elements/MyRT
@@ -67,12 +67,10 @@
 my %allowed_components = map {$_ => 1} @{RT->Config->Get('HomepageComponents')};
 
 my $user = $session{'CurrentUser'}->UserObj;
-$Portlets ||= $session{'my_rt_portlets'};
+$Portlets ||= $user->Preferences('HomepageSettings');
 unless ( $Portlets ) {
-    my ($default_portlets) = RT::System->new($session{'CurrentUser'})->Attributes->Named('HomepageSettings');
-    $Portlets = $session{'my_rt_portlets'} = $user->Preferences(
-        HomepageSettings => $default_portlets? $default_portlets->Content: {},
-    );
+    my ($defaults) = RT::System->new($session{'CurrentUser'})->Attributes->Named('HomepageSettings');
+    $Portlets = $defaults ? $defaults->Content : {};
 }
 
 $m->callback( CallbackName => 'MassagePortlets', Portlets => $Portlets );
diff --git a/rt/share/html/Elements/ShowLink b/rt/share/html/Elements/ShowLink
index cccc3d8..35b2638 100644
--- a/rt/share/html/Elements/ShowLink
+++ b/rt/share/html/Elements/ShowLink
@@ -49,7 +49,7 @@
 % if ($URI->IsLocal) {
 % my $member = $URI->Object;
 % my $has_name = UNIVERSAL::can($member, 'Name') || (UNIVERSAL::can($member, '_Accessible') && $member->_Accessible('Name', 'read'));
-% if (UNIVERSAL::isa($member, "RT::Ticket")) {
+% if (UNIVERSAL::isa($member, "RT::Ticket") and $member->CurrentUserHasRight('ShowTicket')) {
 % my $inactive = $member->QueueObj->IsInactiveStatus($member->Status);
 
 <span class="<% $inactive ? 'ticket-inactive' : '' %>">
diff --git a/rt/share/html/Elements/SimpleSearch b/rt/share/html/Elements/SimpleSearch
index bd8a876..d9f34fa 100755
--- a/rt/share/html/Elements/SimpleSearch
+++ b/rt/share/html/Elements/SimpleSearch
@@ -46,8 +46,9 @@
 %#
 %# END BPS TAGGED BLOCK }}}
 <form action="<% RT->Config->Get('WebPath') %><% $SendTo %>" id="simple-search">
-  <input size="12" name="q" autocomplete="off" accesskey="0" class="field" value="<&|/l&>Search</&>..." onfocus="if (this.value=='<&|/l&>Search</&>...') this.value=''" />
+  <input size="12" name="q" autocomplete="off" accesskey="0" class="field" value="<% $Placeholder %>..." onfocus="if (this.value==(<% $Placeholder, |n,j %>+'...')) this.value=''" />
 </form>
 <%ARGS>
 $SendTo => '/Search/Simple.html'
+$Placeholder => loc('Search')
 </%ARGS>
diff --git a/rt/share/html/Install/DatabaseDetails.html b/rt/share/html/Install/DatabaseDetails.html
index 78672db..b4d3f8c 100644
--- a/rt/share/html/Install/DatabaseDetails.html
+++ b/rt/share/html/Install/DatabaseDetails.html
@@ -165,6 +165,7 @@ if ( $Run ) {
                     );
 
                     my $sth = $dbh->prepare('select * from Users');
+                    $sth->execute();
                 };
 
                 unless ( $@ ) {
diff --git a/rt/share/html/NoAuth/js/cascaded.js b/rt/share/html/NoAuth/js/cascaded.js
index b72a33f..1611fd1 100644
--- a/rt/share/html/NoAuth/js/cascaded.js
+++ b/rt/share/html/NoAuth/js/cascaded.js
@@ -64,10 +64,10 @@ function filter_cascade (id, vals) {
         }
         else {
             jQuery(element).find('div').hide().find('input').attr('disabled', 'disabled');
-            jQuery(element).find('div[name=]').show().find('input').attr('disabled', '');
+            jQuery(element).find('div[data-name=]').show().find('input').attr('disabled', '');
             jQuery(element).find('div.none').show().find('input').attr('disabled','');
             for ( var j = 0; j < vals.length; j++ ) {
-                jQuery(element).find('div[name^=' + vals[j] + ']').show().find('input').attr('disabled', '');
+                jQuery(element).find('div[data-name^=' + vals[j] + ']').show().find('input').attr('disabled', '');
             }
         }
     }
diff --git a/rt/share/html/Prefs/MyRT.html b/rt/share/html/Prefs/MyRT.html
index a595ccf..288df0b 100644
--- a/rt/share/html/Prefs/MyRT.html
+++ b/rt/share/html/Prefs/MyRT.html
@@ -97,18 +97,19 @@ if ( $ARGS{'UpdateSummaryRows'} ) {
 $ARGS{'SummaryRows'} ||= $user->Preferences('SummaryRows', RT->Config->Get('DefaultSummaryRows'));
 
 if ($ARGS{Reset}) {
-    my ($ok, $msg) = $user->SetPreferences('HomepageSettings', {});
-    push @results, $ok ? loc('Preferences saved.') : $msg;
-    delete $session{'my_rt_portlets'};
+    for my $pref_name ('HomepageSettings', 'SummaryRows') {
+        next unless $user->Preferences($pref_name);
+        my ($ok, $msg) = $user->DeletePreferences($pref_name);
+        push @results, $msg unless $ok;
+    }
+    push @results, loc('Preferences saved.') unless @results;
 }
 
-unless (exists $session{'my_rt_portlets'}) {
-    my ($default_portlets) = RT::System->new($session{'CurrentUser'})->Attributes->Named('HomepageSettings');
-    my $portlets = $default_portlets ? $default_portlets->Content : {};
-
-    $session{'my_rt_portlets'} = $user->Preferences('HomepageSettings', $portlets);
+my $portlets = $user->Preferences('HomepageSettings');
+unless ($portlets) {
+    my ($defaults) = RT::System->new($session{'CurrentUser'})->Attributes->Named('HomepageSettings');
+    $portlets = $defaults ? $defaults->Content : {};
 }
-my $portlets = $session{'my_rt_portlets'};
 
 my %seen;
 my @items = map ["component-$_", loc($_)], grep !$seen{$_}++, @{RT->Config->Get('HomepageComponents')};
@@ -157,7 +158,6 @@ my @panes = $m->comp(
         my ( $conf, $pane ) = @_;
         my ($ok, $msg) = $user->SetPreferences( 'HomepageSettings', $conf );
         push @results, $ok ? loc('Preferences saved for [_1].', $pane) : $msg;
-        delete $session{'my_rt_portlets'};
     }
 );
 
diff --git a/rt/share/html/Prefs/Search.html b/rt/share/html/Prefs/Search.html
index bb52a62..3f2c404 100644
--- a/rt/share/html/Prefs/Search.html
+++ b/rt/share/html/Prefs/Search.html
@@ -68,6 +68,14 @@
 
 </form>
 
+<&|/Widgets/TitleBox, title => loc("Reset") &>
+<form method="post" name="ResetSearchOptions" action="Search.html">
+<input type="hidden" name="Reset" value="1" />
+<input type="hidden" name="name" value="<%$ARGS{name}%>" class="hidden" />
+<input type="submit" class="button" name="ResetSearchOptions" value="<% loc('Reset to default') %>">
+</form>
+</&>
+
 <%INIT>
 my @actions;
 my $title = loc("Customize").' ';
@@ -81,6 +89,13 @@ Abort('No search specified')
 
 my $search = $class->new ($session{'CurrentUser'});
 $search->LoadById ($id);
+
+# If we are resetting prefs, do so before attempting to load them
+if ($ARGS{'Reset'}) {
+    my ($ok, $msg) = $session{'CurrentUser'}->UserObj->DeletePreferences($ARGS{name});
+    push @actions, $ok ? loc('Preferences reset.') : $msg;
+}
+
 $title .= loc (RT::SavedSearch->EscapeDescription($search->Description), loc ('"N"'));
 my $user = $session{'CurrentUser'}->UserObj;
 my $SearchArg = $user->Preferences($search, $search->Content);
diff --git a/rt/share/html/Search/Bulk.html b/rt/share/html/Search/Bulk.html
index a215eac..38ca642 100755
--- a/rt/share/html/Search/Bulk.html
+++ b/rt/share/html/Search/Bulk.html
@@ -202,6 +202,13 @@ $cfs->LimitToQueue($_) for keys %$seen_queues;
 % } elsif ($cf->Type eq 'Text') {
 <td><& /Elements/EditCustomFieldText, @add &></td>
 <td> </td>
+% } elsif ($cf->Type eq 'Date') {
+<td><& /Elements/EditCustomFieldDate, @add, Default => undef &></td>
+<td><& /Elements/EditCustomFieldDate, @del, Default => undef &></td>
+% } elsif ($cf->Type eq 'DateTime') {
+% # Pass datemanip format to prevent another tz date conversion
+<td><& /Elements/EditCustomFieldDateTime, @add, Default => undef, Format => 'datemanip' &></td>
+<td><& /Elements/EditCustomFieldDateTime, @del, Default => undef, Format => 'datemanip' &></td>
 % } else {
 %   $RT::Logger->crit("Unknown CustomField type: " . $cf->Type);
 % }
@@ -372,7 +379,27 @@ unless ( $ARGS{'AddMoreAttach'} ) {
                   unless ( $cf->SingleValue );
 
                 my $current_values = $Ticket->CustomFieldValues($cfid);
+
+                if ( $cf->Type eq 'DateTime' || $cf->Type eq 'Date' ){
+                    # Clear out empty string submissions to avoid
+                    # Not set changed to Not set
+                    @values = grep length, @values;
+                }
+
                 foreach my $value (@values) {
+
+                    # Convert for timezone. Without converstion,
+                    # HasEntry and DeleteCustomFieldValue fail because
+                    # the value in the DB is converted.
+                    if ( $op eq 'del'
+                         && ($cf->Type eq 'DateTime' || $cf->Type eq 'Date') ){
+                        my $DateObj = RT::Date->new( $session{'CurrentUser'} );
+                        $DateObj->Set( Format => 'unknown',
+                                       Value  => $value );
+                        $value = $cf->Type eq 'DateTime' ? $DateObj->ISO
+                            : $DateObj->ISO(Time => 0, Seconds => 0);
+                    }
+
                     if ( $op eq 'del' && $current_values->HasEntry($value) ) {
                         my ( $id, $msg ) = $Ticket->DeleteCustomFieldValue(
                             Field => $cfid,
@@ -411,6 +438,8 @@ unless ( $ARGS{'AddMoreAttach'} ) {
 
     # Cleanup WebUI
     delete $session{'Attachments'};
+
+    $Tickets->RedoSearch();
 }
 
 my $TxnCFs = RT::CustomFields->new( $session{CurrentUser} );
diff --git a/rt/share/html/Search/Chart b/rt/share/html/Search/Chart
index 4be28ab..7256106 100644
--- a/rt/share/html/Search/Chart
+++ b/rt/share/html/Search/Chart
@@ -48,7 +48,7 @@
 <%args>
 $Query => "id > 0"
 $PrimaryGroupBy => 'Queue'
-$ChartStyle => 'bars'
+$ChartStyle => 'bar'
 </%args>
 <%init>
 my $chart_class;
diff --git a/rt/share/html/Search/Chart.html b/rt/share/html/Search/Chart.html
index 952f09c..ab25745 100644
--- a/rt/share/html/Search/Chart.html
+++ b/rt/share/html/Search/Chart.html
@@ -47,7 +47,7 @@
 %# END BPS TAGGED BLOCK }}}
 <%args>
 $PrimaryGroupBy => 'Queue'
-$ChartStyle => 'bars'
+$ChartStyle => 'bar'
 $Description => undef
 </%args>
 <%init>
diff --git a/rt/share/html/Search/Elements/BuildFormatString b/rt/share/html/Search/Elements/BuildFormatString
index 14e3a71..66fd147 100644
--- a/rt/share/html/Search/Elements/BuildFormatString
+++ b/rt/share/html/Search/Elements/BuildFormatString
@@ -109,6 +109,8 @@ my @fields = (
   )
 ); # loc_qw
 
+# This callback will only run once and will be removed in 4.4
+# If you want to add a new item to @fields, use the Default callback below.
 $m->callback( CallbackOnce => 1, CallbackName => 'SetFieldsOnce', Fields => \@fields );
 
 my $CustomFields = RT::CustomFields->new( $session{'CurrentUser'});
@@ -227,17 +229,17 @@ my @format_string;
 foreach my $field (@seen) {
     next unless $field;
     my $row = "";
-    if ( $field->{'output'} ) {
-        $row = join '', @{$field->{'output'}};
+    if ( $field->{'original_string'} ) {
+        $row = $field->{'original_string'};
     }
     else {
         $row .= $field->{'Prefix'} if defined $field->{'Prefix'};
         $row .= "__$field->{'Column'}__"
           unless ( $field->{'Column'} eq "<blank>" );
         $row .= $field->{'Suffix'} if defined $field->{'Suffix'};
+        $row =~ s!([\\'])!\\$1!g;
+        $row = "'$row'";
     }
-    $row =~ s!([\\'])!\\$1!g;
-    $row = "'$row'";
     push( @format_string, $row );
 }
 
diff --git a/rt/share/html/Search/Elements/Chart b/rt/share/html/Search/Elements/Chart
index 05a0422..f0d1e4a 100644
--- a/rt/share/html/Search/Elements/Chart
+++ b/rt/share/html/Search/Elements/Chart
@@ -48,7 +48,7 @@
 <%args>
 $Query => "id > 0"
 $PrimaryGroupBy => 'Queue'
-$ChartStyle => 'bars'
+$ChartStyle => 'bar'
 </%args>
 <%init>
 use RT::Report::Tickets;
diff --git a/rt/share/html/Search/Results.html b/rt/share/html/Search/Results.html
index 601786f..3c3187c 100755
--- a/rt/share/html/Search/Results.html
+++ b/rt/share/html/Search/Results.html
@@ -111,7 +111,6 @@ if ( !defined($Rows) ) {
 }
 $Page = 1 unless $Page && $Page > 0;
 
-my ($title, $ticketcount);
 $session{'i'}++;
 $session{'tickets'} = RT::Tickets->new($session{'CurrentUser'}) ;
 my ($ok, $msg) = $Query ? $session{'tickets'}->FromSQL($Query) : (1, "Vacuously OK");
@@ -141,11 +140,10 @@ $session{'CurrentSearchHash'} = {
 };
 
 
+my ($title, $ticketcount) = (loc("Found tickets"), 0);
 if ( $session{'tickets'}->Query()) {
     $ticketcount = $session{tickets}->CountAll();
     $title = loc('Found [quant,_1,ticket]', $ticketcount);
-} else {
-    $title = loc("Find tickets");
 }
 
 my $QueryString = "?".$m->comp('/Elements/QueryString',
diff --git a/rt/t/api/date.t b/rt/t/api/date.t
index cc1c694..22c6f1b 100644
--- a/rt/t/api/date.t
+++ b/rt/t/api/date.t
@@ -4,7 +4,7 @@ use DateTime;
 
 use warnings;
 use strict;
-use RT::Test tests => 173;
+use RT::Test tests => 175;
 use RT::User;
 use Test::Warn;
 
@@ -440,6 +440,14 @@ my $year = (localtime(time))[5] + 1900;
     $date->Unix(0);
     $date->AddDays(31);
     is($date->ISO, '1970-02-01 00:00:00', "added one month");
+
+    $date->Unix(0);
+    $date->AddDays(0);
+    is($date->ISO, '1970-01-01 00:00:00', "added no days");
+
+    $date->Unix(0);
+    $date->AddDays();
+    is($date->ISO, '1970-01-02 00:00:00', "added one day with no argument");
 }
 
 {
diff --git a/rt/t/api/password-types.t b/rt/t/api/password-types.t
index e5155e3..10a874a 100644
--- a/rt/t/api/password-types.t
+++ b/rt/t/api/password-types.t
@@ -3,6 +3,8 @@ use warnings;
 
 use RT::Test;
 use Digest::MD5;
+use Encode 'encode_utf8';
+use utf8;
 
 my $default = "sha512";
 
@@ -38,3 +40,12 @@ my $trunc = MIME::Base64::encode_base64(
 $root->_Set( Field => "Password", Value => $trunc);
 ok($root->IsPassword("secret"), "Unsalted MD5 base64 works");
 like($root->__Value("Password"), qr/^\!$default\!/, "And is now upgraded to salted $default");
+
+# Non-ASCII salted truncated SHA-256
+my $non_ascii_trunc = MIME::Base64::encode_base64(
+    "salt" . substr(Digest::SHA::sha256("salt".Digest::MD5::md5(encode_utf8("áěšý"))),0,26),
+    ""
+);
+$root->_Set( Field => "Password", Value => $non_ascii_trunc);
+ok($root->IsPassword("áěšý"), "Unsalted MD5 base64 works");
+like($root->__Value("Password"), qr/^\!$default\!/, "And is now upgraded to salted $default");
diff --git a/rt/t/fts/indexed_mysql.t b/rt/t/fts/indexed_mysql.t
index a54382f..0a4f026 100644
--- a/rt/t/fts/indexed_mysql.t
+++ b/rt/t/fts/indexed_mysql.t
@@ -32,7 +32,7 @@ sub setup_indexing {
         command        => $RT::SbinPath .'/rt-setup-fulltext-index',
         dba            => $ENV{'RT_DBA_USER'},
         'dba-password' => $ENV{'RT_DBA_PASSWORD'},
-        url            => "sphinx://localhost:$port/rt",
+        url            => "sphinx://127.0.0.1:$port/rt",
     );
     ok(!$exit_code, "setted up index");
     diag "output: $output" if $ENV{'TEST_VERBOSE'};
diff --git a/rt/t/pod.t b/rt/t/pod.t
index d11a497..697a30b 100644
--- a/rt/t/pod.t
+++ b/rt/t/pod.t
@@ -4,4 +4,4 @@ use warnings;
 use Test::More;
 eval "use Test::Pod 1.14";
 plan skip_all => "Test::Pod 1.14 required for testing POD" if $@;
-all_pod_files_ok();
+all_pod_files_ok( all_pod_files("lib","docs","etc","bin","sbin"));
diff --git a/rt/t/validator/group_members.t b/rt/t/validator/group_members.t
index fbe7580..af93c51 100644
--- a/rt/t/validator/group_members.t
+++ b/rt/t/validator/group_members.t
@@ -2,104 +2,45 @@
 use strict;
 use warnings;
 
-use RT::Test tests => 60;
-
-sub load_or_create_group {
-    my $name = shift;
-    my %args = (@_);
-
-    my $group = RT::Group->new( RT->SystemUser );
-    $group->LoadUserDefinedGroup( $name );
-    unless ( $group->id ) {
-        my ($id, $msg) = $group->CreateUserDefinedGroup(
-            Name => $name,
-        );
-        die "$msg" unless $id;
-    }
-
-    if ( $args{Members} ) {
-        my $cur = $group->MembersObj;
-        while ( my $entry = $cur->Next ) {
-            my ($status, $msg) = $entry->Delete;
-            die "$msg" unless $status;
-        }
-
-        foreach my $new ( @{ $args{Members} } ) {
-            my ($status, $msg) = $group->AddMember(
-                ref($new)? $new->id : $new,
-            );
-            die "$msg" unless $status;
-        }
-    }
-    
-    return $group;
-}
-
-my $validator_path = "$RT::SbinPath/rt-validator";
-sub run_validator {
-    my %args = (check => 1, resolve => 0, force => 1, @_ );
-
-    my $cmd = $validator_path;
-    die "Couldn't find $cmd command" unless -f $cmd;
-
-    while( my ($k,$v) = each %args ) {
-        next unless $v;
-        $cmd .= " --$k '$v'";
-    }
-    $cmd .= ' 2>&1';
-
-    require IPC::Open2;
-    my ($child_out, $child_in);
-    my $pid = IPC::Open2::open2($child_out, $child_in, $cmd);
-    close $child_in;
-
-    my $result = do { local $/; <$child_out> };
-    close $child_out;
-    waitpid $pid, 0;
-
-    DBIx::SearchBuilder::Record::Cachable->FlushCache
-        if $args{'resolve'};
-
-    return ($?, $result);
-}
+use RT::Test tests => 63;
 
 {
-    my ($ecode, $res) = run_validator();
+    my ($ecode, $res) = RT::Test->run_validator();
     is $res, '', 'empty result';
 }
 
 {
-    my $group = load_or_create_group('test', Members => [] );
+    my $group = RT::Test->load_or_create_group('test', Members => [] );
     ok $group, "loaded or created a group";
 
-    my ($ecode, $res) = run_validator();
+    my ($ecode, $res) = RT::Test->run_validator();
     is $res, '', 'empty result';
 }
 
 # G1 -> G2
 {
-    my $group1 = load_or_create_group( 'test1', Members => [] );
+    my $group1 = RT::Test->load_or_create_group( 'test1', Members => [] );
     ok $group1, "loaded or created a group";
 
-    my $group2 = load_or_create_group( 'test2', Members => [ $group1 ]);
+    my $group2 = RT::Test->load_or_create_group( 'test2', Members => [ $group1 ]);
     ok $group2, "loaded or created a group";
 
     ok $group2->HasMember( $group1->id ), "has member";
     ok $group2->HasMemberRecursively( $group1->id ), "has member";
 
-    my ($ecode, $res) = run_validator();
+    my ($ecode, $res) = RT::Test->run_validator();
     is $res, '', 'empty result';
 
     $RT::Handle->dbh->do("DELETE FROM CachedGroupMembers");
     DBIx::SearchBuilder::Record::Cachable->FlushCache;
     ok !$group2->HasMemberRecursively( $group1->id ), "has no member, broken DB";
 
-    ($ecode, $res) = run_validator(resolve => 1);
+    ($ecode, $res) = RT::Test->run_validator(resolve => 1);
 
     ok $group2->HasMember( $group1->id ), "has member";
     ok $group2->HasMemberRecursively( $group1->id ), "has member";
 
-    ($ecode, $res) = run_validator();
+    ($ecode, $res) = RT::Test->run_validator();
     is $res, '', 'empty result';
 }
 
@@ -109,7 +50,7 @@ sub run_validator {
     for (1..5) {
         my $child = @groups? $groups[-1]: undef;
 
-        my $group = load_or_create_group( 'test'. $_, Members => [ $child? ($child): () ] );
+        my $group = RT::Test->load_or_create_group( 'test'. $_, Members => [ $child? ($child): () ] );
         ok $group, "loaded or created a group";
 
         ok $group->HasMember( $child->id ), "has member"
@@ -120,7 +61,7 @@ sub run_validator {
         push @groups, $group;
     }
 
-    my ($ecode, $res) = run_validator();
+    my ($ecode, $res) = RT::Test->run_validator();
     is $res, '', 'empty result';
 
     $RT::Handle->dbh->do("DELETE FROM CachedGroupMembers");
@@ -128,7 +69,7 @@ sub run_validator {
 
     ok !$groups[1]->HasMemberRecursively( $groups[0]->id ), "has no member, broken DB";
 
-    ($ecode, $res) = run_validator(resolve => 1);
+    ($ecode, $res) = RT::Test->run_validator(resolve => 1);
 
     for ( my $i = 1; $i < @groups; $i++ ) {
         ok $groups[$i]->HasMember( $groups[$i-1]->id ), "has member";
@@ -136,7 +77,7 @@ sub run_validator {
             foreach 0..$i-1;
     }
 
-    ($ecode, $res) = run_validator();
+    ($ecode, $res) = RT::Test->run_validator();
     is $res, '', 'empty result';
 }
 
@@ -144,34 +85,51 @@ sub run_validator {
 {
     my @groups;
     for (2..5) {
-        my $group = load_or_create_group( 'test'. $_, Members => [] );
+        my $group = RT::Test->load_or_create_group( 'test'. $_, Members => [] );
         ok $group, "loaded or created a group";
         push @groups, $group;
     }
 
-    my $parent = load_or_create_group( 'test1', Members => \@groups );
+    my $parent = RT::Test->load_or_create_group( 'test1', Members => \@groups );
     ok $parent, "loaded or created a group";
 
-    my ($ecode, $res) = run_validator();
+    my ($ecode, $res) = RT::Test->run_validator();
     is $res, '', 'empty result';
 }
 
 # G1 <- (G2, G3, G4) <- G5
 {
-    my $gchild = load_or_create_group( 'test5', Members => [] );
+    my $gchild = RT::Test->load_or_create_group( 'test5', Members => [] );
     ok $gchild, "loaded or created a group";
     
     my @groups;
     for (2..4) {
-        my $group = load_or_create_group( 'test'. $_, Members => [ $gchild ] );
+        my $group = RT::Test->load_or_create_group( 'test'. $_, Members => [ $gchild ] );
         ok $group, "loaded or created a group";
         push @groups, $group;
     }
 
-    my $parent = load_or_create_group( 'test1', Members => \@groups );
+    my $parent = RT::Test->load_or_create_group( 'test1', Members => \@groups );
     ok $parent, "loaded or created a group";
 
-    my ($ecode, $res) = run_validator();
+    my ($ecode, $res) = RT::Test->run_validator();
     is $res, '', 'empty result';
 }
 
+# group without principal record and cgm records
+# was causing infinite loop as principal was not created
+{
+    my $group = RT::Test->load_or_create_group('Test');
+    ok $group && $group->id, 'loaded or created group';
+
+    my $dbh = $group->_Handle->dbh;
+    $dbh->do('DELETE FROM Principals WHERE id = ?', {RaiseError => 1}, $group->id);
+    $dbh->do('DELETE FROM CachedGroupMembers WHERE GroupId = ?', {RaiseError => 1}, $group->id);
+    DBIx::SearchBuilder::Record::Cachable->FlushCache;
+
+    my ($ecode, $res) = RT::Test->run_validator(resolve => 1, timeout => 30);
+    ok $res;
+
+    ($ecode, $res) = RT::Test->run_validator();
+    is $res, '', 'empty result';
+}
diff --git a/rt/t/web/path-traversal.t b/rt/t/web/path-traversal.t
index 5d5c954..01302e6 100644
--- a/rt/t/web/path-traversal.t
+++ b/rt/t/web/path-traversal.t
@@ -1,9 +1,10 @@
 use strict;
 use warnings;
 
-use RT::Test tests => 22;
+use RT::Test tests => undef;
 
 my ($baseurl, $agent) = RT::Test->started_ok;
+ok($agent->login);
 
 $agent->get("$baseurl/NoAuth/../Elements/HeaderJavascript");
 is($agent->status, 400);
@@ -31,6 +32,12 @@ SKIP: {
     $agent->warning_like(qr/Invalid request.*aborting/,);
 };
 
+# Do not reject a simple /. in the URL, for downloading uploaded
+# dotfiles, for example.
+$agent->get("$baseurl/Ticket/Attachment/28/9/.bashrc");
+is($agent->status, 200); # Even for a file not found, we return 200
+$agent->content_contains("Bad attachment id");
+
 # do not reject these URLs, even though they contain /. outside the path
 $agent->get("$baseurl/index.html?ignored=%2F%2E");
 is($agent->status, 200);
@@ -44,3 +51,5 @@ is($agent->status, 200);
 $agent->get("$baseurl/index.html#/.");
 is($agent->status, 200);
 
+undef $agent;
+done_testing;

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

Summary of changes:
 rt/configure                                       |   25 ++-
 rt/configure.ac                                    |    1 +
 rt/docs/UPGRADING-4.0                              |    3 +
 rt/docs/extending/clickable_links.pod              |   11 +-
 rt/etc/RT_Config.pm.in                             |    5 +-
 rt/etc/upgrade/3.8.9/content                       |    1 +
 rt/lib/RT.pm                                       |    4 +-
 rt/lib/RT/Config.pm                                |   13 +-
 rt/lib/RT/Date.pm                                  |    3 +-
 rt/lib/RT/Generated.pm                             |    2 +-
 rt/lib/RT/Handle.pm                                |   41 ++++-
 rt/lib/RT/Interface/REST.pm                        |    2 +-
 rt/lib/RT/Interface/Web.pm                         |    2 +-
 rt/lib/RT/Interface/Web/Handler.pm                 |    2 +-
 rt/lib/RT/Lifecycle.pm                             |    4 +-
 rt/lib/RT/Shredder/Plugin/SQLDump.pm               |    4 +-
 rt/lib/RT/StyleGuide.pod                           |  207 +++++---------------
 rt/lib/RT/Template.pm                              |    6 +
 rt/lib/RT/Test.pm                                  |   70 +++++++
 rt/lib/RT/User.pm                                  |   24 +++-
 rt/lib/RT/Users.pm                                 |   22 ++-
 rt/sbin/rt-server.fcgi.in                          |    1 +
 rt/sbin/rt-server.in                               |    1 +
 rt/sbin/rt-setup-fulltext-index.in                 |   10 +-
 rt/sbin/rt-test-dependencies.in                    |    6 +-
 rt/sbin/rt-validator.in                            |    4 +-
 rt/sbin/standalone_httpd.in                        |    1 +
 rt/share/html/Dashboards/Queries.html              |    2 +-
 .../html/Elements/CollectionAsTable/ParseFormat    |    3 +-
 rt/share/html/Elements/EditCustomFieldAutocomplete |    9 +-
 rt/share/html/Elements/EditCustomFieldDateTime     |    3 +-
 rt/share/html/Elements/EditCustomFieldFreeform     |   15 ++-
 rt/share/html/Elements/EditCustomFieldSelect       |    4 +-
 rt/share/html/Elements/EditCustomFieldText         |   18 ++-
 rt/share/html/Elements/EditCustomFieldWikitext     |   18 ++-
 rt/share/html/Elements/MakeClicky                  |   48 ++----
 rt/share/html/Elements/MyRT                        |    8 +-
 rt/share/html/Elements/ShowLink                    |    2 +-
 rt/share/html/Elements/SimpleSearch                |    3 +-
 rt/share/html/Install/DatabaseDetails.html         |    1 +
 rt/share/html/NoAuth/js/cascaded.js                |    4 +-
 rt/share/html/Prefs/MyRT.html                      |   20 +-
 rt/share/html/Prefs/Search.html                    |   15 ++
 rt/share/html/Search/Bulk.html                     |   29 +++
 rt/share/html/Search/Chart                         |    2 +-
 rt/share/html/Search/Chart.html                    |    2 +-
 rt/share/html/Search/Elements/BuildFormatString    |   10 +-
 rt/share/html/Search/Elements/Chart                |    2 +-
 rt/share/html/Search/Results.html                  |    4 +-
 rt/t/api/date.t                                    |   10 +-
 rt/t/api/password-types.t                          |   11 +
 rt/t/fts/indexed_mysql.t                           |    2 +-
 rt/t/pod.t                                         |    2 +-
 rt/t/validator/group_members.t                     |  116 ++++--------
 rt/t/web/path-traversal.t                          |   11 +-
 55 files changed, 491 insertions(+), 358 deletions(-)




More information about the freeside-commits mailing list