[freeside-commits] branch FREESIDE_3_BRANCH updated. 0ea23112cfa0d82738b0f08d60d90579721b7524

Ivan ivan at 420.am
Fri May 30 13:00:45 PDT 2014


The branch, FREESIDE_3_BRANCH has been updated
       via  0ea23112cfa0d82738b0f08d60d90579721b7524 (commit)
      from  60dd95422a1ad4724e0c5d9dd7f8e8878cd96aa8 (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 0ea23112cfa0d82738b0f08d60d90579721b7524
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 18f2b7a..2344193 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