From e15237998d6de3fd4e58042077c986cab0d2a972 Mon Sep 17 00:00:00 2001
From: Demian Katz <demian.katz@villanova.edu>
Date: Wed, 13 Mar 2013 14:49:36 -0400
Subject: [PATCH] Refactored WorldCat search to use new search subsystem.

---
 module/VuFind/config/module.config.php        |   7 +-
 .../Search/Factory/WorldCatBackendFactory.php | 153 +++++++++
 .../src/VuFind/Search/WorldCat/Results.php    |  99 ++----
 .../src/VuFind/View/Helper/Root/WorldCat.php  |   9 +-
 .../VuFindSearch/Backend/SRU/Connector.php}   |   4 +-
 .../VuFindSearch/Backend/WorldCat/Backend.php | 306 ++++++++++++++++++
 .../Backend/WorldCat/Connector.php}           | 104 +++---
 .../Backend/WorldCat/QueryBuilder.php         |  33 +-
 .../Response/XML/RecordCollection.php         | 241 ++++++++++++++
 .../Response/XML/RecordCollectionFactory.php  |  97 ++++++
 themes/root/theme.config.php                  |   2 +-
 11 files changed, 934 insertions(+), 121 deletions(-)
 create mode 100644 module/VuFind/src/VuFind/Search/Factory/WorldCatBackendFactory.php
 rename module/{VuFind/src/VuFind/Connection/SRU.php => VuFindSearch/src/VuFindSearch/Backend/SRU/Connector.php} (98%)
 create mode 100644 module/VuFindSearch/src/VuFindSearch/Backend/WorldCat/Backend.php
 rename module/{VuFind/src/VuFind/Connection/WorldCat.php => VuFindSearch/src/VuFindSearch/Backend/WorldCat/Connector.php} (52%)
 create mode 100644 module/VuFindSearch/src/VuFindSearch/Backend/WorldCat/Response/XML/RecordCollection.php
 create mode 100644 module/VuFindSearch/src/VuFindSearch/Backend/WorldCat/Response/XML/RecordCollectionFactory.php

diff --git a/module/VuFind/config/module.config.php b/module/VuFind/config/module.config.php
index 6831976a851..a1e2ba4581f 100644
--- a/module/VuFind/config/module.config.php
+++ b/module/VuFind/config/module.config.php
@@ -276,12 +276,6 @@ $config = array(
 
                 return $translator;
             },
-            'VuFind\WorldCatConnection' => function ($sm) {
-                return new \VuFind\Connection\WorldCat(
-                    $sm->get('VuFind\Config')->get('config'),
-                    $sm->get('VuFind\Http')->createClient()
-                );
-            },
             'VuFind\WorldCatUtils' => function ($sm) {
                 $config = $sm->get('VuFind\Config')->get('config');
                 $wcId = isset($config->WorldCat->id)
@@ -720,6 +714,7 @@ $config = array(
                     'Solr' => 'VuFind\Search\Factory\SolrDefaultBackendFactory',
                     'SolrAuth' => 'VuFind\Search\Factory\SolrAuthBackendFactory',
                     'SolrReserves' => 'VuFind\Search\Factory\SolrReservesBackendFactory',
+                    'WorldCat' => 'VuFind\Search\Factory\WorldCatBackendFactory',
                 )
             ),
             'session' => array(
diff --git a/module/VuFind/src/VuFind/Search/Factory/WorldCatBackendFactory.php b/module/VuFind/src/VuFind/Search/Factory/WorldCatBackendFactory.php
new file mode 100644
index 00000000000..bdba88b11c0
--- /dev/null
+++ b/module/VuFind/src/VuFind/Search/Factory/WorldCatBackendFactory.php
@@ -0,0 +1,153 @@
+<?php
+
+/**
+ * Factory for WorldCat backends.
+ *
+ * PHP version 5
+ *
+ * Copyright (C) Villanova University 2013.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2,
+ * as published by the Free Software Foundation.
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ * @category VuFind2
+ * @package  Search
+ * @author   David Maus <maus@hab.de>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org   Main Site
+ */
+
+namespace VuFind\Search\Factory;
+
+use VuFindSearch\Backend\BackendInterface;
+use VuFindSearch\Backend\WorldCat\Response\XML\RecordCollectionFactory;
+use VuFindSearch\Backend\WorldCat\QueryBuilder;
+use VuFindSearch\Backend\WorldCat\Connector;
+use VuFindSearch\Backend\WorldCat\Backend;
+
+use Zend\ServiceManager\ServiceLocatorInterface;
+use Zend\ServiceManager\FactoryInterface;
+
+/**
+ * Factory for WorldCat backends.
+ *
+ * @category VuFind2
+ * @package  Search
+ * @author   David Maus <maus@hab.de>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org   Main Site
+ */
+class WorldCatBackendFactory implements FactoryInterface
+{
+    /**
+     * Logger.
+     *
+     * @var Zend\Log\LoggerInterface
+     */
+    protected $logger;
+
+    /**
+     * Superior service manager.
+     *
+     * @var ServiceLocatorInterface
+     */
+    protected $serviceLocator;
+
+    /**
+     * VuFind configuration
+     *
+     * @var \Zend\Config\Config
+     */
+    protected $config;
+
+    /**
+     * Create the backend.
+     *
+     * @param ServiceLocatorInterface $serviceLocator Superior service manager
+     *
+     * @return BackendInterface
+     */
+    public function createService (ServiceLocatorInterface $serviceLocator)
+    {
+        $this->serviceLocator = $serviceLocator;
+        $this->config = $this->serviceLocator->get('VuFind\Config')->get('config');
+        if ($this->serviceLocator->has('VuFind\Logger')) {
+            $this->logger = $this->serviceLocator->get('VuFind\Logger');
+        }
+        $connector = $this->createConnector();
+        $backend   = $this->createBackend($connector);
+        return $backend;
+    }
+
+    /**
+     * Create the WorldCat backend.
+     *
+     * @param Connector $connector  Connector
+     *
+     * @return Backend
+     */
+    protected function createBackend (Connector $connector)
+    {
+        $backend = new Backend($connector, $this->createRecordCollectionFactory());
+        $backend->setLogger($this->logger);
+        $backend->setQueryBuilder($this->createQueryBuilder());
+        return $backend;
+    }
+
+    /**
+     * Create the WorldCat connector.
+     *
+     * @return Connector
+     */
+    protected function createConnector ()
+    {
+        $wsKey = isset($this->config->WorldCat->apiKey)
+            ? $this->config->WorldCat->apiKey : null;
+        $limitCodes = isset($this->config->WorldCat->LimitCodes)
+            ? $this->config->WorldCat->LimitCodes : null;
+        $connector = new Connector(
+            $wsKey, $limitCodes,
+            $this->serviceLocator->get('VuFind\Http')->createClient()
+        );
+        $connector->setLogger($this->logger);
+        return $connector;
+    }
+
+    /**
+     * Create the WorldCat query builder.
+     *
+     * @return QueryBuilder
+     */
+    protected function createQueryBuilder ()
+    {
+        $exclude = isset($this->config->WorldCat->OCLCCode)
+            ? $this->config->WorldCat->OCLCCode : null;
+        return new QueryBuilder($exclude);
+    }
+
+    /**
+     * Create the record collection factory
+     *
+     * @return RecordCollectionFactory
+     */
+    protected function createRecordCollectionFactory ()
+    {
+        $manager = $this->serviceLocator->get('VuFind\RecordDriverPluginManager');
+        $callback = function ($data) use ($manager) {
+            $driver = $manager->get('WorldCat');
+            $driver->setRawData($data);
+            return $driver;
+        };
+        return new RecordCollectionFactory($callback);
+    }
+}
\ No newline at end of file
diff --git a/module/VuFind/src/VuFind/Search/WorldCat/Results.php b/module/VuFind/src/VuFind/Search/WorldCat/Results.php
index 0321537d906..8f180a0fb21 100644
--- a/module/VuFind/src/VuFind/Search/WorldCat/Results.php
+++ b/module/VuFind/src/VuFind/Search/WorldCat/Results.php
@@ -26,8 +26,9 @@
  * @link     http://www.vufind.org  Main Page
  */
 namespace VuFind\Search\WorldCat;
-use VuFind\Exception\RecordMissing as RecordMissingException,
-    VuFind\Search\Base\Results as BaseResults;
+use VuFind\Exception\RecordMissing as RecordMissingException;
+use VuFindSearch\Query\AbstractQuery;
+use VuFindSearch\ParamBag;
 
 /**
  * WorldCat Search Parameters
@@ -38,23 +39,8 @@ use VuFind\Exception\RecordMissing as RecordMissingException,
  * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
  * @link     http://www.vufind.org  Main Page
  */
-class Results extends BaseResults
+class Results extends \VuFind\Search\Base\Results
 {
-    /**
-     * Raw search response:
-     */
-    protected $rawResponse = null;
-
-    /**
-     * Get a connection to the WorldCat API.
-     *
-     * @return \VuFind\Connection\WorldCat
-     */
-    public function getWorldCatConnection()
-    {
-        return $this->getServiceLocator()->get('VuFind\WorldCatConnection');
-    }
-
     /**
      * Support method for performAndProcessSearch -- perform a search based on the
      * parameters passed to the object.
@@ -63,34 +49,14 @@ class Results extends BaseResults
      */
     protected function performSearch()
     {
-        // Collect the search parameters:
-        $config = $this->getServiceLocator()->get('VuFind\Config')->get('config');
-        $wc = $this->getWorldCatConnection();
-        $queryBuilder = new \VuFindSearch\Backend\WorldCat\QueryBuilder();
-        $query = $queryBuilder->build($this->getParams()->getQuery())->get('query');
-        $query = $query[0];
-
-        // Perform the search:
-        $this->rawResponse  = $wc->search(
-            $query, $config->WorldCat->OCLCCode, $this->getParams()->getPage(),
-            $this->getParams()->getLimit(), $this->getParams()->getSort()
-        );
+        $query  = $this->getParams()->getQuery();
+        $limit  = $this->getParams()->getLimit();
+        $offset = $this->getStartRecord() - 1;
+        $params = $this->createBackendParameters($query, $this->getParams());
+        $collection = $this->getSearchService()->search('WorldCat', $query, $offset, $limit, $params);
 
-        // How many results were there?
-        $this->resultTotal = isset($this->rawResponse->numberOfRecords)
-            ? intval($this->rawResponse->numberOfRecords) : 0;
-
-        // Construct record drivers for all the items in the response:
-        $this->results = array();
-        if (isset($this->rawResponse->records->record)
-            && count($this->rawResponse->records->record) > 0
-        ) {
-            foreach ($this->rawResponse->records->record as $current) {
-                $this->results[] = $this->initRecordDriver(
-                    $current->recordData->record->asXML()
-                );
-            }
-        }
+        $this->resultTotal = $collection->getTotal();
+        $this->results = $collection->getRecords();
     }
 
     /**
@@ -103,31 +69,15 @@ class Results extends BaseResults
      */
     public function getRecord($id)
     {
-        $wc = $this->getWorldCatConnection();
-        $record = $wc->getRecord($id);
-        if (empty($record)) {
+        $collection = $this->getSearchService()->retrieve('WorldCat', $id);
+
+        if (count($collection) == 0) {
             throw new RecordMissingException(
                 'Record ' . $id . ' does not exist.'
             );
         }
-        return $this->initRecordDriver($record);
-    }
 
-    /**
-     * Support method for performSearch(): given a WorldCat MARC record,
-     * construct an appropriate record driver object.
-     *
-     * @param string $data Raw record data
-     *
-     * @return \VuFind\RecordDriver\Base
-     */
-    protected function initRecordDriver($data)
-    {
-        $factory = $this->getServiceLocator()
-            ->get('VuFind\RecordDriverPluginManager');
-        $driver = $factory->get('WorldCat');
-        $driver->setRawData($data);
-        return $driver;
+        return current($collection->getRecords());
     }
 
     /**
@@ -143,4 +93,23 @@ class Results extends BaseResults
         // No facets in WorldCat:
         return array();
     }
+
+    /**
+     * Create search backend parameters for advanced features.
+     *
+     * @param Params $params Search parameters
+     *
+     * @return ParamBag
+     * @tag NEW SEARCH
+     */
+    protected function createBackendParameters (AbstractQuery $query, Params $params)
+    {
+        $backendParams = new ParamBag();
+
+        // Sort
+        $sort = $params->getSort();
+        $backendParams->set('sortKeys', empty($sort) ? 'relevance' : $sort);
+
+        return $backendParams;
+    }
 }
\ No newline at end of file
diff --git a/module/VuFind/src/VuFind/View/Helper/Root/WorldCat.php b/module/VuFind/src/VuFind/View/Helper/Root/WorldCat.php
index 57e38e8c460..01b0e8da8eb 100644
--- a/module/VuFind/src/VuFind/View/Helper/Root/WorldCat.php
+++ b/module/VuFind/src/VuFind/View/Helper/Root/WorldCat.php
@@ -26,6 +26,7 @@
  * @link     http://vufind.org/wiki/vufind2:developer_manual Wiki
  */
 namespace VuFind\View\Helper\Root;
+use VuFindSearch\Backend\WorldCat\Connector;
 use Zend\View\Helper\AbstractHelper;
 
 /**
@@ -42,16 +43,16 @@ class WorldCat extends AbstractHelper
     /**
      * WorldCat connection
      *
-     * @var \VuFind\Connection\WorldCat
+     * @var Connector
      */
     protected $wc;
 
     /**
      * Constructor
      *
-     * @param \VuFind\Connection\WorldCat $wc WorldCat connection
+     * @param Connector $wc WorldCat connection
      */
-    public function __construct(\VuFind\Connection\WorldCat $wc)
+    public function __construct(Connector $wc)
     {
         $this->wc = $wc;
     }
@@ -61,7 +62,7 @@ class WorldCat extends AbstractHelper
      *
      * @param string $id Record ID
      *
-     * @return SimpleXMLElement
+     * @return \SimpleXMLElement
      */
     public function getHoldings($id)
     {
diff --git a/module/VuFind/src/VuFind/Connection/SRU.php b/module/VuFindSearch/src/VuFindSearch/Backend/SRU/Connector.php
similarity index 98%
rename from module/VuFind/src/VuFind/Connection/SRU.php
rename to module/VuFindSearch/src/VuFindSearch/Backend/SRU/Connector.php
index cfd00134b6f..22661528112 100644
--- a/module/VuFind/src/VuFind/Connection/SRU.php
+++ b/module/VuFindSearch/src/VuFindSearch/Backend/SRU/Connector.php
@@ -25,7 +25,7 @@
  * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
  * @link     http://vufind.org/wiki/vufind2:developer_manual Wiki
  */
-namespace VuFind\Connection;
+namespace VuFindSearch\Backend\SRU;
 use VuFind\XSLT\Processor as XSLTProcessor, Zend\Log\LoggerInterface;
 
 /**
@@ -37,7 +37,7 @@ use VuFind\XSLT\Processor as XSLTProcessor, Zend\Log\LoggerInterface;
  * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
  * @link     http://vufind.org/wiki/vufind2:developer_manual Wiki
  */
-class SRU implements \Zend\Log\LoggerAwareInterface
+class Connector implements \Zend\Log\LoggerAwareInterface
 {
     /**
      * Logger object for debug info (or false for no debugging).
diff --git a/module/VuFindSearch/src/VuFindSearch/Backend/WorldCat/Backend.php b/module/VuFindSearch/src/VuFindSearch/Backend/WorldCat/Backend.php
new file mode 100644
index 00000000000..f939039c6de
--- /dev/null
+++ b/module/VuFindSearch/src/VuFindSearch/Backend/WorldCat/Backend.php
@@ -0,0 +1,306 @@
+<?php
+
+/**
+ * WorldCat backend.
+ *
+ * PHP version 5
+ *
+ * Copyright (C) Villanova University 2010.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2,
+ * as published by the Free Software Foundation.
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ * @category VuFind2
+ * @package  Search
+ * @author   David Maus <maus@hab.de>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org
+ */
+
+namespace VuFindSearch\Backend\WorldCat;
+
+use VuFindSearch\Query\AbstractQuery;
+
+use VuFindSearch\ParamBag;
+
+use VuFindSearch\Response\RecordCollectionInterface;
+use VuFindSearch\Response\RecordCollectionFactoryInterface;
+
+use VuFindSearch\Backend\BackendInterface;
+
+use Zend\Log\LoggerInterface;
+
+/**
+ * WorldCat backend.
+ *
+ * @category VuFind2
+ * @package  Search
+ * @author   David Maus <maus@hab.de>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org
+ */
+class Backend implements BackendInterface
+{
+    /**
+     * Record collection factory.
+     *
+     * @var RecordCollectionFactoryInterface
+     */
+    protected $collectionFactory;
+
+    /**
+     * Logger, if any.
+     *
+     * @var LoggerInterface
+     */
+    protected $logger;
+
+    /**
+     * Connector.
+     *
+     * @var Connector
+     */
+    protected $connector;
+
+    /**
+     * Backend identifier.
+     *
+     * @var string
+     */
+    protected $identifier;
+
+    /**
+     * Query builder.
+     *
+     * @var QueryBuilder
+     */
+    protected $queryBuilder;
+
+    /**
+     * Constructor.
+     *
+     * @param Connector                        $connector WorldCat connector
+     * @param RecordCollectionFactoryInterface $factory   Record collection factory
+     *
+     * @return void
+     */
+    public function __construct (Connector $connector,
+        RecordCollectionFactoryInterface $factory
+    ) {
+        $this->setRecordCollectionFactory($factory);
+        $this->connector    = $connector;
+        $this->identifier   = null;
+    }
+
+    /**
+     * Set the backend identifier.
+     *
+     * @param string $identifier Backend identifier
+     *
+     * @return void
+     */
+    public function setIdentifier ($identifier)
+    {
+        $this->identifier = $identifier;
+    }
+
+    /**
+     * Perform a search and return record collection.
+     *
+     * @param AbstractQuery $query  Search query
+     * @param integer       $offset Search offset
+     * @param integer       $limit  Search limit
+     * @param ParamBag      $params Search backend parameters
+     *
+     * @return RecordCollectionInterface
+     */
+    public function search (AbstractQuery $query, $offset, $limit,
+        ParamBag $params = null
+    ) {
+        $response   = $this->connector->search(
+            $query, $offset, $limit, $this->getQueryBuilder(), $params
+        );
+        $collection = $this->createRecordCollection($response);
+        $this->injectSourceIdentifier($collection);
+        return $collection;
+    }
+
+    /**
+     * Retrieve a single document.
+     *
+     * @param string   $id     Document identifier
+     * @param ParamBag $params Search backend parameters
+     *
+     * @return RecordCollectionInterface
+     */
+    public function retrieve ($id, ParamBag $params = null)
+    {
+        $response   = $this->connector->getRecord($id, $params);
+        $collection = $this->createRecordCollection($response);
+        $this->injectSourceIdentifier($collection);
+        return $collection;
+    }
+
+    /**
+     * Return similar records.
+     *
+     * @param string   $id     Id of record to compare with
+     * @param ParamBag $params Search backend parameters
+     *
+     * @return RecordCollectionInterface
+     */
+    public function similar ($id, ParamBag $params = null)
+    {
+        // Not supported here -- see \VuFind\Related\WorldCatSimilar for an alternate
+        // approach.
+        return $this->createRecordCollection(
+            array(
+                'docs' => array(),
+                'time' => 0,
+                'total' => 0,
+                'offset' => 0
+            )
+        );
+    }
+
+    /**
+     * Set the Logger.
+     *
+     * @param LoggerInterface $logger Logger
+     *
+     * @return void
+     */
+    public function setLogger (LoggerInterface $logger)
+    {
+        $this->logger = $logger;
+    }
+
+    /**
+     * Return query builder.
+     *
+     * Lazy loads an empty QueryBuilder if none was set.
+     *
+     * @return QueryBuilder
+     */
+    public function getQueryBuilder ()
+    {
+        if (!$this->queryBuilder) {
+            $this->queryBuilder = new QueryBuilder();
+        }
+        return $this->queryBuilder;
+    }
+
+    /**
+     * Set the query builder.
+     *
+     * @param QueryBuilder $queryBuilder Query builder
+     *
+     * @return void
+     *
+     * @todo Typehint QueryBuilderInterface
+     */
+    public function setQueryBuilder (QueryBuilder $queryBuilder)
+    {
+        $this->queryBuilder = $queryBuilder;
+    }
+
+    /**
+     * Return backend identifier.
+     *
+     * @return string
+     */
+    public function getIdentifier ()
+    {
+        return $this->identifier;
+    }
+
+    /**
+     * Set the record collection factory.
+     *
+     * @param RecordCollectionFactoryInterface $factory Factory
+     *
+     * @return void
+     */
+    public function setRecordCollectionFactory (RecordCollectionFactoryInterface $factory)
+    {
+        $this->collectionFactory = $factory;
+    }
+
+    /**
+     * Return the record collection factory.
+     *
+     * Lazy loads a generic collection factory.
+     *
+     * @return RecordCollectionFactoryInterface
+     */
+    public function getRecordCollectionFactory ()
+    {
+        return $this->collectionFactory;
+    }
+
+    /**
+     * Return the WorldCat connector.
+     *
+     * @return Connector
+     */
+    public function getConnector ()
+    {
+        return $this->connector;
+    }
+
+    /// Internal API
+
+    /**
+     * Inject source identifier in record collection and all contained records.
+     *
+     * @param ResponseInterface $response Response
+     *
+     * @return void
+     */
+    protected function injectSourceIdentifier (RecordCollectionInterface $response)
+    {
+        $response->setSourceIdentifier($this->identifier);
+        foreach ($response as $record) {
+            $record->setSourceIdentifier($this->identifier);
+        }
+        return $response;
+    }
+
+    /**
+     * Send a message to the logger.
+     *
+     * @param string $level   Log level
+     * @param string $message Log message
+     * @param array  $context Log context
+     *
+     * @return void
+     */
+    protected function log ($level, $message, array $context = array())
+    {
+        if ($this->logger) {
+            $this->logger->$level($message, $context);
+        }
+    }
+
+    /**
+     * Create record collection.
+     *
+     * @param array $records Records to process
+     *
+     * @return RecordCollectionInterface
+     */
+    protected function createRecordCollection ($records)
+    {
+        return $this->getRecordCollectionFactory()->factory($records);
+    }
+}
\ No newline at end of file
diff --git a/module/VuFind/src/VuFind/Connection/WorldCat.php b/module/VuFindSearch/src/VuFindSearch/Backend/WorldCat/Connector.php
similarity index 52%
rename from module/VuFind/src/VuFind/Connection/WorldCat.php
rename to module/VuFindSearch/src/VuFindSearch/Backend/WorldCat/Connector.php
index f148ddeae80..0336b5ffc24 100644
--- a/module/VuFind/src/VuFind/Connection/WorldCat.php
+++ b/module/VuFindSearch/src/VuFindSearch/Backend/WorldCat/Connector.php
@@ -26,7 +26,9 @@
  * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
  * @link     http://vufind.org/wiki/vufind2:developer_manual Wiki
  */
-namespace VuFind\Connection;
+namespace VuFindSearch\Backend\WorldCat;
+use VuFindSearch\Query\AbstractQuery;
+use VuFindSearch\ParamBag;
 
 /**
  * WorldCat SRU Search Interface
@@ -37,7 +39,7 @@ namespace VuFind\Connection;
  * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
  * @link     http://vufind.org/wiki/vufind2:developer_manual Wiki
  */
-class WorldCat extends SRU
+class Connector extends \VuFindSearch\Backend\SRU\Connector
 {
     /**
      * OCLC API key
@@ -56,19 +58,16 @@ class WorldCat extends SRU
     /**
      * Constructor
      *
-     * @param \Zend\Config\Config $config VuFind configuration
-     * @param \Zend\Http\Client   $client An HTTP client object
+     * @param string            $wsKey      Web services key
+     * @param string            $limitCodes OCLC codes to use for limiting
+     * @param \Zend\Http\Client $client     An HTTP client object
      */
-    public function __construct(\Zend\Config\Config $config,
-        \Zend\Http\Client $client
-    ) {
+    public function __construct($wsKey, $limitCodes, \Zend\Http\Client $client) {
         parent::__construct(
             'http://www.worldcat.org/webservices/catalog/search/sru', $client
         );
-        $this->wskey = isset($config->WorldCat->apiKey)
-            ? $config->WorldCat->apiKey : null;
-        $this->limitCodes = isset($config->WorldCat->LimitCodes)
-            ? $config->WorldCat->LimitCodes : null;
+        $this->wskey = $wsKey;
+        $this->limitCodes = $limitCodes;
     }
 
     /**
@@ -95,58 +94,83 @@ class WorldCat extends SRU
     /**
      * Retrieve a specific record.
      *
-     * @param string $id Record ID to retrieve
+     * @param string   $id     Record ID to retrieve
+     * @param ParamBag $params Parameters
      *
      * @throws \Exception
      * @return string    MARC XML
      */
-    public function getRecord($id)
+    public function getRecord($id, ParamBag $params = null)
     {
+        $params = $params ?: new ParamBag();
+        $params->set('servicelevel', 'full');
+        $params->set('wskey', $this->wskey);
+
         $this->client->resetParameters();
         $uri = 'http://www.worldcat.org/webservices/catalog/content/' . $id;
-        $uri .= "?wskey={$this->wskey}&servicelevel=full";
+        $uri .= '?' . implode('&', $params->request());
         $this->client->setUri($uri);
         $this->debug('Connect: ' . $uri);
+        $start = microtime(true);
         $result = $this->client->setMethod('POST')->send();
+        $length = microtime(true) - $start;
         $this->checkForHttpError($result);
 
-        return $result->getBody();
+        // Check for error message in response:
+        $body = $result->getBody();
+        $xml = simplexml_load_string($body);
+        $error = isset($xml->diagnostic);
+
+        return array(
+            'docs' => $error ? array() : array($body),
+            'offset' => 0,
+            'total' => $error ? 0 : 1,
+            'time' => $length
+        );
     }
 
     /**
-     * Search
+     * Execute a search.
      *
-     * @param string $query    The search query
-     * @param string $oclcCode An OCLC code to exclude from results
-     * @param int    $page     The page of records to start with
-     * @param int    $limit    The number of records to return per page
-     * @param string $sort     The value to be used by for sorting
+     * @param AbstractQuery $query        Search query
+     * @param integer       $offset       Search offset
+     * @param integer       $limit        Search limit
+     * @param QueryBuilder  $queryBuilder Query builder
+     * @param ParamBag      $params       Parameters
      *
-     * @throws \Exception
-     * @return array          An array of query results
+     * @return array
      */
-    public function search($query, $oclcCode = null, $page = 1, $limit = 10,
-        $sort = null
+    public function search (AbstractQuery $query, $offset, $limit,
+        QueryBuilder $queryBuilder, ParamBag $params = null
     ) {
-        // Exclude current library from results
-        if ($oclcCode) {
-            $query .= ' not srw.li all "' . $oclcCode . '"';
-        }
-
-        // Submit query
-        $start = ($page-1) * $limit;
-        $params = array('query' => $query,
-                        'startRecord' => $start,
-                        'maximumRecords' => $limit,
-                        'sortKeys' => empty($sort) ? 'relevance' : $sort,
-                        'servicelevel' => 'full',
-                        'wskey' => $this->wskey);
+        $params = $params ?: new ParamBag();
+        $params->set('startRecord', $offset);
+        $params->set('maximumRecords', $limit);
+        $params->set('servicelevel', 'full');
+        $params->set('wskey', $this->wskey);
+        $params->mergeWith($queryBuilder->build($query));
 
         // Establish a limitation on searching by OCLC Codes
         if (!empty($this->limitCodes)) {
-            $params['oclcsymbol'] = $this->limitCodes;
+            $params->set('oclcsymbol', $this->limitCodes);
         }
 
-        return simplexml_load_string($this->call('POST', $params, false));
+        $start = microtime(true);
+        $response = $this->call('POST', $params->getArrayCopy(), false);
+        $length = microtime(true) - $start;
+
+        $xml = simplexml_load_string($response);
+        $docs = isset($xml->records->record) ? $xml->records->record : array();
+        $finalDocs = array();
+        foreach ($docs as $doc) {
+            $finalDocs[] = $doc->recordData->asXML();
+        }
+        return array(
+            'docs' => $finalDocs,
+            'offset' => $offset,
+            'total' => isset($xml->numberOfRecords) ? (int)$xml->numberOfRecords : 0,
+            'time' => $length
+        );
     }
+
 }
diff --git a/module/VuFindSearch/src/VuFindSearch/Backend/WorldCat/QueryBuilder.php b/module/VuFindSearch/src/VuFindSearch/Backend/WorldCat/QueryBuilder.php
index 6db74ad4658..8eb765ea922 100644
--- a/module/VuFindSearch/src/VuFindSearch/Backend/WorldCat/QueryBuilder.php
+++ b/module/VuFindSearch/src/VuFindSearch/Backend/WorldCat/QueryBuilder.php
@@ -50,8 +50,25 @@ use VuFindSearch\ParamBag;
  */
 class QueryBuilder
 {
+    /**
+     * OCLC code to exclude from results
+     *
+     * @var string
+     */
+    protected $oclcCodeToExclude;
+
     /// Public API
 
+    /**
+     * Constructor
+     *
+     * @param string $exclude OCLC code to exclude from results
+     */
+    public function __construct($exclude = null)
+    {
+        $this->oclcCodeToExclude = $exclude;
+    }
+
     /**
      * Return WorldCat search parameters based on a user query and params.
      *
@@ -61,8 +78,17 @@ class QueryBuilder
      */
     public function build (AbstractQuery $query)
     {
+        // Build base query
+        $queryStr = $this->abstractQueryToString($query);
+
+        // Exclude current library from results (if applicable)
+        if (null !== $this->oclcCodeToExclude) {
+            $queryStr .= ' not srw.li all "' . $this->oclcCodeToExclude . '"';
+        }
+
+        // Send back results
         $params = new ParamBag();
-        $params->set('query', $this->abstractQueryToString($query));
+        $params->set('query', $queryStr);
         return $params;
     }
 
@@ -141,11 +167,12 @@ class QueryBuilder
     protected function queryToString(Query $query)
     {
         // Clean and validate input:
-        $lookfor = str_replace('"', '', $query->getString());
         $index = $query->getHandler();
         if (empty($index)) {
-            $index = 'srw.kw';
+            // No handler?  Just accept query string as-is; no modifications needed.
+            return $query->getString();
         }
+        $lookfor = str_replace('"', '', $query->getString());
 
         // The index may contain multiple parts -- we want to search all listed index
         // fields:
diff --git a/module/VuFindSearch/src/VuFindSearch/Backend/WorldCat/Response/XML/RecordCollection.php b/module/VuFindSearch/src/VuFindSearch/Backend/WorldCat/Response/XML/RecordCollection.php
new file mode 100644
index 00000000000..a23cd499269
--- /dev/null
+++ b/module/VuFindSearch/src/VuFindSearch/Backend/WorldCat/Response/XML/RecordCollection.php
@@ -0,0 +1,241 @@
+<?php
+
+/**
+ * WorldCat record collection.
+ *
+ * PHP version 5
+ *
+ * Copyright (C) Villanova University 2010.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2,
+ * as published by the Free Software Foundation.
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ * @category VuFind2
+ * @package  Search
+ * @author   David Maus <maus@hab.de>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org
+ */
+
+namespace VuFindSearch\Backend\WorldCat\Response\XML;
+
+use VuFindSearch\Response\RecordCollectionInterface;
+use VuFindSearch\Response\RecordInterface;
+
+use VuFindSearch\Exception\RuntimeException;
+
+/**
+ * WorldCat record collection.
+ *
+ * @category VuFind2
+ * @package  Search
+ * @author   David Maus <maus@hab.de>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org
+ */
+class RecordCollection implements RecordCollectionInterface
+{
+    /**
+     * Raw response.
+     *
+     * @var array
+     */
+    protected $response;
+
+    /**
+     * Response records.
+     *
+     * @var array
+     */
+    protected $records;
+
+    /**
+     * Constructor.
+     *
+     * @param array $response WorldCat response
+     * @param int   $offset   Starting offset
+     * @param int   $time     Search execution time (in MS)
+     * @param int   $total    Total record count (optional)
+     *
+     * @return void
+     */
+    public function __construct (array $response)
+    {
+        $this->response = $response;
+        $this->records  = array();
+        $this->offset = $response['offset'];
+        $this->rewind();
+    }
+
+    /**
+     * Return total number of records found.
+     *
+     * @return int
+     */
+    public function getTotal ()
+    {
+        return $this->response['total'];
+    }
+
+    /**
+     * Return query time in milli-seconds.
+     *
+     * @return float
+     */
+    public function getQueryTime ()
+    {
+        return $this->response['time'];
+    }
+
+    /**
+     * Return facet information.
+     *
+     * @return array
+     */
+    public function getFacets ()
+    {
+        return array(); // not supported by WorldCat
+    }
+
+    /**
+     * Return records.
+     *
+     * @return array
+     */
+    public function getRecords ()
+    {
+        return $this->records;
+    }
+
+    /**
+     * Return offset in the total search result set.
+     *
+     * @return int
+     */
+    public function getOffset ()
+    {
+        return $this->offset;
+    }
+
+    /**
+     * Return first record in response.
+     *
+     * @return RecordInterface|null
+     */
+    public function first ()
+    {
+        return isset($this->records[$this->offset]) ? $this->records[$this->offset] : null;
+    }
+
+    /**
+     * Set the source backend identifier.
+     *
+     * @param string $identifier Backend identifier
+     *
+     * @return void
+     */
+    public function setSourceIdentifier ($identifier)
+    {
+        $this->source = $identifier;
+    }
+
+    /**
+     * Return the source backend identifier.
+     *
+     * @return string
+     */
+    public function getSourceIdentifier ()
+    {
+        return $this->source;
+    }
+
+    /**
+     * Add a record to the collection.
+     *
+     * @param RecordInterface $record Record to add
+     *
+     * @return void
+     */
+    public function add (RecordInterface $record)
+    {
+        if (!in_array($record, $this->records, true)) {
+            $this->records[$this->pointer] = $record;
+            $this->next();
+        }
+    }
+
+    /// Iterator interface
+
+    /**
+     * Return true if current collection index is valid.
+     *
+     * @return boolean
+     */
+    public function valid ()
+    {
+        return isset($this->records[$this->pointer]);
+    }
+
+    /**
+     * Return record at current collection index.
+     *
+     * @return RecordInterface
+     */
+    public function current ()
+    {
+        return $this->records[$this->pointer];
+    }
+
+    /**
+     * Rewind collection index.
+     *
+     * @return void
+     */
+    public function rewind ()
+    {
+        $this->pointer = $this->offset;
+    }
+
+    /**
+     * Move to next collection index.
+     *
+     * @return void
+     */
+    public function next ()
+    {
+        $this->pointer++;
+    }
+
+    /**
+     * Return current collection index.
+     *
+     * @return integer
+     */
+    public function key ()
+    {
+        return $this->pointer;
+    }
+
+    /// Countable interface
+
+    /**
+     * Return number of records in collection.
+     *
+     * @return integer
+     */
+    public function count ()
+    {
+        return count($this->records);
+    }
+
+}
\ No newline at end of file
diff --git a/module/VuFindSearch/src/VuFindSearch/Backend/WorldCat/Response/XML/RecordCollectionFactory.php b/module/VuFindSearch/src/VuFindSearch/Backend/WorldCat/Response/XML/RecordCollectionFactory.php
new file mode 100644
index 00000000000..e7e310bc3bc
--- /dev/null
+++ b/module/VuFindSearch/src/VuFindSearch/Backend/WorldCat/Response/XML/RecordCollectionFactory.php
@@ -0,0 +1,97 @@
+<?php
+
+/**
+ * Simple XML-based factory for record collection.
+ *
+ * PHP version 5
+ *
+ * Copyright (C) Villanova University 2010.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2,
+ * as published by the Free Software Foundation.
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ * @category VuFind2
+ * @package  Search
+ * @author   David Maus <maus@hab.de>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org
+ */
+
+namespace VuFindSearch\Backend\WorldCat\Response\XML;
+
+use VuFindSearch\Response\RecordCollectionFactoryInterface;
+use VuFindSearch\Exception\InvalidArgumentException;
+
+/**
+ * Simple XML-based factory for record collection.
+ *
+ * @category VuFind2
+ * @package  Search
+ * @author   David Maus <maus@hab.de>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org
+ */
+class RecordCollectionFactory implements RecordCollectionFactoryInterface
+{
+    /**
+     * Factory to turn data into a record object.
+     *
+     * @var Callable
+     */
+    protected $recordFactory;
+
+    /**
+     * Class of collection.
+     *
+     * @var string
+     */
+    protected $collectionClass;
+
+    /**
+     * Constructor.
+     *
+     * @param string $recordClass     Class of collection records
+     * @param string $collectionClass Class of collection
+     *
+     * @return void
+     */
+    public function __construct ($recordFactory = null, $collectionClass = null) {
+        if (!is_callable($recordFactory)) {
+            throw new InvalidArgumentException('Record factory must be callable.');
+        }
+        $this->recordFactory = $recordFactory;
+        $this->collectionClass = (null === $collectionClass)
+            ? 'VuFindSearch\Backend\WorldCat\Response\XML\RecordCollection'
+            : $collectionClass;
+    }
+
+    /**
+     * Return record collection.
+     *
+     * @param array $response Collection of XML documents
+     *
+     * @return RecordCollection
+     */
+    public function factory ($response)
+    {
+        if (!is_array($response)) {
+            throw new InvalidArgumentException(sprintf('Unexpected type of value: Expected array, got %s', gettype($response)));
+        }
+        $collection = new $this->collectionClass($response);
+        foreach ($response['docs'] as $doc) {
+            $collection->add(call_user_func($this->recordFactory, $doc));
+        }
+        return $collection;
+    }
+
+}
\ No newline at end of file
diff --git a/themes/root/theme.config.php b/themes/root/theme.config.php
index 3193669a6c6..65702beebbd 100644
--- a/themes/root/theme.config.php
+++ b/themes/root/theme.config.php
@@ -116,7 +116,7 @@ return array(
             },
             'worldcat' => function ($sm) {
                 return new \VuFind\View\Helper\Root\WorldCat(
-                    $sm->getServiceLocator()->get('VuFind\WorldCatConnection')
+                    $sm->getServiceLocator()->get('VuFind\Search')->getBackend('WorldCat')->getConnector()
                 );
             }
         ),
-- 
GitLab