[open-ils-commits] [GIT] Evergreen ILS branch master updated. 029deb6ce6cd2ac4a2858edf786d364909687fc0

Evergreen Git git at git.evergreen-ils.org
Fri Jan 27 14:46:56 EST 2017


This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "Evergreen ILS".

The branch, master has been updated
       via  029deb6ce6cd2ac4a2858edf786d364909687fc0 (commit)
       via  f838339871e9c7ceabedb54d7d7ec9d1ddac57a6 (commit)
       via  abecff8a02343a18f00173d12970c8ed3ec3d8ef (commit)
       via  28561ed72740f0c1fdf19dcc5ed009398678a025 (commit)
       via  8bedc56a8b9f2727360d1ca533ba5c149ef75999 (commit)
       via  4014d3d022d8aeb875584d03f7d8e9a45e452c60 (commit)
       via  0d4a1f2bc2d69491e96fb9564c402c81c5707d06 (commit)
      from  3b36b462e1bffff16f44c0d6835aea4af61ff52b (commit)

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

- Log -----------------------------------------------------------------
commit 029deb6ce6cd2ac4a2858edf786d364909687fc0
Author: Jason Stephenson <jason at sigio.com>
Date:   Sat Aug 20 13:42:31 2016 -0400

    LP#1485374: Add missing comma on line 667 of oils_auth.c.
    
    Fixes the following compiler error:
    oils_auth.c: In function ‘oilsAuthComplete’:
    oils_auth.c:668:9: error: expected ‘)’ before string constant
             "open-ils.auth_internal",
             ^
    
    Signed-off-by: Jason Stephenson <jason at sigio.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/src/c-apps/oils_auth.c b/Open-ILS/src/c-apps/oils_auth.c
index 628efc8..0fd924f 100644
--- a/Open-ILS/src/c-apps/oils_auth.c
+++ b/Open-ILS/src/c-apps/oils_auth.c
@@ -664,7 +664,7 @@ int oilsAuthComplete( osrfMethodContext* ctx ) {
     if (barcode) jsonObjectSetKey(params, "barcode", jsonNewObject(barcode));
 
     jsonObject* authEvt = oilsUtilsQuickReqCtx( // freed after password test
-        ctx
+        ctx,
         "open-ils.auth_internal",
         "open-ils.auth_internal.user.validate", params);
     jsonObjectFree(params);

commit f838339871e9c7ceabedb54d7d7ec9d1ddac57a6
Author: Mike Rylander <mrylander at gmail.com>
Date:   Tue Feb 23 12:35:07 2016 -0500

    LP#1485374: Add release notes
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Jason Stephenson <jason at sigio.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/docs/RELEASE_NOTES_NEXT/Infrastructure/TZ_awareness.adoc b/docs/RELEASE_NOTES_NEXT/Infrastructure/TZ_awareness.adoc
new file mode 100644
index 0000000..cee11c7
--- /dev/null
+++ b/docs/RELEASE_NOTES_NEXT/Infrastructure/TZ_awareness.adoc
@@ -0,0 +1,34 @@
+Client Timezone Awareness
+^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Previously, adjusting the time zone in which a database session operates
+could not be done in any way except globally, directly within the database.
+However, allowing modification of the timezone parameter now supports
+localization efforts for those consortia that span multiple time zones.
+
+Implementation
+++++++++++++++
+
+CStore and other services that interact with the primary Evergreen database
+make use of the functionality provided by LP#1485371 in OpenSRF in order to
+set the time zone configuration parameter available in Postgres.  This has
+the effect of interpreting all timestamps written to or read from the database
+in the client's time zone.
+
+Within CStore (and related, C-based services), all stateful sessions make use
+of this capability, setting the database time zone upon a successful CONNECT
+message from the client.  The time zone is reset to the database default when
+a session is terminated either due to client DISCONNECT or server keepalive
+timeout.
+
+All stateless requests record the current database time zone, set the database
+time zone to that of the client's, run the query, and then reset the database
+time zone on each request that carries a client time zone value.  It is expect
+that this will not cause any noticeable increase in latency or query execution
+time, as this setting is local to the specific Postgres server backend process.
+
+Within the Storage service, the timezone will be set automatically by a simple
+wrapper method used by the existing method registration mechanism for method
+publishing.  Disconnect and error callbacks are registered to revert the time
+zone setting within the database.  This provides completely transparent time
+zone manipulation for backend services that make use of open-ils.storage.

commit abecff8a02343a18f00173d12970c8ed3ec3d8ef
Author: Mike Rylander <mrylander at gmail.com>
Date:   Tue Feb 23 12:32:57 2016 -0500

    LP#1485374: Adjust TZ scope in mod_perl
    
    Use of a 'local' variable causes the TZ to be dropped prematurely. So,
    instead, we set $ENV{TZ} globally and reset it when the mod_perl handler
    object is destroyed.
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Jason Stephenson <jason at sigio.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader.pm b/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader.pm
index 0bf86c7..c5b2840 100644
--- a/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader.pm
+++ b/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader.pm
@@ -57,6 +57,11 @@ sub new {
     return $self;
 }
 
+sub DESTROY {
+    my $self = shift;
+    $ENV{TZ} = $self->ctx->{original_tz}
+        if ($self->ctx && exists $self->ctx->{original_tz});
+}
 
 # current Apache2::RequestRec;
 sub apache {
@@ -269,7 +274,8 @@ sub load_common {
     $ctx->{unparsed_uri} = $self->apache->unparsed_uri;
     $ctx->{opac_root} = $ctx->{base_path} . "/opac"; # absolute base url
 
-    local $ENV{TZ} = $ctx->{client_tz};
+    $ctx->{original_tz} = $ENV{TZ};
+    $ENV{TZ} = $ctx->{client_tz};
 
     my $xul_wrapper = 
         ($self->apache->headers_in->get('OILS-Wrapper') || '') =~ /true/;

commit 28561ed72740f0c1fdf19dcc5ed009398678a025
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Tue Feb 23 12:09:15 2016 -0500

    LP#1485374: add way for C code to make TZ-aware subrequests
    
    This patch adds a helper routine to allow C
    methods to make subrequests that pass the client
    time zone along. This helper is in turn used during
    authentication to ensure that the user object
    that is returned has timestamps that are in the
    time zone of the client that makes the original
    authentication request.
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>
    
    Conflicts:
    	Open-ILS/src/c-apps/oils_auth.c
    
    Signed-off-by: Jason Stephenson <jason at sigio.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/include/openils/oils_utils.h b/Open-ILS/include/openils/oils_utils.h
index 6b391f2..839c582 100644
--- a/Open-ILS/include/openils/oils_utils.h
+++ b/Open-ILS/include/openils/oils_utils.h
@@ -13,6 +13,7 @@
 #include "oils_constants.h"
 #include "opensrf/osrf_app_session.h"
 #include "opensrf/osrf_settings.h"
+#include "opensrf/osrf_application.h"
 
 #ifdef __cplusplus
 extern "C" {
@@ -65,15 +66,20 @@ oilsEvent* oilsUtilsCheckPerms( int userid, int orgid, char* permissions[], int
 jsonObject* oilsUtilsQuickReq( const char* service, const char* method,
 		const jsonObject* params );
 
+jsonObject* oilsUtilsQuickReqCtx( osrfMethodContext* ctx, const char* service,
+		const char* method, const jsonObject* params );
+
 jsonObject* oilsUtilsStorageReq( const char* method, const jsonObject* params );
+jsonObject* oilsUtilsStorageReqCtx( osrfMethodContext* ctx, const char* method, const jsonObject* params );
 
 jsonObject* oilsUtilsCStoreReq( const char* method, const jsonObject* params );
+jsonObject* oilsUtilsCStoreReqCtx( osrfMethodContext* ctx, const char* method, const jsonObject* params );
 
 /**
  * Searches the storage server for a user with the given username 
  * Caller is responsible for freeing the returned object
  */
-jsonObject* oilsUtilsFetchUserByUsername( const char* name );
+jsonObject* oilsUtilsFetchUserByUsername( osrfMethodContext* ctx, const char* name );
 
 
 /**
@@ -95,7 +101,7 @@ char* oilsUtilsLogin( const char* uname, const char* passwd, const char* type, i
  */
 jsonObject* oilsUtilsFetchWorkstation( long id );
 
-jsonObject* oilsUtilsFetchUserByBarcode(const char* barcode);
+jsonObject* oilsUtilsFetchUserByBarcode(osrfMethodContext* ctx, const char* barcode);
 
 jsonObject* oilsUtilsFetchWorkstationByName( const char* name );
 
@@ -108,7 +114,7 @@ long oilsUtilsIntervalToSeconds( const char* interval );
  * Creates actor.usr_activity entries
  * @return The number of rows created.  0 or 1.
  */
-int oilsUtilsTrackUserActivity( long usr, const char* ewho, const char* ewhat, const char* ehow );
+int oilsUtilsTrackUserActivity( osrfMethodContext* ctx, long usr, const char* ewho, const char* ewhat, const char* ehow );
 
 /**
  * Returns the ID of the root org unit (parent_ou = NULL)
diff --git a/Open-ILS/src/c-apps/oils_auth.c b/Open-ILS/src/c-apps/oils_auth.c
index 068e0f1..628efc8 100644
--- a/Open-ILS/src/c-apps/oils_auth.c
+++ b/Open-ILS/src/c-apps/oils_auth.c
@@ -239,7 +239,7 @@ static int oilsAuthInitUsernameHandler(
 
     int user_id = -1;
     jsonObject* resp = NULL; // free
-    jsonObject* user_obj = oilsUtilsFetchUserByUsername(username); // free
+    jsonObject* user_obj = oilsUtilsFetchUserByUsername(ctx, username); // free
 
     if (user_obj && user_obj->type != JSON_NULL) 
         user_id = oilsFMGetObjectId(user_obj);
@@ -281,7 +281,7 @@ static int oilsAuthInitBarcodeHandler(
 
     int user_id = -1;
     jsonObject* resp = NULL; // free
-    jsonObject* user_obj = oilsUtilsFetchUserByBarcode(barcode); // free
+    jsonObject* user_obj = oilsUtilsFetchUserByBarcode(ctx, barcode); // free
 
     if (user_obj && user_obj->type != JSON_NULL) 
         user_id = oilsFMGetObjectId(user_obj);
@@ -490,7 +490,7 @@ static int oilsAuthVerifyPassword( const osrfMethodContext* ctx, int user_id,
 	Returns the event that should be returned to the user.
 	Event must be freed
 */
-static oilsEvent* oilsAuthHandleLoginOK( jsonObject* userObj, const char* uname,
+static oilsEvent* oilsAuthHandleLoginOK( osrfMethodContext* ctx, jsonObject* userObj, const char* uname,
 		const char* type, int orgloc, const char* workstation ) {
 
 	oilsEvent* response = NULL;
@@ -503,7 +503,8 @@ static oilsEvent* oilsAuthHandleLoginOK( jsonObject* userObj, const char* uname,
     if (workstation) 
         jsonObjectSetKey(params, "workstation", jsonNewObject(workstation));
 
-    jsonObject* authEvt = oilsUtilsQuickReq(
+    jsonObject* authEvt = oilsUtilsQuickReqCtx(
+        ctx,
         "open-ils.auth_internal",
         "open-ils.auth_internal.session.create", params);
     jsonObjectFree(params);
@@ -644,8 +645,8 @@ int oilsAuthComplete( osrfMethodContext* ctx ) {
     }
 
     jsonObject* param = jsonNewNumberObject(user_id); // free
-    userObj = oilsUtilsCStoreReq(
-        "open-ils.cstore.direct.actor.user.retrieve", param);
+    userObj = oilsUtilsCStoreReqCtx(
+        ctx, "open-ils.cstore.direct.actor.user.retrieve", param);
     jsonObjectFree(param);
 
     char* freeable_uname = NULL;
@@ -662,7 +663,8 @@ int oilsAuthComplete( osrfMethodContext* ctx ) {
     jsonObjectSetKey(params, "login_type", jsonNewObject(type));
     if (barcode) jsonObjectSetKey(params, "barcode", jsonNewObject(barcode));
 
-    jsonObject* authEvt = oilsUtilsQuickReq( // freed after password test
+    jsonObject* authEvt = oilsUtilsQuickReqCtx( // freed after password test
+        ctx
         "open-ils.auth_internal",
         "open-ils.auth_internal.user.validate", params);
     jsonObjectFree(params);
@@ -743,10 +745,11 @@ int oilsAuthComplete( osrfMethodContext* ctx ) {
 
         } else {
             response = oilsAuthHandleLoginOK(
-                userObj, uname, type, orgloc, workstation);
+                ctx, userObj, uname, type, orgloc, workstation);
         }
 
         oilsUtilsTrackUserActivity(
+            ctx,
             oilsFMGetObjectId(userObj), 
             ewho, ewhat, 
             osrfAppSessionGetIngress()
diff --git a/Open-ILS/src/c-apps/oils_auth_internal.c b/Open-ILS/src/c-apps/oils_auth_internal.c
index a2fa08e..96ad193 100644
--- a/Open-ILS/src/c-apps/oils_auth_internal.c
+++ b/Open-ILS/src/c-apps/oils_auth_internal.c
@@ -295,8 +295,8 @@ int oilsAuthInternalCreateSession(osrfMethodContext* ctx) {
 
     // fetch the user object
     jsonObject* idParam = jsonNewNumberStringObject(user_id);
-    jsonObject* userObj = oilsUtilsCStoreReq(
-        "open-ils.cstore.direct.actor.user.retrieve", idParam);
+    jsonObject* userObj = oilsUtilsCStoreReqCtx(
+        ctx, "open-ils.cstore.direct.actor.user.retrieve", idParam);
     jsonObjectFree(idParam);
 
     if (!userObj) {
@@ -392,8 +392,8 @@ int oilsAuthInternalValidate(osrfMethodContext* ctx) {
 
     // Confirm user exists, active=true, barred=false, deleted=false
     params = jsonNewNumberStringObject(user_id);
-    userObj = oilsUtilsCStoreReq(
-        "open-ils.cstore.direct.actor.user.retrieve", params);
+    userObj = oilsUtilsCStoreReqCtx(
+        ctx, "open-ils.cstore.direct.actor.user.retrieve", params);
     jsonObjectFree(params);
 
     if (userObj && userObj->type != JSON_NULL) {
@@ -428,8 +428,8 @@ int oilsAuthInternalValidate(osrfMethodContext* ctx) {
 
         int card_ok = 0;
         params = jsonParseFmt("{\"barcode\":\"%s\"}", barcode);
-        jsonObject* card = oilsUtilsCStoreReq(
-            "open-ils.cstore.direct.actor.card.search", params);
+        jsonObject* card = oilsUtilsCStoreReqCtx(
+            ctx, "open-ils.cstore.direct.actor.card.search", params);
         jsonObjectFree(params);
 
         if (card && card->type != JSON_NULL) {
diff --git a/Open-ILS/src/c-apps/oils_utils.c b/Open-ILS/src/c-apps/oils_utils.c
index 155274f..4074d9e 100644
--- a/Open-ILS/src/c-apps/oils_utils.c
+++ b/Open-ILS/src/c-apps/oils_utils.c
@@ -120,7 +120,7 @@ long oilsFMGetObjectId( const jsonObject* obj ) {
 	return id;
 }
 
-int oilsUtilsTrackUserActivity(long usr, const char* ewho, const char* ewhat, const char* ehow) {
+int oilsUtilsTrackUserActivity(osrfMethodContext* ctx, long usr, const char* ewho, const char* ewhat, const char* ehow) {
     if (!usr && !(ewho || ewhat || ehow)) return 0;
     int rowcount = 0;
 
@@ -260,6 +260,42 @@ jsonObject* oilsUtilsQuickReq( const char* service, const char* method,
 }
 
 /**
+	@brief Perform a remote procedure call, propagating session
+        locale and timezone
+	@param service The name of the service to invoke.
+	@param method The name of the method to call.
+	@param params The parameters to be passed to the method, if any.
+	@return A copy of whatever the method returns as a result, or a JSON_NULL if the method
+	doesn't return anything.
+
+	If the @a params parameter points to a JSON_ARRAY, pass each element of the array
+	as a separate parameter.  If it points to any other kind of jsonObject, pass it as a
+	single parameter.  If it is NULL, pass no parameters.
+
+	The calling code is responsible for freeing the returned object by calling jsonObjectFree().
+*/
+jsonObject* oilsUtilsQuickReqCtx( osrfMethodContext* ctx, const char* service,
+                const char* method, const jsonObject* params ) {
+	if(!(service && method && ctx)) return NULL;
+
+	osrfLogDebug(OSRF_LOG_MARK, "oilsUtilsQuickReqCtx(): %s - %s (%s)", service, method, ctx->session->session_tz );
+
+	// Open an application session with the service, and send the request
+	osrfAppSession* session = osrfAppSessionClientInit( service );
+	osrf_app_session_set_tz(session, ctx->session->session_tz);
+	int reqid = osrfAppSessionSendRequest( session, params, method, 1 );
+
+	// Get the response
+	osrfMessage* omsg = osrfAppSessionRequestRecv( session, reqid, 60 );
+	jsonObject* result = jsonObjectClone( osrfMessageGetResult(omsg) );
+
+	// Clean up
+	osrfMessageFree(omsg);
+	osrfAppSessionFree(session);
+	return result;
+}
+
+/**
 	@brief Call a method of the open-ils.storage service.
 	@param method Name of the method.
 	@param params Parameters to be passed to the method, if any.
@@ -293,6 +329,24 @@ jsonObject* oilsUtilsCStoreReq( const char* method, const jsonObject* params ) {
 	return oilsUtilsQuickReq("open-ils.cstore", method, params);
 }
 
+/**
+	@brief Call a method of the open-ils.cstore service, context aware.
+	@param ctx Method context object.
+	@param method Name of the method.
+	@param params Parameters to be passed to the method, if any.
+	@return A copy of whatever the method returns as a result, or a JSON_NULL if the method
+	doesn't return anything.
+
+	If the @a params parameter points to a JSON_ARRAY, pass each element of the array
+	as a separate parameter.  If it points to any other kind of jsonObject, pass it as a
+	single parameter.  If it is NULL, pass no parameters.
+
+	The calling code is responsible for freeing the returned object by calling jsonObjectFree().
+*/
+jsonObject* oilsUtilsCStoreReqCtx( osrfMethodContext* ctx, const char* method, const jsonObject* params ) {
+	return oilsUtilsQuickReqCtx(ctx, "open-ils.cstore", method, params);
+}
+
 
 
 /**
@@ -303,11 +357,11 @@ jsonObject* oilsUtilsCStoreReq( const char* method, const jsonObject* params ) {
 
 	The calling code is responsible for freeing the returned object by calling jsonObjectFree().
 */
-jsonObject* oilsUtilsFetchUserByUsername( const char* name ) {
+jsonObject* oilsUtilsFetchUserByUsername( osrfMethodContext* ctx, const char* name ) {
 	if(!name) return NULL;
 	jsonObject* params = jsonParseFmt("{\"usrname\":\"%s\"}", name);
-	jsonObject* user = oilsUtilsQuickReq(
-		"open-ils.cstore", "open-ils.cstore.direct.actor.user.search", params );
+	jsonObject* user = oilsUtilsQuickReqCtx(
+		ctx, "open-ils.cstore", "open-ils.cstore.direct.actor.user.search", params );
 
 	jsonObjectFree(params);
 	long id = oilsFMGetObjectId(user);
@@ -326,14 +380,14 @@ jsonObject* oilsUtilsFetchUserByUsername( const char* name ) {
 
 	The calling code is responsible for freeing the returned object by calling jsonObjectFree().
 */
-jsonObject* oilsUtilsFetchUserByBarcode(const char* barcode) {
+jsonObject* oilsUtilsFetchUserByBarcode(osrfMethodContext* ctx, const char* barcode) {
 	if(!barcode) return NULL;
 
 	osrfLogInfo(OSRF_LOG_MARK, "Fetching user by barcode %s", barcode);
 
 	jsonObject* params = jsonParseFmt("{\"barcode\":\"%s\"}", barcode);
-	jsonObject* card = oilsUtilsQuickReq(
-		"open-ils.cstore", "open-ils.cstore.direct.actor.card.search", params );
+	jsonObject* card = oilsUtilsQuickReqCtx(
+		ctx, "open-ils.cstore", "open-ils.cstore.direct.actor.card.search", params );
 	jsonObjectFree(params);
 
 	if(!card)
@@ -349,8 +403,8 @@ jsonObject* oilsUtilsFetchUserByBarcode(const char* barcode) {
 
 	// Look up the user in actor.usr
 	params = jsonParseFmt("[%f]", iusr);
-	jsonObject* user = oilsUtilsQuickReq(
-		"open-ils.cstore", "open-ils.cstore.direct.actor.user.retrieve", params);
+	jsonObject* user = oilsUtilsQuickReqCtx(
+		ctx, "open-ils.cstore", "open-ils.cstore.direct.actor.user.retrieve", params);
 
 	jsonObjectFree(params);
 	return user;
diff --git a/Open-ILS/src/extras/oils_requestor.c b/Open-ILS/src/extras/oils_requestor.c
index 73ea934..7e7baa1 100644
--- a/Open-ILS/src/extras/oils_requestor.c
+++ b/Open-ILS/src/extras/oils_requestor.c
@@ -10,6 +10,7 @@
 
 char* script    = NULL;
 char* authtoken = NULL;
+static char* tz = NULL;
 
 static int do_request( char* request );
 static char* format_response( const jsonObject* o );
@@ -64,6 +65,8 @@ int main( int argc, char* argv[] ) {
 
 	printf("Connected to OpenSRF network...\n");
 
+    tz = getenv("TZ");
+
 	if( username && password &&
 			( authtoken = oilsUtilsLogin(username, password, "staff", -1 )) ) {
 		printf("Login Session: %s\n", authtoken);
@@ -115,6 +118,8 @@ static int do_request( char* request ) {
 		}
 
 		osrfAppSession* session = osrfAppSessionClientInit(service);
+        if (tz) osrf_app_session_set_tz(session,tz);
+
 		int req_id = osrfAppSessionSendRequest( session, params, method, 1 );
 		osrfMessage* omsg;
 

commit 8bedc56a8b9f2727360d1ca533ba5c149ef75999
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Wed Feb 3 17:50:58 2016 -0500

    LP#1485374: call tzset() after setting timezone
    
    Testing shows this to be needed to make strftime()
    return the time in the client's time zone.
    
    This is a bit confusing, however, as strftime per
    POSIX should be acting as if it always calls tzset()
    when doing formatting that involves the local timezone.
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Jason Stephenson <jason at sigio.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/src/c-apps/oils_sql.c b/Open-ILS/src/c-apps/oils_sql.c
index 9ea52f6..c33a3af 100644
--- a/Open-ILS/src/c-apps/oils_sql.c
+++ b/Open-ILS/src/c-apps/oils_sql.c
@@ -859,6 +859,7 @@ int beginTransaction( osrfMethodContext* ctx ) {
 
 	if (tz) {
 		setenv("TZ",tz,1);
+		tzset();
 		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);
@@ -871,6 +872,7 @@ int beginTransaction( osrfMethodContext* ctx ) {
 		}
 	} else {
 		unsetenv("TZ");
+		tzset();
 		dbi_result res = dbi_conn_queryf( writehandle, "SET timezone TO DEFAULT; -- no tz" );
 		if( !res ) {
 			osrfLogError( OSRF_LOG_MARK, "%s: Error resetting timezone", modulename);
@@ -5890,6 +5892,7 @@ static jsonObject* doFieldmapperSearch( osrfMethodContext* ctx, osrfHash* class_
 	if (!getXactId(ctx)) {
 		if (tz) {
 			setenv("TZ",tz,1);
+			tzset();
 			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);
@@ -5904,6 +5907,7 @@ static jsonObject* doFieldmapperSearch( osrfMethodContext* ctx, osrfHash* class_
 			}
 		} else {
 			unsetenv("TZ");
+			tzset();
 			dbi_result res = dbi_conn_queryf( writehandle, "SET timezone TO DEFAULT; -- cstore" );
 			if( !res ) {
 				osrfLogError( OSRF_LOG_MARK, "%s: Error resetting timezone", modulename);

commit 4014d3d022d8aeb875584d03f7d8e9a45e452c60
Author: Mike Rylander <mrylander at gmail.com>
Date:   Fri Sep 11 11:19:34 2015 -0400

    LP#1485374: Allow server timezone via CStoreEditor
    
    In rare cases, the server's timezone may be preferable to the client's.
    To plan for such cases we provide a way to signal that we want to use the
    server's timezone.
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Jason Stephenson <jason at sigio.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Utils/CStoreEditor.pm b/Open-ILS/src/perlmods/lib/OpenILS/Utils/CStoreEditor.pm
index d1a935e..e381b21 100644
--- a/Open-ILS/src/perlmods/lib/OpenILS/Utils/CStoreEditor.pm
+++ b/Open-ILS/src/perlmods/lib/OpenILS/Utils/CStoreEditor.pm
@@ -797,6 +797,8 @@ sub runmethod {
         $method .= '.atomic';
     }
 
+    local $ENV{TZ} = $$options{no_tz} ? undef : $ENV{TZ};
+
     $method =~ s/search/id_list/o if $options->{idlist};
 
     $method =~ s/\.atomic$//o if $self->substream($$options{substream} || 0);

commit 0d4a1f2bc2d69491e96fb9564c402c81c5707d06
Author: Mike Rylander <mrylander at gmail.com>
Date:   Mon Aug 3 13:27:56 2015 -0400

    LP#1485374: Use client TZ in the database when supplied to the server
    
    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 at gmail.com>
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>
    
    Conflicts:
    	Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader.pm
    
    Signed-off-by: Jason Stephenson <jason at sigio.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/src/c-apps/oils_sql.c b/Open-ILS/src/c-apps/oils_sql.c
index bacb867..9ea52f6 100644
--- a/Open-ILS/src/c-apps/oils_sql.c
+++ b/Open-ILS/src/c-apps/oils_sql.c
@@ -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;
+}
+
 /*@}*/
diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/Storage.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/Storage.pm
index 7401846..3b55c62 100644
--- a/Open-ILS/src/perlmods/lib/OpenILS/Application/Storage.pm
+++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/Storage.pm
@@ -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);
diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/Driver/Pg/storage.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/Driver/Pg/storage.pm
index c9fd303..157aa7c 100644
--- a/Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/Driver/Pg/storage.pm
+++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/Driver/Pg/storage.pm
@@ -60,29 +60,41 @@
             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 );
+                }
             }
         }
 
@@ -121,14 +133,16 @@
             $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;
@@ -162,14 +176,16 @@
             $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;
@@ -260,15 +276,17 @@
 
         $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;
 
@@ -292,9 +310,11 @@
             $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;
     }
diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/Publisher/metabib.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/Publisher/metabib.pm
index 5fea5c7..7aec5f6 100644
--- a/Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/Publisher/metabib.pm
+++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/Publisher/metabib.pm
@@ -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,
diff --git a/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader.pm b/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader.pm
index bd9c3df..0bf86c7 100644
--- a/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader.pm
+++ b/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader.pm
@@ -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
     );
 }
 
diff --git a/Open-ILS/src/templates/opac/parts/js.tt2 b/Open-ILS/src/templates/opac/parts/js.tt2
index 2fc0a4f..5cfdebe 100644
--- a/Open-ILS/src/templates/opac/parts/js.tt2
+++ b/Open-ILS/src/templates/opac/parts/js.tt2
@@ -146,4 +146,5 @@ var aou_hash = {
   </script>
 [% END %]
 
+<script type="text/javascript">$('client_tz_id').value = OpenSRF.tz</script>
 [%- END; # want_dojo -%]
diff --git a/Open-ILS/src/templates/opac/parts/login/form.tt2 b/Open-ILS/src/templates/opac/parts/login/form.tt2
index 1657855..19319fa 100644
--- a/Open-ILS/src/templates/opac/parts/login/form.tt2
+++ b/Open-ILS/src/templates/opac/parts/login/form.tt2
@@ -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" %]

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

Summary of changes:
 Open-ILS/include/openils/oils_utils.h              |   12 ++-
 Open-ILS/src/c-apps/oils_auth.c                    |   19 ++--
 Open-ILS/src/c-apps/oils_auth_internal.c           |   12 +-
 Open-ILS/src/c-apps/oils_sql.c                     |  115 +++++++++++++++++++-
 Open-ILS/src/c-apps/oils_utils.c                   |   72 +++++++++++--
 Open-ILS/src/extras/oils_requestor.c               |    5 +
 .../perlmods/lib/OpenILS/Application/Storage.pm    |   58 ++++++++++
 .../Application/Storage/Driver/Pg/storage.pm       |  114 ++++++++++++--------
 .../Application/Storage/Publisher/metabib.pm       |   41 +++++++
 .../src/perlmods/lib/OpenILS/Utils/CStoreEditor.pm |    2 +
 .../src/perlmods/lib/OpenILS/WWW/EGCatLoader.pm    |   63 ++++++++----
 Open-ILS/src/templates/opac/parts/js.tt2           |    1 +
 Open-ILS/src/templates/opac/parts/login/form.tt2   |    1 +
 .../Infrastructure/TZ_awareness.adoc               |   34 ++++++
 14 files changed, 456 insertions(+), 93 deletions(-)
 create mode 100644 docs/RELEASE_NOTES_NEXT/Infrastructure/TZ_awareness.adoc


hooks/post-receive
-- 
Evergreen ILS


More information about the open-ils-commits mailing list