Bug 12495 - Include streetnumber in hold alert address
[koha_fer] / C4 / Context.pm
index b9084b4..9a7b75d 100644 (file)
@@ -12,13 +12,13 @@ package C4::Context;
 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 # A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
 #
-# You should have received a copy of the GNU General Public License along with
-# Koha; if not, write to the Free Software Foundation, Inc., 59 Temple Place,
-# Suite 330, Boston, MA  02111-1307 USA
+# You should have received a copy of the GNU General Public License along
+# with Koha; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 
 use strict;
-use vars qw($VERSION $AUTOLOAD $context @context_stack);
-
+use warnings;
+use vars qw($VERSION $AUTOLOAD $context @context_stack $servers $memcached $ismemcached);
 BEGIN {
        if ($ENV{'HTTP_USER_AGENT'})    {
                require CGI::Carp;
@@ -26,8 +26,15 @@ BEGIN {
         #  "Note that fatalsToBrowser does not work with mod_perl version 2.0 and higher."
                import CGI::Carp qw(fatalsToBrowser);
                        sub handle_errors {
-                               my $msg = shift;
-                               my $debug_level =  C4::Context->preference("DebugLevel");
+                           my $msg = shift;
+                           my $debug_level;
+                           eval {C4::Context->dbh();};
+                           if ($@){
+                               $debug_level = 1;
+                           } 
+                           else {
+                               $debug_level =  C4::Context->preference("DebugLevel");
+                           }
 
                 print q(<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
                             "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
@@ -63,14 +70,31 @@ BEGIN {
                                }       
                 print "</body></html>";
                        }
-               CGI::Carp::set_message(\&handle_errors);
+               #CGI::Carp::set_message(\&handle_errors);
                ## give a stack backtrace if KOHA_BACKTRACES is set
                ## can't rely on DebugLevel for this, as we're not yet connected
                if ($ENV{KOHA_BACKTRACES}) {
                        $main::SIG{__DIE__} = \&CGI::Carp::confess;
                }
     }          # else there is no browser to send fatals to!
-       $VERSION = '3.00.00.036';
+
+    # Check if there are memcached servers set
+    $servers = $ENV{'MEMCACHED_SERVERS'};
+    if ($servers) {
+        # Load required libraries and create the memcached object
+        require Cache::Memcached;
+        $memcached = Cache::Memcached->new({
+        servers => [ $servers ],
+        debug   => 0,
+        compress_threshold => 10_000,
+        expire_time => 600,
+        namespace => $ENV{'MEMCACHED_NAMESPACE'} || 'koha'
+    });
+        # Verify memcached available (set a variable and test the output)
+    $ismemcached = $memcached->set('ismemcached','1');
+    }
+
+    $VERSION = '3.07.00.049';
 }
 
 use DBI;
@@ -78,6 +102,10 @@ use ZOOM;
 use XML::Simple;
 use C4::Boolean;
 use C4::Debug;
+use POSIX ();
+use DateTime::TimeZone;
+use Module::Load::Conditional qw(can_load);
+use Carp;
 
 =head1 NAME
 
@@ -128,8 +156,6 @@ environment variable to the pathname of a configuration file to use.
 
 =head1 METHODS
 
-=over 2
-
 =cut
 
 #'
@@ -177,29 +203,37 @@ $context = undef;        # Initially, no context is set
 @context_stack = ();        # Initially, no saved contexts
 
 
-=item KOHAVERSION
-    returns the kohaversion stored in kohaversion.pl file
+=head2 KOHAVERSION
+
+returns the kohaversion stored in kohaversion.pl file
 
 =cut
 
 sub KOHAVERSION {
-    my $cgidir = C4::Context->intranetdir ."/cgi-bin";
-
-    # 2 cases here : on CVS install, $cgidir does not need a /cgi-bin
-    # on a standard install, /cgi-bin need to be added.
-    # test one, then the other
-    # FIXME - is this all really necessary?
-    unless (opendir(DIR, "$cgidir/cataloguing/value_builder")) {
-        $cgidir = C4::Context->intranetdir;
-        closedir(DIR);
-    }
+    my $cgidir = C4::Context->intranetdir;
 
+    # Apparently the GIT code does not run out of a CGI-BIN subdirectory
+    # but distribution code does?  (Stan, 1jan08)
+    if(-d $cgidir . "/cgi-bin"){
+        my $cgidir .= "/cgi-bin";
+    }
+    
     do $cgidir."/kohaversion.pl" || die "NO $cgidir/kohaversion.pl";
     return kohaversion();
 }
-=item read_config_file
 
-=over 4
+=head2 final_linear_version
+
+Returns the version number of the final update to run in updatedatabase.pl.
+This number is equal to the version in kohaversion.pl
+
+=cut
+
+sub final_linear_version {
+    return KOHAVERSION;
+}
+
+=head2 read_config_file
 
 Reads the specified Koha config file. 
 
@@ -222,43 +256,78 @@ The elements nested within the <server> element:
 
 Returns undef in case of error.
 
-=back
-
 =cut
 
 sub read_config_file {         # Pass argument naming config file to read
-    my $koha = XMLin(shift, keyattr => ['id'], forcearray => ['listen', 'server', 'serverinfo']);
+    my $koha = XMLin(shift, keyattr => ['id'], forcearray => ['listen', 'server', 'serverinfo'], suppressempty => '');
+
+    if ($ismemcached) {
+      $memcached->set('kohaconf',$koha);
+    }
+
     return $koha;                      # Return value: ref-to-hash holding the configuration
 }
 
-# db_scheme2dbi
-# Translates the full text name of a database into de appropiate dbi name
-# 
-sub db_scheme2dbi {
-    my $name = shift;
+=head2 ismemcached
+
+Returns the value of the $ismemcached variable (0/1)
+
+=cut
 
-    for ($name) {
-# FIXME - Should have other databases. 
-        if (/mysql/i) { return("mysql"); }
-        if (/Postgres|Pg|PostgresSQL/) { return("Pg"); }
-        if (/oracle/i) { return("Oracle"); }
+sub ismemcached {
+    return $ismemcached;
+}
+
+=head2 memcached
+
+If $ismemcached is true, returns the $memcache variable.
+Returns undef otherwise
+
+=cut
+
+sub memcached {
+    if ($ismemcached) {
+      return $memcached;
+    } else {
+      return;
     }
-    return undef;         # Just in case
+}
+
+=head2 db_scheme2dbi
+
+    my $dbd_driver_name = C4::Context::db_schema2dbi($scheme);
+
+This routines translates a database type to part of the name
+of the appropriate DBD driver to use when establishing a new
+database connection.  It recognizes 'mysql' and 'Pg'; if any
+other scheme is supplied it defaults to 'mysql'.
+
+=cut
+
+sub db_scheme2dbi {
+    my $scheme = shift // '';
+    return $scheme eq 'Pg' ? $scheme : 'mysql';
 }
 
 sub import {
-    my $package = shift;
-    my $conf_fname = shift;        # Config file name
-    $context;
-
-    # Create a new context from the given config file name, if
-    # any, then set it as the current context.
-    $context = new C4::Context($conf_fname) unless $context;
-    return undef if !defined($context);
-    $context->set_context;
+    # Create the default context ($C4::Context::Context)
+    # the first time the module is called
+    # (a config file can be optionaly passed)
+
+    # default context allready exists? 
+    return if $context;
+
+    # no ? so load it!
+    my ($pkg,$config_file) = @_ ;
+    my $new_ctx = __PACKAGE__->new($config_file);
+    return unless $new_ctx;
+
+    # if successfully loaded, use it by default
+    $new_ctx->set_context;
+    1;
 }
 
-=item new
+=head2 new
 
   $context = new C4::Context;
   $context = new C4::Context("/path/to/koha-conf.xml");
@@ -267,6 +336,10 @@ Allocates a new context. Initializes the context from the specified
 file, which defaults to either the file given by the C<$KOHA_CONF>
 environment variable, or F</etc/koha/koha-conf.xml>.
 
+It saves the koha-conf.xml values in the declared memcached server(s)
+if currently available and uses those values until them expire and
+re-reads them.
+
 C<&new> does not set this context as the new default context; for
 that, use C<&set_context>.
 
@@ -298,15 +371,25 @@ sub new {
             $conf_fname = CONFIG_FNAME;
         } else {
             warn "unable to locate Koha configuration file koha-conf.xml";
-            return undef;
+            return;
         }
     }
-        # Load the desired config file.
-    $self = read_config_file($conf_fname);
-    $self->{"config_file"} = $conf_fname;
     
+    if ($ismemcached) {
+        # retreive from memcached
+        $self = $memcached->get('kohaconf');
+        if (not defined $self) {
+            # not in memcached yet
+            $self = read_config_file($conf_fname);
+        }
+    } else {
+        # non-memcached env, read from file
+        $self = read_config_file($conf_fname);
+    }
+
+    $self->{"config_file"} = $conf_fname;
     warn "read_config_file($conf_fname) returned undef" if !defined($self->{"config"});
-    return undef if !defined($self->{"config"});
+    return if !defined($self->{"config"});
 
     $self->{"dbh"} = undef;        # Database handle
     $self->{"Zconn"} = undef;    # Zebra Connections
@@ -315,12 +398,14 @@ sub new {
     $self->{"userenv"} = undef;        # User env
     $self->{"activeuser"} = undef;        # current active user
     $self->{"shelves"} = undef;
+    $self->{tz} = undef; # local timezone object
 
     bless $self, $class;
+    $self->{db_driver} = db_scheme2dbi($self->config('db_scheme'));  # cache database driver
     return $self;
 }
 
-=item set_context
+=head2 set_context
 
   $context = new C4::Context;
   $context->set_context();
@@ -368,7 +453,7 @@ sub set_context
     $context = $new_context;
 }
 
-=item restore_context
+=head2 restore_context
 
   &restore_context;
 
@@ -394,7 +479,7 @@ sub restore_context
     # that was current when this was called?
 }
 
-=item config
+=head2 config
 
   $value = C4::Context->config("config_variable");
 
@@ -409,10 +494,10 @@ C<C4::Config-E<gt>new> will not return it.
 
 =cut
 
-sub _common_config ($$) {
+sub _common_config {
        my $var = shift;
        my $term = shift;
-    return undef if !defined($context->{$term});
+    return if !defined($context->{$term});
        # Presumably $self->{$term} might be
        # undefined if the config file given to &new
        # didn't exist, and the caller didn't bother
@@ -432,43 +517,139 @@ sub ModZebrations {
        return _common_config($_[1],'serverinfo');
 }
 
-=item preference
+=head2 preference
 
-  $sys_preference = C4::Context->preference("some_variable");
+  $sys_preference = C4::Context->preference('some_variable');
 
 Looks up the value of the given system preference in the
 systempreferences table of the Koha database, and returns it. If the
-variable is not set, or in case of error, returns the undefined value.
+variable is not set or does not exist, undef is returned.
+
+In case of an error, this may return 0.
+
+Note: It is impossible to tell the difference between system
+preferences which do not exist, and those whose values are set to NULL
+with this method.
 
 =cut
 
-#'
-# FIXME - The preferences aren't likely to change over the lifetime of
-# the script (and things might break if they did change), so perhaps
-# this function should cache the results it finds.
-sub preference
-{
+# FIXME: running this under mod_perl will require a means of
+# flushing the caching mechanism.
+
+my %sysprefs;
+my $use_syspref_cache = 1;
+
+sub preference {
     my $self = shift;
-    my $var = shift;        # The system preference to return
-    my $retval;            # Return value
-    my $dbh = C4::Context->dbh or return 0;
-    # Look up systempreferences.variable==$var
-    $retval = $dbh->selectrow_array(<<EOT);
-        SELECT    value
-        FROM    systempreferences
-        WHERE    variable='$var'
-        LIMIT    1
-EOT
-    return $retval;
+    my $var  = shift;    # The system preference to return
+
+    if ($use_syspref_cache && exists $sysprefs{lc $var}) {
+        return $sysprefs{lc $var};
+    }
+
+    my $dbh  = C4::Context->dbh or return 0;
+
+    my $value;
+    if ( defined $ENV{"OVERRIDE_SYSPREF_$var"} ) {
+        $value = $ENV{"OVERRIDE_SYSPREF_$var"};
+    } else {
+        # Look up systempreferences.variable==$var
+        my $sql = q{
+            SELECT  value
+            FROM    systempreferences
+            WHERE   variable = ?
+            LIMIT   1
+        };
+        $value = $dbh->selectrow_array( $sql, {}, lc $var );
+    }
+
+    $sysprefs{lc $var} = $value;
+    return $value;
 }
 
-sub boolean_preference ($) {
+sub boolean_preference {
     my $self = shift;
     my $var = shift;        # The system preference to return
     my $it = preference($self, $var);
     return defined($it)? C4::Boolean::true_p($it): undef;
 }
 
+=head2 enable_syspref_cache
+
+  C4::Context->enable_syspref_cache();
+
+Enable the in-memory syspref cache used by C4::Context. This is the
+default behavior.
+
+=cut
+
+sub enable_syspref_cache {
+    my ($self) = @_;
+    $use_syspref_cache = 1;
+}
+
+=head2 disable_syspref_cache
+
+  C4::Context->disable_syspref_cache();
+
+Disable the in-memory syspref cache used by C4::Context. This should be
+used with Plack and other persistent environments.
+
+=cut
+
+sub disable_syspref_cache {
+    my ($self) = @_;
+    $use_syspref_cache = 0;
+    $self->clear_syspref_cache();
+}
+
+=head2 clear_syspref_cache
+
+  C4::Context->clear_syspref_cache();
+
+cleans the internal cache of sysprefs. Please call this method if
+you update the systempreferences table. Otherwise, your new changes
+will not be seen by this process.
+
+=cut
+
+sub clear_syspref_cache {
+    %sysprefs = ();
+}
+
+=head2 set_preference
+
+  C4::Context->set_preference( $variable, $value );
+
+This updates a preference's value both in the systempreferences table and in
+the sysprefs cache.
+
+=cut
+
+sub set_preference {
+    my $self = shift;
+    my $var = lc(shift);
+    my $value = shift;
+
+    my $dbh = C4::Context->dbh or return 0;
+
+    my $type = $dbh->selectrow_array( "SELECT type FROM systempreferences WHERE variable = ?", {}, $var );
+
+    $value = 0 if ( $type && $type eq 'YesNo' && $value eq '' );
+
+    my $sth = $dbh->prepare( "
+      INSERT INTO systempreferences
+        ( variable, value )
+        VALUES( ?, ? )
+        ON DUPLICATE KEY UPDATE value = VALUES(value)
+    " );
+
+    if($sth->execute( $var, $value )) {
+        $sysprefs{$var} = $value;
+    }
+    $sth->finish;
+}
+
 # AUTOLOAD
 # This implements C4::Config->foo, and simply returns
 # C4::Context->config("foo"), as described in the documentation for
@@ -487,9 +668,9 @@ sub AUTOLOAD
     return $self->config($AUTOLOAD);
 }
 
-=item Zconn
+=head2 Zconn
 
-$Zconn = C4::Context->Zconn
+  $Zconn = C4::Context->Zconn
 
 Returns a connection to the Zebra database for the current
 context. If no connection has yet been made, this method 
@@ -507,14 +688,13 @@ C<$auth> whether this connection has rw access (1) or just r access (0 or NULL)
 =cut
 
 sub Zconn {
-    my $self=shift;
-    my $server=shift;
-    my $async=shift;
-    my $auth=shift;
-    my $piggyback=shift;
-    my $syntax=shift;
-    if ( defined($context->{"Zconn"}->{$server}) && (0 == $context->{"Zconn"}->{$server}->errcode()) ) {
-        return $context->{"Zconn"}->{$server};
+    my ($self, $server, $async, $auth, $piggyback, $syntax) = @_;
+    #TODO: We actually just ignore the auth and syntax parameter
+    #It also looks like we are not passing auth, piggyback, syntax anywhere
+
+    my $cache_key = join ('::', (map { $_ // '' } ($server, $async, $auth, $piggyback, $syntax)));
+    if ( defined($context->{"Zconn"}->{$cache_key}) && (0 == $context->{"Zconn"}->{$cache_key}->errcode()) ) {
+        return $context->{"Zconn"}->{$cache_key};
     # No connection object or it died. Create one.
     }else {
         # release resources if we're closing a connection and making a new one
@@ -523,14 +703,14 @@ sub Zconn {
         # and make a new one, particularly for a batch job.  However, at
         # first glance it does not look like there's a way to easily check
         # the basic health of a ZOOM::Connection
-        $context->{"Zconn"}->{$server}->destroy() if defined($context->{"Zconn"}->{$server});
+        $context->{"Zconn"}->{$cache_key}->destroy() if defined($context->{"Zconn"}->{$cache_key});
 
-        $context->{"Zconn"}->{$server} = &_new_Zconn($server,$async,$auth,$piggyback,$syntax);
-        return $context->{"Zconn"}->{$server};
+        $context->{"Zconn"}->{$cache_key} = &_new_Zconn( $server, $async, $piggyback );
+        return $context->{"Zconn"}->{$cache_key};
     }
 }
 
-=item _new_Zconn
+=head2 _new_Zconn
 
 $context->{"Zconn"} = &_new_Zconn($server,$async);
 
@@ -545,31 +725,48 @@ C<$auth> whether this connection has rw access (1) or just r access (0 or NULL)
 =cut
 
 sub _new_Zconn {
-    my ($server,$async,$auth,$piggyback,$syntax) = @_;
+    my ( $server, $async, $piggyback ) = @_;
 
     my $tried=0; # first attempt
     my $Zconn; # connection object
-    $server = "biblioserver" unless $server;
-    $syntax = "usmarc" unless $syntax;
+    my $elementSetName;
+    my $index_mode;
+    my $syntax;
+
+    $server //= "biblioserver";
+
+    if ( $server eq 'biblioserver' ) {
+        $index_mode = $context->{'config'}->{'zebra_bib_index_mode'} // 'grs1';
+    } elsif ( $server eq 'authorityserver' ) {
+        $index_mode = $context->{'config'}->{'zebra_auth_index_mode'} // 'dom';
+    }
+
+    if ( $index_mode eq 'grs1' ) {
+        $elementSetName = 'F';
+        $syntax = ( $context->preference("marcflavour") eq 'UNIMARC' )
+                ? 'unimarc'
+                : 'usmarc';
+
+    } else { # $index_mode eq 'dom'
+        $syntax = 'xml';
+        $elementSetName = 'marcxml';
+    }
 
     my $host = $context->{'listen'}->{$server}->{'content'};
-    my $servername = $context->{"config"}->{$server};
     my $user = $context->{"serverinfo"}->{$server}->{"user"};
     my $password = $context->{"serverinfo"}->{$server}->{"password"};
- $auth = 1 if($user && $password);   
-    retry:
     eval {
         # set options
         my $o = new ZOOM::Options();
-        $o->option(user=>$user) if $auth;
-        $o->option(password=>$password) if $auth;
+        $o->option(user => $user) if $user && $password;
+        $o->option(password => $password) if $user && $password;
         $o->option(async => 1) if $async;
         $o->option(count => $piggyback) if $piggyback;
         $o->option(cqlfile=> $context->{"server"}->{$server}->{"cql2rpn"});
         $o->option(cclfile=> $context->{"serverinfo"}->{$server}->{"ccl2rpn"});
         $o->option(preferredRecordSyntax => $syntax);
-        $o->option(elementSetName => "F"); # F for 'full' as opposed to B for 'brief'
-        $o->option(databaseName => ($servername?$servername:"biblios"));
+        $o->option(elementSetName => $elementSetName) if $elementSetName;
+        $o->option(databaseName => $context->{"config"}->{$server}||"biblios");
 
         # create a new connection object
         $Zconn= create ZOOM::Connection($o);
@@ -581,25 +778,7 @@ sub _new_Zconn {
         if ($Zconn->errcode() !=0) {
             warn "something wrong with the connection: ". $Zconn->errmsg();
         }
-
     };
-#     if ($@) {
-#         # Koha manages the Zebra server -- this doesn't work currently for me because of permissions issues
-#         # Also, I'm skeptical about whether it's the best approach
-#         warn "problem with Zebra";
-#         if ( C4::Context->preference("ManageZebra") ) {
-#             if ($@->code==10000 && $tried==0) { ##No connection try restarting Zebra
-#                 $tried=1;
-#                 warn "trying to restart Zebra";
-#                 my $res=system("zebrasrv -f $ENV{'KOHA_CONF'} >/koha/log/zebra-error.log");
-#                 goto "retry";
-#             } else {
-#                 warn "Error ", $@->code(), ": ", $@->message(), "\n";
-#                 $Zconn="error";
-#                 return $Zconn;
-#             }
-#         }
-#     }
     return $Zconn;
 }
 
@@ -610,37 +789,53 @@ sub _new_Zconn {
 sub _new_dbh
 {
 
-### $context
-    ##correct name for db_schme        
-    my $db_driver;
-    if ($context->config("db_scheme")){
-    $db_driver=db_scheme2dbi($context->config("db_scheme"));
-    }else{
-    $db_driver="mysql";
-    }
+    ## $context
+    ## correct name for db_scheme
+    my $db_driver = $context->{db_driver};
 
     my $db_name   = $context->config("database");
     my $db_host   = $context->config("hostname");
-    my $db_port   = $context->config("port");
-    $db_port = "" unless defined $db_port;
+    my $db_port   = $context->config("port") || '';
     my $db_user   = $context->config("user");
     my $db_passwd = $context->config("pass");
     # MJR added or die here, as we can't work without dbh
-    my $dbh= DBI->connect("DBI:$db_driver:dbname=$db_name;host=$db_host;port=$db_port",
-         $db_user, $db_passwd) or die $DBI::errstr;
+    my $dbh = DBI->connect("DBI:$db_driver:dbname=$db_name;host=$db_host;port=$db_port",
+    $db_user, $db_passwd, {'RaiseError' => $ENV{DEBUG}?1:0 }) or die $DBI::errstr;
+
+    # Check for the existence of a systempreference table; if we don't have this, we don't
+    # have a valid database and should not set RaiseError in order to allow the installer
+    # to run; installer will not run otherwise since we raise all db errors
+
+    eval {
+                local $dbh->{PrintError} = 0;
+                local $dbh->{RaiseError} = 1;
+                $dbh->do(qq{SELECT * FROM systempreferences WHERE 1 = 0 });
+    };
+
+    if ($@) {
+        $dbh->{RaiseError} = 0;
+    }
+
+    if ( $db_driver eq 'mysql' ) {
+        $dbh->{mysql_auto_reconnect} = 1;
+    }
+
+       my $tz = $ENV{TZ};
     if ( $db_driver eq 'mysql' ) { 
         # Koha 3.0 is utf-8, so force utf8 communication between mySQL and koha, whatever the mysql default config.
         # this is better than modifying my.cnf (and forcing all communications to be in utf8)
         $dbh->{'mysql_enable_utf8'}=1; #enable
         $dbh->do("set NAMES 'utf8'");
+        ($tz) and $dbh->do(qq(SET time_zone = "$tz"));
     }
     elsif ( $db_driver eq 'Pg' ) {
            $dbh->do( "set client_encoding = 'UTF8';" );
+        ($tz) and $dbh->do(qq(SET TIME ZONE = "$tz"));
     }
     return $dbh;
 }
 
-=item dbh
+=head2 dbh
 
   $dbh = C4::Context->dbh;
 
@@ -659,11 +854,15 @@ possibly C<&set_dbh>.
 sub dbh
 {
     my $self = shift;
+    my $params = shift;
     my $sth;
 
-    if (defined($context->{"dbh"})) {
-        $sth=$context->{"dbh"}->prepare("select 1");
-        return $context->{"dbh"} if (defined($sth->execute));
+    unless ( $params->{new} ) {
+        if ( defined($context->{db_driver}) && $context->{db_driver} eq 'mysql' && $context->{"dbh"} ) {
+            return $context->{"dbh"};
+        } elsif ( defined($context->{"dbh"}) && $context->{"dbh"}->ping() ) {
+            return $context->{"dbh"};
+        }
     }
 
     # No database handle or it died . Create one.
@@ -672,7 +871,7 @@ sub dbh
     return $context->{"dbh"};
 }
 
-=item new_dbh
+=head2 new_dbh
 
   $dbh = C4::Context->new_dbh;
 
@@ -693,7 +892,7 @@ sub new_dbh
     return &_new_dbh();
 }
 
-=item set_dbh
+=head2 set_dbh
 
   $my_dbh = C4::Connect->new_dbh;
   C4::Connect->set_dbh($my_dbh);
@@ -724,7 +923,7 @@ sub set_dbh
     $context->{"dbh"} = $new_dbh;
 }
 
-=item restore_dbh
+=head2 restore_dbh
 
   C4::Context->restore_dbh;
 
@@ -751,7 +950,52 @@ sub restore_dbh
     # return something, then this function should, too.
 }
 
-=item marcfromkohafield
+=head2 queryparser
+
+  $queryparser = C4::Context->queryparser
+
+Returns a handle to an initialized Koha::QueryParser::Driver::PQF object.
+
+=cut
+
+sub queryparser {
+    my $self = shift;
+    unless (defined $context->{"queryparser"}) {
+        $context->{"queryparser"} = &_new_queryparser();
+    }
+
+    return
+      defined( $context->{"queryparser"} )
+      ? $context->{"queryparser"}->new
+      : undef;
+}
+
+=head2 _new_queryparser
+
+Internal helper function to create a new QueryParser object. QueryParser
+is loaded dynamically so as to keep the lack of the QueryParser library from
+getting in anyone's way.
+
+=cut
+
+sub _new_queryparser {
+    my $qpmodules = {
+        'OpenILS::QueryParser'           => undef,
+        'Koha::QueryParser::Driver::PQF' => undef
+    };
+    if ( can_load( 'modules' => $qpmodules ) ) {
+        my $QParser     = Koha::QueryParser::Driver::PQF->new();
+        my $config_file = $context->config('queryparser_config');
+        $config_file ||= '/etc/koha/searchengine/queryparser.yaml';
+        if ( $QParser->load_config($config_file) ) {
+            # TODO: allow indexes to be configured in the database
+            return $QParser;
+        }
+    }
+    return;
+}
+
+=head2 marcfromkohafield
 
   $dbh = C4::Context->marcfromkohafield;
 
@@ -792,7 +1036,7 @@ sub _new_marcfromkohafield
     return $marcfromkohafield;
 }
 
-=item stopwords
+=head2 stopwords
 
   $dbh = C4::Context->stopwords;
 
@@ -827,102 +1071,90 @@ sub _new_stopwords
     my $sth = $dbh->prepare("select word from stopwords");
     $sth->execute;
     while (my $stopword = $sth->fetchrow_array) {
-        my $retval = {};
         $stopwordlist->{$stopword} = uc($stopword);
     }
     $stopwordlist->{A} = "A" unless $stopwordlist;
     return $stopwordlist;
 }
 
-=item userenv
+=head2 userenv
 
   C4::Context->userenv;
 
-Builds a hash for user environment variables.
+Retrieves a hash for user environment variables.
 
 This hash shall be cached for future use: if you call
 C<C4::Context-E<gt>userenv> twice, you will get the same hash without real DB access
 
-set_userenv is called in Auth.pm
-
 =cut
 
 #'
-sub userenv
-{
+sub userenv {
     my $var = $context->{"activeuser"};
-    return $context->{"userenv"}->{$var} if (defined $context->{"userenv"}->{$var});
-    # insecure=1 management
-    if ($context->{"dbh"} && $context->preference('insecure')) {
-        my %insecure;
-        $insecure{flags} = '16382';
-        $insecure{branchname} ='Insecure';
-        $insecure{number} ='0';
-        $insecure{cardnumber} ='0';
-        $insecure{id} = 'insecure';
-        $insecure{branch} = 'INS';
-        $insecure{emailaddress} = 'test@mode.insecure.com';
-        return \%insecure;
+    if (defined $var and defined $context->{"userenv"}->{$var}) {
+        return $context->{"userenv"}->{$var};
     } else {
-        return 0;
+        return;
     }
 }
 
-=item set_userenv
+=head2 set_userenv
 
-  C4::Context->set_userenv($usernum, $userid, $usercnum, $userfirstname, $usersurname, $userbranch, $userflags, $emailaddress);
+  C4::Context->set_userenv($usernum, $userid, $usercnum, $userfirstname, 
+                  $usersurname, $userbranch, $userflags, $emailaddress, $branchprinter,
+                  $persona);
 
-Informs a hash for user environment variables.
-
-This hash shall be cached for future use: if you call
-C<C4::Context-E<gt>userenv> twice, you will get the same hash without real DB access
+Establish a hash of user environment variables.
 
 set_userenv is called in Auth.pm
 
 =cut
 
 #'
-sub set_userenv{
-    my ($usernum, $userid, $usercnum, $userfirstname, $usersurname, $userbranch, $branchname, $userflags, $emailaddress, $branchprinter)= @_;
-    my $var=$context->{"activeuser"};
+sub set_userenv {
+    my ($usernum, $userid, $usercnum, $userfirstname, $usersurname, $userbranch, $branchname, $userflags, $emailaddress, $branchprinter, $persona)= @_;
+    my $var=$context->{"activeuser"} || '';
     my $cell = {
         "number"     => $usernum,
         "id"         => $userid,
         "cardnumber" => $usercnum,
         "firstname"  => $userfirstname,
         "surname"    => $usersurname,
-#possibly a law problem
+        #possibly a law problem
         "branch"     => $userbranch,
         "branchname" => $branchname,
         "flags"      => $userflags,
-        "emailaddress"    => $emailaddress,
-               "branchprinter"    => $branchprinter
+        "emailaddress"     => $emailaddress,
+        "branchprinter"    => $branchprinter,
+        "persona"    => $persona,
     };
     $context->{userenv}->{$var} = $cell;
     return $cell;
 }
 
-sub set_shelves_userenv ($$) {
-       my ($type, $shelves) = @_ or return undef;
-       my $activeuser = $context->{activeuser} or return undef;
+sub set_shelves_userenv {
+       my ($type, $shelves) = @_ or return;
+       my $activeuser = $context->{activeuser} or return;
        $context->{userenv}->{$activeuser}->{barshelves} = $shelves if $type eq 'bar';
        $context->{userenv}->{$activeuser}->{pubshelves} = $shelves if $type eq 'pub';
+       $context->{userenv}->{$activeuser}->{totshelves} = $shelves if $type eq 'tot';
 }
 
-sub get_shelves_userenv () {
+sub get_shelves_userenv {
        my $active;
        unless ($active = $context->{userenv}->{$context->{activeuser}}) {
                $debug and warn "get_shelves_userenv cannot retrieve context->{userenv}->{context->{activeuser}}";
-               return undef;
+               return;
        }
+       my $totshelves = $active->{totshelves} or undef;
        my $pubshelves = $active->{pubshelves} or undef;
-       my $barshelves = $active->{barshelves} or undef;#  die "get_shelves_userenv: activeenv has no ->{shelves}";
-       return $pubshelves, $barshelves;
+       my $barshelves = $active->{barshelves} or undef;
+       return ($totshelves, $pubshelves, $barshelves);
 }
 
-=item _new_userenv
+=head2 _new_userenv
 
-  C4::Context->_new_userenv($session);
+  C4::Context->_new_userenv($session);  # FIXME: This calling style is wrong for what looks like an _internal function
 
 Builds a hash for user environment variables.
 
@@ -936,12 +1168,12 @@ _new_userenv is called in Auth.pm
 #'
 sub _new_userenv
 {
-    shift;
+    shift;  # Useless except it compensates for bad calling style
     my ($sessionID)= @_;
      $context->{"activeuser"}=$sessionID;
 }
 
-=item _unset_userenv
+=head2 _unset_userenv
 
   C4::Context->_unset_userenv;
 
@@ -958,7 +1190,7 @@ sub _unset_userenv
 }
 
 
-=item get_versions
+=head2 get_versions
 
   C4::Context->get_versions
 
@@ -973,32 +1205,91 @@ sub get_versions {
     my %versions;
     $versions{kohaVersion}  = KOHAVERSION();
     $versions{kohaDbVersion} = C4::Context->preference('version');
-    $versions{osVersion} = `uname -a`;
+    $versions{osVersion} = join(" ", POSIX::uname());
     $versions{perlVersion} = $];
-    $versions{mysqlVersion} = `mysql -V`;
-    $versions{apacheVersion} =  `httpd -v`;
-    $versions{apacheVersion} =  `httpd2 -v`            unless  $versions{apacheVersion} ;
-    $versions{apacheVersion} =  `apache2 -v`           unless  $versions{apacheVersion} ;
-    $versions{apacheVersion} =  `/usr/sbin/apache2 -v` unless  $versions{apacheVersion} ;
+    {
+        no warnings qw(exec); # suppress warnings if unable to find a program in $PATH
+        $versions{mysqlVersion}  = `mysql -V`;
+        $versions{apacheVersion} = `httpd -v`;
+        $versions{apacheVersion} = `httpd2 -v`            unless  $versions{apacheVersion} ;
+        $versions{apacheVersion} = `apache2 -v`           unless  $versions{apacheVersion} ;
+        $versions{apacheVersion} = `/usr/sbin/apache2 -v` unless  $versions{apacheVersion} ;
+    }
     return %versions;
 }
 
 
+=head2 tz
+
+  C4::Context->tz
+
+  Returns a DateTime::TimeZone object for the system timezone
+
+=cut
+
+sub tz {
+    my $self = shift;
+    if (!defined $context->{tz}) {
+        $context->{tz} = DateTime::TimeZone->new(name => 'local');
+    }
+    return $context->{tz};
+}
+
+
+=head2 IsSuperLibrarian
+
+    C4::Context->IsSuperlibrarian();
+
+=cut
+
+sub IsSuperLibrarian {
+    my $userenv = C4::Context->userenv;
+
+    unless ( $userenv and exists $userenv->{flags} ) {
+        # If we reach this without a user environment,
+        # assume that we're running from a command-line script,
+        # and act as a superlibrarian.
+        carp("C4::Context->userenv not defined!");
+        return 1;
+    }
+
+    return ($userenv->{flags}//0) % 2;
+}
+
+=head2 interface
+
+Sets the current interface for later retrieval in any Perl module
+
+    C4::Context->interface('opac');
+    C4::Context->interface('intranet');
+    my $interface = C4::Context->interface;
+
+=cut
+
+sub interface {
+    my ($class, $interface) = @_;
+
+    if (defined $interface) {
+        $interface = lc $interface;
+        if ($interface eq 'opac' || $interface eq 'intranet') {
+            $context->{interface} = $interface;
+        } else {
+            warn "invalid interface : '$interface'";
+        }
+    }
+
+    return $context->{interface} // 'opac';
+}
+
 1;
 __END__
 
-=back
-
 =head1 ENVIRONMENT
 
-=over 4
-
-=item C<KOHA_CONF>
+=head2 C<KOHA_CONF>
 
 Specifies the configuration file to read.
 
-=back
-
 =head1 SEE ALSO
 
 XML::Simple
@@ -1010,217 +1301,3 @@ Andrew Arensburger <arensb at ooblick dot com>
 Joshua Ferraro <jmf at liblime dot com>
 
 =cut
-
-# Revision 1.57  2007/05/22 09:13:55  tipaul
-# Bugfixes & improvements (various and minor) :
-# - updating templates to have tmpl_process3.pl running without any errors
-# - adding a drupal-like css for prog templates (with 3 small images)
-# - fixing some bugs in circulation & other scripts
-# - updating french translation
-# - fixing some typos in templates
-#
-# Revision 1.56  2007/04/23 15:21:17  tipaul
-# renaming currenttransfers to transferstoreceive
-#
-# Revision 1.55  2007/04/17 08:48:00  tipaul
-# circulation cleaning continued: bufixing
-#
-# Revision 1.54  2007/03/29 16:45:53  tipaul
-# Code cleaning of Biblio.pm (continued)
-#
-# All subs have be cleaned :
-# - removed useless
-# - merged some
-# - reordering Biblio.pm completly
-# - using only naming conventions
-#
-# Seems to have broken nothing, but it still has to be heavily tested.
-# Note that Biblio.pm is now much more efficient than previously & probably more reliable as well.
-#
-# Revision 1.53  2007/03/29 13:30:31  tipaul
-# Code cleaning :
-# == Biblio.pm cleaning (useless) ==
-# * some sub declaration dropped
-# * removed modbiblio sub
-# * removed moditem sub
-# * removed newitems. It was used only in finishrecieve. Replaced by a TransformKohaToMarc+AddItem, that is better.
-# * removed MARCkoha2marcItem
-# * removed MARCdelsubfield declaration
-# * removed MARCkoha2marcBiblio
-#
-# == Biblio.pm cleaning (naming conventions) ==
-# * MARCgettagslib renamed to GetMarcStructure
-# * MARCgetitems renamed to GetMarcItem
-# * MARCfind_frameworkcode renamed to GetFrameworkCode
-# * MARCmarc2koha renamed to TransformMarcToKoha
-# * MARChtml2marc renamed to TransformHtmlToMarc
-# * MARChtml2xml renamed to TranformeHtmlToXml
-# * zebraop renamed to ModZebra
-#
-# == MARC=OFF ==
-# * removing MARC=OFF related scripts (in cataloguing directory)
-# * removed checkitems (function related to MARC=off feature, that is completly broken in head. If someone want to reintroduce it, hard work coming...)
-# * removed getitemsbybiblioitem (used only by MARC=OFF scripts, that is removed as well)
-#
-# Revision 1.52  2007/03/16 01:25:08  kados
-# Using my precrash CVS copy I did the following:
-#
-# cvs -z3 -d:ext:kados@cvs.savannah.nongnu.org:/sources/koha co -P koha
-# find koha.precrash -type d -name "CVS" -exec rm -v {} \;
-# cp -r koha.precrash/* koha/
-# cd koha/
-# cvs commit
-#
-# This should in theory put us right back where we were before the crash
-#
-# Revision 1.52  2007/03/12 21:17:05  rych
-# add server, serverinfo as arrays from config
-#
-# Revision 1.51  2007/03/09 14:31:47  tipaul
-# rel_3_0 moved to HEAD
-#
-# Revision 1.43.2.10  2007/02/09 17:17:56  hdl
-# Managing a little better database absence.
-# (preventing from BIG 550)
-#
-# Revision 1.43.2.9  2006/12/20 16:50:48  tipaul
-# improving "insecure" management
-#
-# WARNING KADOS :
-# you told me that you had some libraries with insecure=ON (behind a firewall).
-# In this commit, I created a "fake" user when insecure=ON. It has a fake branch. You may find better to have the 1st branch in branch table instead of a fake one.
-#
-# Revision 1.43.2.8  2006/12/19 16:48:16  alaurin
-# reident programs, and adding branchcode value in reserves
-#
-# Revision 1.43.2.7  2006/12/06 21:55:38  hdl
-# Adding ModZebrations for servers to get serverinfos in Context.pm
-# Using this function in rebuild_zebra.pl
-#
-# Revision 1.43.2.6  2006/11/24 21:18:31  kados
-# very minor changes, no functional ones, just comments, etc.
-#
-# Revision 1.43.2.5  2006/10/30 13:24:16  toins
-# fix some minor POD error.
-#
-# Revision 1.43.2.4  2006/10/12 21:42:49  hdl
-# Managing multiple zebra connections
-#
-# Revision 1.43.2.3  2006/10/11 14:27:26  tipaul
-# removing a warning
-#
-# Revision 1.43.2.2  2006/10/10 15:28:16  hdl
-# BUG FIXING : using database name in Zconn if defined and not hard coded value
-#
-# Revision 1.43.2.1  2006/10/06 13:47:28  toins
-# Synch with dev_week.
-#  /!\ WARNING :: Please now use the new version of koha.xml.
-#
-# Revision 1.18.2.5.2.14  2006/09/24 15:24:06  kados
-# remove Zebraauth routine, fold the functionality into Zconn
-# Zconn can now take several arguments ... this will probably
-# change soon as I'm not completely happy with the readability
-# of the current format ... see the POD for details.
-#
-# cleaning up Biblio.pm, removing unnecessary routines.
-#
-# DeleteBiblio - used to delete a biblio from zebra and koha tables
-#     -- checks to make sure there are no existing issues
-#     -- saves backups of biblio,biblioitems,items in deleted* tables
-#     -- does commit operation
-#
-# getRecord - used to retrieve one record from zebra in piggyback mode using biblionumber
-# brought back z3950_extended_services routine
-#
-# Lots of modifications to Context.pm, you can now store user and pass info for
-# multiple servers (for federated searching) using the <serverinfo> element.
-# I'll commit my koha.xml to demonstrate this or you can refer to the POD in
-# Context.pm (which I also expanded on).
-#
-# Revision 1.18.2.5.2.13  2006/08/10 02:10:21  kados
-# Turned warnings on, and running a search turned up lots of warnings.
-# Cleaned up those ...
-#
-# removed getitemtypes from Koha.pm (one in Search.pm looks newer)
-# removed itemcount from Biblio.pm
-#
-# made some local subs local with a _ prefix (as they were redefined
-# elsewhere)
-#
-# Add two new search subs to Search.pm the start of a new search API
-# that's a bit more scalable
-#
-# Revision 1.18.2.5.2.10  2006/07/21 17:50:51  kados
-# moving the *.properties files to intranetdir/etc dir
-#
-# Revision 1.18.2.5.2.9  2006/07/17 08:05:20  tipaul
-# there was a hardcoded link to /koha/etc/ I replaced it with intranetdir config value
-#
-# Revision 1.18.2.5.2.8  2006/07/11 12:20:37  kados
-# adding ccl and cql files ... Tumer, if you want to fit these into the
-# config file by all means do.
-#
-# Revision 1.18.2.5.2.7  2006/06/04 22:50:33  tgarip1957
-# We do not hard code cql2rpn conversion file in context.pm our koha.xml configuration file already describes the path for this file.
-# At cql searching we use method CQL not CQL2RPN as the cql2rpn conversion file is defined at server level
-#
-# Revision 1.18.2.5.2.6  2006/06/02 23:11:24  kados
-# Committing my working dev_week. It's been tested only with
-# searching, and there's quite a lot of config stuff to set up
-# beforehand. As things get closer to a release, we'll be making
-# some scripts to do it for us
-#
-# Revision 1.18.2.5.2.5  2006/05/28 18:49:12  tgarip1957
-# This is an unusual commit. The main purpose is a working model of Zebra on a modified rel2_2.
-# Any questions regarding these commits should be asked to Joshua Ferraro unless you are Joshua whom I'll report to
-#
-# Revision 1.36  2006/05/09 13:28:08  tipaul
-# adding the branchname and the librarian name in every page :
-# - modified userenv to add branchname
-# - modifier menus.inc to have the librarian name & userenv displayed on every page. they are in a librarian_information div.
-#
-# Revision 1.35  2006/04/13 08:40:11  plg
-# bug fixed: typo on Zconnauth name
-#
-# Revision 1.34  2006/04/10 21:40:23  tgarip1957
-# A new handler defined for zebra Zconnauth with read/write permission. Zconnauth should only be called in biblio.pm where write operations are. Use of this handler will break things unless koha.conf contains new variables:
-# zebradb=localhost
-# zebraport=<your port>
-# zebrauser=<username>
-# zebrapass=<password>
-#
-# The zebra.cfg file should read:
-# perm.anonymous:r
-# perm.username:rw
-# passw.c:<yourpasswordfile>
-#
-# Password file should be prepared with Apaches htpasswd utility in encrypted mode and should exist in a folder zebra.cfg can read
-#
-# Revision 1.33  2006/03/15 11:21:56  plg
-# bug fixed: utf-8 data where not displayed correctly in screens. Supposing
-# your data are truely utf-8 encoded in your database, they should be
-# correctly displayed. "set names 'UTF8'" on mysql connection (C4/Context.pm)
-# is mandatory and "binmode" to utf8 (C4/Interface/CGI/Output.pm) seemed to
-# converted data twice, so it was removed.
-#
-# Revision 1.32  2006/03/03 17:25:01  hdl
-# Bug fixing : a line missed a comment sign.
-#
-# Revision 1.31  2006/03/03 16:45:36  kados
-# Remove the search that tests the Zconn -- warning, still no fault
-# tollerance
-#
-# Revision 1.30  2006/02/22 00:56:59  kados
-# First go at a connection object for Zebra. You can now get a
-# connection object by doing:
-#
-# my $Zconn = C4::Context->Zconn;
-#
-# My initial tests indicate that as soon as your funcion ends
-# (ie, when you're done doing something) the connection will be
-# closed automatically. There may be some other way to make the
-# connection more stateful, I'm not sure...
-#
-# Local Variables:
-# tab-width: 4
-# End: