[Rt-commit] rt branch, tickets_map, created. rt-3.8.6-90-g054c5f3

Ruslan Zakirov ruz at bestpractical.com
Mon Nov 16 12:53:38 EST 2009


The branch, tickets_map has been created
        at  054c5f3bb927914c91a167b8f749ef5a69937673 (commit)

- Log -----------------------------------------------------------------
commit 3318b3103d3e52633a02e67849195038c43a3cfd
Author: Kevin Falcone <falcone at bestpractical.com>
Date:   Fri Oct 9 18:51:10 2009 -0400

    Bump version to 3.8.6rc1

diff --git a/configure.ac b/configure.ac
index e9d9bc7..afa4e9c 100755
--- a/configure.ac
+++ b/configure.ac
@@ -7,7 +7,7 @@ AC_REVISION($Revision$)dnl
 
 dnl Setup autoconf
 AC_PREREQ([2.53])
-AC_INIT(RT, 3.8.HEAD, [rt-bugs at bestpractical.com])
+AC_INIT(RT, 3.8.6rc1, [rt-bugs at bestpractical.com])
 AC_CONFIG_SRCDIR([lib/RT.pm.in])
 
 dnl Extract RT version number components

commit 9982496c0ca2cc46265b5eb894fc28edcbed3666
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Wed Oct 14 03:04:20 2009 +0400

    refactor catching mails in tests
    
    * we had duplication, mailsent_ok didn't work with catcher
    * use external file as tests under apache can not share variables

diff --git a/lib/RT/Test.pm b/lib/RT/Test.pm
index 6de822a..f2f0e9f 100644
--- a/lib/RT/Test.pm
+++ b/lib/RT/Test.pm
@@ -77,7 +77,6 @@ wrap 'HTTP::Request::Common::form_data',
 our @EXPORT = qw(is_empty);
 
 our ($port, $dbname);
-my $mailsent;
 
 =head1 NAME
 
@@ -147,15 +146,6 @@ sub import {
 
     if (RT->Config->Get('DevelMode')) { require Module::Refresh; }
 
-    # make it another function
-    $mailsent = 0;
-    my $mailfunc = sub { 
-        my $Entity = shift;
-        $mailsent++;
-        return 1;
-    };
-    RT->Config->Set( 'MailCommand' => $mailfunc );
-
     $class->bootstrap_db( %args );
 
     RT->Init;
@@ -202,6 +192,7 @@ sub db_requires_no_dba {
 }
 
 my $config;
+my $mailbox_catcher = File::Temp->new( OPEN => 0, CLEANUP => 0 )->filename;
 sub bootstrap_config {
     my $self = shift;
     my %args = @_;
@@ -224,6 +215,20 @@ Set( \$MailCommand, 'testfile');
     print $config "Set( \$DevelMode, 0 );\n"
         if $INC{'Devel/Cover.pm'};
 
+    # set mail catcher
+    print $config <<END;
+Set( \$MailCommand, sub {
+    my \$MIME = shift;
+
+    open my \$handle, '>>', '$mailbox_catcher'
+        or die "Unable to open '$mailbox_catcher' for appending: \$!";
+
+    \$MIME->print(\$handle);
+    print \$handle "%% split me! %%\n";
+    close \$handle;
+} );
+END
+
     print $config $args{'config'} if $args{'config'};
 
     print $config "\n1;\n";
@@ -377,29 +382,6 @@ sub _get_dbh {
     return $dbh;
 }
 
-sub open_mailgate_ok {
-    my $class   = shift;
-    my $baseurl = shift;
-    my $queue   = shift || 'general';
-    my $action  = shift || 'correspond';
-    Test::More::ok(open(my $mail, "|$RT::BinPath/rt-mailgate --url $baseurl --queue $queue --action $action"), "Opened the mailgate - $!");
-    return $mail;
-}
-
-
-sub close_mailgate_ok {
-    my $class = shift;
-    my $mail  = shift;
-    close $mail;
-    Test::More::is ($? >> 8, 0, "The mail gateway exited normally. yay");
-}
-
-sub mailsent_ok {
-    my $class = shift;
-    my $expected  = shift;
-    Test::More::is ($mailsent, $expected, "The number of mail sent ($expected) matches. yay");
-}
-
 =head1 UTILITIES
 
 =head2 load_or_create_user
@@ -654,40 +636,71 @@ sub send_via_mailgate {
     my $message = shift;
     my %args = (@_);
 
-    my ($status, $gate_result) = $self->run_mailgate( message => $message, %args );
+    my ($status, $gate_result) = $self->run_mailgate(
+        message => $message, %args
+    );
 
     my $id;
     unless ( $status >> 8 ) {
         ($id) = ($gate_result =~ /Ticket:\s*(\d+)/i);
         unless ( $id ) {
-            Test::More::diag "Couldn't find ticket id in text:\n$gate_result" if $ENV{'TEST_VERBOSE'};
+            Test::More::diag "Couldn't find ticket id in text:\n$gate_result"
+                if $ENV{'TEST_VERBOSE'};
         }
     } else {
-        Test::More::diag "Mailgate output:\n$gate_result" if $ENV{'TEST_VERBOSE'};
+        Test::More::diag "Mailgate output:\n$gate_result"
+            if $ENV{'TEST_VERBOSE'};
     }
     return ($status, $id);
 }
 
-my $mailbox_catcher = File::Temp->new( OPEN => 0, CLEANUP => 0 )->filename;
-sub set_mail_catcher {
-    my $self = shift;
-    my $catcher = sub {
-        my $MIME = shift;
+sub open_mailgate_ok {
+    my $class   = shift;
+    my $baseurl = shift;
+    my $queue   = shift || 'general';
+    my $action  = shift || 'correspond';
+    Test::More::ok(open(my $mail, "|$RT::BinPath/rt-mailgate --url $baseurl --queue $queue --action $action"), "Opened the mailgate - $!");
+    return $mail;
+}
 
-        open my $handle, '>>', $mailbox_catcher
-            or die "Unable to open $mailbox_catcher for appending: $!";
 
-        $MIME->print($handle);
-        print $handle "%% split me! %%\n";
-        close $handle;
-    };
-    RT->Config->Set( MailCommand => $catcher );
+sub close_mailgate_ok {
+    my $class = shift;
+    my $mail  = shift;
+    close $mail;
+    Test::More::is ($? >> 8, 0, "The mail gateway exited normally. yay");
+}
+
+sub mailsent_ok {
+    my $class = shift;
+    my $expected  = shift;
+
+    my $mailsent = scalar grep /\S/, split /%% split me! %%\n/,
+        RT::Test->file_content(
+            $mailbox_catcher,
+            'unlink' => 0,
+            noexist => 1
+        );
+
+    Test::More::is(
+        $mailsent, $expected,
+        "The number of mail sent ($expected) matches. yay"
+    );
+}
+
+sub set_mail_catcher {
+    my $self = shift;
+    return 1;
 }
 
 sub fetch_caught_mails {
     my $self = shift;
     return grep /\S/, split /%% split me! %%\n/,
-        RT::Test->file_content( $mailbox_catcher, 'unlink' => 1, noexist => 1 );
+        RT::Test->file_content(
+            $mailbox_catcher,
+            'unlink' => 1,
+            noexist => 1
+        );
 }
 
 sub clean_caught_mails {

commit 7b3d05fdc3977e7536b5dd71728af07ebc1c9447
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Wed Oct 14 12:09:09 2009 +0400

    adjust test level so failures reported in the right place

diff --git a/t/maildigest/attributes.t b/t/maildigest/attributes.t
index f52ed00..ba2a585 100644
--- a/t/maildigest/attributes.t
+++ b/t/maildigest/attributes.t
@@ -152,6 +152,8 @@ sub email_digest_like {
     my $arg = shift;
     my $pattern = shift;
 
+    local $Test::Builder::Level = $Test::Builder::Level + 1;
+
     my $perl = $^X . ' ' . join ' ', map { "-I$_" } @INC;
     open my $digester, "-|", "$perl $RT::SbinPath/rt-email-digest $arg";
     my @results = <$digester>;

commit d343e18de308e6d3de4856d623450a3000758ac8
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Wed Oct 14 12:10:13 2009 +0400

    clean cought emails on END

diff --git a/lib/RT/Test.pm b/lib/RT/Test.pm
index f2f0e9f..b072d51 100644
--- a/lib/RT/Test.pm
+++ b/lib/RT/Test.pm
@@ -1173,6 +1173,8 @@ END {
 
     RT::Test->stop_server;
 
+    RT::Test->clean_caught_mails;
+
     if ( $ENV{RT_TEST_PARALLEL} && $created_new_db ) {
 
         # Pg doesn't like if you issue a DROP DATABASE while still connected

commit 94637640afa1ebf8288b5a4e608d64637c56666f
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Wed Oct 14 14:02:25 2009 +0400

    move standalone related code into start_standalone_server

diff --git a/lib/RT/Test.pm b/lib/RT/Test.pm
index b072d51..560e5a8 100644
--- a/lib/RT/Test.pm
+++ b/lib/RT/Test.pm
@@ -118,14 +118,6 @@ BEGIN {
     $dbname = $ENV{RT_TEST_PARALLEL}? "rt3test_$port" : "rt3test";
 };
 
-use RT::Interface::Web::Standalone;
-use Test::HTTP::Server::Simple::StashWarnings;
-use Test::WWW::Mechanize;
-use File::Path 'mkpath';
-
-unshift @RT::Interface::Web::Standalone::ISA, 'Test::HTTP::Server::Simple::StashWarnings';
-sub RT::Interface::Web::Standalone::test_warning_path { "/__test_warnings" }
-
 sub import {
     my $class = shift;
     my %args = @_;
@@ -926,6 +918,16 @@ sub started_ok {
 sub start_standalone_server {
     my $self = shift;
 
+
+    require RT::Interface::Web::Standalone;
+
+    require Test::HTTP::Server::Simple::StashWarnings;
+    unshift @RT::Interface::Web::Standalone::ISA,
+        'Test::HTTP::Server::Simple::StashWarnings';
+    *RT::Interface::Web::Standalone::test_warning_path = sub {
+        "/__test_warnings";
+    };
+
     my $s = RT::Interface::Web::Standalone->new($port);
 
     my $ret = $s->started_ok;

commit ec0256fdb2554218667f7e6243eb926244a20bf5
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Wed Oct 14 14:03:22 2009 +0400

    minor changes in t/

diff --git a/lib/RT/Test.pm b/lib/RT/Test.pm
index 560e5a8..29d442c 100644
--- a/lib/RT/Test.pm
+++ b/lib/RT/Test.pm
@@ -54,7 +54,8 @@ use warnings;
 use base 'Test::More';
 
 use Socket;
-use File::Temp;
+use File::Temp qw(tempfile);
+use File::Path qw(mkpath);
 use File::Spec;
 
 our $SKIP_REQUEST_WORK_AROUND = 0;
@@ -75,7 +76,6 @@ wrap 'HTTP::Request::Common::form_data',
 
 
 our @EXPORT = qw(is_empty);
-
 our ($port, $dbname);
 
 =head1 NAME
@@ -940,8 +940,6 @@ sub start_standalone_server {
     return ($ret, RT::Test::Web->new);
 }
 
-use File::Temp qw(tempfile);
-
 sub start_apache_server {
     my $self = shift;
     my $variant = shift || 'mod_perl';

commit 9803f6b7e7a75c188177d876a69ddda68ce14c7b
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Wed Oct 14 23:30:04 2009 +0400

    refactor tests: new tmp dir, Cfg->Set updates file and more
    
    * new central tmp dir under t/tmp
    * tmp dir is not deleted on failures
    * centrall %tmp hash in RT::Test to hold names
      of files
    * set_config_wrapper that wraps RT->Config->Set calls and
      append changes into the test config file, so we can
      catch them in UI by restarting server

diff --git a/lib/RT/Test.pm b/lib/RT/Test.pm
index 29d442c..c022854 100644
--- a/lib/RT/Test.pm
+++ b/lib/RT/Test.pm
@@ -77,6 +77,16 @@ wrap 'HTTP::Request::Common::form_data',
 
 our @EXPORT = qw(is_empty);
 our ($port, $dbname);
+our @SERVERS;
+
+my %tmp = (
+    directory => undef,
+    config    => {
+        RT => undef,
+        apache => undef,
+    },
+    mailbox   => undef,
+);
 
 =head1 NAME
 
@@ -131,6 +141,8 @@ sub import {
         $class->builder->no_plan unless $class->builder->has_plan;
     }
 
+    $class->bootstrap_tempdir;
+
     $class->bootstrap_config( %args );
 
     use RT;
@@ -144,6 +156,8 @@ sub import {
 
     $class->bootstrap_plugins( %args );
 
+    $class->set_config_wrapper;
+
     my $screen_logger = $RT::Logger->remove( 'screen' );
     require Log::Dispatch::Perl;
     $RT::Logger->add( Log::Dispatch::Perl->new
@@ -183,13 +197,29 @@ sub db_requires_no_dba {
     return 1 if $db_type eq 'SQLite';
 }
 
-my $config;
-my $mailbox_catcher = File::Temp->new( OPEN => 0, CLEANUP => 0 )->filename;
+sub bootstrap_tempdir {
+    my $self = shift;
+    my $test_file = (
+        File::Spec->rel2abs((caller)[1])
+            =~ m{(?:^|[\\/])t[/\\](.*)}
+    );
+    my $dir_name = File::Spec->rel2abs('t/tmp/'. $test_file);
+    mkpath( $dir_name );
+    return $tmp{'directory'} = File::Temp->newdir(
+        DIR => $dir_name
+    );
+}
+
 sub bootstrap_config {
     my $self = shift;
     my %args = @_;
 
-    $config = File::Temp->new;
+    $tmp{'config'}{'RT'} = File::Spec->catfile(
+        "$tmp{'directory'}", 'RT_SiteConfig.pm'
+    );
+    open my $config, '>', $tmp{'config'}{'RT'}
+        or die "Couldn't open $tmp{'config'}{'RT'}: $!";
+
     print $config qq{
 Set( \$WebPort , $port);
 Set( \$WebBaseURL , "http://localhost:\$WebPort");
@@ -208,12 +238,15 @@ Set( \$MailCommand, 'testfile');
         if $INC{'Devel/Cover.pm'};
 
     # set mail catcher
+    my $mail_catcher = $tmp{'mailbox'} = File::Spec->catfile(
+        $tmp{'directory'}->dirname, 'mailbox.eml'
+    );
     print $config <<END;
 Set( \$MailCommand, sub {
     my \$MIME = shift;
 
-    open my \$handle, '>>', '$mailbox_catcher'
-        or die "Unable to open '$mailbox_catcher' for appending: \$!";
+    open my \$handle, '>>', '$mail_catcher'
+        or die "Unable to open '$mail_catcher' for appending: \$!";
 
     \$MIME->print(\$handle);
     print \$handle "%% split me! %%\n";
@@ -224,12 +257,45 @@ END
     print $config $args{'config'} if $args{'config'};
 
     print $config "\n1;\n";
-    $ENV{'RT_SITE_CONFIG'} = $config->filename;
+    $ENV{'RT_SITE_CONFIG'} = $tmp{'config'}{'RT'};
     close $config;
 
     return $config;
 }
 
+sub set_config_wrapper {
+    my $self = shift;
+
+    my $old_sub = \&RT::Config::Set;
+    *RT::Config::Set = sub {
+        my @caller = caller;
+        if ( ($caller[1]||'') =~ /\.t$/ ) {
+            my ($self, $name) = @_;
+            my $type = $RT::Config::META{$name}->{'Type'} || 'SCALAR';
+            my %sigils = (
+                HASH   => '%',
+                ARRAY  => '@',
+                SCALAR => '$',
+            );
+            my $sigil = $sigils{$type} || $sigils{'SCALAR'};
+            open my $fh, '>>', $tmp{'config'}{'RT'}
+                or die "Couldn't open config file: $!";
+            require Data::Dumper;
+            print $fh
+                "\nSet(${sigil}${name}, \@{"
+                    . Data::Dumper::Dumper([@_[2 .. $#_]])
+                ."}); 1;\n";
+            close $fh;
+
+            if ( @SERVERS ) {
+                warn "you're changing config option in a test file"
+                    ." when server is active";
+            }
+        }
+        return $old_sub->(@_);
+    };
+}
+
 sub bootstrap_db {
     my $self = shift;
     my %args = @_;
@@ -669,7 +735,7 @@ sub mailsent_ok {
 
     my $mailsent = scalar grep /\S/, split /%% split me! %%\n/,
         RT::Test->file_content(
-            $mailbox_catcher,
+            $tmp{'mailbox'},
             'unlink' => 0,
             noexist => 1
         );
@@ -689,14 +755,14 @@ sub fetch_caught_mails {
     my $self = shift;
     return grep /\S/, split /%% split me! %%\n/,
         RT::Test->file_content(
-            $mailbox_catcher,
+            $tmp{'mailbox'},
             'unlink' => 1,
             noexist => 1
         );
 }
 
 sub clean_caught_mails {
-    unlink $mailbox_catcher;
+    unlink $tmp{'mailbox'};
 }
 
 =head2 get_relocatable_dir
@@ -899,7 +965,6 @@ sub trust_gnupg_key {
     return %res;
 }
 
-my @SERVERS;
 sub started_ok {
     my $self = shift;
 
@@ -946,10 +1011,18 @@ sub start_apache_server {
 
     my %info = $self->apache_server_info( variant => $variant );
 
-    my ($log_fh, $log_fn) = tempfile();
-    my $pid_fn = File::Spec->rel2abs( File::Spec->catfile(
-        't', "apache.$$.pid"
-    ) );
+    Test::More::diag(do {
+        open my $fh, '<', $tmp{'config'}{'RT'};
+        local $/;
+        <$fh>
+    });
+
+    my $log_fn = File::Spec->catfile(
+        "$tmp{'directory'}", 'apache.log'
+    );
+    my $pid_fn = File::Spec->catfile(
+        "$tmp{'directory'}", "apache.pid"
+    );
     my $tmpl = File::Spec->rel2abs( File::Spec->catfile(
         't', 'data', 'configs',
         'apache'. $info{'version'} .'+'. $variant .'.conf'
@@ -968,11 +1041,16 @@ sub start_apache_server {
         my $method = 'apache_'.$variant.'_server_options';
         $self->$method( \%info, \%opt );
     }
-    my ($conf_fh, $conf_fn) = $self->process_in_file(
-        in => $tmpl, options => \%opt, out => $tmpl .'.final',
+    $tmp{'config'}{'apache'} = File::Spec->catfile(
+        "$tmp{'directory'}", "apache.conf"
+    );
+    $self->process_in_file(
+        in      => $tmpl, 
+        out     => $tmp{'config'}{'apache'},
+        options => \%opt,
     );
 
-    $self->fork_exec($info{'executable'}, '-f', $conf_fn);
+    $self->fork_exec($info{'executable'}, '-f', $tmp{'config'}{'apache'});
     my $pid = do {
         my $tries = 60;
         while ( !-e $pid_fn ) {
@@ -1149,7 +1227,7 @@ sub process_in_file {
     }
 
     my ($out_fh, $out_conf);
-    if ( $args{'out'} ) {
+    unless ( $args{'out'} ) {
         ($out_fh, $out_conf) = tempfile();
     } else {
         $out_conf = $args{'out'};
@@ -1173,7 +1251,15 @@ END {
 
     RT::Test->stop_server;
 
-    RT::Test->clean_caught_mails;
+    # not success
+    if ( grep !$_, $Test->summary ) {
+        $tmp{'directory'}->unlink_on_destroy(0);
+
+        Test::More::diag(
+            "Some tests failed, tmp directory"
+            ." '$tmp{directory}' is not cleaned"
+        );
+    }
 
     if ( $ENV{RT_TEST_PARALLEL} && $created_new_db ) {
 

commit e59e81974bfe584db371e166f0f59e5949bb40c2
Author: Kevin Falcone <falcone at bestpractical.com>
Date:   Wed Oct 14 16:35:31 2009 -0400

    Fix dependency

diff --git a/sbin/rt-test-dependencies.in b/sbin/rt-test-dependencies.in
index 8a22700..9819108 100755
--- a/sbin/rt-test-dependencies.in
+++ b/sbin/rt-test-dependencies.in
@@ -294,7 +294,7 @@ Test::Warn
 Test::Builder 0.77 # needed to fix TODO test
 IPC::Run3
 Test::MockTime
-Test::HTTP::Server::Simple 0.13
+HTTP::Server::Simple::Mason 0.13
 .
 
 $deps{'FASTCGI'} = [ text_to_hash( << '.') ];

commit 68ff8b6dcf1c93df1488d791eeb05d588f2b175e
Author: Kevin Falcone <falcone at bestpractical.com>
Date:   Wed Oct 14 16:35:31 2009 -0400

    Fix dependency
    (cherry picked from commit e59e81974bfe584db371e166f0f59e5949bb40c2)

diff --git a/sbin/rt-test-dependencies.in b/sbin/rt-test-dependencies.in
index 8a22700..9819108 100755
--- a/sbin/rt-test-dependencies.in
+++ b/sbin/rt-test-dependencies.in
@@ -294,7 +294,7 @@ Test::Warn
 Test::Builder 0.77 # needed to fix TODO test
 IPC::Run3
 Test::MockTime
-Test::HTTP::Server::Simple 0.13
+HTTP::Server::Simple::Mason 0.13
 .
 
 $deps{'FASTCGI'} = [ text_to_hash( << '.') ];

commit c213a434691610808a31702e13d4eb0d1e6f4dc6
Author: Emmanuel Lacour <elacour at easter-eggs.com>
Date:   Wed Oct 14 14:48:20 2009 +0200

    Fix URL used for CF of type autocomplete.
    
    * a double quote was in the middle of the URL making the browser unable to
      retrieve values

diff --git a/share/html/Elements/EditCustomFieldAutocomplete b/share/html/Elements/EditCustomFieldAutocomplete
index 696490a..ba7907b 100644
--- a/share/html/Elements/EditCustomFieldAutocomplete
+++ b/share/html/Elements/EditCustomFieldAutocomplete
@@ -51,7 +51,7 @@
 new Ajax.Autocompleter(
     "<% $name %>-Values",
     "<% $name %>-Choices",
-    <% RT->Config->Get('WebPath')%>"/Helpers/Autocomplete/CustomFieldValues",
+    "<% RT->Config->Get('WebPath')%>/Helpers/Autocomplete/CustomFieldValues",
     { tokens: [ '\n' ] }
 );
 % } else {
@@ -60,7 +60,7 @@ new Ajax.Autocompleter(
 new Ajax.Autocompleter(
     "<% $name %>-Value",
     "<% $name %>-Choices",
-    <% RT->Config->Get('WebPath')%>"/Helpers/Autocomplete/CustomFieldValues",
+    "<% RT->Config->Get('WebPath')%>/Helpers/Autocomplete/CustomFieldValues",
     {}
 );
 % }

commit 0ae31e21e7e2743dade881de22e0917f7d3d64ed
Author: Emmanuel Lacour <elacour at easter-eggs.com>
Date:   Thu Oct 15 16:41:58 2009 +0200

    Fix "bareword" error on search charts when ChartFont isn't defined
    
    * add missing quotes around last font name

diff --git a/share/html/Search/Chart b/share/html/Search/Chart
index 59e9fc6..11e7f35 100644
--- a/share/html/Search/Chart
+++ b/share/html/Search/Chart
@@ -72,7 +72,7 @@ my ($count_name, $value_name) = $tix->SetupGroupings(
 
 my $chart = $chart_class->new( 600 => 400 );
 
-my $font = RT->Config->Get('ChartFont') || ['verdana', 'arial', gdMediumBoldFont];
+my $font = RT->Config->Get('ChartFont') || ['verdana', 'arial', 'gdMediumBoldFont'];
 $chart->set_title_font( $font, 12 ) if $chart->can('set_title_font');
 $chart->set_legend_font( $font, 12 ) if $chart->can('set_legend_font');
 $chart->set_x_label_font( $font, 10 ) if $chart->can('set_x_label_font');

commit 2e3048aa48803009e845a2b81d2132b425e37b7f
Author: Emmanuel Lacour <elacour at easter-eggs.com>
Date:   Thu Oct 15 17:13:18 2009 +0200

    Revert "Fix "bareword" error on search charts when ChartFont isn't defined"
    which was incorrectly discovered on a non standard git tree
    
    This reverts commit 0ae31e21e7e2743dade881de22e0917f7d3d64ed.

diff --git a/share/html/Search/Chart b/share/html/Search/Chart
index 11e7f35..59e9fc6 100644
--- a/share/html/Search/Chart
+++ b/share/html/Search/Chart
@@ -72,7 +72,7 @@ my ($count_name, $value_name) = $tix->SetupGroupings(
 
 my $chart = $chart_class->new( 600 => 400 );
 
-my $font = RT->Config->Get('ChartFont') || ['verdana', 'arial', 'gdMediumBoldFont'];
+my $font = RT->Config->Get('ChartFont') || ['verdana', 'arial', gdMediumBoldFont];
 $chart->set_title_font( $font, 12 ) if $chart->can('set_title_font');
 $chart->set_legend_font( $font, 12 ) if $chart->can('set_legend_font');
 $chart->set_x_label_font( $font, 10 ) if $chart->can('set_x_label_font');

commit 0c6959ab9ad12d4dcd2fc53960ab1c40d6bdc18d
Author: Shawn M Moore <sartak at bestpractical.com>
Date:   Thu Oct 15 13:16:03 2009 -0400

    Add a MassageDashboards callback for the dashboard homepage

diff --git a/share/html/Dashboards/index.html b/share/html/Dashboards/index.html
index 10e77f7..8f71ab0 100644
--- a/share/html/Dashboards/index.html
+++ b/share/html/Dashboards/index.html
@@ -54,18 +54,21 @@
 
 <& /Dashboards/Elements/ShowDashboards,
     Title      => loc('Personal Dashboards'),
-    Dashboards => [sort { $a->Id <=> $b->Id } @{ $dashboards->{personal} || [] }],
+    Dashboards => \@personal_dashboards,
 &>
 
 <& /Dashboards/Elements/ShowDashboards,
     Title      => loc('System Dashboards'),
-    Dashboards => [sort { $a->Id <=> $b->Id } @{ $dashboards->{system} || [] }],
+    Dashboards => \@system_dashboards,
 &>
 
-% for my $group (sort keys %{ $dashboards->{group} || {} }) {
+% for (@grouped_dashboards) {
+%   my $group = $_->{name};
+%   my $dashboards = $_->{dashboards};
+
     <& /Dashboards/Elements/ShowDashboards,
         Title      => loc('[_1] DashBoards', $group),
-        Dashboards => [sort { $a->Id <=> $b->Id } @{ $dashboards->{group}{$group} || [] }],
+        Dashboards => $dashboards,
     &>
 % }
 
@@ -80,6 +83,23 @@ if (defined $Deleted) {
     push @actions, loc("Deleted dashboard [_1]", $Deleted);
 }
 
+my @personal_dashboards = sort { $a->Id <=> $b->Id } @{ $dashboards->{personal} || [] };
+my @system_dashboards = sort { $a->Id <=> $b->Id } @{ $dashboards->{system} || [] };
+
+my @groups = sort keys %{ $dashboards->{group} || {} };
+my @grouped_dashboards = map {
+    {
+        name => $_,
+        dashboards => [ sort { $a->Id <=> $b->Id } @{ $dashboards->{group}{$_} || [] } ],
+    }
+} @groups;
+
+$m->callback(
+    PersonalDashboards => \@personal_dashboards,
+    SystemDashboards   => \@system_dashboards,
+    GroupedDashboards  => \@grouped_dashboards,
+    CallbackName       => 'MassageDashboards',
+);
 </%INIT>
 <%ARGS>
 $Deleted => undef

commit ee3731ffdcc5cdbdd4027ae8821888112f4ba4a0
Author: Shawn M Moore <sartak at bestpractical.com>
Date:   Thu Oct 15 14:32:06 2009 -0400

    Callback for massaging the dashboard tabs on the homepage and dashboards

diff --git a/share/html/Elements/DashboardTabs b/share/html/Elements/DashboardTabs
index e54a7cf..10a3ad8 100644
--- a/share/html/Elements/DashboardTabs
+++ b/share/html/Elements/DashboardTabs
@@ -3,12 +3,19 @@ $CurrentDashboard => undef
 </%args>
 <%init>
 my @dashboards = $m->comp("/Dashboards/Elements/ListOfDashboards");
+my $limit = 7;
+
+$m->callback(
+    Dashboards   => \@dashboards,
+    Limit        => \$limit,
+    CallbackName => 'MassageDashboards',
+);
 
 # limit to a maximum of 7 dashboards
 my $more = 0;
-if (@dashboards > 7) {
+if (@dashboards > $limit) {
     $more = 1;
-    splice @dashboards, 7;
+    splice @dashboards, $limit;
 }
 
 # always include the current dashboard, even if it's not in the initial list
@@ -39,7 +46,6 @@ if ($more) {
     $tabs->{"D-more"} = {
         title => loc('More'),
         path => 'Dashboards/index.html',
-
     }
 }
 

commit e5e0bbec968445a9f4297f274a0b6d4149847784
Author: Shawn M Moore <sartak at bestpractical.com>
Date:   Thu Oct 15 14:35:03 2009 -0400

    Remove dated comment

diff --git a/share/html/Elements/DashboardTabs b/share/html/Elements/DashboardTabs
index 10a3ad8..f9493d5 100644
--- a/share/html/Elements/DashboardTabs
+++ b/share/html/Elements/DashboardTabs
@@ -11,7 +11,6 @@ $m->callback(
     CallbackName => 'MassageDashboards',
 );
 
-# limit to a maximum of 7 dashboards
 my $more = 0;
 if (@dashboards > $limit) {
     $more = 1;

commit fb5ed4f20b1963699fa9ce5c8d5caa11ffcc10d0
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Mon Oct 19 23:05:39 2009 +0400

    refactor validation of transactions CFs on ticket update

diff --git a/share/html/Ticket/Update.html b/share/html/Ticket/Update.html
index c8020fc..d3c2a09 100755
--- a/share/html/Ticket/Update.html
+++ b/share/html/Ticket/Update.html
@@ -234,16 +234,21 @@ $m->comp( '/Elements/GnuPG/SignEncryptWidget:Process',
     TicketObj => $TicketObj,
 );
 
-my $CFs = $TicketObj->TransactionCustomFields();
-my $ValidCFs = undef;
-
 if ( $ARGS{'SubmitTicket'} ) {
-    $ValidCFs = $m->comp(
+    my $CFs = $TicketObj->TransactionCustomFields;
+    my $ValidCFs = $m->comp(
         '/Elements/ValidateCustomFields',
         CustomFields => $CFs,
         NamePrefix => "Object-RT::Transaction--CustomField-",
         ARGSRef => \%ARGS
     );
+    unless ( $ValidCFs ) {
+        $checks_failure = 1;
+        while (my $CF = $CFs->Next) {
+            my $msg = $m->notes('InvalidField-' . $CF->Id) or next;
+            push @results, loc($CF->Name) . ': ' . $msg;
+        }
+    }
     my $status = $m->comp('/Elements/GnuPG/SignEncryptWidget:Check',
         self      => $gnupg_widget,
         TicketObj => $TicketObj,
@@ -251,17 +256,10 @@ if ( $ARGS{'SubmitTicket'} ) {
     $checks_failure = 1 unless $status;
 }
 
-if ( $ValidCFs && !$checks_failure && exists $ARGS{SubmitTicket} ) {
+if ( !$checks_failure && exists $ARGS{SubmitTicket} ) {
     $m->callback( Ticket => $TicketObj, ARGSRef => \%ARGS, CallbackName => 'BeforeDisplay' );
     return $m->comp('Display.html', TicketObj => $TicketObj, %ARGS);
-} elsif ( !$ValidCFs ) {
-    # Invalid CFs
-    while (my $CF = $CFs->Next) {
-        my $msg = $m->notes('InvalidField-' . $CF->Id) or next;
-        push @results, $CF->Name . ': ' . $msg;
-    }
 }
-
 </%INIT>
 
 <%ARGS>

commit 6aeaa1033d2ba1034bcbb908734825fe0a15a36f
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Mon Oct 19 23:17:08 2009 +0400

    * show_customize -> ShowCustomize

diff --git a/share/html/Dashboards/Elements/ShowPortlet/search b/share/html/Dashboards/Elements/ShowPortlet/search
index 53c7b90..eb65cb8 100644
--- a/share/html/Dashboards/Elements/ShowPortlet/search
+++ b/share/html/Dashboards/Elements/ShowPortlet/search
@@ -59,5 +59,5 @@ my @for_showsearch = $Dashboard->ShowSearchName($Portlet);
     @for_showsearch,
     Override       => { Rows => $Rows },
     hideable       => $Preview,
-    show_customize => $Preview,
+    ShowCustomize => $Preview,
 &>
diff --git a/share/html/Elements/ShowSearch b/share/html/Elements/ShowSearch
index 41fd758..a7fc545 100644
--- a/share/html/Elements/ShowSearch
+++ b/share/html/Elements/ShowSearch
@@ -79,7 +79,7 @@ if ($SavedSearch) {
         $query_display_component
             = '/Search/Elements/' . $SearchArg->{SearchType};
         $query_link_url = RT->Config->Get('WebURL') . "/Search/$SearchArg->{SearchType}.html";
-    } elsif ($show_customize) {
+    } elsif ($ShowCustomize) {
         $customize = RT->Config->Get('WebPath') . '/Search/Build.html?'
             . $m->comp( '/Elements/QueryString',
             SavedSearchLoad => $SavedSearch );
@@ -98,7 +98,7 @@ if ($SavedSearch) {
     }
 
     $SearchArg = $user->Preferences( $search, $search->Content );
-    if ($show_customize) {
+    if ($ShowCustomize) {
         $customize = RT->Config->Get('WebPath') . '/Prefs/Search.html?'
             . $m->comp( '/Elements/QueryString',
                 name => ref($search) . '-' . $search->Id );
@@ -147,5 +147,5 @@ $SavedSearch    => undef
 %Override       => ()
 $IgnoreMissing  => undef
 $hideable       => 1
-$show_customize => 1
+$ShowCustomize => 1
 </%ARGS>

commit 6c516bb34adcbff0d8b63598c488db444f899fcc
Author: Kevin Falcone <falcone at bestpractical.com>
Date:   Mon Oct 19 15:55:31 2009 -0400

    bump version for 3.8.6

diff --git a/configure.ac b/configure.ac
index afa4e9c..bf8c4aa 100755
--- a/configure.ac
+++ b/configure.ac
@@ -7,7 +7,7 @@ AC_REVISION($Revision$)dnl
 
 dnl Setup autoconf
 AC_PREREQ([2.53])
-AC_INIT(RT, 3.8.6rc1, [rt-bugs at bestpractical.com])
+AC_INIT(RT, 3.8.6, [rt-bugs at bestpractical.com])
 AC_CONFIG_SRCDIR([lib/RT.pm.in])
 
 dnl Extract RT version number components

commit f9619d3b0def137adff3d63dfec33b2eb727e189
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Tue Oct 20 15:03:40 2009 +0800

    detect browser lang in LocalizedDateTime in Date.pm too

diff --git a/lib/RT/Date.pm b/lib/RT/Date.pm
index e4391e6..18f7ce7 100755
--- a/lib/RT/Date.pm
+++ b/lib/RT/Date.pm
@@ -653,7 +653,12 @@ sub LocalizedDateTime
     my $date_format = $args{'DateFormat'};
     my $time_format = $args{'TimeFormat'};
 
-    my $lang = $self->CurrentUser->UserObj->Lang || 'en';
+    my $lang = $self->CurrentUser->UserObj->Lang;
+    unless ($lang) {
+        require I18N::LangTags::Detect;
+        $lang = ( I18N::LangTags::Detect::detect(), 'en' )[0];
+    }
+    
 
     my $formatter = DateTime::Locale->load($lang);
     $date_format = $formatter->$date_format;

commit 170d9facaabcaa1f5feeac5aa213b54445f86225
Merge: f9619d3 6c516bb
Author: Kevin Falcone <falcone at bestpractical.com>
Date:   Tue Oct 20 10:44:10 2009 -0400

    Merge branch '3.8.6-releng' into 3.8-trunk
    Undo version number bump


commit 22e95676e0467cd0f0b48a98f362d41b9723b9c9
Author: Jesse Vincent <jesse at bestpractical.com>
Date:   Wed Oct 21 11:03:08 2009 -0400

    Add a systemwide plugin directory at the request of the Debian RT maintainers

diff --git a/aclocal.m4 b/aclocal.m4
index a4b5be6..0e041b6 100755
--- a/aclocal.m4
+++ b/aclocal.m4
@@ -116,6 +116,7 @@ AC_DEFUN([RT_LAYOUT],[
 	RT_SUBST_EXPANDED_ARG(datadir)
 	RT_SUBST_EXPANDED_ARG(htmldir)
 	RT_SUBST_EXPANDED_ARG(manualdir)
+	RT_SUBST_EXPANDED_ARG(plugindir)
 	RT_SUBST_EXPANDED_ARG(localstatedir)
 	RT_SUBST_EXPANDED_ARG(logfiledir)
 	RT_SUBST_EXPANDED_ARG(masonstatedir)
diff --git a/config.layout b/config.layout
index 7fe9e05..52fcef1 100755
--- a/config.layout
+++ b/config.layout
@@ -23,6 +23,7 @@
   sbindir:		${exec_prefix}/sbin
   sysconfdir:		${prefix}/etc
   mandir:		${prefix}/man
+  plugindir:		${prefix}/plugins
   libdir:		${prefix}/lib
   datadir:		${prefix}/share
   htmldir:		${datadir}/html
@@ -44,6 +45,7 @@
   sbindir:		${exec_prefix}/sbin
   sysconfdir:		${prefix}/etc
   mandir:		${prefix}/man
+  plugindir:		${prefix}/plugins
   libdir:		${prefix}/lib
   datadir:		${prefix}/share
   htmldir:		${datadir}/html
@@ -70,6 +72,7 @@
   libdir:		${prefix}/lib
   mandir:		${datadir}/man
 # FIXME: no such directory in FHS; shouldn't go to somewhere in "${datadir}/rt/"?
+  plugindir:		${datadir}/plugins
   htmldir:		${datadir}/html
   manualdir:		${datadir}/doc
   localstatedir:	/var
@@ -91,6 +94,7 @@
   sbindir:		${exec_prefix}/sbin
   sysconfdir:		${prefix}/etc+
   mandir:		${prefix}/man
+  plugindir:		${prefix}/plugins
   libdir:		${prefix}/lib+
   datadir:		${prefix}/share+
   htmldir:		${datadir}/html
@@ -113,6 +117,7 @@
   sbindir:		${exec_prefix}/sbin
   sysconfdir:		${prefix}/etc
   mandir:		${prefix}/man
+  plugindir:		${prefix}/plugins
   libdir:		${prefix}/lib
   datadir:		${prefix}
   htmldir:		${datadir}/html
@@ -140,6 +145,7 @@
   datadir:		/var/rt
   htmldir:		${datadir}/html
   manualdir:		${datadir}/doc
+  plugindir:		${datadir}/plugins
   localstatedir:	/var
   logfiledir:		${localstatedir}/log/rt
   masonstatedir:	${localstatedir}/rt/mason_data
@@ -158,6 +164,7 @@
   sbindir:		sbin
   sysconfdir:	etc
   mandir:		man
+  plugindir:		plugins
   libdir:		lib
   datadir:		share
   htmldir:		${datadir}/html
diff --git a/configure.ac b/configure.ac
index e9d9bc7..ae3c0d4 100755
--- a/configure.ac
+++ b/configure.ac
@@ -333,6 +333,7 @@ AC_SUBST([RT_BIN_PATH],			${exp_bindir})
 AC_SUBST([RT_SBIN_PATH],		${exp_sbindir})
 AC_SUBST([RT_VAR_PATH],			${exp_localstatedir})
 AC_SUBST([RT_MAN_PATH],			${exp_mandir})
+AC_SUBST([RT_PLUGIN_PATH],			${exp_plugindir})
 AC_SUBST([MASON_DATA_PATH],		${exp_masonstatedir})
 AC_SUBST([MASON_SESSION_PATH],		${exp_sessionstatedir})
 AC_SUBST([MASON_HTML_PATH],		${exp_htmldir})
@@ -353,6 +354,7 @@ AC_SUBST([RT_BIN_PATH_R],			${exp_prefix}/${exp_bindir})
 AC_SUBST([RT_SBIN_PATH_R],		${exp_prefix}/${exp_sbindir})
 AC_SUBST([RT_VAR_PATH_R],			${exp_prefix}/${exp_localstatedir})
 AC_SUBST([RT_MAN_PATH_R],			${exp_prefix}/${exp_mandir})
+AC_SUBST([RT_PLUGIN_PATH_R],		${exp_prefix}/${exp_plugindir})
 AC_SUBST([MASON_DATA_PATH_R],		${exp_prefix}/${exp_masonstatedir})
 AC_SUBST([MASON_SESSION_PATH_R],		${exp_prefix}/${exp_sessionstatedir})
 AC_SUBST([MASON_HTML_PATH_R],		${exp_prefix}/${exp_htmldir})
@@ -367,6 +369,7 @@ AC_SUBST([RT_DOC_PATH_R],			${exp_manualdir})
 AC_SUBST([RT_LOCAL_PATH_R],		${exp_customdir})
 AC_SUBST([RT_LIB_PATH_R],			${exp_libdir})
 AC_SUBST([RT_ETC_PATH_R],			${exp_sysconfdir})
+AC_SUBST([RT_PLUGIN_PATH_R],		${exp_plugindir})
 AC_SUBST([CONFIG_FILE_PATH_R],		${exp_sysconfdir})
 AC_SUBST([RT_BIN_PATH_R],			${exp_bindir})
 AC_SUBST([RT_SBIN_PATH_R],		${exp_sbindir})
diff --git a/lib/RT.pm.in b/lib/RT.pm.in
index 8112045..fb8afcb 100755
--- a/lib/RT.pm.in
+++ b/lib/RT.pm.in
@@ -66,9 +66,10 @@ our $EtcPath = '@RT_ETC_PATH@';
 our $BinPath = '@RT_BIN_PATH@';
 our $SbinPath = '@RT_SBIN_PATH@';
 our $VarPath = '@RT_VAR_PATH@';
+our $PluginPath = '@RT_PLUGIN_PATH@';
 our $LocalPath = '@RT_LOCAL_PATH@';
 our $LocalEtcPath = '@LOCAL_ETC_PATH@';
-our $LocalLibPath		=	'@LOCAL_LIB_PATH@';
+our $LocalLibPath        =    '@LOCAL_LIB_PATH@';
 our $LocalLexiconPath = '@LOCAL_LEXICON_PATH@';
 our $LocalPluginPath = $LocalPath."/plugins";
 
@@ -108,8 +109,9 @@ unless (  File::Spec->file_name_is_absolute($EtcPath) ) {
     $BasePath = Cwd::realpath( $BasePath );
 
     for my $path ( qw/EtcPath BinPath SbinPath VarPath LocalPath LocalEtcPath
-            LocalLibPath LocalLexiconPath LocalPluginPath MasonComponentRoot
-            MasonLocalComponentRoot MasonDataDir MasonSessionDir/ ) {
+            LocalLibPath LocalLexiconPath PluginPath LocalPluginPath 
+            MasonComponentRoot MasonLocalComponentRoot MasonDataDir 
+            MasonSessionDir/ ) {
         no strict 'refs';
         # just change relative ones
         $$path = File::Spec->catfile( $BasePath, $$path )
diff --git a/lib/RT/Plugin.pm b/lib/RT/Plugin.pm
index 8f016f5..ae2193d 100644
--- a/lib/RT/Plugin.pm
+++ b/lib/RT/Plugin.pm
@@ -106,8 +106,10 @@ sub _BasePath {
     my $self = shift;
     my $base = $self->{'name'};
     $base =~ s/::/-/g;
+    my $local_base = $RT::LocalPluginPath."/".$base;
+    my $base_base = $RT::PluginPath."/".$base;
 
-    return $RT::LocalPluginPath."/".$base;
+    return -d $local_base ? $local_base : $base_base;
 }
 
 =head2 ComponentRoot
diff --git a/m4/rt_layout.m4 b/m4/rt_layout.m4
index c92a108..fbb2890 100755
--- a/m4/rt_layout.m4
+++ b/m4/rt_layout.m4
@@ -27,7 +27,7 @@ AC_DEFUN([RT_LAYOUT],[
 		s/\s+$/\n/gim;
 		s/\+$/\/rt3/gim;
 		# m4 will not let us just use $1, we need @S|@1
-#		s/^((?:bin|sbin|libexec|data|sysconf|sharedstate|localstate|lib|include|oldinclude|info|man)dir)\s*:\s*(.*)$/@S|@1=@S|@2/gim;
+#		s/^((?:bin|sbin|libexec|data|sysconf|sharedstate|localstate|lib|include|oldinclude|plugin|info|man)dir)\s*:\s*(.*)$/@S|@1=@S|@2/gim;
 		# uh, should be [:=], but m4 apparently substitutes something...
 		s/^(.*?)\s*(?::|=)\s*(.*)$/\(test "x\@S|@@S|@1" = "xNONE" || test "x\@S|@@S|@1" = "x") && @S|@1=@S|@2/gim;
 		 ' < $1 > $pldconf
@@ -38,7 +38,7 @@ AC_DEFUN([RT_LAYOUT],[
 			changequote({,})
 			for var in prefix exec_prefix bindir sbindir \
 				 sysconfdir mandir libdir datadir htmldir \
-				 localstatedir logfiledir masonstatedir \
+				 localstatedir logfiledir masonstatedir plugindir \
 				 sessionstatedir customdir custometcdir customhtmldir \
 				 customlexdir customlibdir manualdir; do
 				eval "val=\"\$$var\""
@@ -63,6 +63,7 @@ AC_DEFUN([RT_LAYOUT],[
 	RT_SUBST_EXPANDED_ARG(datadir)
 	RT_SUBST_EXPANDED_ARG(htmldir)
 	RT_SUBST_EXPANDED_ARG(manualdir)
+	RT_SUBST_EXPANDED_ARG(plugindir)
 	RT_SUBST_EXPANDED_ARG(localstatedir)
 	RT_SUBST_EXPANDED_ARG(logfiledir)
 	RT_SUBST_EXPANDED_ARG(masonstatedir)

commit 60491e9698361f7d74129ced60edcefc3b1ab57a
Author: Kevin Falcone <falcone at bestpractical.com>
Date:   Wed Oct 21 15:57:19 2009 -0400

    Fold hardcoded SelfService search format into a config option

diff --git a/etc/RT_Config.pm.in b/etc/RT_Config.pm.in
index 51a63a0..1933c12 100755
--- a/etc/RT_Config.pm.in
+++ b/etc/RT_Config.pm.in
@@ -1209,6 +1209,19 @@ Set ($DefaultSearchResultFormat, qq{
    '<small>__LastUpdatedRelative__</small>',
    '<small>__TimeLeft__</small>'});
 
+=item C<$DefaultSelfServiceSearchResultFormat>
+
+C<$DefaultSelfServiceSearchResultFormat> is the default format of searches displayed in the 
+SelfService interface.
+
+=cut
+
+Set($DefaultSelfServiceSearchResultFormat, qq{
+   '<B><A HREF="__WebPath__/SelfService/Display.html?id=__id__">__id__</a></B>/TITLE:#',
+   '<B><A HREF="__WebPath__/SelfService/Display.html?id=__id__">__Subject__</a></B>/TITLE:Subject',
+   Status,
+   Requestors,
+   OwnerName});
 
 =item C<$SuppressInlineTextFiles>
 
diff --git a/share/html/SelfService/Elements/MyRequests b/share/html/SelfService/Elements/MyRequests
index b3320ed..9eed22b 100755
--- a/share/html/SelfService/Elements/MyRequests
+++ b/share/html/SelfService/Elements/MyRequests
@@ -69,12 +69,7 @@ if ( @status ) {
         . join( ' OR ', map "Status = '$_'", @status )
         . " )";
 }
-my $Format = qq{
-   '<B><A HREF="}. RT->Config->Get('WebPath') .qq{/SelfService/Display.html?id=__id__">__id__</a></B>/TITLE:#',
-   '<B><A HREF="}. RT->Config->Get('WebPath') .qq{/SelfService/Display.html?id=__id__">__Subject__</a></B>/TITLE:Subject',
-   Status,
-   Requestors,
-   OwnerName};
+my $Format = RT->Config->Get('DefaultSelfServiceSearchResultFormat');
 
 </%INIT>
 <%ARGS>

commit 37683bce2e2b5e5d72a68f5cc6767435556958e5
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Fri Oct 23 15:56:36 2009 +0800

    added t/web/ticket_update_without_content.t

diff --git a/t/web/ticket_update_without_content.t b/t/web/ticket_update_without_content.t
new file mode 100644
index 0000000..1ffa896
--- /dev/null
+++ b/t/web/ticket_update_without_content.t
@@ -0,0 +1,54 @@
+#!/usr/bin/env perl
+use strict;
+use warnings;
+
+use RT::Test tests => 10;
+my ( $url, $m ) = RT::Test->started_ok;
+
+# merged tickets still show up in search
+my $ticket = RT::Ticket->new($RT::SystemUser);
+my ( $ret, $msg ) = $ticket->Create(
+    Subject   => 'base ticket' . $$,
+    Queue     => 'general',
+    Owner     => 'root',
+    Requestor => 'root at localhost',
+    MIMEObj   => MIME::Entity->build(
+        From    => 'root at localhost',
+        To      => 'rt at localhost',
+        Subject => 'base ticket' . $$,
+        Data    => "",
+    ),
+);
+ok( $ret, "ticket created: $msg" );
+
+ok( $m->login, 'logged in' );
+
+$m->get( $url . "/Ticket/ModifyAll.html?id=" . $ticket->id );
+is( $m->{'status'}, 200, "Loaded ModifyAll.html" );
+
+$m->submit_form(
+    form_number => 3,
+    fields      => { Priority => '1', }
+);
+
+$m->content_like(qr/priority changed/i);
+$m->content_unlike(qr/message recorded/i);
+
+my $root = RT::User->new( $RT::SystemUser );
+$root->Load('root');
+( $ret, $msg ) = $root->SetSignature(<<EOF);
+best wishes
+foo
+EOF
+
+ok( $ret, $msg );
+
+$m->get( $url . "/Ticket/ModifyAll.html?id=" . $ticket->id );
+is( $m->{'status'}, 200, "Loaded ModifyAll.html" );
+
+$m->submit_form(
+    form_number => 3,
+    fields      => { Priority => '2', }
+);
+$m->content_like(qr/priority changed/i);
+$m->content_unlike(qr/message recorded/i);

commit e150432be7c5a6bd7b6bad8820f773b4e6e0d6e4
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Fri Oct 23 16:17:05 2009 +0800

    refactor StripContent: make it return empty string as long as the content does *not* have any *real* data, i.e. \S but without <br/> and &nbsp;

diff --git a/lib/RT/Interface/Web.pm b/lib/RT/Interface/Web.pm
index 549d24c..07f3358 100755
--- a/lib/RT/Interface/Web.pm
+++ b/lib/RT/Interface/Web.pm
@@ -595,37 +595,48 @@ sub SendStaticFile {
 sub StripContent {
     my %args    = @_;
     my $content = $args{Content};
-    my $html    = ( ( $args{ContentType} || '' ) eq "text/html" );
+    return '' unless $content;
+
+    # Make the content have no 'weird' newlines in it
+    $content =~ s/\r+\n/\n/g;
+
+    my $return_content = $content;
+
+    my $html = $args{ContentType} && $args{ContentType} eq "text/html";
     my $sigonly = $args{StripSignature};
 
     # Save us from undef warnings
     return '' unless defined $content;
 
-    # Make the content have no 'weird' newlines in it
-    $content =~ s/\r+\n/\n/g;
+    # massage content to easily detect if there's any real content
+    $content =~ s/\s+//g; # yes! remove all the spaces
+    if ( $html ) {
+        # remove html version of spaces and newlines
+        $content =~ s!&nbsp;!!g;
+        $content =~ s!<br/?>!!g;
+    }
 
     # Filter empty content when type is text/html
-    return '' if $html && $content =~ m{^\s*(?:<br[^>]*/?>)*\s*$}s;
+    return '' if $html && $content !~ /\S/;
 
     # If we aren't supposed to strip the sig, just bail now.
-    return $content unless $sigonly;
+    return $return_content unless $sigonly;
 
     # Find the signature
     my $sig = $args{'CurrentUser'}->UserObj->Signature || '';
-    $sig =~ s/^\s+//;
-    $sig =~ s/\s+$//;
+    $sig =~ s/\s+//g;
 
     # Check for plaintext sig
-    return '' if not $html and $content =~ /^\s*(--)?\s*\Q$sig\E\s*$/;
+    return '' if not $html and $content =~ /^(--)?\Q$sig\E$/;
 
     # Check for html-formatted sig
     RT::Interface::Web::EscapeUTF8( \$sig );
     return ''
-        if $html
-            and $content =~ m{^\s*(?:<p>)?\s*(--)?\s*<br[^>]*?/?>\s*\Q$sig\E\s*(?:</p>)?\s*$}s;
+      if $html
+          and $content =~ m{^(?:<p>)?(--)?\Q$sig\E(?:</p>)?$}s;
 
     # Pass it through
-    return $content;
+    return $return_content;
 }
 
 sub DecodeARGS {

commit e05230b35cc4ef99e5f869d1b14a1ecccc7acb5f
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Fri Oct 23 20:46:17 2009 +0800

    add monospace font to .plain-text-white-space: .mono is merged to it

diff --git a/share/html/NoAuth/css/web2/ticket.css b/share/html/NoAuth/css/web2/ticket.css
index f60576e..78477e0 100644
--- a/share/html/NoAuth/css/web2/ticket.css
+++ b/share/html/NoAuth/css/web2/ticket.css
@@ -120,6 +120,7 @@ div#ticket-history div.content {
 
 .plain-text-white-space {
  white-space: pre-wrap;
+ font-family: monospace;
 }
 
 .ticket-transaction .messagebody {

commit 9f1f17b61421d643f477232149fab33fb456c2c0
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Fri Oct 23 21:08:48 2009 +0800

    remove needless lines

diff --git a/lib/RT/Interface/Web.pm b/lib/RT/Interface/Web.pm
index 07f3358..b82b638 100755
--- a/lib/RT/Interface/Web.pm
+++ b/lib/RT/Interface/Web.pm
@@ -605,9 +605,6 @@ sub StripContent {
     my $html = $args{ContentType} && $args{ContentType} eq "text/html";
     my $sigonly = $args{StripSignature};
 
-    # Save us from undef warnings
-    return '' unless defined $content;
-
     # massage content to easily detect if there's any real content
     $content =~ s/\s+//g; # yes! remove all the spaces
     if ( $html ) {

commit c355c7e57b4c79daacc3dde25f0490d6ba3bdad3
Author: Emmanuel Lacour <elacour at easter-eggs.com>
Date:   Fri Oct 23 17:41:49 2009 +0200

    Fix unconsistent component name
    
        Component Search/Elements/SelectChartType were used to select (then save)
        ChartStyle, so rename it to SelectChartStyle

diff --git a/share/html/Search/Chart.html b/share/html/Search/Chart.html
index 70e54c0..1e8cc95 100644
--- a/share/html/Search/Chart.html
+++ b/share/html/Search/Chart.html
@@ -88,7 +88,7 @@ my @actions = $m->comp( '/Widgets/SavedSearch:process', args => \%ARGS, self =>
 <&| /Widgets/TitleBox, title => loc('Graph Properties')&>
 <form method="get" action="<%RT->Config->Get('WebPath')%>/Search/Chart.html">
 <input type="hidden" class="hidden" name="Query" value="<% $Query %>" />
-<&|/l, $m->scomp('Elements/SelectChartType', Name => 'ChartStyle', Default => $ChartStyle), $m->scomp('Elements/SelectGroupBy', Name => 'PrimaryGroupBy', Query => $Query, Default => $PrimaryGroupBy) 
+<&|/l, $m->scomp('Elements/SelectChartStyle', Name => 'ChartStyle', Default => $ChartStyle), $m->scomp('Elements/SelectGroupBy', Name => 'PrimaryGroupBy', Query => $Query, Default => $PrimaryGroupBy) 
 &>[_1] chart by [_2]</&><input type="submit" class="button" value="<%loc('Update Graph')%>" />
 </form>
 </&>
diff --git a/share/html/Search/Elements/ResultViews b/share/html/Search/Elements/ResultViews
index b438b56..784d50d 100644
--- a/share/html/Search/Elements/ResultViews
+++ b/share/html/Search/Elements/ResultViews
@@ -69,7 +69,7 @@ $ShortQueryString => undef
 % foreach my $key (keys(%hiddens)) {
 <input type="hidden" class="hidden" name="<%$key%>" value="<%defined($hiddens{$key})?$hiddens{$key}:''%>" />
 % }
-<&|/l, $m->scomp('SelectChartType', Name => 'ChartStyle'), $m->scomp('SelectGroupBy', Name => 'PrimaryGroupBy', Query => $Query) 
+<&|/l, $m->scomp('SelectChartStyle', Name => 'ChartStyle'), $m->scomp('SelectGroupBy', Name => 'PrimaryGroupBy', Query => $Query) 
 &>[_1] chart by [_2]</&><input type="submit" class="button" value="<%loc('Go')%>" />
 </form>
 <%init>
diff --git a/share/html/Search/Elements/SelectChartType b/share/html/Search/Elements/SelectChartStyle
similarity index 99%
rename from share/html/Search/Elements/SelectChartType
rename to share/html/Search/Elements/SelectChartStyle
index fcb7234..7158983 100644
--- a/share/html/Search/Elements/SelectChartType
+++ b/share/html/Search/Elements/SelectChartStyle
@@ -46,7 +46,7 @@
 %# 
 %# END BPS TAGGED BLOCK }}}
 <%args>
-$Name => 'ChartType'
+$Name => 'ChartStyle'
 $Default => 'bar'
 </%args>
 <select id="<%$Name%>" name="<%$Name%>">

commit 5aff830963de0f92b117cae79b3714c50a428b5c
Author: Emmanuel Lacour <elacour at easter-eggs.com>
Date:   Fri Oct 23 18:13:18 2009 +0200

    Revert "Fix unconsistent component name"
    
    This reverts commit c355c7e57b4c79daacc3dde25f0490d6ba3bdad3.
    
    * renaming components is not a good practice during a stable release

diff --git a/share/html/Search/Chart.html b/share/html/Search/Chart.html
index 1e8cc95..70e54c0 100644
--- a/share/html/Search/Chart.html
+++ b/share/html/Search/Chart.html
@@ -88,7 +88,7 @@ my @actions = $m->comp( '/Widgets/SavedSearch:process', args => \%ARGS, self =>
 <&| /Widgets/TitleBox, title => loc('Graph Properties')&>
 <form method="get" action="<%RT->Config->Get('WebPath')%>/Search/Chart.html">
 <input type="hidden" class="hidden" name="Query" value="<% $Query %>" />
-<&|/l, $m->scomp('Elements/SelectChartStyle', Name => 'ChartStyle', Default => $ChartStyle), $m->scomp('Elements/SelectGroupBy', Name => 'PrimaryGroupBy', Query => $Query, Default => $PrimaryGroupBy) 
+<&|/l, $m->scomp('Elements/SelectChartType', Name => 'ChartStyle', Default => $ChartStyle), $m->scomp('Elements/SelectGroupBy', Name => 'PrimaryGroupBy', Query => $Query, Default => $PrimaryGroupBy) 
 &>[_1] chart by [_2]</&><input type="submit" class="button" value="<%loc('Update Graph')%>" />
 </form>
 </&>
diff --git a/share/html/Search/Elements/ResultViews b/share/html/Search/Elements/ResultViews
index 784d50d..b438b56 100644
--- a/share/html/Search/Elements/ResultViews
+++ b/share/html/Search/Elements/ResultViews
@@ -69,7 +69,7 @@ $ShortQueryString => undef
 % foreach my $key (keys(%hiddens)) {
 <input type="hidden" class="hidden" name="<%$key%>" value="<%defined($hiddens{$key})?$hiddens{$key}:''%>" />
 % }
-<&|/l, $m->scomp('SelectChartStyle', Name => 'ChartStyle'), $m->scomp('SelectGroupBy', Name => 'PrimaryGroupBy', Query => $Query) 
+<&|/l, $m->scomp('SelectChartType', Name => 'ChartStyle'), $m->scomp('SelectGroupBy', Name => 'PrimaryGroupBy', Query => $Query) 
 &>[_1] chart by [_2]</&><input type="submit" class="button" value="<%loc('Go')%>" />
 </form>
 <%init>
diff --git a/share/html/Search/Elements/SelectChartStyle b/share/html/Search/Elements/SelectChartType
similarity index 99%
rename from share/html/Search/Elements/SelectChartStyle
rename to share/html/Search/Elements/SelectChartType
index 7158983..fcb7234 100644
--- a/share/html/Search/Elements/SelectChartStyle
+++ b/share/html/Search/Elements/SelectChartType
@@ -46,7 +46,7 @@
 %# 
 %# END BPS TAGGED BLOCK }}}
 <%args>
-$Name => 'ChartStyle'
+$Name => 'ChartType'
 $Default => 'bar'
 </%args>
 <select id="<%$Name%>" name="<%$Name%>">

commit bdebe4a4a5a479fe0afd805c14857a7d120c6913
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Sat Oct 24 12:10:55 2009 +0800

    url path fix in /Admin/Elements/ToolTabs

diff --git a/share/html/Admin/Elements/ToolTabs b/share/html/Admin/Elements/ToolTabs
index 3231208..d8be841 100755
--- a/share/html/Admin/Elements/ToolTabs
+++ b/share/html/Admin/Elements/ToolTabs
@@ -58,7 +58,7 @@
                path => 'Admin/Tools/Configuration.html',
         },
         E => { title => loc('Shredder'),
-               path  => 'Admin/Tools/Shredder',
+               path  => 'Admin/Tools/Shredder/',
         },
     };
 

commit 8875af6ef0f78d1ac983506f340c1e218758f498
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Mon Oct 26 12:27:28 2009 +0800

    comment one confusing code

diff --git a/share/html/REST/1.0/Forms/ticket/default b/share/html/REST/1.0/Forms/ticket/default
index ce15423..9654592 100755
--- a/share/html/REST/1.0/Forms/ticket/default
+++ b/share/html/REST/1.0/Forms/ticket/default
@@ -431,7 +431,11 @@ else {
             $e = 1;
             push @comments, "# $key: $s";
             unless (@$o) {
+                # XXX by sunnavy
+                # the following line is probably wrong, maybe
+                # my %o = %$changes;  ???
                 my %o = keys %$changes;
+
                 delete $o{id};
                 @$o = ("id", keys %o);
                 $k = $changes;

commit 3d974f472ede8eee5b5225204d6b0bd9fc28c073
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Mon Oct 26 14:06:31 2009 +0800

    update Query str in Tabs in Chart.html

diff --git a/share/html/Search/Chart.html b/share/html/Search/Chart.html
index 70e54c0..4b3785e 100644
--- a/share/html/Search/Chart.html
+++ b/share/html/Search/Chart.html
@@ -80,7 +80,7 @@ my @actions = $m->comp( '/Widgets/SavedSearch:process', args => \%ARGS, self =>
 
 </%init>
 <& /Elements/Header, Title => $title &>
-<& /Ticket/Elements/Tabs, Title => $title &>
+<& /Ticket/Elements/Tabs, Title => $title, Query => $ARGS{Query}, &>
 <& /Elements/ListActions, actions => \@actions &>
 <& /Search/Elements/Chart, %ARGS &>
 

commit ff2224d68054e3e0e1d5383992a7b3dc8094c5bb
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Mon Oct 26 14:49:49 2009 +0800

    do *not* show the old saved search in /Search/Build.html if one loaded a saved search in /Search/Chart.html

diff --git a/share/html/Widgets/SavedSearch b/share/html/Widgets/SavedSearch
index 385351c..784cf65 100644
--- a/share/html/Widgets/SavedSearch
+++ b/share/html/Widgets/SavedSearch
@@ -68,6 +68,14 @@ if ( my ( $container_object, $search_id ) = _parse_saved_search( $args->{'SavedS
     $self->{SearchId} = $args->{'SavedSearchLoad'};
     $self->{CurrentSearch}{Object} = $search;
     $args->{$_} = $search->SubValue($_) for @{ $self->{SearchFields} };
+# saved search in /Search/Chart.html is different from /Search/Build.html
+# the former is of type 'Chart', while the latter is of type 'Ticket'.
+# After loading a saved search from the former after loading one from the
+# latter, accessing /Search/Build.html will still show the old one, so we
+# need to delete $session{CurrentSearchHash} to let it not show the old one.
+# of course, the new one should not be shown there either because it's of
+# different type
+    delete $session{'CurrentSearchHash'};
 }
 
 # look for the current one in the available saved searches

commit b58cd4fe84a350a56c1808d5181a46ea48390d5d
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Mon Oct 26 16:53:09 2009 +0800

    use $ARGS{Query} instead of $Query because we may change $ARGS{Query} later in /Search/Chart.html

diff --git a/share/html/Search/Chart.html b/share/html/Search/Chart.html
index 4b3785e..a3f8e9b 100644
--- a/share/html/Search/Chart.html
+++ b/share/html/Search/Chart.html
@@ -46,7 +46,6 @@
 %# 
 %# END BPS TAGGED BLOCK }}}
 <%args>
-$Query => "id > 0"
 $PrimaryGroupBy => 'Queue'
 $SecondaryGroupBy => ''
 $ChartStyle => 'bars'
@@ -54,6 +53,7 @@ $Description => undef
 </%args>
 <%init>
 $ARGS{SecondaryGroupBy} ||= '';
+$ARGS{Query} ||= 'id > 0';
 
 # FIXME: should be factored with RT::Report::Tickets::Label :(
 my $PrimaryGroupByLabel;
@@ -87,8 +87,8 @@ my @actions = $m->comp( '/Widgets/SavedSearch:process', args => \%ARGS, self =>
 <br />
 <&| /Widgets/TitleBox, title => loc('Graph Properties')&>
 <form method="get" action="<%RT->Config->Get('WebPath')%>/Search/Chart.html">
-<input type="hidden" class="hidden" name="Query" value="<% $Query %>" />
-<&|/l, $m->scomp('Elements/SelectChartType', Name => 'ChartStyle', Default => $ChartStyle), $m->scomp('Elements/SelectGroupBy', Name => 'PrimaryGroupBy', Query => $Query, Default => $PrimaryGroupBy) 
+<input type="hidden" class="hidden" name="Query" value="<% $ARGS{Query} %>" />
+<&|/l, $m->scomp('Elements/SelectChartType', Name => 'ChartStyle', Default => $ChartStyle), $m->scomp('Elements/SelectGroupBy', Name => 'PrimaryGroupBy', Query => $ARGS{Query}, Default => $PrimaryGroupBy) 
 &>[_1] chart by [_2]</&><input type="submit" class="button" value="<%loc('Update Graph')%>" />
 </form>
 </&>

commit 1a8fd23ee6c068f9d7f949ada4c28be31e1b8dfe
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Mon Oct 26 22:28:47 2009 +0800

    add t/web/saved_search_chart.t

diff --git a/t/web/saved_search_chart.t b/t/web/saved_search_chart.t
new file mode 100644
index 0000000..5fe88e8
--- /dev/null
+++ b/t/web/saved_search_chart.t
@@ -0,0 +1,54 @@
+#!/usr/bin/env perl
+use strict;
+use warnings;
+
+use RT::Test tests => 9;
+my ( $url, $m ) = RT::Test->started_ok;
+
+# merged tickets still show up in search
+my $ticket = RT::Ticket->new($RT::SystemUser);
+my ( $ret, $msg ) = $ticket->Create(
+    Subject   => 'base ticket' . $$,
+    Queue     => 'general',
+    Owner     => 'root',
+    Requestor => 'root at localhost',
+    MIMEObj   => MIME::Entity->build(
+        From    => 'root at localhost',
+        To      => 'rt at localhost',
+        Subject => 'base ticket' . $$,
+        Data    => "",
+    ),
+);
+ok( $ret, "ticket created: $msg" );
+
+ok( $m->login, 'logged in' );
+
+$m->get( $url . "/Search/Chart.html?Query=" . 'id=1' );
+is( $m->{'status'}, 200, "Loaded /Search/Chart.html" );
+my ($owner) = $m->content =~ /value="(RT::User-\d+)"/;
+
+$m->submit_form(
+    form_name => 'SaveSearch',
+    fields    => {
+        SavedSearchDescription => 'first chart',
+        SavedSearchOwner       => $owner,
+    },
+    button => 'SavedSearchSave',
+);
+
+$m->content_like(qr/Chart first chart saved/);
+
+my ($search) = $m->content =~ /value="(RT::User-\d+-SavedSearch-\d+)"/;
+$m->submit_form(
+    form_name => 'SaveSearch',
+    fields      => { SavedSearchLoad => $search },
+);
+
+$m->content_like(qr/name="SavedSearchDelete"\s+value="Delete"/);
+TODO: {
+    local $TODO = 'add Saved Chart Search fully update support';
+    $m->content_like(qr/name="SavedSearchDescription"\s+value="first chart"/);
+    $m->content_like(qr/name="SavedSearchSave"\s+value="Update"/);
+    $m->content_unlike(qr/name="SavedSearchSave"\s+value="Save"/);
+}
+

commit 8ec9c13c06758f77b38e86edb1c8f3f9ab24907c
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Tue Oct 27 09:03:40 2009 +0800

    add t/web/command_line_with_unknown_field

diff --git a/t/web/command_line_with_unknown_field.t b/t/web/command_line_with_unknown_field.t
new file mode 100644
index 0000000..9a7ec7a
--- /dev/null
+++ b/t/web/command_line_with_unknown_field.t
@@ -0,0 +1,34 @@
+#!/usr/bin/perl -w
+
+use strict;
+use File::Spec ();
+use Test::Expect;
+use RT::Test tests => 10;
+my ($baseurl, $m) = RT::Test->started_ok;
+my $rt_tool_path = "$RT::BinPath/rt";
+
+$ENV{'RTUSER'} = 'root';
+$ENV{'RTPASSWD'} = 'password';
+$RT::Logger->debug("Connecting to server at ".RT->Config->Get('WebBaseURL'));
+$ENV{'RTSERVER'} =RT->Config->Get('WebBaseURL') ;
+$ENV{'RTDEBUG'} = '1';
+
+expect_run(
+    command => "$rt_tool_path shell",
+    prompt => 'rt> ',
+    quit => 'quit',
+);
+expect_send(q{create -t ticket set subject='new ticket' add cc=foo at example.com}, "Creating a ticket...");
+expect_like(qr/Ticket \d+ created/, "Created the ticket");
+expect_handle->before() =~ /Ticket (\d+) created/;
+my $ticket_id = $1;
+
+expect_send("edit ticket/$ticket_id set marge=simpson", 'set unknown field');
+expect_like(qr/marge: Unknown field/, 'marge is unknown field');
+expect_like(qr/marge: simpson/, 'the value we set for marge is shown too');
+
+expect_send("edit ticket/$ticket_id set homer=simpson", 'set unknown field');
+expect_like(qr/homer: Unknown field/, 'homer is unknown field');
+expect_like(qr/homer: simpson/, 'the value we set for homer is shown too');
+
+expect_quit();

commit 10dbd767bb098cd4ff0b9ab55151f8e82137db44
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Tue Oct 27 09:08:52 2009 +0800

    my %o = keys %$changes; is indeed wrong

diff --git a/share/html/REST/1.0/Forms/ticket/default b/share/html/REST/1.0/Forms/ticket/default
index 9654592..2af68f8 100755
--- a/share/html/REST/1.0/Forms/ticket/default
+++ b/share/html/REST/1.0/Forms/ticket/default
@@ -431,11 +431,7 @@ else {
             $e = 1;
             push @comments, "# $key: $s";
             unless (@$o) {
-                # XXX by sunnavy
-                # the following line is probably wrong, maybe
-                # my %o = %$changes;  ???
-                my %o = keys %$changes;
-
+                my %o = %$changes;
                 delete $o{id};
                 @$o = ("id", keys %o);
                 $k = $changes;

commit d58e655c8d39bddef64c68f8c4154b9018cc12e1
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Tue Oct 27 09:27:25 2009 +0800

    refactor a little

diff --git a/share/html/REST/1.0/Forms/ticket/default b/share/html/REST/1.0/Forms/ticket/default
index 2af68f8..b82cb5e 100755
--- a/share/html/REST/1.0/Forms/ticket/default
+++ b/share/html/REST/1.0/Forms/ticket/default
@@ -431,9 +431,8 @@ else {
             $e = 1;
             push @comments, "# $key: $s";
             unless (@$o) {
-                my %o = %$changes;
-                delete $o{id};
-                @$o = ("id", keys %o);
+                # move id forward
+                @$o = ("id", grep { $_ ne 'id' } keys %$changes);
                 $k = $changes;
             }
         }

commit f79f002f5678de9ef998aeb2bc247317c1583f1b
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Tue Oct 27 13:16:30 2009 +0800

    make sure $PrimaryGroupBy is not undef in /Search/Elements/Chart

diff --git a/share/html/Search/Elements/Chart b/share/html/Search/Elements/Chart
index 4f8c1e1..3db92c4 100644
--- a/share/html/Search/Elements/Chart
+++ b/share/html/Search/Elements/Chart
@@ -53,6 +53,8 @@ $ChartStyle => 'bars'
 </%args>
 <%init>
 use RT::Report::Tickets;
+$PrimaryGroupBy ||= 'Queue'; # make sure PrimaryGroupBy is not undef
+
 my $tix = RT::Report::Tickets->new( $session{'CurrentUser'} );
 my ($count_name, $value_name) = $tix->SetupGroupings(
     Query => $Query, GroupBy => $PrimaryGroupBy,

commit e1a7593cca1c5662ad4dbe052323d5054d00f9ac
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Tue Oct 27 14:59:15 2009 +0800

    more saved chart search tests

diff --git a/t/web/saved_search_chart.t b/t/web/saved_search_chart.t
index 5fe88e8..2eef31c 100644
--- a/t/web/saved_search_chart.t
+++ b/t/web/saved_search_chart.t
@@ -2,10 +2,9 @@
 use strict;
 use warnings;
 
-use RT::Test tests => 9;
+use RT::Test tests => 12;
 my ( $url, $m ) = RT::Test->started_ok;
 
-# merged tickets still show up in search
 my $ticket = RT::Ticket->new($RT::SystemUser);
 my ( $ret, $msg ) = $ticket->Create(
     Subject   => 'base ticket' . $$,
@@ -36,19 +35,37 @@ $m->submit_form(
     button => 'SavedSearchSave',
 );
 
-$m->content_like(qr/Chart first chart saved/);
+$m->content_like( qr/Chart first chart saved/, 'saved first chart' );
 
 my ($search) = $m->content =~ /value="(RT::User-\d+-SavedSearch-\d+)"/;
 $m->submit_form(
     form_name => 'SaveSearch',
-    fields      => { SavedSearchLoad => $search },
+    fields    => { SavedSearchLoad => $search },
 );
 
-$m->content_like(qr/name="SavedSearchDelete"\s+value="Delete"/);
-TODO: {
-    local $TODO = 'add Saved Chart Search fully update support';
-    $m->content_like(qr/name="SavedSearchDescription"\s+value="first chart"/);
-    $m->content_like(qr/name="SavedSearchSave"\s+value="Update"/);
-    $m->content_unlike(qr/name="SavedSearchSave"\s+value="Save"/);
-}
+$m->content_like( qr/name="SavedSearchDelete"\s+value="Delete"/,
+    'found Delete button' );
+$m->content_like(
+    qr/name="SavedSearchDescription"\s+value="first chart"/,
+    'found Description input with the value filled'
+);
+$m->content_like( qr/name="SavedSearchSave"\s+value="Update"/,
+    'found Update button' );
+$m->content_unlike( qr/name="SavedSearchSave"\s+value="Save"/,
+    'no Save button' );
+
+$m->submit_form(
+    form_name => 'SaveSearch',
+    fields    => { Query => 'id=2' },
+    button    => 'SavedSearchSave',
+);
 
+$m->content_like( qr/Chart first chart updated/, 'found updated message' );
+
+$m->submit_form(
+    form_name => 'SaveSearch',
+    button    => 'SavedSearchDelete',
+);
+$m->content_like(qr/Chart first chart deleted/, 'found deleted message');
+$m->content_unlike( qr/value="RT::User-\d+-SavedSearch-\d+"/,
+    'no saved search' );

commit e8c391f7a4119cee3176956fe3d2f7f11599b874
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Tue Oct 27 15:20:17 2009 +0800

    make people can update saved chart search easily

diff --git a/share/html/Search/Build.html b/share/html/Search/Build.html
index 06edfdd..e2c13ff 100644
--- a/share/html/Search/Build.html
+++ b/share/html/Search/Build.html
@@ -71,10 +71,12 @@
     Title => $title,
     %query,
     SavedSearchId => $saved_search{'Id'},
+    SavedChartSearchId => $ARGS{SavedChartSearchId},
 &>
 
 <form method="post" action="Build.html" name="BuildQuery">
 <input type="hidden" class="hidden" name="SavedSearchId" value="<% $saved_search{'Id'} %>" />
+<input type="hidden" class="hidden" name="SavedChartSearchId" value="<% $ARGS{'SavedChartSearchId'} %>" />
 <input type="hidden" class="hidden" name="Query" value="<% $query{'Query'} %>" />
 <input type="hidden" class="hidden" name="Format" value="<% $query{'Format'} %>" />
 
@@ -114,6 +116,8 @@
 use RT::Interface::Web::QueryBuilder;
 use RT::Interface::Web::QueryBuilder::Tree;
 
+$ARGS{SavedChartSearchId} ||= 'new';
+
 my $title = loc("Query Builder");
 
 my %query;
@@ -291,7 +295,7 @@ $session{'CurrentSearchHash'} = {
 # {{{ Show the results, if we were asked.
 
 if ( $ARGS{'DoSearch'} ) {
-    $m->comp( 'Results.html', %query );
+    $m->comp( 'Results.html', %query, SavedChartSearchId => $ARGS{'SavedChartSearchId'}, );
     $m->comp( '/Elements/Footer' );
     $m->abort;
 }
diff --git a/share/html/Search/Bulk.html b/share/html/Search/Bulk.html
index e11bdba..a0638c1 100755
--- a/share/html/Search/Bulk.html
+++ b/share/html/Search/Bulk.html
@@ -54,11 +54,13 @@
     Rows => $Rows,
     OrderBy => $OrderBy,
     Order => $Order,
-    SavedSearchId => $SavedSearchId &>
+    SavedSearchId => $SavedSearchId,
+    SavedChartSearchId => $SavedChartSearchId,
+    &>
 
 <& /Elements/ListActions, actions => \@results &>
 <form method="post" action="<% RT->Config->Get('WebPath') %>/Search/Bulk.html" enctype="multipart/form-data">
-% foreach my $var qw(Query Format OrderBy Order Rows Page) {
+% foreach my $var qw(Query Format OrderBy Order Rows Page SavedChartSearchId) {
 <input type="hidden" class="hidden" name="<%$var%>" value="<%$ARGS{$var} || ''%>" />
 %}
 <& /Elements/CollectionList, 
@@ -440,4 +442,5 @@ $Order => 'ASC'
 $OrderBy => 'id'
 $Query => undef
 $SavedSearchId => undef
+$SavedChartSearchId => undef
 </%args>
diff --git a/share/html/Search/Chart.html b/share/html/Search/Chart.html
index a3f8e9b..fb9f45c 100644
--- a/share/html/Search/Chart.html
+++ b/share/html/Search/Chart.html
@@ -80,7 +80,8 @@ my @actions = $m->comp( '/Widgets/SavedSearch:process', args => \%ARGS, self =>
 
 </%init>
 <& /Elements/Header, Title => $title &>
-<& /Ticket/Elements/Tabs, Title => $title, Query => $ARGS{Query}, &>
+<& /Ticket/Elements/Tabs, Title => $title, Query => $ARGS{Query},
+    SavedChartSearchId => $saved_search->{SearchId} &>
 <& /Elements/ListActions, actions => \@actions &>
 <& /Search/Elements/Chart, %ARGS &>
 
@@ -88,6 +89,7 @@ my @actions = $m->comp( '/Widgets/SavedSearch:process', args => \%ARGS, self =>
 <&| /Widgets/TitleBox, title => loc('Graph Properties')&>
 <form method="get" action="<%RT->Config->Get('WebPath')%>/Search/Chart.html">
 <input type="hidden" class="hidden" name="Query" value="<% $ARGS{Query} %>" />
+<input type="hidden" class="hidden" name="SavedChartSearchId" value="<% $saved_search->{SearchId} || 'new' %>" />
 <&|/l, $m->scomp('Elements/SelectChartType', Name => 'ChartStyle', Default => $ChartStyle), $m->scomp('Elements/SelectGroupBy', Name => 'PrimaryGroupBy', Query => $ARGS{Query}, Default => $PrimaryGroupBy) 
 &>[_1] chart by [_2]</&><input type="submit" class="button" value="<%loc('Update Graph')%>" />
 </form>
diff --git a/share/html/Search/Edit.html b/share/html/Search/Edit.html
index eb6d610..e3e2da2 100755
--- a/share/html/Search/Edit.html
+++ b/share/html/Search/Edit.html
@@ -55,12 +55,14 @@
     OrderBy => $OrderBy,
     Order   => $Order,
     SavedSearchId => $SavedSearchId,
+    SavedChartSearchId => $SavedChartSearchId,
 &>
 
 <& Elements/NewListActions, actions => \@actions &>
 
 <form method="post" action="Build.html">
 <input type="hidden" class="hidden" name="SavedSearchId" value="<% $SavedSearchId %>" />
+<input type="hidden" class="hidden" name="SavedChartSearchId" value="<% $SavedChartSearchId %>" />
 <&|/Widgets/TitleBox, title => loc('Query'), &>
 <textarea name="Query" rows="8" cols="72"><% $Query %></textarea>
 </&>
@@ -86,7 +88,7 @@ my $QueryString = $m->comp('/Elements/QueryString',
 
 <%ARGS>
 $SavedSearchId => 'new'
-
+$SavedChartSearchId => 'new'
 $Query         => ''
 $Format        => ''
 $Rows          => '50'
diff --git a/share/html/Search/Elements/EditSearches b/share/html/Search/Elements/EditSearches
index 0920107..2122fb9 100644
--- a/share/html/Search/Elements/EditSearches
+++ b/share/html/Search/Elements/EditSearches
@@ -143,7 +143,8 @@ $SavedSearch => {}
 </%ARGS>
 <%INIT>
 
-$SavedSearch->{'Id'}          = $ARGS{'SavedSearchId'}          || 'new';
+$SavedSearch->{'Id'}          = ( $ARGS{Type} && $ARGS{Type} eq 'Chart' ?
+$ARGS{'SavedChartSearchId'} : $ARGS{'SavedSearchId'} ) || 'new';
 $SavedSearch->{'Description'} = $ARGS{'SavedSearchDescription'} || undef;
 $SavedSearch->{'Privacy'}     = $ARGS{'SavedSearchOwner'}       || undef;
 
diff --git a/share/html/Search/Results.html b/share/html/Search/Results.html
index 3d12b69..13252ee 100755
--- a/share/html/Search/Results.html
+++ b/share/html/Search/Results.html
@@ -57,7 +57,9 @@
     Rows => $Rows,
     OrderBy => $OrderBy,
     Order => $Order,
-    SavedSearchId => $SavedSearchId &>
+    SavedSearchId => $SavedSearchId,
+    SavedChartSearchId => $SavedChartSearchId,
+    &>
 <& /Elements/CollectionList, 
     Query => $Query,
     AllowSorting => 1,
@@ -70,7 +72,7 @@
     BaseURL => $BaseURL
 
    &>
-% my %hiddens = (Query => $Query, Format => $Format, Rows => $Rows, OrderBy => $OrderBy, Order => $Order, HideResults => $HideResults, Page => $Page );
+% my %hiddens = (Query => $Query, Format => $Format, Rows => $Rows, OrderBy => $OrderBy, Order => $Order, HideResults => $HideResults, Page => $Page, SavedChartSearchId => $SavedChartSearchId );
 <div align="right" class="refresh">
 <form method="get" action="<%RT->Config->Get('WebPath')%>/Search/Results.html">
 % foreach my $key (keys(%hiddens)) {
@@ -195,4 +197,5 @@ $Page => 1
 $OrderBy => undef
 $Order => undef
 $SavedSearchId => undef
+$SavedChartSearchId => undef
 </%ARGS>
diff --git a/share/html/Ticket/Elements/Tabs b/share/html/Ticket/Elements/Tabs
index 83d9382..7deb8c1 100755
--- a/share/html/Ticket/Elements/Tabs
+++ b/share/html/Ticket/Elements/Tabs
@@ -247,12 +247,13 @@ my $has_query = '';
 my %query_args;
 my $search_id = $ARGS{'SavedSearchId'}
             || $session{'CurrentSearchHash'}->{'SearchId'} || '';
+my $chart_search_id = $ARGS{'SavedChartSearchId'} || '';
 
 $has_query = 1 if ( $ARGS{'Query'} or $session{'CurrentSearchHash'}->{'Query'} );
   
 %query_args = (
-
         SavedSearchId => ($search_id eq 'new') ? undef : $search_id,
+        SavedChartSearchId => $chart_search_id,
         Query  => $ARGS{'Query'}  || $session{'CurrentSearchHash'}->{'Query'},
         Format => $ARGS{'Format'} || $session{'CurrentSearchHash'}->{'Format'},
         OrderBy => $ARGS{'OrderBy'}
diff --git a/share/html/Widgets/SavedSearch b/share/html/Widgets/SavedSearch
index 784cf65..a8cb3dd 100644
--- a/share/html/Widgets/SavedSearch
+++ b/share/html/Widgets/SavedSearch
@@ -59,15 +59,25 @@ my @Objects = RT::SavedSearches->new( $session{CurrentUser} )->_PrivacyObjects;
 push @Objects, RT::System->new($session{'CurrentUser'})
     if $session{'CurrentUser'}->HasRight( Object=> $RT::System,
                                           Right => 'SuperUser' );
-$self->{SearchId} ||= 'new';
+$self->{SearchId} ||= $args->{'SavedChartSearchId'} || 'new';
+
 my $SearchParams = { map { $_ => $args->{$_} } @{$self->{SearchFields}} };
 
-if ( my ( $container_object, $search_id ) = _parse_saved_search( $args->{'SavedSearchLoad'} ) ) {
+if ( my ( $container_object, $search_id ) = _parse_saved_search(
+            $args->{'SavedSearchLoad'} || $args->{'SavedChartSearchId'} ) ) {
     my $search = $container_object->Attributes->WithId($search_id);
     # We have a $search and now; import the others
-    $self->{SearchId} = $args->{'SavedSearchLoad'};
+    $self->{SearchId} = $args->{'SavedSearchLoad'} ||
+        $args->{'SavedChartSearchId'};
     $self->{CurrentSearch}{Object} = $search;
-    $args->{$_} = $search->SubValue($_) for @{ $self->{SearchFields} };
+    for ( @{ $self->{SearchFields} } ) {
+        # we may updated Query
+        next if $_ eq 'Query' && ! $args->{'SavedSearchLoad'};
+
+        $args->{$_} = $search->SubValue($_) 
+    }
+    $args->{SavedChartSearchId} = $args->{'SavedSearchLoad'}
+        if $args->{'SavedSearchLoad'};
 # saved search in /Search/Chart.html is different from /Search/Build.html
 # the former is of type 'Chart', while the latter is of type 'Ticket'.
 # After loading a saved search from the former after loading one from the
@@ -99,7 +109,8 @@ if ( $args->{SavedSearchSave} ) {
     if ( my $search = $self->{CurrentSearch}{Object} ) {
         # rename
         $search->SetDescription( $args->{SavedSearchDescription} );
-	push @actions, loc( '[_1] [_2] renamed to [_3].', loc($self->{SearchType}), $self->{CurrentSearch}{Description}, $args->{SavedSearchDescription} );
+        $search->SetSubValues( Query => $args->{Query} );
+	    push @actions, loc( '[_1] [_2] updated.', loc($self->{SearchType}), $args->{SavedSearchDescription} );
     }
     else {
         # new saved search
@@ -111,7 +122,10 @@ if ( $args->{SavedSearchSave} ) {
             SearchParams => $SearchParams
         );
         if ($ok) {
-	    $self->{CurrentSearch}{Object} = $saved_search->{Attribute};
+	        $self->{CurrentSearch}{Object} = $saved_search->{Attribute};
+            $self->{SearchId} = $args->{SavedChartSearchId} = 'RT::User-' .
+                $session{CurrentUser}->id . '-SavedSearch-' .
+                $saved_search->Id;
             push @actions, loc( '[_1] [_2] saved.', loc($self->{SearchType}), $args->{SavedSearchDescription} );
         } else {
             push @actions,
@@ -125,7 +139,7 @@ if ( $args->{SavedSearchDelete} && $self->{CurrentSearch}{Object} ) {
     push @actions, $ok ? loc( '[_1] [_2] deleted.', loc($self->{SearchType}), $self->{CurrentSearch}{Object}->Description ) : $msg;
     delete $self->{CurrentSearch}{Object};
     delete $self->{SearchId};
-
+    delete $args->{SavedChartSearchId};
 }
 
 $self->{CurrentSearch}{Description} = $self->{CurrentSearch}{Object}->Description
@@ -143,11 +157,14 @@ $args
 <%method show>
 <form method="post" action="<% $Action %>" name="SaveSearch">
 <& /Search/Elements/EditSearches,
-    Id            => $self->{SearchId},
+    Id            => $self->{SearchId} || 'new',
     Type          => $self->{SearchType},
     CurrentSearch => $self->{CurrentSearch},
     Title         => $Title,
     AllowCopy     => 0,
+    $self->{CurrentSearch}{Object} ? 
+    ( Object        => $self->{CurrentSearch}{Object},
+    Description   => $self->{CurrentSearch}{Object}->Description, ) : (),
 &><br />
 <%PERL>
 foreach my $field ( @{$self->{SearchFields}} ) {
@@ -160,6 +177,7 @@ foreach my $field ( @{$self->{SearchFields}} ) {
 <input type="hidden" class="hidden" name="<% $field %>" value="<% $value %>" />
 %   }
 % }
+<input type="hidden" class="hidden" name="SavedChartSearchId" value="<% $self->{SearchId} || 'new'  %>" />
 </form>
 <%ARGS>
 $self   => undef

commit ce5f1e7b2d8a86ba9dbe08bd2672d6c113102a48
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Tue Oct 27 18:38:10 2009 +0800

    only when SaveSearchLoad, we can update Query,ChartType,PrimaryGroupBy,etc.

diff --git a/share/html/Widgets/SavedSearch b/share/html/Widgets/SavedSearch
index a8cb3dd..15af277 100644
--- a/share/html/Widgets/SavedSearch
+++ b/share/html/Widgets/SavedSearch
@@ -70,12 +70,12 @@ if ( my ( $container_object, $search_id ) = _parse_saved_search(
     $self->{SearchId} = $args->{'SavedSearchLoad'} ||
         $args->{'SavedChartSearchId'};
     $self->{CurrentSearch}{Object} = $search;
-    for ( @{ $self->{SearchFields} } ) {
-        # we may updated Query
-        next if $_ eq 'Query' && ! $args->{'SavedSearchLoad'};
-
-        $args->{$_} = $search->SubValue($_) 
+    if ( $args->{'SaveSearchLoad'} ) {
+        for ( @{ $self->{SearchFields} } ) {
+            $args->{$_} = $search->SubValue($_) 
+        }
     }
+
     $args->{SavedChartSearchId} = $args->{'SavedSearchLoad'}
         if $args->{'SavedSearchLoad'};
 # saved search in /Search/Chart.html is different from /Search/Build.html

commit 79ac4f8c3f729dbe5a8a8e4826836425802878e6
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Tue Oct 27 18:39:09 2009 +0800

    we should save all the info when SavedSearchSave, not just Query

diff --git a/share/html/Widgets/SavedSearch b/share/html/Widgets/SavedSearch
index 15af277..d3348b3 100644
--- a/share/html/Widgets/SavedSearch
+++ b/share/html/Widgets/SavedSearch
@@ -109,7 +109,7 @@ if ( $args->{SavedSearchSave} ) {
     if ( my $search = $self->{CurrentSearch}{Object} ) {
         # rename
         $search->SetDescription( $args->{SavedSearchDescription} );
-        $search->SetSubValues( Query => $args->{Query} );
+        $search->SetSubValues(%$SearchParams);
 	    push @actions, loc( '[_1] [_2] updated.', loc($self->{SearchType}), $args->{SavedSearchDescription} );
     }
     else {

commit 583d32f4848be36dd90ad3e817fddb99f730565a
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Tue Oct 27 18:59:10 2009 +0800

    test PrimaryGroupBy and ChartStyle too in saved_search_chart.t

diff --git a/t/web/saved_search_chart.t b/t/web/saved_search_chart.t
index 2eef31c..fe1bc7e 100644
--- a/t/web/saved_search_chart.t
+++ b/t/web/saved_search_chart.t
@@ -2,9 +2,10 @@
 use strict;
 use warnings;
 
-use RT::Test tests => 12;
+use RT::Test tests => 19;
 my ( $url, $m ) = RT::Test->started_ok;
-
+use RT::Attribute;
+my $search = RT::Attribute->new($RT::SystemUser);
 my $ticket = RT::Ticket->new($RT::SystemUser);
 my ( $ret, $msg ) = $ticket->Create(
     Subject   => 'base ticket' . $$,
@@ -37,10 +38,10 @@ $m->submit_form(
 
 $m->content_like( qr/Chart first chart saved/, 'saved first chart' );
 
-my ($search) = $m->content =~ /value="(RT::User-\d+-SavedSearch-\d+)"/;
+my ( $search_uri, $id ) = $m->content =~ /value="(RT::User-\d+-SavedSearch-(\d+))"/;
 $m->submit_form(
     form_name => 'SaveSearch',
-    fields    => { SavedSearchLoad => $search },
+    fields    => { SavedSearchLoad => $search_uri },
 );
 
 $m->content_like( qr/name="SavedSearchDelete"\s+value="Delete"/,
@@ -56,16 +57,31 @@ $m->content_unlike( qr/name="SavedSearchSave"\s+value="Save"/,
 
 $m->submit_form(
     form_name => 'SaveSearch',
-    fields    => { Query => 'id=2' },
-    button    => 'SavedSearchSave',
+    fields    => {
+        Query          => 'id=2',
+        PrimaryGroupBy => 'Status',
+        ChartStyle     => 'pie',
+    },
+    button => 'SavedSearchSave',
 );
 
 $m->content_like( qr/Chart first chart updated/, 'found updated message' );
+$m->content_like( qr/id=2/,                      'Query is updated' );
+$m->content_like( qr/value="Status"\s+selected="selected"/,
+    'PrimaryGroupBy is updated' );
+$m->content_like( qr/value="pie"\s+selected="selected"/,
+    'ChartType is updated' );
+ok( $search->Load($id) );
+is( $search->SubValue('Query'), 'id=2', 'Query is indeed updated' );
+is( $search->SubValue('PrimaryGroupBy'),
+    'Status', 'PrimaryGroupBy is indeed updated' );
+is( $search->SubValue('ChartStyle'), 'pie', 'ChartStyle is indeed updated' );
 
+# finally, let's test delete
 $m->submit_form(
     form_name => 'SaveSearch',
     button    => 'SavedSearchDelete',
 );
-$m->content_like(qr/Chart first chart deleted/, 'found deleted message');
+$m->content_like( qr/Chart first chart deleted/, 'found deleted message' );
 $m->content_unlike( qr/value="RT::User-\d+-SavedSearch-\d+"/,
     'no saved search' );

commit 831d0a518146be36b4a331fee69bad364c054c70
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Wed Oct 28 12:46:13 2009 +0800

    add t/web/offline_utf8.t

diff --git a/t/web/offline_utf8.t b/t/web/offline_utf8.t
new file mode 100644
index 0000000..35501be
--- /dev/null
+++ b/t/web/offline_utf8.t
@@ -0,0 +1,56 @@
+#!/usr/bin/env perl
+use strict;
+use warnings;
+
+use RT::Test tests => 8;
+use File::Temp qw/tempfile/;
+use Encode;
+use RT::Ticket;
+my ( $fh, $file ) = tempfile;
+my $template = <<EOF;
+===Create-Ticket: ticket1
+Queue: General
+Subject: 标题
+Status: new
+Content: 
+这是正文
+ENDOFCONTENT
+EOF
+
+print $fh $template;
+close $fh;
+
+my ( $url, $m ) = RT::Test->started_ok;
+ok( $m->login, 'logged in' );
+
+$m->get( $url . '/Tools/Offline.html' );
+is( $m->{'status'}, 200, "Loaded /Tools/Offline.html" );
+
+$m->submit_form(
+    form_name => 'TicketUpdate',
+    fields    => { Template => $file, },
+    button    => 'Parse',
+);
+
+$m->content_contains( '这是正文', 'content is parsed right' );
+
+$m->submit_form(
+    form_name => 'TicketUpdate',
+    button    => 'UpdateTickets',
+
+    # mimic what browsers do: they seems decoded $template
+    fields    => { string => decode( 'utf8', $template ), },
+);
+
+$m->content_like( qr/Ticket \d+ created/, 'found ticket created message' );
+my ( $ticket_id ) = $m->content =~ /Ticket (\d+) created/;
+
+my $ticket = RT::Ticket->new( $RT::SystemUser );
+$ticket->Load( $ticket_id );
+is( $ticket->Subject, '标题', 'subject in $ticket is right' );
+
+$m->get( $url . "/Ticket/Display.html?id=$ticket_id" );
+is( $m->{'status'}, 200, "Loaded /Ticket/Display.html?id=$ticket_id" );
+$m->content_contains( '这是正文',
+    'content is right in ticket display page' );
+

commit fc5851034f891f9302fc82a0ce735f8bb7d16828
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Wed Oct 28 14:11:37 2009 +0800

    we should try to decode uploaded template for offline

diff --git a/share/html/Tools/Offline.html b/share/html/Tools/Offline.html
index ce9f4ea..9086197 100644
--- a/share/html/Tools/Offline.html
+++ b/share/html/Tools/Offline.html
@@ -115,6 +115,9 @@ if ($ARGS{'Parse'} && $ARGS{'Template'}) {
     while ( my $bytesread = read( $fh, $buffer, 4096 ) ) {
 	    $template .= $buffer;
     }
+    my $encode = RT::I18N::_GuessCharset( $template );
+    require Encode;
+    $template = Encode::decode( $encode, $template );
     $template =~ s/\r\n/\n/gs;
     $action->Parse(Content => $template, Queue => $qname, Requestor => $requestoraddress);
     foreach ( @{ $action->{'create_tickets'} } ) {

commit 2643a97bfa546899db7f8976d33c78f5cfe3649b
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Wed Oct 28 14:18:24 2009 +0800

    clean a bit: file input's value attr is useless

diff --git a/share/html/Tools/Offline.html b/share/html/Tools/Offline.html
index 9086197..8bd5105 100644
--- a/share/html/Tools/Offline.html
+++ b/share/html/Tools/Offline.html
@@ -86,7 +86,7 @@
 <&|/l&>Get template from file</&>:
 </td>
 <td>
-<input name="Template" type="file" value="foo" />
+<input name="Template" type="file" />
 <input type="submit" class="button" name="Parse" value="<&|/l&>Go!</&>" />
 </td>
 </tr>

commit 15afb7b20affec5cd0ad59477406c94fefa854d6
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Wed Oct 28 15:25:21 2009 +0800

    append plugin lib path to @INC if local lib path is *not* in @INC: see also ticket #13944

diff --git a/lib/RT.pm.in b/lib/RT.pm.in
index fb8afcb..f0e56e8 100755
--- a/lib/RT.pm.in
+++ b/lib/RT.pm.in
@@ -595,6 +595,7 @@ sub PluginDirs {
 =head2 InitPluginPaths
 
 Push plugins' lib paths into @INC right after F<local/lib>.
+In case F<local/lib> isn't in @INC, append them to @INC
 
 =cut
 
@@ -604,13 +605,19 @@ sub InitPluginPaths {
     my @lib_dirs = $self->PluginDirs('lib');
 
     my @tmp_inc;
+    my $added;
     for (@INC) {
         if ( Cwd::realpath($_) eq $RT::LocalLibPath) {
             push @tmp_inc, $_, @lib_dirs;
+            $added = 1;
         } else {
             push @tmp_inc, $_;
         }
     }
+
+    # append @lib_dirs in case $RT::LocalLibPath isn't in @INC
+    push @tmp_inc, @lib_dirs unless $added;
+
     my %seen;
     @INC = grep !$seen{$_}++, @tmp_inc;
 }

commit 76d907a69859ad04c172f82acf10e92dc614578f
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Wed Oct 28 20:37:37 2009 +0800

    add t/web/dashboard_with_deleted_saved_search.t

diff --git a/t/web/dashboard_with_deleted_saved_search.t b/t/web/dashboard_with_deleted_saved_search.t
new file mode 100644
index 0000000..06eadb4
--- /dev/null
+++ b/t/web/dashboard_with_deleted_saved_search.t
@@ -0,0 +1,90 @@
+#!/usr/bin/env perl
+use strict;
+use warnings;
+
+use RT::Test tests => 19;
+my ( $url, $m ) = RT::Test->started_ok;
+ok( $m->login, 'logged in' );
+
+# create a saved search
+$m->get_ok( $url . "/Search/Build.html?Query=" . 'id=1' );
+
+$m->submit_form(
+    form_name => 'BuildQuery',
+    fields    => { SavedSearchDescription => 'foo', },
+    button    => 'SavedSearchSave',
+);
+
+my ( $search_uri, $user_id, $search_id ) =
+  $m->content =~ /value="(RT::User-(\d+)-SavedSearch-(\d+))"/;
+$m->submit_form(
+    form_name => 'BuildQuery',
+    fields    => { SavedSearchLoad => $search_uri },
+    button    => 'SavedSearchSave',
+);
+
+$m->content_like( qr/name="SavedSearchDelete"\s+value="Delete"/,
+    'found Delete button' );
+$m->content_like(
+    qr/name="SavedSearchDescription"\s+value="foo"/,
+    'found Description input with the value filled'
+);
+
+# create a dashboard with the created search
+
+$m->get_ok( $url . "/Dashboards/Modify.html?Create=1" );
+$m->submit_form(
+    form_name => 'ModifyDashboard',
+    fields    => { Name => 'bar' },
+);
+
+$m->content_like( qr/Saved dashboard bar/i, 'dashboard saved' );
+my $dashboard_queries_link = $m->find_link( text_regex => qr/Queries/ );
+my ( $dashboard_id ) = $dashboard_queries_link->url =~ /id=(\d+)/;
+
+$m->get_ok( $url . "/Dashboards/Queries.html?id=$dashboard_id" );
+
+$m->content_lacks( 'value="Update"', 'no update button' );
+
+$m->submit_form(
+    form_name => 'Dashboard-Searches-body',
+    fields =>
+      { 'Searches-body-Available' => "search-$search_id-RT::User-$user_id" },
+    button => 'add',
+);
+
+$m->content_like( qr/Dashboard updated/i, 'added search foo to dashboard bar' );
+
+# delete the created search
+
+$m->get_ok( $url . "/Search/Build.html?Query=" . 'id=1' );
+$m->submit_form(
+    form_name => 'BuildQuery',
+    fields    => { SavedSearchLoad => $search_uri },
+);
+$m->submit_form(
+    form_name => 'BuildQuery',
+    button    => 'SavedSearchDelete',
+);
+
+$m->content_lacks( $search_uri, 'deleted search foo' );
+
+# here is what we really want to test
+
+$m->get_ok( $url . "/Dashboards/Queries.html?id=$dashboard_id" );
+is( $m->{'status'}, 200, "Loaded /Dashboards/Queries.html" );
+$m->content_like( qr/Deleted queries/i, 'found deleted message' );
+
+# Update button shows so we can update the deleted search easily
+$m->content_contains( 'value="Update"', 'found update button' );
+
+$m->submit_form(
+    form_name => 'Dashboard-Searches-body',
+    button    => 'update',
+);
+
+$m->content_unlike( qr/Deleted queries/i, 'deleted message is gone' );
+$m->content_lacks( 'value="Update"', 'update button is gone too' );
+
+$m->get_warnings; # we'll get a lot of warnings because the deleted search
+

commit 0df8fb6894d3dfc1cb3d07543ce87f8881df25b2
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Wed Oct 28 20:39:09 2009 +0800

    show Update botton when a dashboard contains deleted searches

diff --git a/share/html/Dashboards/Queries.html b/share/html/Dashboards/Queries.html
index 2ce092f..df0b44c 100644
--- a/share/html/Dashboards/Queries.html
+++ b/share/html/Dashboards/Queries.html
@@ -67,7 +67,8 @@
 <input type="hidden" class="hidden" name="Privacy" value="<%$Dashboard->Privacy%>" />
 
 <&| /Widgets/TitleBox, title => $pane->{DisplayName} &>
-    <& /Widgets/SelectionBox:show, self => $pane, nojs => 1 &>
+    <& /Widgets/SelectionBox:show, self => $pane, nojs => 1, grep( {
+            lc $_->{pane} eq lc $pane->{DisplayName} } @deleted ) ? ( ShowUpdate => 1 ) : () &>
 </&>
 </form>
 </td></tr>
diff --git a/share/html/Widgets/SelectionBox b/share/html/Widgets/SelectionBox
index f055b35..7a2e5ad 100644
--- a/share/html/Widgets/SelectionBox
+++ b/share/html/Widgets/SelectionBox
@@ -220,6 +220,9 @@ selected="selected"
 % if ($ARGS{'Clear'}) {
  <input name="clear" type="submit" class="button" value="<&|/l&>Clear</&>" />
 % }
+% if ( $ARGS{'ShowUpdate'} ) {
+ <input name="update" type="submit" class="button" value="<&|/l&>Update</&>" />
+% }
 % }
 
 % my $caption = "";

commit a7c75aeff4f684a06ff12e29113536bdee2cbe34
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Wed Oct 28 20:43:04 2009 +0800

    tiny typo fix

diff --git a/share/html/Widgets/SelectionBox b/share/html/Widgets/SelectionBox
index 7a2e5ad..fe64948 100644
--- a/share/html/Widgets/SelectionBox
+++ b/share/html/Widgets/SelectionBox
@@ -68,7 +68,7 @@
 %# and @selected is an arrayref of selected values from @items.
 %#
 %# and in html:
-%# <& /Widgets/SelectionBox:sow, self => $sel &>
+%# <& /Widgets/SelectionBox:show, self => $sel &>
 %#
 %# if the SelectionBox is created with AutoSave option, OnSubmit will be called
 %# on every button clicked in non-js mode.

commit 101490ac3f41069092318df77f48b2fb3df0d6b1
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Wed Oct 28 21:56:36 2009 +0800

    DisplayName is translated string

diff --git a/share/html/Dashboards/Queries.html b/share/html/Dashboards/Queries.html
index df0b44c..10d9cb8 100644
--- a/share/html/Dashboards/Queries.html
+++ b/share/html/Dashboards/Queries.html
@@ -68,7 +68,7 @@
 
 <&| /Widgets/TitleBox, title => $pane->{DisplayName} &>
     <& /Widgets/SelectionBox:show, self => $pane, nojs => 1, grep( {
-            lc $_->{pane} eq lc $pane->{DisplayName} } @deleted ) ? ( ShowUpdate => 1 ) : () &>
+            lc loc($_->{pane}) eq lc $pane->{DisplayName} } @deleted ) ? ( ShowUpdate => 1 ) : () &>
 </&>
 </form>
 </td></tr>

commit 48f5c2a31f889e7baf2d4f38555a73dc620a9a3a
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Wed Oct 28 22:11:52 2009 +0800

    better way to compare pane in @panes and @deleted in /Dashboards/Queries.html

diff --git a/share/html/Dashboards/Queries.html b/share/html/Dashboards/Queries.html
index 10d9cb8..1a1066b 100644
--- a/share/html/Dashboards/Queries.html
+++ b/share/html/Dashboards/Queries.html
@@ -67,8 +67,9 @@
 <input type="hidden" class="hidden" name="Privacy" value="<%$Dashboard->Privacy%>" />
 
 <&| /Widgets/TitleBox, title => $pane->{DisplayName} &>
+% my ( $pane_name ) = $pane->{Name} =~ /Searches-(.+)/;
     <& /Widgets/SelectionBox:show, self => $pane, nojs => 1, grep( {
-            lc loc($_->{pane}) eq lc $pane->{DisplayName} } @deleted ) ? ( ShowUpdate => 1 ) : () &>
+            $_->{pane} eq $pane_name} @deleted ) ? ( ShowUpdate => 1 ) : () &>
 </&>
 </form>
 </td></tr>

commit 86bfda78d2d9442e94f73e67789a77cbef41d5af
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Thu Oct 29 09:30:12 2009 +0800

    use get_ok() so we do *not* need to test the status stuff for new added tests

diff --git a/t/web/dashboard_with_deleted_saved_search.t b/t/web/dashboard_with_deleted_saved_search.t
index 06eadb4..328095a 100644
--- a/t/web/dashboard_with_deleted_saved_search.t
+++ b/t/web/dashboard_with_deleted_saved_search.t
@@ -2,7 +2,7 @@
 use strict;
 use warnings;
 
-use RT::Test tests => 19;
+use RT::Test tests => 18;
 my ( $url, $m ) = RT::Test->started_ok;
 ok( $m->login, 'logged in' );
 
@@ -72,7 +72,6 @@ $m->content_lacks( $search_uri, 'deleted search foo' );
 # here is what we really want to test
 
 $m->get_ok( $url . "/Dashboards/Queries.html?id=$dashboard_id" );
-is( $m->{'status'}, 200, "Loaded /Dashboards/Queries.html" );
 $m->content_like( qr/Deleted queries/i, 'found deleted message' );
 
 # Update button shows so we can update the deleted search easily
diff --git a/t/web/offline_utf8.t b/t/web/offline_utf8.t
index 35501be..2a3e64d 100644
--- a/t/web/offline_utf8.t
+++ b/t/web/offline_utf8.t
@@ -23,8 +23,7 @@ close $fh;
 my ( $url, $m ) = RT::Test->started_ok;
 ok( $m->login, 'logged in' );
 
-$m->get( $url . '/Tools/Offline.html' );
-is( $m->{'status'}, 200, "Loaded /Tools/Offline.html" );
+$m->get_ok( $url . '/Tools/Offline.html' );
 
 $m->submit_form(
     form_name => 'TicketUpdate',
@@ -49,8 +48,7 @@ my $ticket = RT::Ticket->new( $RT::SystemUser );
 $ticket->Load( $ticket_id );
 is( $ticket->Subject, '标题', 'subject in $ticket is right' );
 
-$m->get( $url . "/Ticket/Display.html?id=$ticket_id" );
-is( $m->{'status'}, 200, "Loaded /Ticket/Display.html?id=$ticket_id" );
+$m->get_ok( $url . "/Ticket/Display.html?id=$ticket_id" );
 $m->content_contains( '这是正文',
     'content is right in ticket display page' );
 
diff --git a/t/web/saved_search_chart.t b/t/web/saved_search_chart.t
index fe1bc7e..1051662 100644
--- a/t/web/saved_search_chart.t
+++ b/t/web/saved_search_chart.t
@@ -23,8 +23,7 @@ ok( $ret, "ticket created: $msg" );
 
 ok( $m->login, 'logged in' );
 
-$m->get( $url . "/Search/Chart.html?Query=" . 'id=1' );
-is( $m->{'status'}, 200, "Loaded /Search/Chart.html" );
+$m->get_ok( $url . "/Search/Chart.html?Query=" . 'id=1' );
 my ($owner) = $m->content =~ /value="(RT::User-\d+)"/;
 
 $m->submit_form(
diff --git a/t/web/ticket_update_without_content.t b/t/web/ticket_update_without_content.t
index 1ffa896..595cb74 100644
--- a/t/web/ticket_update_without_content.t
+++ b/t/web/ticket_update_without_content.t
@@ -23,8 +23,7 @@ ok( $ret, "ticket created: $msg" );
 
 ok( $m->login, 'logged in' );
 
-$m->get( $url . "/Ticket/ModifyAll.html?id=" . $ticket->id );
-is( $m->{'status'}, 200, "Loaded ModifyAll.html" );
+$m->get_ok( $url . "/Ticket/ModifyAll.html?id=" . $ticket->id );
 
 $m->submit_form(
     form_number => 3,
@@ -43,8 +42,7 @@ EOF
 
 ok( $ret, $msg );
 
-$m->get( $url . "/Ticket/ModifyAll.html?id=" . $ticket->id );
-is( $m->{'status'}, 200, "Loaded ModifyAll.html" );
+$m->get_ok( $url . "/Ticket/ModifyAll.html?id=" . $ticket->id );
 
 $m->submit_form(
     form_number => 3,

commit d71fab11096ab703d966eda863e03ea4d56b379b
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Thu Oct 29 15:37:30 2009 +0800

    erase the leading space in FormatType

diff --git a/lib/RT/Record.pm b/lib/RT/Record.pm
index 02dc6fd..e14f91f 100755
--- a/lib/RT/Record.pm
+++ b/lib/RT/Record.pm
@@ -1228,6 +1228,7 @@ sub FormatType{
 		 @_
 	       );
     $args{Type} =~ s/([A-Z])/" " . lc $1/ge;
+    $args{Type} =~ s/^\s+//; # in case the first letter is A-Z
     return $args{Type};
 }
 

commit 18796b3e76e740f8dda27efd4d181bed4faa9ef1
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Thu Oct 29 15:39:06 2009 +0800

    added t/web/search_bulk_update_links.t

diff --git a/t/web/search_bulk_update_links.t b/t/web/search_bulk_update_links.t
new file mode 100644
index 0000000..d6bfdfd
--- /dev/null
+++ b/t/web/search_bulk_update_links.t
@@ -0,0 +1,147 @@
+#!/usr/bin/env perl
+use strict;
+use warnings;
+
+use RT::Test tests => 28;
+my ( $url, $m ) = RT::Test->started_ok;
+ok( $m->login, 'logged in' );
+
+my $rtname = RT->Config->Get('rtname');
+
+# create tickets
+use RT::Ticket;
+
+my ( @link_tickets, @search_tickets );
+for ( 1 .. 3 ) {
+    my $link_ticket = RT::Ticket->new($RT::SystemUser);
+    my ( $ret, $msg ) = $link_ticket->Create(
+        Subject   => "link ticket $_",
+        Queue     => 'general',
+        Owner     => 'root',
+        Requestor => 'root at localhost',
+    );
+    ok( $ret, "link ticket created: $msg" );
+    push @link_tickets, $ret;
+}
+
+for ( 1 .. 3 ) {
+    my $ticket = RT::Ticket->new($RT::SystemUser);
+    my ( $ret, $msg ) = $ticket->Create(
+        Subject   => "search ticket $_",
+        Queue     => 'general',
+        Owner     => 'root',
+        Requestor => 'root at localhost',
+    );
+    ok( $ret, "search ticket created: $msg" );
+    push @search_tickets, $ret;
+}
+
+# let's add link to 1 search ticket first
+$m->get_ok( $url . "/Search/Bulk.html?Query=id=$search_tickets[0]&Rows=10" );
+$m->content_contains( 'Current Links', 'has current links part' );
+$m->content_lacks( 'DeleteLink--', 'no delete link stuff' );
+$m->submit_form(
+    form_number => 3,
+    fields      => {
+        'Ticket-DependsOn' => $link_tickets[0],
+        'Ticket-MemberOf'  => $link_tickets[1],
+        'Ticket-RefersTo'  => $link_tickets[2],
+    },
+);
+$m->content_contains(
+    "Ticket $search_tickets[0] depends on Ticket $link_tickets[0]",
+    'depends on msg',
+);
+$m->content_contains(
+    "Ticket $search_tickets[0] member of Ticket $link_tickets[1]",
+    'member of msg',
+);
+$m->content_contains(
+    "Ticket $search_tickets[0] refers to Ticket $link_tickets[2]",
+    'refers to msg',
+);
+
+$m->content_contains(
+    "DeleteLink--DependsOn-fsck.com-rt://$rtname/ticket/$link_tickets[0]",
+    'found depends on link' );
+$m->content_contains(
+    "DeleteLink--MemberOf-fsck.com-rt://$rtname/ticket/$link_tickets[1]",
+    'found member of link' );
+$m->content_contains(
+    "DeleteLink--RefersTo-fsck.com-rt://$rtname/ticket/$link_tickets[2]",
+    'found refers to link' );
+
+# here we check the *real* bulk update
+my $query = join ' OR ', map { "id=$_" } @search_tickets;
+$m->get_ok( $url . "/Search/Bulk.html?Query=$query&Rows=10" );
+$m->content_contains( 'Current Links', 'has current links part' );
+$m->content_lacks( 'DeleteLink--', 'no delete link stuff' );
+
+# test DependsOn, MemberOf and RefersTo
+$m->submit_form(
+    form_number => 3,
+    fields      => {
+        'Ticket-DependsOn' => $link_tickets[0],
+        'Ticket-MemberOf'  => $link_tickets[1],
+        'Ticket-RefersTo'  => $link_tickets[2],
+    },
+);
+
+$m->content_contains(
+    "DeleteLink--DependsOn-fsck.com-rt://$rtname/ticket/$link_tickets[0]",
+    'found depends on link' );
+$m->content_contains(
+    "DeleteLink--MemberOf-fsck.com-rt://$rtname/ticket/$link_tickets[1]",
+    'found member of link' );
+$m->content_contains(
+    "DeleteLink--RefersTo-fsck.com-rt://$rtname/ticket/$link_tickets[2]",
+    'found refers to link' );
+
+$m->submit_form(
+    form_number => 3,
+    fields      => {
+        "DeleteLink--DependsOn-fsck.com-rt://$rtname/ticket/$link_tickets[0]" =>
+          1,
+        "DeleteLink--MemberOf-fsck.com-rt://$rtname/ticket/$link_tickets[1]" =>
+          1,
+        "DeleteLink--RefersTo-fsck.com-rt://$rtname/ticket/$link_tickets[2]" =>
+          1,
+    },
+);
+
+$m->content_lacks( 'DeleteLink--', 'links are all deleted' );
+
+# test DependedOnBy, Members and ReferredToBy
+
+$m->submit_form(
+    form_number => 3,
+    fields      => {
+        'DependsOn-Ticket' => $link_tickets[0],
+        'MemberOf-Ticket'  => $link_tickets[1],
+        'RefersTo-Ticket'  => $link_tickets[2],
+    },
+);
+
+$m->content_contains(
+    "DeleteLink-fsck.com-rt://$rtname/ticket/$link_tickets[0]-DependsOn-",
+    'found depended on link' );
+$m->content_contains(
+    "DeleteLink-fsck.com-rt://$rtname/ticket/$link_tickets[1]-MemberOf-",
+    'found members link' );
+$m->content_contains(
+    "DeleteLink-fsck.com-rt://$rtname/ticket/$link_tickets[2]-RefersTo-",
+    'found referrd to link' );
+
+$m->submit_form(
+    form_number => 3,
+    fields      => {
+        "DeleteLink-fsck.com-rt://$rtname/ticket/$link_tickets[0]-DependsOn-" =>
+          1,
+        "DeleteLink-fsck.com-rt://$rtname/ticket/$link_tickets[1]-MemberOf-" =>
+          1,
+        "DeleteLink-fsck.com-rt://$rtname/ticket/$link_tickets[2]-RefersTo-" =>
+          1,
+    },
+);
+$m->content_lacks( 'DeleteLink--', 'links are all deleted' );
+

commit 93345dec507c2fd3d529c0b008793df18d6b3b8d
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Thu Oct 29 15:41:16 2009 +0800

    implement "Current Links" section in bulk update

diff --git a/share/html/Search/Bulk.html b/share/html/Search/Bulk.html
index a0638c1..2034f40 100755
--- a/share/html/Search/Bulk.html
+++ b/share/html/Search/Bulk.html
@@ -213,7 +213,7 @@ $cfs->LimitToQueue($_) for keys %$seen_queues;
 
 <&|/Widgets/TitleBox, title => loc('Edit Links'), color => "#336633"&>
 <em><&|/l&>Enter tickets or URIs to link tickets to. Separate multiple entries with spaces.</&></em><br />
-<& /Ticket/Elements/BulkLinks &>
+<& /Ticket/Elements/BulkLinks, Tickets => $Tickets &>
 </&>
 
 <& /Elements/Submit, Label => loc('Update') &>
diff --git a/share/html/Ticket/Elements/BulkLinks b/share/html/Ticket/Elements/BulkLinks
index 8b1dd0f..0205766 100755
--- a/share/html/Ticket/Elements/BulkLinks
+++ b/share/html/Ticket/Elements/BulkLinks
@@ -45,6 +45,83 @@
 %# those contributions and any derivatives thereof.
 %# 
 %# END BPS TAGGED BLOCK }}}
+<table width="100%">
+  <tr>
+    <td valign="top" width="50%">
+      <h3><&|/l&>Current Links</&></h3>
+<table>
+  <tr>
+    <td class="labeltop"><&|/l&>Depends on</&>:</td>
+    <td class="value">
+% if ( $hash{DependsOn} ) {
+% for my $link ( values %{$hash{DependsOn}} ) {
+      <input type="checkbox" class="checkbox" name="DeleteLink--<%$link->Type%>-<%$link->Target%>" value="1" />
+        <& /Elements/ShowLink, URI => $link->TargetURI &><br />
+% } }
+    </td>
+  </tr>
+  <tr>
+    <td class="labeltop"><&|/l&>Depended on by</&>:</td>
+    <td class="value">
+% if ( $hash{DependedOnBy} ) {
+% for my $link ( values %{$hash{DependedOnBy}} ) {
+      <input type="checkbox" class="checkbox" name="DeleteLink-<%$link->Base%>-<%$link->Type%>-" value="1" />
+        <& /Elements/ShowLink, URI => $link->BaseURI &><br />
+% } }
+    </td>
+  </tr>
+  <tr>
+    <td class="labeltop"><&|/l&>Parents</&>:</td>
+    <td class="value">
+% if ( $hash{MemberOf} ) {
+% for my $link ( values %{$hash{MemberOf}} ) {
+      <input type="checkbox" class="checkbox" name="DeleteLink--<%$link->Type%>-<%$link->Target%>" value="1" />
+        <& /Elements/ShowLink, URI => $link->TargetURI &><br />
+% } }
+    </td>
+  </tr>
+  <tr>
+    <td class="labeltop"><&|/l&>Children</&>:</td>
+    <td class="value">
+% if ( $hash{Members} ) {
+% for my $link ( values %{$hash{Members}} ) {
+      <input type="checkbox" class="checkbox" name="DeleteLink-<%$link->Base%>-<%$link->Type%>-" value="1" />
+        <& /Elements/ShowLink, URI => $link->BaseURI &><br />
+% } }
+    </td>
+  </tr>
+  <tr>
+    <td class="labeltop"><&|/l&>Refers to</&>:</td>
+    <td class="value">
+% if ( $hash{RefersTo} ) {
+% for my $link ( values %{$hash{RefersTo}} ) {
+      <input type="checkbox" class="checkbox" name="DeleteLink--<%$link->Type%>-<%$link->Target%>" value="1" />
+        <& /Elements/ShowLink, URI => $link->TargetURI &><br />
+% } }
+    </td>
+  </tr>
+  <tr>
+    <td class="labeltop"><&|/l&>Referred to by</&>:</td>
+    <td class="value">
+% if ( $hash{ReferredToBy} ) {
+% for my $link ( values %{$hash{ReferredToBy}} ) {
+% # Skip reminders
+% next if (UNIVERSAL::isa($link->BaseObj, 'RT::Ticket')  && $link->BaseObj->Type eq 'reminder');
+      <input type="checkbox" class="checkbox" name="DeleteLink-<%$link->Base%>-<%$link->Type%>-" value="1" />
+        <& /Elements/ShowLink, URI => $link->BaseURI &><br />
+% } }
+    </td>
+  </tr>
+  <tr>
+    <td></td>
+    <td><i><&|/l&>(Check box to delete)</&></i></td>
+  </tr>
+</table>
+</td>
+<td valign="top">
+<h3><&|/l&>New Links</&></h3>
+<i><&|/l&>Enter tickets or URIs to link tickets to. Separate multiple entries with spaces.</&>
+</i><br />
 <table>
   <tr>
     <td class="label"><&|/l&>Merge into</&>:</td>
@@ -75,3 +152,43 @@
     <td class="entry"> <input name="RefersTo-Ticket" /></td>
   </tr>
 </table>
+</td>
+</tr>
+</table>
+
+<%ARGS>
+$Tickets => undef
+</%ARGS>
+
+<%INIT>
+my %hash;
+if ( $Tickets && $Tickets->Count ) {
+    my $first_ticket = $Tickets->Next;
+    # we only show current links that eixst on all the tickets
+    for my $type ( qw/DependsOn DependedOnBy Members MemberOf RefersTo
+            ReferredToBy/ ) {
+        my $target_or_base =
+            $type =~ /DependsOn|MemberOf|RefersTo/ ? 'Target' : 'Base';
+        while ( my $link = $first_ticket->$type->Next ) {
+            $hash{$type}{$link->$target_or_base} = $link;
+        }
+    }
+
+    while ( my $ticket = $Tickets->Next ) {
+        for my $type ( qw/DependsOn DependedOnBy Members MemberOf RefersTo
+                ReferredToBy/ ) {
+            my $target_or_base =
+                $type =~ /DependsOn|MemberOf|RefersTo/ ? 'Target' : 'Base';
+            next unless $hash{$type};
+            my %exists;
+            while ( my $link = $ticket->$type->Next ) {
+                $exists{$link->$target_or_base}++;
+            }
+
+            for ( keys %{$hash{$type}} ) {
+                delete $hash{$type}{$_} unless $exists{$_};
+            }
+        }
+    }
+}
+</%INIT>

commit 066464c1eec2e5231173b75e26c2c7ce42e1d1c5
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Fri Oct 30 09:25:57 2009 +0800

    tweak BulkLinks a bit

diff --git a/share/html/Ticket/Elements/BulkLinks b/share/html/Ticket/Elements/BulkLinks
index 0205766..7f87cef 100755
--- a/share/html/Ticket/Elements/BulkLinks
+++ b/share/html/Ticket/Elements/BulkLinks
@@ -179,7 +179,8 @@ if ( $Tickets && $Tickets->Count ) {
                 ReferredToBy/ ) {
             my $target_or_base =
                 $type =~ /DependsOn|MemberOf|RefersTo/ ? 'Target' : 'Base';
-            next unless $hash{$type};
+            # if $hash{$type} is empty, no need to check any more
+            next unless $hash{$type} && keys %{$hash{$type}};
             my %exists;
             while ( my $link = $ticket->$type->Next ) {
                 $exists{$link->$target_or_base}++;

commit 0f7aba944e10231fc320a3ada3d1c3d59f79316a
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Fri Oct 30 18:52:55 2009 +0800

    fixed a typo in fr.po. thanks, JeanBenoit++

diff --git a/lib/RT/I18N/fr.po b/lib/RT/I18N/fr.po
index 2cdfe9c..94ea412 100755
--- a/lib/RT/I18N/fr.po
+++ b/lib/RT/I18N/fr.po
@@ -2398,7 +2398,7 @@ msgstr "Modifier les modèles système"
 
 #: lib/RT/Group_Overlay.pm:93
 msgid "EditSavedSearches"
-msgstr "ModifierRecherchesSaugardées"
+msgstr "ModifierRecherchesSauvegardées"
 
 #: share/html/Search/Elements/ResultViews:63
 msgid "Editable text"

commit 4623dff516ef04d1d7530d5ae618871360f19001
Author: Shawn M Moore <sartak at bestpractical.com>
Date:   Fri Oct 30 10:26:48 2009 -0400

    Begin a new test file for testing dashboard permissions

diff --git a/t/web/dashboards-permissions.t b/t/web/dashboards-permissions.t
new file mode 100644
index 0000000..1724042
--- /dev/null
+++ b/t/web/dashboards-permissions.t
@@ -0,0 +1,38 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+
+use RT::Test tests => 7;
+my ($baseurl, $m) = RT::Test->started_ok;
+
+my $url = $m->rt_base_url;
+
+# create user and queue {{{
+my $user_obj = RT::User->new($RT::SystemUser);
+my ($ok, $msg) = $user_obj->LoadOrCreateByEmail('customer at example.com');
+ok($ok, 'ACL test user creation');
+$user_obj->SetName('customer');
+$user_obj->SetPrivileged(1);
+($ok, $msg) = $user_obj->SetPassword('customer');
+$user_obj->PrincipalObj->GrantRight(Right => 'ModifySelf');
+my $currentuser = RT::CurrentUser->new($user_obj);
+
+my $queue = RT::Queue->new($RT::SystemUser);
+$queue->Create(Name => 'SearchQueue'.$$);
+
+$user_obj->PrincipalObj->GrantRight(Right => $_, Object => $queue)
+    for qw/SeeQueue ShowTicket OwnTicket/;
+
+$user_obj->PrincipalObj->GrantRight(Right => $_, Object => $RT::System)
+    for qw/SubscribeDashboard CreateOwnDashboard SeeOwnDashboard ModifyOwnDashboard DeleteOwnDashboard/;
+# }}}
+
+ok $m->login(customer => 'customer'), "logged in";
+
+$m->get_ok("$url/Dashboards");
+
+$m->follow_link_ok({text => "New"});
+$m->form_name('ModifyDashboard');
+is_deeply([$m->current_form->find_input('Privacy')->possible_values], ["RT::User-" . $user_obj->Id], "the only selectable privacy is user");
+$m->content_lacks('Delete', "Delete button hidden because we are creating");
+

commit 7b4d378f4861c66e2e3c834c1649dcfb7a174e61
Author: Shawn M Moore <sartak at bestpractical.com>
Date:   Fri Oct 30 10:39:03 2009 -0400

    Slightly more clear --all explanation for rt-email-dashboards

diff --git a/sbin/rt-email-dashboards.in b/sbin/rt-email-dashboards.in
index caa5535..5565435 100644
--- a/sbin/rt-email-dashboards.in
+++ b/sbin/rt-email-dashboards.in
@@ -559,8 +559,8 @@ being considered)
 
 =item --all
 
-Disable checking of whether each subscription should be sent right now (should
-only be used with --dryrun)
+Ignore subscription frequency when considering each dashboard (should only be
+used with --dryrun)
 
 =back
 

commit c325f902cb9ff174b15b73d5809bce55b42fc764
Author: Shawn M Moore <sartak at bestpractical.com>
Date:   Fri Oct 30 10:48:40 2009 -0400

    Refactor run_mailgate into run_and_capture
    
        We should improve and move to Test::Script::Run

diff --git a/lib/RT/Test.pm b/lib/RT/Test.pm
index c022854..2f04a93 100644
--- a/lib/RT/Test.pm
+++ b/lib/RT/Test.pm
@@ -656,14 +656,33 @@ sub run_mailgate {
         message => '',
         action  => 'correspond',
         queue   => 'General',
+        debug   => 1,
+        command => $RT::BinPath .'/rt-mailgate',
         @_
     );
     my $message = delete $args{'message'};
 
-    my $cmd = $RT::BinPath .'/rt-mailgate';
-    die "Couldn't find mailgate ($cmd) command" unless -f $cmd;
+    $args{after_open} = sub {
+        my $child_in = shift;
+        if ( UNIVERSAL::isa($message, 'MIME::Entity') ) {
+            $message->print( $child_in );
+        } else {
+            print $child_in $message;
+        }
+    };
+
+    $self->run_and_capture(%args);
+}
+
+sub run_and_capture {
+    my $self = shift;
+    my %args = @_;
+
+    my $cmd = delete $args{'command'};
+    die "Couldn't find command ($cmd)" unless -f $cmd;
+
+    $cmd .= ' --debug' if delete $args{'debug'};
 
-    $cmd .= ' --debug';
     while( my ($k,$v) = each %args ) {
         next unless $v;
         $cmd .= " --$k '$v'";
@@ -676,11 +695,8 @@ sub run_mailgate {
     my ($child_out, $child_in);
     my $pid = IPC::Open2::open2($child_out, $child_in, $cmd);
 
-    if ( UNIVERSAL::isa($message, 'MIME::Entity') ) {
-        $message->print( $child_in );
-    } else {
-        print $child_in $message;
-    }
+    $args{after_open}->($child_in, $child_out) if $args{after_open};
+
     close $child_in;
 
     my $result = do { local $/; <$child_out> };

commit 4eb846f4b7da66940f6bc479a0367d226969e99d
Author: Jesse Vincent <jesse at bestpractical.com>
Date:   Fri Oct 30 14:54:21 2009 -0400

    RT was accidentally injecting too many newlines when rendering plaintext messages without <pre>.
    
    This commit fixes the regex.

diff --git a/share/html/Ticket/Elements/ShowMessageStanza b/share/html/Ticket/Elements/ShowMessageStanza
index e9b57bb..0d4fe61 100755
--- a/share/html/Ticket/Elements/ShowMessageStanza
+++ b/share/html/Ticket/Elements/ShowMessageStanza
@@ -98,7 +98,7 @@ my $print_content = sub {
     $m->callback( content => $ref, %ARGS );
     $m->comp('/Elements/MakeClicky', content => $ref, ticket => $ticket, %ARGS);
     unless ( $plain_text_pre || $plain_text_mono ) {
-        $$ref =~ s{(?=\r*\n)}{<br />}g if defined $$ref;
+        $$ref =~ s{(\r?\n)}{<br />}g if defined $$ref;
     }
     $m->out( $$ref );
 };

commit f1eb6a3433e9cab95a8737b3976e0900028407ad
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Mon Nov 2 14:13:55 2009 +0800

    add t/web/offline_messages_utf8.t

diff --git a/t/web/offline_messages_utf8.t b/t/web/offline_messages_utf8.t
new file mode 100644
index 0000000..c32e0bc
--- /dev/null
+++ b/t/web/offline_messages_utf8.t
@@ -0,0 +1,67 @@
+#!/usr/bin/env perl
+use strict;
+use warnings;
+
+use RT::Test tests => 6;
+use File::Temp qw/tempfile/;
+use Encode;
+use RT::Ticket;
+
+my ( $url, $m ) = RT::Test->started_ok;
+$m->default_header( 'Accept-Language' => "zh-cn" );
+ok( $m->login, 'logged in' );
+
+my $ticket_id;
+my $template;
+
+{
+
+    # test create message
+    $template = <<EOF;
+===Create-Ticket: ticket1
+Queue: General
+Subject: test message
+Status: new
+Content: 
+ENDOFCONTENT
+Due: 
+TimeEstimated: 100
+TimeLeft: 100
+FinalPriority: 90
+EOF
+
+    $m->get_ok( $url . '/Tools/Offline.html' );
+
+    $m->submit_form(
+        form_name => 'TicketUpdate',
+        fields    => { string => $template, },
+        button    => 'UpdateTickets',
+    );
+    my $content = encode 'utf8', $m->content;
+    ok( $content =~ qr/申请单 #(\d+) 成功新增于 &#39;General&#39; 表单/, 'message is shown right' );
+    $ticket_id = $1;
+}
+
+{
+
+    # test update message
+    $template = <<EOF;
+===Update-Ticket: 1
+Subject: test message update
+EOF
+
+    $m->get_ok( $url . '/Tools/Offline.html' );
+    $m->submit_form(
+        form_name => 'TicketUpdate',
+        fields    => { string => $template, },
+        button    => 'UpdateTickets',
+    );
+
+    my $content = encode 'utf8', $m->content;
+    ok(
+        $content =~
+qr/主题\s*的值从\s*&#39;test message&#39;\s*改为\s*&#39;test message update&#39;/,
+        'subject is updated'
+    );
+}
+

commit 30e610f1d916bbd3d91e24380a7406deea6bd595
Merge: f1eb6a3 4eb846f
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Mon Nov 2 14:14:23 2009 +0800

    Merge branch '3.8-trunk' of diesel:/git/rt into 3.8-trunk


commit 84022062cec889f1cabf1d4a10e28b7b66addf23
Author: Kevin Falcone <falcone at bestpractical.com>
Date:   Tue Oct 27 12:06:22 2009 -0400

    use err_headers_out instead of headers_out
    
    This means cookies will still be set if you redirect
    Apache/mod_perl won't send the cookie when using headers_out

diff --git a/lib/RT/Interface/Web.pm b/lib/RT/Interface/Web.pm
index b82b638..5127f05 100755
--- a/lib/RT/Interface/Web.pm
+++ b/lib/RT/Interface/Web.pm
@@ -480,7 +480,7 @@ sub SendSessionCookie {
         -secure => ( RT->Config->Get('WebSecureCookies') ? 1 : 0 )
     );
 
-    $HTML::Mason::Commands::r->headers_out->{'Set-Cookie'} = $cookie->as_string;
+    $HTML::Mason::Commands::r->err_headers_out->{'Set-Cookie'} = $cookie->as_string;
 }
 
 =head2 Redirect URL

commit 9307bb81790b903615d106226f3b1f71358c0945
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Tue Nov 3 09:49:00 2009 +0800

    remove misleading comment

diff --git a/lib/RT/Record.pm b/lib/RT/Record.pm
index e14f91f..2a95b6a 100755
--- a/lib/RT/Record.pm
+++ b/lib/RT/Record.pm
@@ -1228,7 +1228,7 @@ sub FormatType{
 		 @_
 	       );
     $args{Type} =~ s/([A-Z])/" " . lc $1/ge;
-    $args{Type} =~ s/^\s+//; # in case the first letter is A-Z
+    $args{Type} =~ s/^\s+//;
     return $args{Type};
 }
 

commit d17c5d0a96ce3a497bde93addcbb879925a1fd6f
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Tue Nov 3 10:50:56 2009 +0800

    remove "use bytes;" in CreateTickets

diff --git a/lib/RT/Action/CreateTickets.pm b/lib/RT/Action/CreateTickets.pm
index ce1c28e..4883ae3 100755
--- a/lib/RT/Action/CreateTickets.pm
+++ b/lib/RT/Action/CreateTickets.pm
@@ -347,7 +347,6 @@ sub CreateByTemplate {
     my @results;
 
     # XXX: cargo cult programming that works. i'll be back.
-    use bytes;
 
     local %T::Tickets = %T::Tickets;
     local $T::TOP     = $T::TOP;
@@ -409,7 +408,6 @@ sub UpdateByTemplate {
     my $top  = shift;
 
     # XXX: cargo cult programming that works. i'll be back.
-    use bytes;
 
     my @results;
     local %T::Tickets = %T::Tickets;

commit c81489178f7b58bd15ec6e3a4cc182574233c18b
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Tue Nov 3 11:39:52 2009 -0500

    Perltidy

diff --git a/share/html/Ticket/Create.html b/share/html/Ticket/Create.html
index 6daec7b..9ea1ea4 100755
--- a/share/html/Ticket/Create.html
+++ b/share/html/Ticket/Create.html
@@ -250,77 +250,80 @@ $m->callback( CallbackName => "Init", ARGSRef => \%ARGS );
 my $Queue = $ARGS{Queue};
 
 my $CloneTicketObj;
-if ( $CloneTicket ) {
+if ($CloneTicket) {
     $CloneTicketObj = RT::Ticket->new( $session{CurrentUser} );
-    $CloneTicketObj->Load($CloneTicket) or Abort(loc("Ticket could not be loaded"));
-    
+    $CloneTicketObj->Load($CloneTicket)
+        or Abort( loc("Ticket could not be loaded") );
+
     my $clone = {
-        Requestors       => join( ',', $CloneTicketObj->RequestorAddresses ),
-        Cc               => join( ',', $CloneTicketObj->CcAddresses),
-        AdminCc          => join( ',', $CloneTicketObj->AdminCcAddresses),
-        InitialPriority => $CloneTicketObj->Priority, 
+        Requestors => join( ',', $CloneTicketObj->RequestorAddresses ),
+        Cc         => join( ',', $CloneTicketObj->CcAddresses ),
+        AdminCc    => join( ',', $CloneTicketObj->AdminCcAddresses ),
+        InitialPriority => $CloneTicketObj->Priority,
     };
-    
-    $clone->{$_} = $CloneTicketObj->$_() 
-        for qw/Owner Subject FinalPriority TimeEstimated TimeWorked 
-                Status TimeLeft Starts Started Due Resolved/;
-    
-        my $members = $CloneTicketObj->Members;
-        my ( @members, @members_of, @refers, @refers_by, @depends, @depends_by );
-        my $refers = $CloneTicketObj->RefersTo;
-        while ( my $refer = $refers->Next ) {
-            push @refers, $refer->LocalTarget;
+
+    $clone->{$_} = $CloneTicketObj->$_()
+        for qw/Owner Subject FinalPriority TimeEstimated TimeWorked
+        Status TimeLeft Starts Started Due Resolved/;
+
+    my $members = $CloneTicketObj->Members;
+    my ( @members, @members_of, @refers, @refers_by, @depends, @depends_by );
+    my $refers = $CloneTicketObj->RefersTo;
+    while ( my $refer = $refers->Next ) {
+        push @refers, $refer->LocalTarget;
+    }
+    $clone->{'new-RefersTo'} = join ' ', @refers;
+
+    my $refers_by = $CloneTicketObj->ReferredToBy;
+    while ( my $refer_by = $refers_by->Next ) {
+        push @refers_by, $refer_by->LocalBase;
+    }
+    $clone->{'RefersTo-new'} = join ' ', @refers_by;
+    if (0) {    # Temporarily disabled
+        my $depends = $CloneTicketObj->DependsOn;
+        while ( my $depend = $depends->Next ) {
+            push @depends, $depend->LocalTarget;
         }
-        $clone->{'new-RefersTo'} = join ' ', @refers;
-    
-        my $refers_by = $CloneTicketObj->ReferredToBy;
-        while ( my $refer_by = $refers_by->Next ) {
-            push @refers_by, $refer_by->LocalBase;
+        $clone->{'new-DependsOn'} = join ' ', @depends;
+
+        my $depends_by = $CloneTicketObj->DependedOnBy;
+        while ( my $depend_by = $depends_by->Next ) {
+            push @depends_by, $depend_by->LocalBase;
         }
-        $clone->{'RefersTo-new'} = join ' ', @refers_by;
-        if (0) { # Temporarily disabled
-            my $depends = $CloneTicketObj->DependsOn;
-            while ( my $depend = $depends->Next ) {
-                push @depends, $depend->LocalTarget;
-            }
-            $clone->{'new-DependsOn'} = join ' ', @depends;
-
-            my $depends_by = $CloneTicketObj->DependedOnBy;
-            while ( my $depend_by = $depends_by->Next ) {
-                push @depends_by, $depend_by->LocalBase;
-            }
-            $clone->{'DependsOn-new'} = join ' ', @depends_by;
-
-            while ( my $member = $members->Next ) {
-                push @members, $member->LocalBase;
-            }
-            $clone->{'MemberOf-new'} = join ' ', @members;
-
-            my $members_of = $CloneTicketObj->MemberOf;
-            while ( my $member_of = $members_of->Next ) {
-                push @members_of, $member_of->LocalTarget;
-            }
-            $clone->{'new-MemberOf'} = join ' ', @members_of;
+        $clone->{'DependsOn-new'} = join ' ', @depends_by;
 
+        while ( my $member = $members->Next ) {
+            push @members, $member->LocalBase;
         }
-    
+        $clone->{'MemberOf-new'} = join ' ', @members;
+
+        my $members_of = $CloneTicketObj->MemberOf;
+        while ( my $member_of = $members_of->Next ) {
+            push @members_of, $member_of->LocalTarget;
+        }
+        $clone->{'new-MemberOf'} = join ' ', @members_of;
+
+    }
+
     my $cfs = $CloneTicketObj->QueueObj->TicketCustomFields();
     while ( my $cf = $cfs->Next ) {
-        my $cf_id = $cf->id;
+        my $cf_id     = $cf->id;
         my $cf_values = $CloneTicketObj->CustomFieldValues( $cf->id );
         my @cf_values;
         while ( my $cf_value = $cf_values->Next ) {
             push @cf_values, $cf_value->Content;
         }
-        $clone->{"Object-RT::Ticket--CustomField-$cf_id-Value"} 
-            = join "\n", @cf_values;
+        $clone->{"Object-RT::Ticket--CustomField-$cf_id-Value"} = join "\n",
+            @cf_values;
     }
-    
+
     for ( keys %$clone ) {
         $ARGS{$_} = $clone->{$_} if not defined $ARGS{$_};
     }
 
 }
+
+
 my @results;
 my $QueueObj = new RT::Queue($session{'CurrentUser'});
 $QueueObj->Load($Queue) || Abort(loc("Queue could not be loaded."));

commit fb984b624e4f3d2d301e88bad38c1ef6180c18db
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Tue Nov 3 12:59:23 2009 -0500

    Only set time values on clone if they are non-zero

diff --git a/share/html/Ticket/Create.html b/share/html/Ticket/Create.html
index 9ea1ea4..0f817ab 100755
--- a/share/html/Ticket/Create.html
+++ b/share/html/Ticket/Create.html
@@ -264,7 +264,11 @@ if ($CloneTicket) {
 
     $clone->{$_} = $CloneTicketObj->$_()
         for qw/Owner Subject FinalPriority TimeEstimated TimeWorked
-        Status TimeLeft Starts Started Due Resolved/;
+        Status TimeLeft/;
+
+    $clone->{$_} = $CloneTicketObj->$_->AsString
+        for grep { $CloneTicketObj->$_->Unix }
+        map      { $_ . "Obj" } qw/Starts Started Due Resolved/;
 
     my $members = $CloneTicketObj->Members;
     my ( @members, @members_of, @refers, @refers_by, @depends, @depends_by );

commit 0703e80114f6e64c85dc89df29779c34f6752ae1
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Wed Nov 4 13:14:54 2009 +0800

    add t/web/saved_search_permissions.t

diff --git a/t/web/saved_search_permissions.t b/t/web/saved_search_permissions.t
new file mode 100644
index 0000000..f91ca13
--- /dev/null
+++ b/t/web/saved_search_permissions.t
@@ -0,0 +1,34 @@
+#!/usr/bin/env perl
+use strict;
+use warnings;
+
+use RT::Test tests => 10;
+my $user = RT::User->new($RT::SystemUser);
+ok(
+    $user->Create(
+        Name       => 'foo',
+        Privileged => 1,
+        Password   => 'foobar'
+    )
+);
+
+my ( $url, $m ) = RT::Test->started_ok;
+ok( $m->login, 'root logged in' );
+$m->get_ok( $url . '/Search/Build.html?Query=id<100' );
+$m->submit_form(
+    form_name => 'BuildQuery',
+    fields    => { SavedSearchDescription => 'test' },
+    button    => 'SavedSearchSave',
+);
+$m->content_contains( q{name="SavedSearchDescription" value="test"},
+    'saved test search' );
+my ($id) = $m->content =~ /value="(RT::User-\d+-SavedSearch-\d+)"/;
+ok( $m->login( 'foo', 'foobar' ), 'logged in' );
+$m->get_ok( $url . "/Search/Build.html?SavedSearchLoad=$id" );
+
+my $message = qq{Can not load saved search "$id"};
+RT::Interface::Web::EscapeUTF8( \$message );
+$m->content_contains( $message, 'user foo can not load saved search of root' );
+
+$m->warning_like( qr/User #\d+ tried to load container user #\d+/,
+    'get warning' );

commit 8cac533616e5e9fda3349207f8b08343d21837d8
Merge: 0703e80 fb984b6
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Wed Nov 4 13:15:55 2009 +0800

    Merge branch '3.8-trunk' of diesel:/git/rt into 3.8-trunk


commit 55f5e824dcb06e344a8ae7535f14c973639a0bea
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Wed Nov 4 13:18:35 2009 +0800

    check $container to see if $ARGS{\'SavedSearchLoad\'} can be loaded

diff --git a/share/html/Search/Elements/EditSearches b/share/html/Search/Elements/EditSearches
index 2122fb9..62871fd 100644
--- a/share/html/Search/Elements/EditSearches
+++ b/share/html/Search/Elements/EditSearches
@@ -156,17 +156,23 @@ if ( $ARGS{'SavedSearchRevert'} ) {
 
 if ( $ARGS{'SavedSearchLoad'} ) {
     my ($container, $id ) = _parse_saved_search ($ARGS{'SavedSearchLoad'});
-    my $search = $container->Attributes->WithId( $id );
-
-    $SavedSearch->{'Id'}          = $ARGS{'SavedSearchLoad'};
-    $SavedSearch->{'Object'}      = $search;
-    $SavedSearch->{'Description'} = $search->Description;
-    $Query->{$_} = $search->SubValue($_) foreach @SearchFields;
-
-    if ( $ARGS{'SavedSearchRevert'} ) {
-        push @results, loc('Loaded original "[_1]" saved search', $SavedSearch->{'Description'} );
-    } else {
-        push @results, loc('Loaded saved search "[_1]"', $SavedSearch->{'Description'} );
+    if ( $container ) {
+        my $search = $container->Attributes->WithId( $id );
+        $SavedSearch->{'Id'}          = $ARGS{'SavedSearchLoad'};
+        $SavedSearch->{'Object'}      = $search;
+        $SavedSearch->{'Description'} = $search->Description;
+        $Query->{$_} = $search->SubValue($_) foreach @SearchFields;
+
+        if ( $ARGS{'SavedSearchRevert'} ) {
+            push @results, loc('Loaded original "[_1]" saved search', $SavedSearch->{'Description'} );
+        } else {
+            push @results, loc('Loaded saved search "[_1]"', $SavedSearch->{'Description'} );
+        }
+    }
+    else {
+        push @results, loc( 'Can not load saved search "[_1]"',
+                $ARGS{'SavedSearchLoad'} );
+        return @results;
     }
 }
 elsif ( $ARGS{'SavedSearchDelete'} ) {

commit 835ca9e393799c47bac9be284e5c8bdd045ed3aa
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Wed Nov 4 13:21:47 2009 +0800

    we can't use @actions to store query's parse results: we should use another variable to do this

diff --git a/share/html/Search/Build.html b/share/html/Search/Build.html
index e2c13ff..0fd7974 100644
--- a/share/html/Search/Build.html
+++ b/share/html/Search/Build.html
@@ -174,10 +174,11 @@ my $ParseQuery = sub {
     return $tree;
 };
 
-my $tree = $ParseQuery->( $query{'Query'}, \@actions );
+my @parse_results;
+my $tree = $ParseQuery->( $query{'Query'}, \@parse_results );
 
 # if parsing went poorly, send them to the edit page to fix it
-if ( $actions[0] ) {
+if ( @parse_results ) {
     return $m->comp( "Edit.html", Query => $query{'Query'}, actions => \@actions );
 }
 

commit 7cb0d2226c4f47180371b5f9c9544f1bd3252afd
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Wed Nov 4 13:46:43 2009 +0800

    tiny url fix: we do *not* need 2 leading /

diff --git a/lib/RT/Test/Web.pm b/lib/RT/Test/Web.pm
index 370e30f..9e3d6ae 100644
--- a/lib/RT/Test/Web.pm
+++ b/lib/RT/Test/Web.pm
@@ -59,7 +59,7 @@ require Test::More;
 sub get_ok {
     my $self = shift;
     my $url = shift;
-    if ( $url =~ m{^/} ) {
+    if ( $url =~ s!^/!! ) {
         $url = $self->rt_base_url . $url;
     }
     my $rv = $self->SUPER::get_ok($url, @_);

commit 0181edeef105bb8ca53002375368222c86962521
Author: Jesse Vincent <jesse at bestpractical.com>
Date:   Fri Nov 6 11:55:20 2009 -0500

    Feed ticket information to MakeClicky when we're clicky-fying attachments
    
    - Thanks to Salih Goenuellue at SWITCH

diff --git a/share/html/Ticket/Elements/ShowTransactionAttachments b/share/html/Ticket/Elements/ShowTransactionAttachments
index 0674342..9c40b28 100644
--- a/share/html/Ticket/Elements/ShowTransactionAttachments
+++ b/share/html/Ticket/Elements/ShowTransactionAttachments
@@ -198,7 +198,8 @@ my $render_attachment = sub {
                 $content = $m->comp( '/Elements/ScrubHTML', Content => $content );
                 if ( $message->ContentType eq 'text/html' ) {
                     $m->comp('/Elements/MakeClicky', 
-                            content => \$content, html => 1 );
+                            content => \$content, html => 1,
+                            ticket => $Ticket );
                 }
                 $m->out( $content );
             }

commit faa431530cb245fa2b9dfc6f2f31480b2550aaae
Author: Carlos Fuentes Bermejo <carlos.fuentes at rediris.es>
Date:   Fri Nov 6 15:33:11 2009 -0500

    If there is no ticket for outgoing mail, check a new configuration option for the From address

diff --git a/etc/RT_Config.pm.in b/etc/RT_Config.pm.in
index 1933c12..1503376 100755
--- a/etc/RT_Config.pm.in
+++ b/etc/RT_Config.pm.in
@@ -372,6 +372,29 @@ if you use 'sendmail' rather than 'sendmailpipe'
 
 Set($MailCommand , 'sendmailpipe');
 
+=item C<$SetOutgoingMailFrom>
+
+C<$SetOutgoingMailFrom> enables that RT sets the sender envelope with
+the
+correspond mail address of the ticket's queue.
+
+Warning: If you use RT in this way, you should know that bounce mails
+will
+be incoming to the system, and creating new tickets.
+=cut
+
+Set ($SetOutgoingMailFrom, 1);
+
+=item C<$OverrideOutgoingMailFrom>
+
+C<$OverrideOutgoingMailFrom> is used for overwriting the Correspond
+address of the queue.
+
+=cut
+Set($OverrideOutgoingMailFrom, {
+}
+);
+
 =back
 
 =head1 Sendmail Configuration
diff --git a/lib/RT/Interface/Email.pm b/lib/RT/Interface/Email.pm
index 1279816..0396935 100755
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -382,12 +382,35 @@ sub SendEmail {
     # if it is a sub routine, we just return it;
     return $mail_command->($args{'Entity'}) if UNIVERSAL::isa( $mail_command, 'CODE' );
 
+    my $ticketObj = $args{'Ticket'};
+    my $transactionObj = $args{'Transaction'};
+
     if ( $mail_command eq 'sendmailpipe' ) {
         my $path = RT->Config->Get('SendmailPath');
         my $args = RT->Config->Get('SendmailArguments');
-        $args .= ' '. RT->Config->Get('SendmailBounceArguments') if $args{'Bounce'};
-
-        # VERP
+	
+	# SetOutgoingMailFrom
+	
+	my $outgoingMailAddress;
+	if ( RT->Config->Get('SetOutgoingMailFrom') ) {
+		if ( defined $ticketObj ){
+			my $queueName = $ticketObj->QueueObj->Name;
+			if (not defined RT->Config->Get('OverrideOutgoingMailFrom')->{$queueName}) {
+				$outgoingMailAddress = $ticketObj->QueueObj->CorrespondAddress;
+			} else {
+				$outgoingMailAddress = RT->Config->Get('OverrideOutgoingMailFrom')->{$queueName};
+			}
+			$args .= ' -f '.$outgoingMailAddress;
+		} elsif (RT->Config->Get('OverrideOutgoingMailFrom')->{'Default'} ) {
+			$outgoingMailAddress  = RT->Config->Get('OverrideOutgoingMailFrom')->{'Default'};
+			$args .= ' -f '.$outgoingMailAddress;
+		}
+	}
+
+	# Set Bounce Arguments
+	$args .= ' '. RT->Config->Get('SendmailBounceArguments') if $args{'Bounce'};
+	
+	# VERP
         if ( $args{'Transaction'} and
              my $prefix = RT->Config->Get('VERPPrefix') and
              my $domain = RT->Config->Get('VERPDomain') )

commit cb9b2716a21a92f288f62548386a4ed2dbcb1e6f
Author: Shawn M Moore <sartak at bestpractical.com>
Date:   Fri Nov 6 15:36:15 2009 -0500

    Documentation tweaks for new OutgoingMailFrom config

diff --git a/etc/RT_Config.pm.in b/etc/RT_Config.pm.in
index 1503376..3481f94 100755
--- a/etc/RT_Config.pm.in
+++ b/etc/RT_Config.pm.in
@@ -374,26 +374,27 @@ Set($MailCommand , 'sendmailpipe');
 
 =item C<$SetOutgoingMailFrom>
 
-C<$SetOutgoingMailFrom> enables that RT sets the sender envelope with
-the
-correspond mail address of the ticket's queue.
+C<$SetOutgoingMailFrom> tells RT to set the sender envelope with the correspond
+mail address of the ticket's queue.
+
+Warning: If you use this setting, bounced mails will appear to be incoming
+mail to the system, thus creating new tickets.
 
-Warning: If you use RT in this way, you should know that bounce mails
-will
-be incoming to the system, and creating new tickets.
 =cut
 
-Set ($SetOutgoingMailFrom, 1);
+Set($SetOutgoingMailFrom, 0);
 
 =item C<$OverrideOutgoingMailFrom>
 
 C<$OverrideOutgoingMailFrom> is used for overwriting the Correspond
-address of the queue.
+address of the queue. The option is a hash reference of queue name to
+email address.
 
 =cut
+
 Set($OverrideOutgoingMailFrom, {
-}
-);
+#    'General' => 'general at rt.example.com',
+});
 
 =back
 

commit 177b396f8889db717a972e1fe5c42e4b4573eed9
Author: Shawn M Moore <sartak at bestpractical.com>
Date:   Fri Nov 6 15:40:12 2009 -0500

    Pluck Ticket and Transaction out of %args sooner

diff --git a/lib/RT/Interface/Email.pm b/lib/RT/Interface/Email.pm
index 0396935..ba15d3a 100755
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -315,6 +315,10 @@ sub SendEmail {
         Transaction => undef,
         @_,
     );
+
+    my $TicketObj = $args{'Ticket'};
+    my $TransactionObj = $args{'Transaction'};
+
     foreach my $arg( qw(Entity Bounce) ) {
         next unless defined $args{ lc $arg };
 
@@ -339,26 +343,26 @@ sub SendEmail {
         return -1;
     }
 
-    if ( $args{'Transaction'} && !$args{'Ticket'}
-        && $args{'Transaction'}->ObjectType eq 'RT::Ticket' )
+    if ( $TransactionObj && !$TicketObj
+        && $TransactionObj->ObjectType eq 'RT::Ticket' )
     {
-        $args{'Ticket'} = $args{'Transaction'}->Object;
+        $TicketObj = $TransactionObj->Object;
     }
 
     if ( RT->Config->Get('GnuPG')->{'Enable'} ) {
         my %crypt;
 
         my $attachment;
-        $attachment = $args{'Transaction'}->Attachments->First
-            if $args{'Transaction'};
+        $attachment = $TransactionObj->Attachments->First
+            if $TransactionObj;
 
         foreach my $argument ( qw(Sign Encrypt) ) {
             next if defined $args{ $argument };
 
             if ( $attachment && defined $attachment->GetHeader("X-RT-$argument") ) {
                 $crypt{$argument} = $attachment->GetHeader("X-RT-$argument");
-            } elsif ( $args{'Ticket'} ) {
-                $crypt{$argument} = $args{'Ticket'}->QueueObj->$argument();
+            } elsif ( $TicketObj ) {
+                $crypt{$argument} = $TicketObj->QueueObj->$argument();
             }
         }
 
@@ -382,21 +386,18 @@ sub SendEmail {
     # if it is a sub routine, we just return it;
     return $mail_command->($args{'Entity'}) if UNIVERSAL::isa( $mail_command, 'CODE' );
 
-    my $ticketObj = $args{'Ticket'};
-    my $transactionObj = $args{'Transaction'};
-
     if ( $mail_command eq 'sendmailpipe' ) {
         my $path = RT->Config->Get('SendmailPath');
         my $args = RT->Config->Get('SendmailArguments');
-	
+
 	# SetOutgoingMailFrom
-	
+
 	my $outgoingMailAddress;
 	if ( RT->Config->Get('SetOutgoingMailFrom') ) {
-		if ( defined $ticketObj ){
-			my $queueName = $ticketObj->QueueObj->Name;
+		if ( defined $TicketObj ){
+			my $queueName = $TicketObj->QueueObj->Name;
 			if (not defined RT->Config->Get('OverrideOutgoingMailFrom')->{$queueName}) {
-				$outgoingMailAddress = $ticketObj->QueueObj->CorrespondAddress;
+				$outgoingMailAddress = $TicketObj->QueueObj->CorrespondAddress;
 			} else {
 				$outgoingMailAddress = RT->Config->Get('OverrideOutgoingMailFrom')->{$queueName};
 			}
@@ -409,13 +410,13 @@ sub SendEmail {
 
 	# Set Bounce Arguments
 	$args .= ' '. RT->Config->Get('SendmailBounceArguments') if $args{'Bounce'};
-	
+
 	# VERP
-        if ( $args{'Transaction'} and
+        if ( $TransactionObj and
              my $prefix = RT->Config->Get('VERPPrefix') and
              my $domain = RT->Config->Get('VERPDomain') )
         {
-            my $from = $args{'Transaction'}->CreatorObj->EmailAddress;
+            my $from = $TransactionObj->CreatorObj->EmailAddress;
             $from =~ s/@/=/g;
             $from =~ s/\s//g;
             $args .= " -f $prefix$from\@$domain";

commit 7c7494de7508343948e750db6210f7fb12bca9d8
Author: Shawn M Moore <sartak at bestpractical.com>
Date:   Fri Nov 6 15:44:53 2009 -0500

    Use spaces for indentation not tabs

diff --git a/lib/RT/Interface/Email.pm b/lib/RT/Interface/Email.pm
index ba15d3a..f2a978a 100755
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -390,28 +390,28 @@ sub SendEmail {
         my $path = RT->Config->Get('SendmailPath');
         my $args = RT->Config->Get('SendmailArguments');
 
-	# SetOutgoingMailFrom
-
-	my $outgoingMailAddress;
-	if ( RT->Config->Get('SetOutgoingMailFrom') ) {
-		if ( defined $TicketObj ){
-			my $queueName = $TicketObj->QueueObj->Name;
-			if (not defined RT->Config->Get('OverrideOutgoingMailFrom')->{$queueName}) {
-				$outgoingMailAddress = $TicketObj->QueueObj->CorrespondAddress;
-			} else {
-				$outgoingMailAddress = RT->Config->Get('OverrideOutgoingMailFrom')->{$queueName};
-			}
-			$args .= ' -f '.$outgoingMailAddress;
-		} elsif (RT->Config->Get('OverrideOutgoingMailFrom')->{'Default'} ) {
-			$outgoingMailAddress  = RT->Config->Get('OverrideOutgoingMailFrom')->{'Default'};
-			$args .= ' -f '.$outgoingMailAddress;
-		}
-	}
-
-	# Set Bounce Arguments
-	$args .= ' '. RT->Config->Get('SendmailBounceArguments') if $args{'Bounce'};
-
-	# VERP
+    # SetOutgoingMailFrom
+
+    my $outgoingMailAddress;
+    if ( RT->Config->Get('SetOutgoingMailFrom') ) {
+        if ( defined $TicketObj ){
+            my $queueName = $TicketObj->QueueObj->Name;
+            if (not defined RT->Config->Get('OverrideOutgoingMailFrom')->{$queueName}) {
+                $outgoingMailAddress = $TicketObj->QueueObj->CorrespondAddress;
+            } else {
+                $outgoingMailAddress = RT->Config->Get('OverrideOutgoingMailFrom')->{$queueName};
+            }
+            $args .= ' -f '.$outgoingMailAddress;
+        } elsif (RT->Config->Get('OverrideOutgoingMailFrom')->{'Default'} ) {
+            $outgoingMailAddress  = RT->Config->Get('OverrideOutgoingMailFrom')->{'Default'};
+            $args .= ' -f '.$outgoingMailAddress;
+        }
+    }
+
+    # Set Bounce Arguments
+    $args .= ' '. RT->Config->Get('SendmailBounceArguments') if $args{'Bounce'};
+
+    # VERP
         if ( $TransactionObj and
              my $prefix = RT->Config->Get('VERPPrefix') and
              my $domain = RT->Config->Get('VERPDomain') )

commit d3e08429723d85804365820f08e00d26173f08bf
Author: Shawn M Moore <sartak at bestpractical.com>
Date:   Fri Nov 6 15:48:24 2009 -0500

    More cleanup

diff --git a/lib/RT/Interface/Email.pm b/lib/RT/Interface/Email.pm
index f2a978a..97fe2a8 100755
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -392,20 +392,22 @@ sub SendEmail {
 
     # SetOutgoingMailFrom
 
-    my $outgoingMailAddress;
     if ( RT->Config->Get('SetOutgoingMailFrom') ) {
-        if ( defined $TicketObj ){
-            my $queueName = $TicketObj->QueueObj->Name;
-            if (not defined RT->Config->Get('OverrideOutgoingMailFrom')->{$queueName}) {
-                $outgoingMailAddress = $TicketObj->QueueObj->CorrespondAddress;
+        my $OutgoingMailAddress;
+
+        if ($TicketObj) {
+            my $QueueName = $TicketObj->QueueObj->Name;
+            if (not defined RT->Config->Get('OverrideOutgoingMailFrom')->{$QueueName}) {
+                $OutgoingMailAddress = $TicketObj->QueueObj->CorrespondAddress;
             } else {
-                $outgoingMailAddress = RT->Config->Get('OverrideOutgoingMailFrom')->{$queueName};
+                $OutgoingMailAddress = RT->Config->Get('OverrideOutgoingMailFrom')->{$QueueName};
             }
-            $args .= ' -f '.$outgoingMailAddress;
-        } elsif (RT->Config->Get('OverrideOutgoingMailFrom')->{'Default'} ) {
-            $outgoingMailAddress  = RT->Config->Get('OverrideOutgoingMailFrom')->{'Default'};
-            $args .= ' -f '.$outgoingMailAddress;
         }
+
+        $OutgoingMailAddress ||= RT->Config->Get('OverrideOutgoingMailFrom')->{'Default'};
+
+        $args .= ' -f ' . $OutgoingMailAddress
+            if $OutgoingMailAddress;
     }
 
     # Set Bounce Arguments

commit 17f46e8a81158500003451964240670d819e5e54
Author: Shawn M Moore <sartak at bestpractical.com>
Date:   Fri Nov 6 15:52:06 2009 -0500

    Avoid redefine warnings

diff --git a/lib/RT/Test.pm b/lib/RT/Test.pm
index 2f04a93..34e5fae 100644
--- a/lib/RT/Test.pm
+++ b/lib/RT/Test.pm
@@ -267,6 +267,7 @@ sub set_config_wrapper {
     my $self = shift;
 
     my $old_sub = \&RT::Config::Set;
+    no warnings 'redefine';
     *RT::Config::Set = sub {
         my @caller = caller;
         if ( ($caller[1]||'') =~ /\.t$/ ) {

commit 92a31983f4118638f3d93613b73a2e8308381de6
Author: Shawn M Moore <sartak at bestpractical.com>
Date:   Fri Nov 6 15:56:25 2009 -0500

    Document the Default key

diff --git a/etc/RT_Config.pm.in b/etc/RT_Config.pm.in
index 3481f94..8f06b93 100755
--- a/etc/RT_Config.pm.in
+++ b/etc/RT_Config.pm.in
@@ -390,9 +390,13 @@ C<$OverrideOutgoingMailFrom> is used for overwriting the Correspond
 address of the queue. The option is a hash reference of queue name to
 email address.
 
+If there is no ticket involved, then the value of the C<Default> key will be
+used.
+
 =cut
 
 Set($OverrideOutgoingMailFrom, {
+#    'Default' => 'admin at rt.example.com',
 #    'General' => 'general at rt.example.com',
 });
 

commit 9265075b4a4721ff85b71fb58147e65ece92d90d
Author: Shawn M Moore <sartak at bestpractical.com>
Date:   Fri Nov 6 15:58:15 2009 -0500

    Clean up some double-negative logic

diff --git a/lib/RT/Interface/Email.pm b/lib/RT/Interface/Email.pm
index 97fe2a8..2cae7a4 100755
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -397,10 +397,12 @@ sub SendEmail {
 
         if ($TicketObj) {
             my $QueueName = $TicketObj->QueueObj->Name;
-            if (not defined RT->Config->Get('OverrideOutgoingMailFrom')->{$QueueName}) {
-                $OutgoingMailAddress = $TicketObj->QueueObj->CorrespondAddress;
+            my $QueueAddressOverride = RT->Config->Get('OverrideOutgoingMailFrom')->{$QueueName};
+
+            if ($QueueAddressOverride) {
+                $OutgoingMailAddress = $QueueAddressOverride;
             } else {
-                $OutgoingMailAddress = RT->Config->Get('OverrideOutgoingMailFrom')->{$QueueName};
+                $OutgoingMailAddress = $TicketObj->QueueObj->CorrespondAddress;
             }
         }
 

commit dfcdec9aa8416d1e1e12bdeb9c95de76a2563954
Author: Shawn M Moore <sartak at bestpractical.com>
Date:   Fri Nov 6 16:00:46 2009 -0500

    Ignore t/tmp/

diff --git a/.gitignore b/.gitignore
index 59186a9..c5006d5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -15,6 +15,7 @@ Makefile
 t/data/gnupg/keyrings/random_seed
 t/data/configs/apache2.2+fastcgi.conf
 t/data/configs/apache2.2+mod_perl.conf
+t/tmp/
 sbin/rt-attributes-viewer
 sbin/rt-clean-sessions
 sbin/rt-dump-database

commit 882da8defc632e57ee56989c22fd4616c6aac812
Author: Shawn M Moore <sartak at bestpractical.com>
Date:   Fri Nov 6 16:03:11 2009 -0500

    Tidy

diff --git a/lib/RT/Interface/Email.pm b/lib/RT/Interface/Email.pm
index 2cae7a4..3091625 100755
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -390,32 +390,31 @@ sub SendEmail {
         my $path = RT->Config->Get('SendmailPath');
         my $args = RT->Config->Get('SendmailArguments');
 
-    # SetOutgoingMailFrom
-
-    if ( RT->Config->Get('SetOutgoingMailFrom') ) {
-        my $OutgoingMailAddress;
-
-        if ($TicketObj) {
-            my $QueueName = $TicketObj->QueueObj->Name;
-            my $QueueAddressOverride = RT->Config->Get('OverrideOutgoingMailFrom')->{$QueueName};
-
-            if ($QueueAddressOverride) {
-                $OutgoingMailAddress = $QueueAddressOverride;
-            } else {
-                $OutgoingMailAddress = $TicketObj->QueueObj->CorrespondAddress;
+        # SetOutgoingMailFrom
+        if ( RT->Config->Get('SetOutgoingMailFrom') ) {
+            my $OutgoingMailAddress;
+
+            if ($TicketObj) {
+                my $QueueName = $TicketObj->QueueObj->Name;
+                my $QueueAddressOverride = RT->Config->Get('OverrideOutgoingMailFrom')->{$QueueName};
+
+                if ($QueueAddressOverride) {
+                    $OutgoingMailAddress = $QueueAddressOverride;
+                } else {
+                    $OutgoingMailAddress = $TicketObj->QueueObj->CorrespondAddress;
+                }
             }
-        }
 
-        $OutgoingMailAddress ||= RT->Config->Get('OverrideOutgoingMailFrom')->{'Default'};
+            $OutgoingMailAddress ||= RT->Config->Get('OverrideOutgoingMailFrom')->{'Default'};
 
-        $args .= ' -f ' . $OutgoingMailAddress
-            if $OutgoingMailAddress;
-    }
+            $args .= " -f $OutgoingMailAddress"
+                if $OutgoingMailAddress;
+        }
 
-    # Set Bounce Arguments
-    $args .= ' '. RT->Config->Get('SendmailBounceArguments') if $args{'Bounce'};
+        # Set Bounce Arguments
+        $args .= ' '. RT->Config->Get('SendmailBounceArguments') if $args{'Bounce'};
 
-    # VERP
+        # VERP
         if ( $TransactionObj and
              my $prefix = RT->Config->Get('VERPPrefix') and
              my $domain = RT->Config->Get('VERPDomain') )

commit cf02ac1c1509cd06c7bb3045dd905a58fc581e93
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Fri Oct 23 02:08:41 2009 +0400

    in error message we were using static value, when it's dynamic

diff --git a/lib/RT/Interface/Email.pm b/lib/RT/Interface/Email.pm
index 3091625..b669b5b 100755
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -656,7 +656,7 @@ sub SendForward {
         $RT::Logger->warning($msg);
     }
     unless ( $mail ) {
-        $RT::Logger->warning("Couldn't generate email using template 'Forward'");
+        $RT::Logger->warning("Couldn't generate email using template '$args{Template}'");
 
         my $description;
         unless ( $args{'Transaction'} ) {

commit dfcb998dbb52b890bc55d3b6112ffe080a7e818e
Author: Emmanuel Lacour <elacour at easter-eggs.com>
Date:   Fri Nov 6 12:48:25 2009 +0100

    Fix perldoc for Queue object
    
    * remove =testing that make perldoc stop just after it
    * add description like other RT objects

diff --git a/lib/RT/Queue_Overlay.pm b/lib/RT/Queue_Overlay.pm
index d5d8651..a839679 100755
--- a/lib/RT/Queue_Overlay.pm
+++ b/lib/RT/Queue_Overlay.pm
@@ -56,14 +56,10 @@
 
 =head1 DESCRIPTION
 
+An RT queue object.
 
 =head1 METHODS
 
-=begin testing 
-
-use RT::Queue;
-
-
 =cut
 
 

commit 28454656545278bc38aabe3b2b63fc64f4541152
Author: Emmanuel Lacour <elacour at easter-eggs.com>
Date:   Fri Nov 6 13:01:13 2009 +0100

    Fix shredder documentation typo

diff --git a/lib/RT/Shredder/Plugin/Objects.pm b/lib/RT/Shredder/Plugin/Objects.pm
index 787c44e..fe2314a 100644
--- a/lib/RT/Shredder/Plugin/Objects.pm
+++ b/lib/RT/Shredder/Plugin/Objects.pm
@@ -60,7 +60,7 @@ RT::Shredder::Plugin::Objects - search plugin for wiping any selected object.
 
 =head1 ARGUMENTS
 
-This plugin searches and RT object you want, so you can use
+This plugin searches an RT object you want, so you can use
 the object name as argument and id as value, for example if
 you want select ticket #123 then from CLI you write next
 command:

commit 59000984a6f50c1290a81d116c38a0a87534eae0
Author: Kevin Falcone <falcone at bestpractical.com>
Date:   Sun Nov 8 11:16:16 2009 -0500

    Stop people with old DateTime or DateTime::Locales from exploding in Preferences
    
    We need to find a better way to work around the DateTime namespace
    conflicts

diff --git a/lib/RT/Date.pm b/lib/RT/Date.pm
index 18f7ce7..fc4c43c 100755
--- a/lib/RT/Date.pm
+++ b/lib/RT/Date.pm
@@ -117,7 +117,8 @@ our @FORMATTERS = (
     'RFC2616',       # loc
     'iCal',          # loc
 );
-if ( eval 'use DateTime qw(); 1;' && eval 'use DateTime::Locale qw(); 1;' ) {
+if ( eval 'use DateTime qw(); 1;' && eval 'use DateTime::Locale qw(); 1;' && 
+     DateTime->can('format_cldr') && DateTime::Locale::root->can('date_format_full') ) {
     push @FORMATTERS, 'LocalizedDateTime'; # loc
 }
 
@@ -501,6 +502,9 @@ as described in L</Get>.
 
 sub DateTime {
     my $self = shift;
+    unless (defined $self) {
+        use Carp; Carp::confess("undefined $self");
+    }
     return $self->Get( @_, Date => 1, Time => 1 );
 }
 
@@ -650,6 +654,10 @@ sub LocalizedDateTime
 
     return $self->loc("DateTime module missing") unless ( eval 'use DateTime qw(); 1;' );
     return $self->loc("DateTime::Locale module missing") unless ( eval 'use DateTime::Locale qw(); 1;' );
+    return $self->loc("DateTime doesn't support format_cldr, you must upgrade to use this feature") 
+        unless can DateTime::('format_cldr');
+
+
     my $date_format = $args{'DateFormat'};
     my $time_format = $args{'TimeFormat'};
 
@@ -661,6 +669,8 @@ sub LocalizedDateTime
     
 
     my $formatter = DateTime::Locale->load($lang);
+    return $self->loc("DateTime::Locale doesn't support date_format_full, you must upgrade to use this feature") 
+        unless $formatter->can('date_format_full');
     $date_format = $formatter->$date_format;
     $time_format = $formatter->$time_format;
     $date_format =~ s/EEEE/EEE/g if ( $args{'AbbrDay'} );
diff --git a/t/api/date.t b/t/api/date.t
index 7d0e853..bc1446f 100644
--- a/t/api/date.t
+++ b/t/api/date.t
@@ -9,7 +9,8 @@ my $localized_datetime_tests;
 BEGIN {
     $tests = 167;
     $localized_datetime_tests =
-      eval { require DateTime; 1; } && eval { require DateTime::Locale; 1; };
+      eval { require DateTime; 1; } && eval { require DateTime::Locale; 1; } &&
+      DateTime->can('format_cldr') && DateTime::Locale::root->can('date_format_full');
 
     if ($localized_datetime_tests) {
 

commit e221be4bb8168d36c6cf40f83f3d02d9d738273a
Author: Emmanuel Lacour <elacour at easter-eggs.com>
Date:   Thu Nov 12 18:16:48 2009 +0100

    Fix warning message
    
        It's not possible to concatenate strings with  conditions like "() ? :"

diff --git a/lib/RT/User_Overlay.pm b/lib/RT/User_Overlay.pm
index a7982e5..db3964c 100755
--- a/lib/RT/User_Overlay.pm
+++ b/lib/RT/User_Overlay.pm
@@ -1140,7 +1140,7 @@ sub SetDisabled {
     my $set_err = $self->PrincipalObj->SetDisabled($val);
     unless ($set_err) {
         $RT::Handle->Rollback();
-        $RT::Logger->warning("Couldn't ".($val == 1) ? "disable" : "enable"." user ".$self->PrincipalObj->Id);
+        $RT::Logger->warning(sprintf("Couldn't %s user %s", ($val == 1) ? "disable" : "enable", $self->PrincipalObj->Id));
         return (undef);
     }
     $self->_NewTransaction( Type => ($val == 1) ? "Disabled" : "Enabled" );

commit 054c5f3bb927914c91a167b8f749ef5a69937673
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Fri Nov 13 16:09:36 2009 -0500

    Move all JS for hierarchical CFs onto derivative field; remove DerivativeCFs method
    
    Oracle does not allow for SQL queries which attemt to match on the
    'Content' field of attrbiutes, as it is a CLOB column -- additionally,
    searches on it are not likely to be well-indexed.  Because of this, we
    move all code involving hierarchical CFs onto display of the
    derivative field, instead of being split between base and derivative
    field; this saves us the need to list all derivative CFs.
    
    As part of this, remove the (non-functioning under Oracle) and
    now-unused DerivativeCFs method on CustomField.

diff --git a/lib/RT/CustomField_Overlay.pm b/lib/RT/CustomField_Overlay.pm
index e0df9ad..9286d7a 100755
--- a/lib/RT/CustomField_Overlay.pm
+++ b/lib/RT/CustomField_Overlay.pm
@@ -1289,16 +1289,4 @@ sub BasedOnObj {
     return $obj;
 }
 
-sub DerivativeCFs {
-    my $self = shift;
-    my $attrs = RT::Attributes->new( $self->CurrentUser );
-    $attrs->Limit( FIELD => 'ObjectType', VALUE => 'RT::CustomField' );
-    $attrs->Limit( FIELD => 'Name',       VALUE => 'BasedOn' );
-    $attrs->Limit( FIELD => 'Content',    VALUE => $self->id );
-
-    my @cfs;
-    push @cfs, $_->Object while $_ = $attrs->Next;
-    return @cfs;
-}
-
 1;
diff --git a/share/html/Elements/EditCustomFieldSelect b/share/html/Elements/EditCustomFieldSelect
index 4aec2b0..8fe79f9 100644
--- a/share/html/Elements/EditCustomFieldSelect
+++ b/share/html/Elements/EditCustomFieldSelect
@@ -62,6 +62,26 @@
       <option value="<% $name %>"><% '&nbsp;' x $depth |n %><% $name %></option>
 %   }
     </select><br />
+% } elsif ($CustomField->BasedOnObj->id) {
+<script type="text/javascript" src="<%RT->Config->Get('WebPath')%>/NoAuth/js/cascaded.js"></script>
+<script type="text/javascript"><!--
+doOnLoad(  function () {
+    var basedon = document.getElementById('<% $NamePrefix . $CustomField->BasedOnObj->id %>-Values');
+    if (basedon != null) {
+        var oldchange = basedon.onchange;
+        basedon.onchange = function () {
+            filter_cascade(
+                '<% $id %>-Values',
+                basedon.value,
+                1
+            );
+            if (oldchange != null)
+                oldchange();
+        };
+        basedon.onchange();
+    }
+});
+--></script>
 % }
 % if (@category) {
 %# this hidden select is to supply a full list of values,
@@ -71,16 +91,7 @@
 %       $m->out($out);
       </select>
 % }
-% my @Derivatives = map {$_->id} $CustomField->DerivativeCFs;
-% if (@Derivatives) {
-  <script type="text/javascript" src="<%RT->Config->Get('WebPath')%>/NoAuth/js/cascaded.js"></script>
-<script type="text/javascript"><!--
-doOnLoad(  function () {<% join(";", map {"filter_cascade('$NamePrefix$_-Values',document.getElementById('$id-Values').value, 1)"} @Derivatives) |n%>} );
---></script>
-<select onchange="<% join(";", map {"filter_cascade('$NamePrefix$_-Values', this.value, 1)"} @Derivatives) |n%>"
-% } else {
 <select
-% }
   name="<%$id%>-Values" id="<%$id%>-Values" class="CF-<%$CustomField->id%>-Edit"
 % if ( $Rows && ( $Multiple || !@category ) ) {
   size="<% $Rows %>"

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


More information about the Rt-commit mailing list