Bugzilla – Attachment 25979 Details for
Bug 10486
Allow external Z39.50 targets to be searched from the OPAC
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Help
|
New Account
|
Log In
[x]
|
Forgot Password
Login:
[x]
[patch]
Bug 10486 - Allow external Z39.50 targets to be searched from the OPAC
Bug-10486---Allow-external-Z3950-targets-to-be-sea.patch (text/plain), 172.83 KB, created by
Jesse Weaver
on 2014-03-10 10:52:12 UTC
(
hide
)
Description:
Bug 10486 - Allow external Z39.50 targets to be searched from the OPAC
Filename:
MIME Type:
Creator:
Jesse Weaver
Created:
2014-03-10 10:52:12 UTC
Size:
172.83 KB
patch
obsolete
>From 98a24b8602f71e7e2316809c78b66debac9f94d6 Mon Sep 17 00:00:00 2001 >From: Jesse Weaver <pianohacker@gmail.com> >Date: Wed, 29 May 2013 14:59:57 -0600 >Subject: [PATCH] Bug 10486 - Allow external Z39.50 targets to be searched from > the OPAC > >This adds a new page to the OPAC that can search several Z39.50 targets >using Pazpar2. > >Test plan: >1) Apply patches. >2) Update database. >3) Add one or more Z39.50 targets, using the Administration -> External > targets screen. Note that not all targets support keyword search, but > here are some that do: > * Library of Congress: lx2.loc.gov, 210, LCDB, none, none, MARC21, UTF-8 > * BNF2: z3950.bnf.fr, 2211, TOUT, Z3950, Z3950_BNF, UNIMARC, UTF-8 >4) Perform a search on the OPAC, check to see if the "external targets" > notice pops up at the top. >5) Click on the results link near that message. >6) This should bring back results from all enabled targets, all of which > should be clickable to bring up a detailed view of the record. > >(This version fixes some typos and accidental changes.) >--- > C4/Search.pm | 30 + > C4/XSLT.pm | 24 +- > Makefile.PL | 4 +- > admin/external_targets.pl | 125 +++ > etc/koha-conf.xml | 13 - > etc/koha-httpd.conf | 15 +- > etc/pazpar2/generic-settings.xml | 21 + > etc/pazpar2/pazpar2.xml | 2 +- > etc/pazpar2/unimarc-work-groups.xsl | 98 ++ > installer/data/mysql/sysprefs.sql | 1 + > installer/data/mysql/updatedatabase.pl | 34 +- > .../intranet-tmpl/prog/en/css/staff-global.css | 14 + > .../intranet-tmpl/prog/en/includes/admin-menu.inc | 1 + > .../prog/en/modules/admin/admin-home.tt | 2 + > .../prog/en/modules/admin/external-targets.tt | 180 ++++ > .../prog/en/modules/admin/preferences/opac.pref | 7 + > koha-tmpl/opac-tmpl/bootstrap/css/opac.css | 21 +- > .../bootstrap/en/modules/opac-external-search.tt | 252 +++++ > .../opac-tmpl/bootstrap/en/modules/opac-results.tt | 59 +- > .../bootstrap/en/xslt/MARC21slim2OPACResults.xsl | 7 +- > .../bootstrap/en/xslt/MARC21slimUtils.xsl | 2 +- > .../bootstrap/en/xslt/UNIMARCslim2OPACResults.xsl | 4 + > koha-tmpl/opac-tmpl/bootstrap/js/externalsearch.js | 106 ++ > .../lib/jquery/plugins/jquery.lightbox_me.js | 254 +++++ > koha-tmpl/opac-tmpl/ccsr/en/css/opac.css | 16 + > koha-tmpl/opac-tmpl/ccsr/en/js/externalsearch.js | 106 ++ > .../en/lib/jquery/plugins/jquery.lightbox_me.js | 254 +++++ > koha-tmpl/opac-tmpl/lib/pz2.js | 1124 ++++++++++++++++++++ > koha-tmpl/opac-tmpl/prog/en/css/opac.css | 27 +- > koha-tmpl/opac-tmpl/prog/en/js/externalsearch.js | 106 ++ > .../en/lib/jquery/plugins/jquery.lightbox_me.js | 254 +++++ > .../prog/en/modules/opac-external-search.tt | 240 +++++ > .../opac-tmpl/prog/en/modules/opac-results.tt | 59 +- > .../prog/en/xslt/MARC21slim2OPACResults.xsl | 6 +- > .../opac-tmpl/prog/en/xslt/MARC21slimUtils.xsl | 2 +- > .../prog/en/xslt/UNIMARCslim2OPACResults.xsl | 4 + > opac/opac-external-search.pl | 62 ++ > opac/opac-search.pl | 12 +- > opac/svc/pazpar2_init | 100 ++ > rewrite-config.PL | 8 +- > t/XSLT.t | 94 +- > 41 files changed, 3676 insertions(+), 74 deletions(-) > create mode 100755 admin/external_targets.pl > create mode 100644 etc/pazpar2/generic-settings.xml > create mode 100644 etc/pazpar2/unimarc-work-groups.xsl > create mode 100644 koha-tmpl/intranet-tmpl/prog/en/modules/admin/external-targets.tt > create mode 100644 koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-external-search.tt > create mode 100644 koha-tmpl/opac-tmpl/bootstrap/js/externalsearch.js > create mode 100644 koha-tmpl/opac-tmpl/bootstrap/lib/jquery/plugins/jquery.lightbox_me.js > create mode 100644 koha-tmpl/opac-tmpl/ccsr/en/js/externalsearch.js > create mode 100644 koha-tmpl/opac-tmpl/ccsr/en/lib/jquery/plugins/jquery.lightbox_me.js > create mode 100644 koha-tmpl/opac-tmpl/lib/pz2.js > create mode 100644 koha-tmpl/opac-tmpl/prog/en/js/externalsearch.js > create mode 100644 koha-tmpl/opac-tmpl/prog/en/lib/jquery/plugins/jquery.lightbox_me.js > create mode 100644 koha-tmpl/opac-tmpl/prog/en/modules/opac-external-search.tt > create mode 100755 opac/opac-external-search.pl > create mode 100755 opac/svc/pazpar2_init > >diff --git a/C4/Search.pm b/C4/Search.pm >index 09c1951..b1da652 100644 >--- a/C4/Search.pm >+++ b/C4/Search.pm >@@ -72,6 +72,7 @@ This module provides searching functions for Koha's bibliographic databases > &GetDistinctValues > &enabled_staff_search_views > &PurgeSearchHistory >+ &GetExternalSearchTargets > ); > > # make all your functions, whether exported or not; >@@ -2431,6 +2432,35 @@ sub new_record_from_zebra { > > } > >+=head2 GetExternalSearchTargets >+ >+Returns the list of Z39.50 servers that are marked for search in the OPAC using >+Pazpar2. >+ >+=cut >+ >+sub GetExternalSearchTargets { >+ my ( $branchcode ) = @_; >+ >+ if ( $branchcode ) { >+ return C4::Context->dbh->selectall_arrayref( q{ >+ SELECT * FROM external_targets et >+ LEFT JOIN external_target_restrictions etr >+ ON (etr.target_id = et.target_id and etr.branchcode = ?) >+ WHERE etr.target_id IS NULL >+ ORDER BY et.name >+ }, { Slice => {} }, $branchcode ); >+ } else { >+ return C4::Context->dbh->selectall_arrayref( q{ >+ SELECT * FROM external_targets et >+ LEFT JOIN external_target_restrictions etr USING (target_id) >+ GROUP by et.target_id >+ HAVING branchcode IS NULL >+ ORDER BY et.name >+ }, { Slice => {} } ); >+ } >+} >+ > END { } # module clean-up code here (global destructor) > > 1; >diff --git a/C4/XSLT.pm b/C4/XSLT.pm >index d85b048..7ebac8a 100644 >--- a/C4/XSLT.pm >+++ b/C4/XSLT.pm >@@ -42,6 +42,7 @@ BEGIN { > $VERSION = 3.07.00.049; > @ISA = qw(Exporter); > @EXPORT = qw( >+ &XSLTGetFilename > &XSLTParse4Display > &GetURI > ); >@@ -156,9 +157,10 @@ sub _get_best_default_xslt_filename { > return $xslfilename; > } > >-sub XSLTParse4Display { >- my ( $biblionumber, $orig_record, $xslsyspref, $fixamps, $hidden_items ) = @_; >- my $xslfilename = C4::Context->preference($xslsyspref); >+sub XSLTGetFilename { >+ my ( $marcflavour, $xslsyspref ) = @_; >+ >+ my $xslfilename = $marcflavour eq C4::Context->preference('marcflavour') ? C4::Context->preference($xslsyspref) : 'default'; > if ( $xslfilename =~ /^\s*"?default"?\s*$/i ) { > my $htdocs; > my $theme; >@@ -167,22 +169,22 @@ sub XSLTParse4Display { > if ($xslsyspref eq "XSLTDetailsDisplay") { > $htdocs = C4::Context->config('intrahtdocs'); > $theme = C4::Context->preference("template"); >- $xslfile = C4::Context->preference('marcflavour') . >+ $xslfile = $marcflavour . > "slim2intranetDetail.xsl"; > } elsif ($xslsyspref eq "XSLTResultsDisplay") { > $htdocs = C4::Context->config('intrahtdocs'); > $theme = C4::Context->preference("template"); >- $xslfile = C4::Context->preference('marcflavour') . >+ $xslfile = $marcflavour . > "slim2intranetResults.xsl"; > } elsif ($xslsyspref eq "OPACXSLTDetailsDisplay") { > $htdocs = C4::Context->config('opachtdocs'); > $theme = C4::Context->preference("opacthemes"); >- $xslfile = C4::Context->preference('marcflavour') . >+ $xslfile = $marcflavour . > "slim2OPACDetail.xsl"; > } elsif ($xslsyspref eq "OPACXSLTResultsDisplay") { > $htdocs = C4::Context->config('opachtdocs'); > $theme = C4::Context->preference("opacthemes"); >- $xslfile = C4::Context->preference('marcflavour') . >+ $xslfile = $marcflavour . > "slim2OPACResults.xsl"; > } > $xslfilename = _get_best_default_xslt_filename($htdocs, $theme, $lang, $xslfile); >@@ -193,6 +195,14 @@ sub XSLTParse4Display { > $xslfilename =~ s/\{langcode\}/$lang/; > } > >+ return $xslfilename; >+} >+ >+sub XSLTParse4Display { >+ my ( $biblionumber, $orig_record, $xslsyspref, $fixamps, $hidden_items ) = @_; >+ >+ my $xslfilename = XSLTGetFilename( C4::Context->preference( 'marcflavour' ), $xslsyspref ); >+ > # grab the XML, run it through our stylesheet, push it out to the browser > my $record = transformMARCXML4XSLT($biblionumber, $orig_record); > #return $record->as_formatted(); >diff --git a/Makefile.PL b/Makefile.PL >index 8b89633..8f01e49 100644 >--- a/Makefile.PL >+++ b/Makefile.PL >@@ -531,8 +531,8 @@ push @{ $pl_files->{'rewrite-config.PL'} }, ( > ); > if ($config{'INSTALL_PAZPAR2'} eq 'yes') { > push @{ $pl_files->{'rewrite-config.PL'} }, ( >- 'blib/PAZPAR2_CONF_DIR/koha-biblios.xml', >- 'blib/PAZPAR2_CONF_DIR/pazpar2.xml' >+ 'blib/PAZPAR2_CONF_DIR/generic-settings.xml', >+ 'blib/PAZPAR2_CONF_DIR/pazpar2.xml', > ); > } > $config{'ZEBRA_AUTH_CFG'} = $config{'AUTH_INDEX_MODE'} eq 'dom' >diff --git a/admin/external_targets.pl b/admin/external_targets.pl >new file mode 100755 >index 0000000..0fc9037 >--- /dev/null >+++ b/admin/external_targets.pl >@@ -0,0 +1,125 @@ >+#!/usr/bin/perl >+# >+# Copyright 2013 Jesse Weaver >+# >+# This file is part of Koha. >+# >+# Koha is free software; you can redistribute it and/or modify it under the >+# terms of the GNU General Public License as published by the Free Software >+# Foundation; either version 2 of the License, or (at your option) any later >+# version. >+# >+# Koha is distributed in the hope that it will be useful, but WITHOUT ANY >+# 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., >+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. >+# >+ >+use Modern::Perl '2010'; >+ >+use CGI; >+ >+use C4::Auth; >+use C4::Context; >+use C4::Branch; >+use C4::Output; >+use URI::Escape; >+ >+our $dbh = C4::Context->dbh; >+our $input = new CGI; >+ >+our ( $template, $loggedinuser, $cookie ) = get_template_and_user( { >+ template_name => "admin/external-targets.tt", >+ query => $input, >+ type => "intranet", >+ authnotrequired => 0, >+ flagsrequired => {parameters => 'parameters_remaining_permissions'}, >+ debug => 1, >+} ); >+ >+our $op = $input->param( 'op' ) // 'show'; >+$template->{VARS}->{op} = $op; >+ >+given ( $op ) { >+ when ( 'show' ) { show_external_targets(); } >+ when ( 'add' ) { show_edit_form(); } >+ when ( 'edit' ) { show_edit_form(); } >+ when ( 'save' ) { save_target(); } >+ when ( 'delete' ) { delete_target(); } >+} >+ >+output_html_with_http_headers $input, $cookie, $template->output; >+ >+sub show_external_targets { >+ $template->{VARS}->{saved_id} = $input->param( 'saved_id' ); >+ $template->{VARS}->{deleted_name} = $input->param( 'deleted_name' ); >+ $template->{VARS}->{targets} = $dbh->selectall_arrayref( q{ >+ SELECT * >+ FROM external_targets >+ }, { Slice => {} } ); >+} >+ >+sub show_edit_form { >+ $template->{VARS}->{branches} = GetBranchesLoop( undef, 0 ); >+ $template->{VARS}->{syntaxes} = [ 'MARC21', 'UNIMARC', 'NORMARC' ]; >+ $template->{VARS}->{encodings} = { 'utf8' => 'UTF-8', 'marc8' => 'MARC-8' }; >+ >+ my $target_id; >+ if ( $target_id = $input->param( 'target_id' ) ) { >+ $template->{VARS}->{target} = $dbh->selectrow_hashref( q{ SELECT * FROM external_targets WHERE target_id = ? }, {}, $target_id ); >+ >+ my $available_branches = $dbh->selectall_hashref( q{ SELECT * FROM external_target_restrictions WHERE target_id = ? }, 'branchcode', {}, $target_id ); >+ >+ foreach my $branch ( @{ $template->{VARS}->{branches} } ) { >+ $branch->{selected} = 1 if ( $available_branches->{$branch->{branchcode}} ); >+ } >+ } >+} >+ >+sub save_target { >+ my $target_id; >+ if ( $target_id = $input->param( 'target_id' ) ) { >+ $dbh->do( q{ >+ UPDATE external_targets >+ SET name = ?, host = ?, port = ?, db = ?, userid = ?, password = ?, syntax = ?, encoding = ? >+ WHERE target_id = ? >+ }, {}, map { $input->param( $_ ) // '' } qw( name host port db userid password syntax encoding target_id ) ); >+ } else { >+ $dbh->do( q{ >+ INSERT >+ INTO external_targets(name, host, port, db, userid, password, syntax, encoding) >+ VALUES(?, ?, ?, ?, ?, ?, ?, ?) >+ }, {}, map { $input->param( $_ ) // '' } qw( name host port db userid password syntax encoding ) ); >+ $target_id = $dbh->last_insert_id( undef, undef, undef, undef ); >+ } >+ >+ $dbh->do( q{ >+ DELETE >+ FROM external_target_restrictions >+ WHERE target_id = ? >+ }, {}, $target_id ); >+ >+ foreach my $branchcode ( $input->param( 'branch' ) ) { >+ $dbh->do( q{ >+ INSERT >+ INTO external_target_restrictions(branchcode, target_id) >+ VALUES(?, ?) >+ }, {}, $branchcode, $target_id ); >+ } >+ >+ print $input->redirect( '/cgi-bin/koha/admin/external_targets.pl?saved_id=' . $target_id ); >+ exit; >+} >+ >+sub delete_target { >+ my ($target_id, $target); >+ >+ return unless ( $target_id = $input->param( 'target_id' ) and $target = $dbh->selectrow_hashref( q{ SELECT * FROM external_targets WHERE target_id = ? }, {}, $target_id ) ); >+ >+ $dbh->do( q{ DELETE FROM external_targets WHERE target_id = ? }, {}, $target_id ); >+ >+ print $input->redirect( '/cgi-bin/koha/admin/external_targets.pl?deleted_name=' . uri_escape( $target->{'name'} ) ); >+} >diff --git a/etc/koha-conf.xml b/etc/koha-conf.xml >index 72da2de..78b55ac 100644 >--- a/etc/koha-conf.xml >+++ b/etc/koha-conf.xml >@@ -19,19 +19,6 @@ > <listen id="publicserver" >tcp:@:__ZEBRA_SRU_BIBLIOS_PORT__</listen> > --> > >-<!-- Settings for special biblio server instance for PazPar2. >- Because PazPar2 only connects to a Z39.50 server using TCP/IP, >- it cannot use the Unix-domain socket that biblioserver uses. >- Therefore, a custom server is defined. --> >-__PAZPAR2_TOGGLE_XML_PRE__ >-<listen id="mergeserver">tcp:@:__MERGE_SERVER_PORT__</listen> >-<server id="mergeserver" listenref="mergeserver"> >- <directory>__ZEBRA_DATA_DIR__/biblios</directory> >- <config>__ZEBRA_CONF_DIR__/__ZEBRA_BIB_CFG__</config> >- <cql2rpn>__ZEBRA_CONF_DIR__/pqf.properties</cql2rpn> >-</server> >-__PAZPAR2_TOGGLE_XML_POST__ >- > <!-- BIBLIOGRAPHIC RECORDS --> > <server id="biblioserver" listenref="biblioserver"> > <directory>__ZEBRA_DATA_DIR__/biblios</directory> >diff --git a/etc/koha-httpd.conf b/etc/koha-httpd.conf >index f9a1970..df3dae1 100644 >--- a/etc/koha-httpd.conf >+++ b/etc/koha-httpd.conf >@@ -32,7 +32,7 @@ > </Directory> > > # Secure internal stuff >- <DirectoryMatch "__OPAC_WWW_DIR__/.*/(modules|xslt|includes)"> >+ <DirectoryMatch "__OPAC_WWW_DIR__/.*/(modules|includes)"> > Order deny,allow > Deny from all > </DirectoryMatch> >@@ -113,6 +113,17 @@ > RewriteRule ^/isbn/([^\/]*)/?$ /search?q=isbn:$1 [PT] > RewriteRule ^/issn/([^\/]*)/?$ /search?q=issn:$1 [PT] > </IfModule> >+ >+ __PAZPAR2_TOGGLE_HTTPD_PRE__ >+ <Proxy *> >+ AddDefaultCharset off >+ Order deny,allow >+ Allow from all >+ </Proxy> >+ >+ ProxyRequests off >+ ProxyPass /pazpar2/search.pz2 http://__PAZPAR2_HOST__:__PAZPAR2_PORT__/search.pz2 >+ __PAZPAR2_TOGGLE_HTTPD_POST__ > </VirtualHost> > > ## Intranet >@@ -149,7 +160,7 @@ > </Directory> > > # Secure internal stuff >- <DirectoryMatch "__INTRANET_WWW_DIR__/.*/(modules|xslt|includes)"> >+ <DirectoryMatch "__INTRANET_WWW_DIR__/.*/(modules|includes)"> > Order deny,allow > Deny from all > </DirectoryMatch> >diff --git a/etc/pazpar2/generic-settings.xml b/etc/pazpar2/generic-settings.xml >new file mode 100644 >index 0000000..5d56b99 >--- /dev/null >+++ b/etc/pazpar2/generic-settings.xml >@@ -0,0 +1,21 @@ >+<settings target="*"> >+ <!-- This file introduces default settings for pazpar2 --> >+ <!-- $Id: loc.xml,v 1.2 2007-07-10 13:43:07 adam Exp $ --> >+ >+ <!-- mapping for unqualified search --> >+ <set name="pz:cclmap:term" value="u=1016 t=l,r s=al"/> >+ >+ <!-- field-specific mappings --> >+ >+ <set name="pz:cclmap:au" value="u=1004 s=al"/> >+ <set name="pz:cclmap:ti" value="u=4 s=al"/> >+ <set name="pz:cclmap:su" value="u=21 s=al"/> >+ <set name="pz:cclmap:isbn" value="u=7"/> >+ <set name="pz:cclmap:issn" value="u=8"/> >+ <set name="pz:cclmap:date" value="u=30 r=r"/> >+ >+ <!-- Retrieval settings --> >+ >+ <set name="pz:elements" value="F"/> >+ >+</settings> >diff --git a/etc/pazpar2/pazpar2.xml b/etc/pazpar2/pazpar2.xml >index 2269647..a3a3404 100644 >--- a/etc/pazpar2/pazpar2.xml >+++ b/etc/pazpar2/pazpar2.xml >@@ -4,7 +4,7 @@ > > <server> > <listen port="__PAZPAR2_PORT__"/> >- <settings src="__PAZPAR2_CONF_DIR__/koha-biblios.xml"/> >+ <settings src="__PAZPAR2_CONF_DIR__/generic-settings.xml"/> > > <relevance> > <icu_chain id="relevance" locale="el"> >diff --git a/etc/pazpar2/unimarc-work-groups.xsl b/etc/pazpar2/unimarc-work-groups.xsl >new file mode 100644 >index 0000000..50cbe3c >--- /dev/null >+++ b/etc/pazpar2/unimarc-work-groups.xsl >@@ -0,0 +1,98 @@ >+<?xml version="1.0" encoding="UTF-8"?> >+<xsl:stylesheet >+ version="1.0" >+ xmlns:xsl="http://www.w3.org/1999/XSL/Transform" >+ xmlns:pz="http://www.indexdata.com/pazpar2/1.0" >+ xmlns:marc="http://www.loc.gov/MARC21/slim"> >+ >+ <xsl:output indent="yes" method="xml" version="1.0" encoding="UTF-8"/> >+ >+ >+ <xsl:template match="/marc:record"> >+ <pz:record> >+ >+ <xsl:for-each select="marc:controlfield[@tag='001']"> >+ <pz:metadata type="id"> >+ <xsl:value-of select="."/> >+ </pz:metadata> >+ </xsl:for-each> >+ >+ <!-- --> >+ <xsl:for-each select="marc:datafield[@tag='020']"> >+ <xsl:if test="marc:subfield[@code='a'] = 'US'"> >+ <pz:metadata type="lccn"> >+ <xsl:value-of select="marc:subfield[@code='b']"/> >+ </pz:metadata> >+ </xsl:if> >+ </xsl:for-each> >+ >+ <xsl:for-each select="marc:datafield[@tag='010']"> >+ <pz:metadata type="isbn"> >+ <xsl:value-of select="marc:subfield[@code='a']"/> >+ </pz:metadata> >+ </xsl:for-each> >+ >+ <xsl:for-each select="marc:datafield[@tag='011']"> >+ <pz:metadata type="issn"> >+ <xsl:value-of select="marc:subfield[@code='a']"/> >+ </pz:metadata> >+ </xsl:for-each> >+ >+ >+ <xsl:for-each select="marc:datafield[@tag='200']"> >+ <pz:metadata type="work-title"> >+ <xsl:value-of select="marc:subfield[@code='a']"/> >+ </pz:metadata> >+ </xsl:for-each> >+ >+ >+ <!-- Date of Pulbication --> >+ <xsl:for-each select="marc:datafield[@tag='210']"> >+ <pz:metadata type="date"> >+ <xsl:value-of select="marc:subfield[@code='d']"/> >+ </pz:metadata> >+ </xsl:for-each> >+ >+ <!-- Usmarc 650 maps to unimarc 606 and marc21 653 maps to unimarc 610 --> >+ <xsl:for-each select="marc:datafield[@tag='606' or @tag='610']"> >+ <pz:metadata type="subject"> >+ <xsl:value-of select="marc:subfield[@code='a']"/> >+ </pz:metadata> >+ </xsl:for-each> >+ >+ <xsl:for-each select="marc:datafield[@tag >= 300 and @tag <= 345] >+ [@tag != '325']"> >+ <pz:metadata type="description"> >+ <xsl:value-of select="*/text()"/> >+ </pz:metadata> >+ </xsl:for-each> >+ >+ >+ <!-- Author : primary, alternative and secondary responsibility (equivalent marc21 tags : 100, 700 --> >+ <xsl:for-each select="marc:datafield[@tag='700' or @tag='701' or @tag='702']"> >+ <pz:metadata type="work-author"> >+ <xsl:value-of select="marc:subfield[@code='a']"/> >+ <xsl:text>, </xsl:text> >+ <xsl:value-of select="marc:subfield[@code='b']"/> >+ </pz:metadata> >+ </xsl:for-each> >+ >+ <!-- Author : marc21 tag 720 maps to unimarc 730 >+ <xsl:for-each select="marc:datafield[@tag='730']"> >+ <pz:metadata type="author"> >+ <xsl:value-of select="marc:subfield[@code='a']"/> >+ </pz:metadata> >+ </xsl:for-each> >+ --> >+ >+ <!-- --> >+ <xsl:for-each select="marc:datafield[@tag='856']"> >+ <pz:metadata type="url"> >+ <xsl:value-of select="marc:subfield[@code='u']"/> >+ </pz:metadata> >+ </xsl:for-each> >+ >+ </pz:record> >+ </xsl:template> >+ >+</xsl:stylesheet> >diff --git a/installer/data/mysql/sysprefs.sql b/installer/data/mysql/sysprefs.sql >index 2c45345..f695c50 100644 >--- a/installer/data/mysql/sysprefs.sql >+++ b/installer/data/mysql/sysprefs.sql >@@ -258,6 +258,7 @@ INSERT INTO systempreferences ( `variable`, `value`, `options`, `explanation`, ` > ('OpacRenewalAllowed','0',NULL,'If ON, users can renew their issues directly from their OPAC account','YesNo'), > ('OpacRenewalBranch','checkoutbranch','itemhomebranch|patronhomebranch|checkoutbranch|null','Choose how the branch for an OPAC renewal is recorded in statistics','Choice'), > ('OPACResultsSidebar','','70|10','Define HTML to be included on the search results page, underneath the facets sidebar','Textarea'), >+('OPACSearchExternalTargets','0',NULL,'Whether to search external targets in the OPAC','YesNo'), > ('OPACSearchForTitleIn','<li><a href=\"http://worldcat.org/search?q={TITLE}\" target=\"_blank\">Other Libraries (WorldCat)</a></li>\n<li><a href=\"http://www.scholar.google.com/scholar?q={TITLE}\" target=\"_blank\">Other Databases (Google Scholar)</a></li>\n<li><a href=\"http://www.bookfinder.com/search/?author={AUTHOR}&title={TITLE}&st=xl&ac=qr\" target=\"_blank\">Online Stores (Bookfinder.com)</a></li>\n<li><a href=\"http://openlibrary.org/search/?author=({AUTHOR})&title=({TITLE})\" target=\"_blank\">Open Library (openlibrary.org)</a></li>','70|10','Enter the HTML that will appear in the \'Search for this title in\' box on the detail page in the OPAC. Enter {TITLE}, {AUTHOR}, or {ISBN} in place of their respective variables in the URL. Leave blank to disable \'More Searches\' menu.','Textarea'), > ('OpacSeparateHoldings','0',NULL,'Separate current branch holdings from other holdings (OPAC)','YesNo'), > ('OpacSeparateHoldingsBranch','homebranch','homebranch|holdingbranch','Branch used to separate holdings (OPAC)','Choice'), >diff --git a/installer/data/mysql/updatedatabase.pl b/installer/data/mysql/updatedatabase.pl >index c8e883f..af1d2c2 100755 >--- a/installer/data/mysql/updatedatabase.pl >+++ b/installer/data/mysql/updatedatabase.pl >@@ -7953,7 +7953,6 @@ if ( C4::Context->preference("Version") < TransformToNum($DBversion) ) { > SetVersion($DBversion); > } > >-$DBversion = "3.15.00.017"; > if(CheckVersion($DBversion)) { > $dbh->do(q{ > UPDATE systempreferences >@@ -8012,6 +8011,39 @@ if ( C4::Context->preference("Version") < TransformToNum($DBversion) ) { > SetVersion($DBversion); > } > >+$DBversion = "3.13.00.XXX"; >+if(CheckVersion($DBversion)) { >+ $dbh->do( >+"INSERT IGNORE INTO systempreferences (variable,value,explanation,options,type) VALUES('OPACSearchExternalTargets','0','Whether to search external targets in the OPAC','','YesNo')" >+ ); >+ $dbh->do( q{ >+CREATE TABLE `external_targets` ( >+ `target_id` int(11) NOT NULL AUTO_INCREMENT, >+ `host` varchar(128) NOT NULL, >+ `port` int(11) NOT NULL, >+ `db` varchar(64) NOT NULL, >+ `userid` varchar(64) DEFAULT '', >+ `password` varchar(64) DEFAULT '', >+ `name` varchar(64) NOT NULL, >+ `syntax` varchar(64) NOT NULL, >+ `encoding` varchar(16) DEFAULT 'MARC-8', >+ PRIMARY KEY (`target_id`) >+) ENGINE=InnoDB AUTO_INCREMENT=21 DEFAULT CHARSET=utf8 >+ } ); >+ $dbh->do( q{ >+CREATE TABLE `external_target_restrictions` ( >+ `branchcode` varchar(10) NOT NULL, >+ `target_id` int(11) NOT NULL, >+ KEY `branchcode` (`branchcode`), >+ KEY `target_id` (`target_id`), >+ CONSTRAINT `external_target_restrictions_ibfk_1` FOREIGN KEY (`branchcode`) REFERENCES `branches` (`branchcode`) ON DELETE CASCADE, >+ CONSTRAINT `external_target_restrictions_ibfk_2` FOREIGN KEY (`target_id`) REFERENCES `external_targets` (`target_id`) ON DELETE CASCADE >+) ENGINE=InnoDB DEFAULT CHARSET=utf8 >+ } ); >+ print "Upgrade to $DBversion done (Bug 10486 - Allow external Z39.50 targets to be searched from the OPAC)\n"; >+ SetVersion($DBversion); >+} >+ > =head1 FUNCTIONS > > =head2 TableExists($table) >diff --git a/koha-tmpl/intranet-tmpl/prog/en/css/staff-global.css b/koha-tmpl/intranet-tmpl/prog/en/css/staff-global.css >index 2a04c35..f5fe3c8 100644 >--- a/koha-tmpl/intranet-tmpl/prog/en/css/staff-global.css >+++ b/koha-tmpl/intranet-tmpl/prog/en/css/staff-global.css >@@ -270,6 +270,16 @@ tr.even td, tr.even.highlight td { > border-right : 1px solid #BCBCBC; > } > >+tr.highlight td { >+ background-color : #F6F6F6; >+ border-color : #BCBCBC; >+} >+ >+tr.highlight th[scope=row] { >+ background-color : #DDDDDD; >+ border-color : #BCBCBC; >+} >+ > td.od { > color : #cc0000; > font-weight : bold; >@@ -287,6 +297,10 @@ tr.odd.onissue td { > background-color: #FFFFE1; > } > >+tr.updated td { >+ background-color: #FFFFBB; >+} >+ > tfoot td { > background-color : #f3f3f3; > font-weight : bold; >diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/admin-menu.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/admin-menu.inc >index ebd345c..1731e39 100644 >--- a/koha-tmpl/intranet-tmpl/prog/en/includes/admin-menu.inc >+++ b/koha-tmpl/intranet-tmpl/prog/en/includes/admin-menu.inc >@@ -45,6 +45,7 @@ > <li><a href="/cgi-bin/koha/admin/classsources.pl">Classification sources</a></li> > <li><a href="/cgi-bin/koha/admin/matching-rules.pl">Record matching rules</a></li> > <li><a href="/cgi-bin/koha/admin/oai_sets.pl">OAI sets configuration</a></li> >+ <li><a href="/cgi-bin/koha/admin/external_targets.pl">External search targets</a></li> > </ul> > > <h5>Acquisition parameters</h5> >diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/admin-home.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/admin-home.tt >index 217b069..435ac5a 100644 >--- a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/admin-home.tt >+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/admin-home.tt >@@ -81,6 +81,8 @@ > <dt><a href="/cgi-bin/koha/admin/searchengine/solr/indexes.pl">Search engine configuration</a></dt> > <dd>Manage indexes, facets, and their mappings to MARC fields and subfields.</dd> > [% END %] >+ <dt><a href="/cgi-bin/koha/admin/external_targets.pl">External search targets</a></dt> >+ <dd>Define external search targets that can be searched from the OPAC.</dd> > </dl> > > <h3>Acquisition parameters</h3> >diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/external-targets.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/external-targets.tt >new file mode 100644 >index 0000000..8858a62 >--- /dev/null >+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/external-targets.tt >@@ -0,0 +1,180 @@ >+[% INCLUDE 'doc-head-open.inc' %] >+[% IF op == 'show' %] >+<title>Koha › Administration › External targets</title> >+[% ELSIF op == 'add' %] >+<title>Koha › Administration › External targets › Create an external target</title> >+[% ELSIF op == 'edit' %] >+<title>Koha › Administration › External targets › Editing '[% target.name %]'</title> >+[% END %] >+[% INCLUDE 'doc-head-close.inc' %] >+<link rel="stylesheet" type="text/css" href="[% themelang %]/css/datatables.css" /> >+<script type="text/javascript" src="[% themelang %]/lib/jquery/plugins/jquery.dataTables.min.js"></script> >+[% INCLUDE 'datatables-strings.inc' %] >+<script type="text/javascript" src="[% themelang %]/js/datatables.js"></script> >+<script type="text/javascript"> >+//<![CDATA[ >+ $(document).ready(function() { >+ [% IF ( targets.size ) %] >+ var dTable = $("#targets").dataTable( $.extend( true, {}, dataTablesDefaults, { >+ aoColumnDefs: [ >+ { aTargets: [ 1,2,3,4,5 ], bSortable: false, bSearchable: false }, >+ ], >+ asStripeClasses: [ '', 'highlight' ], >+ bPaginate: false, >+ } ) ); >+ >+ [% IF saved_id %] >+ $( '#targets tr[data-targetid=[% saved_id %]]' ).addClass( 'updated' ); >+ [% END %] >+ >+ $( '#targets .delete' ).click( function() { >+ return confirm( _("Are you sure you wish to delete this target?") ); >+ } ); >+ [% END %] >+ }); >+//]]> >+</script> >+ >+</head> >+<body id="admin_z3950servers" class="admin"> >+[% INCLUDE 'header.inc' %] >+[% INCLUDE 'cat-search.inc' %] >+ >+[% IF op == 'show' %] >+ >+<div id="breadcrumbs"><a href="/cgi-bin/koha/mainpage.pl">Home</a> › <a href="/cgi-bin/koha/admin/admin-home.pl">Administration</a> › External targets</div> >+ >+[% ELSIF op == 'add' %] >+ >+<div id="breadcrumbs"><a href="/cgi-bin/koha/mainpage.pl">Home</a> › <a href="/cgi-bin/koha/admin/admin-home.pl">Administration</a> › <a href="/cgi-bin/koha/admin/external_targets.pl">External targets</a> › Create an external target</div> >+ >+[% ELSIF op == 'edit' %] >+ >+<div id="breadcrumbs"><a href="/cgi-bin/koha/mainpage.pl">Home</a> › <a href="/cgi-bin/koha/admin/admin-home.pl">Administration</a> › <a href="/cgi-bin/koha/admin/external_targets.pl">External targets</a> › Editing '[% target.name %]'</div> >+ >+[% END %] >+ >+<div id="doc3" class="yui-t2"> >+<div id="bd"> >+ >+<div id="yui-main"><div class="yui-b"> >+ >+[% IF op == 'show' %] >+ >+[% IF deleted_name %] >+<div class="alert"> >+ <p>Deleted target '[% deleted_name %]'</p> >+</div> >+[% END %] >+ >+<div id="toolbar" class="btn-toolbar"> >+ <a id="newtarget" class="btn btn-small" href="/cgi-bin/koha/admin/external_targets.pl?op=add"><i class="icon-plus"></i> New external target</a> >+</div> >+ >+<p>These Z39.50 targets are searched in the OPAC using the "external targets" feature. Note that Pazpar2 must be installed and configured for this to work.</p> >+ >+[% IF targets.size %] >+<table id="targets"> >+ <thead><tr><th>Name</th><th>Connection</th><th>Login</th><th>Syntax</th><th>Encoding</th><th> </th><th> </th></tr></thead> >+ <tbody> >+ [% FOREACH target = targets %] >+ <tr data-targetid="[% target.target_id %]"> >+ <td>[% target.name %]</td> >+ <td>[% target.host %]:[% target.port %]/[% target.db %]</td> >+ <td>[% IF target.userid %][% target.userid %] / [% IF target.password %]********[% ELSE %]<span class="hint">none</span>[% END %][% ELSE %]<span class="hint">none</span>[% END %]</td> >+ <td>[% target.syntax %]</td> >+ <td> >+ [% IF target.encoding == 'marc8' %] >+ MARC-8 >+ [% ELSIF target.encoding == 'utf8' %] >+ UTF-8 >+ [% END %] >+ </td> >+ <td><a href="/cgi-bin/koha/admin/external_targets.pl?op=edit&target_id=[% target.target_id %]">Edit</a></td> >+ <td><a class="delete" href="/cgi-bin/koha/admin/external_targets.pl?op=delete&target_id=[% target.target_id %]">Delete</a></td> >+ </tr> >+ [% END %] >+ </tbody> >+</table> >+[% ELSE %] >+<p>No external targets have been defined yet.</p> >+[% END %] >+ >+[% ELSIF op == 'add' || op == 'edit' %] >+ >+<form action="/cgi-bin/koha/admin/external_targets.pl" method="POST"> >+ [% IF op == 'add' %] >+ <h1>Create an external target</h1> >+ [% ELSIF op == 'edit' %] >+ <h1>Editing '[% target.name %]'</h1> >+ [% END %] >+ >+ <input type="hidden" name="op" value="save"> >+ <input type="hidden" name="target_id" value="[% target.target_id %]"> >+ >+ <fieldset class="rows"> >+ <ol> >+ <li><label for="name">Name:</label> <input type="text" id="name" name="name" value="[% target.name %]" required></li> >+ <li><label for="host">Host:</label> <input type="text" id="host" name="host" value="[% target.host %]" required></li> >+ <li><label for="port">Port:</label> <input type="num" id="port" name="port" value="[% target.port %]" required></li> >+ <li><label for="db">Database:</label> <input type="text" id="db" name="db" value="[% target.db %]" required></li> >+ <li><label for="userid">User:</label> <input type="text" id="userid" name="userid" value="[% target.userid %]"></li> >+ <li><label for="password">Password:</label> <input type="password" id="password" name="password" value="[% target.password %]" autocomplete="off"></li> >+ <li> >+ <label for="syntax">Syntax:</label> >+ <select id="syntax" name="syntax"> >+ [% FOREACH syntax = syntaxes %] >+ [% IF syntax == target.syntax %] >+ <option selected>[% syntax %] >+ [% ELSE %] >+ <option>[% syntax %]</option> >+ [% END %] >+ [% END %] >+ </select> >+ </li> >+ <li> >+ <label for="encoding">Encoding:</label> >+ <select id="encoding" name="encoding"> >+ [% FOREACH encoding = encodings %] >+ [% IF encoding.key == target.encoding %] >+ <option value="[% encoding.key %]" selected>[% encoding.value %] >+ [% ELSE %] >+ <option value="[% encoding.key %]">[% encoding.value %]</option> >+ [% END %] >+ [% END %] >+ </select> >+ </li> >+ <li> >+ <label>Restricted libraries:</label> >+ <fieldset> >+ <legend>Not available to patrons from:</legend> >+ <p>Only targets with no restrictions will be shown to anonymous (not logged in) users.</p> >+ <ol> >+ [% FOREACH branch = branches %] >+ <li> >+ <label for="branch-[% branch.branchcode %]">[% branch.branchname %]</label> >+ [% IF branch.selected %] >+ <input type="checkbox" id="branch-[% branch.branchcode %]" name="branch" value="[% branch.branchcode %]" checked /> >+ [% ELSE %] >+ <input type="checkbox" id="branch-[% branch.branchcode %]" name="branch" value="[% branch.branchcode %]" /> >+ [% END %] >+ </li> >+ [% END %] >+ </ol> >+ </fieldset> >+ </li> >+ </ol> >+ </fieldset> >+ >+ <fieldset class="action"><input type="submit" value="Save"></fieldset> >+</form> >+ >+[% END %] >+ >+</div></div> >+<div class="yui-b"> >+[% INCLUDE 'admin-menu.inc' %] >+</div> >+ >+</div> >+[% INCLUDE 'intranet-bottom.inc' %] >diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/opac.pref b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/opac.pref >index d541440..a48416c 100644 >--- a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/opac.pref >+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/opac.pref >@@ -436,6 +436,13 @@ OPAC: > yes: Allow > no: Do not allow > - users to add a note when placing a hold. >+ - >+ - pref: OPACSearchExternalTargets >+ default: 0 >+ choices: >+ yes: Search >+ no: "Don't search" >+ - external targets from the OPAC. > > Policy: > - >diff --git a/koha-tmpl/opac-tmpl/bootstrap/css/opac.css b/koha-tmpl/opac-tmpl/bootstrap/css/opac.css >index 99719d5..105beb7 100644 >--- a/koha-tmpl/opac-tmpl/bootstrap/css/opac.css >+++ b/koha-tmpl/opac-tmpl/bootstrap/css/opac.css >@@ -2142,7 +2142,7 @@ td img { > margin: 0; > padding: 0; > } >-#overdrive-results { >+#overdrive-results, #pazpar2-results { > font-weight: bold; > padding-left: 1em; > } >@@ -2278,6 +2278,25 @@ a.reviewlink:visited { > -moz-box-shadow: 0px 1px 1px 0px rgba(0, 0, 0, 0.2); > box-shadow: 0px 1px 1px 0px rgba(0, 0, 0, 0.2); > } >+.sourcecol { >+ vertical-align: top; >+ width: 100px; >+} >+.notesrow label { >+ font-weight: bold; >+} >+.notesrow span { >+ display: block; >+} >+.notesrow textarea { >+ width: 100%; >+} >+#modal-overlay { >+ background-color: white; >+ border-radius: 5px; >+ max-width: 75%; >+ padding: 15px; >+} > @media only screen and (min-width: 0px) and (max-width: 304px) { > /* Screens bewteen 0 and 304 pixels wide */ > #oh:after { >diff --git a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-external-search.tt b/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-external-search.tt >new file mode 100644 >index 0000000..54adea6 >--- /dev/null >+++ b/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-external-search.tt >@@ -0,0 +1,252 @@ >+[% USE Koha %] >+[% INCLUDE 'doc-head-open.inc' %] >+[% IF ( LibraryNameTitle ) %][% LibraryNameTitle %][% ELSE %]Koha online[% END %] catalog › External search for '[% q | html %]' >+[% INCLUDE 'doc-head-close.inc' %] >+[% BLOCK cssinclude %] >+<style> >+.actions a.addtocart { >+ display: inline; >+} >+#targets-facet label { >+ display: inline; >+ font-size: inherit; >+} >+</style> >+[% END %] >+</head> >+<body id="results" class="scrollto"> >+[% INCLUDE 'masthead.inc' %] >+ >+ <div class="main"> >+ <ul class="breadcrumb"> >+ <li><a href="/cgi-bin/koha/opac-main.pl">Home</a> <span class="divider">›</span></li> >+ <li> >+ <a href="#">External search for '[% q | html %]'</a> >+ </li> >+ </ul> >+ >+ <div id="maincontent" class="container-fluid"> >+ <div class="row-fluid"> >+ <div class="span3"> >+ <div id="facetcontainer"> >+ <div id="search-facets"> >+ <h4><a href="#" class="menu-collapse-toggle">Refine your search</a></h4> >+ <ul class="menu-collapse"> >+ <li id="targets-facet"> >+ Targets >+ <ul> >+ [% FOREACH target = external_search_targets %] >+ <li> >+ <input data-url="[% target.host %]:[% target.port %]/[% target.db %]" type="checkbox" id="target-[% loop.index %]" checked /> >+ <label for="target-[% loop.index %]">[% target.name %] >+ </li> >+ [% END %] >+ </ul> >+ </li> >+ </ul> >+ </div> >+ </div> >+ </div> >+ >+ <div class="span9 maincontent"> >+ <p id="numresults"></p> >+ <div id="top-pages"> >+ <div class="pagination pagination-small"><ul></ul></div> >+ </div> >+ <div id="searchresults"> >+ <table class="table table-striped" id="results"> >+ <tbody> >+ </tbody> >+ </table> >+ </div> >+ <div id="bottom-pages"> >+ <div class="pagination pagination-small"><ul></ul></div> >+ </div> >+ </div> >+ </div> >+ </div> >+ </div> >+</div> >+ >+</div> >+ >+<div id="modal-overlay" style="display: none"></div> >+[% INCLUDE 'opac-bottom.inc' %] >+[% BLOCK jsinclude %] >+<script type="text/javascript" src="[% interface %]/[% theme %]/lib/jquery/plugins/jquery.lightbox_me.js"></script> >+<script type="text/javascript" src="/opac-tmpl/lib/pz2.js"></script> >+<script type="text/javascript" src="[% interface %]/[% theme %]/js/externalsearch.js"></script> >+<script type="text/javascript"> >+var querystring = "[% q |replace( "'", "\'" ) |replace( '\n', '\\n' ) |replace( '\r', '\\r' ) |html %]"; >+var results_per_page = [% OPACnumSearchResults %]; >+KOHA.ExternalSearch.targets = { >+ [% FOREACH target IN external_search_targets %] >+ '[% target.host %]:[% target.port %]/[% target.db %]': { >+ name: '[% target.name %]', >+ syntax: '[% target.syntax %]', >+ }, >+ [% END %] >+}; >+ >+var xsltResultStylesheets = { >+ [% FOREACH stylesheet IN xslt_result_stylesheets %] >+ '[% stylesheet.syntax %]': KOHA.XSLTGet( '[% stylesheet.url %]' ), >+ [% END %] >+}; >+ >+var xsltDetailStylesheets = { >+ [% FOREACH stylesheet IN xslt_detail_stylesheets %] >+ '[% stylesheet.syntax %]': KOHA.XSLTGet( '[% stylesheet.url %]' ), >+ [% END %] >+}; >+ >+var recordCache = {}; >+var resultRenderCache = {}; >+ >+function showResult( syntax, recid ) { >+ if ( recordCache[ recid ] ) { >+ done( recordCache[ recid ] ); >+ } else { >+ KOHA.ExternalSearch.GetDetailedRecord( recid, function( record ) { >+ done( recordCache[ recid ] = record.xmlDoc ); >+ } ); >+ } >+ >+ function done( record ) { >+ xsltResultStylesheets[ syntax ].done( function( xslDoc ) { >+ var fragment = resultRenderCache[ recid ] = KOHA.TransformToFragment( record, xslDoc ); >+ var $tr = $( '#results tr' ).filter( function() { return $( this ).data( 'recid' ) == recid } ); >+ $tr.find( '.info' ).html( fragment ); >+ $tr.find( 'a' ).attr( 'href', '#' ).click( function() { >+ showDetail( syntax, recid ); >+ >+ return false; >+ } ); >+ } ); >+ } >+} >+ >+function showDetail( syntax, recid ) { >+ var record = recordCache[ recid ]; >+ >+ xsltDetailStylesheets[ syntax ].done( function( xslDoc ) { >+ var fragment = KOHA.TransformToFragment( record, xslDoc ); >+ >+ $( '#modal-overlay' ).html( fragment ).lightbox_me( { >+ centered: true, >+ } ); >+ } ); >+} >+ >+function search( offset, reset_search ) { >+ $( '#pazpar2-status' ).html( _("Searching external targets...") + '<img class="throbber" src="/opac-tmpl/lib/jquery/plugins/themes/classic/throbber.gif" />' ); >+ >+ if ( reset_search ) { >+ KOHA.ExternalSearch.Search( querystring, results_per_page, callback ); >+ } else { >+ KOHA.ExternalSearch.Fetch( offset, callback ); >+ } >+ >+ function callback( data ) { >+ if ( data.error ) { >+ $( '#pazpar2-status' ).html( '<strong class="unavailable">' + _("Error searching external targets.") + '</strong>' ); >+ return; >+ } >+ >+ if ( !data.total ) { >+ $( '#pazpar2-status' ).html( '<strong>' + _("No results found in the external targets.") + '</strong>' ); >+ return; >+ } >+ >+ $( '#results tbody' ).empty(); >+ >+ $( '#pazpar2-status' ).html( '<strong>' + _("Found __RESULTS__ results in __TARGETS__ external targets.").replace('__RESULTS__', data.total).replace('__TARGETS__', $( '#targets-facet input:checked' ).length ) ); >+ >+ for ( var i = 0; data.hits[i]; i++ ) { >+ var hit = data.hits[i]; >+ var results = []; >+ var recordSyntax = KOHA.ExternalSearch.targets[ hit.location[0]['@id'] ].syntax; >+ >+ results.push( '<tr>' ); >+ >+ results.push( '<td class="sourcecol">', hit.location[0]['@name'], '</td>' ); >+ >+ results.push( '<td class="info">' ); >+ >+ if ( resultRenderCache[ hit.recid[0] ] ) { >+ results.push( resultRenderCache[ hit.recid[0] ] ); >+ } else { >+ results.push( hit['md-work-title'] ? hit['md-work-title'][0] : _("Loading...") ); >+ showResult( recordSyntax, hit.recid[0] ); >+ } >+ >+ results.push( '</td>' ); >+ >+ results.push( '</tr>' ); >+ var $tr = $( results.join( '' ) ); >+ $tr.data( 'recid', hit.recid[0] ); >+ $( '#results tbody' ).append( $tr ); >+ >+ ( function( hit, recordSyntax ) { >+ $tr.find( 'a' ).attr( 'href', '#' ).click( function() { >+ showDetail( recordSyntax, hit.recid[0] ); >+ >+ return false; >+ } ); >+ } )( hit, recordSyntax ); >+ } >+ >+ var pages = []; >+ var cur_page = data.start / results_per_page; >+ var max_page = Math.floor( data.total / results_per_page ); >+ >+ if ( cur_page != 0 ) { >+ pages.push( '<li><a href="#" data-offset="' + (offset - results_per_page) + '"><< ' + _("Previous") + '</a></li>' ); >+ } >+ >+ for ( var page = Math.max( 0, cur_page - 9 ); page <= Math.min( max_page, cur_page + 9 ); page++ ) { >+ if ( page == cur_page ) { >+ pages.push( ' <li class="active"><a href="#">' + ( page + 1 ) + '</a></span>' ); >+ } else { >+ pages.push( ' <li><a href="#" data-offset="' + ( page * results_per_page ) + '">' + ( page + 1 ) + '</a></li>' ); >+ } >+ } >+ >+ if ( cur_page < max_page ) { >+ pages.push( ' <li><a href="#" data-offset="' + (offset + results_per_page) + '">' + _("Next") + ' >></a></li>' ); >+ } >+ >+ if ( pages.length > 1 ) $( '#top-pages, #bottom-pages' ).find( '.pagination ul' ).html( pages.join( '' ) ); >+ } >+} >+ >+$( document ).ready( function() { >+ $( '#numresults' ) >+ .append( ' ' ) >+ .append( '<span id="pazpar2-status"></span>' ); >+ >+ $( document ).on( 'click', 'a[data-offset]', function() { >+ search( $(this).data('offset') ); >+ return false; >+ }); >+ >+ var reSearchTimeout; >+ >+ $( '#targets-facet input' ).each( function() { >+ $( this ).click( function() { >+ KOHA.ExternalSearch.targets[ $( this ).data( 'url' ) ].disabled = !this.checked; >+ >+ if ( reSearchTimeout ) clearTimeout( reSearchTimeout ); >+ >+ reSearchTimeout = setTimeout( function() { >+ if ( $( '#targets-facet input:checked' ).length ) search( 0, true ); >+ }, 1000 ); >+ } ); >+ >+ KOHA.ExternalSearch.targets[ $( this ).data( 'url' ) ].disabled = !this.checked; >+ } ); >+ >+ search( 0, true ); >+} ); >+</script> >+[% END %] >diff --git a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-results.tt b/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-results.tt >index 8f0a089..fbdbb72 100644 >--- a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-results.tt >+++ b/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-results.tt >@@ -552,7 +552,12 @@ > [% IF ( OverDriveEnabled ) %]<script type="text/javascript" src="[% interface %]/[% theme %]/js/overdrive.js"></script>[% END %] > <script type="text/javascript" src="[% interface %]/[% theme %]/lib/jquery/plugins/jquery.checkboxes.min.js"></script> > [% IF ( OpacHighlightedWords ) %]<script type="text/javascript" src="[% interface %]/[% theme %]/lib/jquery/plugins/jquery.highlight-3.js"></script> >-[% END %]<script type="text/javascript"> >+[% END %] >+[% IF ( Koha.Preference('OPACSearchExternalTargets') ) %] >+<script type="text/javascript" src="/opac-tmpl/lib/pz2.js"></script> >+<script type="text/javascript" src="[% interface %]/[% theme %]/js/externalsearch.js"></script> >+[% END %] >+<script type="text/javascript"> > //<![CDATA[ > [% IF ( Koha.Preference( 'opacuserlogin' ) == 1 ) && ( Koha.Preference( 'RequestOnOpac' ) == 1 ) %] > function holdMultiple() { >@@ -836,21 +841,57 @@ $(document).ready(function(){ > $("#highlight_toggle_on" ).hide().click(function() {highlightOn() ;}); > $("#highlight_toggle_off").show().click(function() {highlightOff();}); > [% END %] >+ >+ function StartExternalSearch(name, text) { >+ $( '#numresults' ) >+ .eq( 0 ) >+ .append( ' <span id="' + name + '-results">' + text + '... <img class="throbber" src="/opac-tmpl/lib/jquery/plugins/themes/classic/throbber.gif" /></span>' ); >+ } >+ function FailExternalSearch( name, text ) { >+ $( '#' + name + '-results' ).html( text ); >+ } >+ function FinishExternalSearch( name, text, numItems, url ) { >+ if ( numItems ) { >+ $( '#' + name + '-results' ).html( text.replace( '__LINK__', '<a href="' + url + '">' + _("__RESULTS__ results").replace( '__RESULTS__', numItems ) + '</a>' ) ); >+ } else { >+ $( '#' + name + '-results' ).remove(); >+ } >+ } >+ > [% IF ( OverDriveEnabled ) %] >- var $overdrive_results = $( '<span id="overdrive-results">' + _( 'Searching OverDrive...' ) + ' <img class="throbber" src="[% interface %]/lib/jquery/plugins/themes/classic/throbber.gif" /></span>' ); >- $( '#numresults' ) .append( ' ' ) >- .append( $overdrive_results ); >+ StartExternalSearch( 'overdrive', _("Searching OverDrive") ); > KOHA.OverDrive.Search( "[% OverDriveLibraryID %]", querystring, 1, 0, function( data ) { > if ( data.error ) { >- $overdrive_results.html( _( 'Error searching OverDrive collection' ) ); >+ FailExternalSearch( 'overdrive', _("Error searching OverDrive collection") ); > return; > } > >- if ( data.totalItems ) { >- $overdrive_results.html( _( 'Found' ) + ' <a href="/cgi-bin/koha/opac-overdrive-search.pl?q=' + escape( querystring ) + '">' + data.totalItems + ' ' + _( 'results' ) + '</a> ' + _( 'in OverDrive collection' ) ); >- } else { >- $overdrive_results.remove(); >+ FinishExternalSearch( 'overdrive', _("Found __LINK__ in OverDrive collection"), data.totalItems, '/cgi-bin/koha/opac-overdrive-search.pl?q=' + escape( querystring ) ); >+ } ); >+ [% END %] >+ >+ [% IF ( OPACSearchExternalTargets ) %] >+ KOHA.ExternalSearch.targets = { >+ [% FOREACH target IN external_search_targets %] >+ '[% target.host %]:[% target.port %]/[% target.db %]': { >+ id: '[% target.target_id %]', >+ name: '[% target.name %]', >+ syntax: '[% target.syntax %]', >+ }, >+ [% END %] >+ }; >+ var num_targets = 0; $.each( KOHA.ExternalSearch.targets, function() { num_targets++ } ); >+ var first_succeeded; >+ >+ StartExternalSearch( 'pazpar2', _("Searching external targets") ); >+ KOHA.ExternalSearch.Search( querystring, 1, function( data ) { >+ if ( data.error ) { >+ if ( !first_succeeded ) FailExternalSearch( 'pazpar2', _("Error searching external targets") ); >+ return; > } >+ >+ first_succeeded = true; >+ FinishExternalSearch( 'pazpar2', _("Found __LINK__ in __TARGETS__ external targets").replace( '__TARGETS__', num_targets ), data.total, '/cgi-bin/koha/opac-external-search.pl?q=' + escape( querystring ) ); > } ); > [% END %] > [% END %] >diff --git a/koha-tmpl/opac-tmpl/bootstrap/en/xslt/MARC21slim2OPACResults.xsl b/koha-tmpl/opac-tmpl/bootstrap/en/xslt/MARC21slim2OPACResults.xsl >index a4981c6..eb8601e 100644 >--- a/koha-tmpl/opac-tmpl/bootstrap/en/xslt/MARC21slim2OPACResults.xsl >+++ b/koha-tmpl/opac-tmpl/bootstrap/en/xslt/MARC21slim2OPACResults.xsl >@@ -5,11 +5,13 @@ > xmlns:marc="http://www.loc.gov/MARC21/slim" > xmlns:items="http://www.koha-community.org/items" > xmlns:xsl="http://www.w3.org/1999/XSL/Transform" >+ xmlns="http://www.w3.org/1999/xhtml" > exclude-result-prefixes="marc items"> > <xsl:import href="MARC21slimUtils.xsl"/> > <xsl:output method = "html" indent="yes" omit-xml-declaration = "yes" encoding="UTF-8"/> > <xsl:key name="item-by-status" match="items:item" use="items:status"/> > <xsl:key name="item-by-status-and-branch" match="items:item" use="concat(items:status, ' ', items:homebranch)"/> >+ <xsl:param name="showAvailability" select="true()"/> > > <xsl:template match="/"> > <xsl:apply-templates/> >@@ -1027,6 +1029,7 @@ > </xsl:for-each> > </span> > </xsl:if> >+ <xsl:if test="$showAvailability"> > <span class="results_summary availability"> > <span class="label">Availability: </span> > <xsl:choose> >@@ -1035,7 +1038,8 @@ > <xsl:when test="string-length($AlternateHoldingsField)=3 and marc:datafield[@tag=$AlternateHoldingsField]"> > <xsl:variable name="AlternateHoldingsCount" select="count(marc:datafield[@tag=$AlternateHoldingsField])"/> > <xsl:for-each select="marc:datafield[@tag=$AlternateHoldingsField][1]"> >- <xsl:call-template select="marc:datafield[@tag=$AlternateHoldingsField]" name="subfieldSelect"> >+ <!-- Removed select="marc:datafield[@tag=$AlternateHoldingsField]" due to incompatibility with browser XSLT --> >+ <xsl:call-template name="subfieldSelect"> > <xsl:with-param name="codes"><xsl:value-of select="$AlternateHoldingsSubfields"/></xsl:with-param> > <xsl:with-param name="delimeter"><xsl:value-of select="$AlternateHoldingsSeparator"/></xsl:with-param> > </xsl:call-template> >@@ -1145,6 +1149,7 @@ > <xsl:text>). </xsl:text> </span> > </xsl:if> > </span> >+ </xsl:if> > <xsl:choose> > <xsl:when test="($OPACItemLocation='location' or $OPACItemLocation='ccode') and (count(key('item-by-status', 'available'))!=0 or count(key('item-by-status', 'reference'))!=0)"> > <span class="results_summary" id="location"> >diff --git a/koha-tmpl/opac-tmpl/bootstrap/en/xslt/MARC21slimUtils.xsl b/koha-tmpl/opac-tmpl/bootstrap/en/xslt/MARC21slimUtils.xsl >index 9599566..07fd266 100644 >--- a/koha-tmpl/opac-tmpl/bootstrap/en/xslt/MARC21slimUtils.xsl >+++ b/koha-tmpl/opac-tmpl/bootstrap/en/xslt/MARC21slimUtils.xsl >@@ -1,6 +1,6 @@ > <?xml version='1.0'?> > <!DOCTYPE stylesheet [<!ENTITY nbsp " " >]> >-<xsl:stylesheet version="1.0" xmlns:marc="http://www.loc.gov/MARC21/slim" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> >+<xsl:stylesheet version="1.0" xmlns:marc="http://www.loc.gov/MARC21/slim" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns="http://www.w3.org/1999/xhtml"> > <xsl:template name="datafield"> > <xsl:param name="tag"/> > <xsl:param name="ind1"><xsl:text> </xsl:text></xsl:param> >diff --git a/koha-tmpl/opac-tmpl/bootstrap/en/xslt/UNIMARCslim2OPACResults.xsl b/koha-tmpl/opac-tmpl/bootstrap/en/xslt/UNIMARCslim2OPACResults.xsl >index dd70bca..1b9db4c 100644 >--- a/koha-tmpl/opac-tmpl/bootstrap/en/xslt/UNIMARCslim2OPACResults.xsl >+++ b/koha-tmpl/opac-tmpl/bootstrap/en/xslt/UNIMARCslim2OPACResults.xsl >@@ -6,12 +6,14 @@ > xmlns:marc="http://www.loc.gov/MARC21/slim" > xmlns:items="http://www.koha-community.org/items" > xmlns:xsl="http://www.w3.org/1999/XSL/Transform" >+ xmlns="http://www.w3.org/1999/xhtml" > exclude-result-prefixes="marc items"> > > <xsl:import href="UNIMARCslimUtils.xsl"/> > <xsl:output method = "html" indent="yes" omit-xml-declaration = "yes" encoding="UTF-8"/> > <xsl:key name="item-by-status" match="items:item" use="items:status"/> > <xsl:key name="item-by-status-and-branch" match="items:item" use="concat(items:status, ' ', items:homebranch)"/> >+<xsl:param name="showAvailability" select="true()"/> > > <xsl:template match="/"> > <xsl:apply-templates/> >@@ -99,6 +101,7 @@ > > <xsl:call-template name="tag_215" /> > >+ <xsl:if test="$showAvailability"> > <span class="results_summary availability"> > <span class="label">Availability: </span> > <xsl:choose> >@@ -247,6 +250,7 @@ > </span> > </xsl:if> > </span> >+ </xsl:if> > > </xsl:template> > >diff --git a/koha-tmpl/opac-tmpl/bootstrap/js/externalsearch.js b/koha-tmpl/opac-tmpl/bootstrap/js/externalsearch.js >new file mode 100644 >index 0000000..b2fe4a7 >--- /dev/null >+++ b/koha-tmpl/opac-tmpl/bootstrap/js/externalsearch.js >@@ -0,0 +1,106 @@ >+if ( typeof KOHA == "undefined" || !KOHA ) { >+ var KOHA = {}; >+} >+ >+KOHA.XSLTGet = ( function() { >+ // Horrible browser hack, but required due to the following hard-to-detect and long-unfixed bug: >+ // https://bugs.webkit.org/show_bug.cgi?id=60276 >+ var isChrome = /Chrome/.test(navigator.userAgent) && /Google Inc/.test(navigator.vendor); >+ var isSafari = /Safari/.test(navigator.userAgent) && /Apple Computer/.test(navigator.vendor); >+ >+ if ( !isChrome && !isSafari ) return $.get; >+ >+ return function( url ) { >+ var result = new jQuery.Deferred(); >+ var basepath = url.match( /(.*\/)*/ )[0]; >+ >+ $.get( url ).done( function( xslDoc ) { >+ var xslImports = xslDoc.getElementsByTagNameNS( 'http://www.w3.org/1999/XSL/Transform', 'import' ); >+ var importsRemaining = xslImports.length; >+ >+ if ( importsRemaining == 0 ) { >+ result.resolve( xslDoc ); >+ return; >+ } >+ >+ $.each( xslImports, function( i, importElem ) { >+ var path = $( importElem ).attr( 'href' ); >+ if ( !/^(\/|https?:)/.test( path ) ) path = basepath + path; >+ >+ KOHA.XSLTGet( path ).done( function( subDoc ) { >+ importsRemaining--; >+ $( importElem ).replaceWith( subDoc.documentElement.childNodes ); >+ >+ if ( importsRemaining == 0 ) result.resolve( xslDoc ); >+ } ).fail( function() { >+ importsRemaining = -1; >+ >+ result.reject(); >+ } ); >+ } ); >+ } ).fail( function() { >+ result.reject(); >+ } ); >+ >+ return result; >+ }; >+} )(); >+ >+KOHA.TransformToFragment = function( xmlDoc, xslDoc ) { >+ if ( window.XSLTProcessor ) { >+ var proc = new XSLTProcessor(); >+ proc.importStylesheet( xslDoc ); >+ proc.setParameter( null, 'showAvailability', false ); >+ return (new XMLSerializer).serializeToString( proc.transformToFragment( xmlDoc, document ) ); >+ } else if ( window.ActiveXObject ) { >+ var xslt = new ActiveXObject( "Msxml2.XSLTemplate" ); >+ xslt.stylesheet = xslDoc; >+ var xslProc = xslt.createProcessor(); >+ xslProc.input = xmlDoc; >+ xslProc.addParameter( 'showAvailability', false ); >+ xslProc.transform(); >+ return xslProc.output; >+ } >+}; >+ >+KOHA.ExternalSearch = ( function() { >+ return { >+ targets: {}, >+ Search: function( q, limit, callback ) { >+ var targetIDs = []; >+ var includedTargets = []; >+ >+ $.each( KOHA.ExternalSearch.targets, function ( url, info ) { >+ if ( !info.disabled ) { >+ includedTargets.push( url ); >+ targetIDs.push( info.id ); >+ } >+ } ); >+ >+ if ( KOHA.ExternalSearch._pz !== undefined ) { >+ afterinit( KOHA.ExternalSearch._pz ); >+ } else { >+ $.get( '/cgi-bin/koha/svc/pazpar2_init', { targets: targetIDs.join(',') }, function( data ) { >+ KOHA.ExternalSearch._pz = new pz2({ >+ sessionId: data.sessionID, >+ onshow: callback, >+ errorhandler: function ( error ) { callback( { error: error } ) }, >+ } ); >+ afterinit( KOHA.ExternalSearch._pz ); >+ } ); >+ } >+ >+ function afterinit( pz ) { >+ pz.search( q, limit, 'relevance:0', 'pz:id=' + includedTargets.join( '|' ) ); >+ } >+ }, >+ Fetch: function( offset, callback ) { >+ var pz = KOHA.ExternalSearch._pz; >+ pz.showCallback = callback; >+ pz.show( offset ); >+ }, >+ GetDetailedRecord: function( recid, callback ) { >+ KOHA.ExternalSearch._pz.record( recid, 0, undefined, { callback: callback } ); >+ }, >+ }; >+} )(); >diff --git a/koha-tmpl/opac-tmpl/bootstrap/lib/jquery/plugins/jquery.lightbox_me.js b/koha-tmpl/opac-tmpl/bootstrap/lib/jquery/plugins/jquery.lightbox_me.js >new file mode 100644 >index 0000000..effe8a6 >--- /dev/null >+++ b/koha-tmpl/opac-tmpl/bootstrap/lib/jquery/plugins/jquery.lightbox_me.js >@@ -0,0 +1,254 @@ >+/* >+* $ lightbox_me >+* By: Buck Wilson >+* Version : 2.3 >+* >+* Licensed under the Apache License, Version 2.0 (the "License"); >+* you may not use this file except in compliance with the License. >+* You may obtain a copy of the License at >+* >+* http://www.apache.org/licenses/LICENSE-2.0 >+* >+* Unless required by applicable law or agreed to in writing, software >+* distributed under the License is distributed on an "AS IS" BASIS, >+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. >+* See the License for the specific language governing permissions and >+* limitations under the License. >+*/ >+ >+ >+(function($) { >+ >+ $.fn.lightbox_me = function(options) { >+ >+ return this.each(function() { >+ >+ var >+ opts = $.extend({}, $.fn.lightbox_me.defaults, options), >+ $overlay = $(), >+ $self = $(this), >+ $iframe = $('<iframe id="foo" style="z-index: ' + (opts.zIndex + 1) + ';border: none; margin: 0; padding: 0; position: absolute; width: 100%; height: 100%; top: 0; left: 0; filter: mask();"/>'), >+ ie6 = ($.browser.msie && $.browser.version < 7); >+ >+ if (opts.showOverlay) { >+ //check if there's an existing overlay, if so, make subequent ones clear >+ var $currentOverlays = $(".js_lb_overlay:visible"); >+ if ($currentOverlays.length > 0){ >+ $overlay = $('<div class="lb_overlay_clear js_lb_overlay"/>'); >+ } else { >+ $overlay = $('<div class="' + opts.classPrefix + '_overlay js_lb_overlay"/>'); >+ } >+ } >+ >+ /*---------------------------------------------------- >+ DOM Building >+ ---------------------------------------------------- */ >+ if (ie6) { >+ var src = /^https/i.test(window.location.href || '') ? 'javascript:false' : 'about:blank'; >+ $iframe.attr('src', src); >+ $('body').append($iframe); >+ } // iframe shim for ie6, to hide select elements >+ $('body').append($self.hide()).append($overlay); >+ >+ >+ /*---------------------------------------------------- >+ Overlay CSS stuffs >+ ---------------------------------------------------- */ >+ >+ // set css of the overlay >+ if (opts.showOverlay) { >+ setOverlayHeight(); // pulled this into a function because it is called on window resize. >+ $overlay.css({ position: 'absolute', width: '100%', top: 0, left: 0, right: 0, bottom: 0, zIndex: (opts.zIndex + 2), display: 'none' }); >+ if (!$overlay.hasClass('lb_overlay_clear')){ >+ $overlay.css(opts.overlayCSS); >+ } >+ } >+ >+ /*---------------------------------------------------- >+ Animate it in. >+ ---------------------------------------------------- */ >+ // >+ if (opts.showOverlay) { >+ $overlay.fadeIn(opts.overlaySpeed, function() { >+ setSelfPosition(); >+ $self[opts.appearEffect](opts.lightboxSpeed, function() { setOverlayHeight(); setSelfPosition(); opts.onLoad()}); >+ }); >+ } else { >+ setSelfPosition(); >+ $self[opts.appearEffect](opts.lightboxSpeed, function() { opts.onLoad()}); >+ } >+ >+ /*---------------------------------------------------- >+ Hide parent if parent specified (parentLightbox should be jquery reference to any parent lightbox) >+ ---------------------------------------------------- */ >+ if (opts.parentLightbox) { >+ opts.parentLightbox.fadeOut(200); >+ } >+ >+ >+ /*---------------------------------------------------- >+ Bind Events >+ ---------------------------------------------------- */ >+ >+ $(window).resize(setOverlayHeight) >+ .resize(setSelfPosition) >+ .scroll(setSelfPosition); >+ >+ $(window).bind('keyup.lightbox_me', observeKeyPress); >+ >+ if (opts.closeClick) { >+ $overlay.click(function(e) { closeLightbox(); e.preventDefault; }); >+ } >+ $self.delegate(opts.closeSelector, "click", function(e) { >+ closeLightbox(); e.preventDefault(); >+ }); >+ $self.bind('close', closeLightbox); >+ $self.bind('reposition', setSelfPosition); >+ >+ >+ >+ /*-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- >+ -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- */ >+ >+ >+ /*---------------------------------------------------- >+ Private Functions >+ ---------------------------------------------------- */ >+ >+ /* Remove or hide all elements */ >+ function closeLightbox() { >+ var s = $self[0].style; >+ if (opts.destroyOnClose) { >+ $self.add($overlay).remove(); >+ } else { >+ $self.add($overlay).hide(); >+ } >+ >+ //show the hidden parent lightbox >+ if (opts.parentLightbox) { >+ opts.parentLightbox.fadeIn(200); >+ } >+ >+ $iframe.remove(); >+ >+ // clean up events. >+ $self.undelegate(opts.closeSelector, "click"); >+ >+ $(window).unbind('reposition', setOverlayHeight); >+ $(window).unbind('reposition', setSelfPosition); >+ $(window).unbind('scroll', setSelfPosition); >+ $(window).unbind('keyup.lightbox_me'); >+ if (ie6) >+ s.removeExpression('top'); >+ opts.onClose(); >+ } >+ >+ >+ /* Function to bind to the window to observe the escape/enter key press */ >+ function observeKeyPress(e) { >+ if((e.keyCode == 27 || (e.DOM_VK_ESCAPE == 27 && e.which==0)) && opts.closeEsc) closeLightbox(); >+ } >+ >+ >+ /* Set the height of the overlay >+ : if the document height is taller than the window, then set the overlay height to the document height. >+ : otherwise, just set overlay height: 100% >+ */ >+ function setOverlayHeight() { >+ if ($(window).height() < $(document).height()) { >+ $overlay.css({height: $(document).height() + 'px'}); >+ $iframe.css({height: $(document).height() + 'px'}); >+ } else { >+ $overlay.css({height: '100%'}); >+ if (ie6) { >+ $('html,body').css('height','100%'); >+ $iframe.css('height', '100%'); >+ } // ie6 hack for height: 100%; TODO: handle this in IE7 >+ } >+ } >+ >+ >+ /* Set the position of the modal'd window ($self) >+ : if $self is taller than the window, then make it absolutely positioned >+ : otherwise fixed >+ */ >+ function setSelfPosition() { >+ var s = $self[0].style; >+ >+ // reset CSS so width is re-calculated for margin-left CSS >+ $self.css({left: '50%', marginLeft: ($self.outerWidth() / 2) * -1, zIndex: (opts.zIndex + 3) }); >+ >+ >+ /* we have to get a little fancy when dealing with height, because lightbox_me >+ is just so fancy. >+ */ >+ >+ // if the height of $self is bigger than the window and self isn't already position absolute >+ if (($self.height() + 80 >= $(window).height()) && ($self.css('position') != 'absolute' || ie6)) { >+ >+ // we are going to make it positioned where the user can see it, but they can still scroll >+ // so the top offset is based on the user's scroll position. >+ var topOffset = $(document).scrollTop() + 40; >+ $self.css({position: 'absolute', top: topOffset + 'px', marginTop: 0}) >+ if (ie6) { >+ s.removeExpression('top'); >+ } >+ } else if ($self.height()+ 80 < $(window).height()) { >+ //if the height is less than the window height, then we're gonna make this thing position: fixed. >+ // in ie6 we're gonna fake it. >+ if (ie6) { >+ s.position = 'absolute'; >+ if (opts.centered) { >+ s.setExpression('top', '(document.documentElement.clientHeight || document.body.clientHeight) / 2 - (this.offsetHeight / 2) + (blah = document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop) + "px"') >+ s.marginTop = 0; >+ } else { >+ var top = (opts.modalCSS && opts.modalCSS.top) ? parseInt(opts.modalCSS.top) : 0; >+ s.setExpression('top', '((blah = document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop) + '+top+') + "px"') >+ } >+ } else { >+ if (opts.centered) { >+ $self.css({ position: 'fixed', top: '50%', marginTop: ($self.outerHeight() / 2) * -1}) >+ } else { >+ $self.css({ position: 'fixed'}).css(opts.modalCSS); >+ } >+ >+ } >+ } >+ } >+ >+ }); >+ >+ >+ >+ }; >+ >+ $.fn.lightbox_me.defaults = { >+ >+ // animation >+ appearEffect: "fadeIn", >+ appearEase: "", >+ overlaySpeed: 250, >+ lightboxSpeed: 300, >+ >+ // close >+ closeSelector: ".close", >+ closeClick: true, >+ closeEsc: true, >+ >+ // behavior >+ destroyOnClose: false, >+ showOverlay: true, >+ parentLightbox: false, >+ >+ // callbacks >+ onLoad: function() {}, >+ onClose: function() {}, >+ >+ // style >+ classPrefix: 'lb', >+ zIndex: 999, >+ centered: false, >+ modalCSS: {top: '40px'}, >+ overlayCSS: {background: 'black', opacity: .3} >+ } >+})(jQuery); >\ No newline at end of file >diff --git a/koha-tmpl/opac-tmpl/ccsr/en/css/opac.css b/koha-tmpl/opac-tmpl/ccsr/en/css/opac.css >index 590cb19..7e59110 100644 >--- a/koha-tmpl/opac-tmpl/ccsr/en/css/opac.css >+++ b/koha-tmpl/opac-tmpl/ccsr/en/css/opac.css >@@ -1596,6 +1596,10 @@ strong em, em strong { > vertical-align: top; > width: 10px; > } >+.sourcecol { >+ vertical-align: top; >+ width: 100px; >+} > #container { > color : #000; > } >@@ -2865,3 +2869,15 @@ a.reviewlink,a.reviewlink:visited { > display: block; > overflow: auto; > } >+ >+#modal-overlay { >+ background-color: white; >+ border-radius: 5px; >+ max-width: 75%; >+ padding: 15px; >+} >+ >+#overdrive-results, #pazpar2-results { >+ font-weight: bold; >+ padding-left: 1em; >+} >diff --git a/koha-tmpl/opac-tmpl/ccsr/en/js/externalsearch.js b/koha-tmpl/opac-tmpl/ccsr/en/js/externalsearch.js >new file mode 100644 >index 0000000..b2fe4a7 >--- /dev/null >+++ b/koha-tmpl/opac-tmpl/ccsr/en/js/externalsearch.js >@@ -0,0 +1,106 @@ >+if ( typeof KOHA == "undefined" || !KOHA ) { >+ var KOHA = {}; >+} >+ >+KOHA.XSLTGet = ( function() { >+ // Horrible browser hack, but required due to the following hard-to-detect and long-unfixed bug: >+ // https://bugs.webkit.org/show_bug.cgi?id=60276 >+ var isChrome = /Chrome/.test(navigator.userAgent) && /Google Inc/.test(navigator.vendor); >+ var isSafari = /Safari/.test(navigator.userAgent) && /Apple Computer/.test(navigator.vendor); >+ >+ if ( !isChrome && !isSafari ) return $.get; >+ >+ return function( url ) { >+ var result = new jQuery.Deferred(); >+ var basepath = url.match( /(.*\/)*/ )[0]; >+ >+ $.get( url ).done( function( xslDoc ) { >+ var xslImports = xslDoc.getElementsByTagNameNS( 'http://www.w3.org/1999/XSL/Transform', 'import' ); >+ var importsRemaining = xslImports.length; >+ >+ if ( importsRemaining == 0 ) { >+ result.resolve( xslDoc ); >+ return; >+ } >+ >+ $.each( xslImports, function( i, importElem ) { >+ var path = $( importElem ).attr( 'href' ); >+ if ( !/^(\/|https?:)/.test( path ) ) path = basepath + path; >+ >+ KOHA.XSLTGet( path ).done( function( subDoc ) { >+ importsRemaining--; >+ $( importElem ).replaceWith( subDoc.documentElement.childNodes ); >+ >+ if ( importsRemaining == 0 ) result.resolve( xslDoc ); >+ } ).fail( function() { >+ importsRemaining = -1; >+ >+ result.reject(); >+ } ); >+ } ); >+ } ).fail( function() { >+ result.reject(); >+ } ); >+ >+ return result; >+ }; >+} )(); >+ >+KOHA.TransformToFragment = function( xmlDoc, xslDoc ) { >+ if ( window.XSLTProcessor ) { >+ var proc = new XSLTProcessor(); >+ proc.importStylesheet( xslDoc ); >+ proc.setParameter( null, 'showAvailability', false ); >+ return (new XMLSerializer).serializeToString( proc.transformToFragment( xmlDoc, document ) ); >+ } else if ( window.ActiveXObject ) { >+ var xslt = new ActiveXObject( "Msxml2.XSLTemplate" ); >+ xslt.stylesheet = xslDoc; >+ var xslProc = xslt.createProcessor(); >+ xslProc.input = xmlDoc; >+ xslProc.addParameter( 'showAvailability', false ); >+ xslProc.transform(); >+ return xslProc.output; >+ } >+}; >+ >+KOHA.ExternalSearch = ( function() { >+ return { >+ targets: {}, >+ Search: function( q, limit, callback ) { >+ var targetIDs = []; >+ var includedTargets = []; >+ >+ $.each( KOHA.ExternalSearch.targets, function ( url, info ) { >+ if ( !info.disabled ) { >+ includedTargets.push( url ); >+ targetIDs.push( info.id ); >+ } >+ } ); >+ >+ if ( KOHA.ExternalSearch._pz !== undefined ) { >+ afterinit( KOHA.ExternalSearch._pz ); >+ } else { >+ $.get( '/cgi-bin/koha/svc/pazpar2_init', { targets: targetIDs.join(',') }, function( data ) { >+ KOHA.ExternalSearch._pz = new pz2({ >+ sessionId: data.sessionID, >+ onshow: callback, >+ errorhandler: function ( error ) { callback( { error: error } ) }, >+ } ); >+ afterinit( KOHA.ExternalSearch._pz ); >+ } ); >+ } >+ >+ function afterinit( pz ) { >+ pz.search( q, limit, 'relevance:0', 'pz:id=' + includedTargets.join( '|' ) ); >+ } >+ }, >+ Fetch: function( offset, callback ) { >+ var pz = KOHA.ExternalSearch._pz; >+ pz.showCallback = callback; >+ pz.show( offset ); >+ }, >+ GetDetailedRecord: function( recid, callback ) { >+ KOHA.ExternalSearch._pz.record( recid, 0, undefined, { callback: callback } ); >+ }, >+ }; >+} )(); >diff --git a/koha-tmpl/opac-tmpl/ccsr/en/lib/jquery/plugins/jquery.lightbox_me.js b/koha-tmpl/opac-tmpl/ccsr/en/lib/jquery/plugins/jquery.lightbox_me.js >new file mode 100644 >index 0000000..effe8a6 >--- /dev/null >+++ b/koha-tmpl/opac-tmpl/ccsr/en/lib/jquery/plugins/jquery.lightbox_me.js >@@ -0,0 +1,254 @@ >+/* >+* $ lightbox_me >+* By: Buck Wilson >+* Version : 2.3 >+* >+* Licensed under the Apache License, Version 2.0 (the "License"); >+* you may not use this file except in compliance with the License. >+* You may obtain a copy of the License at >+* >+* http://www.apache.org/licenses/LICENSE-2.0 >+* >+* Unless required by applicable law or agreed to in writing, software >+* distributed under the License is distributed on an "AS IS" BASIS, >+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. >+* See the License for the specific language governing permissions and >+* limitations under the License. >+*/ >+ >+ >+(function($) { >+ >+ $.fn.lightbox_me = function(options) { >+ >+ return this.each(function() { >+ >+ var >+ opts = $.extend({}, $.fn.lightbox_me.defaults, options), >+ $overlay = $(), >+ $self = $(this), >+ $iframe = $('<iframe id="foo" style="z-index: ' + (opts.zIndex + 1) + ';border: none; margin: 0; padding: 0; position: absolute; width: 100%; height: 100%; top: 0; left: 0; filter: mask();"/>'), >+ ie6 = ($.browser.msie && $.browser.version < 7); >+ >+ if (opts.showOverlay) { >+ //check if there's an existing overlay, if so, make subequent ones clear >+ var $currentOverlays = $(".js_lb_overlay:visible"); >+ if ($currentOverlays.length > 0){ >+ $overlay = $('<div class="lb_overlay_clear js_lb_overlay"/>'); >+ } else { >+ $overlay = $('<div class="' + opts.classPrefix + '_overlay js_lb_overlay"/>'); >+ } >+ } >+ >+ /*---------------------------------------------------- >+ DOM Building >+ ---------------------------------------------------- */ >+ if (ie6) { >+ var src = /^https/i.test(window.location.href || '') ? 'javascript:false' : 'about:blank'; >+ $iframe.attr('src', src); >+ $('body').append($iframe); >+ } // iframe shim for ie6, to hide select elements >+ $('body').append($self.hide()).append($overlay); >+ >+ >+ /*---------------------------------------------------- >+ Overlay CSS stuffs >+ ---------------------------------------------------- */ >+ >+ // set css of the overlay >+ if (opts.showOverlay) { >+ setOverlayHeight(); // pulled this into a function because it is called on window resize. >+ $overlay.css({ position: 'absolute', width: '100%', top: 0, left: 0, right: 0, bottom: 0, zIndex: (opts.zIndex + 2), display: 'none' }); >+ if (!$overlay.hasClass('lb_overlay_clear')){ >+ $overlay.css(opts.overlayCSS); >+ } >+ } >+ >+ /*---------------------------------------------------- >+ Animate it in. >+ ---------------------------------------------------- */ >+ // >+ if (opts.showOverlay) { >+ $overlay.fadeIn(opts.overlaySpeed, function() { >+ setSelfPosition(); >+ $self[opts.appearEffect](opts.lightboxSpeed, function() { setOverlayHeight(); setSelfPosition(); opts.onLoad()}); >+ }); >+ } else { >+ setSelfPosition(); >+ $self[opts.appearEffect](opts.lightboxSpeed, function() { opts.onLoad()}); >+ } >+ >+ /*---------------------------------------------------- >+ Hide parent if parent specified (parentLightbox should be jquery reference to any parent lightbox) >+ ---------------------------------------------------- */ >+ if (opts.parentLightbox) { >+ opts.parentLightbox.fadeOut(200); >+ } >+ >+ >+ /*---------------------------------------------------- >+ Bind Events >+ ---------------------------------------------------- */ >+ >+ $(window).resize(setOverlayHeight) >+ .resize(setSelfPosition) >+ .scroll(setSelfPosition); >+ >+ $(window).bind('keyup.lightbox_me', observeKeyPress); >+ >+ if (opts.closeClick) { >+ $overlay.click(function(e) { closeLightbox(); e.preventDefault; }); >+ } >+ $self.delegate(opts.closeSelector, "click", function(e) { >+ closeLightbox(); e.preventDefault(); >+ }); >+ $self.bind('close', closeLightbox); >+ $self.bind('reposition', setSelfPosition); >+ >+ >+ >+ /*-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- >+ -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- */ >+ >+ >+ /*---------------------------------------------------- >+ Private Functions >+ ---------------------------------------------------- */ >+ >+ /* Remove or hide all elements */ >+ function closeLightbox() { >+ var s = $self[0].style; >+ if (opts.destroyOnClose) { >+ $self.add($overlay).remove(); >+ } else { >+ $self.add($overlay).hide(); >+ } >+ >+ //show the hidden parent lightbox >+ if (opts.parentLightbox) { >+ opts.parentLightbox.fadeIn(200); >+ } >+ >+ $iframe.remove(); >+ >+ // clean up events. >+ $self.undelegate(opts.closeSelector, "click"); >+ >+ $(window).unbind('reposition', setOverlayHeight); >+ $(window).unbind('reposition', setSelfPosition); >+ $(window).unbind('scroll', setSelfPosition); >+ $(window).unbind('keyup.lightbox_me'); >+ if (ie6) >+ s.removeExpression('top'); >+ opts.onClose(); >+ } >+ >+ >+ /* Function to bind to the window to observe the escape/enter key press */ >+ function observeKeyPress(e) { >+ if((e.keyCode == 27 || (e.DOM_VK_ESCAPE == 27 && e.which==0)) && opts.closeEsc) closeLightbox(); >+ } >+ >+ >+ /* Set the height of the overlay >+ : if the document height is taller than the window, then set the overlay height to the document height. >+ : otherwise, just set overlay height: 100% >+ */ >+ function setOverlayHeight() { >+ if ($(window).height() < $(document).height()) { >+ $overlay.css({height: $(document).height() + 'px'}); >+ $iframe.css({height: $(document).height() + 'px'}); >+ } else { >+ $overlay.css({height: '100%'}); >+ if (ie6) { >+ $('html,body').css('height','100%'); >+ $iframe.css('height', '100%'); >+ } // ie6 hack for height: 100%; TODO: handle this in IE7 >+ } >+ } >+ >+ >+ /* Set the position of the modal'd window ($self) >+ : if $self is taller than the window, then make it absolutely positioned >+ : otherwise fixed >+ */ >+ function setSelfPosition() { >+ var s = $self[0].style; >+ >+ // reset CSS so width is re-calculated for margin-left CSS >+ $self.css({left: '50%', marginLeft: ($self.outerWidth() / 2) * -1, zIndex: (opts.zIndex + 3) }); >+ >+ >+ /* we have to get a little fancy when dealing with height, because lightbox_me >+ is just so fancy. >+ */ >+ >+ // if the height of $self is bigger than the window and self isn't already position absolute >+ if (($self.height() + 80 >= $(window).height()) && ($self.css('position') != 'absolute' || ie6)) { >+ >+ // we are going to make it positioned where the user can see it, but they can still scroll >+ // so the top offset is based on the user's scroll position. >+ var topOffset = $(document).scrollTop() + 40; >+ $self.css({position: 'absolute', top: topOffset + 'px', marginTop: 0}) >+ if (ie6) { >+ s.removeExpression('top'); >+ } >+ } else if ($self.height()+ 80 < $(window).height()) { >+ //if the height is less than the window height, then we're gonna make this thing position: fixed. >+ // in ie6 we're gonna fake it. >+ if (ie6) { >+ s.position = 'absolute'; >+ if (opts.centered) { >+ s.setExpression('top', '(document.documentElement.clientHeight || document.body.clientHeight) / 2 - (this.offsetHeight / 2) + (blah = document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop) + "px"') >+ s.marginTop = 0; >+ } else { >+ var top = (opts.modalCSS && opts.modalCSS.top) ? parseInt(opts.modalCSS.top) : 0; >+ s.setExpression('top', '((blah = document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop) + '+top+') + "px"') >+ } >+ } else { >+ if (opts.centered) { >+ $self.css({ position: 'fixed', top: '50%', marginTop: ($self.outerHeight() / 2) * -1}) >+ } else { >+ $self.css({ position: 'fixed'}).css(opts.modalCSS); >+ } >+ >+ } >+ } >+ } >+ >+ }); >+ >+ >+ >+ }; >+ >+ $.fn.lightbox_me.defaults = { >+ >+ // animation >+ appearEffect: "fadeIn", >+ appearEase: "", >+ overlaySpeed: 250, >+ lightboxSpeed: 300, >+ >+ // close >+ closeSelector: ".close", >+ closeClick: true, >+ closeEsc: true, >+ >+ // behavior >+ destroyOnClose: false, >+ showOverlay: true, >+ parentLightbox: false, >+ >+ // callbacks >+ onLoad: function() {}, >+ onClose: function() {}, >+ >+ // style >+ classPrefix: 'lb', >+ zIndex: 999, >+ centered: false, >+ modalCSS: {top: '40px'}, >+ overlayCSS: {background: 'black', opacity: .3} >+ } >+})(jQuery); >\ No newline at end of file >diff --git a/koha-tmpl/opac-tmpl/lib/pz2.js b/koha-tmpl/opac-tmpl/lib/pz2.js >new file mode 100644 >index 0000000..4234c07 >--- /dev/null >+++ b/koha-tmpl/opac-tmpl/lib/pz2.js >@@ -0,0 +1,1124 @@ >+/* >+ * pz2.js - pazpar2's javascript client library. >+ * Copyright (C) 2006-2013 Index Data. >+ * >+ * This program is free software; you can redistribute it and/or >+ * modify it under the terms of the GNU General Public License >+ * as published by the Free Software Foundation; either version 2 >+ * of the License, or (at your option) any later version. >+ * >+ * This program is distributed in the hope that it will be useful, >+ * but WITHOUT ANY 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 this program; if not, write to the Free Software >+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. >+*/ >+ >+//since explorer is flawed >+if (!window['Node']) { >+ window.Node = new Object(); >+ Node.ELEMENT_NODE = 1; >+ Node.ATTRIBUTE_NODE = 2; >+ Node.TEXT_NODE = 3; >+ Node.CDATA_SECTION_NODE = 4; >+ Node.ENTITY_REFERENCE_NODE = 5; >+ Node.ENTITY_NODE = 6; >+ Node.PROCESSING_INSTRUCTION_NODE = 7; >+ Node.COMMENT_NODE = 8; >+ Node.DOCUMENT_NODE = 9; >+ Node.DOCUMENT_TYPE_NODE = 10; >+ Node.DOCUMENT_FRAGMENT_NODE = 11; >+ Node.NOTATION_NODE = 12; >+} >+ >+// prevent execution of more than once >+if(typeof window.pz2 == "undefined") { >+window.undefined = window.undefined; >+ >+var pz2 = function ( paramArray ) >+{ >+ >+ // at least one callback required >+ if ( !paramArray ) >+ throw new Error("Pz2.js: Array with parameters has to be supplied."); >+ >+ //supported pazpar2's protocol version >+ this.suppProtoVer = '1'; >+ if (typeof paramArray.pazpar2path != "undefined") >+ this.pz2String = paramArray.pazpar2path; >+ else >+ this.pz2String = "/pazpar2/search.pz2"; >+ this.useSessions = true; >+ >+ this.stylesheet = paramArray.detailstylesheet || null; >+ //load stylesheet if required in async mode >+ if( this.stylesheet ) { >+ var context = this; >+ var request = new pzHttpRequest( this.stylesheet ); >+ request.get( {}, function ( doc ) { context.xslDoc = doc; } ); >+ } >+ >+ this.errorHandler = paramArray.errorhandler || null; >+ this.showResponseType = paramArray.showResponseType || "xml"; >+ >+ // function callbacks >+ this.initCallback = paramArray.oninit || null; >+ this.statCallback = paramArray.onstat || null; >+ this.showCallback = paramArray.onshow || null; >+ this.termlistCallback = paramArray.onterm || null; >+ this.recordCallback = paramArray.onrecord || null; >+ this.bytargetCallback = paramArray.onbytarget || null; >+ this.resetCallback = paramArray.onreset || null; >+ >+ // termlist keys >+ this.termKeys = paramArray.termlist || "subject"; >+ >+ // some configurational stuff >+ this.keepAlive = 50000; >+ >+ if ( paramArray.keepAlive < this.keepAlive ) >+ this.keepAlive = paramArray.keepAlive; >+ >+ this.sessionID = paramArray.sessionId || null; >+ this.serviceId = paramArray.serviceId || null; >+ this.initStatusOK = false; >+ this.pingStatusOK = false; >+ this.searchStatusOK = false; >+ >+ // for sorting >+ this.currentSort = "relevance"; >+ >+ // where are we? >+ this.currentStart = 0; >+ // currentNum can be overwritten in show >+ this.currentNum = 20; >+ >+ // last full record retrieved >+ this.currRecID = null; >+ >+ // current query >+ this.currQuery = null; >+ >+ //current raw record offset >+ this.currRecOffset = null; >+ >+ //timers >+ this.pingTimer = null; >+ this.statTime = paramArray.stattime || 1000; >+ this.statTimer = null; >+ this.termTime = paramArray.termtime || 1000; >+ this.termTimer = null; >+ this.showTime = paramArray.showtime || 1000; >+ this.showTimer = null; >+ this.showFastCount = 4; >+ this.bytargetTime = paramArray.bytargettime || 1000; >+ this.bytargetTimer = null; >+ this.recordTime = paramArray.recordtime || 500; >+ this.recordTimer = null; >+ >+ // counters for each command and applied delay >+ this.dumpFactor = 500; >+ this.showCounter = 0; >+ this.termCounter = 0; >+ this.statCounter = 0; >+ this.bytargetCounter = 0; >+ this.recordCounter = 0; >+ >+ // active clients, updated by stat and show >+ // might be an issue since bytarget will poll accordingly >+ this.activeClients = 1; >+ >+ // if in proxy mode no need to init >+ if (paramArray.usesessions != undefined) { >+ this.useSessions = paramArray.usesessions; >+ this.initStatusOK = true; >+ } >+ // else, auto init session or wait for a user init? >+ if (this.useSessions && paramArray.autoInit !== false) { >+ this.init(this.sessionID, this.serviceId); >+ } >+ // Version parameter >+ this.version = paramArray.version || null; >+}; >+ >+pz2.prototype = >+{ >+ //error handler for async error throws >+ throwError: function (errMsg, errCode) >+ { >+ var err = new Error(errMsg); >+ if (errCode) err.code = errCode; >+ >+ if (this.errorHandler) { >+ this.errorHandler(err); >+ } >+ else { >+ throw err; >+ } >+ }, >+ >+ // stop activity by clearing tiemouts >+ stop: function () >+ { >+ clearTimeout(this.statTimer); >+ clearTimeout(this.showTimer); >+ clearTimeout(this.termTimer); >+ clearTimeout(this.bytargetTimer); >+ }, >+ >+ // reset status variables >+ reset: function () >+ { >+ if ( this.useSessions ) { >+ this.sessionID = null; >+ this.initStatusOK = false; >+ this.pingStatusOK = false; >+ clearTimeout(this.pingTimer); >+ } >+ this.searchStatusOK = false; >+ this.stop(); >+ >+ if ( this.resetCallback ) >+ this.resetCallback(); >+ }, >+ >+ init: function (sessionId, serviceId) >+ { >+ this.reset(); >+ >+ // session id as a param >+ if (sessionId && this.useSessions ) { >+ this.initStatusOK = true; >+ this.sessionID = sessionId; >+ this.ping(); >+ // old school direct pazpar2 init >+ } else if (this.useSessions) { >+ var context = this; >+ var request = new pzHttpRequest(this.pz2String, this.errorHandler); >+ var opts = {'command' : 'init'}; >+ if (serviceId) opts.service = serviceId; >+ request.safeGet( >+ opts, >+ function(data) { >+ if ( data.getElementsByTagName("status")[0] >+ .childNodes[0].nodeValue == "OK" ) { >+ if ( data.getElementsByTagName("protocol")[0] >+ .childNodes[0].nodeValue >+ != context.suppProtoVer ) >+ throw new Error( >+ "Server's protocol not supported by the client" >+ ); >+ context.initStatusOK = true; >+ context.sessionID = >+ data.getElementsByTagName("session")[0] >+ .childNodes[0].nodeValue; >+ if (data.getElementsByTagName("keepAlive").length > 0) { >+ context.keepAlive = data.getElementsByTagName("keepAlive")[0].childNodes[0].nodeValue; >+ } >+ context.pingTimer = >+ setTimeout( >+ function () { >+ context.ping(); >+ }, >+ context.keepAlive >+ ); >+ if ( context.initCallback ) >+ context.initCallback(); >+ } >+ else >+ context.throwError('Init failed. Malformed WS resonse.', >+ 110); >+ } >+ ); >+ // when through proxy no need to init >+ } else { >+ this.initStatusOK = true; >+ } >+ }, >+ // no need to ping explicitly >+ ping: function () >+ { >+ // pinging only makes sense when using pazpar2 directly >+ if( !this.initStatusOK || !this.useSessions ) >+ throw new Error( >+ 'Pz2.js: Ping not allowed (proxy mode) or session not initialized.' >+ ); >+ var context = this; >+ >+ clearTimeout(context.pingTimer); >+ >+ var request = new pzHttpRequest(this.pz2String, this.errorHandler); >+ request.safeGet( >+ { "command": "ping", "session": this.sessionID, "windowid" : window.name }, >+ function(data) { >+ if ( data.getElementsByTagName("status")[0] >+ .childNodes[0].nodeValue == "OK" ) { >+ context.pingStatusOK = true; >+ context.pingTimer = >+ setTimeout( >+ function () { >+ context.ping(); >+ }, >+ context.keepAlive >+ ); >+ } >+ else >+ context.throwError('Ping failed. Malformed WS resonse.', >+ 111); >+ } >+ ); >+ }, >+ search: function (query, num, sort, filter, showfrom, addParamsArr) >+ { >+ clearTimeout(this.statTimer); >+ clearTimeout(this.showTimer); >+ clearTimeout(this.termTimer); >+ clearTimeout(this.bytargetTimer); >+ >+ this.showCounter = 0; >+ this.termCounter = 0; >+ this.bytargetCounter = 0; >+ this.statCounter = 0; >+ this.activeClients = 1; >+ >+ // no proxy mode >+ if( !this.initStatusOK ) >+ throw new Error('Pz2.js: session not initialized.'); >+ >+ if( query !== undefined ) >+ this.currQuery = query; >+ else >+ throw new Error("Pz2.js: no query supplied to the search command."); >+ >+ if ( showfrom !== undefined ) >+ var start = showfrom; >+ else >+ var start = 0; >+ >+ var searchParams = { >+ "command": "search", >+ "query": this.currQuery, >+ "session": this.sessionID, >+ "windowid" : window.name >+ }; >+ >+ if( sort !== undefined ) { >+ this.currentSort = sort; >+ searchParams["sort"] = sort; >+ } >+ if (filter !== undefined) >+ searchParams["filter"] = filter; >+ >+ // copy additional parmeters, do not overwrite >+ if (addParamsArr != undefined) { >+ for (var prop in addParamsArr) { >+ if (!searchParams.hasOwnProperty(prop)) >+ searchParams[prop] = addParamsArr[prop]; >+ } >+ } >+ >+ var context = this; >+ var request = new pzHttpRequest(this.pz2String, this.errorHandler); >+ request.safeGet( >+ searchParams, >+ function(data) { >+ if ( data.getElementsByTagName("status")[0] >+ .childNodes[0].nodeValue == "OK" ) { >+ context.searchStatusOK = true; >+ //piggyback search >+ context.show(start, num, sort); >+ if (context.statCallback) >+ context.stat(); >+ if (context.termlistCallback) >+ context.termlist(); >+ if (context.bytargetCallback) >+ context.bytarget(); >+ } >+ else >+ context.throwError('Search failed. Malformed WS resonse.', >+ 112); >+ } >+ ); >+ }, >+ stat: function() >+ { >+ if( !this.initStatusOK ) >+ throw new Error('Pz2.js: session not initialized.'); >+ >+ // if called explicitly takes precedence >+ clearTimeout(this.statTimer); >+ >+ var context = this; >+ var request = new pzHttpRequest(this.pz2String, this.errorHandler); >+ request.safeGet( >+ { "command": "stat", "session": this.sessionID, "windowid" : window.name }, >+ function(data) { >+ if ( data.getElementsByTagName("stat") ) { >+ var activeClients = >+ Number( data.getElementsByTagName("activeclients")[0] >+ .childNodes[0].nodeValue ); >+ context.activeClients = activeClients; >+ >+ var stat = Element_parseChildNodes(data.documentElement); >+ >+ context.statCounter++; >+ var delay = context.statTime >+ + context.statCounter * context.dumpFactor; >+ >+ if ( activeClients > 0 ) >+ context.statTimer = >+ setTimeout( >+ function () { >+ context.stat(); >+ }, >+ delay >+ ); >+ context.statCallback(stat); >+ } >+ else >+ context.throwError('Stat failed. Malformed WS resonse.', >+ 113); >+ } >+ ); >+ }, >+ show: function(start, num, sort, query_state) >+ { >+ if( !this.searchStatusOK && this.useSessions ) >+ throw new Error( >+ 'Pz2.js: show command has to be preceded with a search command.' >+ ); >+ >+ // if called explicitly takes precedence >+ clearTimeout(this.showTimer); >+ >+ if( sort !== undefined ) >+ this.currentSort = sort; >+ if( start !== undefined ) >+ this.currentStart = Number( start ); >+ if( num !== undefined ) >+ this.currentNum = Number( num ); >+ >+ var context = this; >+ var request = new pzHttpRequest(this.pz2String, this.errorHandler); >+ var requestParameters = >+ { >+ "command": "show", >+ "session": this.sessionID, >+ "start": this.currentStart, >+ "num": this.currentNum, >+ "sort": this.currentSort, >+ "block": 1, >+ "type": this.showResponseType, >+ "windowid" : window.name >+ }; >+ if (query_state) >+ requestParameters["query-state"] = query_state; >+ if (this.version && this.version > 0) >+ requestParameters["version"] = this.version; >+ request.safeGet( >+ requestParameters, >+ function(data, type) { >+ var show = null; >+ var activeClients = 0; >+ if (type === "json") { >+ show = {}; >+ activeClients = Number(data.activeclients[0]); >+ show.activeclients = activeClients; >+ show.merged = Number(data.merged[0]); >+ show.total = Number(data.total[0]); >+ show.start = Number(data.start[0]); >+ show.num = Number(data.num[0]); >+ show.hits = data.hit; >+ } else if (data.getElementsByTagName("status")[0] >+ .childNodes[0].nodeValue == "OK") { >+ // first parse the status data send along with records >+ // this is strictly bound to the format >+ activeClients = >+ Number(data.getElementsByTagName("activeclients")[0] >+ .childNodes[0].nodeValue); >+ show = { >+ "activeclients": activeClients, >+ "merged": >+ Number( data.getElementsByTagName("merged")[0] >+ .childNodes[0].nodeValue ), >+ "total": >+ Number( data.getElementsByTagName("total")[0] >+ .childNodes[0].nodeValue ), >+ "start": >+ Number( data.getElementsByTagName("start")[0] >+ .childNodes[0].nodeValue ), >+ "num": >+ Number( data.getElementsByTagName("num")[0] >+ .childNodes[0].nodeValue ), >+ "hits": [] >+ }; >+ // parse all the first-level nodes for all <hit> tags >+ var hits = data.getElementsByTagName("hit"); >+ for (i = 0; i < hits.length; i++) >+ show.hits[i] = Element_parseChildNodes(hits[i]); >+ } else { >+ context.throwError('Show failed. Malformed WS resonse.', >+ 114); >+ }; >+ >+ var approxNode = data.getElementsByTagName("approximation"); >+ if (approxNode && approxNode[0] && approxNode[0].childNodes[0] && approxNode[0].childNodes[0].nodeValue) >+ show['approximation'] = >+ Number( approxNode[0].childNodes[0].nodeValue); >+ >+ >+ data.getElementsByTagName("") >+ context.activeClients = activeClients; >+ context.showCounter++; >+ var delay = context.showTime; >+ if (context.showCounter > context.showFastCount) >+ delay += context.showCounter * context.dumpFactor; >+ if ( activeClients > 0 ) >+ context.showTimer = setTimeout( >+ function () { >+ context.show(); >+ }, >+ delay); >+ context.showCallback(show); >+ } >+ ); >+ }, >+ record: function(id, offset, syntax, handler) >+ { >+ // we may call record with no previous search if in proxy mode >+ if(!this.searchStatusOK && this.useSessions) >+ throw new Error( >+ 'Pz2.js: record command has to be preceded with a search command.' >+ ); >+ >+ if( id !== undefined ) >+ this.currRecID = id; >+ >+ var recordParams = { >+ "command": "record", >+ "session": this.sessionID, >+ "id": this.currRecID, >+ "windowid" : window.name >+ }; >+ >+ this.currRecOffset = null; >+ if (offset != undefined) { >+ recordParams["offset"] = offset; >+ this.currRecOffset = offset; >+ } >+ >+ if (syntax != undefined) >+ recordParams['syntax'] = syntax; >+ >+ //overwrite default callback id needed >+ var callback = this.recordCallback; >+ var args = undefined; >+ if (handler != undefined) { >+ callback = handler['callback']; >+ args = handler['args']; >+ } >+ >+ var context = this; >+ var request = new pzHttpRequest(this.pz2String, this.errorHandler); >+ >+ request.safeGet( >+ recordParams, >+ function(data) { >+ var recordNode; >+ var record; >+ //raw record >+ if (context.currRecOffset !== null) { >+ record = new Array(); >+ record['xmlDoc'] = data; >+ record['offset'] = context.currRecOffset; >+ callback(record, args); >+ //pz2 record >+ } else if ( recordNode = >+ data.getElementsByTagName("record")[0] ) { >+ // if stylesheet was fetched do not parse the response >+ if ( context.xslDoc ) { >+ record = new Array(); >+ record['xmlDoc'] = data; >+ record['xslDoc'] = context.xslDoc; >+ record['recid'] = >+ recordNode.getElementsByTagName("recid")[0] >+ .firstChild.nodeValue; >+ //parse record >+ } else { >+ record = Element_parseChildNodes(recordNode); >+ } >+ var activeClients = >+ Number( data.getElementsByTagName("activeclients")[0] >+ .childNodes[0].nodeValue ); >+ context.activeClients = activeClients; >+ context.recordCounter++; >+ var delay = context.recordTime + context.recordCounter * context.dumpFactor; >+ if ( activeClients > 0 ) >+ context.recordTimer = >+ setTimeout ( >+ function() { >+ context.record(id, offset, syntax, handler); >+ }, >+ delay >+ ); >+ callback(record, args); >+ } >+ else >+ context.throwError('Record failed. Malformed WS resonse.', >+ 115); >+ } >+ ); >+ }, >+ >+ termlist: function() >+ { >+ if( !this.searchStatusOK && this.useSessions ) >+ throw new Error( >+ 'Pz2.js: termlist command has to be preceded with a search command.' >+ ); >+ >+ // if called explicitly takes precedence >+ clearTimeout(this.termTimer); >+ >+ var context = this; >+ var request = new pzHttpRequest(this.pz2String, this.errorHandler); >+ request.safeGet( >+ { >+ "command": "termlist", >+ "session": this.sessionID, >+ "name": this.termKeys, >+ "windowid" : window.name, >+ "version" : this.version >+ >+ }, >+ function(data) { >+ if ( data.getElementsByTagName("termlist") ) { >+ var activeClients = >+ Number( data.getElementsByTagName("activeclients")[0] >+ .childNodes[0].nodeValue ); >+ context.activeClients = activeClients; >+ var termList = { "activeclients": activeClients }; >+ var termLists = data.getElementsByTagName("list"); >+ //for each termlist >+ for (i = 0; i < termLists.length; i++) { >+ var listName = termLists[i].getAttribute('name'); >+ termList[listName] = new Array(); >+ var terms = termLists[i].getElementsByTagName('term'); >+ //for each term in the list >+ for (j = 0; j < terms.length; j++) { >+ var term = { >+ "name": >+ (terms[j].getElementsByTagName("name")[0] >+ .childNodes.length >+ ? terms[j].getElementsByTagName("name")[0] >+ .childNodes[0].nodeValue >+ : 'ERROR'), >+ "freq": >+ terms[j] >+ .getElementsByTagName("frequency")[0] >+ .childNodes[0].nodeValue || 'ERROR' >+ }; >+ >+ // Only for xtargets: id, records, filtered >+ var termIdNode = >+ terms[j].getElementsByTagName("id"); >+ if(terms[j].getElementsByTagName("id").length) >+ term["id"] = >+ termIdNode[0].childNodes[0].nodeValue; >+ termList[listName][j] = term; >+ >+ var recordsNode = terms[j].getElementsByTagName("records"); >+ if (recordsNode && recordsNode.length) >+ term["records"] = recordsNode[0].childNodes[0].nodeValue; >+ >+ var filteredNode = terms[j].getElementsByTagName("filtered"); >+ if (filteredNode && filteredNode.length) >+ term["filtered"] = filteredNode[0].childNodes[0].nodeValue; >+ >+ } >+ } >+ >+ context.termCounter++; >+ var delay = context.termTime >+ + context.termCounter * context.dumpFactor; >+ if ( activeClients > 0 ) >+ context.termTimer = >+ setTimeout( >+ function () { >+ context.termlist(); >+ }, >+ delay >+ ); >+ >+ context.termlistCallback(termList); >+ } >+ else >+ context.throwError('Termlist failed. Malformed WS resonse.', >+ 116); >+ } >+ ); >+ >+ }, >+ bytarget: function() >+ { >+ if( !this.initStatusOK && this.useSessions ) >+ throw new Error( >+ 'Pz2.js: bytarget command has to be preceded with a search command.' >+ ); >+ >+ // no need to continue >+ if( !this.searchStatusOK ) >+ return; >+ >+ // if called explicitly takes precedence >+ clearTimeout(this.bytargetTimer); >+ >+ var context = this; >+ var request = new pzHttpRequest(this.pz2String, this.errorHandler); >+ request.safeGet( >+ { >+ "command": "bytarget", >+ "session": this.sessionID, >+ "block": 1, >+ "windowid" : window.name, >+ "version" : this.version >+ }, >+ function(data) { >+ if ( data.getElementsByTagName("status")[0] >+ .childNodes[0].nodeValue == "OK" ) { >+ var targetNodes = data.getElementsByTagName("target"); >+ var bytarget = new Array(); >+ for ( i = 0; i < targetNodes.length; i++) { >+ bytarget[i] = new Array(); >+ for( j = 0; j < targetNodes[i].childNodes.length; j++ ) { >+ if ( targetNodes[i].childNodes[j].nodeType >+ == Node.ELEMENT_NODE ) { >+ var nodeName = >+ targetNodes[i].childNodes[j].nodeName; >+ if (targetNodes[i].childNodes[j].firstChild != null) >+ { >+ var nodeText = targetNodes[i].childNodes[j] >+ .firstChild.nodeValue; >+ bytarget[i][nodeName] = nodeText; >+ } >+ else { >+ bytarget[i][nodeName] = ""; >+ } >+ >+ >+ } >+ } >+ if (bytarget[i]["state"]=="Client_Disconnected") { >+ bytarget[i]["hits"] = "Error"; >+ } else if (bytarget[i]["state"]=="Client_Error") { >+ bytarget[i]["hits"] = "Error"; >+ } else if (bytarget[i]["state"]=="Client_Working") { >+ bytarget[i]["hits"] = "..."; >+ } >+ if (bytarget[i].diagnostic == "1") { >+ bytarget[i].diagnostic = "Permanent system error"; >+ } else if (bytarget[i].diagnostic == "2") { >+ bytarget[i].diagnostic = "Temporary system error"; >+ } >+ var targetsSuggestions = targetNodes[i].getElementsByTagName("suggestions"); >+ if (targetsSuggestions != undefined && targetsSuggestions.length>0) { >+ var suggestions = targetsSuggestions[0]; >+ bytarget[i]["suggestions"] = Element_parseChildNodes(suggestions); >+ } >+ } >+ >+ context.bytargetCounter++; >+ var delay = context.bytargetTime >+ + context.bytargetCounter * context.dumpFactor; >+ if ( context.activeClients > 0 ) >+ context.bytargetTimer = >+ setTimeout( >+ function () { >+ context.bytarget(); >+ }, >+ delay >+ ); >+ >+ context.bytargetCallback(bytarget); >+ } >+ else >+ context.throwError('Bytarget failed. Malformed WS resonse.', >+ 117); >+ } >+ ); >+ }, >+ >+ // just for testing, probably shouldn't be here >+ showNext: function(page) >+ { >+ var step = page || 1; >+ this.show( ( step * this.currentNum ) + this.currentStart ); >+ }, >+ >+ showPrev: function(page) >+ { >+ if (this.currentStart == 0 ) >+ return false; >+ var step = page || 1; >+ var newStart = this.currentStart - (step * this.currentNum ); >+ this.show( newStart > 0 ? newStart : 0 ); >+ }, >+ >+ showPage: function(pageNum) >+ { >+ //var page = pageNum || 1; >+ this.show(pageNum * this.currentNum); >+ } >+}; >+ >+/* >+******************************************************************************** >+** AJAX HELPER CLASS *********************************************************** >+******************************************************************************** >+*/ >+var pzHttpRequest = function ( url, errorHandler ) { >+ this.maxUrlLength = 2048; >+ this.request = null; >+ this.url = url; >+ this.errorHandler = errorHandler || null; >+ this.async = true; >+ this.requestHeaders = {}; >+ >+ if ( window.XMLHttpRequest ) { >+ this.request = new XMLHttpRequest(); >+ } else if ( window.ActiveXObject ) { >+ try { >+ this.request = new ActiveXObject( 'Msxml2.XMLHTTP' ); >+ } catch (err) { >+ this.request = new ActiveXObject( 'Microsoft.XMLHTTP' ); >+ } >+ } >+}; >+ >+ >+pzHttpRequest.prototype = >+{ >+ safeGet: function ( params, callback ) >+ { >+ var encodedParams = this.encodeParams(params); >+ var url = this._urlAppendParams(encodedParams); >+ if (url.length >= this.maxUrlLength) { >+ this.requestHeaders["Content-Type"] >+ = "application/x-www-form-urlencoded"; >+ this._send( 'POST', this.url, encodedParams, callback ); >+ } else { >+ this._send( 'GET', url, '', callback ); >+ } >+ }, >+ >+ get: function ( params, callback ) >+ { >+ this._send( 'GET', this._urlAppendParams(this.encodeParams(params)), >+ '', callback ); >+ }, >+ >+ post: function ( params, data, callback ) >+ { >+ this._send( 'POST', this._urlAppendParams(this.encodeParams(params)), >+ data, callback ); >+ }, >+ >+ load: function () >+ { >+ this.async = false; >+ this.request.open( 'GET', this.url, this.async ); >+ this.request.send(''); >+ if ( this.request.status == 200 ) >+ return this.request.responseXML; >+ }, >+ >+ encodeParams: function (params) >+ { >+ var sep = ""; >+ var encoded = ""; >+ for (var key in params) { >+ if (params[key] != null) { >+ encoded += sep + key + '=' + encodeURIComponent(params[key]); >+ sep = '&'; >+ } >+ } >+ return encoded; >+ }, >+ >+ _send: function ( type, url, data, callback) >+ { >+ var context = this; >+ this.callback = callback; >+ this.async = true; >+ this.request.open( type, url, this.async ); >+ for (var key in this.requestHeaders) >+ this.request.setRequestHeader(key, this.requestHeaders[key]); >+ this.request.onreadystatechange = function () { >+ context._handleResponse(url); /// url used ONLY for error reporting >+ } >+ this.request.send(data); >+ }, >+ >+ _urlAppendParams: function (encodedParams) >+ { >+ if (encodedParams) >+ return this.url + "?" + encodedParams; >+ else >+ return this.url; >+ }, >+ >+ _handleResponse: function (savedUrlForErrorReporting) >+ { >+ if ( this.request.readyState == 4 ) { >+ // pick up appplication errors first >+ var errNode = null; >+ if (this.request.responseXML && >+ (errNode = this.request.responseXML.documentElement) >+ && errNode.nodeName == 'error') { >+ var errMsg = errNode.getAttribute("msg"); >+ var errCode = errNode.getAttribute("code"); >+ var errAddInfo = ''; >+ if (errNode.childNodes.length) >+ errAddInfo = ': ' + errNode.childNodes[0].nodeValue; >+ >+ var err = new Error(errMsg + errAddInfo); >+ err.code = errCode; >+ >+ if (this.errorHandler) { >+ this.errorHandler(err); >+ } >+ else { >+ throw err; >+ } >+ } else if (this.request.status == 200 && >+ this.request.responseXML == null) { >+ if (this.request.responseText != null) { >+ //assume JSON >+ >+ var json = null; >+ var text = this.request.responseText; >+ if (typeof window.JSON == "undefined") >+ json = eval("(" + text + ")"); >+ else { >+ try { >+ json = JSON.parse(text); >+ } >+ catch (e) { >+ // Safari: eval will fail as well. Considering trying JSON2 (non-native implementation) instead >+ /* DEBUG only works in mk2-mobile >+ if (document.getElementById("log")) >+ document.getElementById("log").innerHTML = "" + e + " " + length + ": " + text; >+ */ >+ try { >+ json = eval("(" + text + ")"); >+ } >+ catch (e) { >+ /* DEBUG only works in mk2-mobile >+ if (document.getElementById("log")) >+ document.getElementById("log").innerHTML = "" + e + " " + length + ": " + text; >+ */ >+ } >+ } >+ } >+ this.callback(json, "json"); >+ } else { >+ var err = new Error("XML response is empty but no error " + >+ "for " + savedUrlForErrorReporting); >+ err.code = -1; >+ if (this.errorHandler) { >+ this.errorHandler(err); >+ } else { >+ throw err; >+ } >+ } >+ } else if (this.request.status == 200) { >+ this.callback(this.request.responseXML); >+ } else { >+ var err = new Error("HTTP response not OK: " >+ + this.request.status + " - " >+ + this.request.statusText ); >+ err.code = '00' + this.request.status; >+ if (this.errorHandler) { >+ this.errorHandler(err); >+ } >+ else { >+ throw err; >+ } >+ } >+ } >+ } >+}; >+ >+/* >+******************************************************************************** >+** XML HELPER FUNCTIONS ******************************************************** >+******************************************************************************** >+*/ >+ >+// DOMDocument >+ >+if ( window.ActiveXObject) { >+ var DOMDoc = document; >+} else { >+ var DOMDoc = Document.prototype; >+} >+ >+DOMDoc.newXmlDoc = function ( root ) >+{ >+ var doc; >+ >+ if (document.implementation && document.implementation.createDocument) { >+ doc = document.implementation.createDocument('', root, null); >+ } else if ( window.ActiveXObject ) { >+ doc = new ActiveXObject("MSXML2.DOMDocument"); >+ doc.loadXML('<' + root + '/>'); >+ } else { >+ throw new Error ('No XML support in this browser'); >+ } >+ >+ return doc; >+} >+ >+ >+DOMDoc.parseXmlFromString = function ( xmlString ) >+{ >+ var doc; >+ >+ if ( window.DOMParser ) { >+ var parser = new DOMParser(); >+ doc = parser.parseFromString( xmlString, "text/xml"); >+ } else if ( window.ActiveXObject ) { >+ doc = new ActiveXObject("MSXML2.DOMDocument"); >+ doc.loadXML( xmlString ); >+ } else { >+ throw new Error ("No XML parsing support in this browser."); >+ } >+ >+ return doc; >+} >+ >+DOMDoc.transformToDoc = function (xmlDoc, xslDoc) >+{ >+ if ( window.XSLTProcessor ) { >+ var proc = new XSLTProcessor(); >+ proc.importStylesheet( xslDoc ); >+ return proc.transformToDocument(xmlDoc); >+ } else if ( window.ActiveXObject ) { >+ return document.parseXmlFromString(xmlDoc.transformNode(xslDoc)); >+ } else { >+ alert( 'Unable to perform XSLT transformation in this browser' ); >+ } >+} >+ >+// DOMElement >+ >+Element_removeFromDoc = function (DOM_Element) >+{ >+ DOM_Element.parentNode.removeChild(DOM_Element); >+} >+ >+Element_emptyChildren = function (DOM_Element) >+{ >+ while( DOM_Element.firstChild ) { >+ DOM_Element.removeChild( DOM_Element.firstChild ) >+ } >+} >+ >+Element_appendTransformResult = function ( DOM_Element, xmlDoc, xslDoc ) >+{ >+ if ( window.XSLTProcessor ) { >+ var proc = new XSLTProcessor(); >+ proc.importStylesheet( xslDoc ); >+ var docFrag = false; >+ docFrag = proc.transformToFragment( xmlDoc, DOM_Element.ownerDocument ); >+ DOM_Element.appendChild(docFrag); >+ } else if ( window.ActiveXObject ) { >+ DOM_Element.innerHTML = xmlDoc.transformNode( xslDoc ); >+ } else { >+ alert( 'Unable to perform XSLT transformation in this browser' ); >+ } >+} >+ >+Element_appendTextNode = function (DOM_Element, tagName, textContent ) >+{ >+ var node = DOM_Element.ownerDocument.createElement(tagName); >+ var text = DOM_Element.ownerDocument.createTextNode(textContent); >+ >+ DOM_Element.appendChild(node); >+ node.appendChild(text); >+ >+ return node; >+} >+ >+Element_setTextContent = function ( DOM_Element, textContent ) >+{ >+ if (typeof DOM_Element.textContent !== "undefined") { >+ DOM_Element.textContent = textContent; >+ } else if (typeof DOM_Element.innerText !== "undefined" ) { >+ DOM_Element.innerText = textContent; >+ } else { >+ throw new Error("Cannot set text content of the node, no such method."); >+ } >+} >+ >+Element_getTextContent = function (DOM_Element) >+{ >+ if ( typeof DOM_Element.textContent != 'undefined' ) { >+ return DOM_Element.textContent; >+ } else if (typeof DOM_Element.text != 'undefined') { >+ return DOM_Element.text; >+ } else { >+ throw new Error("Cannot get text content of the node, no such method."); >+ } >+} >+ >+Element_parseChildNodes = function (node) >+{ >+ var parsed = {}; >+ var hasChildElems = false; >+ var textContent = ''; >+ >+ if (node.hasChildNodes()) { >+ var children = node.childNodes; >+ for (var i = 0; i < children.length; i++) { >+ var child = children[i]; >+ switch (child.nodeType) { >+ case Node.ELEMENT_NODE: >+ hasChildElems = true; >+ var nodeName = child.nodeName; >+ if (!(nodeName in parsed)) >+ parsed[nodeName] = []; >+ parsed[nodeName].push(Element_parseChildNodes(child)); >+ break; >+ case Node.TEXT_NODE: >+ textContent += child.nodeValue; >+ break; >+ case Node.CDATA_SECTION_NODE: >+ textContent += child.nodeValue; >+ break; >+ } >+ } >+ } >+ >+ var attrs = node.attributes; >+ for (var i = 0; i < attrs.length; i++) { >+ hasChildElems = true; >+ var attrName = '@' + attrs[i].nodeName; >+ var attrValue = attrs[i].nodeValue; >+ parsed[attrName] = attrValue; >+ } >+ >+ // if no nested elements/attrs set value to text >+ if (hasChildElems) >+ parsed['#text'] = textContent; >+ else >+ parsed = textContent; >+ >+ return parsed; >+} >+ >+/* do not remove trailing bracket */ >+} >diff --git a/koha-tmpl/opac-tmpl/prog/en/css/opac.css b/koha-tmpl/opac-tmpl/prog/en/css/opac.css >index 9e15cee..4aa962a 100644 >--- a/koha-tmpl/opac-tmpl/prog/en/css/opac.css >+++ b/koha-tmpl/opac-tmpl/prog/en/css/opac.css >@@ -1677,6 +1677,10 @@ strong em, em strong { > vertical-align: top; > width: 10px; > } >+.sourcecol { >+ vertical-align: top; >+ width: 100px; >+} > #container { > color : #000; > } >@@ -3006,6 +3010,16 @@ float:left; > padding: 0.1em 0; > } > >+.notesrow label { >+ font-weight: bold; >+} >+.notesrow span { >+ display: block; >+} >+.notesrow textarea { >+ width: 100%; >+} >+ > .thumbnail-shelfbrowser span { > margin: 0px auto; > } >@@ -3030,7 +3044,7 @@ padding: 0.1em 0; > background: #EEEEEE none; > } > >-#overdrive-results { >+#overdrive-results, #pazpar2-results { > font-weight: bold; > padding-left: 1em; > } >@@ -3074,3 +3088,14 @@ padding: 0.1em 0; > width: 35%; > font-size: 111%; > } >+ >+.thumbnail-shelfbrowser span { >+ margin: 0px auto; >+} >+ >+#modal-overlay { >+ background-color: white; >+ border-radius: 5px; >+ max-width: 75%; >+ padding: 15px; >+} >diff --git a/koha-tmpl/opac-tmpl/prog/en/js/externalsearch.js b/koha-tmpl/opac-tmpl/prog/en/js/externalsearch.js >new file mode 100644 >index 0000000..b2fe4a7 >--- /dev/null >+++ b/koha-tmpl/opac-tmpl/prog/en/js/externalsearch.js >@@ -0,0 +1,106 @@ >+if ( typeof KOHA == "undefined" || !KOHA ) { >+ var KOHA = {}; >+} >+ >+KOHA.XSLTGet = ( function() { >+ // Horrible browser hack, but required due to the following hard-to-detect and long-unfixed bug: >+ // https://bugs.webkit.org/show_bug.cgi?id=60276 >+ var isChrome = /Chrome/.test(navigator.userAgent) && /Google Inc/.test(navigator.vendor); >+ var isSafari = /Safari/.test(navigator.userAgent) && /Apple Computer/.test(navigator.vendor); >+ >+ if ( !isChrome && !isSafari ) return $.get; >+ >+ return function( url ) { >+ var result = new jQuery.Deferred(); >+ var basepath = url.match( /(.*\/)*/ )[0]; >+ >+ $.get( url ).done( function( xslDoc ) { >+ var xslImports = xslDoc.getElementsByTagNameNS( 'http://www.w3.org/1999/XSL/Transform', 'import' ); >+ var importsRemaining = xslImports.length; >+ >+ if ( importsRemaining == 0 ) { >+ result.resolve( xslDoc ); >+ return; >+ } >+ >+ $.each( xslImports, function( i, importElem ) { >+ var path = $( importElem ).attr( 'href' ); >+ if ( !/^(\/|https?:)/.test( path ) ) path = basepath + path; >+ >+ KOHA.XSLTGet( path ).done( function( subDoc ) { >+ importsRemaining--; >+ $( importElem ).replaceWith( subDoc.documentElement.childNodes ); >+ >+ if ( importsRemaining == 0 ) result.resolve( xslDoc ); >+ } ).fail( function() { >+ importsRemaining = -1; >+ >+ result.reject(); >+ } ); >+ } ); >+ } ).fail( function() { >+ result.reject(); >+ } ); >+ >+ return result; >+ }; >+} )(); >+ >+KOHA.TransformToFragment = function( xmlDoc, xslDoc ) { >+ if ( window.XSLTProcessor ) { >+ var proc = new XSLTProcessor(); >+ proc.importStylesheet( xslDoc ); >+ proc.setParameter( null, 'showAvailability', false ); >+ return (new XMLSerializer).serializeToString( proc.transformToFragment( xmlDoc, document ) ); >+ } else if ( window.ActiveXObject ) { >+ var xslt = new ActiveXObject( "Msxml2.XSLTemplate" ); >+ xslt.stylesheet = xslDoc; >+ var xslProc = xslt.createProcessor(); >+ xslProc.input = xmlDoc; >+ xslProc.addParameter( 'showAvailability', false ); >+ xslProc.transform(); >+ return xslProc.output; >+ } >+}; >+ >+KOHA.ExternalSearch = ( function() { >+ return { >+ targets: {}, >+ Search: function( q, limit, callback ) { >+ var targetIDs = []; >+ var includedTargets = []; >+ >+ $.each( KOHA.ExternalSearch.targets, function ( url, info ) { >+ if ( !info.disabled ) { >+ includedTargets.push( url ); >+ targetIDs.push( info.id ); >+ } >+ } ); >+ >+ if ( KOHA.ExternalSearch._pz !== undefined ) { >+ afterinit( KOHA.ExternalSearch._pz ); >+ } else { >+ $.get( '/cgi-bin/koha/svc/pazpar2_init', { targets: targetIDs.join(',') }, function( data ) { >+ KOHA.ExternalSearch._pz = new pz2({ >+ sessionId: data.sessionID, >+ onshow: callback, >+ errorhandler: function ( error ) { callback( { error: error } ) }, >+ } ); >+ afterinit( KOHA.ExternalSearch._pz ); >+ } ); >+ } >+ >+ function afterinit( pz ) { >+ pz.search( q, limit, 'relevance:0', 'pz:id=' + includedTargets.join( '|' ) ); >+ } >+ }, >+ Fetch: function( offset, callback ) { >+ var pz = KOHA.ExternalSearch._pz; >+ pz.showCallback = callback; >+ pz.show( offset ); >+ }, >+ GetDetailedRecord: function( recid, callback ) { >+ KOHA.ExternalSearch._pz.record( recid, 0, undefined, { callback: callback } ); >+ }, >+ }; >+} )(); >diff --git a/koha-tmpl/opac-tmpl/prog/en/lib/jquery/plugins/jquery.lightbox_me.js b/koha-tmpl/opac-tmpl/prog/en/lib/jquery/plugins/jquery.lightbox_me.js >new file mode 100644 >index 0000000..effe8a6 >--- /dev/null >+++ b/koha-tmpl/opac-tmpl/prog/en/lib/jquery/plugins/jquery.lightbox_me.js >@@ -0,0 +1,254 @@ >+/* >+* $ lightbox_me >+* By: Buck Wilson >+* Version : 2.3 >+* >+* Licensed under the Apache License, Version 2.0 (the "License"); >+* you may not use this file except in compliance with the License. >+* You may obtain a copy of the License at >+* >+* http://www.apache.org/licenses/LICENSE-2.0 >+* >+* Unless required by applicable law or agreed to in writing, software >+* distributed under the License is distributed on an "AS IS" BASIS, >+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. >+* See the License for the specific language governing permissions and >+* limitations under the License. >+*/ >+ >+ >+(function($) { >+ >+ $.fn.lightbox_me = function(options) { >+ >+ return this.each(function() { >+ >+ var >+ opts = $.extend({}, $.fn.lightbox_me.defaults, options), >+ $overlay = $(), >+ $self = $(this), >+ $iframe = $('<iframe id="foo" style="z-index: ' + (opts.zIndex + 1) + ';border: none; margin: 0; padding: 0; position: absolute; width: 100%; height: 100%; top: 0; left: 0; filter: mask();"/>'), >+ ie6 = ($.browser.msie && $.browser.version < 7); >+ >+ if (opts.showOverlay) { >+ //check if there's an existing overlay, if so, make subequent ones clear >+ var $currentOverlays = $(".js_lb_overlay:visible"); >+ if ($currentOverlays.length > 0){ >+ $overlay = $('<div class="lb_overlay_clear js_lb_overlay"/>'); >+ } else { >+ $overlay = $('<div class="' + opts.classPrefix + '_overlay js_lb_overlay"/>'); >+ } >+ } >+ >+ /*---------------------------------------------------- >+ DOM Building >+ ---------------------------------------------------- */ >+ if (ie6) { >+ var src = /^https/i.test(window.location.href || '') ? 'javascript:false' : 'about:blank'; >+ $iframe.attr('src', src); >+ $('body').append($iframe); >+ } // iframe shim for ie6, to hide select elements >+ $('body').append($self.hide()).append($overlay); >+ >+ >+ /*---------------------------------------------------- >+ Overlay CSS stuffs >+ ---------------------------------------------------- */ >+ >+ // set css of the overlay >+ if (opts.showOverlay) { >+ setOverlayHeight(); // pulled this into a function because it is called on window resize. >+ $overlay.css({ position: 'absolute', width: '100%', top: 0, left: 0, right: 0, bottom: 0, zIndex: (opts.zIndex + 2), display: 'none' }); >+ if (!$overlay.hasClass('lb_overlay_clear')){ >+ $overlay.css(opts.overlayCSS); >+ } >+ } >+ >+ /*---------------------------------------------------- >+ Animate it in. >+ ---------------------------------------------------- */ >+ // >+ if (opts.showOverlay) { >+ $overlay.fadeIn(opts.overlaySpeed, function() { >+ setSelfPosition(); >+ $self[opts.appearEffect](opts.lightboxSpeed, function() { setOverlayHeight(); setSelfPosition(); opts.onLoad()}); >+ }); >+ } else { >+ setSelfPosition(); >+ $self[opts.appearEffect](opts.lightboxSpeed, function() { opts.onLoad()}); >+ } >+ >+ /*---------------------------------------------------- >+ Hide parent if parent specified (parentLightbox should be jquery reference to any parent lightbox) >+ ---------------------------------------------------- */ >+ if (opts.parentLightbox) { >+ opts.parentLightbox.fadeOut(200); >+ } >+ >+ >+ /*---------------------------------------------------- >+ Bind Events >+ ---------------------------------------------------- */ >+ >+ $(window).resize(setOverlayHeight) >+ .resize(setSelfPosition) >+ .scroll(setSelfPosition); >+ >+ $(window).bind('keyup.lightbox_me', observeKeyPress); >+ >+ if (opts.closeClick) { >+ $overlay.click(function(e) { closeLightbox(); e.preventDefault; }); >+ } >+ $self.delegate(opts.closeSelector, "click", function(e) { >+ closeLightbox(); e.preventDefault(); >+ }); >+ $self.bind('close', closeLightbox); >+ $self.bind('reposition', setSelfPosition); >+ >+ >+ >+ /*-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- >+ -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- */ >+ >+ >+ /*---------------------------------------------------- >+ Private Functions >+ ---------------------------------------------------- */ >+ >+ /* Remove or hide all elements */ >+ function closeLightbox() { >+ var s = $self[0].style; >+ if (opts.destroyOnClose) { >+ $self.add($overlay).remove(); >+ } else { >+ $self.add($overlay).hide(); >+ } >+ >+ //show the hidden parent lightbox >+ if (opts.parentLightbox) { >+ opts.parentLightbox.fadeIn(200); >+ } >+ >+ $iframe.remove(); >+ >+ // clean up events. >+ $self.undelegate(opts.closeSelector, "click"); >+ >+ $(window).unbind('reposition', setOverlayHeight); >+ $(window).unbind('reposition', setSelfPosition); >+ $(window).unbind('scroll', setSelfPosition); >+ $(window).unbind('keyup.lightbox_me'); >+ if (ie6) >+ s.removeExpression('top'); >+ opts.onClose(); >+ } >+ >+ >+ /* Function to bind to the window to observe the escape/enter key press */ >+ function observeKeyPress(e) { >+ if((e.keyCode == 27 || (e.DOM_VK_ESCAPE == 27 && e.which==0)) && opts.closeEsc) closeLightbox(); >+ } >+ >+ >+ /* Set the height of the overlay >+ : if the document height is taller than the window, then set the overlay height to the document height. >+ : otherwise, just set overlay height: 100% >+ */ >+ function setOverlayHeight() { >+ if ($(window).height() < $(document).height()) { >+ $overlay.css({height: $(document).height() + 'px'}); >+ $iframe.css({height: $(document).height() + 'px'}); >+ } else { >+ $overlay.css({height: '100%'}); >+ if (ie6) { >+ $('html,body').css('height','100%'); >+ $iframe.css('height', '100%'); >+ } // ie6 hack for height: 100%; TODO: handle this in IE7 >+ } >+ } >+ >+ >+ /* Set the position of the modal'd window ($self) >+ : if $self is taller than the window, then make it absolutely positioned >+ : otherwise fixed >+ */ >+ function setSelfPosition() { >+ var s = $self[0].style; >+ >+ // reset CSS so width is re-calculated for margin-left CSS >+ $self.css({left: '50%', marginLeft: ($self.outerWidth() / 2) * -1, zIndex: (opts.zIndex + 3) }); >+ >+ >+ /* we have to get a little fancy when dealing with height, because lightbox_me >+ is just so fancy. >+ */ >+ >+ // if the height of $self is bigger than the window and self isn't already position absolute >+ if (($self.height() + 80 >= $(window).height()) && ($self.css('position') != 'absolute' || ie6)) { >+ >+ // we are going to make it positioned where the user can see it, but they can still scroll >+ // so the top offset is based on the user's scroll position. >+ var topOffset = $(document).scrollTop() + 40; >+ $self.css({position: 'absolute', top: topOffset + 'px', marginTop: 0}) >+ if (ie6) { >+ s.removeExpression('top'); >+ } >+ } else if ($self.height()+ 80 < $(window).height()) { >+ //if the height is less than the window height, then we're gonna make this thing position: fixed. >+ // in ie6 we're gonna fake it. >+ if (ie6) { >+ s.position = 'absolute'; >+ if (opts.centered) { >+ s.setExpression('top', '(document.documentElement.clientHeight || document.body.clientHeight) / 2 - (this.offsetHeight / 2) + (blah = document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop) + "px"') >+ s.marginTop = 0; >+ } else { >+ var top = (opts.modalCSS && opts.modalCSS.top) ? parseInt(opts.modalCSS.top) : 0; >+ s.setExpression('top', '((blah = document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop) + '+top+') + "px"') >+ } >+ } else { >+ if (opts.centered) { >+ $self.css({ position: 'fixed', top: '50%', marginTop: ($self.outerHeight() / 2) * -1}) >+ } else { >+ $self.css({ position: 'fixed'}).css(opts.modalCSS); >+ } >+ >+ } >+ } >+ } >+ >+ }); >+ >+ >+ >+ }; >+ >+ $.fn.lightbox_me.defaults = { >+ >+ // animation >+ appearEffect: "fadeIn", >+ appearEase: "", >+ overlaySpeed: 250, >+ lightboxSpeed: 300, >+ >+ // close >+ closeSelector: ".close", >+ closeClick: true, >+ closeEsc: true, >+ >+ // behavior >+ destroyOnClose: false, >+ showOverlay: true, >+ parentLightbox: false, >+ >+ // callbacks >+ onLoad: function() {}, >+ onClose: function() {}, >+ >+ // style >+ classPrefix: 'lb', >+ zIndex: 999, >+ centered: false, >+ modalCSS: {top: '40px'}, >+ overlayCSS: {background: 'black', opacity: .3} >+ } >+})(jQuery); >\ No newline at end of file >diff --git a/koha-tmpl/opac-tmpl/prog/en/modules/opac-external-search.tt b/koha-tmpl/opac-tmpl/prog/en/modules/opac-external-search.tt >new file mode 100644 >index 0000000..5308ccd >--- /dev/null >+++ b/koha-tmpl/opac-tmpl/prog/en/modules/opac-external-search.tt >@@ -0,0 +1,240 @@ >+[% INCLUDE 'doc-head-open.inc' %] >+[% IF ( LibraryNameTitle ) %][% LibraryNameTitle %][% ELSE %]Koha online[% END %] catalog › External search for '[% q | html %]' >+[% INCLUDE 'doc-head-close.inc' %] >+<script type="text/javascript" src="[% themelang %]/lib/jquery/plugins/jquery.lightbox_me.js"></script> >+<script type="text/javascript" src="/opac-tmpl/lib/pz2.js"></script> >+<script type="text/javascript" src="[% themelang %]/js/externalsearch.js"></script> >+<script type="text/javascript"> >+var querystring = "[% q |replace( "'", "\'" ) |replace( '\n', '\\n' ) |replace( '\r', '\\r' ) |html %]"; >+var results_per_page = [% OPACnumSearchResults %]; >+KOHA.ExternalSearch.targets = { >+ [% FOREACH target IN external_search_targets %] >+ '[% target.host %]:[% target.port %]/[% target.db %]': { >+ name: '[% target.name %]', >+ syntax: '[% target.syntax %]', >+ }, >+ [% END %] >+}; >+ >+var xsltResultStylesheets = { >+ [% FOREACH stylesheet IN xslt_result_stylesheets %] >+ '[% stylesheet.syntax %]': KOHA.XSLTGet( '[% stylesheet.url %]' ), >+ [% END %] >+}; >+ >+var xsltDetailStylesheets = { >+ [% FOREACH stylesheet IN xslt_detail_stylesheets %] >+ '[% stylesheet.syntax %]': KOHA.XSLTGet( '[% stylesheet.url %]' ), >+ [% END %] >+}; >+ >+var recordCache = {}; >+var resultRenderCache = {}; >+ >+function showResult( syntax, recid ) { >+ if ( recordCache[ recid ] ) { >+ done( recordCache[ recid ] ); >+ } else { >+ KOHA.ExternalSearch.GetDetailedRecord( recid, function( record ) { >+ done( recordCache[ recid ] = record.xmlDoc ); >+ } ); >+ } >+ >+ function done( record ) { >+ xsltResultStylesheets[ syntax ].done( function( xslDoc ) { >+ var fragment = resultRenderCache[ recid ] = KOHA.TransformToFragment( record, xslDoc ); >+ var $tr = $( '#results tr' ).filter( function() { return $( this ).data( 'recid' ) == recid } ); >+ $tr.find( '.info' ).html( fragment ); >+ $tr.find( 'a' ).attr( 'href', '#' ).click( function() { >+ showDetail( syntax, recid ); >+ >+ return false; >+ } ); >+ } ); >+ } >+} >+ >+function showDetail( syntax, recid ) { >+ var record = recordCache[ recid ]; >+ console.info((new XMLSerializer).serializeToString( record )); >+ >+ xsltDetailStylesheets[ syntax ].done( function( xslDoc ) { >+ var fragment = KOHA.TransformToFragment( record, xslDoc ); >+ >+ $( '#modal-overlay' ).html( fragment ).lightbox_me( { >+ centered: true, >+ } ); >+ } ); >+} >+ >+function search( offset, reset_search ) { >+ $( '#pazpar2-status' ).html( _("Searching external targets...") + '<img class="throbber" src="/opac-tmpl/lib/jquery/plugins/themes/classic/throbber.gif" />' ); >+ >+ if ( reset_search ) { >+ KOHA.ExternalSearch.Search( querystring, results_per_page, callback ); >+ } else { >+ KOHA.ExternalSearch.Fetch( offset, callback ); >+ } >+ >+ function callback( data ) { >+ if ( data.error ) { >+ $( '#pazpar2-status' ).html( '<strong class="unavailable">' + _("Error searching external targets.") + '</strong>' ); >+ return; >+ } >+ >+ if ( !data.total ) { >+ $( '#pazpar2-status' ).html( '<strong>' + _("No results found in the external targets.") + '</strong>' ); >+ return; >+ } >+ >+ $( '#results tbody' ).empty(); >+ >+ $( '#pazpar2-status' ).html( '<strong>' + _("Found __RESULTS__ results in __TARGETS__ external targets.").replace('__RESULTS__', data.total).replace('__TARGETS__', $( '#targets-facet input:checked' ).length ); >+ >+ for ( var i = 0; data.hits[i]; i++ ) { >+ var hit = data.hits[i]; >+ var results = []; >+ var recordSyntax = KOHA.ExternalSearch.targets[ hit.location[0]['@id'] ].syntax; >+ >+ results.push( '<tr>' ); >+ >+ results.push( '<td class="sourcecol">', hit.location[0]['@name'], '</td>' ); >+ >+ results.push( '<td class="info">' ); >+ >+ if ( resultRenderCache[ hit.recid[0] ] ) { >+ results.push( resultRenderCache[ hit.recid[0] ] ); >+ } else { >+ results.push( hit['md-work-title'] ? hit['md-work-title'][0] : _("Loading...") ); >+ showResult( recordSyntax, hit.recid[0] ); >+ } >+ >+ results.push( '</td>' ); >+ >+ results.push( '</tr>' ); >+ var $tr = $( results.join( '' ) ); >+ $tr.data( 'recid', hit.recid[0] ); >+ $( '#results tbody' ).append( $tr ); >+ >+ ( function( hit, recordSyntax ) { >+ $tr.find( 'a' ).attr( 'href', '#' ).click( function() { >+ showDetail( recordSyntax, hit.recid[0] ); >+ >+ return false; >+ } ); >+ } )( hit, recordSyntax ); >+ } >+ >+ $( '#results tr:odd' ).addClass( 'highlight' ); >+ >+ var pages = []; >+ var cur_page = data.start / results_per_page; >+ var max_page = Math.floor( data.total / results_per_page ); >+ >+ if ( cur_page != 0 ) { >+ pages.push( '<a class="nav" href="#" data-offset="' + (offset - results_per_page) + '"><< ' + _("Previous") + '</a>' ); >+ } >+ >+ for ( var page = Math.max( 0, cur_page - 9 ); page <= Math.min( max_page, cur_page + 9 ); page++ ) { >+ if ( page == cur_page ) { >+ pages.push( ' <span class="current">' + ( page + 1 ) + '</span>' ); >+ } else { >+ pages.push( ' <a class="nav" href="#" data-offset="' + ( page * results_per_page ) + '">' + ( page + 1 ) + '</a>' ); >+ } >+ } >+ >+ if ( cur_page < max_page ) { >+ pages.push( ' <a class="nav" href="#" data-offset="' + (offset + results_per_page) + '">' + _("Next") + ' >></a>' ); >+ } >+ >+ if ( pages.length > 1 ) $( '#top-pages, #bottom-pages' ).find( '.pages' ).html( pages.join( '' ) ); >+ } >+} >+ >+$( document ).ready( function() { >+ $( '#breadcrumbs p' ) >+ .append( ' ' ) >+ .append( '<span id="pazpar2-status"></span>' ); >+ >+ $( document ).on( 'click', 'a.nav', function() { >+ search( $( this ).data( 'offset' ) ); >+ return false; >+ }); >+ >+ var reSearchTimeout; >+ >+ $( '#targets-facet input' ).each( function() { >+ $( this ).click( function() { >+ KOHA.ExternalSearch.targets[ $( this ).data( 'url' ) ].disabled = !this.checked; >+ >+ if ( reSearchTimeout ) clearTimeout( reSearchTimeout ); >+ >+ reSearchTimeout = setTimeout( function() { >+ if ( $( '#targets-facet input:checked' ).length ) search( 0, true ); >+ }, 1000 ); >+ } ); >+ >+ KOHA.ExternalSearch.targets[ $( this ).data( 'url' ) ].disabled = !this.checked; >+ } ); >+ >+ search( 0, true ); >+} ); >+</script> >+<style> >+.actions a.addtocart { >+ display: inline; >+} >+</style> >+</head> >+<body> >+<div id="doc3" class="yui-t1"> >+ >+<div id="bd"> >+ [% INCLUDE 'masthead.inc' %] >+ >+ <h1>External search for '[% q | html %]'</h1> >+ <div id="breadcrumbs"> >+ <p></p> >+ </div> >+ >+ <div id="yui-main"><div class="yui-b searchresults"> >+ <div id="top-pages"> >+ <div class="pages"> >+ </div> >+ </div> >+ <table id="results"> >+ <tbody> >+ </tbody> >+ </table> >+ <div id="bottom-pages"> >+ <div class="pages"> >+ </div> >+ </div> >+ </div></div> >+ >+ <div class="yui-b"><div id="facetcontainer" class="container"> >+ <div id="search-facets"> >+ >+ <h4>Refine your search</h4> >+ >+ <ul> >+ <li id="targets-facet"> >+ Targets >+ <ul> >+ [% FOREACH target = external_search_targets %] >+ <li> >+ <input data-url="[% target.host %]:[% target.port %]/[% target.db %]" type="checkbox" id="target-[% loop.index %]" checked /> >+ <label for="target-[% loop.index %]">[% target.name %] >+ </li> >+ [% END %] >+ </ul> >+ </li> >+ </ul> >+ </div> >+ </div></div> >+</div> >+ >+</div> >+ >+<div id="modal-overlay" style="display: none"></div> >+[% INCLUDE 'opac-bottom.inc' %] >diff --git a/koha-tmpl/opac-tmpl/prog/en/modules/opac-results.tt b/koha-tmpl/opac-tmpl/prog/en/modules/opac-results.tt >index 9235b2b..89e1d38 100644 >--- a/koha-tmpl/opac-tmpl/prog/en/modules/opac-results.tt >+++ b/koha-tmpl/opac-tmpl/prog/en/modules/opac-results.tt >@@ -16,7 +16,11 @@ > <link rel="stylesheet" type="text/css" href="[% themelang %]/css/jquery.rating.css" /> > [% END %] > >-<script type="text/javascript" src="[% themelang %]/js/overdrive.js"></script> >+[% IF ( OverDriveEnabled ) %]<script type="text/javascript" src="[% themelang %]/js/overdrive.js"></script>[% END %] >+[% IF ( Koha.Preference('OPACSearchExternalTargets') ) %] >+<script type="text/javascript" src="/opac-tmpl/lib/pz2.js"></script> >+<script type="text/javascript" src="[% themelang %]/js/externalsearch.js"></script> >+[% END %] > <script type="text/javascript" src="[% themelang %]/lib/jquery/plugins/jquery.checkboxes.min.js"></script> > [% IF ( OpacHighlightedWords ) %]<script type="text/javascript" src="[% themelang %]/lib/jquery/plugins/jquery.highlight-3.js"></script> > [% END %]<script type="text/javascript"> >@@ -266,24 +270,59 @@ $(document).ready(function(){ > $("#highlight_toggle_on" ).hide().click(function() {highlightOn() ;}); > $("#highlight_toggle_off").show().click(function() {highlightOff();}); > [% END %] >+ function StartExternalSearch(name, text) { >+ $( '#breadcrumbs p' ) >+ .eq( 0 ) >+ .append( ' <span id="' + name + '-results">' + text + '... <img class="throbber" src="/opac-tmpl/lib/jquery/plugins/themes/classic/throbber.gif" /></span>' ); >+ } >+ function FailExternalSearch( name, text ) { >+ $( '#' + name + '-results' ).html( text ); >+ } >+ function FinishExternalSearch( name, text, numItems, url ) { >+ if ( numItems ) { >+ $( '#' + name + '-results' ).html( text.replace( '__LINK__', '<a href="' + url + '">' + numItems + _(" results") + '</a>' ) ); >+ } else { >+ $( '#' + name + '-results' ).remove(); >+ } >+ } >+ > [% IF ( OverDriveEnabled ) %] >- var $overdrive_results = $( '<span id="overdrive-results">' + _( 'Searching OverDrive...' ) + ' <img class="throbber" src="/opac-tmpl/lib/jquery/plugins/themes/classic/throbber.gif" /></span>' ); >- $( '#breadcrumbs p' ).eq(0) >- .append( ' ' ) >- .append( $overdrive_results ); >+ StartExternalSearch( 'overdrive', _("Searching OverDrive") ); > KOHA.OverDrive.Search( "[% OverDriveLibraryID %]", querystring, 1, 0, function( data ) { > if ( data.error ) { >- $overdrive_results.html( _( 'Error searching OverDrive collection' ) ); >+ FailExternalSearch( 'overdrive', _("Error searching OverDrive collection") ); > return; > } > >- if ( data.totalItems ) { >- $overdrive_results.html( _( 'Found' ) + ' <a href="/cgi-bin/koha/opac-overdrive-search.pl?q=' + escape( querystring ) + '">' + data.totalItems + ' ' + _( 'results' ) + '</a> ' + _( 'in OverDrive collection' ) ); >- } else { >- $overdrive_results.remove(); >+ FinishExternalSearch( 'overdrive', _("Found __LINK__ in OverDrive collection"), data.totalItems, '/cgi-bin/koha/opac-overdrive-search.pl?q=' + escape( querystring ) ); >+ } ); >+ [% END %] >+ >+ [% IF ( OPACSearchExternalTargets ) %] >+ KOHA.ExternalSearch.targets = { >+ [% FOREACH target IN external_search_targets %] >+ '[% target.host %]:[% target.port %]/[% target.db %]': { >+ id: '[% target.target_id %]', >+ name: '[% target.name %]', >+ syntax: '[% target.syntax %]', >+ }, >+ [% END %] >+ }; >+ var num_targets = 0; $.each( KOHA.ExternalSearch.targets, function() { num_targets++ } ); >+ var first_succeeded; >+ >+ StartExternalSearch( 'pazpar2', _("Searching external targets") ); >+ KOHA.ExternalSearch.Search( querystring, 1, function( data ) { >+ if ( data.error ) { >+ if ( !first_succeeded ) FailExternalSearch( 'pazpar2', _("Error searching external targets") ); >+ return; > } >+ >+ first_succeeded = true; >+ FinishExternalSearch( 'pazpar2', _("Found __LINK__ in ") + num_targets + _(" external targets"), data.total, '/cgi-bin/koha/opac-external-search.pl?q=' + escape( querystring ) ); > } ); > [% END %] >+ > [% END %] > > [% IF ( TagsInputEnabled && loggedinusername ) %] >diff --git a/koha-tmpl/opac-tmpl/prog/en/xslt/MARC21slim2OPACResults.xsl b/koha-tmpl/opac-tmpl/prog/en/xslt/MARC21slim2OPACResults.xsl >index 24ffa85..f71cdeb 100644 >--- a/koha-tmpl/opac-tmpl/prog/en/xslt/MARC21slim2OPACResults.xsl >+++ b/koha-tmpl/opac-tmpl/prog/en/xslt/MARC21slim2OPACResults.xsl >@@ -5,11 +5,13 @@ > xmlns:marc="http://www.loc.gov/MARC21/slim" > xmlns:items="http://www.koha-community.org/items" > xmlns:xsl="http://www.w3.org/1999/XSL/Transform" >+ xmlns="http://www.w3.org/1999/xhtml" > exclude-result-prefixes="marc items"> > <xsl:import href="MARC21slimUtils.xsl"/> > <xsl:output method = "html" indent="yes" omit-xml-declaration = "yes" encoding="UTF-8"/> > <xsl:key name="item-by-status" match="items:item" use="items:status"/> > <xsl:key name="item-by-status-and-branch" match="items:item" use="concat(items:status, ' ', items:homebranch)"/> >+ <xsl:param name="showAvailability" select="true()"/> > > <xsl:template match="/"> > <xsl:apply-templates/> >@@ -1027,6 +1029,7 @@ > </xsl:for-each> > </span> > </xsl:if> >+ <xsl:if test="$showAvailability"> > <span class="results_summary availability"> > <span class="label">Availability: </span> > <xsl:choose> >@@ -1035,7 +1038,7 @@ > <xsl:when test="string-length($AlternateHoldingsField)=3 and marc:datafield[@tag=$AlternateHoldingsField]"> > <xsl:variable name="AlternateHoldingsCount" select="count(marc:datafield[@tag=$AlternateHoldingsField])"/> > <xsl:for-each select="marc:datafield[@tag=$AlternateHoldingsField][1]"> >- <xsl:call-template select="marc:datafield[@tag=$AlternateHoldingsField]" name="subfieldSelect"> >+ <xsl:call-template name="subfieldSelect"> > <xsl:with-param name="codes"><xsl:value-of select="$AlternateHoldingsSubfields"/></xsl:with-param> > <xsl:with-param name="delimeter"><xsl:value-of select="$AlternateHoldingsSeparator"/></xsl:with-param> > </xsl:call-template> >@@ -1145,6 +1148,7 @@ > <xsl:text>). </xsl:text> </span> > </xsl:if> > </span> >+ </xsl:if> > <xsl:choose> > <xsl:when test="($OPACItemLocation='location' or $OPACItemLocation='ccode') and (count(key('item-by-status', 'available'))!=0 or count(key('item-by-status', 'reference'))!=0)"> > <span class="results_summary" id="location"> >diff --git a/koha-tmpl/opac-tmpl/prog/en/xslt/MARC21slimUtils.xsl b/koha-tmpl/opac-tmpl/prog/en/xslt/MARC21slimUtils.xsl >index df7c450..1f18054 100644 >--- a/koha-tmpl/opac-tmpl/prog/en/xslt/MARC21slimUtils.xsl >+++ b/koha-tmpl/opac-tmpl/prog/en/xslt/MARC21slimUtils.xsl >@@ -1,6 +1,6 @@ > <?xml version='1.0'?> > <!DOCTYPE stylesheet [<!ENTITY nbsp " " >]> >-<xsl:stylesheet version="1.0" xmlns:marc="http://www.loc.gov/MARC21/slim" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> >+<xsl:stylesheet version="1.0" xmlns:marc="http://www.loc.gov/MARC21/slim" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns="http://www.w3.org/1999/xhtml"> > <xsl:template name="datafield"> > <xsl:param name="tag"/> > <xsl:param name="ind1"><xsl:text> </xsl:text></xsl:param> >diff --git a/koha-tmpl/opac-tmpl/prog/en/xslt/UNIMARCslim2OPACResults.xsl b/koha-tmpl/opac-tmpl/prog/en/xslt/UNIMARCslim2OPACResults.xsl >index 9dde821..aaac50c 100644 >--- a/koha-tmpl/opac-tmpl/prog/en/xslt/UNIMARCslim2OPACResults.xsl >+++ b/koha-tmpl/opac-tmpl/prog/en/xslt/UNIMARCslim2OPACResults.xsl >@@ -6,12 +6,14 @@ > xmlns:marc="http://www.loc.gov/MARC21/slim" > xmlns:items="http://www.koha-community.org/items" > xmlns:xsl="http://www.w3.org/1999/XSL/Transform" >+ xmlns="http://www.w3.org/1999/xhtml" > exclude-result-prefixes="marc items"> > > <xsl:import href="UNIMARCslimUtils.xsl"/> > <xsl:output method = "html" indent="yes" omit-xml-declaration = "yes" encoding="UTF-8"/> > <xsl:key name="item-by-status" match="items:item" use="items:status"/> > <xsl:key name="item-by-status-and-branch" match="items:item" use="concat(items:status, ' ', items:homebranch)"/> >+<xsl:param name="showAvailability" select="true()"/> > > <xsl:template match="/"> > <xsl:apply-templates/> >@@ -99,6 +101,7 @@ > > <xsl:call-template name="tag_215" /> > >+ <xsl:if test="$showAvailability"> > <span class="results_summary availability"> > <span class="label">Availability: </span> > <xsl:choose> >@@ -247,6 +250,7 @@ > </span> > </xsl:if> > </span> >+ </xsl:if> > > </xsl:template> > >diff --git a/opac/opac-external-search.pl b/opac/opac-external-search.pl >new file mode 100755 >index 0000000..423dc0d >--- /dev/null >+++ b/opac/opac-external-search.pl >@@ -0,0 +1,62 @@ >+#!/usr/bin/perl >+ >+# Copyright 2013 ByWater Solutions >+# >+# This file is part of Koha. >+# >+# Koha is free software; you can redistribute it and/or modify it >+# under the terms of the GNU General Public License as published by >+# the Free Software Foundation; either version 3 of the License, or >+# (at your option) any later version. >+# >+# Koha is distributed in the hope that it will be useful, but >+# WITHOUT ANY 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, see <http://www.gnu.org/licenses>. >+ >+use Modern::Perl; >+ >+use CGI; >+ >+use C4::Auth qw(:DEFAULT get_session); >+use C4::Output; >+use C4::Search qw( GetExternalSearchTargets ); >+use C4::XSLT qw( XSLTGetFilename ); >+ >+my $cgi = new CGI; >+ >+# Getting the template and auth >+my ($template, $loggedinuser, $cookie) >+= get_template_and_user({template_name => "opac-external-search.tmpl", >+ query => $cgi, >+ type => "opac", >+ authnotrequired => 1, >+ flagsrequired => {borrowers => 1}, >+ debug => 1, >+ }); >+ >+$template->{VARS}->{q} = $cgi->param('q'); >+$template->{VARS}->{limit} = C4::Context->preference('OPACnumSearchResults') || 20; >+$template->{VARS}->{OPACnumSearchResults} = C4::Context->preference('OPACnumSearchResults') || 20; >+$template->{VARS}->{external_search_targets} = GetExternalSearchTargets( C4::Context->userenv ? C4::Context->userenv->{branch} : '' ); >+ >+my @xsltResultStylesheets; >+my @xsltDetailStylesheets; >+ >+foreach my $syntax ( qw( MARC21 UNIMARC NORMARC ) ) { >+ if ( XSLTGetFilename( $syntax, 'OPACXSLTResultsDisplay' ) =~ m,/opac-tmpl/.*|^https:?.*, ) { >+ push @xsltResultStylesheets, { syntax => $syntax, url => $& }; >+ } >+ >+ if ( XSLTGetFilename( $syntax, 'OPACXSLTDetailsDisplay' ) =~ m,/opac-tmpl/.*|^https:?.*, ) { >+ push @xsltDetailStylesheets, { syntax => $syntax, url => $& }; >+ } >+} >+ >+$template->{VARS}->{xslt_result_stylesheets} = \@xsltResultStylesheets; >+$template->{VARS}->{xslt_detail_stylesheets} = \@xsltDetailStylesheets; >+ >+output_html_with_http_headers $cgi, $cookie, $template->output; >diff --git a/opac/opac-search.pl b/opac/opac-search.pl >index 617d67e..42e3740 100755 >--- a/opac/opac-search.pl >+++ b/opac/opac-search.pl >@@ -74,6 +74,9 @@ if ( $branch_group_limit ) { > -name => 'multibranchlimit', > -values => substr($branch_group_limit, 17) > ); >+ } elsif ( $branch_group_limit eq '@overdrive' ) { >+ print $cgi->redirect( '/cgi-bin/koha/opac-overdrive-search.pl?q=' . join( '+', $cgi->param( 'q' ) ) ); >+ exit; > } else { > $cgi->append( > -name => 'limit', >@@ -879,10 +882,11 @@ $template->{VARS}->{IDreamBooksReviews} = C4::Context->preference('IDreamBooksRe > $template->{VARS}->{IDreamBooksReadometer} = C4::Context->preference('IDreamBooksReadometer'); > $template->{VARS}->{IDreamBooksResults} = C4::Context->preference('IDreamBooksResults'); > >-if ($offset == 0 && IsOverDriveEnabled()) { >- $template->param(OverDriveEnabled => 1); >- $template->param(OverDriveLibraryID => C4::Context->preference('OverDriveLibraryID')); >-} >+$template->{VARS}->{OPACSearchExternalTargets} = C4::Context->preference('OPACSearchExternalTargets'); >+$template->{VARS}->{external_search_targets} = GetExternalSearchTargets( C4::Context->userenv ? C4::Context->userenv->{branch} : '' ); >+ >+$template->{VARS}->{OverDriveLibraryID} = C4::Context->preference('OverDriveLibraryID'); >+$template->{VARS}->{OverDriveEnabled} = ($offset == 0 && IsOverDriveEnabled()); > > $template->param( borrowernumber => $borrowernumber); > output_with_http_headers $cgi, $cookie, $template->output, $content_type; >diff --git a/opac/svc/pazpar2_init b/opac/svc/pazpar2_init >new file mode 100755 >index 0000000..3c9fc6d >--- /dev/null >+++ b/opac/svc/pazpar2_init >@@ -0,0 +1,100 @@ >+#!/usr/bin/perl >+ >+# Copyright 2013 ByWater Solutions >+# >+# This file is part of Koha. >+# >+# Koha is free software; you can redistribute it and/or modify it >+# under the terms of the GNU General Public License as published by >+# the Free Software Foundation; either version 3 of the License, or >+# (at your option) any later version. >+# >+# Koha is distributed in the hope that it will be useful, but >+# WITHOUT ANY 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, see <http://www.gnu.org/licenses>. >+ >+=head1 NAME >+ >+svc/pazpar2_init: Initialize a Pazpar2 session >+ >+=head1 SYNOPSIS >+ >+svc/pazpar2_init -> ../pazpar2/search.pz2?command=init&... >+ >+=head1 DESCRIPTION >+ >+This services connects to pazpar2 and authenticates connections on behalf of >+client-side search code. The proxy is necessary to prevent exposing >+authentication information. >+ >+=cut >+ >+use strict; >+use warnings; >+ >+use CGI qw(-oldstyle_urls); >+use HTTP::Request::Common; >+use JSON; >+use URI; >+use XML::Simple; >+ >+use C4::Context; >+use C4::Search; >+use C4::Output; >+ >+my $dbh = C4::Context->dbh; >+my $query = new CGI; >+ >+my %init_opts; >+ >+my $targets = GetExternalSearchTargets( C4::Context->userenv ? C4::Context->userenv->{branch} : '' ); >+ >+foreach my $target ( @$targets ) { >+ my $target_url = $target->{'host'} . ':' . $target->{'port'} . '/' . $target->{'db'}; >+ $init_opts{ 'pz:name[' . $target_url . ']' } = $target->{'name'}; >+ $init_opts{ 'pz:queryencoding[' . $target_url . ']' } = $target->{'encoding'}; >+ $init_opts{ 'pz:xslt[' . $target_url . ']' } = lc( $target->{'syntax'} ) . '-work-groups.xsl'; >+ $init_opts{ 'pz:requestsyntax[' . $target_url . ']' } = $target->{'syntax'}; >+ $init_opts{ 'pz:nativesyntax[' . $target_url . ']' } = 'iso2709'; >+ >+ if ( $target->{'userid'} ) { >+ if ( $target->{'password'} ) { >+ $init_opts{ 'pz:authentication[' . $target_url . ']' } = $target->{'userid'} . '/' . $target->{'password'}; >+ } else { >+ $init_opts{ 'pz:authentication[' . $target_url . ']' } = $target->{'userid'}; >+ } >+ } >+} >+ >+my $uri = 'http://' . C4::Context->preference( 'OPACBaseURL' ) . "/pazpar2/search.pz2"; >+ >+my $request = HTTP::Request::Common::POST( $uri, [ command => 'init', %init_opts ] ); >+ >+my $ua = LWP::UserAgent->new( "Koha " . C4::Context->KOHAVERSION ); >+ >+my $response = $ua->request( $request ) ; >+if ( !$response->is_success ) { >+ print $query->header( >+ -status => '500 Internal Server Error' >+ ); >+ >+ warn "Pazpar2 init failed: " . $response->message; >+ my $content = to_json({ >+ error => 'Could not connect to Pazpar2', >+ }); >+ output_with_http_headers $query, undef, $content, 'json', '500 Internal Server Error'; >+ >+ exit; >+} else { >+ my $xs = XML::Simple->new; >+ my $data = $xs->XMLin( $response->content ); >+ >+ my $content = to_json({ >+ sessionID => $data->{'session'} >+ }); >+ output_with_http_headers $query, undef, $content, 'json', '200 OK'; >+} >diff --git a/rewrite-config.PL b/rewrite-config.PL >index ea69c94..5467bf5 100644 >--- a/rewrite-config.PL >+++ b/rewrite-config.PL >@@ -136,8 +136,8 @@ $prefix = $ENV{'INSTALL_BASE'} || "/usr"; > "__INSTALL_BASE__" => '/usr/share/koha', > "__INSTALL_SRU__" => 'yes', > "__INSTALL_PAZPAR2__" => 'no', >- "__PAZPAR2_TOGGLE_XML_PRE__" => '<!--', >- "__PAZPAR2_TOGGLE_XML_POST__" => '-->', >+ "__PAZPAR2_TOGGLE_HTTPD_PRE__" => '<IfDefine PAZPAR2_IS_DISABLED>', >+ "__PAZPAR2_TOGGLE_HTTPD_POST__" => '</IfDefine>', > "__AUTH_INDEX_MODE__" => 'grs1', > "__BIB_INDEX_MODE__" => 'grs1', > "__RUN_DATABASE_TESTS__" => 'no', >@@ -157,8 +157,8 @@ foreach $key (keys %configuration) { > # munge commenting out the PazPar2 mergeserver > # entry in koha-conf.xml if necessary > if ($configuration{'__INSTALL_PAZPAR2__'} eq 'yes') { >- $configuration{'__PAZPAR2_TOGGLE_XML_PRE__'} = ''; >- $configuration{'__PAZPAR2_TOGGLE_XML_POST__'} = ''; >+ $configuration{'__PAZPAR2_TOGGLE_HTTPD_PRE__'} = ''; >+ $configuration{'__PAZPAR2_TOGGLE_HTTPD_POST__'} = ''; > } > > $fname = $ARGV[0]; >diff --git a/t/XSLT.t b/t/XSLT.t >index 64cd597..d9aaf24 100755 >--- a/t/XSLT.t >+++ b/t/XSLT.t >@@ -6,7 +6,11 @@ > use strict; > use warnings; > >-use Test::More tests => 8; >+use C4::Context; >+use C4::Templates; >+use Test::More tests => 13; >+use Test::MockModule; >+use File::Basename qw/dirname/; > use File::Temp; > use File::Path qw/make_path/; > >@@ -14,36 +18,96 @@ BEGIN { > use_ok('C4::XSLT'); > } > >-my $dir = File::Temp->newdir(); >+my $opacdir = File::Temp->newdir(); >+my $staffdir = File::Temp->newdir(); > my @themes = ('prog', 'test'); > my @langs = ('en', 'es-ES'); > >+sub make_test_file { >+ my ( $filename, $contents ) = @_; >+ >+ make_path(dirname($filename)); >+ open my $fh, '>', $filename or die "Could not create test file: $filename"; >+ print $fh $contents; >+ close $fh; >+} >+ > # create temporary files to be tested later > foreach my $theme (@themes) { > foreach my $lang (@langs) { >- make_path("$dir/$theme/$lang/xslt"); >- open my $fh, '>', "$dir/$theme/$lang/xslt/my_file.xslt"; >- print $fh "Theme $theme, language $lang"; >- close $fh; >+ foreach my $dir ($opacdir, $staffdir) { >+ make_test_file( "$dir/$theme/$lang/xslt/my_file.xslt", "Theme $theme, language $lang" ); >+ make_test_file( "$dir/$theme/$lang/xslt/MARC21slim2intranetDetail.xsl", "Theme $theme, language $lang, MARC21slim2intranetDetail" ); >+ make_test_file( "$dir/$theme/$lang/xslt/test_en.xsl", "Theme $theme, language $lang, test_en" ); >+ make_test_file( "$dir/$theme/$lang/xslt/UNIMARCslim2OPACDetail.xsl", "Theme $theme, language $lang, UNIMARCslim2OPACDetail" ); >+ make_test_file( "$dir/$theme/$lang/xslt/nondefault_test.xsl", "Theme $theme, language $lang, nondefault_test" ); >+ make_test_file( "$dir/$theme/$lang/xslt/MARC21slim2OPACResults.xsl", "Theme $theme, language $lang, MARC21slim2OPACResults" ); >+ } > } > } > >-sub find_and_slurp { >+sub find_and_slurp_default { > my ($dir, $theme, $lang) = @_; > > my $filename = C4::XSLT::_get_best_default_xslt_filename($dir, $theme, $lang, 'my_file.xslt'); >- open my $fh, '<', $filename; >+ open my $fh, '<', $filename or return "Could not open: $filename"; > my $str = <$fh>; > close $fh; > return $str; > } > >+sub find_and_slurp { >+ my ($marcflavour, $xslsyspref) = @_; >+ >+ my $filename = C4::XSLT::XSLTGetFilename( $marcflavour, $xslsyspref ); >+ open my $fh, '<', $filename or return "Could not open: $filename"; >+ my $str = <$fh>; >+ close $fh; >+ return $str; >+} >+ >+my $module_context = new Test::MockModule('C4::Context'); >+$module_context->mock( >+ 'config', >+ sub { >+ my ( $self, $var ) = @_; >+ my %predefs = ( >+ opachtdocs => $opacdir, >+ intrahtdocs => $staffdir, >+ ); >+ >+ return $predefs{$var} || $module_context->original('config')->(@_); >+ } >+); >+$module_context->mock( >+ 'preference', >+ sub { >+ my ( $self, $var ) = @_; >+ my %predefs = ( >+ template => 'prog', >+ marcflavour => 'MARC21', >+ opacthemes => 'test', >+ XSLTDetailsDisplay => 'default', >+ XSLTResultsDisplay => "$staffdir/prog/en/xslt/test_en.xsl", >+ OPACXSLTDetailsDisplay => "$opacdir/test/en/xslt/nondefault_test.xsl", >+ OPACXSLTResultsDisplay => '"default"', >+ ); >+ >+ return $predefs{$var} || $module_context->original('preference')->(@_); >+ } >+); >+ > # These tests verify that we're finding the right XSLT file when present, > # and falling back to the right XSLT file when an exact match is not present. >-is(find_and_slurp($dir, 'test', 'en' ), 'Theme test, language en', 'Found test/en'); >-is(find_and_slurp($dir, 'test', 'es-ES'), 'Theme test, language es-ES', 'Found test/es-ES'); >-is(find_and_slurp($dir, 'prog', 'en', ), 'Theme prog, language en', 'Found test/en'); >-is(find_and_slurp($dir, 'prog', 'es-ES'), 'Theme prog, language es-ES', 'Found test/es-ES'); >-is(find_and_slurp($dir, 'test', 'fr-FR'), 'Theme test, language en', 'Fell back to test/en for test/fr-FR'); >-is(find_and_slurp($dir, 'nope', 'es-ES'), 'Theme prog, language es-ES', 'Fell back to prog/es-ES for nope/es-ES'); >-is(find_and_slurp($dir, 'nope', 'fr-FR'), 'Theme prog, language en', 'Fell back to prog/en for nope/fr-FR'); >+is(find_and_slurp_default($opacdir, 'test', 'en' ), 'Theme test, language en', 'Found test/en'); >+is(find_and_slurp_default($opacdir, 'test', 'es-ES'), 'Theme test, language es-ES', 'Found test/es-ES'); >+is(find_and_slurp_default($opacdir, 'prog', 'en', ), 'Theme prog, language en', 'Found test/en'); >+is(find_and_slurp_default($opacdir, 'prog', 'es-ES'), 'Theme prog, language es-ES', 'Found test/es-ES'); >+is(find_and_slurp_default($opacdir, 'test', 'fr-FR'), 'Theme test, language en', 'Fell back to test/en for test/fr-FR'); >+is(find_and_slurp_default($opacdir, 'nope', 'es-ES'), 'Theme prog, language es-ES', 'Fell back to prog/es-ES for nope/es-ES'); >+is(find_and_slurp_default($opacdir, 'nope', 'fr-FR'), 'Theme prog, language en', 'Fell back to prog/en for nope/fr-FR'); >+is(find_and_slurp('MARC21', 'XSLTDetailsDisplay'), 'Theme prog, language en, MARC21slim2intranetDetail', 'Used default for staff/details'); >+is(find_and_slurp('MARC21', 'XSLTResultsDisplay'), 'Theme prog, language en, test_en', 'Used non-default with langcode for staff/results'); >+is(find_and_slurp('MARC21', 'OPACXSLTDetailsDisplay'), 'Theme test, language en, nondefault_test', 'not-prog: Used non-default for local marcflavour and opac/details'); >+is(find_and_slurp('UNIMARC', 'OPACXSLTDetailsDisplay'), 'Theme test, language en, UNIMARCslim2OPACDetail', 'not-prog: Used default for non-local marcflavour and opac/details'); >+is(find_and_slurp('MARC21', 'OPACXSLTResultsDisplay'), 'Theme test, language en, MARC21slim2OPACResults', 'not-prog: Used "default" for opac/results'); >-- >1.8.5.3
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Diff
|
Splinter Review
Attachments on
bug 10486
:
19142
|
19143
|
19226
|
19227
|
19680
|
21687
|
21734
|
22084
|
22540
|
23109
|
23112
|
23113
|
24036
|
24206
|
24207
|
24208
|
25235
|
25236
|
25979
|
26002
|
26017
|
26129
|
26804
|
26988
|
27275
|
27276
|
27438
|
27439
|
27440
|
27559
|
27756
|
27781
|
27783
|
27785
|
27786
|
27787
|
27788
|
27789
|
27790