LP#1485374: Use client TZ in the database when supplied to the server
authorMike Rylander <mrylander@gmail.com>
Mon, 3 Aug 2015 17:27:56 +0000 (13:27 -0400)
committerKathy Lussier <klussier@masslnc.org>
Fri, 27 Jan 2017 19:45:50 +0000 (14:45 -0500)
In LP#1485371 we teach OpenSRF how to discover and pass the client timezone
to the server.  Now we can use that information to temporarily put the server
into that timezone, including database sessions, so that dates and times
are calculated and reported based on the timezone of the client.

To do that we:

 * Teach CStore and friends to use the client-supplied time zone
 * Teach Storage to use $ENV{TZ} for the db timezone inside transactions,
   and by default except for search-y methods
 * Teach the TPAC to set the client TZ at login

This requires javascript to be enabled in the TPAC, and investigates the
client timezone at patron login time.  No times are displayed in TPAC
interfaces before the patron logs in, so there is no point in detecting
the timezone before this point.

Signed-off-by: Mike Rylander <mrylander@gmail.com>
Signed-off-by: Galen Charlton <gmc@esilibrary.com>
Signed-off-by: Kathy Lussier <klussier@masslnc.org>
Conflicts:
Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader.pm

Signed-off-by: Jason Stephenson <jason@sigio.com>
Signed-off-by: Kathy Lussier <klussier@masslnc.org>
Open-ILS/src/c-apps/oils_sql.c
Open-ILS/src/perlmods/lib/OpenILS/Application/Storage.pm
Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/Driver/Pg/storage.pm
Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/Publisher/metabib.pm
Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader.pm
Open-ILS/src/templates/opac/parts/js.tt2
Open-ILS/src/templates/opac/parts/login/form.tt2

index bacb867..9ea52f6 100644 (file)
@@ -146,6 +146,7 @@ static char* modulename = NULL;
 
 int writeAuditInfo( osrfMethodContext* ctx, const char* user_id, const char* ws_id);
 
+static char* _sanitize_tz_name( const char* tz );
 static char* _sanitize_savepoint_name( const char* sp );
 
 /**
@@ -827,6 +828,8 @@ int beginTransaction( osrfMethodContext* ctx ) {
                return -1;
        }
 
+       const char* tz = _sanitize_tz_name(ctx->session->session_tz);
+
        if( enforce_pcrud ) {
                timeout_needs_resetting = 1;
                const jsonObject* user = verifyUserPCRUD( ctx );
@@ -851,8 +854,36 @@ int beginTransaction( osrfMethodContext* ctx ) {
                jsonObject* ret = jsonNewObject( getXactId( ctx ) );
                osrfAppRespondComplete( ctx, ret );
                jsonObjectFree( ret );
-               return 0;
+
+       }
+
+       if (tz) {
+               setenv("TZ",tz,1);
+               dbi_result tz_res = dbi_conn_queryf( writehandle, "SET LOCAL timezone TO '%s'; -- cstore", tz );
+               if( !tz_res ) {
+                       osrfLogError( OSRF_LOG_MARK, "%s: Error setting timezone %s", modulename, tz);
+                       if( !oilsIsDBConnected( writehandle )) {
+                               osrfAppSessionPanic( ctx->session );
+                               return -1;
+                       }
+               } else {
+                       dbi_result_free( tz_res );
+               }
+       } else {
+               unsetenv("TZ");
+               dbi_result res = dbi_conn_queryf( writehandle, "SET timezone TO DEFAULT; -- no tz" );
+               if( !res ) {
+                       osrfLogError( OSRF_LOG_MARK, "%s: Error resetting timezone", modulename);
+                       if( !oilsIsDBConnected( writehandle )) {
+                               osrfAppSessionPanic( ctx->session );
+                               return -1;
+                       }
+               } else {
+                       dbi_result_free( res );
+               }
        }
+
+       return 0;
 }
 
 /**
@@ -5826,6 +5857,8 @@ int doJSONSearch ( osrfMethodContext* ctx ) {
 static jsonObject* doFieldmapperSearch( osrfMethodContext* ctx, osrfHash* class_meta,
                jsonObject* where_hash, jsonObject* query_hash, int* err ) {
 
+       const char* tz = _sanitize_tz_name(ctx->session->session_tz);
+
        // XXX for now...
        dbhandle = writehandle;
 
@@ -5853,7 +5886,40 @@ static jsonObject* doFieldmapperSearch( osrfMethodContext* ctx, osrfHash* class_
 
        osrfLogDebug( OSRF_LOG_MARK, "%s SQL =  %s", modulename, sql );
 
+       // Setting the timezone if requested and not in a transaction
+       if (!getXactId(ctx)) {
+               if (tz) {
+                       setenv("TZ",tz,1);
+                       dbi_result tz_res = dbi_conn_queryf( writehandle, "SET timezone TO '%s'; -- cstore", tz );
+                       if( !tz_res ) {
+                               osrfLogError( OSRF_LOG_MARK, "%s: Error setting timezone %s", modulename, tz);
+                               osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR,
+                                       "osrfMethodException", ctx->request, "Error setting timezone" );
+                               if( !oilsIsDBConnected( writehandle )) {
+                                       osrfAppSessionPanic( ctx->session );
+                                       return -1;
+                               }
+                       } else {
+                               dbi_result_free( tz_res );
+                       }
+               } else {
+                       unsetenv("TZ");
+                       dbi_result res = dbi_conn_queryf( writehandle, "SET timezone TO DEFAULT; -- cstore" );
+                       if( !res ) {
+                               osrfLogError( OSRF_LOG_MARK, "%s: Error resetting timezone", modulename);
+                               if( !oilsIsDBConnected( writehandle )) {
+                                       osrfAppSessionPanic( ctx->session );
+                                       return -1;
+                               }
+                       } else {
+                               dbi_result_free( res );
+                       }
+               }
+       }
+
+
        dbi_result result = dbi_conn_query( dbhandle, sql );
+
        if( NULL == result ) {
                const char* msg;
                int errnum = dbi_conn_error( dbhandle, &msg );
@@ -5875,6 +5941,7 @@ static jsonObject* doFieldmapperSearch( osrfMethodContext* ctx, osrfHash* class_
 
        } else {
                osrfLogDebug( OSRF_LOG_MARK, "Query returned with no errors" );
+
        }
 
        jsonObject* res_list = jsonNewObjectType( JSON_ARRAY );
@@ -7534,4 +7601,46 @@ static char* _sanitize_savepoint_name( const char* sp ) {
        return safeSpName;
 }
 
+/**
+       @brief Remove all but safe character from TZ name
+       @param tz User-supplied TZ name
+       @return sanitized TZ name, or NULL
+
+    The caller is expected to free the returned string.  Note that
+    this function exists only because we can't use PQescapeLiteral
+    without either forking libdbi or abandoning it.
+*/
+static char* _sanitize_tz_name( const char* tz ) {
+
+       if (NULL == tz) return NULL;
+
+       const char* safe_chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ012345789_/-+";
+
+       // PostgreSQL uses NAMEDATALEN-1 as a max length for identifiers,
+       // and the default value of NAMEDATALEN is 64; that should be long enough
+       // for our purposes, and it's unlikely that anyone is going to recompile
+       // PostgreSQL to have a smaller value, so cap the identifier name
+       // accordingly to avoid the remote chance that someone manages to pass in a
+       // 12GB savepoint name
+       const int MAX_LITERAL_NAMELEN = 63;
+       int len = 0;
+       len = strlen( tz );
+       if (len > MAX_LITERAL_NAMELEN) {
+               len = MAX_LITERAL_NAMELEN;
+       }
+
+       char* safeSpName = safe_malloc( len + 1 );
+       int i = 0;
+       int j;
+       char* found;
+       for (j = 0; j < len; j++) {
+       found = strchr(safe_chars, tz[j]);
+               if (found) {
+                       safeSpName[ i++ ] = found[0];
+               }
+       }
+       safeSpName[ i ] = '\0';
+       return safeSpName;
+}
+
 /*@}*/
index 7401846..3b55c62 100644 (file)
@@ -51,6 +51,64 @@ sub initialize {
     $log->debug("We seem to be OK...",DEBUG);
 }
 
+sub register_method {
+    my $class = shift;
+    my %args = @_;
+
+    $args{package} ||= ref($class) || $class;
+
+    unless ($args{no_tz_force}) {
+        my %dup_args = %args;
+        $dup_args{api_name} = 'no_tz.' . $args{api_name};
+
+        $args{method} = 'force_db_tz';
+        delete $args{package};
+
+        __PACKAGE__->SUPER::register_method( %dup_args );
+
+    }
+
+    __PACKAGE__->SUPER::register_method( %args );
+
+}
+
+sub force_db_tz {
+    my $self = shift;
+    my $client = shift;
+    my @args = @_;
+
+    my ($current_xact) = $self->method_lookup('no_tz.open-ils.storage.transaction.current')->run;
+
+    if (!$current_xact && $ENV{TZ}) {
+        try {
+            OpenILS::Application::Storage::CDBI->db_Main->do(
+                'SET timezone TO ?;',
+                {},
+                $ENV{TZ}
+            );
+        } catch Error with {
+            $log->error( "Could not set timezone: $ENV{TZ}");
+        };
+    }
+
+    my $method = $self->method_lookup('no_tz.' . $self->{api_name});
+    die unless $method;
+
+    $client->respond( $_ ) for ( $method->run(@args) );
+
+    if (!$current_xact && $ENV{TZ}) {
+        try {
+            OpenILS::Application::Storage::CDBI->db_Main->do(
+                'SET timezone TO DEFAULT;',
+            );
+        } catch Error with {
+            $log->error( "Could not reset default timezone");
+        };
+    }
+
+    return undef;
+}
+
 sub child_init {
 
     $log->debug('Running child_init for ' . __PACKAGE__ . '...', DEBUG);
index c9fd303..157aa7c 100644 (file)
             throw $e;
         };
 
+        if ($ENV{TZ}) {
+            try {
+                $dbh->do('SET LOCAL timezone TO ?;',{},$ENV{TZ});
+
+            } catch Error with {
+                my $e = shift;
+                $log->debug("Failed to set timezone: $ENV{TZ}", WARN);
+            };
+        }
 
-        my $death_cb = $client->session->register_callback(
-            death => sub {
-                __PACKAGE__->pg_rollback_xaction;
-            }
-        );
-
-        $log->debug("Registered 'death' callback [$death_cb] for new transaction with Open-ILS XACT-ID [$xact_id]", DEBUG);
-
-        $client->session->session_data( death_cb => $death_cb );
 
-        if ($self->api_name =~ /autocommit$/o) {
-            $pg->current_xact_is_auto(1);
-            my $dc_cb = $client->session->register_callback(
-                disconnect => sub {
-                    my $ses = shift;
-                    $ses->unregister_callback(death => $death_cb);
-                    __PACKAGE__->pg_commit_xaction;
+        if ($client->session) { # not a subrequest
+            my $death_cb = $client->session->register_callback(
+                death => sub {
+                    __PACKAGE__->pg_rollback_xaction;
                 }
             );
-            $log->debug("Registered 'disconnect' callback [$dc_cb] for new transaction with Open-ILS XACT-ID [$xact_id]", DEBUG);
-            if ($client and $client->session) {
-                $client->session->session_data( disconnect_cb => $dc_cb );
+    
+            $log->debug("Registered 'death' callback [$death_cb] for new transaction with Open-ILS XACT-ID [$xact_id]", DEBUG);
+    
+            $client->session->session_data( death_cb => $death_cb );
+    
+            if ($self->api_name =~ /autocommit$/o) {
+                $pg->current_xact_is_auto(1);
+                my $dc_cb = $client->session->register_callback(
+                    disconnect => sub {
+                        my $ses = shift;
+                        $ses->unregister_callback(death => $death_cb);
+                        __PACKAGE__->pg_commit_xaction;
+                    }
+                );
+                $log->debug("Registered 'disconnect' callback [$dc_cb] for new transaction with Open-ILS XACT-ID [$xact_id]", DEBUG);
+                if ($client and $client->session) {
+                    $client->session->session_data( disconnect_cb => $dc_cb );
+                }
             }
         }
 
             $success = 0;
         };
         
-        $pg->current_xact_session->unregister_callback( death => 
-            $pg->current_xact_session->session_data( 'death_cb' )
-        ) if ($pg->current_xact_session);
-
-        if ($pg->current_xact_is_auto) {
-            $pg->current_xact_session->unregister_callback( disconnect => 
-                $pg->current_xact_session->session_data( 'disconnect_cb' )
-            );
+        if ($pg->current_xact_session) { # not a subrequest
+            $pg->current_xact_session->unregister_callback( death => 
+                $pg->current_xact_session->session_data( 'death_cb' )
+            ) if ($pg->current_xact_session);
+    
+            if ($pg->current_xact_is_auto) {
+                $pg->current_xact_session->unregister_callback( disconnect => 
+                    $pg->current_xact_session->session_data( 'disconnect_cb' )
+                );
+            }
         }
 
         $pg->unset_xact_session;
             $success = 0;
         };
     
-        $pg->current_xact_session->unregister_callback( death =>
-            $pg->current_xact_session->session_data( 'death_cb' )
-        ) if ($pg->current_xact_session);
-
-        if ($pg->current_xact_is_auto) {
-            $pg->current_xact_session->unregister_callback( disconnect =>
-                $pg->current_xact_session->session_data( 'disconnect_cb' )
-            );
+        if ($pg->current_xact_session) { # not a subrequest
+            $pg->current_xact_session->unregister_callback( death =>
+                $pg->current_xact_session->session_data( 'death_cb' )
+            ) if ($pg->current_xact_session);
+    
+            if ($pg->current_xact_is_auto) {
+                $pg->current_xact_session->unregister_callback( disconnect =>
+                    $pg->current_xact_session->session_data( 'disconnect_cb' )
+                );
+            }
         }
 
         $pg->unset_xact_session;
 
         $pg->set_audit_session( $client->session );
 
-        my $death_cb = $client->session->register_callback(
-            death => sub {
-                __PACKAGE__->pg_clear_audit_info;
-            }
-        );
-
-        $log->debug("Registered 'death' callback [$death_cb] for clearing audit information", DEBUG);
-
-        $client->session->session_data( death_cb_ai => $death_cb );
+        if ($client->session) { # not a subrequest
+            my $death_cb = $client->session->register_callback(
+                death => sub {
+                    __PACKAGE__->pg_clear_audit_info;
+                }
+            );
+    
+            $log->debug("Registered 'death' callback [$death_cb] for clearing audit information", DEBUG);
+    
+            $client->session->session_data( death_cb_ai => $death_cb );
+        }
 
         return 1;
 
             $log->debug("Failed to clear audit information: ".$e, INFO);
         };
 
-        $pg->current_audit_session->unregister_callback( death => 
-            $pg->current_audit_session->session_data( 'death_cb_ai' )
-        ) if ($pg->current_audit_session);
+        if ($pg->current_audit_session) { # not a subrequest
+            $pg->current_audit_session->unregister_callback( death => 
+                $pg->current_audit_session->session_data( 'death_cb_ai' )
+            ) if ($pg->current_audit_session);
+        }
 
         $pg->unset_audit_session;
     }
index 5fea5c7..7aec5f6 100644 (file)
@@ -150,12 +150,14 @@ sub ordered_records_from_metarecord { # XXX Replace with QP-based search-within-
 }
 __PACKAGE__->register_method(
     api_name    => 'open-ils.storage.ordered.metabib.metarecord.records',
+    no_tz_force => 1,
     method      => 'ordered_records_from_metarecord',
     api_level   => 1,
     cachable    => 1,
 );
 __PACKAGE__->register_method(
     api_name    => 'open-ils.storage.ordered.metabib.metarecord.records.staff',
+    no_tz_force => 1,
     method      => 'ordered_records_from_metarecord',
     api_level   => 1,
     cachable    => 1,
@@ -163,12 +165,14 @@ __PACKAGE__->register_method(
 
 __PACKAGE__->register_method(
     api_name    => 'open-ils.storage.ordered.metabib.metarecord.records.atomic',
+    no_tz_force => 1,
     method      => 'ordered_records_from_metarecord',
     api_level   => 1,
     cachable    => 1,
 );
 __PACKAGE__->register_method(
     api_name    => 'open-ils.storage.ordered.metabib.metarecord.records.staff.atomic',
+    no_tz_force => 1,
     method      => 'ordered_records_from_metarecord',
     api_level   => 1,
     cachable    => 1,
@@ -206,12 +210,14 @@ sub isxn_search {
 }
 __PACKAGE__->register_method(
     api_name    => 'open-ils.storage.id_list.biblio.record_entry.search.isbn',
+    no_tz_force => 1,
     method      => 'isxn_search',
     api_level   => 1,
     stream      => 1,
 );
 __PACKAGE__->register_method(
     api_name    => 'open-ils.storage.id_list.biblio.record_entry.search.issn',
+    no_tz_force => 1,
     method      => 'isxn_search',
     api_level   => 1,
     stream      => 1,
@@ -363,6 +369,7 @@ sub metarecord_copy_count {
 }
 __PACKAGE__->register_method(
     api_name    => 'open-ils.storage.metabib.metarecord.copy_count',
+    no_tz_force => 1,
     method      => 'metarecord_copy_count',
     api_level   => 1,
     stream      => 1,
@@ -370,6 +377,7 @@ __PACKAGE__->register_method(
 );
 __PACKAGE__->register_method(
     api_name    => 'open-ils.storage.metabib.metarecord.copy_count.staff',
+    no_tz_force => 1,
     method      => 'metarecord_copy_count',
     api_level   => 1,
     stream      => 1,
@@ -652,6 +660,7 @@ sub biblio_multi_search_full_rec {
 }
 __PACKAGE__->register_method(
     api_name    => 'open-ils.storage.biblio.full_rec.multi_search',
+    no_tz_force => 1,
     method      => 'biblio_multi_search_full_rec',
     api_level   => 1,
     stream      => 1,
@@ -659,6 +668,7 @@ __PACKAGE__->register_method(
 );
 __PACKAGE__->register_method(
     api_name    => 'open-ils.storage.biblio.full_rec.multi_search.staff',
+    no_tz_force => 1,
     method      => 'biblio_multi_search_full_rec',
     api_level   => 1,
     stream      => 1,
@@ -713,6 +723,7 @@ sub search_full_rec {
 }
 __PACKAGE__->register_method(
     api_name    => 'open-ils.storage.direct.metabib.full_rec.search_fts.value',
+    no_tz_force => 1,
     method      => 'search_full_rec',
     api_level   => 1,
     stream      => 1,
@@ -720,6 +731,7 @@ __PACKAGE__->register_method(
 );
 __PACKAGE__->register_method(
     api_name    => 'open-ils.storage.direct.metabib.full_rec.search_fts.index_vector',
+    no_tz_force => 1,
     method      => 'search_full_rec',
     api_level   => 1,
     stream      => 1,
@@ -879,6 +891,7 @@ sub search_class_fts {
 for my $class ( qw/title author subject keyword series identifier/ ) {
     __PACKAGE__->register_method(
         api_name    => "open-ils.storage.metabib.$class.search_fts.metarecord",
+        no_tz_force => 1,
         method      => 'search_class_fts',
         api_level   => 1,
         stream      => 1,
@@ -887,6 +900,7 @@ for my $class ( qw/title author subject keyword series identifier/ ) {
     );
     __PACKAGE__->register_method(
         api_name    => "open-ils.storage.metabib.$class.search_fts.metarecord.unordered",
+        no_tz_force => 1,
         method      => 'search_class_fts',
         api_level   => 1,
         stream      => 1,
@@ -895,6 +909,7 @@ for my $class ( qw/title author subject keyword series identifier/ ) {
     );
     __PACKAGE__->register_method(
         api_name    => "open-ils.storage.metabib.$class.search_fts.metarecord.staff",
+        no_tz_force => 1,
         method      => 'search_class_fts',
         api_level   => 1,
         stream      => 1,
@@ -903,6 +918,7 @@ for my $class ( qw/title author subject keyword series identifier/ ) {
     );
     __PACKAGE__->register_method(
         api_name    => "open-ils.storage.metabib.$class.search_fts.metarecord.staff.unordered",
+        no_tz_force => 1,
         method      => 'search_class_fts',
         api_level   => 1,
         stream      => 1,
@@ -1027,6 +1043,7 @@ sub search_class_fts_count {
 for my $class ( qw/title author subject keyword series identifier/ ) {
     __PACKAGE__->register_method(
         api_name    => "open-ils.storage.metabib.$class.search_fts.metarecord_count",
+        no_tz_force => 1,
         method      => 'search_class_fts_count',
         api_level   => 1,
         stream      => 1,
@@ -1035,6 +1052,7 @@ for my $class ( qw/title author subject keyword series identifier/ ) {
     );
     __PACKAGE__->register_method(
         api_name    => "open-ils.storage.metabib.$class.search_fts.metarecord_count.staff",
+        no_tz_force => 1,
         method      => 'search_class_fts_count',
         api_level   => 1,
         stream      => 1,
@@ -1400,6 +1418,7 @@ sub postfilter_search_class_fts {
 for my $class ( qw/title author subject keyword series identifier/ ) {
     __PACKAGE__->register_method(
         api_name    => "open-ils.storage.metabib.$class.post_filter.search_fts.metarecord",
+        no_tz_force => 1,
         method      => 'postfilter_search_class_fts',
         api_level   => 1,
         stream      => 1,
@@ -1408,6 +1427,7 @@ for my $class ( qw/title author subject keyword series identifier/ ) {
     );
     __PACKAGE__->register_method(
         api_name    => "open-ils.storage.metabib.$class.post_filter.search_fts.metarecord.staff",
+        no_tz_force => 1,
         method      => 'postfilter_search_class_fts',
         api_level   => 1,
         stream      => 1,
@@ -1891,6 +1911,7 @@ sub postfilter_search_multi_class_fts {
 
 __PACKAGE__->register_method(
     api_name    => "open-ils.storage.metabib.post_filter.multiclass.search_fts.metarecord",
+    no_tz_force => 1,
     method      => 'postfilter_search_multi_class_fts',
     api_level   => 1,
     stream      => 1,
@@ -1898,6 +1919,7 @@ __PACKAGE__->register_method(
 );
 __PACKAGE__->register_method(
     api_name    => "open-ils.storage.metabib.post_filter.multiclass.search_fts.metarecord.staff",
+    no_tz_force => 1,
     method      => 'postfilter_search_multi_class_fts',
     api_level   => 1,
     stream      => 1,
@@ -1906,6 +1928,7 @@ __PACKAGE__->register_method(
 
 __PACKAGE__->register_method(
     api_name    => "open-ils.storage.metabib.multiclass.search_fts",
+    no_tz_force => 1,
     method      => 'postfilter_search_multi_class_fts',
     api_level   => 1,
     stream      => 1,
@@ -1913,6 +1936,7 @@ __PACKAGE__->register_method(
 );
 __PACKAGE__->register_method(
     api_name    => "open-ils.storage.metabib.multiclass.search_fts.staff",
+    no_tz_force => 1,
     method      => 'postfilter_search_multi_class_fts',
     api_level   => 1,
     stream      => 1,
@@ -2298,6 +2322,7 @@ sub biblio_search_multi_class_fts {
 
 __PACKAGE__->register_method(
     api_name    => "open-ils.storage.biblio.multiclass.search_fts.record",
+    no_tz_force => 1,
     method      => 'biblio_search_multi_class_fts',
     api_level   => 1,
     stream      => 1,
@@ -2305,6 +2330,7 @@ __PACKAGE__->register_method(
 );
 __PACKAGE__->register_method(
     api_name    => "open-ils.storage.biblio.multiclass.search_fts.record.staff",
+    no_tz_force => 1,
     method      => 'biblio_search_multi_class_fts',
     api_level   => 1,
     stream      => 1,
@@ -2312,6 +2338,7 @@ __PACKAGE__->register_method(
 );
 __PACKAGE__->register_method(
     api_name    => "open-ils.storage.biblio.multiclass.search_fts",
+    no_tz_force => 1,
     method      => 'biblio_search_multi_class_fts',
     api_level   => 1,
     stream      => 1,
@@ -2319,6 +2346,7 @@ __PACKAGE__->register_method(
 );
 __PACKAGE__->register_method(
     api_name    => "open-ils.storage.biblio.multiclass.search_fts.staff",
+    no_tz_force => 1,
     method      => 'biblio_search_multi_class_fts',
     api_level   => 1,
     stream      => 1,
@@ -2595,6 +2623,7 @@ sub staged_fts {
 }
 __PACKAGE__->register_method(
     api_name    => "open-ils.storage.biblio.multiclass.staged.search_fts",
+    no_tz_force => 1,
     method      => 'staged_fts',
     api_level   => 0,
     stream      => 1,
@@ -2602,6 +2631,7 @@ __PACKAGE__->register_method(
 );
 __PACKAGE__->register_method(
     api_name    => "open-ils.storage.biblio.multiclass.staged.search_fts.staff",
+    no_tz_force => 1,
     method      => 'staged_fts',
     api_level   => 0,
     stream      => 1,
@@ -2609,6 +2639,7 @@ __PACKAGE__->register_method(
 );
 __PACKAGE__->register_method(
     api_name    => "open-ils.storage.metabib.multiclass.staged.search_fts",
+    no_tz_force => 1,
     method      => 'staged_fts',
     api_level   => 0,
     stream      => 1,
@@ -2616,6 +2647,7 @@ __PACKAGE__->register_method(
 );
 __PACKAGE__->register_method(
     api_name    => "open-ils.storage.metabib.multiclass.staged.search_fts.staff",
+    no_tz_force => 1,
     method      => 'staged_fts',
     api_level   => 0,
     stream      => 1,
@@ -2650,6 +2682,7 @@ sub FTS_paging_estimate {
 }
 __PACKAGE__->register_method(
     api_name    => "open-ils.storage.fts_paging_estimate",
+    no_tz_force => 1,
     method      => 'FTS_paging_estimate',
     argc        => 5,
     strict      => 1,
@@ -2749,6 +2782,7 @@ sub xref_count {
 }
 __PACKAGE__->register_method(
     api_name  => "open-ils.storage.search.xref",
+    no_tz_force => 1,
     method    => 'xref_count',
     api_level => 1,
 );
@@ -2763,6 +2797,7 @@ sub abstract_query2str {
 
 __PACKAGE__->register_method(
     api_name    => "open-ils.storage.query_parser.abstract_query.canonicalize",
+    no_tz_force => 1,
     method      => "abstract_query2str",
     api_level   => 1,
     signature   => {
@@ -2804,6 +2839,7 @@ sub str2abstract_query {
 
 __PACKAGE__->register_method(
     api_name    => "open-ils.storage.query_parser.abstract_query.from_string",
+    no_tz_force => 1,
     method      => "str2abstract_query",
     api_level   => 1,
     signature   => {
@@ -3116,6 +3152,7 @@ sub query_parser_fts {
 }
 __PACKAGE__->register_method(
     api_name    => "open-ils.storage.query_parser_search",
+    no_tz_force => 1,
     method      => 'query_parser_fts',
     api_level   => 1,
     stream      => 1,
@@ -3273,6 +3310,7 @@ sub query_parser_fts_wrapper {
 }
 __PACKAGE__->register_method(
     api_name    => "open-ils.storage.biblio.multiclass.staged.search_fts",
+    no_tz_force => 1,
     method      => 'query_parser_fts_wrapper',
     api_level   => 1,
     stream      => 1,
@@ -3280,6 +3318,7 @@ __PACKAGE__->register_method(
 );
 __PACKAGE__->register_method(
     api_name    => "open-ils.storage.biblio.multiclass.staged.search_fts.staff",
+    no_tz_force => 1,
     method      => 'query_parser_fts_wrapper',
     api_level   => 1,
     stream      => 1,
@@ -3287,6 +3326,7 @@ __PACKAGE__->register_method(
 );
 __PACKAGE__->register_method(
     api_name    => "open-ils.storage.metabib.multiclass.staged.search_fts",
+    no_tz_force => 1,
     method      => 'query_parser_fts_wrapper',
     api_level   => 1,
     stream      => 1,
@@ -3294,6 +3334,7 @@ __PACKAGE__->register_method(
 );
 __PACKAGE__->register_method(
     api_name    => "open-ils.storage.metabib.multiclass.staged.search_fts.staff",
+    no_tz_force => 1,
     method      => 'query_parser_fts_wrapper',
     api_level   => 1,
     stream      => 1,
index bd9c3df..0bf86c7 100644 (file)
@@ -31,6 +31,7 @@ my $U = 'OpenILS::Application::AppUtils';
 
 use constant COOKIE_SES => 'ses';
 use constant COOKIE_LOGGEDIN => 'eg_loggedin';
+use constant COOKIE_TZ => 'client_tz';
 use constant COOKIE_PHYSICAL_LOC => 'eg_physical_loc';
 use constant COOKIE_SSS_EXPAND => 'eg_sss_expand';
 
@@ -261,12 +262,15 @@ sub load_common {
     $ctx->{default_sort} =
         ($default_sort && $U->is_true($default_sort->enabled)) ? $default_sort->value : '';
 
+    $ctx->{client_tz} = $self->cgi->cookie(COOKIE_TZ) || $ENV{TZ};
     $ctx->{referer} = $self->cgi->referer;
     $ctx->{path_info} = $self->cgi->path_info;
     $ctx->{full_path} = $ctx->{base_path} . $self->cgi->path_info;
     $ctx->{unparsed_uri} = $self->apache->unparsed_uri;
     $ctx->{opac_root} = $ctx->{base_path} . "/opac"; # absolute base url
 
+    local $ENV{TZ} = $ctx->{client_tz};
+
     my $xul_wrapper = 
         ($self->apache->headers_in->get('OILS-Wrapper') || '') =~ /true/;
 
@@ -406,6 +410,7 @@ sub load_login {
     my $password = $cgi->param('password');
     my $org_unit = $ctx->{physical_loc} || $ctx->{aou_tree}->()->id;
     my $persist = $cgi->param('persist');
+    my $client_tz = $cgi->param('client_tz');
 
     # initial log form only
     return Apache2::Const::OK unless $username and $password;
@@ -466,27 +471,41 @@ sub load_login {
     # both login-related cookies should expire at the same time
     my $login_cookie_expires = ($persist) ? CORE::time + $response->{payload}->{authtime} : undef;
 
+    my $cookie_list = [
+        # contains the actual auth token and should be sent only over https
+        $cgi->cookie(
+            -name => COOKIE_SES,
+            -path => '/',
+            -secure => 1,
+            -value => $response->{payload}->{authtoken},
+            -expires => $login_cookie_expires
+        ),
+        # contains only a hint that we are logged in, and is used to
+        # trigger a redirect to https
+        $cgi->cookie(
+            -name => COOKIE_LOGGEDIN,
+            -path => '/',
+            -secure => 0,
+            -value => '1',
+            -expires => $login_cookie_expires
+        )
+    ];
+
+    if ($client_tz) {
+        # contains the client's tz, as passed by the client
+        # trigger a redirect to https
+        push @$cookie_list, $cgi->cookie(
+            -name => COOKIE_TZ,
+            -path => '/',
+            -secure => 0,
+            -value => $client_tz,
+            -expires => $login_cookie_expires
+        );
+    }
+
     return $self->generic_redirect(
         $cgi->param('redirect_to') || $acct,
-        [
-            # contains the actual auth token and should be sent only over https
-            $cgi->cookie(
-                -name => COOKIE_SES,
-                -path => '/',
-                -secure => 1,
-                -value => $response->{payload}->{authtoken},
-                -expires => $login_cookie_expires
-            ),
-            # contains only a hint that we are logged in, and is used to
-            # trigger a redirect to https
-            $cgi->cookie(
-                -name => COOKIE_LOGGEDIN,
-                -path => '/',
-                -secure => 0,
-                -value => '1',
-                -expires => $login_cookie_expires
-            )
-        ]
+        $cookie_list
     );
 }
 
index 2fc0a4f..5cfdebe 100644 (file)
@@ -146,4 +146,5 @@ var aou_hash = {
   </script>
 [% END %]
 
+<script type="text/javascript">$('client_tz_id').value = OpenSRF.tz</script>
 [%- END; # want_dojo -%]
index 1657855..19319fa 100644 (file)
@@ -53,6 +53,7 @@
             <input type="checkbox" name="persist" id="login_persist" /><label for="login_persist"> [% l('Stay logged in?') %]</label>
             <input type="submit" value="[% l('Log in') %]" alt="[% l('Log in') %]" class="opac-button" />
         </div>
+        <input id="client_tz_id" name="client_tz" type="hidden" />
     </form>
 </div>
 [% INCLUDE "opac/parts/login/help.tt2" %]